mirror of
https://github.com/golang/go.git
synced 2025-12-08 06:10:04 +00:00
We don't support stack frames over 2GB. Rather than detect this during backend compilation, check for it at the end of compilation. This is arguably a more accurate check anyway, since it takes into account the full frame, including local stack, arguments, and arch-specific rounding, although it's unlikely anyone would ever notice. Also, rather than reporting the error right away, take note of it and report it later, at the top level. This is not relevant now, but it will help with making the backend concurrent, as the append to the list of oversized functions can be cheaply protected by a plain mutex. Updates #15756 Updates #19250 Change-Id: Id3fa21906616d62e9dc66e27a17fd5f83304e96e Reviewed-on: https://go-review.googlesource.com/38972 Run-TryBot: Josh Bleecher Snyder <josharian@gmail.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Cherry Zhang <cherryyz@google.com>
993 lines
26 KiB
Go
993 lines
26 KiB
Go
// 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/ssa"
|
|
"cmd/internal/obj"
|
|
"cmd/internal/src"
|
|
"cmd/internal/sys"
|
|
"flag"
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"os"
|
|
"path"
|
|
"runtime"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
var imported_unsafe bool
|
|
|
|
var (
|
|
buildid string
|
|
)
|
|
|
|
var (
|
|
Debug_append int
|
|
Debug_asm bool
|
|
Debug_closure int
|
|
debug_dclstack int
|
|
Debug_panic int
|
|
Debug_slice int
|
|
Debug_vlog bool
|
|
Debug_wb int
|
|
Debug_pctab string
|
|
)
|
|
|
|
// 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
|
|
val interface{} // must be *int or *string
|
|
}{
|
|
{"append", &Debug_append}, // print information about append compilation
|
|
{"closure", &Debug_closure}, // print information about closure compilation
|
|
{"disablenil", &disable_checknil}, // disable nil checks
|
|
{"dclstack", &debug_dclstack}, // run internal dclstack checks
|
|
{"gcprog", &Debug_gcprog}, // print dump of GC programs
|
|
{"nil", &Debug_checknil}, // print information about nil checks
|
|
{"panic", &Debug_panic}, // do not hide any compiler panic
|
|
{"slice", &Debug_slice}, // print information about slice compilation
|
|
{"typeassert", &Debug_typeassert}, // print information about type assertion inlining
|
|
{"wb", &Debug_wb}, // print information about write barriers
|
|
{"export", &Debug_export}, // print export data
|
|
{"pctab", &Debug_pctab}, // print named pc-value table
|
|
}
|
|
|
|
func usage() {
|
|
fmt.Printf("usage: compile [options] file.go...\n")
|
|
obj.Flagprint(1)
|
|
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()
|
|
}
|
|
}
|
|
}
|
|
|
|
func doversion() {
|
|
p := obj.Expstring()
|
|
if p == "X:none" {
|
|
p = ""
|
|
}
|
|
sep := ""
|
|
if p != "" {
|
|
sep = " "
|
|
}
|
|
fmt.Printf("compile version %s%s%s\n", obj.Version, sep, p)
|
|
os.Exit(0)
|
|
}
|
|
|
|
// 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.S390X)
|
|
}
|
|
|
|
// timing data for compiler phases
|
|
var timings Timings
|
|
var benchfile string
|
|
|
|
// 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.DebugInfo = debuginfo
|
|
Ctxt.DiagFunc = yyerror
|
|
Ctxt.Bso = bufio.NewWriter(os.Stdout)
|
|
|
|
localpkg = mkpkg("")
|
|
localpkg.Prefix = "\"\""
|
|
|
|
// pseudo-package, for scoping
|
|
builtinpkg = mkpkg("go.builtin")
|
|
builtinpkg.Prefix = "go.builtin" // not go%2ebuiltin
|
|
|
|
// pseudo-package, accessed by import "unsafe"
|
|
unsafepkg = mkpkg("unsafe")
|
|
unsafepkg.Name = "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 = mkpkg("go.runtime")
|
|
Runtimepkg.Name = "runtime"
|
|
Runtimepkg.Prefix = "runtime"
|
|
|
|
// pseudo-packages used in symbol tables
|
|
itabpkg = mkpkg("go.itab")
|
|
itabpkg.Name = "go.itab"
|
|
itabpkg.Prefix = "go.itab" // not go%2eitab
|
|
|
|
itablinkpkg = mkpkg("go.itablink")
|
|
itablinkpkg.Name = "go.itablink"
|
|
itablinkpkg.Prefix = "go.itablink" // not go%2eitablink
|
|
|
|
trackpkg = mkpkg("go.track")
|
|
trackpkg.Name = "go.track"
|
|
trackpkg.Prefix = "go.track" // not go%2etrack
|
|
|
|
typepkg = mkpkg("type")
|
|
typepkg.Name = "type"
|
|
|
|
// pseudo-package used for map zero values
|
|
mappkg = mkpkg("go.map")
|
|
mappkg.Name = "go.map"
|
|
mappkg.Prefix = "go.map"
|
|
|
|
Nacl = obj.GOOS == "nacl"
|
|
if Nacl {
|
|
flag_largemodel = true
|
|
}
|
|
|
|
flag.BoolVar(&compiling_runtime, "+", false, "compiling runtime")
|
|
obj.Flagcount("%", "debug non-static initializers", &Debug['%'])
|
|
obj.Flagcount("B", "disable bounds checking", &Debug['B'])
|
|
obj.Flagcount("C", "disable printing of columns in error messages", &Debug['C']) // TODO(gri) remove eventually
|
|
flag.StringVar(&localimport, "D", "", "set relative `path` for local imports")
|
|
obj.Flagcount("E", "debug symbol export", &Debug['E'])
|
|
obj.Flagfn1("I", "add `directory` to import search path", addidir)
|
|
obj.Flagcount("K", "debug missing line numbers", &Debug['K'])
|
|
obj.Flagcount("N", "disable optimizations", &Debug['N'])
|
|
flag.BoolVar(&Debug_asm, "S", false, "print assembly listing")
|
|
obj.Flagfn0("V", "print compiler version", doversion)
|
|
obj.Flagcount("W", "debug parse tree after type checking", &Debug['W'])
|
|
flag.StringVar(&asmhdr, "asmhdr", "", "write assembly header to `file`")
|
|
flag.StringVar(&buildid, "buildid", "", "record `id` as the build id in the export metadata")
|
|
flag.BoolVar(&pure_go, "complete", false, "compiling complete package (no C or assembly)")
|
|
flag.StringVar(&debugstr, "d", "", "print debug information about items in `list`")
|
|
obj.Flagcount("e", "no limit on number of errors reported", &Debug['e'])
|
|
obj.Flagcount("f", "debug stack frames", &Debug['f'])
|
|
obj.Flagcount("h", "halt on error", &Debug['h'])
|
|
obj.Flagcount("i", "debug line number stack", &Debug['i'])
|
|
obj.Flagfn1("importmap", "add `definition` of the form source=actual to import map", addImportMap)
|
|
flag.StringVar(&flag_installsuffix, "installsuffix", "", "set pkg directory `suffix`")
|
|
obj.Flagcount("j", "debug runtime-initialized variables", &Debug['j'])
|
|
obj.Flagcount("l", "disable inlining", &Debug['l'])
|
|
flag.StringVar(&linkobj, "linkobj", "", "write linker-specific object to `file`")
|
|
obj.Flagcount("live", "debug liveness analysis", &debuglive)
|
|
obj.Flagcount("m", "print optimization decisions", &Debug['m'])
|
|
flag.BoolVar(&flag_msan, "msan", false, "build code compatible with C/C++ memory sanitizer")
|
|
flag.BoolVar(&dolinkobj, "dolinkobj", true, "generate linker-specific objects; if false, some invalid code may compile")
|
|
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 package file instead of object file")
|
|
obj.Flagcount("r", "debug generated wrappers", &Debug['r'])
|
|
flag.BoolVar(&flag_race, "race", false, "enable race detector")
|
|
obj.Flagcount("s", "warn about composite literals that can be simplified", &Debug['s'])
|
|
flag.StringVar(&pathPrefix, "trimpath", "", "remove `prefix` from recorded source file paths")
|
|
flag.BoolVar(&safemode, "u", false, "reject unsafe code")
|
|
flag.BoolVar(&Debug_vlog, "v", false, "increase debug verbosity")
|
|
obj.Flagcount("w", "debug type checking", &Debug['w'])
|
|
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")
|
|
}
|
|
if thearch.LinkArch.Family == sys.AMD64 {
|
|
flag.BoolVar(&flag_largemodel, "largemodel", false, "generate code that assumes a large memory model")
|
|
}
|
|
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`")
|
|
flag.StringVar(&traceprofile, "traceprofile", "", "write an execution trace to `file`")
|
|
flag.StringVar(&benchfile, "bench", "", "append benchmark times to `file`")
|
|
obj.Flagparse(usage)
|
|
|
|
Ctxt.Flag_shared = flag_dynlink || flag_shared
|
|
Ctxt.Flag_dynlink = flag_dynlink
|
|
Ctxt.Flag_optimize = Debug['N'] == 0
|
|
|
|
Ctxt.Debugasm = Debug_asm
|
|
Ctxt.Debugvlog = Debug_vlog
|
|
|
|
if flag.NArg() < 1 {
|
|
usage()
|
|
}
|
|
|
|
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 {
|
|
racepkg = mkpkg("runtime/race")
|
|
racepkg.Name = "race"
|
|
}
|
|
if flag_msan {
|
|
msanpkg = mkpkg("runtime/msan")
|
|
msanpkg.Name = "msan"
|
|
}
|
|
if flag_race && flag_msan {
|
|
log.Fatal("cannot use both -race and -msan")
|
|
} else if flag_race || flag_msan {
|
|
instrumenting = true
|
|
}
|
|
if compiling_runtime && Debug['N'] != 0 {
|
|
log.Fatal("cannot disable optimizations while compiling runtime")
|
|
}
|
|
|
|
// parse -d argument
|
|
if debugstr != "" {
|
|
Split:
|
|
for _, name := range strings.Split(debugstr, ",") {
|
|
if name == "" {
|
|
continue
|
|
}
|
|
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)
|
|
}
|
|
}
|
|
|
|
// set via a -d flag
|
|
Ctxt.Debugpcln = Debug_pctab
|
|
|
|
// enable inlining. for now:
|
|
// default: inlining on. (debug['l'] == 1)
|
|
// -l: inlining off (debug['l'] == 0)
|
|
// -ll, -lll: inlining on again, with extra debugging (debug['l'] > 1)
|
|
if Debug['l'] <= 1 {
|
|
Debug['l'] = 1 - Debug['l']
|
|
}
|
|
|
|
Widthint = thearch.LinkArch.IntSize
|
|
Widthptr = thearch.LinkArch.PtrSize
|
|
Widthreg = thearch.LinkArch.RegSize
|
|
|
|
initUniverse()
|
|
|
|
blockgen = 1
|
|
dclcontext = PEXTERN
|
|
nerrors = 0
|
|
|
|
autogeneratedPos = makePos(src.NewFileBase("<autogenerated>", "<autogenerated>"), 1, 0)
|
|
|
|
timings.Start("fe", "loadsys")
|
|
loadsys()
|
|
|
|
timings.Start("fe", "parse")
|
|
lines := parseFiles(flag.Args())
|
|
timings.Stop()
|
|
timings.AddEvent(int64(lines), "lines")
|
|
|
|
finishUniverse()
|
|
|
|
typecheckok = true
|
|
if Debug['f'] != 0 {
|
|
frame(1)
|
|
}
|
|
|
|
// 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.
|
|
defercheckwidth()
|
|
|
|
// 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, Etop)
|
|
}
|
|
}
|
|
|
|
// 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, Etop)
|
|
}
|
|
}
|
|
resumecheckwidth()
|
|
|
|
// 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 op := n.Op; op == ODCLFUNC || op == OCLOSURE {
|
|
Curfn = n
|
|
decldepth = 1
|
|
saveerrors()
|
|
typecheckslice(Curfn.Nbody.Slice(), Etop)
|
|
checkreturn(Curfn)
|
|
if nerrors != 0 {
|
|
Curfn.Nbody.Set(nil) // type errors; do not compile
|
|
}
|
|
fcount++
|
|
}
|
|
}
|
|
timings.AddEvent(fcount, "funcs")
|
|
|
|
// 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)
|
|
}
|
|
}
|
|
|
|
Curfn = nil
|
|
|
|
if nsavederrors+nerrors != 0 {
|
|
errorexit()
|
|
}
|
|
|
|
// Phase 5: Inlining
|
|
timings.Start("fe", "inlining")
|
|
if Debug['l'] > 1 {
|
|
// Typecheck imported function bodies if debug['l'] > 1,
|
|
// otherwise lazily when used or re-exported.
|
|
for _, n := range importlist {
|
|
if n.Func.Inl.Len() != 0 {
|
|
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) {
|
|
for _, n := range list {
|
|
if !recursive {
|
|
caninl(n)
|
|
} else {
|
|
if Debug['m'] > 1 {
|
|
fmt.Printf("%v: cannot inline %v: recursive\n", n.Line(), n.Func.Nname)
|
|
}
|
|
}
|
|
inlcalls(n)
|
|
}
|
|
})
|
|
}
|
|
|
|
// 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)
|
|
|
|
if dolinkobj {
|
|
// 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")
|
|
|
|
if nsavederrors+nerrors == 0 {
|
|
fninit(xtop)
|
|
}
|
|
|
|
if compiling_runtime {
|
|
checknowritebarrierrec()
|
|
}
|
|
for _, largePos := range largeStackFrames {
|
|
yyerrorl(largePos, "stack frame too large (>2GB)")
|
|
}
|
|
}
|
|
|
|
// Phase 9: Check external declarations.
|
|
timings.Start("be", "externaldcls")
|
|
for i, n := range externdcl {
|
|
if n.Op == ONAME {
|
|
externdcl[i] = typecheck(externdcl[i], Erv)
|
|
}
|
|
}
|
|
|
|
if nerrors+nsavederrors != 0 {
|
|
errorexit()
|
|
}
|
|
|
|
// Write object data to disk.
|
|
timings.Start("be", "dumpobj")
|
|
dumpobj()
|
|
if asmhdr != "" {
|
|
dumpasmhdr()
|
|
}
|
|
|
|
if nerrors+nsavederrors != 0 {
|
|
errorexit()
|
|
}
|
|
|
|
flusherrors()
|
|
timings.Stop()
|
|
|
|
if benchfile != "" {
|
|
if err := writebench(benchfile); err != nil {
|
|
log.Fatalf("cannot write benchmark data: %v", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
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:", obj.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{}
|
|
|
|
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 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
|
|
}
|
|
|
|
func skiptopkgdef(b *bufio.Reader) bool {
|
|
// archive header
|
|
p, err := b.ReadString('\n')
|
|
if err != nil {
|
|
log.Fatalf("reading input: %v", err)
|
|
}
|
|
if p != "!<arch>\n" {
|
|
return false
|
|
}
|
|
|
|
// package export block should be first
|
|
sz := arsize(b, "__.PKGDEF")
|
|
return sz > 0
|
|
}
|
|
|
|
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 safemode || nolocalimports {
|
|
return "", false
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
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 obj.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", obj.GOROOT, obj.GOOS, obj.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", obj.GOROOT, obj.GOOS, obj.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() {
|
|
block = 1
|
|
|
|
inimport = true
|
|
typecheckok = true
|
|
defercheckwidth()
|
|
|
|
typs := runtimeTypes()
|
|
for _, d := range runtimeDecls {
|
|
sym := Runtimepkg.Lookup(d.name)
|
|
typ := typs[d.typ]
|
|
switch d.tag {
|
|
case funcTag:
|
|
importsym(Runtimepkg, sym, ONAME)
|
|
n := newfuncname(sym)
|
|
n.Type = typ
|
|
declare(n, PFUNC)
|
|
case varTag:
|
|
importvar(Runtimepkg, sym, typ)
|
|
default:
|
|
Fatalf("unhandled declaration tag %v", d.tag)
|
|
}
|
|
}
|
|
|
|
typecheckok = false
|
|
resumecheckwidth()
|
|
inimport = false
|
|
}
|
|
|
|
func importfile(f *Val) *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_) {
|
|
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" {
|
|
if safemode {
|
|
yyerror("cannot import package unsafe")
|
|
errorexit()
|
|
}
|
|
|
|
imported_unsafe = true
|
|
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_) {
|
|
return nil
|
|
}
|
|
}
|
|
|
|
file, found := findpkg(path_)
|
|
if !found {
|
|
yyerror("can't find import: %q", path_)
|
|
errorexit()
|
|
}
|
|
|
|
importpkg := mkpkg(path_)
|
|
if importpkg.Imported {
|
|
return importpkg
|
|
}
|
|
|
|
importpkg.Imported = true
|
|
|
|
impf, err := os.Open(file)
|
|
if err != nil {
|
|
yyerror("can't open import: %q: %v", path_, err)
|
|
errorexit()
|
|
}
|
|
defer impf.Close()
|
|
imp := bufio.NewReader(impf)
|
|
|
|
const pkgSuffix = ".a"
|
|
if strings.HasSuffix(file, pkgSuffix) {
|
|
if !skiptopkgdef(imp) {
|
|
yyerror("import %s: not a package file", file)
|
|
errorexit()
|
|
}
|
|
}
|
|
|
|
// check object header
|
|
p, err := imp.ReadString('\n')
|
|
if err != nil {
|
|
log.Fatalf("reading input: %v", err)
|
|
}
|
|
if len(p) > 0 {
|
|
p = p[:len(p)-1]
|
|
}
|
|
|
|
if p != "empty archive" {
|
|
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", obj.GOOS, obj.GOARCH, obj.Version, obj.Expstring())
|
|
if p[10:] != q {
|
|
yyerror("import %s: object is [%s] expected [%s]", file, p[10:], q)
|
|
errorexit()
|
|
}
|
|
}
|
|
|
|
// process header lines
|
|
safe := false
|
|
for {
|
|
p, err = imp.ReadString('\n')
|
|
if err != nil {
|
|
log.Fatalf("reading input: %v", err)
|
|
}
|
|
if p == "\n" {
|
|
break // header ends with blank line
|
|
}
|
|
if strings.HasPrefix(p, "safe") {
|
|
safe = true
|
|
break // ok to ignore rest
|
|
}
|
|
}
|
|
if safemode && !safe {
|
|
yyerror("cannot import unsafe package %q", importpkg.Path)
|
|
}
|
|
|
|
// assume files move (get installed) so don't record the full path
|
|
// (e.g., for file "/Users/foo/go/pkg/darwin_amd64/math.a" record "math.a")
|
|
Ctxt.AddImport(file[len(file)-len(path_)-len(pkgSuffix):])
|
|
|
|
// 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()
|
|
}
|
|
|
|
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
|
|
Import(importpkg, imp)
|
|
|
|
default:
|
|
yyerror("no import in %q", path_)
|
|
errorexit()
|
|
}
|
|
|
|
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() {
|
|
for _, s := range localpkg.Syms {
|
|
if s.Def == nil {
|
|
continue
|
|
}
|
|
if s.Def.Op == OPACK {
|
|
// throw away top-level package name leftover
|
|
// from previous file.
|
|
// leave s->block set to cause redeclaration
|
|
// errors if a conflicting top-level name is
|
|
// introduced by a different file.
|
|
if !s.Def.Used() && nsyntaxerrors == 0 {
|
|
pkgnotused(s.Def.Pos, s.Def.Name.Pkg.Path, s.Name)
|
|
}
|
|
s.Def = nil
|
|
continue
|
|
}
|
|
|
|
if s.isAlias() {
|
|
// throw away top-level name left over
|
|
// from previous import . "x"
|
|
if s.Def.Name != nil && s.Def.Name.Pack != nil && !s.Def.Name.Pack.Used() && nsyntaxerrors == 0 {
|
|
pkgnotused(s.Def.Name.Pack.Pos, s.Def.Name.Pack.Name.Pkg.Path, "")
|
|
s.Def.Name.Pack.SetUsed(true)
|
|
}
|
|
|
|
s.Def = nil
|
|
continue
|
|
}
|
|
}
|
|
}
|