1
0
mirror of https://github.com/golang/go synced 2024-09-30 22:18:32 -06:00

cmd/compile/internal/abi: use Type.Registers

Now that types can self-report how many registers they need, it's much
easier to determine whether a parameter will fit into the available
registers: simply compare the number of registers needed against the
number of registers still available.

This also eliminates the need for the NumParamRegs cache.

Also, the new code in NumParamRegs is stricter in only allowing it to
be called on types that can actually be passed in registers, which
requires a test to be corrected for that. While here, change mkstruct
to a variadic function, so the call sites require less boilerplate.

Change-Id: Iebe1a0456a8053a10e551e5da796014e5b1b695b
Reviewed-on: https://go-review.googlesource.com/c/go/+/527339
Reviewed-by: Cuong Manh Le <cuong.manhle.vn@gmail.com>
Auto-Submit: Matthew Dempsky <mdempsky@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Heschi Kreinick <heschi@google.com>
This commit is contained in:
Matthew Dempsky 2023-09-11 21:13:10 -07:00 committed by Gopher Robot
parent c80a9f172b
commit c0e2a9dffd
3 changed files with 77 additions and 209 deletions

View File

@ -10,6 +10,7 @@ import (
"cmd/compile/internal/types"
"cmd/internal/src"
"fmt"
"math"
"sync"
)
@ -258,72 +259,46 @@ type RegAmounts struct {
// by the ABI rules for parameter passing and result returning.
type ABIConfig struct {
// Do we need anything more than this?
offsetForLocals int64 // e.g., obj.(*Link).Arch.FixedFrameSize -- extra linkage information on some architectures.
regAmounts RegAmounts
regsForTypeCache map[*types.Type]int
offsetForLocals int64 // e.g., obj.(*Link).Arch.FixedFrameSize -- extra linkage information on some architectures.
regAmounts RegAmounts
}
// NewABIConfig returns a new ABI configuration for an architecture with
// iRegsCount integer/pointer registers and fRegsCount floating point registers.
func NewABIConfig(iRegsCount, fRegsCount int, offsetForLocals int64) *ABIConfig {
return &ABIConfig{offsetForLocals: offsetForLocals, regAmounts: RegAmounts{iRegsCount, fRegsCount}, regsForTypeCache: make(map[*types.Type]int)}
return &ABIConfig{offsetForLocals: offsetForLocals, regAmounts: RegAmounts{iRegsCount, fRegsCount}}
}
// 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
// Copy returns config.
//
// TODO(mdempsky): Remove.
func (config *ABIConfig) Copy() *ABIConfig {
return config
}
// LocalsOffset returns the architecture-dependent offset from SP for args and results.
// In theory this is only used for debugging; it ought to already be incorporated into
// results from the ABI-related methods
func (a *ABIConfig) LocalsOffset() int64 {
return a.offsetForLocals
func (config *ABIConfig) LocalsOffset() int64 {
return config.offsetForLocals
}
// FloatIndexFor translates r into an index in the floating point parameter
// registers. If the result is negative, the input index was actually for the
// integer parameter registers.
func (a *ABIConfig) FloatIndexFor(r RegIndex) int64 {
return int64(r) - int64(a.regAmounts.intRegs)
func (config *ABIConfig) FloatIndexFor(r RegIndex) int64 {
return int64(r) - int64(config.regAmounts.intRegs)
}
// 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 {
var n int
if n, ok := a.regsForTypeCache[t]; ok {
return n
// NumParamRegs returns the total number of registers used to
// represent a parameter of the given type, which must be register
// assignable.
func (config *ABIConfig) NumParamRegs(typ *types.Type) int {
intRegs, floatRegs := typ.Registers()
if intRegs == math.MaxUint8 && floatRegs == math.MaxUint8 {
base.Fatalf("cannot represent parameters of type %v in registers", typ)
}
if t.IsScalar() || t.IsPtrShaped() {
if t.IsComplex() {
n = 2
} else {
n = (int(t.Size()) + types.RegSize - 1) / types.RegSize
}
} else {
typ := t.Kind()
switch typ {
case types.TARRAY:
n = a.NumParamRegs(t.Elem()) * int(t.NumElem())
case types.TSTRUCT:
for _, f := range t.Fields() {
n += a.NumParamRegs(f.Type)
}
case types.TSLICE:
n = a.NumParamRegs(synthSlice)
case types.TSTRING:
n = a.NumParamRegs(synthString)
case types.TINTER:
n = a.NumParamRegs(synthIface)
}
}
a.regsForTypeCache[t] = n
return n
return int(intRegs) + int(floatRegs)
}
// ABIAnalyzeTypes takes slices of parameter and result types, and returns an ABIParamResultInfo,
@ -498,7 +473,6 @@ func (ri *ABIParamResultInfo) String() string {
type assignState struct {
rTotal RegAmounts // total reg amounts from ABI rules
rUsed RegAmounts // regs used by params completely assigned so far
pUsed RegAmounts // regs used by the current param (or pieces therein)
stackOffset int64 // current stack offset
spillOffset int64 // current spill offset
}
@ -516,12 +490,11 @@ func alignTo(a int64, t int) int64 {
return types.RoundUp(a, int64(t))
}
// stackSlot returns a stack offset for a param or result of the
// specified type.
func (state *assignState) stackSlot(t *types.Type) int64 {
rv := align(state.stackOffset, t)
state.stackOffset = rv + t.Size()
return rv
// nextSlot allocates the next available slot for typ.
func nextSlot(offsetp *int64, typ *types.Type) int64 {
offset := align(*offsetp, typ)
*offsetp = offset + typ.Size()
return offset
}
// allocateRegs returns an ordered list of register indices for a parameter or result
@ -575,105 +548,6 @@ func (state *assignState) allocateRegs(regs []RegIndex, t *types.Type) []RegInde
panic("unreachable")
}
// regAllocate creates a register ABIParamAssignment object for a param
// or result with the specified type, as a final step (this assumes
// that all of the safety/suitability analysis is complete).
func (state *assignState) regAllocate(t *types.Type, name types.Object, isResult bool) ABIParamAssignment {
spillLoc := int64(-1)
if !isResult {
// Spill for register-resident t must be aligned for storage of a t.
spillLoc = align(state.spillOffset, t)
state.spillOffset = spillLoc + t.Size()
}
return ABIParamAssignment{
Type: t,
Name: name,
Registers: state.allocateRegs([]RegIndex{}, t),
offset: int32(spillLoc),
}
}
// stackAllocate creates a stack memory ABIParamAssignment object for
// a param or result with the specified type, as a final step (this
// assumes that all of the safety/suitability analysis is complete).
func (state *assignState) stackAllocate(t *types.Type, name types.Object) ABIParamAssignment {
return ABIParamAssignment{
Type: t,
Name: name,
offset: int32(state.stackSlot(t)),
}
}
// intUsed returns the number of integer registers consumed
// at a given point within an assignment stage.
func (state *assignState) intUsed() int {
return state.rUsed.intRegs + state.pUsed.intRegs
}
// floatUsed returns the number of floating point registers consumed at
// a given point within an assignment stage.
func (state *assignState) floatUsed() int {
return state.rUsed.floatRegs + state.pUsed.floatRegs
}
// regassignIntegral examines a param/result of integral type 't' to
// determines whether it can be register-assigned. Returns TRUE if we
// can register allocate, FALSE otherwise (and updates state
// accordingly).
func (state *assignState) regassignIntegral(t *types.Type) bool {
regsNeeded := int(types.RoundUp(t.Size(), int64(types.PtrSize)) / int64(types.PtrSize))
if t.IsComplex() {
regsNeeded = 2
}
// Floating point and complex.
if t.IsFloat() || t.IsComplex() {
if regsNeeded+state.floatUsed() > state.rTotal.floatRegs {
// not enough regs
return false
}
state.pUsed.floatRegs += regsNeeded
return true
}
// Non-floating point
if regsNeeded+state.intUsed() > state.rTotal.intRegs {
// not enough regs
return false
}
state.pUsed.intRegs += regsNeeded
return true
}
// regassignArray processes an array type (or array component within some
// other enclosing type) to determine if it can be register assigned.
// Returns TRUE if we can register allocate, FALSE otherwise.
func (state *assignState) regassignArray(t *types.Type) bool {
nel := t.NumElem()
if nel == 0 {
return true
}
if nel > 1 {
// Not an array of length 1: stack assign
return false
}
// Visit element
return state.regassign(t.Elem())
}
// regassignStruct processes a struct type (or struct component within
// some other enclosing type) to determine if it can be register
// assigned. Returns TRUE if we can register allocate, FALSE otherwise.
func (state *assignState) regassignStruct(t *types.Type) bool {
for _, field := range t.Fields() {
if !state.regassign(field.Type) {
return false
}
}
return true
}
// synthOnce ensures that we only create the synth* fake types once.
var synthOnce sync.Once
@ -711,47 +585,42 @@ func setup() {
})
}
// regassign examines a given param type (or component within some
// composite) to determine if it can be register assigned. Returns
// TRUE if we can register allocate, FALSE otherwise.
func (state *assignState) regassign(pt *types.Type) bool {
typ := pt.Kind()
if pt.IsScalar() || pt.IsPtrShaped() {
return state.regassignIntegral(pt)
}
switch typ {
case types.TARRAY:
return state.regassignArray(pt)
case types.TSTRUCT:
return state.regassignStruct(pt)
case types.TSLICE:
return state.regassignStruct(synthSlice)
case types.TSTRING:
return state.regassignStruct(synthString)
case types.TINTER:
return state.regassignStruct(synthIface)
default:
base.Fatalf("not expected")
panic("unreachable")
}
}
// assignParam processes a given receiver, param, or result
// of field f to determine whether it can be register assigned.
// The result of the analysis is recorded in the result
// ABIParamResultInfo held in 'state'.
func (state *assignState) assignParam(pt *types.Type, n types.Object, isResult bool) ABIParamAssignment {
state.pUsed = RegAmounts{}
if pt.Size() == types.BADWIDTH {
base.Fatalf("should never happen")
panic("unreachable")
} else if pt.Size() == 0 {
return state.stackAllocate(pt, n)
} else if state.regassign(pt) {
return state.regAllocate(pt, n, isResult)
} else {
return state.stackAllocate(pt, n)
func (state *assignState) assignParam(typ *types.Type, name types.Object, isResult bool) ABIParamAssignment {
registers := state.tryAllocRegs(typ)
var offset int64 = -1
if registers == nil { // stack allocated; needs stack slot
offset = nextSlot(&state.stackOffset, typ)
} else if !isResult { // register-allocated param; needs spill slot
offset = nextSlot(&state.spillOffset, typ)
}
return ABIParamAssignment{
Type: typ,
Name: name,
Registers: registers,
offset: int32(offset),
}
}
// tryAllocRegs attempts to allocate registers to represent a
// parameter of the given type. If unsuccessful, it returns nil.
func (state *assignState) tryAllocRegs(typ *types.Type) []RegIndex {
if typ.Size() == 0 {
return nil // zero-size parameters are defined as being stack allocated
}
intRegs, floatRegs := typ.Registers()
if int(intRegs) > state.rTotal.intRegs-state.rUsed.intRegs || int(floatRegs) > state.rTotal.floatRegs-state.rUsed.floatRegs {
return nil // too few available registers
}
regs := make([]RegIndex, 0, int(intRegs)+int(floatRegs))
return state.allocateRegs(regs, typ)
}
// ComputePadding returns a list of "post element" padding values in

View File

@ -157,7 +157,7 @@ func TestABIUtilsStruct1(t *testing.T) {
i16 := types.Types[types.TINT16]
i32 := types.Types[types.TINT32]
i64 := types.Types[types.TINT64]
s := mkstruct([]*types.Type{i8, i8, mkstruct([]*types.Type{}), i8, i16})
s := mkstruct(i8, i8, mkstruct(), i8, i16)
ft := mkFuncType(nil, []*types.Type{i8, s, i64},
[]*types.Type{s, i8, i32})
@ -181,8 +181,8 @@ func TestABIUtilsStruct2(t *testing.T) {
// (r1 fs, r2 fs)
f64 := types.Types[types.TFLOAT64]
i64 := types.Types[types.TINT64]
s := mkstruct([]*types.Type{i64, mkstruct([]*types.Type{})})
fs := mkstruct([]*types.Type{f64, s, mkstruct([]*types.Type{})})
s := mkstruct(i64, mkstruct())
fs := mkstruct(f64, s, mkstruct())
ft := mkFuncType(nil, []*types.Type{s, s, fs},
[]*types.Type{fs, fs})
@ -213,9 +213,10 @@ func TestABIUtilsEmptyFieldAtEndOfStruct(t *testing.T) {
ab2 := types.NewArray(tb, 2)
a2 := types.NewArray(i64, 2)
a3 := types.NewArray(i16, 3)
s := mkstruct([]*types.Type{a2, mkstruct([]*types.Type{})})
s2 := mkstruct([]*types.Type{a3, mkstruct([]*types.Type{})})
fs := mkstruct([]*types.Type{f64, s, mkstruct([]*types.Type{})})
empty := mkstruct()
s := mkstruct(a2, empty)
s2 := mkstruct(a3, empty)
fs := mkstruct(f64, s, empty)
ft := mkFuncType(nil, []*types.Type{s, ab2, s2, fs, fs},
[]*types.Type{fs, ab2, fs})
@ -233,12 +234,11 @@ func TestABIUtilsEmptyFieldAtEndOfStruct(t *testing.T) {
abitest(t, ft, exp)
// Check to make sure that NumParamRegs yields 2 and not 3
// for struct "s" (e.g. that it handles the padding properly).
nps := configAMD64.NumParamRegs(s)
if nps != 2 {
t.Errorf("NumParams(%v) returned %d expected %d\n",
s, nps, 2)
// Test that NumParamRegs doesn't assign registers to trailing padding.
typ := mkstruct(i64, i64, mkstruct())
have := configAMD64.NumParamRegs(typ)
if have != 2 {
t.Errorf("NumParams(%v): have %v, want %v", typ, have, 2)
}
}
@ -279,7 +279,7 @@ func TestABIUtilsMethod(t *testing.T) {
i16 := types.Types[types.TINT16]
i64 := types.Types[types.TINT64]
f64 := types.Types[types.TFLOAT64]
s1 := mkstruct([]*types.Type{i16, i16, i16})
s1 := mkstruct(i16, i16, i16)
ps1 := types.NewPtr(s1)
a7 := types.NewArray(ps1, 7)
ft := mkFuncType(s1, []*types.Type{ps1, a7, f64, i16, i16, i16},
@ -316,7 +316,7 @@ func TestABIUtilsInterfaces(t *testing.T) {
nei := types.NewInterface([]*types.Field{field})
i16 := types.Types[types.TINT16]
tb := types.Types[types.TBOOL]
s1 := mkstruct([]*types.Type{i16, i16, tb})
s1 := mkstruct(i16, i16, tb)
ft := mkFuncType(nil, []*types.Type{s1, ei, ei, nei, pei, nei, i16},
[]*types.Type{ei, nei, pei})
@ -347,8 +347,8 @@ func TestABINumParamRegs(t *testing.T) {
c64 := types.Types[types.TCOMPLEX64]
c128 := types.Types[types.TCOMPLEX128]
s := mkstruct([]*types.Type{i8, i8, mkstruct([]*types.Type{}), i8, i16})
a := types.NewArray(s, 3)
s := mkstruct(i8, i8, mkstruct(), i8, i16)
a := mkstruct(s, s, s)
nrtest(t, i8, 1)
nrtest(t, i16, 1)
@ -360,7 +360,6 @@ func TestABINumParamRegs(t *testing.T) {
nrtest(t, c128, 2)
nrtest(t, s, 4)
nrtest(t, a, 12)
}
func TestABIUtilsComputePadding(t *testing.T) {
@ -369,11 +368,11 @@ func TestABIUtilsComputePadding(t *testing.T) {
i16 := types.Types[types.TINT16]
i32 := types.Types[types.TINT32]
i64 := types.Types[types.TINT64]
emptys := mkstruct([]*types.Type{})
s1 := mkstruct([]*types.Type{i8, i16, emptys, i32, i64})
emptys := mkstruct()
s1 := mkstruct(i8, i16, emptys, i32, i64)
// func (p1 int32, p2 s1, p3 emptys, p4 [1]int32)
a1 := types.NewArray(i32, 1)
ft := mkFuncType(nil, []*types.Type{i32, s1, emptys, a1}, []*types.Type{})
ft := mkFuncType(nil, []*types.Type{i32, s1, emptys, a1}, nil)
// Run abitest() just to document what we're expected to see.
exp := makeExpectedDump(`

View File

@ -29,7 +29,7 @@ func mkParamResultField(t *types.Type, s *types.Sym, which ir.Class) *types.Fiel
// mkstruct is a helper routine to create a struct type with fields
// of the types specified in 'fieldtypes'.
func mkstruct(fieldtypes []*types.Type) *types.Type {
func mkstruct(fieldtypes ...*types.Type) *types.Type {
fields := make([]*types.Field, len(fieldtypes))
for k, t := range fieldtypes {
if t == nil {