cmd/compile: plumb abi info into ssagen/ssa

Plumb abi information into ssa/ssagen for plain calls
and plain functions (not methods).  Does not extend all the
way through the compiler (yet).

One test disabled because it extends far enough to break the test.

Normalized all the compiler's register args TODOs to
// TODO(register args) ...

For #40724.

Change-Id: I0173a4579f032ac3c9db3aef1749d40da5ea01ff
Reviewed-on: https://go-review.googlesource.com/c/go/+/293389
Trust: David Chase <drchase@google.com>
Run-TryBot: David Chase <drchase@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Cherry Zhang <cherryyz@google.com>
This commit is contained in:
David Chase 2021-01-21 12:04:46 -05:00
parent adb467ffd2
commit 7a2f3273c5
12 changed files with 137 additions and 52 deletions

View file

@ -116,6 +116,13 @@ func NewABIConfig(iRegsCount, fRegsCount int) *ABIConfig {
return &ABIConfig{regAmounts: RegAmounts{iRegsCount, fRegsCount}, regsForTypeCache: make(map[*types.Type]int)}
}
// Copy returns a copy of an ABIConfig for use in a function's compilation so that access to the cache does not need to be protected with a mutex.
func (a *ABIConfig) Copy() *ABIConfig {
b := *a
b.regsForTypeCache = make(map[*types.Type]int)
return &b
}
// NumParamRegs returns the number of parameter registers used for a given type,
// without regard for the number available.
func (a *ABIConfig) NumParamRegs(t *types.Type) int {
@ -157,12 +164,12 @@ func (a *ABIConfig) NumParamRegs(t *types.Type) int {
// 'config' and analyzes the function to determine how its parameters
// and results will be passed (in registers or on the stack), returning
// an ABIParamResultInfo object that holds the results of the analysis.
func (config *ABIConfig) ABIAnalyze(t *types.Type) ABIParamResultInfo {
func (config *ABIConfig) ABIAnalyze(t *types.Type) *ABIParamResultInfo {
setup()
s := assignState{
rTotal: config.regAmounts,
}
result := ABIParamResultInfo{config: config}
result := &ABIParamResultInfo{config: config}
// Receiver
ft := t.FuncType()

View file

@ -456,7 +456,7 @@ const (
// Go command pragmas
GoBuildPragma
RegisterParams // TODO remove after register abi is working
RegisterParams // TODO(register args) remove after register abi is working
)

View file

@ -28,7 +28,7 @@ const (
ir.Nosplit |
ir.Noinline |
ir.NoCheckPtr |
ir.RegisterParams | // TODO remove after register abi is working
ir.RegisterParams | // TODO(register args) remove after register abi is working
ir.CgoUnsafeArgs |
ir.UintptrEscapes |
ir.Systemstack |
@ -80,7 +80,7 @@ func pragmaFlag(verb string) ir.PragmaFlag {
// in the argument list.
// Used in syscall/dll_windows.go.
return ir.UintptrEscapes
case "go:registerparams": // TODO remove after register abi is working
case "go:registerparams": // TODO(register args) remove after register abi is working
return ir.RegisterParams
case "go:notinheap":
return ir.NotInHeap

View file

@ -5,6 +5,7 @@
package ssa
import (
"cmd/compile/internal/abi"
"cmd/compile/internal/types"
"cmd/internal/src"
"crypto/sha1"
@ -43,6 +44,10 @@ type Func struct {
DebugTest bool // default true unless $GOSSAHASH != ""; as a debugging aid, make new code conditional on this and use GOSSAHASH to binary search for failing cases
PrintOrHtmlSSA bool // true if GOSSAFUNC matches, true even if fe.Log() (spew phase results to stdout) is false.
ruleMatches map[string]int // number of times countRule was called during compilation for any given string
ABI0 *abi.ABIConfig // A copy, for no-sync access
ABI1 *abi.ABIConfig // A copy, for no-sync access
ABISelf *abi.ABIConfig // ABI for function being compiled
ABIDefault *abi.ABIConfig // ABI for rtcall and other no-parsed-signature/pragma functions.
scheduled bool // Values in Blocks are in final order
laidout bool // Blocks are ordered

View file

@ -246,7 +246,8 @@ func insertLoopReschedChecks(f *Func) {
// mem1 := call resched (mem0)
// goto header
resched := f.fe.Syslook("goschedguarded")
mem1 := sched.NewValue1A(bb.Pos, OpStaticCall, types.TypeMem, StaticAuxCall(resched, nil, nil), mem0)
// TODO(register args) -- will need more details
mem1 := sched.NewValue1A(bb.Pos, OpStaticCall, types.TypeMem, StaticAuxCall(resched, nil, nil, nil), mem0)
sched.AddEdgeTo(h)
headerMemPhi.AddArg(mem1)

View file

@ -5,6 +5,7 @@
package ssa
import (
"cmd/compile/internal/abi"
"cmd/compile/internal/ir"
"cmd/compile/internal/types"
"cmd/internal/obj"
@ -71,15 +72,18 @@ type auxType int8
type Param struct {
Type *types.Type
Offset int32 // Offset of Param if not in a register.
Offset int32 // Offset of Param if not in a register, spill offset if it is in a register input, types.BADWIDTH if it is a register output.
Reg []abi.RegIndex
Name *ir.Name // For OwnAux, need to prepend stores with Vardefs
}
type AuxCall struct {
// TODO(register args) this information is largely redundant with ../abi information, needs cleanup once new ABI is in place.
Fn *obj.LSym
args []Param // Includes receiver for method calls. Does NOT include hidden closure pointer.
results []Param
reg *regInfo // regInfo for this call // TODO for now nil means ignore
reg *regInfo // regInfo for this call // TODO for now nil means ignore
abiInfo *abi.ABIParamResultInfo // TODO remove fields above redundant with this information.
}
// ResultForOffset returns the index of the result at a particular offset among the results
@ -186,9 +190,9 @@ func (a *AuxCall) String() string {
}
// StaticAuxCall returns an AuxCall for a static call.
func StaticAuxCall(sym *obj.LSym, args []Param, results []Param) *AuxCall {
func StaticAuxCall(sym *obj.LSym, args []Param, results []Param, paramResultInfo *abi.ABIParamResultInfo) *AuxCall {
// TODO Create regInfo for AuxCall
return &AuxCall{Fn: sym, args: args, results: results}
return &AuxCall{Fn: sym, args: args, results: results, abiInfo: paramResultInfo}
}
// InterfaceAuxCall returns an AuxCall for an interface call.
@ -206,9 +210,10 @@ func ClosureAuxCall(args []Param, results []Param) *AuxCall {
func (*AuxCall) CanBeAnSSAAux() {}
// OwnAuxCall returns a function's own AuxCall
func OwnAuxCall(fn *obj.LSym, args []Param, results []Param) *AuxCall {
func OwnAuxCall(fn *obj.LSym, args []Param, results []Param, paramResultInfo *abi.ABIParamResultInfo) *AuxCall {
// TODO if this remains identical to ClosureAuxCall above after new ABI is done, should deduplicate.
return &AuxCall{Fn: fn, args: args, results: results}
return &AuxCall{Fn: fn, args: args, results: results, abiInfo: paramResultInfo}
}
const (

View file

@ -765,7 +765,7 @@ func devirt(v *Value, aux Aux, sym Sym, offset int64) *AuxCall {
return nil
}
va := aux.(*AuxCall)
return StaticAuxCall(lsym, va.args, va.results)
return StaticAuxCall(lsym, va.args, va.results, va.abiInfo)
}
// de-virtualize an InterLECall

View file

@ -512,7 +512,8 @@ func wbcall(pos src.XPos, b *Block, fn, typ *obj.LSym, ptr, val, mem, sp, sb *Va
off = round(off, config.PtrSize)
// issue call
mem = b.NewValue1A(pos, OpStaticCall, types.TypeMem, StaticAuxCall(fn, ACArgs, nil), mem)
// TODO(register args) -- will need more details
mem = b.NewValue1A(pos, OpStaticCall, types.TypeMem, StaticAuxCall(fn, ACArgs, nil, nil), mem)
mem.AuxInt = off - config.ctxt.FixedFrameSize()
return mem
}

View file

@ -357,7 +357,20 @@ func buildssa(fn *ir.Func, worker int) *ssa.Func {
if fn.Pragma&ir.Nosplit != 0 {
s.f.NoSplit = true
}
if fn.Pragma&ir.RegisterParams != 0 { // TODO remove after register abi is working
s.f.ABI0 = ssaConfig.ABI0.Copy() // Make a copy to avoid racy map operations in type-width cache.
s.f.ABI1 = ssaConfig.ABI1.Copy()
s.f.ABIDefault = s.f.ABI1 // Default ABI for function calls with no parsed signature for a pragma, e.g. rtcall
// TODO(register args) -- remove "true ||"; in the short run, turning on the register ABI experiment still leaves the compiler defaulting to ABI0.
// TODO(register args) -- remove this conditional entirely when register ABI is not an experiment.
if true || objabi.Regabi_enabled == 0 {
s.f.ABIDefault = s.f.ABI0 // reset
}
s.f.ABISelf = s.f.ABIDefault
if fn.Pragma&ir.RegisterParams != 0 { // TODO(register args) remove after register abi is working
s.f.ABISelf = s.f.ABI1
if strings.Contains(name, ".") {
base.ErrorfAt(fn.Pos(), "Calls to //go:registerparams method %s won't work, remove the pragma from the declaration.", name)
}
@ -449,18 +462,19 @@ func buildssa(fn *ir.Func, worker int) *ssa.Func {
s.vars[memVar] = s.newValue1Apos(ssa.OpVarLive, types.TypeMem, deferBitsTemp, s.mem(), false)
}
params := s.f.ABISelf.ABIAnalyze(fn.Type())
// Generate addresses of local declarations
s.decladdrs = map[*ir.Name]*ssa.Value{}
var args []ssa.Param
var results []ssa.Param
for _, n := range fn.Dcl {
switch n.Class {
case ir.PPARAM:
// Be aware that blank and unnamed input parameters will not appear here, but do appear in the type
s.decladdrs[n] = s.entryNewValue2A(ssa.OpLocalAddr, types.NewPtr(n.Type()), n, s.sp, s.startmem)
args = append(args, ssa.Param{Type: n.Type(), Offset: int32(n.FrameOffset())})
case ir.PPARAMOUT:
s.decladdrs[n] = s.entryNewValue2A(ssa.OpLocalAddr, types.NewPtr(n.Type()), n, s.sp, s.startmem)
results = append(results, ssa.Param{Type: n.Type(), Offset: int32(n.FrameOffset()), Name: n})
results = append(results, ssa.Param{Name: n})
case ir.PAUTO:
// processed at each use, to prevent Addr coming
// before the decl.
@ -468,7 +482,36 @@ func buildssa(fn *ir.Func, worker int) *ssa.Func {
s.Fatalf("local variable with class %v unimplemented", n.Class)
}
}
s.f.OwnAux = ssa.OwnAuxCall(fn.LSym, args, results)
// TODO: figure out why base.Ctxt.FixedFrameSize() is not added to these offsets here (compare to calls).
// The input half is ignored unless a register ABI is used.
var args []ssa.Param
for _, p := range params.InParams() {
r := p.Registers
var o int32
if len(r) == 0 {
o = p.Offset()
} else {
o = p.SpillOffset() + int32(params.SpillAreaOffset())
}
args = append(args, ssa.Param{Type: p.Type, Offset: o, Reg: r})
}
// For now, need the ir.Name attached to these, so update those already created.
for i, p := range params.OutParams() {
r := p.Registers
var o int32
if len(r) == 0 {
o = p.Offset()
} else {
o = types.BADWIDTH
}
results[i].Type = p.Type
results[i].Offset = o
results[i].Reg = r
}
s.f.OwnAux = ssa.OwnAuxCall(fn.LSym, args, results, params)
// Populate SSAable arguments.
for _, n := range fn.Dcl {
@ -1846,7 +1889,7 @@ func (s *state) exit() *ssa.Block {
}
// Run exit code. Today, this is just racefuncexit, in -race mode.
// TODO this seems risky here with a register-ABI, but not clear it is right to do it earlier either.
// TODO(register args) this seems risky here with a register-ABI, but not clear it is right to do it earlier either.
// Spills in register allocation might just fix it.
s.stmtList(s.curfn.Exit)
@ -4691,7 +4734,7 @@ func (s *state) openDeferExit() {
aux := ssa.ClosureAuxCall(ACArgs, ACResults)
call = s.newValue2A(ssa.OpClosureLECall, aux.LateExpansionResultType(), aux, codeptr, v)
} else {
aux := ssa.StaticAuxCall(fn.(*ir.Name).Linksym(), ACArgs, ACResults)
aux := ssa.StaticAuxCall(fn.(*ir.Name).Linksym(), ACArgs, ACResults, nil) // TODO will need types for this.
call = s.newValue0A(ssa.OpStaticLECall, aux.LateExpansionResultType(), aux)
}
callArgs = append(callArgs, s.mem())
@ -4738,18 +4781,9 @@ func (s *state) call(n *ir.CallExpr, k callKind, returnResultAddr bool) *ssa.Val
var codeptr *ssa.Value // ptr to target code (if dynamic)
var rcvr *ssa.Value // receiver to set
fn := n.X
var ACArgs []ssa.Param
var ACResults []ssa.Param
var callArgs []*ssa.Value
res := n.X.Type().Results()
if k == callNormal {
nf := res.NumFields()
for i := 0; i < nf; i++ {
fp := res.Field(i)
ACResults = append(ACResults, ssa.Param{Type: fp.Type, Offset: int32(fp.Offset + base.Ctxt.FixedFrameSize())})
}
}
var ACArgs []ssa.Param // AuxCall args
var ACResults []ssa.Param // AuxCall results
var callArgs []*ssa.Value // For late-expansion, the args themselves (not stored, args to the call instead).
inRegisters := false
switch n.Op() {
@ -4757,7 +4791,7 @@ func (s *state) call(n *ir.CallExpr, k callKind, returnResultAddr bool) *ssa.Val
if k == callNormal && fn.Op() == ir.ONAME && fn.(*ir.Name).Class == ir.PFUNC {
fn := fn.(*ir.Name)
callee = fn
// TODO remove after register abi is working
// TODO(register args) remove after register abi is working
inRegistersImported := fn.Pragma()&ir.RegisterParams != 0
inRegistersSamePackage := fn.Func != nil && fn.Func.Pragma&ir.RegisterParams != 0
inRegisters = inRegistersImported || inRegistersSamePackage
@ -4790,6 +4824,27 @@ func (s *state) call(n *ir.CallExpr, k callKind, returnResultAddr bool) *ssa.Val
types.CalcSize(fn.Type())
stksize := fn.Type().ArgWidth() // includes receiver, args, and results
abi := s.f.ABI1
if !inRegisters {
abi = s.f.ABI0
}
params := abi.ABIAnalyze(n.X.Type())
res := n.X.Type().Results()
if k == callNormal {
for _, p := range params.OutParams() {
r := p.Registers
var o int32
if len(r) == 0 {
o = p.Offset()
} else {
o = p.SpillOffset() + int32(params.SpillAreaOffset())
}
ACResults = append(ACResults, ssa.Param{Type: p.Type, Offset: o + int32(base.Ctxt.FixedFrameSize()), Reg: r})
}
}
var call *ssa.Value
if k == callDeferStack {
// Make a defer struct d on the stack.
@ -4841,14 +4896,14 @@ func (s *state) call(n *ir.CallExpr, k callKind, returnResultAddr bool) *ssa.Val
// Call runtime.deferprocStack with pointer to _defer record.
ACArgs = append(ACArgs, ssa.Param{Type: types.Types[types.TUINTPTR], Offset: int32(base.Ctxt.FixedFrameSize())})
aux := ssa.StaticAuxCall(ir.Syms.DeferprocStack, ACArgs, ACResults)
aux := ssa.StaticAuxCall(ir.Syms.DeferprocStack, ACArgs, ACResults, nil)
callArgs = append(callArgs, addr, s.mem())
call = s.newValue0A(ssa.OpStaticLECall, aux.LateExpansionResultType(), aux)
call.AddArgs(callArgs...)
if stksize < int64(types.PtrSize) {
// We need room for both the call to deferprocStack and the call to
// the deferred function.
// TODO Revisit this if/when we pass args in registers.
// TODO(register args) Revisit this if/when we pass args in registers.
stksize = int64(types.PtrSize)
}
call.AuxInt = stksize
@ -4870,7 +4925,7 @@ func (s *state) call(n *ir.CallExpr, k callKind, returnResultAddr bool) *ssa.Val
// Set receiver (for interface calls).
if rcvr != nil {
ACArgs = append(ACArgs, ssa.Param{Type: types.Types[types.TUINTPTR], Offset: int32(argStart)})
// ACArgs = append(ACArgs, ssa.Param{Type: types.Types[types.TUINTPTR], Offset: int32(argStart)})
callArgs = append(callArgs, rcvr)
}
@ -4880,11 +4935,20 @@ func (s *state) call(n *ir.CallExpr, k callKind, returnResultAddr bool) *ssa.Val
if n.Op() == ir.OCALLMETH {
base.Fatalf("OCALLMETH missed by walkCall")
}
for i, n := range args {
f := t.Params().Field(i)
ACArg, arg := s.putArg(n, f.Type, argStart+f.Offset)
for _, p := range params.InParams() {
r := p.Registers
var o int32
if len(r) == 0 {
o = p.Offset()
} else {
o = p.SpillOffset() + int32(params.SpillAreaOffset())
}
ACArg := ssa.Param{Type: p.Type, Offset: int32(argStart) + o, Reg: r}
ACArgs = append(ACArgs, ACArg)
callArgs = append(callArgs, arg)
}
for i, n := range args {
callArgs = append(callArgs, s.putArg(n, t.Params().Field(i).Type))
}
callArgs = append(callArgs, s.mem())
@ -4892,11 +4956,11 @@ func (s *state) call(n *ir.CallExpr, k callKind, returnResultAddr bool) *ssa.Val
// call target
switch {
case k == callDefer:
aux := ssa.StaticAuxCall(ir.Syms.Deferproc, ACArgs, ACResults)
aux := ssa.StaticAuxCall(ir.Syms.Deferproc, ACArgs, ACResults, nil) // TODO paramResultInfo for DeferProc
call = s.newValue0A(ssa.OpStaticLECall, aux.LateExpansionResultType(), aux)
case k == callGo:
aux := ssa.StaticAuxCall(ir.Syms.Newproc, ACArgs, ACResults)
call = s.newValue0A(ssa.OpStaticLECall, aux.LateExpansionResultType(), aux)
aux := ssa.StaticAuxCall(ir.Syms.Newproc, ACArgs, ACResults, nil)
call = s.newValue0A(ssa.OpStaticLECall, aux.LateExpansionResultType(), aux) // TODO paramResultInfo for NewProc
case closure != nil:
// rawLoad because loading the code pointer from a
// closure is always safe, but IsSanitizerSafeAddr
@ -4910,7 +4974,7 @@ func (s *state) call(n *ir.CallExpr, k callKind, returnResultAddr bool) *ssa.Val
aux := ssa.InterfaceAuxCall(ACArgs, ACResults)
call = s.newValue1A(ssa.OpInterLECall, aux.LateExpansionResultType(), aux, codeptr)
case callee != nil:
aux := ssa.StaticAuxCall(callTargetLSym(callee, s.curfn.LSym), ACArgs, ACResults)
aux := ssa.StaticAuxCall(callTargetLSym(callee, s.curfn.LSym), ACArgs, ACResults, params)
call = s.newValue0A(ssa.OpStaticLECall, aux.LateExpansionResultType(), aux)
default:
s.Fatalf("bad call type %v %v", n.Op(), n)
@ -5391,7 +5455,7 @@ func (s *state) rtcall(fn *obj.LSym, returns bool, results []*types.Type, args .
// Issue call
var call *ssa.Value
aux := ssa.StaticAuxCall(fn, ACArgs, ACResults)
aux := ssa.StaticAuxCall(fn, ACArgs, ACResults, nil) // WILL NEED A TYPE FOR THIS.)
callArgs = append(callArgs, s.mem())
call = s.newValue0A(ssa.OpStaticLECall, aux.LateExpansionResultType(), aux)
call.AddArgs(callArgs...)
@ -5539,15 +5603,15 @@ func (s *state) storeTypePtrs(t *types.Type, left, right *ssa.Value) {
}
}
// putArg evaluates n for the purpose of passing it as an argument to a function and returns the corresponding Param and value for the call.
func (s *state) putArg(n ir.Node, t *types.Type, off int64) (ssa.Param, *ssa.Value) {
// putArg evaluates n for the purpose of passing it as an argument to a function and returns the value for the call.
func (s *state) putArg(n ir.Node, t *types.Type) *ssa.Value {
var a *ssa.Value
if !TypeOK(t) {
a = s.newValue2(ssa.OpDereference, t, s.addr(n), s.mem())
} else {
a = s.expr(n)
}
return ssa.Param{Type: t, Offset: int32(off)}, a
return a
}
func (s *state) storeArgWithBase(n ir.Node, t *types.Type, base *ssa.Value, off int64) {

View file

@ -1025,7 +1025,7 @@ func (w *exportWriter) funcExt(n *ir.Name) {
w.linkname(n.Sym())
w.symIdx(n.Sym())
// TODO remove after register abi is working.
// TODO(register args) remove after register abi is working.
w.uint64(uint64(n.Func.Pragma))
// Escape analysis.

View file

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

View file

@ -1,3 +1,4 @@
// skip
// runindir -gcflags=-c=1
// +build !windows
@ -5,6 +6,7 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// TODO May delete or adapt this test once regabi is the default
// TODO(register args) Temporarily disabled now that register abi info is flowing halfway through the compiler.
// TODO(register args) May delete or adapt this test once regabi is the default
package ignore