diff options
Diffstat (limited to 'src/cmd/compile/internal/abi/abiutils.go')
-rw-r--r-- | src/cmd/compile/internal/abi/abiutils.go | 237 |
1 files changed, 194 insertions, 43 deletions
diff --git a/src/cmd/compile/internal/abi/abiutils.go b/src/cmd/compile/internal/abi/abiutils.go index e935821802..3eab4b8d8b 100644 --- a/src/cmd/compile/internal/abi/abiutils.go +++ b/src/cmd/compile/internal/abi/abiutils.go @@ -27,9 +27,15 @@ type ABIParamResultInfo struct { outparams []ABIParamAssignment offsetToSpillArea int64 spillAreaSize int64 + inRegistersUsed int + outRegistersUsed int config *ABIConfig // to enable String() method } +func (a *ABIParamResultInfo) Config() *ABIConfig { + return a.config +} + func (a *ABIParamResultInfo) InParams() []ABIParamAssignment { return a.inparams } @@ -38,12 +44,20 @@ func (a *ABIParamResultInfo) OutParams() []ABIParamAssignment { return a.outparams } -func (a *ABIParamResultInfo) InParam(i int) ABIParamAssignment { - return a.inparams[i] +func (a *ABIParamResultInfo) InRegistersUsed() int { + return a.inRegistersUsed } -func (a *ABIParamResultInfo) OutParam(i int) ABIParamAssignment { - return a.outparams[i] +func (a *ABIParamResultInfo) OutRegistersUsed() int { + return a.outRegistersUsed +} + +func (a *ABIParamResultInfo) InParam(i int) *ABIParamAssignment { + return &a.inparams[i] +} + +func (a *ABIParamResultInfo) OutParam(i int) *ABIParamAssignment { + return &a.outparams[i] } func (a *ABIParamResultInfo) SpillAreaOffset() int64 { @@ -68,10 +82,11 @@ type RegIndex uint8 // ABIParamAssignment holds information about how a specific param or // result will be passed: in registers (in which case 'Registers' is // populated) or on the stack (in which case 'Offset' is set to a -// non-negative stack offset. The values in 'Registers' are indices (as -// described above), not architected registers. +// non-negative stack offset. The values in 'Registers' are indices +// (as described above), not architected registers. type ABIParamAssignment struct { Type *types.Type + Name types.Object // should always be *ir.Name, used to match with a particular ssa.OpArg. Registers []RegIndex offset int32 } @@ -96,6 +111,18 @@ func (a *ABIParamAssignment) SpillOffset() int32 { return a.offset } +// FrameOffset returns the location that a value would spill to, if any exists. +// For register-allocated inputs, that is their spill offset reserved for morestack +// (might as well use it, it is there); for stack-allocated inputs and outputs, +// that is their location on the stack. For register-allocated outputs, there is +// no defined spill area, so return -1. +func (a *ABIParamAssignment) FrameOffset(i *ABIParamResultInfo) int64 { + if len(a.Registers) == 0 || a.offset == -1 { + return int64(a.offset) + } + return int64(a.offset) + i.SpillAreaOffset() +} + // RegAmounts holds a specified number of integer/float registers. type RegAmounts struct { intRegs int @@ -106,94 +133,216 @@ 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).FixedFrameSize() -- extra linkage information on some architectures. regAmounts RegAmounts regsForTypeCache map[*types.Type]int } // NewABIConfig returns a new ABI configuration for an architecture with // iRegsCount integer/pointer registers and fRegsCount floating point registers. -func NewABIConfig(iRegsCount, fRegsCount int) *ABIConfig { - return &ABIConfig{regAmounts: RegAmounts{iRegsCount, fRegsCount}, regsForTypeCache: make(map[*types.Type]int)} +func NewABIConfig(iRegsCount, fRegsCount int, offsetForLocals int64) *ABIConfig { + return &ABIConfig{offsetForLocals: offsetForLocals, 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 +} + +// 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 +} + +// 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) } // 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 } if t.IsScalar() || t.IsPtrShaped() { - var n int if t.IsComplex() { n = 2 } else { n = (int(t.Size()) + types.RegSize - 1) / types.RegSize } - a.regsForTypeCache[t] = n - return n - } - typ := t.Kind() - n := 0 - switch typ { - case types.TARRAY: - n = a.NumParamRegs(t.Elem()) * int(t.NumElem()) - case types.TSTRUCT: - for _, f := range t.FieldSlice() { - n += a.NumParamRegs(f.Type) + } else { + typ := t.Kind() + switch typ { + case types.TARRAY: + n = a.NumParamRegs(t.Elem()) * int(t.NumElem()) + case types.TSTRUCT: + for _, f := range t.FieldSlice() { + 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) } - 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 } -// ABIAnalyze takes a function type 't' and an ABI rules description +// preAllocateParams gets the slice sizes right for inputs and outputs. +func (a *ABIParamResultInfo) preAllocateParams(hasRcvr bool, nIns, nOuts int) { + if hasRcvr { + nIns++ + } + a.inparams = make([]ABIParamAssignment, 0, nIns) + a.outparams = make([]ABIParamAssignment, 0, nOuts) +} + +// ABIAnalyzeTypes takes an optional receiver type, arrays of ins and outs, and returns an ABIParamResultInfo, +// based on the given configuration. This is the same result computed by config.ABIAnalyze applied to the +// corresponding method/function type, except that all the embedded parameter names are nil. +// This is intended for use by ssagen/ssa.go:(*state).rtcall, for runtime functions that lack a parsed function type. +func (config *ABIConfig) ABIAnalyzeTypes(rcvr *types.Type, ins, outs []*types.Type) *ABIParamResultInfo { + setup() + s := assignState{ + stackOffset: config.offsetForLocals, + rTotal: config.regAmounts, + } + result := &ABIParamResultInfo{config: config} + result.preAllocateParams(rcvr != nil, len(ins), len(outs)) + + // Receiver + if rcvr != nil { + result.inparams = append(result.inparams, + s.assignParamOrReturn(rcvr, nil, false)) + } + + // Inputs + for _, t := range ins { + result.inparams = append(result.inparams, + s.assignParamOrReturn(t, nil, false)) + } + s.stackOffset = types.Rnd(s.stackOffset, int64(types.RegSize)) + result.inRegistersUsed = s.rUsed.intRegs + s.rUsed.floatRegs + + // Outputs + s.rUsed = RegAmounts{} + for _, t := range outs { + result.outparams = append(result.outparams, s.assignParamOrReturn(t, nil, true)) + } + // The spill area is at a register-aligned offset and its size is rounded up to a register alignment. + // TODO in theory could align offset only to minimum required by spilled data types. + result.offsetToSpillArea = alignTo(s.stackOffset, types.RegSize) + result.spillAreaSize = alignTo(s.spillOffset, types.RegSize) + result.outRegistersUsed = s.rUsed.intRegs + s.rUsed.floatRegs + + return result +} + +// ABIAnalyzeFuncType takes a function type 'ft' and an ABI rules description // '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) ABIAnalyzeFuncType(ft *types.Func) *ABIParamResultInfo { setup() s := assignState{ - rTotal: config.regAmounts, + stackOffset: config.offsetForLocals, + rTotal: config.regAmounts, } - result := ABIParamResultInfo{config: config} + result := &ABIParamResultInfo{config: config} + result.preAllocateParams(ft.Receiver != nil, ft.Params.NumFields(), ft.Results.NumFields()) // Receiver - ft := t.FuncType() - if t.NumRecvs() != 0 { - rfsl := ft.Receiver.FieldSlice() + // TODO(register args) ? seems like "struct" and "fields" is not right anymore for describing function parameters + if ft.Receiver != nil && ft.Receiver.NumFields() != 0 { + r := ft.Receiver.FieldSlice()[0] result.inparams = append(result.inparams, - s.assignParamOrReturn(rfsl[0].Type, false)) + s.assignParamOrReturn(r.Type, r.Nname, false)) } // Inputs ifsl := ft.Params.FieldSlice() for _, f := range ifsl { result.inparams = append(result.inparams, - s.assignParamOrReturn(f.Type, false)) + s.assignParamOrReturn(f.Type, f.Nname, false)) } s.stackOffset = types.Rnd(s.stackOffset, int64(types.RegSize)) + result.inRegistersUsed = s.rUsed.intRegs + s.rUsed.floatRegs // Outputs s.rUsed = RegAmounts{} ofsl := ft.Results.FieldSlice() for _, f := range ofsl { - result.outparams = append(result.outparams, s.assignParamOrReturn(f.Type, true)) + result.outparams = append(result.outparams, s.assignParamOrReturn(f.Type, f.Nname, true)) } // The spill area is at a register-aligned offset and its size is rounded up to a register alignment. // TODO in theory could align offset only to minimum required by spilled data types. result.offsetToSpillArea = alignTo(s.stackOffset, types.RegSize) result.spillAreaSize = alignTo(s.spillOffset, types.RegSize) + result.outRegistersUsed = s.rUsed.intRegs + s.rUsed.floatRegs + return result +} +// ABIAnalyze returns the same result as ABIAnalyzeFuncType, but also +// updates the offsets of all the receiver, input, and output fields. +func (config *ABIConfig) ABIAnalyze(t *types.Type) *ABIParamResultInfo { + ft := t.FuncType() + result := config.ABIAnalyzeFuncType(ft) + // Fill in the frame offsets for receiver, inputs, results + k := 0 + if t.NumRecvs() != 0 { + config.updateOffset(result, ft.Receiver.FieldSlice()[0], result.inparams[0], false) + k++ + } + for i, f := range ft.Params.FieldSlice() { + config.updateOffset(result, f, result.inparams[k+i], false) + } + for i, f := range ft.Results.FieldSlice() { + config.updateOffset(result, f, result.outparams[i], true) + } return result } +// parameterUpdateMu protects the Offset field of function/method parameters (a subset of structure Fields) +var parameterUpdateMu sync.Mutex + +// FieldOffsetOf returns a concurency-safe version of f.Offset +func FieldOffsetOf(f *types.Field) int64 { + parameterUpdateMu.Lock() + defer parameterUpdateMu.Unlock() + return f.Offset +} + +func (config *ABIConfig) updateOffset(result *ABIParamResultInfo, f *types.Field, a ABIParamAssignment, isReturn bool) { + // Everything except return values in registers has either a frame home (if not in a register) or a frame spill location. + if !isReturn || len(a.Registers) == 0 { + // The type frame offset DOES NOT show effects of minimum frame size. + // Getting this wrong breaks stackmaps, see liveness/plive.go:WriteFuncMap and typebits/typebits.go:Set + parameterUpdateMu.Lock() + defer parameterUpdateMu.Unlock() + off := a.FrameOffset(result) - config.LocalsOffset() + fOffset := f.Offset + if fOffset == types.BOGUS_FUNARG_OFFSET { + // Set the Offset the first time. After that, we may recompute it, but it should never change. + f.Offset = off + } else if fOffset != off { + panic(fmt.Errorf("Offset changed from %d to %d", fOffset, off)) + } + } +} + //...................................................................... // // Non-public portions. @@ -292,7 +441,7 @@ func (state *assignState) allocateRegs() []RegIndex { // 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, isReturn bool) ABIParamAssignment { +func (state *assignState) regAllocate(t *types.Type, name types.Object, isReturn bool) ABIParamAssignment { spillLoc := int64(-1) if !isReturn { // Spill for register-resident t must be aligned for storage of a t. @@ -301,6 +450,7 @@ func (state *assignState) regAllocate(t *types.Type, isReturn bool) ABIParamAssi } return ABIParamAssignment{ Type: t, + Name: name, Registers: state.allocateRegs(), offset: int32(spillLoc), } @@ -309,9 +459,10 @@ func (state *assignState) regAllocate(t *types.Type, isReturn bool) ABIParamAssi // 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) ABIParamAssignment { +func (state *assignState) stackAllocate(t *types.Type, name types.Object) ABIParamAssignment { return ABIParamAssignment{ Type: t, + Name: name, offset: int32(state.stackSlot(t)), } } @@ -444,18 +595,18 @@ func (state *assignState) regassign(pt *types.Type) bool { } // assignParamOrReturn processes a given receiver, param, or result -// of type 'pt' to determine whether it can be register assigned. +// 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) assignParamOrReturn(pt *types.Type, isReturn bool) ABIParamAssignment { +func (state *assignState) assignParamOrReturn(pt *types.Type, n types.Object, isReturn bool) ABIParamAssignment { state.pUsed = RegAmounts{} if pt.Width == types.BADWIDTH { panic("should never happen") } else if pt.Width == 0 { - return state.stackAllocate(pt) + return state.stackAllocate(pt, n) } else if state.regassign(pt) { - return state.regAllocate(pt, isReturn) + return state.regAllocate(pt, n, isReturn) } else { - return state.stackAllocate(pt) + return state.stackAllocate(pt, n) } } |