go/src/cmd/compile/internal/gc/main.go

917 lines
23 KiB
Go
Raw Normal View History

// 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"
"cmd/compile/internal/ssa"
"cmd/internal/obj"
"cmd/internal/sys"
"flag"
"fmt"
"io"
"log"
"os"
"path"
"runtime"
"strconv"
"strings"
)
var imported_unsafe bool
var (
goos string
goarch string
goroot string
buildid string
)
var (
cmd/compile: ignore OXXX nodes in closure captured vars list Added a debug flag "-d closure" to explain compilation of closures (should this be done some other way? Should we rewrite the "-m" flag to "-d escapes"?) Used this to discover that cause was an OXXX node in the captured vars list, and in turn noticed that OXXX nodes are explicitly ignored in all other processing of captured variables. Couldn't figure out a reproducer, did verify that this OXXX was not caused by an unnamed return value (which is one use of these). Verified lack of heap allocation by examining -S output. Assembly: (runtime/mgc.go:1371) PCDATA $0, $2 (runtime/mgc.go:1371) CALL "".notewakeup(SB) (runtime/mgc.go:1377) LEAQ "".gcBgMarkWorker.func1·f(SB), AX (runtime/mgc.go:1404) MOVQ AX, (SP) (runtime/mgc.go:1404) MOVQ "".autotmp_2242+88(SP), CX (runtime/mgc.go:1404) MOVQ CX, 8(SP) (runtime/mgc.go:1404) LEAQ go.string."GC worker (idle)"(SB), AX (runtime/mgc.go:1404) MOVQ AX, 16(SP) (runtime/mgc.go:1404) MOVQ $16, 24(SP) (runtime/mgc.go:1404) MOVB $20, 32(SP) (runtime/mgc.go:1404) MOVQ $0, 40(SP) (runtime/mgc.go:1404) PCDATA $0, $2 (runtime/mgc.go:1404) CALL "".gopark(SB) Added a check for compiling_runtime to ensure that this is caught in the future. Added a test to test the check. Verified that 1.5.3 did NOT reject the test case when compiled with -+ flag, so this is not a recently added bug. Cause of bug is two-part -- there was no leaking closure detection ever, and instead it relied on capture-of-variables to trigger compiling_runtime test, but closures improved in 1.5.3 so that mere capture of a value did not also capture the variable, which thus allowed closures to escape, as well as this case where the escape was spurious. In fixedbugs/issue14999.go, compare messages for f and g; 1.5.3 would reject g, but not f. 1.4 rejects both because 1.4 heap-allocates parameter x for both. Fixes #14999. Change-Id: I40bcdd27056810628e96763a44f2acddd503aee1 Reviewed-on: https://go-review.googlesource.com/21322 Run-TryBot: David Chase <drchase@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Keith Randall <khr@golang.org>
2016-03-30 14:14:00 -04:00
Debug_append int
Debug_closure int
Debug_panic int
Debug_slice int
Debug_wb int
)
// Debug arguments.
// These can be specified with the -d flag, as in "-d nil"
// to set the debug_checknil variable. In general the list passed
// to -d can be comma-separated.
var debugtab = []struct {
name string
val *int
}{
{"append", &Debug_append}, // print information about append compilation
cmd/compile: ignore OXXX nodes in closure captured vars list Added a debug flag "-d closure" to explain compilation of closures (should this be done some other way? Should we rewrite the "-m" flag to "-d escapes"?) Used this to discover that cause was an OXXX node in the captured vars list, and in turn noticed that OXXX nodes are explicitly ignored in all other processing of captured variables. Couldn't figure out a reproducer, did verify that this OXXX was not caused by an unnamed return value (which is one use of these). Verified lack of heap allocation by examining -S output. Assembly: (runtime/mgc.go:1371) PCDATA $0, $2 (runtime/mgc.go:1371) CALL "".notewakeup(SB) (runtime/mgc.go:1377) LEAQ "".gcBgMarkWorker.func1·f(SB), AX (runtime/mgc.go:1404) MOVQ AX, (SP) (runtime/mgc.go:1404) MOVQ "".autotmp_2242+88(SP), CX (runtime/mgc.go:1404) MOVQ CX, 8(SP) (runtime/mgc.go:1404) LEAQ go.string."GC worker (idle)"(SB), AX (runtime/mgc.go:1404) MOVQ AX, 16(SP) (runtime/mgc.go:1404) MOVQ $16, 24(SP) (runtime/mgc.go:1404) MOVB $20, 32(SP) (runtime/mgc.go:1404) MOVQ $0, 40(SP) (runtime/mgc.go:1404) PCDATA $0, $2 (runtime/mgc.go:1404) CALL "".gopark(SB) Added a check for compiling_runtime to ensure that this is caught in the future. Added a test to test the check. Verified that 1.5.3 did NOT reject the test case when compiled with -+ flag, so this is not a recently added bug. Cause of bug is two-part -- there was no leaking closure detection ever, and instead it relied on capture-of-variables to trigger compiling_runtime test, but closures improved in 1.5.3 so that mere capture of a value did not also capture the variable, which thus allowed closures to escape, as well as this case where the escape was spurious. In fixedbugs/issue14999.go, compare messages for f and g; 1.5.3 would reject g, but not f. 1.4 rejects both because 1.4 heap-allocates parameter x for both. Fixes #14999. Change-Id: I40bcdd27056810628e96763a44f2acddd503aee1 Reviewed-on: https://go-review.googlesource.com/21322 Run-TryBot: David Chase <drchase@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Keith Randall <khr@golang.org>
2016-03-30 14:14:00 -04:00
{"closure", &Debug_closure}, // print information about closure compilation
{"disablenil", &Disable_checknil}, // disable nil 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
}
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.Getgoversion(), 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)
}
func Main() {
defer hidePanic()
goarch = obj.Getgoarch()
Ctxt = obj.Linknew(Thearch.LinkArch)
Ctxt.DiagFunc = Yyerror
bstdout = bufio.NewWriter(os.Stdout)
Ctxt.Bso = bstdout
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"
// real package, referred to by generated runtime calls
Runtimepkg = mkpkg("runtime")
Runtimepkg.Name = "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"
goroot = obj.Getgoroot()
goos = obj.Getgoos()
Nacl = goos == "nacl"
if Nacl {
flag_largemodel = true
}
flag.BoolVar(&compiling_runtime, "+", false, "compiling runtime")
obj.Flagcount("%", "debug non-static initializers", &Debug['%'])
obj.Flagcount("A", "for bootstrapping, allow 'any' type", &Debug['A'])
obj.Flagcount("B", "disable bounds checking", &Debug['B'])
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("M", "debug move generation", &Debug['M'])
obj.Flagcount("N", "disable optimizations", &Debug['N'])
obj.Flagcount("P", "debug peephole optimizer", &Debug['P'])
obj.Flagcount("R", "debug register optimizer", &Debug['R'])
obj.Flagcount("S", "print assembly listing", &Debug['S'])
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("g", "debug code generation", &Debug['g'])
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'])
cmd/compile: add -linkobj flag to allow writing object file in two parts This flag is experimental and the semantics may change even after Go 1.7 is released. There are no changes to code not using the flag. The first part is for reading by future compiles. The second part is for reading by the final link step. Splitting the file this way allows distributed build systems to ship the compile-input part only to compile steps and the linker-input part only to linker steps. The first part is basically just the export data, and the second part is basically everything else. The overall files still have the same broad structure, so that existing tools will work with both halves. It's just that various pieces are empty in the two halves. This also copies the two bits of data the linker needed from export data into the object header proper, so that the linker doesn't need any export data at all. That eliminates a TODO that was left for switching to the binary export data. (Now the linker doesn't need to know about the switch.) The default is still to write out a combined output file. Nothing changes unless you pass -linkobj to the compiler. There is no support in the go command for -linkobj, since the go command doesn't copy objects around. The expectation is that other build systems (like bazel, say) might take advantage of this. The header adjustment and the option for the split output was intended as part of the zip archives, but the zip archives have been cut from Go 1.7. Doing this to the current archives both unblocks one step in the switch to binary export data and enables alternate build systems to experiment with the new flag using the Go 1.7 release. Change-Id: I8b6eab25b8a22b0a266ba0ac6d31e594f3d117f3 Reviewed-on: https://go-review.googlesource.com/22500 Run-TryBot: Russ Cox <rsc@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Ian Lance Taylor <iant@golang.org> Reviewed-by: Robert Griesemer <gri@golang.org>
2016-04-26 21:50:59 -04:00
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")
cmd/compile: switch to compact export format by default builtin.go was auto-generated via go generate; all other changes were manual. The new format reduces the export data size by ~65% on average for the std library packages (and there is still quite a bit of room for improvement). The average time to write export data is reduced by (at least) 62% as measured in one run over the std lib, it is likely more. The average time to read import data is reduced by (at least) 37% as measured in one run over the std lib, it is likely more. There is also room to improve this time. The compiler transparently handles both packages using the old and the new format. Comparing the -S output of the go build for each package via the cmp.bash script (added) shows identical assembly code for all packages, but 6 files show file:line differences: The following files have differences because they use cgo and cgo uses different temp. directories for different builds. Harmless. src/crypto/x509 src/net src/os/user src/runtime/cgo The following files have file:line differences that are not yet fully explained; however the differences exist w/ and w/o new export format (pre-existing condition). See issue #15453. src/go/internal/gccgoimporter src/go/internal/gcimporter In summary, switching to the new export format produces the same package files as before for all practical purposes. How can you tell which one you have (if you care): Open a package (.a) file in an editor. Textual export data starts with a $$ after the header and is more or less legible; binary export data starts with a $$B after the header and is mostly unreadable. A stand-alone decoder (for debugging) is in the works. In case of a problem, please first try reverting back to the old textual format to determine if the cause is the new export format: For a stand-alone compiler invocation: - go tool compile -newexport=0 <files> For a single package: - go build -gcflags="-newexport=0" <pkg> For make/all.bash: - (export GO_GCFLAGS="-newexport=0"; sh make.bash) Fixes #13241. Change-Id: I2588cb463be80af22446bf80c225e92ab79878b8 Reviewed-on: https://go-review.googlesource.com/22123 Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org> Run-TryBot: Robert Griesemer <gri@golang.org> Reviewed-by: Matthew Dempsky <mdempsky@google.com>
2016-04-15 14:14:04 -07:00
flag.BoolVar(&newexport, "newexport", true, "use new export format") // TODO(gri) remove eventually (issue 15323)
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(&Ctxt.LineHist.TrimPathPrefix, "trimpath", "", "remove `prefix` from recorded source file paths")
flag.BoolVar(&safemode, "u", false, "reject unsafe code")
obj.Flagcount("v", "increase debug verbosity", &Debug['v'])
obj.Flagcount("w", "debug type checking", &Debug['w'])
flag.BoolVar(&use_writebarrier, "wb", true, "enable write barrier")
obj.Flagcount("x", "debug lexer", &Debug['x'])
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.BoolVar(&ssaEnabled, "ssa", true, "use SSA backend to generate code")
obj.Flagparse(usage)
Ctxt.Flag_shared = flag_dynlink || flag_shared
Ctxt.Flag_dynlink = flag_dynlink
Ctxt.Flag_optimize = Debug['N'] == 0
Ctxt.Debugasm = int32(Debug['S'])
Ctxt.Debugvlog = int32(Debug['v'])
if flag.NArg() < 1 {
usage()
}
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
}
// parse -d argument
if debugstr != "" {
Split:
for _, name := range strings.Split(debugstr, ",") {
if name == "" {
continue
}
val := 1
if i := strings.Index(name, "="); i >= 0 {
var err error
val, err = strconv.Atoi(name[i+1:])
if err != nil {
log.Fatalf("invalid debug value %v", name)
}
name = name[:i]
}
for _, t := range debugtab {
if t.name == name {
if t.val != nil {
*t.val = val
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)
if err != "" {
log.Fatalf(err)
}
continue Split
}
log.Fatalf("unknown debug key -d %s\n", name)
}
}
// 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']
}
Thearch.Betypeinit()
Widthint = Thearch.LinkArch.IntSize
Widthptr = Thearch.LinkArch.PtrSize
Widthreg = Thearch.LinkArch.RegSize
initUniverse()
blockgen = 1
dclcontext = PEXTERN
nerrors = 0
lexlineno = 1
loadsys()
for _, infile = range flag.Args() {
if trace && Debug['x'] != 0 {
fmt.Printf("--- %s ---\n", infile)
}
linehistpush(infile)
f, err := os.Open(infile)
if err != nil {
fmt.Printf("open %s: %v\n", infile, err)
errorexit()
}
bin := bufio.NewReader(f)
// Skip initial BOM if present.
if r, _, _ := bin.ReadRune(); r != BOM {
bin.UnreadRune()
}
block = 1
iota_ = -1000000
imported_unsafe = false
parse_file(bin)
if nsyntaxerrors != 0 {
errorexit()
}
// Instead of converting EOF into '\n' in getc and count it as an extra line
// for the line history to work, and which then has to be corrected elsewhere,
// just add a line here.
lexlineno++
linehistpop()
f.Close()
}
testdclstack()
mkpackage(localpkg.Name) // final import not used checks
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.
defercheckwidth()
// Don't use range--typecheck can add closures to xtop.
for i := 0; i < len(xtop); i++ {
if xtop[i].Op != ODCL && xtop[i].Op != OAS && xtop[i].Op != OAS2 {
cmd/compile: reduce use of **Node parameters Escape analysis has a hard time with tree-like structures (see #13493 and #14858). This is unlikely to change. As a result, when invoking a function that accepts a **Node parameter, we usually allocate a *Node on the heap. This happens a whole lot. This CL changes functions from taking a **Node to acting more like append: It both modifies the input and returns a replacement for it. Because of the cascading nature of escape analysis, in order to get the benefits, I had to modify almost all such functions. The remaining functions are in racewalk and the backend. I would be happy to update them as well in a separate CL. This CL was created by manually updating the function signatures and the directly impacted bits of code. The callsites were then automatically updated using a bespoke script: https://gist.github.com/josharian/046b1be7aceae244de39 For ease of reviewing and future understanding, this CL is also broken down into four CLs, mailed separately, which show the manual and the automated changes separately. They are CLs 20990, 20991, 20992, and 20993. Passes toolstash -cmp. name old time/op new time/op delta Template 335ms ± 5% 324ms ± 5% -3.35% (p=0.000 n=23+24) Unicode 176ms ± 9% 165ms ± 6% -6.12% (p=0.000 n=23+24) GoTypes 1.10s ± 4% 1.07s ± 2% -2.77% (p=0.000 n=24+24) Compiler 5.31s ± 3% 5.15s ± 3% -2.95% (p=0.000 n=24+24) MakeBash 41.6s ± 1% 41.7s ± 2% ~ (p=0.586 n=23+23) name old alloc/op new alloc/op delta Template 63.3MB ± 0% 62.4MB ± 0% -1.36% (p=0.000 n=25+23) Unicode 42.4MB ± 0% 41.6MB ± 0% -1.99% (p=0.000 n=24+25) GoTypes 220MB ± 0% 217MB ± 0% -1.11% (p=0.000 n=25+25) Compiler 994MB ± 0% 973MB ± 0% -2.08% (p=0.000 n=24+25) name old allocs/op new allocs/op delta Template 681k ± 0% 574k ± 0% -15.71% (p=0.000 n=24+25) Unicode 518k ± 0% 413k ± 0% -20.34% (p=0.000 n=25+24) GoTypes 2.08M ± 0% 1.78M ± 0% -14.62% (p=0.000 n=25+25) Compiler 9.26M ± 0% 7.64M ± 0% -17.48% (p=0.000 n=25+25) name old text-bytes new text-bytes delta HelloSize 578k ± 0% 578k ± 0% ~ (all samples are equal) CmdGoSize 6.46M ± 0% 6.46M ± 0% ~ (all samples are equal) name old data-bytes new data-bytes delta HelloSize 128k ± 0% 128k ± 0% ~ (all samples are equal) CmdGoSize 281k ± 0% 281k ± 0% ~ (all samples are equal) name old exe-bytes new exe-bytes delta HelloSize 921k ± 0% 921k ± 0% ~ (all samples are equal) CmdGoSize 9.86M ± 0% 9.86M ± 0% ~ (all samples are equal) Change-Id: I277d95bd56d51c166ef7f560647aeaa092f3f475 Reviewed-on: https://go-review.googlesource.com/20959 Reviewed-by: Dave Cheney <dave@cheney.net> Reviewed-by: Ian Lance Taylor <iant@golang.org>
2016-03-20 08:03:31 -07:00
xtop[i] = typecheck(xtop[i], Etop)
}
}
// Phase 2: Variable assignments.
// To check interface assignments, depends on phase 1.
// Don't use range--typecheck can add closures to xtop.
for i := 0; i < len(xtop); i++ {
if xtop[i].Op == ODCL || xtop[i].Op == OAS || xtop[i].Op == OAS2 {
cmd/compile: reduce use of **Node parameters Escape analysis has a hard time with tree-like structures (see #13493 and #14858). This is unlikely to change. As a result, when invoking a function that accepts a **Node parameter, we usually allocate a *Node on the heap. This happens a whole lot. This CL changes functions from taking a **Node to acting more like append: It both modifies the input and returns a replacement for it. Because of the cascading nature of escape analysis, in order to get the benefits, I had to modify almost all such functions. The remaining functions are in racewalk and the backend. I would be happy to update them as well in a separate CL. This CL was created by manually updating the function signatures and the directly impacted bits of code. The callsites were then automatically updated using a bespoke script: https://gist.github.com/josharian/046b1be7aceae244de39 For ease of reviewing and future understanding, this CL is also broken down into four CLs, mailed separately, which show the manual and the automated changes separately. They are CLs 20990, 20991, 20992, and 20993. Passes toolstash -cmp. name old time/op new time/op delta Template 335ms ± 5% 324ms ± 5% -3.35% (p=0.000 n=23+24) Unicode 176ms ± 9% 165ms ± 6% -6.12% (p=0.000 n=23+24) GoTypes 1.10s ± 4% 1.07s ± 2% -2.77% (p=0.000 n=24+24) Compiler 5.31s ± 3% 5.15s ± 3% -2.95% (p=0.000 n=24+24) MakeBash 41.6s ± 1% 41.7s ± 2% ~ (p=0.586 n=23+23) name old alloc/op new alloc/op delta Template 63.3MB ± 0% 62.4MB ± 0% -1.36% (p=0.000 n=25+23) Unicode 42.4MB ± 0% 41.6MB ± 0% -1.99% (p=0.000 n=24+25) GoTypes 220MB ± 0% 217MB ± 0% -1.11% (p=0.000 n=25+25) Compiler 994MB ± 0% 973MB ± 0% -2.08% (p=0.000 n=24+25) name old allocs/op new allocs/op delta Template 681k ± 0% 574k ± 0% -15.71% (p=0.000 n=24+25) Unicode 518k ± 0% 413k ± 0% -20.34% (p=0.000 n=25+24) GoTypes 2.08M ± 0% 1.78M ± 0% -14.62% (p=0.000 n=25+25) Compiler 9.26M ± 0% 7.64M ± 0% -17.48% (p=0.000 n=25+25) name old text-bytes new text-bytes delta HelloSize 578k ± 0% 578k ± 0% ~ (all samples are equal) CmdGoSize 6.46M ± 0% 6.46M ± 0% ~ (all samples are equal) name old data-bytes new data-bytes delta HelloSize 128k ± 0% 128k ± 0% ~ (all samples are equal) CmdGoSize 281k ± 0% 281k ± 0% ~ (all samples are equal) name old exe-bytes new exe-bytes delta HelloSize 921k ± 0% 921k ± 0% ~ (all samples are equal) CmdGoSize 9.86M ± 0% 9.86M ± 0% ~ (all samples are equal) Change-Id: I277d95bd56d51c166ef7f560647aeaa092f3f475 Reviewed-on: https://go-review.googlesource.com/20959 Reviewed-by: Dave Cheney <dave@cheney.net> Reviewed-by: Ian Lance Taylor <iant@golang.org>
2016-03-20 08:03:31 -07:00
xtop[i] = typecheck(xtop[i], Etop)
}
}
resumecheckwidth()
// Phase 3: Type check function bodies.
// Don't use range--typecheck can add closures to xtop.
for i := 0; i < len(xtop); i++ {
if xtop[i].Op == ODCLFUNC || xtop[i].Op == OCLOSURE {
Curfn = xtop[i]
decldepth = 1
saveerrors()
typecheckslice(Curfn.Nbody.Slice(), Etop)
checkreturn(Curfn)
if nerrors != 0 {
Curfn.Nbody.Set(nil) // type errors; do not compile
}
}
}
// Phase 4: Decide how to capture closed variables.
// This needs to run before escape analysis,
// because variables captured by value do not escape.
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
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 n.Op == ODCLFUNC {
caninl(n)
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.
escapes(xtop)
// 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.
for _, n := range xtop {
if n.Op == ODCLFUNC && n.Func.Closure != nil {
Curfn = n
transformclosure(n)
}
}
Curfn = nil
// Phase 8: Compile top level functions.
// Don't use range--walk can add functions to xtop.
for i := 0; i < len(xtop); i++ {
if xtop[i].Op == ODCLFUNC {
funccompile(xtop[i])
}
}
if nsavederrors+nerrors == 0 {
fninit(xtop)
}
if compiling_runtime {
checknowritebarrierrec()
}
// Phase 9: Check external declarations.
for i, n := range externdcl {
if n.Op == ONAME {
cmd/compile: reduce use of **Node parameters Escape analysis has a hard time with tree-like structures (see #13493 and #14858). This is unlikely to change. As a result, when invoking a function that accepts a **Node parameter, we usually allocate a *Node on the heap. This happens a whole lot. This CL changes functions from taking a **Node to acting more like append: It both modifies the input and returns a replacement for it. Because of the cascading nature of escape analysis, in order to get the benefits, I had to modify almost all such functions. The remaining functions are in racewalk and the backend. I would be happy to update them as well in a separate CL. This CL was created by manually updating the function signatures and the directly impacted bits of code. The callsites were then automatically updated using a bespoke script: https://gist.github.com/josharian/046b1be7aceae244de39 For ease of reviewing and future understanding, this CL is also broken down into four CLs, mailed separately, which show the manual and the automated changes separately. They are CLs 20990, 20991, 20992, and 20993. Passes toolstash -cmp. name old time/op new time/op delta Template 335ms ± 5% 324ms ± 5% -3.35% (p=0.000 n=23+24) Unicode 176ms ± 9% 165ms ± 6% -6.12% (p=0.000 n=23+24) GoTypes 1.10s ± 4% 1.07s ± 2% -2.77% (p=0.000 n=24+24) Compiler 5.31s ± 3% 5.15s ± 3% -2.95% (p=0.000 n=24+24) MakeBash 41.6s ± 1% 41.7s ± 2% ~ (p=0.586 n=23+23) name old alloc/op new alloc/op delta Template 63.3MB ± 0% 62.4MB ± 0% -1.36% (p=0.000 n=25+23) Unicode 42.4MB ± 0% 41.6MB ± 0% -1.99% (p=0.000 n=24+25) GoTypes 220MB ± 0% 217MB ± 0% -1.11% (p=0.000 n=25+25) Compiler 994MB ± 0% 973MB ± 0% -2.08% (p=0.000 n=24+25) name old allocs/op new allocs/op delta Template 681k ± 0% 574k ± 0% -15.71% (p=0.000 n=24+25) Unicode 518k ± 0% 413k ± 0% -20.34% (p=0.000 n=25+24) GoTypes 2.08M ± 0% 1.78M ± 0% -14.62% (p=0.000 n=25+25) Compiler 9.26M ± 0% 7.64M ± 0% -17.48% (p=0.000 n=25+25) name old text-bytes new text-bytes delta HelloSize 578k ± 0% 578k ± 0% ~ (all samples are equal) CmdGoSize 6.46M ± 0% 6.46M ± 0% ~ (all samples are equal) name old data-bytes new data-bytes delta HelloSize 128k ± 0% 128k ± 0% ~ (all samples are equal) CmdGoSize 281k ± 0% 281k ± 0% ~ (all samples are equal) name old exe-bytes new exe-bytes delta HelloSize 921k ± 0% 921k ± 0% ~ (all samples are equal) CmdGoSize 9.86M ± 0% 9.86M ± 0% ~ (all samples are equal) Change-Id: I277d95bd56d51c166ef7f560647aeaa092f3f475 Reviewed-on: https://go-review.googlesource.com/20959 Reviewed-by: Dave Cheney <dave@cheney.net> Reviewed-by: Ian Lance Taylor <iant@golang.org>
2016-03-20 08:03:31 -07:00
externdcl[i] = typecheck(externdcl[i], Erv)
}
}
if nerrors+nsavederrors != 0 {
errorexit()
}
dumpobj()
if asmhdr != "" {
dumpasmhdr()
}
if nerrors+nsavederrors != 0 {
errorexit()
}
Flusherrors()
}
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 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", goroot, goos, 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", goroot, goos, 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 and unsafe functions,
// so that the compiler can generate calls to them,
// but does not make the names "runtime" or "unsafe" visible as packages.
func loadsys() {
if Debug['A'] != 0 {
return
}
block = 1
iota_ = -1000000
incannedimport = 1
// The first byte in the binary export format is a 'c' or 'd'
// specifying the encoding format. We could just check that
// byte, but this is a perhaps more robust. Also, it is not
// speed-critical.
// TODO(gri) simplify once textual export format has gone
if strings.HasPrefix(runtimeimport, "package") {
// textual export format
importpkg = Runtimepkg
parse_import(bufio.NewReader(strings.NewReader(runtimeimport)), nil)
importpkg = unsafepkg
parse_import(bufio.NewReader(strings.NewReader(unsafeimport)), nil)
} else {
// binary export format
importpkg = Runtimepkg
Import(bufio.NewReader(strings.NewReader(runtimeimport)))
importpkg = unsafepkg
Import(bufio.NewReader(strings.NewReader(unsafeimport)))
}
importpkg = nil
incannedimport = 0
}
func importfile(f *Val, indent []byte) {
if importpkg != nil {
Fatalf("importpkg not nil")
}
path_, ok := f.U.(string)
if !ok {
Yyerror("import statement not a string")
return
}
if len(path_) == 0 {
Yyerror("import path is empty")
return
}
if isbadimport(path_) {
return
}
// 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()
}
importpkg = unsafepkg
imported_unsafe = true
return
}
if islocalname(path_) {
if path_[0] == '/' {
Yyerror("import path cannot be absolute path")
return
}
prefix := Ctxt.Pathname
if localimport != "" {
prefix = localimport
}
path_ = path.Join(prefix, path_)
if isbadimport(path_) {
return
}
}
file, found := findpkg(path_)
if !found {
Yyerror("can't find import: %q", path_)
errorexit()
}
importpkg = mkpkg(path_)
if importpkg.Imported {
return
}
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)
if strings.HasSuffix(file, ".a") {
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 ") {
cmd/compile: add -linkobj flag to allow writing object file in two parts This flag is experimental and the semantics may change even after Go 1.7 is released. There are no changes to code not using the flag. The first part is for reading by future compiles. The second part is for reading by the final link step. Splitting the file this way allows distributed build systems to ship the compile-input part only to compile steps and the linker-input part only to linker steps. The first part is basically just the export data, and the second part is basically everything else. The overall files still have the same broad structure, so that existing tools will work with both halves. It's just that various pieces are empty in the two halves. This also copies the two bits of data the linker needed from export data into the object header proper, so that the linker doesn't need any export data at all. That eliminates a TODO that was left for switching to the binary export data. (Now the linker doesn't need to know about the switch.) The default is still to write out a combined output file. Nothing changes unless you pass -linkobj to the compiler. There is no support in the go command for -linkobj, since the go command doesn't copy objects around. The expectation is that other build systems (like bazel, say) might take advantage of this. The header adjustment and the option for the split output was intended as part of the zip archives, but the zip archives have been cut from Go 1.7. Doing this to the current archives both unblocks one step in the switch to binary export data and enables alternate build systems to experiment with the new flag using the Go 1.7 release. Change-Id: I8b6eab25b8a22b0a266ba0ac6d31e594f3d117f3 Reviewed-on: https://go-review.googlesource.com/22500 Run-TryBot: Russ Cox <rsc@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Ian Lance Taylor <iant@golang.org> Reviewed-by: Robert Griesemer <gri@golang.org>
2016-04-26 21:50:59 -04:00
Yyerror("import %s: not a go object file: %s", file, p)
errorexit()
}
q := fmt.Sprintf("%s %s %s %s", obj.Getgoos(), obj.Getgoarch(), obj.Getgoversion(), obj.Expstring())
if p[10:] != q {
Yyerror("import %s: object is [%s] expected [%s]", file, p[10:], q)
errorexit()
}
}
// assume files move (get installed)
// so don't record the full path.
linehistpragma(file[len(file)-len(path_)-2:]) // acts as #pragma lib
// In the importfile, if we find:
// $$\n (old format): position the input right after $$\n and return
// $$B\n (new 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':
// old export format
parse_import(imp, indent)
case 'B':
// new export format
cmd/compile: export inlined function bodies Completed implementation for exporting inlined functions using the new binary export format. This change passes (export GO_GCFLAGS=-newexport; make all.bash) but for gc's builtin_test.go which we need to adjust before enabling this code by default. For a high-level description of the export format see the comment at the top of bexport.go. Major changes: 1) The export format for the platform independent export data changed: When we export inlined function bodies, additional objects (other functions, types, etc.) that are referred to by the function bodies will need to be exported. While this doesn't affect the platform-independent portion directly, it adds more objects to the exportlist while we are exporting. Instead of trying to sort the objects into groups, just export objects as they appear in the export list. This is slightly less compact (one extra byte per object), but it is simpler and much more flexible. 2) The export format contains now three sections: 1) The plat- form independent objects, 2) the objects pulled in for export via inlined function bodies, and 3) the inlined function bodies. 3) Completed the exporting and importing code for inlined function bodies. The format is completely compiler-specific and easily changeable w/o affecting other tools. There is still quite a bit of room for denser encoding. This can happen at any time in the future. This change contains also the adjustments for go/internal/gcimporter, necessary because of the export format change 1) mentioned above. For #13241. Change-Id: I86bca0bd984b12ccf13d0d30892e6e25f6d04ed5 Reviewed-on: https://go-review.googlesource.com/21172 Run-TryBot: Robert Griesemer <gri@golang.org> Reviewed-by: Matthew Dempsky <mdempsky@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org>
2016-03-18 17:21:32 -07:00
if Debug_export != 0 {
fmt.Printf("importing %s (%s)\n", path_, file)
}
imp.ReadByte() // skip \n after $$B
Import(imp)
default:
Yyerror("no import in %q", path_)
errorexit()
}
if safemode && !importpkg.Safe {
Yyerror("cannot import unsafe package %q", importpkg.Path)
}
}
func pkgnotused(lineno int32, 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)
}
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.Lineno, s.Def.Name.Pkg.Path, s.Name)
}
s.Def = nil
continue
}
if s.Def.Sym != s {
// 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.Lineno, s.Def.Name.Pack.Name.Pkg.Path, "")
s.Def.Name.Pack.Used = true
}
s.Def = nil
continue
}
}
}
if outfile == "" {
p := infile
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
}
}