From 73df946d56c74384511a194dd01dbe099584fd1a Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 28 Apr 2024 15:14:23 +0200 Subject: Adding upstream version 1.16.10. Signed-off-by: Daniel Baumann --- src/cmd/compile/internal/gc/main.go | 1610 +++++++++++++++++++++++++++++++++++ 1 file changed, 1610 insertions(+) create mode 100644 src/cmd/compile/internal/gc/main.go (limited to 'src/cmd/compile/internal/gc/main.go') diff --git a/src/cmd/compile/internal/gc/main.go b/src/cmd/compile/internal/gc/main.go new file mode 100644 index 0000000..a6963a3 --- /dev/null +++ b/src/cmd/compile/internal/gc/main.go @@ -0,0 +1,1610 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:generate go run mkbuiltin.go + +package gc + +import ( + "bufio" + "bytes" + "cmd/compile/internal/logopt" + "cmd/compile/internal/ssa" + "cmd/compile/internal/types" + "cmd/internal/bio" + "cmd/internal/dwarf" + "cmd/internal/goobj" + "cmd/internal/obj" + "cmd/internal/objabi" + "cmd/internal/src" + "cmd/internal/sys" + "flag" + "fmt" + "internal/goversion" + "io" + "io/ioutil" + "log" + "os" + "path" + "regexp" + "runtime" + "sort" + "strconv" + "strings" +) + +var ( + buildid string + spectre string + spectreIndex bool +) + +var ( + Debug_append int + Debug_checkptr int + Debug_closure int + Debug_compilelater int + debug_dclstack int + Debug_dumpptrs int + Debug_libfuzzer int + Debug_panic int + Debug_slice int + Debug_vlog bool + Debug_wb int + Debug_pctab string + Debug_locationlist int + Debug_typecheckinl int + Debug_gendwarfinl int + Debug_softfloat int + Debug_defer int +) + +// Debug arguments. +// These can be specified with the -d flag, as in "-d nil" +// to set the debug_checknil variable. +// Multiple options can be comma-separated. +// Each option accepts an optional argument, as in "gcprog=2" +var debugtab = []struct { + name string + help string + val interface{} // must be *int or *string +}{ + {"append", "print information about append compilation", &Debug_append}, + {"checkptr", "instrument unsafe pointer conversions", &Debug_checkptr}, + {"closure", "print information about closure compilation", &Debug_closure}, + {"compilelater", "compile functions as late as possible", &Debug_compilelater}, + {"disablenil", "disable nil checks", &disable_checknil}, + {"dclstack", "run internal dclstack check", &debug_dclstack}, + {"dumpptrs", "show Node pointer values in Dump/dumplist output", &Debug_dumpptrs}, + {"gcprog", "print dump of GC programs", &Debug_gcprog}, + {"libfuzzer", "coverage instrumentation for libfuzzer", &Debug_libfuzzer}, + {"nil", "print information about nil checks", &Debug_checknil}, + {"panic", "do not hide any compiler panic", &Debug_panic}, + {"slice", "print information about slice compilation", &Debug_slice}, + {"typeassert", "print information about type assertion inlining", &Debug_typeassert}, + {"wb", "print information about write barriers", &Debug_wb}, + {"export", "print export data", &Debug_export}, + {"pctab", "print named pc-value table", &Debug_pctab}, + {"locationlists", "print information about DWARF location list creation", &Debug_locationlist}, + {"typecheckinl", "eager typechecking of inline function bodies", &Debug_typecheckinl}, + {"dwarfinl", "print information about DWARF inlined function creation", &Debug_gendwarfinl}, + {"softfloat", "force compiler to emit soft-float code", &Debug_softfloat}, + {"defer", "print information about defer compilation", &Debug_defer}, + {"fieldtrack", "enable fieldtracking", &objabi.Fieldtrack_enabled}, +} + +const debugHelpHeader = `usage: -d arg[,arg]* and arg is [=] + + is one of: + +` + +const debugHelpFooter = ` + is key-specific. + +Key "checkptr" supports values: + "0": instrumentation disabled + "1": conversions involving unsafe.Pointer are instrumented + "2": conversions to unsafe.Pointer force heap allocation + +Key "pctab" supports values: + "pctospadj", "pctofile", "pctoline", "pctoinline", "pctopcdata" +` + +func usage() { + fmt.Fprintf(os.Stderr, "usage: compile [options] file.go...\n") + objabi.Flagprint(os.Stderr) + Exit(2) +} + +func hidePanic() { + if Debug_panic == 0 && nsavederrors+nerrors > 0 { + // If we've already complained about things + // in the program, don't bother complaining + // about a panic too; let the user clean up + // the code and try again. + if err := recover(); err != nil { + errorexit() + } + } +} + +// supportsDynlink reports whether or not the code generator for the given +// architecture supports the -shared and -dynlink flags. +func supportsDynlink(arch *sys.Arch) bool { + return arch.InFamily(sys.AMD64, sys.ARM, sys.ARM64, sys.I386, sys.PPC64, sys.RISCV64, sys.S390X) +} + +// timing data for compiler phases +var timings Timings +var benchfile string + +var nowritebarrierrecCheck *nowritebarrierrecChecker + +// Main parses flags and Go source files specified in the command-line +// arguments, type-checks the parsed Go package, compiles functions to machine +// code, and finally writes the compiled package definition to disk. +func Main(archInit func(*Arch)) { + timings.Start("fe", "init") + + defer hidePanic() + + archInit(&thearch) + + Ctxt = obj.Linknew(thearch.LinkArch) + Ctxt.DiagFunc = yyerror + Ctxt.DiagFlush = flusherrors + Ctxt.Bso = bufio.NewWriter(os.Stdout) + + // UseBASEntries is preferred because it shaves about 2% off build time, but LLDB, dsymutil, and dwarfdump + // on Darwin don't support it properly, especially since macOS 10.14 (Mojave). This is exposed as a flag + // to allow testing with LLVM tools on Linux, and to help with reporting this bug to the LLVM project. + // See bugs 31188 and 21945 (CLs 170638, 98075, 72371). + Ctxt.UseBASEntries = Ctxt.Headtype != objabi.Hdarwin + + localpkg = types.NewPkg("", "") + localpkg.Prefix = "\"\"" + + // We won't know localpkg's height until after import + // processing. In the mean time, set to MaxPkgHeight to ensure + // height comparisons at least work until then. + localpkg.Height = types.MaxPkgHeight + + // pseudo-package, for scoping + builtinpkg = types.NewPkg("go.builtin", "") // TODO(gri) name this package go.builtin? + builtinpkg.Prefix = "go.builtin" // not go%2ebuiltin + + // pseudo-package, accessed by import "unsafe" + unsafepkg = types.NewPkg("unsafe", "unsafe") + + // Pseudo-package that contains the compiler's builtin + // declarations for package runtime. These are declared in a + // separate package to avoid conflicts with package runtime's + // actual declarations, which may differ intentionally but + // insignificantly. + Runtimepkg = types.NewPkg("go.runtime", "runtime") + Runtimepkg.Prefix = "runtime" + + // pseudo-packages used in symbol tables + itabpkg = types.NewPkg("go.itab", "go.itab") + itabpkg.Prefix = "go.itab" // not go%2eitab + + itablinkpkg = types.NewPkg("go.itablink", "go.itablink") + itablinkpkg.Prefix = "go.itablink" // not go%2eitablink + + trackpkg = types.NewPkg("go.track", "go.track") + trackpkg.Prefix = "go.track" // not go%2etrack + + // pseudo-package used for map zero values + mappkg = types.NewPkg("go.map", "go.map") + mappkg.Prefix = "go.map" + + // pseudo-package used for methods with anonymous receivers + gopkg = types.NewPkg("go", "") + + Wasm := objabi.GOARCH == "wasm" + + // Whether the limit for stack-allocated objects is much smaller than normal. + // This can be helpful for diagnosing certain causes of GC latency. See #27732. + smallFrames := false + jsonLogOpt := "" + + flag.BoolVar(&compiling_runtime, "+", false, "compiling runtime") + flag.BoolVar(&compiling_std, "std", false, "compiling standard library") + flag.StringVar(&localimport, "D", "", "set relative `path` for local imports") + + objabi.Flagcount("%", "debug non-static initializers", &Debug.P) + objabi.Flagcount("B", "disable bounds checking", &Debug.B) + objabi.Flagcount("C", "disable printing of columns in error messages", &Debug.C) + objabi.Flagcount("E", "debug symbol export", &Debug.E) + objabi.Flagcount("K", "debug missing line numbers", &Debug.K) + objabi.Flagcount("L", "show full file names in error messages", &Debug.L) + objabi.Flagcount("N", "disable optimizations", &Debug.N) + objabi.Flagcount("S", "print assembly listing", &Debug.S) + objabi.Flagcount("W", "debug parse tree after type checking", &Debug.W) + objabi.Flagcount("e", "no limit on number of errors reported", &Debug.e) + objabi.Flagcount("h", "halt on error", &Debug.h) + objabi.Flagcount("j", "debug runtime-initialized variables", &Debug.j) + objabi.Flagcount("l", "disable inlining", &Debug.l) + objabi.Flagcount("m", "print optimization decisions", &Debug.m) + objabi.Flagcount("r", "debug generated wrappers", &Debug.r) + objabi.Flagcount("w", "debug type checking", &Debug.w) + + objabi.Flagfn1("I", "add `directory` to import search path", addidir) + objabi.AddVersionFlag() // -V + flag.StringVar(&asmhdr, "asmhdr", "", "write assembly header to `file`") + flag.StringVar(&buildid, "buildid", "", "record `id` as the build id in the export metadata") + flag.IntVar(&nBackendWorkers, "c", 1, "concurrency during compilation, 1 means no concurrency") + flag.BoolVar(&pure_go, "complete", false, "compiling complete package (no C or assembly)") + flag.StringVar(&debugstr, "d", "", "print debug information about items in `list`; try -d help") + flag.BoolVar(&flagDWARF, "dwarf", !Wasm, "generate DWARF symbols") + flag.BoolVar(&Ctxt.Flag_locationlists, "dwarflocationlists", true, "add location lists to DWARF in optimized mode") + flag.IntVar(&genDwarfInline, "gendwarfinl", 2, "generate DWARF inline info records") + objabi.Flagfn1("embedcfg", "read go:embed configuration from `file`", readEmbedCfg) + objabi.Flagfn1("importmap", "add `definition` of the form source=actual to import map", addImportMap) + objabi.Flagfn1("importcfg", "read import configuration from `file`", readImportCfg) + flag.StringVar(&flag_installsuffix, "installsuffix", "", "set pkg directory `suffix`") + flag.StringVar(&flag_lang, "lang", "", "release to compile for") + flag.StringVar(&linkobj, "linkobj", "", "write linker-specific object to `file`") + objabi.Flagcount("live", "debug liveness analysis", &debuglive) + if sys.MSanSupported(objabi.GOOS, objabi.GOARCH) { + flag.BoolVar(&flag_msan, "msan", false, "build code compatible with C/C++ memory sanitizer") + } + flag.BoolVar(&nolocalimports, "nolocalimports", false, "reject local (relative) imports") + flag.StringVar(&outfile, "o", "", "write output to `file`") + flag.StringVar(&myimportpath, "p", "", "set expected package import `path`") + flag.BoolVar(&writearchive, "pack", false, "write to file.a instead of file.o") + if sys.RaceDetectorSupported(objabi.GOOS, objabi.GOARCH) { + flag.BoolVar(&flag_race, "race", false, "enable race detector") + } + flag.StringVar(&spectre, "spectre", spectre, "enable spectre mitigations in `list` (all, index, ret)") + if enableTrace { + flag.BoolVar(&trace, "t", false, "trace type-checking") + } + flag.StringVar(&pathPrefix, "trimpath", "", "remove `prefix` from recorded source file paths") + flag.BoolVar(&Debug_vlog, "v", false, "increase debug verbosity") + flag.BoolVar(&use_writebarrier, "wb", true, "enable write barrier") + var flag_shared bool + var flag_dynlink bool + if supportsDynlink(thearch.LinkArch.Arch) { + flag.BoolVar(&flag_shared, "shared", false, "generate code that can be linked into a shared library") + flag.BoolVar(&flag_dynlink, "dynlink", false, "support references to Go symbols defined in other shared libraries") + flag.BoolVar(&Ctxt.Flag_linkshared, "linkshared", false, "generate code that will be linked against Go shared libraries") + } + flag.StringVar(&cpuprofile, "cpuprofile", "", "write cpu profile to `file`") + flag.StringVar(&memprofile, "memprofile", "", "write memory profile to `file`") + flag.Int64Var(&memprofilerate, "memprofilerate", 0, "set runtime.MemProfileRate to `rate`") + var goversion string + flag.StringVar(&goversion, "goversion", "", "required version of the runtime") + var symabisPath string + flag.StringVar(&symabisPath, "symabis", "", "read symbol ABIs from `file`") + flag.StringVar(&traceprofile, "traceprofile", "", "write an execution trace to `file`") + flag.StringVar(&blockprofile, "blockprofile", "", "write block profile to `file`") + flag.StringVar(&mutexprofile, "mutexprofile", "", "write mutex profile to `file`") + flag.StringVar(&benchfile, "bench", "", "append benchmark times to `file`") + flag.BoolVar(&smallFrames, "smallframes", false, "reduce the size limit for stack allocated objects") + flag.BoolVar(&Ctxt.UseBASEntries, "dwarfbasentries", Ctxt.UseBASEntries, "use base address selection entries in DWARF") + flag.StringVar(&jsonLogOpt, "json", "", "version,destination for JSON compiler/optimizer logging") + + objabi.Flagparse(usage) + + Ctxt.Pkgpath = myimportpath + + for _, f := range strings.Split(spectre, ",") { + f = strings.TrimSpace(f) + switch f { + default: + log.Fatalf("unknown setting -spectre=%s", f) + case "": + // nothing + case "all": + spectreIndex = true + Ctxt.Retpoline = true + case "index": + spectreIndex = true + case "ret": + Ctxt.Retpoline = true + } + } + + if spectreIndex { + switch objabi.GOARCH { + case "amd64": + // ok + default: + log.Fatalf("GOARCH=%s does not support -spectre=index", objabi.GOARCH) + } + } + + // Record flags that affect the build result. (And don't + // record flags that don't, since that would cause spurious + // changes in the binary.) + recordFlags("B", "N", "l", "msan", "race", "shared", "dynlink", "dwarflocationlists", "dwarfbasentries", "smallframes", "spectre") + + if smallFrames { + maxStackVarSize = 128 * 1024 + maxImplicitStackVarSize = 16 * 1024 + } + + Ctxt.Flag_shared = flag_dynlink || flag_shared + Ctxt.Flag_dynlink = flag_dynlink + Ctxt.Flag_optimize = Debug.N == 0 + + Ctxt.Debugasm = Debug.S + Ctxt.Debugvlog = Debug_vlog + if flagDWARF { + Ctxt.DebugInfo = debuginfo + Ctxt.GenAbstractFunc = genAbstractFunc + Ctxt.DwFixups = obj.NewDwarfFixupTable(Ctxt) + } else { + // turn off inline generation if no dwarf at all + genDwarfInline = 0 + Ctxt.Flag_locationlists = false + } + + if flag.NArg() < 1 && debugstr != "help" && debugstr != "ssa/help" { + usage() + } + + if goversion != "" && goversion != runtime.Version() { + fmt.Printf("compile: version %q does not match go tool version %q\n", runtime.Version(), goversion) + Exit(2) + } + + checkLang() + + if symabisPath != "" { + readSymABIs(symabisPath, myimportpath) + } + + thearch.LinkArch.Init(Ctxt) + + if outfile == "" { + p := flag.Arg(0) + if i := strings.LastIndex(p, "/"); i >= 0 { + p = p[i+1:] + } + if runtime.GOOS == "windows" { + if i := strings.LastIndex(p, `\`); i >= 0 { + p = p[i+1:] + } + } + if i := strings.LastIndex(p, "."); i >= 0 { + p = p[:i] + } + suffix := ".o" + if writearchive { + suffix = ".a" + } + outfile = p + suffix + } + + startProfile() + + if flag_race && flag_msan { + log.Fatal("cannot use both -race and -msan") + } + if flag_race || flag_msan { + // -race and -msan imply -d=checkptr for now. + Debug_checkptr = 1 + } + if ispkgin(omit_pkgs) { + flag_race = false + flag_msan = false + } + if flag_race { + racepkg = types.NewPkg("runtime/race", "") + } + if flag_msan { + msanpkg = types.NewPkg("runtime/msan", "") + } + if flag_race || flag_msan { + instrumenting = true + } + + if compiling_runtime && Debug.N != 0 { + log.Fatal("cannot disable optimizations while compiling runtime") + } + if nBackendWorkers < 1 { + log.Fatalf("-c must be at least 1, got %d", nBackendWorkers) + } + if nBackendWorkers > 1 && !concurrentBackendAllowed() { + log.Fatalf("cannot use concurrent backend compilation with provided flags; invoked as %v", os.Args) + } + if Ctxt.Flag_locationlists && len(Ctxt.Arch.DWARFRegisters) == 0 { + log.Fatalf("location lists requested but register mapping not available on %v", Ctxt.Arch.Name) + } + + // parse -d argument + if debugstr != "" { + Split: + for _, name := range strings.Split(debugstr, ",") { + if name == "" { + continue + } + // display help about the -d option itself and quit + if name == "help" { + fmt.Print(debugHelpHeader) + maxLen := len("ssa/help") + for _, t := range debugtab { + if len(t.name) > maxLen { + maxLen = len(t.name) + } + } + for _, t := range debugtab { + fmt.Printf("\t%-*s\t%s\n", maxLen, t.name, t.help) + } + // ssa options have their own help + fmt.Printf("\t%-*s\t%s\n", maxLen, "ssa/help", "print help about SSA debugging") + fmt.Print(debugHelpFooter) + os.Exit(0) + } + val, valstring, haveInt := 1, "", true + if i := strings.IndexAny(name, "=:"); i >= 0 { + var err error + name, valstring = name[:i], name[i+1:] + val, err = strconv.Atoi(valstring) + if err != nil { + val, haveInt = 1, false + } + } + for _, t := range debugtab { + if t.name != name { + continue + } + switch vp := t.val.(type) { + case nil: + // Ignore + case *string: + *vp = valstring + case *int: + if !haveInt { + log.Fatalf("invalid debug value %v", name) + } + *vp = val + default: + panic("bad debugtab type") + } + continue Split + } + // special case for ssa for now + if strings.HasPrefix(name, "ssa/") { + // expect form ssa/phase/flag + // e.g. -d=ssa/generic_cse/time + // _ in phase name also matches space + phase := name[4:] + flag := "debug" // default flag is debug + if i := strings.Index(phase, "/"); i >= 0 { + flag = phase[i+1:] + phase = phase[:i] + } + err := ssa.PhaseOption(phase, flag, val, valstring) + if err != "" { + log.Fatalf(err) + } + continue Split + } + log.Fatalf("unknown debug key -d %s\n", name) + } + } + + if compiling_runtime { + // Runtime can't use -d=checkptr, at least not yet. + Debug_checkptr = 0 + + // Fuzzing the runtime isn't interesting either. + Debug_libfuzzer = 0 + } + + // set via a -d flag + Ctxt.Debugpcln = Debug_pctab + if flagDWARF { + dwarf.EnableLogging(Debug_gendwarfinl != 0) + } + + if Debug_softfloat != 0 { + thearch.SoftFloat = true + } + + // enable inlining. for now: + // default: inlining on. (Debug.l == 1) + // -l: inlining off (Debug.l == 0) + // -l=2, -l=3: inlining on again, with extra debugging (Debug.l > 1) + if Debug.l <= 1 { + Debug.l = 1 - Debug.l + } + + if jsonLogOpt != "" { // parse version,destination from json logging optimization. + logopt.LogJsonOption(jsonLogOpt) + } + + ssaDump = os.Getenv("GOSSAFUNC") + ssaDir = os.Getenv("GOSSADIR") + if ssaDump != "" { + if strings.HasSuffix(ssaDump, "+") { + ssaDump = ssaDump[:len(ssaDump)-1] + ssaDumpStdout = true + } + spl := strings.Split(ssaDump, ":") + if len(spl) > 1 { + ssaDump = spl[0] + ssaDumpCFG = spl[1] + } + } + + trackScopes = flagDWARF + + Widthptr = thearch.LinkArch.PtrSize + Widthreg = thearch.LinkArch.RegSize + + // initialize types package + // (we need to do this to break dependencies that otherwise + // would lead to import cycles) + types.Widthptr = Widthptr + types.Dowidth = dowidth + types.Fatalf = Fatalf + types.Sconv = func(s *types.Sym, flag, mode int) string { + return sconv(s, FmtFlag(flag), fmtMode(mode)) + } + types.Tconv = func(t *types.Type, flag, mode int) string { + return tconv(t, FmtFlag(flag), fmtMode(mode)) + } + types.FormatSym = func(sym *types.Sym, s fmt.State, verb rune, mode int) { + symFormat(sym, s, verb, fmtMode(mode)) + } + types.FormatType = func(t *types.Type, s fmt.State, verb rune, mode int) { + typeFormat(t, s, verb, fmtMode(mode)) + } + types.TypeLinkSym = func(t *types.Type) *obj.LSym { + return typenamesym(t).Linksym() + } + types.FmtLeft = int(FmtLeft) + types.FmtUnsigned = int(FmtUnsigned) + types.FErr = int(FErr) + types.Ctxt = Ctxt + + initUniverse() + + dclcontext = PEXTERN + nerrors = 0 + + autogeneratedPos = makePos(src.NewFileBase("", ""), 1, 0) + + timings.Start("fe", "loadsys") + loadsys() + + timings.Start("fe", "parse") + lines := parseFiles(flag.Args()) + timings.Stop() + timings.AddEvent(int64(lines), "lines") + + finishUniverse() + + recordPackageName() + + typecheckok = true + + // Process top-level declarations in phases. + + // Phase 1: const, type, and names and types of funcs. + // This will gather all the information about types + // and methods but doesn't depend on any of it. + // + // We also defer type alias declarations until phase 2 + // to avoid cycles like #18640. + // TODO(gri) Remove this again once we have a fix for #25838. + + // Don't use range--typecheck can add closures to xtop. + timings.Start("fe", "typecheck", "top1") + for i := 0; i < len(xtop); i++ { + n := xtop[i] + if op := n.Op; op != ODCL && op != OAS && op != OAS2 && (op != ODCLTYPE || !n.Left.Name.Param.Alias()) { + xtop[i] = typecheck(n, ctxStmt) + } + } + + // Phase 2: Variable assignments. + // To check interface assignments, depends on phase 1. + + // Don't use range--typecheck can add closures to xtop. + timings.Start("fe", "typecheck", "top2") + for i := 0; i < len(xtop); i++ { + n := xtop[i] + if op := n.Op; op == ODCL || op == OAS || op == OAS2 || op == ODCLTYPE && n.Left.Name.Param.Alias() { + xtop[i] = typecheck(n, ctxStmt) + } + } + + // Phase 3: Type check function bodies. + // Don't use range--typecheck can add closures to xtop. + timings.Start("fe", "typecheck", "func") + var fcount int64 + for i := 0; i < len(xtop); i++ { + n := xtop[i] + if n.Op == ODCLFUNC { + Curfn = n + decldepth = 1 + saveerrors() + typecheckslice(Curfn.Nbody.Slice(), ctxStmt) + checkreturn(Curfn) + if nerrors != 0 { + Curfn.Nbody.Set(nil) // type errors; do not compile + } + // Now that we've checked whether n terminates, + // we can eliminate some obviously dead code. + deadcode(Curfn) + fcount++ + } + } + // With all types checked, it's now safe to verify map keys. One single + // check past phase 9 isn't sufficient, as we may exit with other errors + // before then, thus skipping map key errors. + checkMapKeys() + timings.AddEvent(fcount, "funcs") + + if nsavederrors+nerrors != 0 { + errorexit() + } + + fninit(xtop) + + // Phase 4: Decide how to capture closed variables. + // This needs to run before escape analysis, + // because variables captured by value do not escape. + timings.Start("fe", "capturevars") + for _, n := range xtop { + if n.Op == ODCLFUNC && n.Func.Closure != nil { + Curfn = n + capturevars(n) + } + } + capturevarscomplete = true + + Curfn = nil + + if nsavederrors+nerrors != 0 { + errorexit() + } + + // Phase 5: Inlining + timings.Start("fe", "inlining") + if Debug_typecheckinl != 0 { + // Typecheck imported function bodies if Debug.l > 1, + // otherwise lazily when used or re-exported. + for _, n := range importlist { + if n.Func.Inl != nil { + saveerrors() + typecheckinl(n) + } + } + + if nsavederrors+nerrors != 0 { + errorexit() + } + } + + if Debug.l != 0 { + // Find functions that can be inlined and clone them before walk expands them. + visitBottomUp(xtop, func(list []*Node, recursive bool) { + numfns := numNonClosures(list) + for _, n := range list { + if !recursive || numfns > 1 { + // We allow inlining if there is no + // recursion, or the recursion cycle is + // across more than one function. + caninl(n) + } else { + if Debug.m > 1 { + fmt.Printf("%v: cannot inline %v: recursive\n", n.Line(), n.Func.Nname) + } + } + inlcalls(n) + } + }) + } + + for _, n := range xtop { + if n.Op == ODCLFUNC { + devirtualize(n) + } + } + Curfn = nil + + // Phase 6: Escape analysis. + // Required for moving heap allocations onto stack, + // which in turn is required by the closure implementation, + // which stores the addresses of stack variables into the closure. + // If the closure does not escape, it needs to be on the stack + // or else the stack copier will not update it. + // Large values are also moved off stack in escape analysis; + // because large values may contain pointers, it must happen early. + timings.Start("fe", "escapes") + escapes(xtop) + + // Collect information for go:nowritebarrierrec + // checking. This must happen before transformclosure. + // We'll do the final check after write barriers are + // inserted. + if compiling_runtime { + nowritebarrierrecCheck = newNowritebarrierrecChecker() + } + + // Phase 7: Transform closure bodies to properly reference captured variables. + // This needs to happen before walk, because closures must be transformed + // before walk reaches a call of a closure. + timings.Start("fe", "xclosures") + for _, n := range xtop { + if n.Op == ODCLFUNC && n.Func.Closure != nil { + Curfn = n + transformclosure(n) + } + } + + // Prepare for SSA compilation. + // This must be before peekitabs, because peekitabs + // can trigger function compilation. + initssaconfig() + + // Just before compilation, compile itabs found on + // the right side of OCONVIFACE so that methods + // can be de-virtualized during compilation. + Curfn = nil + peekitabs() + + // Phase 8: Compile top level functions. + // Don't use range--walk can add functions to xtop. + timings.Start("be", "compilefuncs") + fcount = 0 + for i := 0; i < len(xtop); i++ { + n := xtop[i] + if n.Op == ODCLFUNC { + funccompile(n) + fcount++ + } + } + timings.AddEvent(fcount, "funcs") + + compileFunctions() + + if nowritebarrierrecCheck != nil { + // Write barriers are now known. Check the + // call graph. + nowritebarrierrecCheck.check() + nowritebarrierrecCheck = nil + } + + // Finalize DWARF inline routine DIEs, then explicitly turn off + // DWARF inlining gen so as to avoid problems with generated + // method wrappers. + if Ctxt.DwFixups != nil { + Ctxt.DwFixups.Finalize(myimportpath, Debug_gendwarfinl != 0) + Ctxt.DwFixups = nil + genDwarfInline = 0 + } + + // Phase 9: Check external declarations. + timings.Start("be", "externaldcls") + for i, n := range externdcl { + if n.Op == ONAME { + externdcl[i] = typecheck(externdcl[i], ctxExpr) + } + } + // Check the map keys again, since we typechecked the external + // declarations. + checkMapKeys() + + if nerrors+nsavederrors != 0 { + errorexit() + } + + // Write object data to disk. + timings.Start("be", "dumpobj") + dumpdata() + Ctxt.NumberSyms() + dumpobj() + if asmhdr != "" { + dumpasmhdr() + } + + // Check whether any of the functions we have compiled have gigantic stack frames. + sort.Slice(largeStackFrames, func(i, j int) bool { + return largeStackFrames[i].pos.Before(largeStackFrames[j].pos) + }) + for _, large := range largeStackFrames { + if large.callee != 0 { + yyerrorl(large.pos, "stack frame too large (>1GB): %d MB locals + %d MB args + %d MB callee", large.locals>>20, large.args>>20, large.callee>>20) + } else { + yyerrorl(large.pos, "stack frame too large (>1GB): %d MB locals + %d MB args", large.locals>>20, large.args>>20) + } + } + + if len(funcStack) != 0 { + Fatalf("funcStack is non-empty: %v", len(funcStack)) + } + if len(compilequeue) != 0 { + Fatalf("%d uncompiled functions", len(compilequeue)) + } + + logopt.FlushLoggedOpts(Ctxt, myimportpath) + + if nerrors+nsavederrors != 0 { + errorexit() + } + + flusherrors() + timings.Stop() + + if benchfile != "" { + if err := writebench(benchfile); err != nil { + log.Fatalf("cannot write benchmark data: %v", err) + } + } +} + +// numNonClosures returns the number of functions in list which are not closures. +func numNonClosures(list []*Node) int { + count := 0 + for _, n := range list { + if n.Func.Closure == nil { + count++ + } + } + return count +} + +func writebench(filename string) error { + f, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666) + if err != nil { + return err + } + + var buf bytes.Buffer + fmt.Fprintln(&buf, "commit:", objabi.Version) + fmt.Fprintln(&buf, "goos:", runtime.GOOS) + fmt.Fprintln(&buf, "goarch:", runtime.GOARCH) + timings.Write(&buf, "BenchmarkCompile:"+myimportpath+":") + + n, err := f.Write(buf.Bytes()) + if err != nil { + return err + } + if n != buf.Len() { + panic("bad writer") + } + + return f.Close() +} + +var ( + importMap = map[string]string{} + packageFile map[string]string // nil means not in use +) + +func addImportMap(s string) { + if strings.Count(s, "=") != 1 { + log.Fatal("-importmap argument must be of the form source=actual") + } + i := strings.Index(s, "=") + source, actual := s[:i], s[i+1:] + if source == "" || actual == "" { + log.Fatal("-importmap argument must be of the form source=actual; source and actual must be non-empty") + } + importMap[source] = actual +} + +func readImportCfg(file string) { + packageFile = map[string]string{} + data, err := ioutil.ReadFile(file) + if err != nil { + log.Fatalf("-importcfg: %v", err) + } + + for lineNum, line := range strings.Split(string(data), "\n") { + lineNum++ // 1-based + line = strings.TrimSpace(line) + if line == "" || strings.HasPrefix(line, "#") { + continue + } + + var verb, args string + if i := strings.Index(line, " "); i < 0 { + verb = line + } else { + verb, args = line[:i], strings.TrimSpace(line[i+1:]) + } + var before, after string + if i := strings.Index(args, "="); i >= 0 { + before, after = args[:i], args[i+1:] + } + switch verb { + default: + log.Fatalf("%s:%d: unknown directive %q", file, lineNum, verb) + case "importmap": + if before == "" || after == "" { + log.Fatalf(`%s:%d: invalid importmap: syntax is "importmap old=new"`, file, lineNum) + } + importMap[before] = after + case "packagefile": + if before == "" || after == "" { + log.Fatalf(`%s:%d: invalid packagefile: syntax is "packagefile path=filename"`, file, lineNum) + } + packageFile[before] = after + } + } +} + +// symabiDefs and symabiRefs record the defined and referenced ABIs of +// symbols required by non-Go code. These are keyed by link symbol +// name, where the local package prefix is always `"".` +var symabiDefs, symabiRefs map[string]obj.ABI + +// readSymABIs reads a symabis file that specifies definitions and +// references of text symbols by ABI. +// +// The symabis format is a set of lines, where each line is a sequence +// of whitespace-separated fields. The first field is a verb and is +// either "def" for defining a symbol ABI or "ref" for referencing a +// symbol using an ABI. For both "def" and "ref", the second field is +// the symbol name and the third field is the ABI name, as one of the +// named cmd/internal/obj.ABI constants. +func readSymABIs(file, myimportpath string) { + data, err := ioutil.ReadFile(file) + if err != nil { + log.Fatalf("-symabis: %v", err) + } + + symabiDefs = make(map[string]obj.ABI) + symabiRefs = make(map[string]obj.ABI) + + localPrefix := "" + if myimportpath != "" { + // Symbols in this package may be written either as + // "".X or with the package's import path already in + // the symbol. + localPrefix = objabi.PathToPrefix(myimportpath) + "." + } + + for lineNum, line := range strings.Split(string(data), "\n") { + lineNum++ // 1-based + line = strings.TrimSpace(line) + if line == "" || strings.HasPrefix(line, "#") { + continue + } + + parts := strings.Fields(line) + switch parts[0] { + case "def", "ref": + // Parse line. + if len(parts) != 3 { + log.Fatalf(`%s:%d: invalid symabi: syntax is "%s sym abi"`, file, lineNum, parts[0]) + } + sym, abistr := parts[1], parts[2] + abi, valid := obj.ParseABI(abistr) + if !valid { + log.Fatalf(`%s:%d: invalid symabi: unknown abi "%s"`, file, lineNum, abistr) + } + + // If the symbol is already prefixed with + // myimportpath, rewrite it to start with "" + // so it matches the compiler's internal + // symbol names. + if localPrefix != "" && strings.HasPrefix(sym, localPrefix) { + sym = `"".` + sym[len(localPrefix):] + } + + // Record for later. + if parts[0] == "def" { + symabiDefs[sym] = abi + } else { + symabiRefs[sym] = abi + } + default: + log.Fatalf(`%s:%d: invalid symabi type "%s"`, file, lineNum, parts[0]) + } + } +} + +func saveerrors() { + nsavederrors += nerrors + nerrors = 0 +} + +func arsize(b *bufio.Reader, name string) int { + var buf [ArhdrSize]byte + if _, err := io.ReadFull(b, buf[:]); err != nil { + return -1 + } + aname := strings.Trim(string(buf[0:16]), " ") + if !strings.HasPrefix(aname, name) { + return -1 + } + asize := strings.Trim(string(buf[48:58]), " ") + i, _ := strconv.Atoi(asize) + return i +} + +var idirs []string + +func addidir(dir string) { + if dir != "" { + idirs = append(idirs, dir) + } +} + +func isDriveLetter(b byte) bool { + return 'a' <= b && b <= 'z' || 'A' <= b && b <= 'Z' +} + +// is this path a local name? begins with ./ or ../ or / +func islocalname(name string) bool { + return strings.HasPrefix(name, "/") || + runtime.GOOS == "windows" && len(name) >= 3 && isDriveLetter(name[0]) && name[1] == ':' && name[2] == '/' || + strings.HasPrefix(name, "./") || name == "." || + strings.HasPrefix(name, "../") || name == ".." +} + +func findpkg(name string) (file string, ok bool) { + if islocalname(name) { + if nolocalimports { + return "", false + } + + if packageFile != nil { + file, ok = packageFile[name] + return file, ok + } + + // try .a before .6. important for building libraries: + // if there is an array.6 in the array.a library, + // want to find all of array.a, not just array.6. + file = fmt.Sprintf("%s.a", name) + if _, err := os.Stat(file); err == nil { + return file, true + } + file = fmt.Sprintf("%s.o", name) + if _, err := os.Stat(file); err == nil { + return file, true + } + return "", false + } + + // local imports should be canonicalized already. + // don't want to see "encoding/../encoding/base64" + // as different from "encoding/base64". + if q := path.Clean(name); q != name { + yyerror("non-canonical import path %q (should be %q)", name, q) + return "", false + } + + if packageFile != nil { + file, ok = packageFile[name] + return file, ok + } + + for _, dir := range idirs { + file = fmt.Sprintf("%s/%s.a", dir, name) + if _, err := os.Stat(file); err == nil { + return file, true + } + file = fmt.Sprintf("%s/%s.o", dir, name) + if _, err := os.Stat(file); err == nil { + return file, true + } + } + + if objabi.GOROOT != "" { + suffix := "" + suffixsep := "" + if flag_installsuffix != "" { + suffixsep = "_" + suffix = flag_installsuffix + } else if flag_race { + suffixsep = "_" + suffix = "race" + } else if flag_msan { + suffixsep = "_" + suffix = "msan" + } + + file = fmt.Sprintf("%s/pkg/%s_%s%s%s/%s.a", objabi.GOROOT, objabi.GOOS, objabi.GOARCH, suffixsep, suffix, name) + if _, err := os.Stat(file); err == nil { + return file, true + } + file = fmt.Sprintf("%s/pkg/%s_%s%s%s/%s.o", objabi.GOROOT, objabi.GOOS, objabi.GOARCH, suffixsep, suffix, name) + if _, err := os.Stat(file); err == nil { + return file, true + } + } + + return "", false +} + +// loadsys loads the definitions for the low-level runtime functions, +// so that the compiler can generate calls to them, +// but does not make them visible to user code. +func loadsys() { + types.Block = 1 + + inimport = true + typecheckok = true + + typs := runtimeTypes() + for _, d := range &runtimeDecls { + sym := Runtimepkg.Lookup(d.name) + typ := typs[d.typ] + switch d.tag { + case funcTag: + importfunc(Runtimepkg, src.NoXPos, sym, typ) + case varTag: + importvar(Runtimepkg, src.NoXPos, sym, typ) + default: + Fatalf("unhandled declaration tag %v", d.tag) + } + } + + typecheckok = false + inimport = false +} + +// myheight tracks the local package's height based on packages +// imported so far. +var myheight int + +func importfile(f *Val) *types.Pkg { + path_, ok := f.U.(string) + if !ok { + yyerror("import path must be a string") + return nil + } + + if len(path_) == 0 { + yyerror("import path is empty") + return nil + } + + if isbadimport(path_, false) { + return nil + } + + // The package name main is no longer reserved, + // but we reserve the import path "main" to identify + // the main package, just as we reserve the import + // path "math" to identify the standard math package. + if path_ == "main" { + yyerror("cannot import \"main\"") + errorexit() + } + + if myimportpath != "" && path_ == myimportpath { + yyerror("import %q while compiling that package (import cycle)", path_) + errorexit() + } + + if mapped, ok := importMap[path_]; ok { + path_ = mapped + } + + if path_ == "unsafe" { + return unsafepkg + } + + if islocalname(path_) { + if path_[0] == '/' { + yyerror("import path cannot be absolute path") + return nil + } + + prefix := Ctxt.Pathname + if localimport != "" { + prefix = localimport + } + path_ = path.Join(prefix, path_) + + if isbadimport(path_, true) { + return nil + } + } + + file, found := findpkg(path_) + if !found { + yyerror("can't find import: %q", path_) + errorexit() + } + + importpkg := types.NewPkg(path_, "") + if importpkg.Imported { + return importpkg + } + + importpkg.Imported = true + + imp, err := bio.Open(file) + if err != nil { + yyerror("can't open import: %q: %v", path_, err) + errorexit() + } + defer imp.Close() + + // check object header + p, err := imp.ReadString('\n') + if err != nil { + yyerror("import %s: reading input: %v", file, err) + errorexit() + } + + if p == "!\n" { // package archive + // package export block should be first + sz := arsize(imp.Reader, "__.PKGDEF") + if sz <= 0 { + yyerror("import %s: not a package file", file) + errorexit() + } + p, err = imp.ReadString('\n') + if err != nil { + yyerror("import %s: reading input: %v", file, err) + errorexit() + } + } + + if !strings.HasPrefix(p, "go object ") { + yyerror("import %s: not a go object file: %s", file, p) + errorexit() + } + q := fmt.Sprintf("%s %s %s %s\n", objabi.GOOS, objabi.GOARCH, objabi.Version, objabi.Expstring()) + if p[10:] != q { + yyerror("import %s: object is [%s] expected [%s]", file, p[10:], q) + errorexit() + } + + // process header lines + for { + p, err = imp.ReadString('\n') + if err != nil { + yyerror("import %s: reading input: %v", file, err) + errorexit() + } + if p == "\n" { + break // header ends with blank line + } + } + + // In the importfile, if we find: + // $$\n (textual format): not supported anymore + // $$B\n (binary format) : import directly, then feed the lexer a dummy statement + + // look for $$ + var c byte + for { + c, err = imp.ReadByte() + if err != nil { + break + } + if c == '$' { + c, err = imp.ReadByte() + if c == '$' || err != nil { + break + } + } + } + + // get character after $$ + if err == nil { + c, _ = imp.ReadByte() + } + + var fingerprint goobj.FingerprintType + switch c { + case '\n': + yyerror("cannot import %s: old export format no longer supported (recompile library)", path_) + return nil + + case 'B': + if Debug_export != 0 { + fmt.Printf("importing %s (%s)\n", path_, file) + } + imp.ReadByte() // skip \n after $$B + + c, err = imp.ReadByte() + if err != nil { + yyerror("import %s: reading input: %v", file, err) + errorexit() + } + + // Indexed format is distinguished by an 'i' byte, + // whereas previous export formats started with 'c', 'd', or 'v'. + if c != 'i' { + yyerror("import %s: unexpected package format byte: %v", file, c) + errorexit() + } + fingerprint = iimport(importpkg, imp) + + default: + yyerror("no import in %q", path_) + errorexit() + } + + // assume files move (get installed) so don't record the full path + if packageFile != nil { + // If using a packageFile map, assume path_ can be recorded directly. + Ctxt.AddImport(path_, fingerprint) + } else { + // For file "/Users/foo/go/pkg/darwin_amd64/math.a" record "math.a". + Ctxt.AddImport(file[len(file)-len(path_)-len(".a"):], fingerprint) + } + + if importpkg.Height >= myheight { + myheight = importpkg.Height + 1 + } + + return importpkg +} + +func pkgnotused(lineno src.XPos, path string, name string) { + // If the package was imported with a name other than the final + // import path element, show it explicitly in the error message. + // Note that this handles both renamed imports and imports of + // packages containing unconventional package declarations. + // Note that this uses / always, even on Windows, because Go import + // paths always use forward slashes. + elem := path + if i := strings.LastIndex(elem, "/"); i >= 0 { + elem = elem[i+1:] + } + if name == "" || elem == name { + yyerrorl(lineno, "imported and not used: %q", path) + } else { + yyerrorl(lineno, "imported and not used: %q as %s", path, name) + } +} + +func mkpackage(pkgname string) { + if localpkg.Name == "" { + if pkgname == "_" { + yyerror("invalid package name _") + } + localpkg.Name = pkgname + } else { + if pkgname != localpkg.Name { + yyerror("package %s; expected %s", pkgname, localpkg.Name) + } + } +} + +func clearImports() { + type importedPkg struct { + pos src.XPos + path string + name string + } + var unused []importedPkg + + for _, s := range localpkg.Syms { + n := asNode(s.Def) + if n == nil { + continue + } + if n.Op == OPACK { + // throw away top-level package name left over + // from previous file. + // leave s->block set to cause redeclaration + // errors if a conflicting top-level name is + // introduced by a different file. + if !n.Name.Used() && nsyntaxerrors == 0 { + unused = append(unused, importedPkg{n.Pos, n.Name.Pkg.Path, s.Name}) + } + s.Def = nil + continue + } + if IsAlias(s) { + // throw away top-level name left over + // from previous import . "x" + if n.Name != nil && n.Name.Pack != nil && !n.Name.Pack.Name.Used() && nsyntaxerrors == 0 { + unused = append(unused, importedPkg{n.Name.Pack.Pos, n.Name.Pack.Name.Pkg.Path, ""}) + n.Name.Pack.Name.SetUsed(true) + } + s.Def = nil + continue + } + } + + sort.Slice(unused, func(i, j int) bool { return unused[i].pos.Before(unused[j].pos) }) + for _, pkg := range unused { + pkgnotused(pkg.pos, pkg.path, pkg.name) + } +} + +func IsAlias(sym *types.Sym) bool { + return sym.Def != nil && asNode(sym.Def).Sym != sym +} + +// By default, assume any debug flags are incompatible with concurrent +// compilation. A few are safe and potentially in common use for +// normal compiles, though; return true for those. +func concurrentFlagOk() bool { + // Report whether any debug flag that would prevent concurrent + // compilation is set, by zeroing out the allowed ones and then + // checking if the resulting struct is zero. + d := Debug + d.B = 0 // disable bounds checking + d.C = 0 // disable printing of columns in error messages + d.e = 0 // no limit on errors; errors all come from non-concurrent code + d.N = 0 // disable optimizations + d.l = 0 // disable inlining + d.w = 0 // all printing happens before compilation + d.W = 0 // all printing happens before compilation + d.S = 0 // printing disassembly happens at the end (but see concurrentBackendAllowed below) + + return d == DebugFlags{} +} + +func concurrentBackendAllowed() bool { + if !concurrentFlagOk() { + return false + } + + // Debug.S by itself is ok, because all printing occurs + // while writing the object file, and that is non-concurrent. + // Adding Debug_vlog, however, causes Debug.S to also print + // while flushing the plist, which happens concurrently. + if Debug_vlog || debugstr != "" || debuglive > 0 { + return false + } + // TODO: Test and delete this condition. + if objabi.Fieldtrack_enabled != 0 { + return false + } + // TODO: fix races and enable the following flags + if Ctxt.Flag_shared || Ctxt.Flag_dynlink || flag_race { + return false + } + return true +} + +// recordFlags records the specified command-line flags to be placed +// in the DWARF info. +func recordFlags(flags ...string) { + if myimportpath == "" { + // We can't record the flags if we don't know what the + // package name is. + return + } + + type BoolFlag interface { + IsBoolFlag() bool + } + type CountFlag interface { + IsCountFlag() bool + } + var cmd bytes.Buffer + for _, name := range flags { + f := flag.Lookup(name) + if f == nil { + continue + } + getter := f.Value.(flag.Getter) + if getter.String() == f.DefValue { + // Flag has default value, so omit it. + continue + } + if bf, ok := f.Value.(BoolFlag); ok && bf.IsBoolFlag() { + val, ok := getter.Get().(bool) + if ok && val { + fmt.Fprintf(&cmd, " -%s", f.Name) + continue + } + } + if cf, ok := f.Value.(CountFlag); ok && cf.IsCountFlag() { + val, ok := getter.Get().(int) + if ok && val == 1 { + fmt.Fprintf(&cmd, " -%s", f.Name) + continue + } + } + fmt.Fprintf(&cmd, " -%s=%v", f.Name, getter.Get()) + } + + if cmd.Len() == 0 { + return + } + s := Ctxt.Lookup(dwarf.CUInfoPrefix + "producer." + myimportpath) + s.Type = objabi.SDWARFCUINFO + // Sometimes (for example when building tests) we can link + // together two package main archives. So allow dups. + s.Set(obj.AttrDuplicateOK, true) + Ctxt.Data = append(Ctxt.Data, s) + s.P = cmd.Bytes()[1:] +} + +// recordPackageName records the name of the package being +// compiled, so that the linker can save it in the compile unit's DIE. +func recordPackageName() { + s := Ctxt.Lookup(dwarf.CUInfoPrefix + "packagename." + myimportpath) + s.Type = objabi.SDWARFCUINFO + // Sometimes (for example when building tests) we can link + // together two package main archives. So allow dups. + s.Set(obj.AttrDuplicateOK, true) + Ctxt.Data = append(Ctxt.Data, s) + s.P = []byte(localpkg.Name) +} + +// flag_lang is the language version we are compiling for, set by the -lang flag. +var flag_lang string + +// currentLang returns the current language version. +func currentLang() string { + return fmt.Sprintf("go1.%d", goversion.Version) +} + +// goVersionRE is a regular expression that matches the valid +// arguments to the -lang flag. +var goVersionRE = regexp.MustCompile(`^go([1-9][0-9]*)\.(0|[1-9][0-9]*)$`) + +// A lang is a language version broken into major and minor numbers. +type lang struct { + major, minor int +} + +// langWant is the desired language version set by the -lang flag. +// If the -lang flag is not set, this is the zero value, meaning that +// any language version is supported. +var langWant lang + +// langSupported reports whether language version major.minor is +// supported in a particular package. +func langSupported(major, minor int, pkg *types.Pkg) bool { + if pkg == nil { + // TODO(mdempsky): Set Pkg for local types earlier. + pkg = localpkg + } + if pkg != localpkg { + // Assume imported packages passed type-checking. + return true + } + + if langWant.major == 0 && langWant.minor == 0 { + return true + } + return langWant.major > major || (langWant.major == major && langWant.minor >= minor) +} + +// checkLang verifies that the -lang flag holds a valid value, and +// exits if not. It initializes data used by langSupported. +func checkLang() { + if flag_lang == "" { + return + } + + var err error + langWant, err = parseLang(flag_lang) + if err != nil { + log.Fatalf("invalid value %q for -lang: %v", flag_lang, err) + } + + if def := currentLang(); flag_lang != def { + defVers, err := parseLang(def) + if err != nil { + log.Fatalf("internal error parsing default lang %q: %v", def, err) + } + if langWant.major > defVers.major || (langWant.major == defVers.major && langWant.minor > defVers.minor) { + log.Fatalf("invalid value %q for -lang: max known version is %q", flag_lang, def) + } + } +} + +// parseLang parses a -lang option into a langVer. +func parseLang(s string) (lang, error) { + matches := goVersionRE.FindStringSubmatch(s) + if matches == nil { + return lang{}, fmt.Errorf(`should be something like "go1.12"`) + } + major, err := strconv.Atoi(matches[1]) + if err != nil { + return lang{}, err + } + minor, err := strconv.Atoi(matches[2]) + if err != nil { + return lang{}, err + } + return lang{major: major, minor: minor}, nil +} -- cgit v1.2.3