cmd/compile: restructure ABI wrapper generation, export ABI

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>
This commit is contained in:
Austin Clements 2021-03-25 19:50:08 -04:00
parent feb844f1ea
commit 67d565d281
12 changed files with 288 additions and 179 deletions

View file

@ -139,8 +139,9 @@ func Main(archInit func(*ssagen.ArchInfo)) {
types.ParseLangFlag() types.ParseLangFlag()
symABIs := ssagen.NewSymABIs(base.Ctxt.Pkgpath)
if base.Flag.SymABIs != "" { if base.Flag.SymABIs != "" {
ssagen.ReadSymABIs(base.Flag.SymABIs, base.Ctxt.Pkgpath) symABIs.ReadSymABIs(base.Flag.SymABIs)
} }
if base.Compiling(base.NoInstrumentPkgs) { if base.Compiling(base.NoInstrumentPkgs) {
@ -187,7 +188,6 @@ func Main(archInit func(*ssagen.ArchInfo)) {
noder.LoadPackage(flag.Args()) noder.LoadPackage(flag.Args())
dwarfgen.RecordPackageName() dwarfgen.RecordPackageName()
ssagen.CgoSymABIs()
// Build init task. // Build init task.
if initTask := pkginit.Task(); initTask != nil { if initTask := pkginit.Task(); initTask != nil {
@ -233,6 +233,10 @@ func Main(archInit func(*ssagen.ArchInfo)) {
} }
ir.CurFunc = nil ir.CurFunc = nil
// Generate ABI wrappers. Must happen before escape analysis
// and doesn't benefit from dead-coding or inlining.
symABIs.GenABIWrappers()
// Escape analysis. // Escape analysis.
// Required for moving heap allocations onto stack, // Required for moving heap allocations onto stack,
// which in turn is required by the closure implementation, // which in turn is required by the closure implementation,

View file

@ -93,7 +93,7 @@ type Func struct {
FieldTrack map[*obj.LSym]struct{} FieldTrack map[*obj.LSym]struct{}
DebugInfo interface{} DebugInfo interface{}
LSym *obj.LSym LSym *obj.LSym // Linker object in this function's native ABI (Func.ABI)
Inl *Inline Inl *Inline
@ -109,7 +109,22 @@ type Func struct {
Pragma PragmaFlag // go:xxx function annotations Pragma PragmaFlag // go:xxx function annotations
flags bitset16 flags bitset16
// ABI is a function's "definition" ABI. This is the ABI that
// this function's generated code is expecting to be called by.
//
// For most functions, this will be obj.ABIInternal. It may be
// a different ABI for functions defined in assembly or ABI wrappers.
//
// This is included in the export data and tracked across packages.
ABI obj.ABI
// ABIRefs is the set of ABIs by which this function is referenced.
// For ABIs other than this function's definition ABI, the
// compiler generates ABI wrapper functions. This is only tracked
// within a package.
ABIRefs obj.ABISet
NumDefers int32 // number of defer calls in the function NumDefers int32 // number of defer calls in the function
NumReturns int32 // number of explicit returns in the function NumReturns int32 // number of explicit returns in the function
@ -124,6 +139,9 @@ func NewFunc(pos src.XPos) *Func {
f.pos = pos f.pos = pos
f.op = ODCLFUNC f.op = ODCLFUNC
f.Iota = -1 f.Iota = -1
// Most functions are ABIInternal. The importer or symabis
// pass may override this.
f.ABI = obj.ABIInternal
return f return f
} }
@ -163,6 +181,7 @@ type ScopeID int32
const ( const (
funcDupok = 1 << iota // duplicate definitions ok funcDupok = 1 << iota // duplicate definitions ok
funcWrapper // hide frame from users (elide in tracebacks, don't count as a frame for recover()) funcWrapper // hide frame from users (elide in tracebacks, don't count as a frame for recover())
funcABIWrapper // is an ABI wrapper (also set flagWrapper)
funcNeedctxt // function uses context register (has closure variables) funcNeedctxt // function uses context register (has closure variables)
funcReflectMethod // function calls reflect.Type.Method or MethodByName funcReflectMethod // function calls reflect.Type.Method or MethodByName
// true if closure inside a function; false if a simple function or a // true if closure inside a function; false if a simple function or a
@ -184,6 +203,7 @@ type SymAndPos struct {
func (f *Func) Dupok() bool { return f.flags&funcDupok != 0 } func (f *Func) Dupok() bool { return f.flags&funcDupok != 0 }
func (f *Func) Wrapper() bool { return f.flags&funcWrapper != 0 } func (f *Func) Wrapper() bool { return f.flags&funcWrapper != 0 }
func (f *Func) ABIWrapper() bool { return f.flags&funcABIWrapper != 0 }
func (f *Func) Needctxt() bool { return f.flags&funcNeedctxt != 0 } func (f *Func) Needctxt() bool { return f.flags&funcNeedctxt != 0 }
func (f *Func) ReflectMethod() bool { return f.flags&funcReflectMethod != 0 } func (f *Func) ReflectMethod() bool { return f.flags&funcReflectMethod != 0 }
func (f *Func) IsHiddenClosure() bool { return f.flags&funcIsHiddenClosure != 0 } func (f *Func) IsHiddenClosure() bool { return f.flags&funcIsHiddenClosure != 0 }
@ -197,6 +217,7 @@ func (f *Func) ClosureCalled() bool { return f.flags&funcClosureCalle
func (f *Func) SetDupok(b bool) { f.flags.set(funcDupok, b) } func (f *Func) SetDupok(b bool) { f.flags.set(funcDupok, b) }
func (f *Func) SetWrapper(b bool) { f.flags.set(funcWrapper, b) } func (f *Func) SetWrapper(b bool) { f.flags.set(funcWrapper, b) }
func (f *Func) SetABIWrapper(b bool) { f.flags.set(funcABIWrapper, b) }
func (f *Func) SetNeedctxt(b bool) { f.flags.set(funcNeedctxt, b) } func (f *Func) SetNeedctxt(b bool) { f.flags.set(funcNeedctxt, b) }
func (f *Func) SetReflectMethod(b bool) { f.flags.set(funcReflectMethod, b) } func (f *Func) SetReflectMethod(b bool) { f.flags.set(funcReflectMethod, b) }
func (f *Func) SetIsHiddenClosure(b bool) { f.flags.set(funcIsHiddenClosure, b) } func (f *Func) SetIsHiddenClosure(b bool) { f.flags.set(funcIsHiddenClosure, b) }

View file

@ -20,7 +20,7 @@ func TestSizeof(t *testing.T) {
_32bit uintptr // size on 32bit platforms _32bit uintptr // size on 32bit platforms
_64bit uintptr // size on 64bit platforms _64bit uintptr // size on 64bit platforms
}{ }{
{Func{}, 188, 328}, {Func{}, 192, 328},
{Name{}, 112, 200}, {Name{}, 112, 200},
} }

View file

@ -12,7 +12,6 @@ import (
"strings" "strings"
"cmd/compile/internal/base" "cmd/compile/internal/base"
"cmd/compile/internal/escape"
"cmd/compile/internal/ir" "cmd/compile/internal/ir"
"cmd/compile/internal/staticdata" "cmd/compile/internal/staticdata"
"cmd/compile/internal/typecheck" "cmd/compile/internal/typecheck"
@ -21,23 +20,40 @@ import (
"cmd/internal/objabi" "cmd/internal/objabi"
) )
// symabiDefs and symabiRefs record the defined and referenced ABIs of // SymABIs records information provided by the assembler about symbol
// symbols required by non-Go code. These are keyed by link symbol // definition ABIs and reference ABIs.
// name, where the local package prefix is always `"".` type SymABIs struct {
var symabiDefs, symabiRefs map[string]obj.ABI defs map[string]obj.ABI
refs map[string]obj.ABISet
func CgoSymABIs() { localPrefix string
// The linker expects an ABI0 wrapper for all cgo-exported }
// functions.
for _, prag := range typecheck.Target.CgoPragmas { func NewSymABIs(myimportpath string) *SymABIs {
switch prag[0] { var localPrefix string
case "cgo_export_static", "cgo_export_dynamic": if myimportpath != "" {
if symabiRefs == nil { localPrefix = objabi.PathToPrefix(myimportpath) + "."
symabiRefs = make(map[string]obj.ABI)
}
symabiRefs[prag[1]] = obj.ABI0
}
} }
return &SymABIs{
defs: make(map[string]obj.ABI),
refs: make(map[string]obj.ABISet),
localPrefix: localPrefix,
}
}
// canonicalize returns the canonical name used for a linker symbol in
// s's maps. Symbols in this package may be written either as "".X or
// with the package's import path already in the symbol. This rewrites
// both to `"".`, which matches compiler-generated linker symbol names.
func (s *SymABIs) canonicalize(linksym string) string {
// If the symbol is already prefixed with localPrefix,
// rewrite it to start with "" so it matches the
// compiler's internal symbol names.
if s.localPrefix != "" && strings.HasPrefix(linksym, s.localPrefix) {
return `"".` + linksym[len(s.localPrefix):]
}
return linksym
} }
// ReadSymABIs reads a symabis file that specifies definitions and // ReadSymABIs reads a symabis file that specifies definitions and
@ -49,23 +65,12 @@ func CgoSymABIs() {
// symbol using an ABI. For both "def" and "ref", the second field is // symbol using an ABI. For both "def" and "ref", the second field is
// the symbol name and the third field is the ABI name, as one of the // the symbol name and the third field is the ABI name, as one of the
// named cmd/internal/obj.ABI constants. // named cmd/internal/obj.ABI constants.
func ReadSymABIs(file, myimportpath string) { func (s *SymABIs) ReadSymABIs(file string) {
data, err := ioutil.ReadFile(file) data, err := ioutil.ReadFile(file)
if err != nil { if err != nil {
log.Fatalf("-symabis: %v", err) log.Fatalf("-symabis: %v", err)
} }
symabiDefs = make(map[string]obj.ABI)
symabiRefs = make(map[string]obj.ABI)
localPrefix := ""
if myimportpath != "" {
// Symbols in this package may be written either as
// "".X or with the package's import path already in
// the symbol.
localPrefix = objabi.PathToPrefix(myimportpath) + "."
}
for lineNum, line := range strings.Split(string(data), "\n") { for lineNum, line := range strings.Split(string(data), "\n") {
lineNum++ // 1-based lineNum++ // 1-based
line = strings.TrimSpace(line) line = strings.TrimSpace(line)
@ -86,19 +91,13 @@ func ReadSymABIs(file, myimportpath string) {
log.Fatalf(`%s:%d: invalid symabi: unknown abi "%s"`, file, lineNum, abistr) log.Fatalf(`%s:%d: invalid symabi: unknown abi "%s"`, file, lineNum, abistr)
} }
// If the symbol is already prefixed with sym = s.canonicalize(sym)
// myimportpath, rewrite it to start with ""
// so it matches the compiler's internal
// symbol names.
if localPrefix != "" && strings.HasPrefix(sym, localPrefix) {
sym = `"".` + sym[len(localPrefix):]
}
// Record for later. // Record for later.
if parts[0] == "def" { if parts[0] == "def" {
symabiDefs[sym] = abi s.defs[sym] = abi
} else { } else {
symabiRefs[sym] = abi s.refs[sym] |= obj.ABISetOf(abi)
} }
default: default:
log.Fatalf(`%s:%d: invalid symabi type "%s"`, file, lineNum, parts[0]) log.Fatalf(`%s:%d: invalid symabi type "%s"`, file, lineNum, parts[0])
@ -106,6 +105,78 @@ func ReadSymABIs(file, myimportpath string) {
} }
} }
// GenABIWrappers applies ABI information to Funcs and generates ABI
// wrapper functions where necessary.
func (s *SymABIs) GenABIWrappers() {
// The linker expects an ABI0 wrapper for all cgo-exported
// functions.
for _, prag := range typecheck.Target.CgoPragmas {
switch prag[0] {
case "cgo_export_static", "cgo_export_dynamic":
s.refs[s.canonicalize(prag[1])] |= obj.ABISetOf(obj.ABI0)
}
}
// Apply ABI defs and refs to Funcs and generate wrappers.
//
// This may generate new decls for the wrappers, but we
// specifically *don't* want to visit those, lest we create
// wrappers for wrappers.
for _, fn := range typecheck.Target.Decls {
if fn.Op() != ir.ODCLFUNC {
continue
}
fn := fn.(*ir.Func)
nam := fn.Nname
if ir.IsBlank(nam) {
continue
}
sym := nam.Sym()
var symName string
if sym.Linkname != "" {
symName = s.canonicalize(sym.Linkname)
} else {
// These names will already be canonical.
symName = sym.Pkg.Prefix + "." + sym.Name
}
// Apply definitions.
defABI, hasDefABI := s.defs[symName]
if hasDefABI {
fn.ABI = defABI
}
// Apply references.
if abis, ok := s.refs[symName]; ok {
fn.ABIRefs |= abis
}
// Assume all functions are referenced at least as
// ABIInternal, since they may be referenced from
// other packages.
fn.ABIRefs.Set(obj.ABIInternal, true)
// If a symbol is defined in this package (either in
// Go or assembly) and given a linkname, it may be
// referenced from another package, so make it
// callable via any ABI. It's important that we know
// it's defined in this package since other packages
// may "pull" symbols using linkname and we don't want
// to create duplicate ABI wrappers.
hasBody := len(fn.Body) != 0
if sym.Linkname != "" && (hasBody || hasDefABI) {
fn.ABIRefs |= obj.ABISetCallable
}
if !objabi.Experiment.RegabiWrappers {
// We'll generate ABI aliases instead of
// wrappers once we have LSyms in InitLSym.
continue
}
forEachWrapperABI(fn, makeABIWrapper)
}
}
// InitLSym defines f's obj.LSym and initializes it based on the // InitLSym defines f's obj.LSym and initializes it based on the
// properties of f. This includes setting the symbol flags and ABI and // properties of f. This includes setting the symbol flags and ABI and
// creating and initializing related DWARF symbols. // creating and initializing related DWARF symbols.
@ -115,96 +186,73 @@ func ReadSymABIs(file, myimportpath string) {
// For body-less functions, we only create the LSym; for functions // For body-less functions, we only create the LSym; for functions
// with bodies call a helper to setup up / populate the LSym. // with bodies call a helper to setup up / populate the LSym.
func InitLSym(f *ir.Func, hasBody bool) { func InitLSym(f *ir.Func, hasBody bool) {
// FIXME: for new-style ABI wrappers, we set up the lsym at the
// point the wrapper is created.
if f.LSym != nil && objabi.Experiment.RegabiWrappers {
return
}
staticdata.NeedFuncSym(f)
selectLSym(f, hasBody)
if hasBody {
setupTextLSym(f, 0)
}
}
// selectLSym sets up the LSym for a given function, and
// makes calls to helpers to create ABI wrappers if needed.
func selectLSym(f *ir.Func, hasBody bool) {
if f.LSym != nil { if f.LSym != nil {
base.FatalfAt(f.Pos(), "InitLSym called twice on %v", f) base.FatalfAt(f.Pos(), "InitLSym called twice on %v", f)
} }
if nam := f.Nname; !ir.IsBlank(nam) { if nam := f.Nname; !ir.IsBlank(nam) {
f.LSym = nam.LinksymABI(f.ABI)
var wrapperABI obj.ABI
needABIWrapper := false
defABI, hasDefABI := symabiDefs[nam.Linksym().Name]
if hasDefABI && defABI == obj.ABI0 {
// Symbol is defined as ABI0. Create an
// Internal -> ABI0 wrapper.
f.LSym = nam.LinksymABI(obj.ABI0)
needABIWrapper, wrapperABI = true, obj.ABIInternal
} else {
f.LSym = nam.Linksym()
// No ABI override. Check that the symbol is
// using the expected ABI.
want := obj.ABIInternal
if f.LSym.ABI() != want {
base.Fatalf("function symbol %s has the wrong ABI %v, expected %v", f.LSym.Name, f.LSym.ABI(), want)
}
}
if f.Pragma&ir.Systemstack != 0 { if f.Pragma&ir.Systemstack != 0 {
f.LSym.Set(obj.AttrCFunc, true) f.LSym.Set(obj.AttrCFunc, true)
} }
if f.ABI == obj.ABIInternal || !objabi.Experiment.RegabiWrappers {
isLinknameExported := nam.Sym().Linkname != "" && (hasBody || hasDefABI) // Function values can only point to
if abi, ok := symabiRefs[f.LSym.Name]; (ok && abi == obj.ABI0) || isLinknameExported { // ABIInternal entry points. This will create
// Either 1) this symbol is definitely // the funcsym for either the defining
// referenced as ABI0 from this package; or 2) // function or its wrapper as appropriate.
// this symbol is defined in this package but //
// given a linkname, indicating that it may be // If we're using ABI aliases instead of
// referenced from another package. Create an // wrappers, we only InitLSym for the defining
// ABI0 -> Internal wrapper so it can be // ABI of a function, so we make the funcsym
// called as ABI0. In case 2, it's important // when we see that.
// that we know it's defined in this package staticdata.NeedFuncSym(f)
// since other packages may "pull" symbols
// using linkname and we don't want to create
// duplicate ABI wrappers.
if f.LSym.ABI() != obj.ABI0 {
needABIWrapper, wrapperABI = true, obj.ABI0
}
} }
if !objabi.Experiment.RegabiWrappers {
if needABIWrapper { // Create ABI aliases instead of wrappers.
if !objabi.Experiment.RegabiWrappers { forEachWrapperABI(f, makeABIAlias)
// Fallback: use alias instead. FIXME.
// These LSyms have the same name as the
// native function, so we create them directly
// rather than looking them up. The uniqueness
// of f.lsym ensures uniqueness of asym.
asym := &obj.LSym{
Name: f.LSym.Name,
Type: objabi.SABIALIAS,
R: []obj.Reloc{{Sym: f.LSym}}, // 0 size, so "informational"
}
asym.SetABI(wrapperABI)
asym.Set(obj.AttrDuplicateOK, true)
base.Ctxt.ABIAliases = append(base.Ctxt.ABIAliases, asym)
} else {
if base.Debug.ABIWrap != 0 {
fmt.Fprintf(os.Stderr, "=-= %v to %v wrapper for %s.%s\n",
wrapperABI, 1-wrapperABI, types.LocalPkg.Path, f.LSym.Name)
}
makeABIWrapper(f, wrapperABI)
}
} }
} }
if hasBody {
setupTextLSym(f, 0)
}
} }
// makeABIWrapper creates a new function that wraps a cross-ABI call func forEachWrapperABI(fn *ir.Func, cb func(fn *ir.Func, wrapperABI obj.ABI)) {
// to "f". The wrapper is marked as an ABIWRAPPER. need := fn.ABIRefs &^ obj.ABISetOf(fn.ABI)
if need == 0 {
return
}
for wrapperABI := obj.ABI(0); wrapperABI < obj.ABICount; wrapperABI++ {
if !need.Get(wrapperABI) {
continue
}
cb(fn, wrapperABI)
}
}
// makeABIAlias creates a new ABI alias so calls to f via wrapperABI
// will be resolved directly to f's ABI by the linker.
func makeABIAlias(f *ir.Func, wrapperABI obj.ABI) {
// These LSyms have the same name as the native function, so
// we create them directly rather than looking them up.
// The uniqueness of f.lsym ensures uniqueness of asym.
asym := &obj.LSym{
Name: f.LSym.Name,
Type: objabi.SABIALIAS,
R: []obj.Reloc{{Sym: f.LSym}}, // 0 size, so "informational"
}
asym.SetABI(wrapperABI)
asym.Set(obj.AttrDuplicateOK, true)
base.Ctxt.ABIAliases = append(base.Ctxt.ABIAliases, asym)
}
// makeABIWrapper creates a new function that will be called with
// wrapperABI and calls "f" using f.ABI.
func makeABIWrapper(f *ir.Func, wrapperABI obj.ABI) { func makeABIWrapper(f *ir.Func, wrapperABI obj.ABI) {
if base.Debug.ABIWrap != 0 {
fmt.Fprintf(os.Stderr, "=-= %v to %v wrapper for %v\n", wrapperABI, f.ABI, f)
}
// Q: is this needed? // Q: is this needed?
savepos := base.Pos savepos := base.Pos
@ -230,16 +278,17 @@ func makeABIWrapper(f *ir.Func, wrapperABI obj.ABI) {
// Reuse f's types.Sym to create a new ODCLFUNC/function. // Reuse f's types.Sym to create a new ODCLFUNC/function.
fn := typecheck.DeclFunc(f.Nname.Sym(), tfn) fn := typecheck.DeclFunc(f.Nname.Sym(), tfn)
fn.SetDupok(true) fn.ABI = wrapperABI
fn.SetWrapper(true) // ignore frame for panic+recover matching
// Select LSYM now. fn.SetABIWrapper(true)
asym := base.Ctxt.LookupABI(f.LSym.Name, wrapperABI) fn.SetDupok(true)
asym.Type = objabi.STEXT // Set this as a wrapper so it doesn't appear in tracebacks.
if fn.LSym != nil { // Having both ABIWrapper and Wrapper set suppresses obj's
panic("unexpected") // usual panic+recover handling for wrappers; that's okay
} // because we're never going to defer a wrapper for a function
fn.LSym = asym // that then recovers, so that's would just be unnecessary
// code in the ABI wrapper.
fn.SetWrapper(true)
// ABI0-to-ABIInternal wrappers will be mainly loading params from // ABI0-to-ABIInternal wrappers will be mainly loading params from
// stack into registers (and/or storing stack locations back to // stack into registers (and/or storing stack locations back to
@ -266,7 +315,7 @@ func makeABIWrapper(f *ir.Func, wrapperABI obj.ABI) {
// into trouble here. // into trouble here.
// FIXME: at the moment all.bash does not pass when I leave out // FIXME: at the moment all.bash does not pass when I leave out
// NOSPLIT for these wrappers, so all are currently tagged with NOSPLIT. // NOSPLIT for these wrappers, so all are currently tagged with NOSPLIT.
setupTextLSym(fn, obj.NOSPLIT|obj.ABIWRAPPER) fn.Pragma |= ir.Nosplit
// Generate call. Use tail call if no params and no returns, // Generate call. Use tail call if no params and no returns,
// but a regular call otherwise. // but a regular call otherwise.
@ -314,8 +363,6 @@ func makeABIWrapper(f *ir.Func, wrapperABI obj.ABI) {
ir.CurFunc = fn ir.CurFunc = fn
typecheck.Stmts(fn.Body) typecheck.Stmts(fn.Body)
escape.Batch([]*ir.Func{fn}, false)
typecheck.Target.Decls = append(typecheck.Target.Decls, fn) typecheck.Target.Decls = append(typecheck.Target.Decls, fn)
// Restore previous context. // Restore previous context.
@ -332,6 +379,9 @@ func setupTextLSym(f *ir.Func, flag int) {
if f.Wrapper() { if f.Wrapper() {
flag |= obj.WRAPPER flag |= obj.WRAPPER
} }
if f.ABIWrapper() {
flag |= obj.ABIWRAPPER
}
if f.Needctxt() { if f.Needctxt() {
flag |= obj.NEEDCTXT flag |= obj.NEEDCTXT
} }

View file

@ -61,6 +61,12 @@ func newNowritebarrierrecChecker() *nowritebarrierrecChecker {
continue continue
} }
c.curfn = n.(*ir.Func) c.curfn = n.(*ir.Func)
if c.curfn.ABIWrapper() {
// We only want "real" calls to these
// functions, not the generated ones within
// their own ABI wrappers.
continue
}
ir.Visit(n, c.findExtraCalls) ir.Visit(n, c.findExtraCalls)
} }
c.curfn = nil c.curfn = nil

View file

@ -1687,7 +1687,7 @@ func (s *state) stmt(n ir.Node) {
n := n.(*ir.TailCallStmt) n := n.(*ir.TailCallStmt)
b := s.exit() b := s.exit()
b.Kind = ssa.BlockRetJmp // override BlockRet b.Kind = ssa.BlockRetJmp // override BlockRet
b.Aux = callTargetLSym(n.Target, s.curfn.LSym) b.Aux = callTargetLSym(n.Target)
case ir.OCONTINUE, ir.OBREAK: case ir.OCONTINUE, ir.OBREAK:
n := n.(*ir.BranchStmt) n := n.(*ir.BranchStmt)
@ -5031,7 +5031,7 @@ func (s *state) call(n *ir.CallExpr, k callKind, returnResultAddr bool) *ssa.Val
aux := ssa.InterfaceAuxCall(params) aux := ssa.InterfaceAuxCall(params)
call = s.newValue1A(ssa.OpInterLECall, aux.LateExpansionResultType(), aux, codeptr) call = s.newValue1A(ssa.OpInterLECall, aux.LateExpansionResultType(), aux, codeptr)
case callee != nil: case callee != nil:
aux := ssa.StaticAuxCall(callTargetLSym(callee, s.curfn.LSym), params) aux := ssa.StaticAuxCall(callTargetLSym(callee), params)
call = s.newValue0A(ssa.OpStaticLECall, aux.LateExpansionResultType(), aux) call = s.newValue0A(ssa.OpStaticLECall, aux.LateExpansionResultType(), aux)
default: default:
s.Fatalf("bad call type %v %v", n.Op(), n) s.Fatalf("bad call type %v %v", n.Op(), n)
@ -7378,44 +7378,17 @@ func clobberBase(n ir.Node) ir.Node {
return n return n
} }
// callTargetLSym determines the correct LSym for 'callee' when called // callTargetLSym returns the correct LSym to call 'callee' using its ABI.
// from function 'caller'. There are a couple of different scenarios func callTargetLSym(callee *ir.Name) *obj.LSym {
// to contend with here: if callee.Func == nil {
// // TODO(austin): This happens in a few cases of
// 1. if 'caller' is an ABI wrapper, then we always want to use the // compiler-generated functions. These are all
// LSym from the Func for the callee. // ABIInternal. It would be better if callee.Func was
// // never nil and we didn't need this case.
// 2. if 'caller' is not an ABI wrapper, then we looked at the callee return callee.Linksym()
// to see if it corresponds to a "known" ABI0 symbol (e.g. assembly
// routine defined in the current package); if so, we want the call to
// directly target the ABI0 symbol (effectively bypassing the
// ABIInternal->ABI0 wrapper for 'callee').
//
// 3. in all other cases, want the regular ABIInternal linksym
//
func callTargetLSym(callee *ir.Name, callerLSym *obj.LSym) *obj.LSym {
lsym := callee.Linksym()
if !objabi.Experiment.RegabiWrappers {
return lsym
}
fn := callee.Func
if fn == nil {
return lsym
} }
// check for case 1 above return callee.LinksymABI(callee.Func.ABI)
if callerLSym.ABIWrapper() {
if nlsym := fn.LSym; nlsym != nil {
lsym = nlsym
}
} else {
// check for case 2 above
defABI, hasDefABI := symabiDefs[lsym.Name]
if hasDefABI && defABI == obj.ABI0 {
lsym = callee.LinksymABI(obj.ABI0)
}
}
return lsym
} }
func min8(a, b int8) int8 { func min8(a, b int8) int8 {

View file

@ -269,13 +269,23 @@ func NeedFuncSym(fn *ir.Func) {
// funcsymsmu, like in FuncSym. // funcsymsmu, like in FuncSym.
base.Fatalf("NeedFuncSym must be called in serial") base.Fatalf("NeedFuncSym must be called in serial")
} }
if fn.ABI != obj.ABIInternal && objabi.Experiment.RegabiWrappers {
// Function values must always reference ABIInternal
// entry points, so it doesn't make sense to create a
// funcsym for other ABIs.
//
// (If we're using ABI aliases, it doesn't matter.)
base.Fatalf("expected ABIInternal: %v has %v", fn.Nname, fn.ABI)
}
if ir.IsBlank(fn.Nname) {
// Blank functions aren't unique, so we can't make a
// funcsym for them.
base.Fatalf("NeedFuncSym called for _")
}
if !base.Ctxt.Flag_dynlink { if !base.Ctxt.Flag_dynlink {
return return
} }
s := fn.Nname.Sym() s := fn.Nname.Sym()
if s.IsBlank() {
return
}
if base.Flag.CompilingRuntime && (s.Name == "getg" || s.Name == "getclosureptr" || s.Name == "getcallerpc" || s.Name == "getcallersp") { if base.Flag.CompilingRuntime && (s.Name == "getg" || s.Name == "getclosureptr" || s.Name == "getcallerpc" || s.Name == "getcallersp") {
// runtime.getg(), getclosureptr(), getcallerpc(), and // runtime.getg(), getclosureptr(), getcallerpc(), and
// getcallersp() are not real functions and so do not // getcallersp() are not real functions and so do not

View file

@ -1055,7 +1055,11 @@ func (w *exportWriter) funcExt(n *ir.Name) {
w.linkname(n.Sym()) w.linkname(n.Sym())
w.symIdx(n.Sym()) w.symIdx(n.Sym())
// TODO(register args) remove after register abi is working. // Record definition ABI so cross-ABI calls can be direct.
// This is important for the performance of calling some
// common functions implemented in assembly (e.g., bytealg).
w.uint64(uint64(n.Func.ABI))
w.uint64(uint64(n.Func.Pragma)) w.uint64(uint64(n.Func.Pragma))
// Escape analysis. // Escape analysis.

View file

@ -694,7 +694,8 @@ func (r *importReader) funcExt(n *ir.Name) {
r.linkname(n.Sym()) r.linkname(n.Sym())
r.symIdx(n.Sym()) r.symIdx(n.Sym())
// TODO(register args) remove after register abi is working n.Func.ABI = obj.ABI(r.uint64())
n.SetPragma(ir.PragmaFlag(r.uint64())) n.SetPragma(ir.PragmaFlag(r.uint64()))
// Escape analysis. // Escape analysis.

View file

@ -45,7 +45,7 @@ const (
symUniq symUniq
symSiggen // type symbol has been generated symSiggen // type symbol has been generated
symAsm // on asmlist, for writing to -asmhdr symAsm // on asmlist, for writing to -asmhdr
symFunc // function symbol; uses internal ABI symFunc // function symbol
) )
func (sym *Sym) OnExportList() bool { return sym.flags&symOnExportList != 0 } func (sym *Sym) OnExportList() bool { return sym.flags&symOnExportList != 0 }

View file

@ -603,6 +603,48 @@ func ParseABI(abistr string) (ABI, bool) {
} }
} }
// ABISet is a bit set of ABI values.
type ABISet uint8
const (
// ABISetCallable is the set of all ABIs any function could
// potentially be called using.
ABISetCallable ABISet = (1 << ABI0) | (1 << ABIInternal)
)
// Ensure ABISet is big enough to hold all ABIs.
var _ ABISet = 1 << (ABICount - 1)
func ABISetOf(abi ABI) ABISet {
return 1 << abi
}
func (a *ABISet) Set(abi ABI, value bool) {
if value {
*a |= 1 << abi
} else {
*a &^= 1 << abi
}
}
func (a *ABISet) Get(abi ABI) bool {
return (*a>>abi)&1 != 0
}
func (a ABISet) String() string {
s := "{"
for i := ABI(0); a != 0; i++ {
if a&(1<<i) != 0 {
if s != "{" {
s += ","
}
s += i.String()
a &^= 1 << i
}
}
return s + "}"
}
// Attribute is a set of symbol attributes. // Attribute is a set of symbol attributes.
type Attribute uint32 type Attribute uint32

View file

@ -144,11 +144,9 @@ func instinit(ctxt *obj.Link) {
gcWriteBarrier = ctxt.LookupABI("runtime.gcWriteBarrier", obj.ABIInternal) gcWriteBarrier = ctxt.LookupABI("runtime.gcWriteBarrier", obj.ABIInternal)
sigpanic = ctxt.LookupABI("runtime.sigpanic", obj.ABIInternal) sigpanic = ctxt.LookupABI("runtime.sigpanic", obj.ABIInternal)
deferreturn = ctxt.LookupABI("runtime.deferreturn", obj.ABIInternal) deferreturn = ctxt.LookupABI("runtime.deferreturn", obj.ABIInternal)
// jmpdefer is defined in assembly as ABI0, but what we're // jmpdefer is defined in assembly as ABI0. The compiler will
// looking for is the *call* to jmpdefer from the Go function // generate a direct ABI0 call from Go, so look for that.
// deferreturn, so we're looking for the ABIInternal version jmpdefer = ctxt.LookupABI(`"".jmpdefer`, obj.ABI0)
// of jmpdefer that's called by Go.
jmpdefer = ctxt.LookupABI(`"".jmpdefer`, obj.ABIInternal)
} }
func preprocess(ctxt *obj.Link, s *obj.LSym, newprog obj.ProgAlloc) { func preprocess(ctxt *obj.Link, s *obj.LSym, newprog obj.ProgAlloc) {