From 756b9ce3a5a518555114b0e023eb1674084b38e1 Mon Sep 17 00:00:00 2001 From: Josh Bleecher Snyder Date: Sun, 19 Mar 2017 08:27:26 -0700 Subject: [PATCH] cmd/compile: add initial backend concurrency support MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit This CL adds initial support for concurrent backend compilation. BACKGROUND The compiler currently consists (very roughly) of the following phases: 1. Initialization. 2. Lexing and parsing into the cmd/compile/internal/syntax AST. 3. Translation into the cmd/compile/internal/gc AST. 4. Some gc AST passes: typechecking, escape analysis, inlining, closure handling, expression evaluation ordering (order.go), and some lowering and optimization (walk.go). 5. Translation into the cmd/compile/internal/ssa SSA form. 6. Optimization and lowering of SSA form. 7. Translation from SSA form to assembler instructions. 8. Translation from assembler instructions to machine code. 9. Writing lots of output: machine code, DWARF symbols, type and reflection info, export data. Phase 2 was already concurrent as of Go 1.8. Phase 3 is planned for eventual removal; we hope to go straight from syntax AST to SSA. Phases 5–8 are per-function; this CL adds support for processing multiple functions concurrently. The slowest phases in the compiler are 5 and 6, so this offers the opportunity for some good speed-ups. Unfortunately, it's not quite that straightforward. In the current compiler, the latter parts of phase 4 (order, walk) are done function-at-a-time as needed. Making order and walk concurrency-safe proved hard, and they're not particularly slow, so there wasn't much reward. To enable phases 5–8 to be done concurrently, when concurrent backend compilation is requested, we complete phase 4 for all functions before starting later phases for any functions. Also, in reality, we automatically generate new functions in phase 9, such as method wrappers and equality and has routines. Those new functions then go through phases 4–8. This CL disables concurrent backend compilation after the first, big, user-provided batch of functions has been compiled. This is done to keep things simple, and because the autogenerated functions tend to be small, few, simple, and fast to compile. USAGE Concurrent backend compilation still defaults to off. To set the number of functions that may be backend-compiled concurrently, use the compiler flag -c. In future work, cmd/go will automatically set -c. Furthermore, this CL has been intentionally written so that the c=1 path has no backend concurrency whatsoever, not even spawning any goroutines. This helps ensure that, should problems arise late in the development cycle, we can simply have cmd/go set c=1 always, and revert to the original compiler behavior. MUTEXES Most of the work required to make concurrent backend compilation safe has occurred over the past month. This CL adds a handful of mutexes to get the rest of the way there; they are the mutexes that I didn't see a clean way to avoid. Some of them may still be eliminable in future work. In no particular order: * gc.funcsymsmu. The global funcsyms slice is populated lazily when we need function symbols for closures. This occurs during gc AST to SSA translation. The function funcsym also does a package lookup, which is a source of races on types.Pkg.Syms; funcsymsmu also covers that package lookup. This mutex is low priority: it adds a single global, it is in an infrequently used code path, and it is low contention. Since funcsyms may now be added in any order, we must sort them to preserve reproducible builds. * gc.largeStackFramesMu. We don't discover until after SSA compilation that a function's stack frame is gigantic. Recording that error happens basically never, but it does happen concurrently. Fix with a low priority mutex and sorting. * obj.Link.hashmu. ctxt.hash stores the mapping from types.Syms (compiler symbols) to obj.LSyms (linker symbols). It is accessed fairly heavily through all the phases. This is the only heavily contended mutex. * gc.signatlistmu. The global signatlist map is populated with types through several of the concurrent phases, including notably via ngotype during DWARF generation. It is low priority for removal. * gc.typepkgmu. Looking up symbols in the types package happens a fair amount during backend compilation and DWARF generation, particularly via ngotype. This mutex helps us to avoid a broader mutex on types.Pkg.Syms. It has low-to-moderate contention. * types.internedStringsmu. gc AST to SSA conversion and some SSA work introduce new autotmps. Those autotmps have their names interned to reduce allocations. That interning requires protecting types.internedStrings. The autotmp names are heavily re-used, and the mutex overhead and contention here are low, so it is probably a worthwhile performance optimization to keep this mutex. TESTING I have been testing this code locally by running 'go install -race cmd/compile' and then doing 'go build -a -gcflags=-c=128 std cmd' for all architectures and a variety of compiler flags. This obviously needs to be made part of the builders, but it is too expensive to make part of all.bash. I have filed #19962 for this. REPRODUCIBLE BUILDS This version of the compiler generates reproducible builds. Testing reproducible builds also needs automation, however, and is also too expensive for all.bash. This is #19961. Also of note is that some of the compiler flags used by 'toolstash -cmp' are currently incompatible with concurrent backend compilation. They still work fine with c=1. Time will tell whether this is a problem. NEXT STEPS * Continue to find and fix races and bugs, using a combination of code inspection, fuzzing, and hopefully some community experimentation. I do not know of any outstanding races, but there probably are some. * Improve testing. * Improve performance, for many values of c. * Integrate with cmd/go and fine tune. * Support concurrent compilation with the -race flag. It is a sad irony that it does not yet work. * Minor code cleanup that has been deferred during the last month due to uncertainty about the ultimate shape of this CL. PERFORMANCE Here's the buried lede, at last. :) All benchmarks are from my 8 core 2.9 GHz Intel Core i7 darwin/amd64 laptop. First, going from tip to this CL with c=1 has almost no impact. name old time/op new time/op delta Template 195ms ± 3% 194ms ± 5% ~ (p=0.370 n=30+29) Unicode 86.6ms ± 3% 87.0ms ± 7% ~ (p=0.958 n=29+30) GoTypes 548ms ± 3% 555ms ± 4% +1.35% (p=0.001 n=30+28) Compiler 2.51s ± 2% 2.54s ± 2% +1.17% (p=0.000 n=28+30) SSA 5.16s ± 3% 5.16s ± 2% ~ (p=0.910 n=30+29) Flate 124ms ± 5% 124ms ± 4% ~ (p=0.947 n=30+30) GoParser 146ms ± 3% 146ms ± 3% ~ (p=0.150 n=29+28) Reflect 354ms ± 3% 352ms ± 4% ~ (p=0.096 n=29+29) Tar 107ms ± 5% 106ms ± 3% ~ (p=0.370 n=30+29) XML 200ms ± 4% 201ms ± 4% ~ (p=0.313 n=29+28) [Geo mean] 332ms 333ms +0.10% name old user-time/op new user-time/op delta Template 227ms ± 5% 225ms ± 5% ~ (p=0.457 n=28+27) Unicode 109ms ± 4% 109ms ± 5% ~ (p=0.758 n=29+29) GoTypes 713ms ± 4% 721ms ± 5% ~ (p=0.051 n=30+29) Compiler 3.36s ± 2% 3.38s ± 3% ~ (p=0.146 n=30+30) SSA 7.46s ± 3% 7.47s ± 3% ~ (p=0.804 n=30+29) Flate 146ms ± 7% 147ms ± 3% ~ (p=0.833 n=29+27) GoParser 179ms ± 5% 179ms ± 5% ~ (p=0.866 n=30+30) Reflect 431ms ± 4% 429ms ± 4% ~ (p=0.593 n=29+30) Tar 124ms ± 5% 123ms ± 5% ~ (p=0.140 n=29+29) XML 243ms ± 4% 242ms ± 7% ~ (p=0.404 n=29+29) [Geo mean] 415ms 415ms +0.02% name old obj-bytes new obj-bytes delta Template 382k ± 0% 382k ± 0% ~ (all equal) Unicode 203k ± 0% 203k ± 0% ~ (all equal) GoTypes 1.18M ± 0% 1.18M ± 0% ~ (all equal) Compiler 3.98M ± 0% 3.98M ± 0% ~ (all equal) SSA 8.28M ± 0% 8.28M ± 0% ~ (all equal) Flate 230k ± 0% 230k ± 0% ~ (all equal) GoParser 287k ± 0% 287k ± 0% ~ (all equal) Reflect 1.00M ± 0% 1.00M ± 0% ~ (all equal) Tar 190k ± 0% 190k ± 0% ~ (all equal) XML 416k ± 0% 416k ± 0% ~ (all equal) [Geo mean] 660k 660k +0.00% Comparing this CL to itself, from c=1 to c=2 improves real times 20-30%, costs 5-10% more CPU time, and adds about 2% alloc. The allocation increase comes from allocating more ssa.Caches. name old time/op new time/op delta Template 202ms ± 3% 149ms ± 3% -26.15% (p=0.000 n=49+49) Unicode 87.4ms ± 4% 84.2ms ± 3% -3.68% (p=0.000 n=48+48) GoTypes 560ms ± 2% 398ms ± 2% -28.96% (p=0.000 n=49+49) Compiler 2.46s ± 3% 1.76s ± 2% -28.61% (p=0.000 n=48+46) SSA 6.17s ± 2% 4.04s ± 1% -34.52% (p=0.000 n=49+49) Flate 126ms ± 3% 92ms ± 2% -26.81% (p=0.000 n=49+48) GoParser 148ms ± 4% 107ms ± 2% -27.78% (p=0.000 n=49+48) Reflect 361ms ± 3% 281ms ± 3% -22.10% (p=0.000 n=49+49) Tar 109ms ± 4% 86ms ± 3% -20.81% (p=0.000 n=49+47) XML 204ms ± 3% 144ms ± 2% -29.53% (p=0.000 n=48+45) name old user-time/op new user-time/op delta Template 246ms ± 9% 246ms ± 4% ~ (p=0.401 n=50+48) Unicode 109ms ± 4% 111ms ± 4% +1.47% (p=0.000 n=44+50) GoTypes 728ms ± 3% 765ms ± 3% +5.04% (p=0.000 n=46+50) Compiler 3.33s ± 3% 3.41s ± 2% +2.31% (p=0.000 n=49+48) SSA 8.52s ± 2% 9.11s ± 2% +6.93% (p=0.000 n=49+47) Flate 149ms ± 4% 161ms ± 3% +8.13% (p=0.000 n=50+47) GoParser 181ms ± 5% 192ms ± 2% +6.40% (p=0.000 n=49+46) Reflect 452ms ± 9% 474ms ± 2% +4.99% (p=0.000 n=50+48) Tar 126ms ± 6% 136ms ± 4% +7.95% (p=0.000 n=50+49) XML 247ms ± 5% 264ms ± 3% +6.94% (p=0.000 n=48+50) name old alloc/op new alloc/op delta Template 38.8MB ± 0% 39.3MB ± 0% +1.48% (p=0.008 n=5+5) Unicode 29.8MB ± 0% 30.2MB ± 0% +1.19% (p=0.008 n=5+5) GoTypes 113MB ± 0% 114MB ± 0% +0.69% (p=0.008 n=5+5) Compiler 443MB ± 0% 447MB ± 0% +0.95% (p=0.008 n=5+5) SSA 1.25GB ± 0% 1.26GB ± 0% +0.89% (p=0.008 n=5+5) Flate 25.3MB ± 0% 25.9MB ± 1% +2.35% (p=0.008 n=5+5) GoParser 31.7MB ± 0% 32.2MB ± 0% +1.59% (p=0.008 n=5+5) Reflect 78.2MB ± 0% 78.9MB ± 0% +0.91% (p=0.008 n=5+5) Tar 26.6MB ± 0% 27.0MB ± 0% +1.80% (p=0.008 n=5+5) XML 42.4MB ± 0% 43.4MB ± 0% +2.35% (p=0.008 n=5+5) name old allocs/op new allocs/op delta Template 379k ± 0% 378k ± 0% ~ (p=0.421 n=5+5) Unicode 322k ± 0% 321k ± 0% ~ (p=0.222 n=5+5) GoTypes 1.14M ± 0% 1.14M ± 0% ~ (p=0.548 n=5+5) Compiler 4.12M ± 0% 4.11M ± 0% -0.14% (p=0.032 n=5+5) SSA 9.72M ± 0% 9.72M ± 0% ~ (p=0.421 n=5+5) Flate 234k ± 1% 234k ± 0% ~ (p=0.421 n=5+5) GoParser 316k ± 1% 315k ± 0% ~ (p=0.222 n=5+5) Reflect 980k ± 0% 979k ± 0% ~ (p=0.095 n=5+5) Tar 249k ± 1% 249k ± 1% ~ (p=0.841 n=5+5) XML 392k ± 0% 391k ± 0% ~ (p=0.095 n=5+5) From c=1 to c=4, real time is down ~40%, CPU usage up 10-20%, alloc up ~5%: name old time/op new time/op delta Template 203ms ± 3% 131ms ± 5% -35.45% (p=0.000 n=50+50) Unicode 87.2ms ± 4% 84.1ms ± 2% -3.61% (p=0.000 n=48+47) GoTypes 560ms ± 4% 310ms ± 2% -44.65% (p=0.000 n=50+49) Compiler 2.47s ± 3% 1.41s ± 2% -43.10% (p=0.000 n=50+46) SSA 6.17s ± 2% 3.20s ± 2% -48.06% (p=0.000 n=49+49) Flate 126ms ± 4% 74ms ± 2% -41.06% (p=0.000 n=49+48) GoParser 148ms ± 4% 89ms ± 3% -39.97% (p=0.000 n=49+50) Reflect 360ms ± 3% 242ms ± 3% -32.81% (p=0.000 n=49+49) Tar 108ms ± 4% 73ms ± 4% -32.48% (p=0.000 n=50+49) XML 203ms ± 3% 119ms ± 3% -41.56% (p=0.000 n=49+48) name old user-time/op new user-time/op delta Template 246ms ± 9% 287ms ± 9% +16.98% (p=0.000 n=50+50) Unicode 109ms ± 4% 118ms ± 5% +7.56% (p=0.000 n=46+50) GoTypes 735ms ± 4% 806ms ± 2% +9.62% (p=0.000 n=50+50) Compiler 3.34s ± 4% 3.56s ± 2% +6.78% (p=0.000 n=49+49) SSA 8.54s ± 3% 10.04s ± 3% +17.55% (p=0.000 n=50+50) Flate 149ms ± 6% 176ms ± 3% +17.82% (p=0.000 n=50+48) GoParser 181ms ± 5% 213ms ± 3% +17.47% (p=0.000 n=50+50) Reflect 453ms ± 6% 499ms ± 2% +10.11% (p=0.000 n=50+48) Tar 126ms ± 5% 149ms ±11% +18.76% (p=0.000 n=50+50) XML 246ms ± 5% 287ms ± 4% +16.53% (p=0.000 n=49+50) name old alloc/op new alloc/op delta Template 38.8MB ± 0% 40.4MB ± 0% +4.21% (p=0.008 n=5+5) Unicode 29.8MB ± 0% 30.9MB ± 0% +3.68% (p=0.008 n=5+5) GoTypes 113MB ± 0% 116MB ± 0% +2.71% (p=0.008 n=5+5) Compiler 443MB ± 0% 455MB ± 0% +2.75% (p=0.008 n=5+5) SSA 1.25GB ± 0% 1.27GB ± 0% +1.84% (p=0.008 n=5+5) Flate 25.3MB ± 0% 26.9MB ± 1% +6.31% (p=0.008 n=5+5) GoParser 31.7MB ± 0% 33.2MB ± 0% +4.61% (p=0.008 n=5+5) Reflect 78.2MB ± 0% 80.2MB ± 0% +2.53% (p=0.008 n=5+5) Tar 26.6MB ± 0% 27.9MB ± 0% +5.19% (p=0.008 n=5+5) XML 42.4MB ± 0% 44.6MB ± 0% +5.20% (p=0.008 n=5+5) name old allocs/op new allocs/op delta Template 380k ± 0% 379k ± 0% -0.39% (p=0.032 n=5+5) Unicode 321k ± 0% 321k ± 0% ~ (p=0.841 n=5+5) GoTypes 1.14M ± 0% 1.14M ± 0% ~ (p=0.421 n=5+5) Compiler 4.12M ± 0% 4.14M ± 0% +0.52% (p=0.008 n=5+5) SSA 9.72M ± 0% 9.76M ± 0% +0.37% (p=0.008 n=5+5) Flate 234k ± 1% 234k ± 1% ~ (p=0.690 n=5+5) GoParser 316k ± 0% 317k ± 1% ~ (p=0.841 n=5+5) Reflect 981k ± 0% 981k ± 0% ~ (p=1.000 n=5+5) Tar 250k ± 0% 249k ± 1% ~ (p=0.151 n=5+5) XML 393k ± 0% 392k ± 0% ~ (p=0.056 n=5+5) Going beyond c=4 on my machine tends to increase CPU time and allocs without impacting real time. The CPU time numbers matter, because when there are many concurrent compilation processes, that will impact the overall throughput. The numbers above are in many ways the best case scenario; we can take full advantage of all cores. Fortunately, the most common compilation scenario is incremental re-compilation of a single package during a build/test cycle. Updates #15756 Change-Id: I6725558ca2069edec0ac5b0d1683105a9fff6bea Reviewed-on: https://go-review.googlesource.com/40693 Reviewed-by: Matthew Dempsky Reviewed-by: Robert Griesemer Run-TryBot: Brad Fitzpatrick TryBot-Result: Gobot Gobot --- src/cmd/compile/internal/gc/dcl.go | 11 +++++ src/cmd/compile/internal/gc/go.go | 6 ++- src/cmd/compile/internal/gc/gsubr.go | 6 ++- src/cmd/compile/internal/gc/main.go | 61 +++++++++++++++++++++++++- src/cmd/compile/internal/gc/obj.go | 3 ++ src/cmd/compile/internal/gc/pgen.go | 58 ++++++++++++++++++++++-- src/cmd/compile/internal/gc/reflect.go | 25 ++++++++--- src/cmd/compile/internal/gc/ssa.go | 11 ++--- src/cmd/compile/internal/gc/subr.go | 6 ++- src/cmd/compile/internal/types/pkg.go | 8 +++- src/cmd/internal/obj/link.go | 2 + src/cmd/internal/obj/sym.go | 2 + 12 files changed, 177 insertions(+), 22 deletions(-) diff --git a/src/cmd/compile/internal/gc/dcl.go b/src/cmd/compile/internal/gc/dcl.go index 061644443e..471a7e578b 100644 --- a/src/cmd/compile/internal/gc/dcl.go +++ b/src/cmd/compile/internal/gc/dcl.go @@ -1058,6 +1058,16 @@ func funcsymname(s *types.Sym) string { // funcsym returns s·f. func funcsym(s *types.Sym) *types.Sym { + // funcsymsmu here serves to protect not just mutations of funcsyms (below), + // but also the package lookup of the func sym name, + // since this function gets called concurrently from the backend. + // There are no other concurrent package lookups in the backend, + // except for the types package, which is protected separately. + // Reusing funcsymsmu to also cover this package lookup + // avoids a general, broader, expensive package lookup mutex. + // Note makefuncsym also does package look-up of func sym names, + // but that it is only called serially, from the front end. + funcsymsmu.Lock() sf, existed := s.Pkg.LookupOK(funcsymname(s)) // Don't export s·f when compiling for dynamic linking. // When dynamically linking, the necessary function @@ -1066,6 +1076,7 @@ func funcsym(s *types.Sym) *types.Sym { if !Ctxt.Flag_dynlink && !existed { funcsyms = append(funcsyms, s) } + funcsymsmu.Unlock() return sf } diff --git a/src/cmd/compile/internal/gc/go.go b/src/cmd/compile/internal/gc/go.go index f795ce0bf0..1bfe499ab7 100644 --- a/src/cmd/compile/internal/gc/go.go +++ b/src/cmd/compile/internal/gc/go.go @@ -10,6 +10,7 @@ import ( "cmd/internal/bio" "cmd/internal/obj" "cmd/internal/src" + "sync" ) const ( @@ -177,7 +178,10 @@ var exportlist []*Node var importlist []*Node // imported functions and methods with inlinable bodies -var funcsyms []*types.Sym +var ( + funcsymsmu sync.Mutex // protects funcsyms and associated package lookups (see func funcsym) + funcsyms []*types.Sym +) var dclcontext Class // PEXTERN/PAUTO diff --git a/src/cmd/compile/internal/gc/gsubr.go b/src/cmd/compile/internal/gc/gsubr.go index f91a1bd916..2c6d45fd8d 100644 --- a/src/cmd/compile/internal/gc/gsubr.go +++ b/src/cmd/compile/internal/gc/gsubr.go @@ -51,10 +51,12 @@ type Progs struct { } // newProgs returns a new Progs for fn. -func newProgs(fn *Node) *Progs { +// worker indicates which of the backend workers will use the Progs. +func newProgs(fn *Node, worker int) *Progs { pp := new(Progs) if Ctxt.CanReuseProgs() { - pp.progcache = sharedProgArray[:] + sz := len(sharedProgArray) / nBackendWorkers + pp.progcache = sharedProgArray[sz*worker : sz*(worker+1)] } pp.curfn = fn diff --git a/src/cmd/compile/internal/gc/main.go b/src/cmd/compile/internal/gc/main.go index c50b6a054d..f1bb6bca0e 100644 --- a/src/cmd/compile/internal/gc/main.go +++ b/src/cmd/compile/internal/gc/main.go @@ -184,8 +184,7 @@ func Main(archInit func(*Arch)) { objabi.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") - var dashc int - flag.IntVar(&dashc, "c", 0, "makes -c work") + 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", true, "generate DWARF symbols") @@ -281,6 +280,12 @@ func Main(archInit func(*Arch)) { 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) + } // parse -d argument if debugstr != "" { @@ -572,9 +577,23 @@ func Main(archInit func(*Arch)) { fninit(xtop) } + compileFunctions() + + // We autogenerate and compile some small functions + // such as method wrappers and equality/hash routines + // while exporting code. + // Disable concurrent compilation from here on, + // at least until this convoluted structure has been unwound. + nBackendWorkers = 1 + if compiling_runtime { checknowritebarrierrec() } + + // Check whether any of the functions we have compiled have gigantic stack frames. + obj.SortSlice(largeStackFrames, func(i, j int) bool { + return largeStackFrames[i].Before(largeStackFrames[j]) + }) for _, largePos := range largeStackFrames { yyerrorl(largePos, "stack frame too large (>2GB)") } @@ -599,6 +618,10 @@ func Main(archInit func(*Arch)) { dumpasmhdr() } + if len(compilequeue) != 0 { + Fatalf("%d uncompiled functions", len(compilequeue)) + } + if nerrors+nsavederrors != 0 { errorexit() } @@ -1047,3 +1070,37 @@ func clearImports() { 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; mark them as such here. +var concurrentFlagOK = [256]bool{ + 'B': true, // disabled bounds checking + 'C': true, // disable printing of columns in error messages + 'I': true, // add `directory` to import search path + 'N': true, // disable optimizations + 'l': true, // disable inlining +} + +func concurrentBackendAllowed() bool { + for i, x := range Debug { + if x != 0 && !concurrentFlagOK[i] { + return false + } + } + // Debug_asm by itself is ok, because all printing occurs + // while writing the object file, and that is non-concurrent. + // Adding Debug_vlog, however, causes Debug_asm to also print + // while flushing the plist, which happens concurrently. + if Debug_vlog || debugstr != "" || debuglive > 0 { + return false + } + // TODO: test and add builders for GOEXPERIMENT values, and enable + if os.Getenv("GOEXPERIMENT") != "" { + return false + } + // TODO: fix races and enable the following flags + if Ctxt.Flag_shared || Ctxt.Flag_dynlink || flag_race { + return false + } + return true +} diff --git a/src/cmd/compile/internal/gc/obj.go b/src/cmd/compile/internal/gc/obj.go index 5064479fbb..29cdb5684e 100644 --- a/src/cmd/compile/internal/gc/obj.go +++ b/src/cmd/compile/internal/gc/obj.go @@ -224,6 +224,9 @@ func dumpglobls() { ggloblnod(n) } + obj.SortSlice(funcsyms, func(i, j int) bool { + return funcsyms[i].LinksymName() < funcsyms[j].LinksymName() + }) for _, s := range funcsyms { sf := s.Pkg.Lookup(funcsymname(s)).Linksym() dsymptr(sf, 0, s.Linksym(), 0) diff --git a/src/cmd/compile/internal/gc/pgen.go b/src/cmd/compile/internal/gc/pgen.go index c7e27cb947..f98289bf0b 100644 --- a/src/cmd/compile/internal/gc/pgen.go +++ b/src/cmd/compile/internal/gc/pgen.go @@ -14,10 +14,16 @@ import ( "cmd/internal/sys" "fmt" "sort" + "sync" ) // "Portable" code generation. +var ( + nBackendWorkers int // number of concurrent backend workers, set by a compiler flag + compilequeue []*Node // functions waiting to be compiled +) + func emitptrargsmap() { if Curfn.funcname() == "_" { return @@ -207,20 +213,66 @@ func compile(fn *Node) { // Set up the function's LSym early to avoid data races with the assemblers. fn.Func.initLSym() - // Build an SSA backend function. - ssafn := buildssa(fn) - pp := newProgs(fn) + if compilenow() { + compileSSA(fn, 0) + } else { + compilequeue = append(compilequeue, fn) + } +} + +// compilenow reports whether to compile immediately. +// If functions are not compiled immediately, +// they are enqueued in compilequeue, +// which is drained by compileFunctions. +func compilenow() bool { + return nBackendWorkers == 1 +} + +// compileSSA builds an SSA backend function, +// uses it to generate a plist, +// and flushes that plist to machine code. +// worker indicates which of the backend workers is doing the processing. +func compileSSA(fn *Node, worker int) { + ssafn := buildssa(fn, worker) + pp := newProgs(fn, worker) genssa(ssafn, pp) if pp.Text.To.Offset < 1<<31 { pp.Flush() } else { + largeStackFramesMu.Lock() largeStackFrames = append(largeStackFrames, fn.Pos) + largeStackFramesMu.Unlock() } // fieldtrack must be called after pp.Flush. See issue 20014. fieldtrack(pp.Text.From.Sym, fn.Func.FieldTrack) pp.Free() } +// compileFunctions compiles all functions in compilequeue. +// It fans out nBackendWorkers to do the work +// and waits for them to complete. +func compileFunctions() { + if len(compilequeue) != 0 { + var wg sync.WaitGroup + c := make(chan *Node) + for i := 0; i < nBackendWorkers; i++ { + wg.Add(1) + go func(worker int) { + for fn := range c { + compileSSA(fn, worker) + } + wg.Done() + }(i) + } + for _, fn := range compilequeue { + c <- fn + } + close(c) + compilequeue = nil + wg.Wait() + } +} + func debuginfo(fnsym *obj.LSym, curfn interface{}) []*dwarf.Var { fn := curfn.(*Node) if expect := fn.Func.Nname.Sym.Linksym(); fnsym != expect { diff --git a/src/cmd/compile/internal/gc/reflect.go b/src/cmd/compile/internal/gc/reflect.go index 55704741c5..1e165fd465 100644 --- a/src/cmd/compile/internal/gc/reflect.go +++ b/src/cmd/compile/internal/gc/reflect.go @@ -14,6 +14,7 @@ import ( "os" "sort" "strings" + "sync" ) type itabEntry struct { @@ -32,9 +33,13 @@ type ptabEntry struct { } // runtime interface and reflection data structures -var signatlist = make(map[*types.Type]bool) -var itabs []itabEntry -var ptabs []ptabEntry +var ( + signatlistmu sync.Mutex // protects signatlist + signatlist = make(map[*types.Type]bool) + + itabs []itabEntry + ptabs []ptabEntry +) type Sig struct { name string @@ -903,12 +908,16 @@ func typesymname(t *types.Type) string { // Fake package for runtime type info (headers) // Don't access directly, use typeLookup below. -var typepkg = types.NewPkg("type", "type") +var ( + typepkgmu sync.Mutex // protects typepkg lookups + typepkg = types.NewPkg("type", "type") +) func typeLookup(name string) *types.Sym { - // Keep this wrapper function as a future - // version may protect typepkg with a mutex. - return typepkg.Lookup(name) + typepkgmu.Lock() + s := typepkg.Lookup(name) + typepkgmu.Unlock() + return s } func typesym(t *types.Type) *types.Sym { @@ -935,7 +944,9 @@ func typenamesym(t *types.Type) *types.Sym { Fatalf("typenamesym %v", t) } s := typesym(t) + signatlistmu.Lock() addsignat(t) + signatlistmu.Unlock() return s } diff --git a/src/cmd/compile/internal/gc/ssa.go b/src/cmd/compile/internal/gc/ssa.go index 2f3c25223c..e9f31e68c4 100644 --- a/src/cmd/compile/internal/gc/ssa.go +++ b/src/cmd/compile/internal/gc/ssa.go @@ -21,7 +21,7 @@ import ( ) var ssaConfig *ssa.Config -var ssaCache *ssa.Cache +var ssaCaches []ssa.Cache func initssaconfig() { types_ := ssa.Types{ @@ -67,7 +67,7 @@ func initssaconfig() { if thearch.LinkArch.Name == "386" { ssaConfig.Set387(thearch.Use387) } - ssaCache = new(ssa.Cache) + ssaCaches = make([]ssa.Cache, nBackendWorkers) // Set up some runtime functions we'll need to call. Newproc = Sysfunc("newproc") @@ -94,8 +94,9 @@ func initssaconfig() { Udiv = Sysfunc("udiv") } -// buildssa builds an SSA function. -func buildssa(fn *Node) *ssa.Func { +// buildssa builds an SSA function for fn. +// worker indicates which of the backend workers is doing the processing. +func buildssa(fn *Node, worker int) *ssa.Func { name := fn.funcname() printssa := name == os.Getenv("GOSSAFUNC") if printssa { @@ -123,7 +124,7 @@ func buildssa(fn *Node) *ssa.Func { s.f = ssa.NewFunc(&fe) s.config = ssaConfig s.f.Config = ssaConfig - s.f.Cache = ssaCache + s.f.Cache = &ssaCaches[worker] s.f.Cache.Reset() s.f.DebugTest = s.f.DebugHashMatch("GOSSAHASH", name) s.f.Name = name diff --git a/src/cmd/compile/internal/gc/subr.go b/src/cmd/compile/internal/gc/subr.go index 12aa779e68..365cfad81f 100644 --- a/src/cmd/compile/internal/gc/subr.go +++ b/src/cmd/compile/internal/gc/subr.go @@ -16,6 +16,7 @@ import ( "sort" "strconv" "strings" + "sync" "unicode" "unicode/utf8" ) @@ -27,7 +28,10 @@ type Error struct { var errors []Error -var largeStackFrames []src.XPos // positions of functions whose stack frames are too large (rare) +var ( + largeStackFramesMu sync.Mutex // protects largeStackFrames + largeStackFrames []src.XPos // positions of functions whose stack frames are too large (rare) +) func errorexit() { flusherrors() diff --git a/src/cmd/compile/internal/types/pkg.go b/src/cmd/compile/internal/types/pkg.go index 1fe49bd142..81bf72e972 100644 --- a/src/cmd/compile/internal/types/pkg.go +++ b/src/cmd/compile/internal/types/pkg.go @@ -9,6 +9,7 @@ import ( "cmd/internal/objabi" "fmt" "sort" + "sync" ) // pkgMap maps a package path to a package. @@ -108,14 +109,19 @@ func (pkg *Pkg) LookupBytes(name []byte) *Sym { return pkg.Lookup(str) } -var internedStrings = map[string]string{} +var ( + internedStringsmu sync.Mutex // protects internedStrings + internedStrings = map[string]string{} +) func InternString(b []byte) string { + internedStringsmu.Lock() s, ok := internedStrings[string(b)] // string(b) here doesn't allocate if !ok { s = string(b) internedStrings[s] = s } + internedStringsmu.Unlock() return s } diff --git a/src/cmd/internal/obj/link.go b/src/cmd/internal/obj/link.go index d324eebbba..8bdc3f55e9 100644 --- a/src/cmd/internal/obj/link.go +++ b/src/cmd/internal/obj/link.go @@ -37,6 +37,7 @@ import ( "cmd/internal/src" "cmd/internal/sys" "fmt" + "sync" ) // An Addr is an argument to an instruction. @@ -482,6 +483,7 @@ type Link struct { Flag_optimize bool Bso *bufio.Writer Pathname string + hashmu sync.Mutex // protects hash hash map[string]*LSym // name -> sym mapping statichash map[string]*LSym // name -> sym mapping for static syms PosTable src.PosTable diff --git a/src/cmd/internal/obj/sym.go b/src/cmd/internal/obj/sym.go index dcaed5062d..3fb2df169a 100644 --- a/src/cmd/internal/obj/sym.go +++ b/src/cmd/internal/obj/sym.go @@ -85,6 +85,7 @@ func (ctxt *Link) Lookup(name string) *LSym { // If it does not exist, it creates it and // passes it to init for one-time initialization. func (ctxt *Link) LookupInit(name string, init func(s *LSym)) *LSym { + ctxt.hashmu.Lock() s := ctxt.hash[name] if s == nil { s = &LSym{Name: name} @@ -93,6 +94,7 @@ func (ctxt *Link) LookupInit(name string, init func(s *LSym)) *LSym { init(s) } } + ctxt.hashmu.Unlock() return s } -- 2.48.1