diff options
author | David Chase <drchase@google.com> | 2021-01-08 10:15:36 -0500 |
---|---|---|
committer | David Chase <drchase@google.com> | 2021-01-13 15:54:19 +0000 |
commit | c41b999ad410c74bea222ee76488226a06ba4046 (patch) | |
tree | f50ac1f597df6de6b0a7f5399142d78718d6bfec /src/cmd/compile/internal/abi/abiutils.go | |
parent | 861707a8c84f0b1ddbcaea0e9f439398ee2175fb (diff) | |
download | go-c41b999ad410c74bea222ee76488226a06ba4046.tar.gz go-c41b999ad410c74bea222ee76488226a06ba4046.zip |
[dev.regabi] cmd/compile: refactor abiutils from "gc" into new "abi"
Needs to be visible to ssagen, and might as well start clean to avoid
creating a lot of accidental dependencies.
Added some methods for export.
Decided to use a pointer instead of value for ABIConfig uses.
Tests ended up separate from abiutil itself; otherwise there are import cycles.
Change-Id: I5570e1e6a463e303c5e2dc84e8dd4125e7c1adcc
Reviewed-on: https://go-review.googlesource.com/c/go/+/282614
Trust: David Chase <drchase@google.com>
Run-TryBot: David Chase <drchase@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Than McIntosh <thanm@google.com>
Reviewed-by: Jeremy Faller <jeremy@golang.org>
Diffstat (limited to 'src/cmd/compile/internal/abi/abiutils.go')
-rw-r--r-- | src/cmd/compile/internal/abi/abiutils.go | 385 |
1 files changed, 385 insertions, 0 deletions
diff --git a/src/cmd/compile/internal/abi/abiutils.go b/src/cmd/compile/internal/abi/abiutils.go new file mode 100644 index 0000000000..3ac59e6f75 --- /dev/null +++ b/src/cmd/compile/internal/abi/abiutils.go @@ -0,0 +1,385 @@ +// Copyright 2020 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package abi + +import ( + "cmd/compile/internal/types" + "cmd/internal/src" + "fmt" + "sync" +) + +//...................................................................... +// +// Public/exported bits of the ABI utilities. +// + +// ABIParamResultInfo stores the results of processing a given +// function type to compute stack layout and register assignments. For +// each input and output parameter we capture whether the param was +// register-assigned (and to which register(s)) or the stack offset +// for the param if is not going to be passed in registers according +// to the rules in the Go internal ABI specification (1.17). +type ABIParamResultInfo struct { + inparams []ABIParamAssignment // Includes receiver for method calls. Does NOT include hidden closure pointer. + outparams []ABIParamAssignment + intSpillSlots int + floatSpillSlots int + offsetToSpillArea int64 + config *ABIConfig // to enable String() method +} + +func (a *ABIParamResultInfo) InParams() []ABIParamAssignment { + return a.inparams +} + +func (a *ABIParamResultInfo) OutParams() []ABIParamAssignment { + return a.outparams +} + +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) IntSpillCount() int { + return a.intSpillSlots +} + +func (a *ABIParamResultInfo) FloatSpillCount() int { + return a.floatSpillSlots +} + +func (a *ABIParamResultInfo) SpillAreaOffset() int64 { + return a.offsetToSpillArea +} + +// RegIndex stores the index into the set of machine registers used by +// the ABI on a specific architecture for parameter passing. RegIndex +// values 0 through N-1 (where N is the number of integer registers +// used for param passing according to the ABI rules) describe integer +// registers; values N through M (where M is the number of floating +// point registers used). Thus if the ABI says there are 5 integer +// registers and 7 floating point registers, then RegIndex value of 4 +// indicates the 5th integer register, and a RegIndex value of 11 +// indicates the 7th floating point register. +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. +type ABIParamAssignment struct { + Type *types.Type + Registers []RegIndex + Offset int32 +} + +// RegAmounts holds a specified number of integer/float registers. +type RegAmounts struct { + intRegs int + floatRegs int +} + +// ABIConfig captures the number of registers made available +// by the ABI rules for parameter passing and result returning. +type ABIConfig struct { + // Do we need anything more than this? + 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) *ABIConfig { + return &ABIConfig{RegAmounts{iRegsCount, fRegsCount}} +} + +// ABIAnalyze takes a function type 't' 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 ABIAnalyze(t *types.Type, config *ABIConfig) ABIParamResultInfo { + setup() + s := assignState{ + rTotal: config.regAmounts, + } + result := ABIParamResultInfo{config: config} + + // Receiver + ft := t.FuncType() + if t.NumRecvs() != 0 { + rfsl := ft.Receiver.FieldSlice() + result.inparams = append(result.inparams, + s.assignParamOrReturn(rfsl[0].Type)) + } + + // Inputs + ifsl := ft.Params.FieldSlice() + for _, f := range ifsl { + result.inparams = append(result.inparams, + s.assignParamOrReturn(f.Type)) + } + s.stackOffset = types.Rnd(s.stackOffset, int64(types.RegSize)) + + // Record number of spill slots needed. + result.intSpillSlots = s.rUsed.intRegs + result.floatSpillSlots = s.rUsed.floatRegs + + // Outputs + s.rUsed = RegAmounts{} + ofsl := ft.Results.FieldSlice() + for _, f := range ofsl { + result.outparams = append(result.outparams, s.assignParamOrReturn(f.Type)) + } + result.offsetToSpillArea = s.stackOffset + + return result +} + +//...................................................................... +// +// Non-public portions. + +// regString produces a human-readable version of a RegIndex. +func (c *RegAmounts) regString(r RegIndex) string { + if int(r) < c.intRegs { + return fmt.Sprintf("I%d", int(r)) + } else if int(r) < c.intRegs+c.floatRegs { + return fmt.Sprintf("F%d", int(r)-c.intRegs) + } + return fmt.Sprintf("<?>%d", r) +} + +// toString method renders an ABIParamAssignment in human-readable +// form, suitable for debugging or unit testing. +func (ri *ABIParamAssignment) toString(config *ABIConfig) string { + regs := "R{" + for _, r := range ri.Registers { + regs += " " + config.regAmounts.regString(r) + } + return fmt.Sprintf("%s } offset: %d typ: %v", regs, ri.Offset, ri.Type) +} + +// toString method renders an ABIParamResultInfo in human-readable +// form, suitable for debugging or unit testing. +func (ri *ABIParamResultInfo) String() string { + res := "" + for k, p := range ri.inparams { + res += fmt.Sprintf("IN %d: %s\n", k, p.toString(ri.config)) + } + for k, r := range ri.outparams { + res += fmt.Sprintf("OUT %d: %s\n", k, r.toString(ri.config)) + } + res += fmt.Sprintf("intspill: %d floatspill: %d offsetToSpillArea: %d", + ri.intSpillSlots, ri.floatSpillSlots, ri.offsetToSpillArea) + return res +} + +// assignState holds intermediate state during the register assigning process +// for a given function signature. +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 +} + +// stackSlot returns a stack offset for a param or result of the +// specified type. +func (state *assignState) stackSlot(t *types.Type) int64 { + if t.Align > 0 { + state.stackOffset = types.Rnd(state.stackOffset, int64(t.Align)) + } + rv := state.stackOffset + state.stackOffset += t.Width + return rv +} + +// allocateRegs returns a set of register indices for a parameter or result +// that we've just determined to be register-assignable. The number of registers +// needed is assumed to be stored in state.pUsed. +func (state *assignState) allocateRegs() []RegIndex { + regs := []RegIndex{} + + // integer + for r := state.rUsed.intRegs; r < state.rUsed.intRegs+state.pUsed.intRegs; r++ { + regs = append(regs, RegIndex(r)) + } + state.rUsed.intRegs += state.pUsed.intRegs + + // floating + for r := state.rUsed.floatRegs; r < state.rUsed.floatRegs+state.pUsed.floatRegs; r++ { + regs = append(regs, RegIndex(r+state.rTotal.intRegs)) + } + state.rUsed.floatRegs += state.pUsed.floatRegs + + return regs +} + +// 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) ABIParamAssignment { + return ABIParamAssignment{ + Type: t, + Registers: state.allocateRegs(), + Offset: -1, + } +} + +// 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 { + return ABIParamAssignment{ + Type: t, + 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.Rnd(t.Width, int64(types.PtrSize)) / int64(types.PtrSize)) + + // 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.FieldSlice() { + if !state.regassign(field.Type) { + return false + } + } + return true +} + +// synthOnce ensures that we only create the synth* fake types once. +var synthOnce sync.Once + +// synthSlice, synthString, and syncIface are synthesized struct types +// meant to capture the underlying implementations of string/slice/interface. +var synthSlice *types.Type +var synthString *types.Type +var synthIface *types.Type + +// setup performs setup for the register assignment utilities, manufacturing +// a small set of synthesized types that we'll need along the way. +func setup() { + synthOnce.Do(func() { + fname := types.BuiltinPkg.Lookup + nxp := src.NoXPos + unsp := types.Types[types.TUNSAFEPTR] + ui := types.Types[types.TUINTPTR] + synthSlice = types.NewStruct(types.NoPkg, []*types.Field{ + types.NewField(nxp, fname("ptr"), unsp), + types.NewField(nxp, fname("len"), ui), + types.NewField(nxp, fname("cap"), ui), + }) + synthString = types.NewStruct(types.NoPkg, []*types.Field{ + types.NewField(nxp, fname("data"), unsp), + types.NewField(nxp, fname("len"), ui), + }) + synthIface = types.NewStruct(types.NoPkg, []*types.Field{ + types.NewField(nxp, fname("f1"), unsp), + types.NewField(nxp, fname("f2"), unsp), + }) + }) +} + +// 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: + panic("not expected") + } +} + +// assignParamOrReturn processes a given receiver, param, or result +// of type 'pt' 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) ABIParamAssignment { + state.pUsed = RegAmounts{} + if pt.Width == types.BADWIDTH { + panic("should never happen") + } else if pt.Width == 0 { + return state.stackAllocate(pt) + } else if state.regassign(pt) { + return state.regAllocate(pt) + } else { + return state.stackAllocate(pt) + } +} |