"cmd/compile/internal/types"
"cmd/internal/src"
"fmt"
+ "math"
"sync"
)
// 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,
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
}
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
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
})
}
-// 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
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})
// (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})
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})
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)
}
}
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},
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})
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)
nrtest(t, c128, 2)
nrtest(t, s, 4)
nrtest(t, a, 12)
-
}
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(`