mirror of
https://github.com/golang/go.git
synced 2025-12-08 06:10:04 +00:00
This CL restructures how we track function ABIs and generate ABI wrappers in the compiler and adds import/export of ABIs across package boundaries. Currently, we start by tracking definition and referencing ABIs in two global maps and eventually move some of this information into the LSyms for functions. This complicates a lot of the existing code for handling wrappers and makes it particularly hard to export ABI information across packages. This change is built around instead recording this information on the ir.Func. First, this change replaces the global ABI def/ref maps with a type, which makes the data flow and lifetime of this information clear in gc.Main. These are populated during flag parsing. Then, early in the front-end, we loop over all ir.Funcs to 1. attach ABI def/ref information to the ir.Funcs and 2. create new ir.Funcs for ABI wrappers. Step 1 is slightly subtle because the information is keyed by linker symbol names, so we can't simply look things up in the compiler's regular symbol table. By generating ABI wrappers early in the front-end, we decouple this step from LSym creation, which makes LSym creation much simpler (like it was before ABI wrappers). In particular, LSyms for wrappers are now created at the same time as all other functions instead of by makeABIWrapper, which means we're back to the simpler, old situation where InitLSym was the only thing responsible for constructing function LSyms. Hence, we can restore the check that InitLSym is called exactly once per function. Attaching the ABI information to the ir.Func has several follow-on benefits: 1. It's now easy to include in the export info. This enables direct cross-package cross-ABI calls, which are important for the performance of calling various hot assembly functions (e.g., internal/bytealg.*). This was really the point of this whole change. 2. Since all Funcs, including wrappers, now record their definition ABI, callTargetLSym no longer needs to distinguish wrappers from non-wrappers, so it's now nearly trivial (it would be completely trivial except that it has to work around a handful of cases where ir.Name.Func is nil). The simplification of callTargetLSym has one desirable but potentially surprising side-effect: the compiler will now generate direct calls to the definition ABI even when ABI wrappers are turned off. This is almost completely unnoticeable except that cmd/internal/obj/wasm looks for the call from runtime.deferreturn (defined in Go) to runtime.jmpdefer (defined in assembly) to compile is specially. That now looks like a direct call to ABI0 rather than going through the ABIInternal alias. While we're in here, we also set up the structures to support more than just ABI0 and ABIInternal and add various additional consistency checks all around. Performance-wise, this reduces the overhead induced by wrappers from 1.24% geomean (on Sweet) to 0.52% geomean, and reduces the number of benchmarks impacts >2% from 5 to 3. It has no impact on compiler speed. Impact of wrappers before this change: name old time/op new time/op delta BiogoIgor 15.8s ± 2% 15.8s ± 1% ~ (p=0.863 n=25+25) BiogoKrishna 18.3s ± 6% 18.1s ± 7% -1.39% (p=0.015 n=25+25) BleveIndexBatch100 5.88s ± 3% 6.04s ± 6% +2.72% (p=0.000 n=25+25) BleveQuery 6.42s ± 1% 6.76s ± 1% +5.31% (p=0.000 n=24+24) CompileTemplate 245ms ± 3% 250ms ± 6% ~ (p=0.068 n=22+25) CompileUnicode 93.6ms ± 2% 93.9ms ± 5% ~ (p=0.958 n=22+25) CompileGoTypes 1.60s ± 2% 1.59s ± 2% ~ (p=0.115 n=24+24) CompileCompiler 104ms ± 4% 104ms ± 3% ~ (p=0.453 n=22+25) CompileSSA 11.0s ± 2% 11.0s ± 1% ~ (p=0.789 n=24+25) CompileFlate 153ms ± 2% 153ms ± 1% ~ (p=0.055 n=21+20) CompileGoParser 229ms ± 2% 230ms ± 2% ~ (p=0.305 n=21+22) CompileReflect 585ms ± 5% 582ms ± 3% ~ (p=0.365 n=25+25) CompileTar 211ms ± 1% 211ms ± 3% ~ (p=0.592 n=20+22) CompileXML 282ms ± 3% 281ms ± 2% ~ (p=0.937 n=22+23) CompileStdCmd 13.7s ± 3% 13.6s ± 2% ~ (p=0.700 n=25+25) FoglemanFauxGLRenderRotateBoat 8.67s ± 1% 8.78s ± 1% +1.30% (p=0.000 n=25+25) FoglemanPathTraceRenderGopherIter1 20.5s ± 2% 20.9s ± 2% +1.85% (p=0.000 n=25+25) GopherLuaKNucleotide 30.1s ± 2% 31.1s ± 2% +3.38% (p=0.000 n=25+25) MarkdownRenderXHTML 246ms ± 5% 250ms ± 1% +1.42% (p=0.002 n=25+23) Tile38WithinCircle100kmRequest 828µs ± 6% 885µs ± 6% +6.85% (p=0.000 n=23+25) Tile38IntersectsCircle100kmRequest 1.04ms ± 5% 1.10ms ± 7% +5.63% (p=0.000 n=25+25) Tile38KNearestLimit100Request 974µs ± 4% 972µs ± 4% ~ (p=0.356 n=25+24) [Geo mean] 588ms 595ms +1.24% (https://perf.golang.org/search?q=upload:20210328.5) And after this change: name old time/op new time/op delta BiogoIgor 15.9s ± 1% 15.8s ± 1% -0.48% (p=0.008 n=22+25) BiogoKrishna 18.4s ± 6% 17.8s ± 6% -3.55% (p=0.008 n=25+25) BleveIndexBatch100 5.86s ± 3% 5.97s ± 4% +1.88% (p=0.001 n=25+25) BleveQuery 6.42s ± 1% 6.75s ± 1% +5.14% (p=0.000 n=25+25) CompileTemplate 246ms ± 5% 245ms ± 2% ~ (p=0.472 n=23+23) CompileUnicode 93.7ms ± 3% 93.5ms ± 2% ~ (p=0.813 n=22+23) CompileGoTypes 1.60s ± 2% 1.60s ± 2% ~ (p=0.108 n=25+23) CompileCompiler 104ms ± 3% 104ms ± 2% ~ (p=0.845 n=23+23) CompileSSA 11.0s ± 2% 11.0s ± 2% ~ (p=0.525 n=25+25) CompileFlate 152ms ± 1% 153ms ± 2% ~ (p=0.408 n=22+22) CompileGoParser 230ms ± 1% 230ms ± 1% ~ (p=0.363 n=21+23) CompileReflect 582ms ± 3% 584ms ± 4% ~ (p=0.658 n=25+25) CompileTar 212ms ± 2% 211ms ± 2% ~ (p=0.315 n=23+24) CompileXML 282ms ± 1% 282ms ± 1% ~ (p=0.991 n=23+22) CompileStdCmd 13.6s ± 2% 13.6s ± 2% ~ (p=0.699 n=25+24) FoglemanFauxGLRenderRotateBoat 8.66s ± 1% 8.69s ± 1% +0.28% (p=0.002 n=25+24) FoglemanPathTraceRenderGopherIter1 20.5s ± 3% 20.5s ± 2% ~ (p=0.407 n=25+25) GopherLuaKNucleotide 30.1s ± 2% 31.2s ± 2% +3.82% (p=0.000 n=25+25) MarkdownRenderXHTML 246ms ± 3% 245ms ± 1% ~ (p=0.478 n=23+22) Tile38WithinCircle100kmRequest 820µs ± 4% 856µs ± 5% +4.39% (p=0.000 n=24+25) Tile38IntersectsCircle100kmRequest 1.05ms ± 6% 1.07ms ± 6% +1.91% (p=0.014 n=25+25) Tile38KNearestLimit100Request 970µs ± 4% 970µs ± 3% ~ (p=0.819 n=22+24) [Geo mean] 588ms 591ms +0.52% (https://perf.golang.org/search?q=upload:20210328.6) For #40724. Change-Id: I1c374e32d4bbc88efed062a1b360017d3642140d Reviewed-on: https://go-review.googlesource.com/c/go/+/305274 Trust: Austin Clements <austin@google.com> Run-TryBot: Austin Clements <austin@google.com> Reviewed-by: Cherry Zhang <cherryyz@google.com> TryBot-Result: Go Bot <gobot@golang.org>
353 lines
10 KiB
Go
353 lines
10 KiB
Go
// Copyright 2009 The Go Authors. All rights reserved.
|
|
// Use of this source code is governed by a BSD-style
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package gc
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"cmd/compile/internal/base"
|
|
"cmd/compile/internal/deadcode"
|
|
"cmd/compile/internal/devirtualize"
|
|
"cmd/compile/internal/dwarfgen"
|
|
"cmd/compile/internal/escape"
|
|
"cmd/compile/internal/inline"
|
|
"cmd/compile/internal/ir"
|
|
"cmd/compile/internal/logopt"
|
|
"cmd/compile/internal/noder"
|
|
"cmd/compile/internal/pkginit"
|
|
"cmd/compile/internal/reflectdata"
|
|
"cmd/compile/internal/ssa"
|
|
"cmd/compile/internal/ssagen"
|
|
"cmd/compile/internal/typecheck"
|
|
"cmd/compile/internal/types"
|
|
"cmd/internal/dwarf"
|
|
"cmd/internal/obj"
|
|
"cmd/internal/objabi"
|
|
"cmd/internal/src"
|
|
"flag"
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"runtime"
|
|
)
|
|
|
|
func hidePanic() {
|
|
if base.Debug.Panic == 0 && base.Errors() > 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 {
|
|
if err == "-h" {
|
|
panic(err)
|
|
}
|
|
base.ErrorExit()
|
|
}
|
|
}
|
|
}
|
|
|
|
// Main parses flags and Go source files specified in the command-line
|
|
// arguments, type-checks the parsed Go package, compiles functions to machine
|
|
// code, and finally writes the compiled package definition to disk.
|
|
func Main(archInit func(*ssagen.ArchInfo)) {
|
|
base.Timer.Start("fe", "init")
|
|
|
|
defer hidePanic()
|
|
|
|
archInit(&ssagen.Arch)
|
|
|
|
base.Ctxt = obj.Linknew(ssagen.Arch.LinkArch)
|
|
base.Ctxt.DiagFunc = base.Errorf
|
|
base.Ctxt.DiagFlush = base.FlushErrors
|
|
base.Ctxt.Bso = bufio.NewWriter(os.Stdout)
|
|
|
|
// UseBASEntries is preferred because it shaves about 2% off build time, but LLDB, dsymutil, and dwarfdump
|
|
// on Darwin don't support it properly, especially since macOS 10.14 (Mojave). This is exposed as a flag
|
|
// to allow testing with LLVM tools on Linux, and to help with reporting this bug to the LLVM project.
|
|
// See bugs 31188 and 21945 (CLs 170638, 98075, 72371).
|
|
base.Ctxt.UseBASEntries = base.Ctxt.Headtype != objabi.Hdarwin
|
|
|
|
types.LocalPkg = types.NewPkg("", "")
|
|
types.LocalPkg.Prefix = "\"\""
|
|
|
|
// We won't know localpkg's height until after import
|
|
// processing. In the mean time, set to MaxPkgHeight to ensure
|
|
// height comparisons at least work until then.
|
|
types.LocalPkg.Height = types.MaxPkgHeight
|
|
|
|
// pseudo-package, for scoping
|
|
types.BuiltinPkg = types.NewPkg("go.builtin", "") // TODO(gri) name this package go.builtin?
|
|
types.BuiltinPkg.Prefix = "go.builtin" // not go%2ebuiltin
|
|
|
|
// pseudo-package, accessed by import "unsafe"
|
|
ir.Pkgs.Unsafe = types.NewPkg("unsafe", "unsafe")
|
|
|
|
// Pseudo-package that contains the compiler's builtin
|
|
// declarations for package runtime. These are declared in a
|
|
// separate package to avoid conflicts with package runtime's
|
|
// actual declarations, which may differ intentionally but
|
|
// insignificantly.
|
|
ir.Pkgs.Runtime = types.NewPkg("go.runtime", "runtime")
|
|
ir.Pkgs.Runtime.Prefix = "runtime"
|
|
|
|
// pseudo-packages used in symbol tables
|
|
ir.Pkgs.Itab = types.NewPkg("go.itab", "go.itab")
|
|
ir.Pkgs.Itab.Prefix = "go.itab" // not go%2eitab
|
|
|
|
// pseudo-package used for methods with anonymous receivers
|
|
ir.Pkgs.Go = types.NewPkg("go", "")
|
|
|
|
base.DebugSSA = ssa.PhaseOption
|
|
base.ParseFlags()
|
|
|
|
// Record flags that affect the build result. (And don't
|
|
// record flags that don't, since that would cause spurious
|
|
// changes in the binary.)
|
|
dwarfgen.RecordFlags("B", "N", "l", "msan", "race", "shared", "dynlink", "dwarf", "dwarflocationlists", "dwarfbasentries", "smallframes", "spectre")
|
|
|
|
if !base.EnableTrace && base.Flag.LowerT {
|
|
log.Fatalf("compiler not built with support for -t")
|
|
}
|
|
|
|
// Enable inlining (after RecordFlags, to avoid recording the rewritten -l). For now:
|
|
// default: inlining on. (Flag.LowerL == 1)
|
|
// -l: inlining off (Flag.LowerL == 0)
|
|
// -l=2, -l=3: inlining on again, with extra debugging (Flag.LowerL > 1)
|
|
if base.Flag.LowerL <= 1 {
|
|
base.Flag.LowerL = 1 - base.Flag.LowerL
|
|
}
|
|
|
|
if base.Flag.SmallFrames {
|
|
ir.MaxStackVarSize = 128 * 1024
|
|
ir.MaxImplicitStackVarSize = 16 * 1024
|
|
}
|
|
|
|
if base.Flag.Dwarf {
|
|
base.Ctxt.DebugInfo = dwarfgen.Info
|
|
base.Ctxt.GenAbstractFunc = dwarfgen.AbstractFunc
|
|
base.Ctxt.DwFixups = obj.NewDwarfFixupTable(base.Ctxt)
|
|
} else {
|
|
// turn off inline generation if no dwarf at all
|
|
base.Flag.GenDwarfInl = 0
|
|
base.Ctxt.Flag_locationlists = false
|
|
}
|
|
if base.Ctxt.Flag_locationlists && len(base.Ctxt.Arch.DWARFRegisters) == 0 {
|
|
log.Fatalf("location lists requested but register mapping not available on %v", base.Ctxt.Arch.Name)
|
|
}
|
|
|
|
types.ParseLangFlag()
|
|
|
|
symABIs := ssagen.NewSymABIs(base.Ctxt.Pkgpath)
|
|
if base.Flag.SymABIs != "" {
|
|
symABIs.ReadSymABIs(base.Flag.SymABIs)
|
|
}
|
|
|
|
if base.Compiling(base.NoInstrumentPkgs) {
|
|
base.Flag.Race = false
|
|
base.Flag.MSan = false
|
|
}
|
|
|
|
ssagen.Arch.LinkArch.Init(base.Ctxt)
|
|
startProfile()
|
|
if base.Flag.Race || base.Flag.MSan {
|
|
base.Flag.Cfg.Instrumenting = true
|
|
}
|
|
if base.Flag.Dwarf {
|
|
dwarf.EnableLogging(base.Debug.DwarfInl != 0)
|
|
}
|
|
if base.Debug.SoftFloat != 0 {
|
|
ssagen.Arch.SoftFloat = true
|
|
}
|
|
|
|
if base.Flag.JSON != "" { // parse version,destination from json logging optimization.
|
|
logopt.LogJsonOption(base.Flag.JSON)
|
|
}
|
|
|
|
ir.EscFmt = escape.Fmt
|
|
ir.IsIntrinsicCall = ssagen.IsIntrinsicCall
|
|
inline.SSADumpInline = ssagen.DumpInline
|
|
ssagen.InitEnv()
|
|
ssagen.InitTables()
|
|
|
|
types.PtrSize = ssagen.Arch.LinkArch.PtrSize
|
|
types.RegSize = ssagen.Arch.LinkArch.RegSize
|
|
types.MaxWidth = ssagen.Arch.MAXWIDTH
|
|
|
|
typecheck.Target = new(ir.Package)
|
|
|
|
typecheck.NeedITab = func(t, iface *types.Type) { reflectdata.ITabAddr(t, iface) }
|
|
typecheck.NeedRuntimeType = reflectdata.NeedRuntimeType // TODO(rsc): TypeSym for lock?
|
|
|
|
base.AutogeneratedPos = makePos(src.NewFileBase("<autogenerated>", "<autogenerated>"), 1, 0)
|
|
|
|
typecheck.InitUniverse()
|
|
|
|
// Parse and typecheck input.
|
|
noder.LoadPackage(flag.Args())
|
|
|
|
dwarfgen.RecordPackageName()
|
|
|
|
// Build init task.
|
|
if initTask := pkginit.Task(); initTask != nil {
|
|
typecheck.Export(initTask)
|
|
}
|
|
|
|
// Eliminate some obviously dead code.
|
|
// Must happen after typechecking.
|
|
for _, n := range typecheck.Target.Decls {
|
|
if n.Op() == ir.ODCLFUNC {
|
|
deadcode.Func(n.(*ir.Func))
|
|
}
|
|
}
|
|
|
|
// Compute Addrtaken for names.
|
|
// We need to wait until typechecking is done so that when we see &x[i]
|
|
// we know that x has its address taken if x is an array, but not if x is a slice.
|
|
// We compute Addrtaken in bulk here.
|
|
// After this phase, we maintain Addrtaken incrementally.
|
|
if typecheck.DirtyAddrtaken {
|
|
typecheck.ComputeAddrtaken(typecheck.Target.Decls)
|
|
typecheck.DirtyAddrtaken = false
|
|
}
|
|
typecheck.IncrementalAddrtaken = true
|
|
|
|
if base.Debug.TypecheckInl != 0 {
|
|
// Typecheck imported function bodies if Debug.l > 1,
|
|
// otherwise lazily when used or re-exported.
|
|
typecheck.AllImportedBodies()
|
|
}
|
|
|
|
// Inlining
|
|
base.Timer.Start("fe", "inlining")
|
|
if base.Flag.LowerL != 0 {
|
|
inline.InlinePackage()
|
|
}
|
|
|
|
// Devirtualize.
|
|
for _, n := range typecheck.Target.Decls {
|
|
if n.Op() == ir.ODCLFUNC {
|
|
devirtualize.Func(n.(*ir.Func))
|
|
}
|
|
}
|
|
ir.CurFunc = nil
|
|
|
|
// Generate ABI wrappers. Must happen before escape analysis
|
|
// and doesn't benefit from dead-coding or inlining.
|
|
symABIs.GenABIWrappers()
|
|
|
|
// 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.
|
|
base.Timer.Start("fe", "escapes")
|
|
escape.Funcs(typecheck.Target.Decls)
|
|
|
|
// Collect information for go:nowritebarrierrec
|
|
// checking. This must happen before transforming closures during Walk
|
|
// We'll do the final check after write barriers are
|
|
// inserted.
|
|
if base.Flag.CompilingRuntime {
|
|
ssagen.EnableNoWriteBarrierRecCheck()
|
|
}
|
|
|
|
// Prepare for SSA compilation.
|
|
// This must be before CompileITabs, because CompileITabs
|
|
// can trigger function compilation.
|
|
typecheck.InitRuntime()
|
|
ssagen.InitConfig()
|
|
|
|
// Just before compilation, compile itabs found on
|
|
// the right side of OCONVIFACE so that methods
|
|
// can be de-virtualized during compilation.
|
|
ir.CurFunc = nil
|
|
reflectdata.CompileITabs()
|
|
|
|
// Compile top level functions.
|
|
// Don't use range--walk can add functions to Target.Decls.
|
|
base.Timer.Start("be", "compilefuncs")
|
|
fcount := int64(0)
|
|
for i := 0; i < len(typecheck.Target.Decls); i++ {
|
|
if fn, ok := typecheck.Target.Decls[i].(*ir.Func); ok {
|
|
enqueueFunc(fn)
|
|
fcount++
|
|
}
|
|
}
|
|
base.Timer.AddEvent(fcount, "funcs")
|
|
|
|
compileFunctions()
|
|
|
|
if base.Flag.CompilingRuntime {
|
|
// Write barriers are now known. Check the call graph.
|
|
ssagen.NoWriteBarrierRecCheck()
|
|
}
|
|
|
|
// Finalize DWARF inline routine DIEs, then explicitly turn off
|
|
// DWARF inlining gen so as to avoid problems with generated
|
|
// method wrappers.
|
|
if base.Ctxt.DwFixups != nil {
|
|
base.Ctxt.DwFixups.Finalize(base.Ctxt.Pkgpath, base.Debug.DwarfInl != 0)
|
|
base.Ctxt.DwFixups = nil
|
|
base.Flag.GenDwarfInl = 0
|
|
}
|
|
|
|
// Write object data to disk.
|
|
base.Timer.Start("be", "dumpobj")
|
|
dumpdata()
|
|
base.Ctxt.NumberSyms()
|
|
dumpobj()
|
|
if base.Flag.AsmHdr != "" {
|
|
dumpasmhdr()
|
|
}
|
|
|
|
ssagen.CheckLargeStacks()
|
|
typecheck.CheckFuncStack()
|
|
|
|
if len(compilequeue) != 0 {
|
|
base.Fatalf("%d uncompiled functions", len(compilequeue))
|
|
}
|
|
|
|
logopt.FlushLoggedOpts(base.Ctxt, base.Ctxt.Pkgpath)
|
|
base.ExitIfErrors()
|
|
|
|
base.FlushErrors()
|
|
base.Timer.Stop()
|
|
|
|
if base.Flag.Bench != "" {
|
|
if err := writebench(base.Flag.Bench); err != nil {
|
|
log.Fatalf("cannot write benchmark data: %v", err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func writebench(filename string) error {
|
|
f, err := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0666)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var buf bytes.Buffer
|
|
fmt.Fprintln(&buf, "commit:", objabi.Version)
|
|
fmt.Fprintln(&buf, "goos:", runtime.GOOS)
|
|
fmt.Fprintln(&buf, "goarch:", runtime.GOARCH)
|
|
base.Timer.Write(&buf, "BenchmarkCompile:"+base.Ctxt.Pkgpath+":")
|
|
|
|
n, err := f.Write(buf.Bytes())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if n != buf.Len() {
|
|
panic("bad writer")
|
|
}
|
|
|
|
return f.Close()
|
|
}
|
|
|
|
func makePos(b *src.PosBase, line, col uint) src.XPos {
|
|
return base.Ctxt.PosTable.XPos(src.MakePos(b, line, col))
|
|
}
|