]> Cypherpunks repositories - gostls13.git/commitdiff
cmd/compile: add initial backend concurrency support
authorJosh Bleecher Snyder <josharian@gmail.com>
Sun, 19 Mar 2017 15:27:26 +0000 (08:27 -0700)
committerJosh Bleecher Snyder <josharian@gmail.com>
Thu, 27 Apr 2017 00:59:07 +0000 (00:59 +0000)
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 <mdempsky@google.com>
Reviewed-by: Robert Griesemer <gri@golang.org>
Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>

12 files changed:
src/cmd/compile/internal/gc/dcl.go
src/cmd/compile/internal/gc/go.go
src/cmd/compile/internal/gc/gsubr.go
src/cmd/compile/internal/gc/main.go
src/cmd/compile/internal/gc/obj.go
src/cmd/compile/internal/gc/pgen.go
src/cmd/compile/internal/gc/reflect.go
src/cmd/compile/internal/gc/ssa.go
src/cmd/compile/internal/gc/subr.go
src/cmd/compile/internal/types/pkg.go
src/cmd/internal/obj/link.go
src/cmd/internal/obj/sym.go

index 061644443e8df2416f3a86c16ba7da6eeb0b3f8e..471a7e578b1f71bcb8362daf639f2a4c7e1db063 100644 (file)
@@ -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
 }
 
index f795ce0bf0e4a4349b7eab4f330103002f45ab42..1bfe499ab77c4bd8be96dbd48b5cd76bbbb074b9 100644 (file)
@@ -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
 
index f91a1bd9168c1dcc59ed3104f580e47ada82024a..2c6d45fd8df8b139ee4b532a575413b03f0f986a 100644 (file)
@@ -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
 
index c50b6a054db93042ca4af86a699c74859c51f26e..f1bb6bca0e969269c8e6cfd81a9a9fec48f677e6 100644 (file)
@@ -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
+}
index 5064479fbb5c20021789eeb97823e9f89b64291b..29cdb5684efba0f66041a1b06165a24ac5e1c7d6 100644 (file)
@@ -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)
index c7e27cb947258b046a6fb2687c3185370904d0a5..f98289bf0be2a492ac1afdb2d2237df11d4b9254 100644 (file)
@@ -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 {
index 55704741c541b1744801a1ea48bd5e7b7ac0759f..1e165fd465da162a0b3f1bbc3f46dd0f0f2bcf49 100644 (file)
@@ -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
 }
 
index 2f3c25223c7ed3dd088d30c403feb0383f546ba9..e9f31e68c46c8556a422fbf7df46691512a7532d 100644 (file)
@@ -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
index 12aa779e682e426f72994543ba994e88c4346bf1..365cfad81f0fb3fe3bedbdf35ded51cdfd335729 100644 (file)
@@ -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()
index 1fe49bd142568f9e6de695b2b223363330ed3735..81bf72e9721e4cd05ac0bc852bbb4b2f58fea8f5 100644 (file)
@@ -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
 }
 
index d324eebbba54997547d67d96b36e532156d2d47d..8bdc3f55e99e4b4a6783dca4831cd582c56448e4 100644 (file)
@@ -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
index dcaed5062de9af3d6e4940f832af95395e6d108b..3fb2df169a1827c464c8812fcc05f63c715865e1 100644 (file)
@@ -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
 }