diff --git a/src/cmd/compile/internal/abi/abiutils.go b/src/cmd/compile/internal/abi/abiutils.go index db795b2d0a..16126347cf 100644 --- a/src/cmd/compile/internal/abi/abiutils.go +++ b/src/cmd/compile/internal/abi/abiutils.go @@ -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 diff --git a/src/cmd/compile/internal/test/abiutils_test.go b/src/cmd/compile/internal/test/abiutils_test.go index 8ed7622632..dad7991b5d 100644 --- a/src/cmd/compile/internal/test/abiutils_test.go +++ b/src/cmd/compile/internal/test/abiutils_test.go @@ -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(` diff --git a/src/cmd/compile/internal/test/abiutilsaux_test.go b/src/cmd/compile/internal/test/abiutilsaux_test.go index 7f929c603d..fb1c3983a8 100644 --- a/src/cmd/compile/internal/test/abiutilsaux_test.go +++ b/src/cmd/compile/internal/test/abiutilsaux_test.go @@ -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 {