mirror of
https://github.com/golang/go.git
synced 2025-05-05 23:53:05 +00:00
[dev.regabi] cmd/compile: add register ABI analysis utilities
Introduce a new utility routine for analyzing a given function signature to how its various input and output parameters will be passed (in registers or on the stack) for a given ABI description, along with some unit tests. Change-Id: Id64a98a0a142e42dd9c2dc9f6607c0d827ef84fb Reviewed-on: https://go-review.googlesource.com/c/go/+/273011 Run-TryBot: Than McIntosh <thanm@google.com> Reviewed-by: Jeremy Faller <jeremy@golang.org> Trust: Than McIntosh <thanm@google.com>
This commit is contained in:
parent
8ce37e4110
commit
89f38323fa
@ -36,6 +36,7 @@ var knownFormats = map[string]string{
|
|||||||
"*math/big.Int %s": "",
|
"*math/big.Int %s": "",
|
||||||
"[]cmd/compile/internal/syntax.token %s": "",
|
"[]cmd/compile/internal/syntax.token %s": "",
|
||||||
"cmd/compile/internal/arm.shift %d": "",
|
"cmd/compile/internal/arm.shift %d": "",
|
||||||
|
"cmd/compile/internal/gc.RegIndex %d": "",
|
||||||
"cmd/compile/internal/gc.initKind %d": "",
|
"cmd/compile/internal/gc.initKind %d": "",
|
||||||
"cmd/compile/internal/ir.Class %d": "",
|
"cmd/compile/internal/ir.Class %d": "",
|
||||||
"cmd/compile/internal/ir.Node %+v": "",
|
"cmd/compile/internal/ir.Node %+v": "",
|
||||||
|
351
src/cmd/compile/internal/gc/abiutils.go
Normal file
351
src/cmd/compile/internal/gc/abiutils.go
Normal file
@ -0,0 +1,351 @@
|
|||||||
|
// 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 gc
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 = Rnd(s.stackOffset, int64(Widthreg))
|
||||||
|
|
||||||
|
// 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 = 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(Rnd(t.Width, int64(Widthptr)) / int64(Widthptr))
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
}
|
270
src/cmd/compile/internal/gc/abiutils_test.go
Normal file
270
src/cmd/compile/internal/gc/abiutils_test.go
Normal file
@ -0,0 +1,270 @@
|
|||||||
|
// 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 gc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"cmd/compile/internal/base"
|
||||||
|
"cmd/compile/internal/types"
|
||||||
|
"cmd/internal/obj"
|
||||||
|
"cmd/internal/obj/x86"
|
||||||
|
"cmd/internal/src"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AMD64 registers available:
|
||||||
|
// - integer: RAX, RBX, RCX, RDI, RSI, R8, R9, r10, R11
|
||||||
|
// - floating point: X0 - X14
|
||||||
|
var configAMD64 = ABIConfig{
|
||||||
|
regAmounts: RegAmounts{
|
||||||
|
intRegs: 9,
|
||||||
|
floatRegs: 15,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMain(m *testing.M) {
|
||||||
|
thearch.LinkArch = &x86.Linkamd64
|
||||||
|
thearch.REGSP = x86.REGSP
|
||||||
|
thearch.MAXWIDTH = 1 << 50
|
||||||
|
base.Ctxt = obj.Linknew(thearch.LinkArch)
|
||||||
|
base.Ctxt.DiagFunc = base.Errorf
|
||||||
|
base.Ctxt.DiagFlush = base.FlushErrors
|
||||||
|
base.Ctxt.Bso = bufio.NewWriter(os.Stdout)
|
||||||
|
Widthptr = thearch.LinkArch.PtrSize
|
||||||
|
Widthreg = thearch.LinkArch.RegSize
|
||||||
|
initializeTypesPackage()
|
||||||
|
os.Exit(m.Run())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestABIUtilsBasic1(t *testing.T) {
|
||||||
|
|
||||||
|
// func(x int32) int32
|
||||||
|
i32 := types.Types[types.TINT32]
|
||||||
|
ft := mkFuncType(nil, []*types.Type{i32}, []*types.Type{i32})
|
||||||
|
|
||||||
|
// expected results
|
||||||
|
exp := makeExpectedDump(`
|
||||||
|
IN 0: R{ I0 } offset: -1 typ: int32
|
||||||
|
OUT 0: R{ I0 } offset: -1 typ: int32
|
||||||
|
intspill: 1 floatspill: 0 offsetToSpillArea: 0
|
||||||
|
`)
|
||||||
|
|
||||||
|
abitest(t, ft, exp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestABIUtilsBasic2(t *testing.T) {
|
||||||
|
// func(x int32, y float64) (int32, float64, float64)
|
||||||
|
i8 := types.Types[types.TINT8]
|
||||||
|
i16 := types.Types[types.TINT16]
|
||||||
|
i32 := types.Types[types.TINT32]
|
||||||
|
i64 := types.Types[types.TINT64]
|
||||||
|
f32 := types.Types[types.TFLOAT32]
|
||||||
|
f64 := types.Types[types.TFLOAT64]
|
||||||
|
c64 := types.Types[types.TCOMPLEX64]
|
||||||
|
c128 := types.Types[types.TCOMPLEX128]
|
||||||
|
ft := mkFuncType(nil,
|
||||||
|
[]*types.Type{
|
||||||
|
i8, i16, i32, i64,
|
||||||
|
f32, f32, f64, f64,
|
||||||
|
i8, i16, i32, i64,
|
||||||
|
f32, f32, f64, f64,
|
||||||
|
c128, c128, c128, c128, c64,
|
||||||
|
i8, i16, i32, i64,
|
||||||
|
i8, i16, i32, i64},
|
||||||
|
[]*types.Type{i32, f64, f64})
|
||||||
|
exp := makeExpectedDump(`
|
||||||
|
IN 0: R{ I0 } offset: -1 typ: int8
|
||||||
|
IN 1: R{ I1 } offset: -1 typ: int16
|
||||||
|
IN 2: R{ I2 } offset: -1 typ: int32
|
||||||
|
IN 3: R{ I3 } offset: -1 typ: int64
|
||||||
|
IN 4: R{ F0 } offset: -1 typ: float32
|
||||||
|
IN 5: R{ F1 } offset: -1 typ: float32
|
||||||
|
IN 6: R{ F2 } offset: -1 typ: float64
|
||||||
|
IN 7: R{ F3 } offset: -1 typ: float64
|
||||||
|
IN 8: R{ I4 } offset: -1 typ: int8
|
||||||
|
IN 9: R{ I5 } offset: -1 typ: int16
|
||||||
|
IN 10: R{ I6 } offset: -1 typ: int32
|
||||||
|
IN 11: R{ I7 } offset: -1 typ: int64
|
||||||
|
IN 12: R{ F4 } offset: -1 typ: float32
|
||||||
|
IN 13: R{ F5 } offset: -1 typ: float32
|
||||||
|
IN 14: R{ F6 } offset: -1 typ: float64
|
||||||
|
IN 15: R{ F7 } offset: -1 typ: float64
|
||||||
|
IN 16: R{ F8 F9 } offset: -1 typ: complex128
|
||||||
|
IN 17: R{ F10 F11 } offset: -1 typ: complex128
|
||||||
|
IN 18: R{ F12 F13 } offset: -1 typ: complex128
|
||||||
|
IN 19: R{ } offset: 0 typ: complex128
|
||||||
|
IN 20: R{ F14 } offset: -1 typ: complex64
|
||||||
|
IN 21: R{ I8 } offset: -1 typ: int8
|
||||||
|
IN 22: R{ } offset: 16 typ: int16
|
||||||
|
IN 23: R{ } offset: 20 typ: int32
|
||||||
|
IN 24: R{ } offset: 24 typ: int64
|
||||||
|
IN 25: R{ } offset: 32 typ: int8
|
||||||
|
IN 26: R{ } offset: 34 typ: int16
|
||||||
|
IN 27: R{ } offset: 36 typ: int32
|
||||||
|
IN 28: R{ } offset: 40 typ: int64
|
||||||
|
OUT 0: R{ I0 } offset: -1 typ: int32
|
||||||
|
OUT 1: R{ F0 } offset: -1 typ: float64
|
||||||
|
OUT 2: R{ F1 } offset: -1 typ: float64
|
||||||
|
intspill: 9 floatspill: 15 offsetToSpillArea: 48
|
||||||
|
`)
|
||||||
|
|
||||||
|
abitest(t, ft, exp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestABIUtilsArrays(t *testing.T) {
|
||||||
|
i32 := types.Types[types.TINT32]
|
||||||
|
ae := types.NewArray(i32, 0)
|
||||||
|
a1 := types.NewArray(i32, 1)
|
||||||
|
a2 := types.NewArray(i32, 2)
|
||||||
|
aa1 := types.NewArray(a1, 1)
|
||||||
|
ft := mkFuncType(nil, []*types.Type{a1, ae, aa1, a2},
|
||||||
|
[]*types.Type{a2, a1, ae, aa1})
|
||||||
|
|
||||||
|
exp := makeExpectedDump(`
|
||||||
|
IN 0: R{ I0 } offset: -1 typ: [1]int32
|
||||||
|
IN 1: R{ } offset: 0 typ: [0]int32
|
||||||
|
IN 2: R{ I1 } offset: -1 typ: [1][1]int32
|
||||||
|
IN 3: R{ } offset: 0 typ: [2]int32
|
||||||
|
OUT 0: R{ } offset: 8 typ: [2]int32
|
||||||
|
OUT 1: R{ I0 } offset: -1 typ: [1]int32
|
||||||
|
OUT 2: R{ } offset: 16 typ: [0]int32
|
||||||
|
OUT 3: R{ I1 } offset: -1 typ: [1][1]int32
|
||||||
|
intspill: 2 floatspill: 0 offsetToSpillArea: 16
|
||||||
|
`)
|
||||||
|
|
||||||
|
abitest(t, ft, exp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestABIUtilsStruct1(t *testing.T) {
|
||||||
|
i8 := types.Types[types.TINT8]
|
||||||
|
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})
|
||||||
|
ft := mkFuncType(nil, []*types.Type{i8, s, i64},
|
||||||
|
[]*types.Type{s, i8, i32})
|
||||||
|
|
||||||
|
exp := makeExpectedDump(`
|
||||||
|
IN 0: R{ I0 } offset: -1 typ: int8
|
||||||
|
IN 1: R{ I1 I2 I3 I4 } offset: -1 typ: struct { int8; int8; struct {}; int8; int16 }
|
||||||
|
IN 2: R{ I5 } offset: -1 typ: int64
|
||||||
|
OUT 0: R{ I0 I1 I2 I3 } offset: -1 typ: struct { int8; int8; struct {}; int8; int16 }
|
||||||
|
OUT 1: R{ I4 } offset: -1 typ: int8
|
||||||
|
OUT 2: R{ I5 } offset: -1 typ: int32
|
||||||
|
intspill: 6 floatspill: 0 offsetToSpillArea: 0
|
||||||
|
`)
|
||||||
|
|
||||||
|
abitest(t, ft, exp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestABIUtilsStruct2(t *testing.T) {
|
||||||
|
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{})})
|
||||||
|
ft := mkFuncType(nil, []*types.Type{s, s, fs},
|
||||||
|
[]*types.Type{fs, fs})
|
||||||
|
|
||||||
|
exp := makeExpectedDump(`
|
||||||
|
IN 0: R{ I0 } offset: -1 typ: struct { int64; struct {} }
|
||||||
|
IN 1: R{ I1 } offset: -1 typ: struct { int64; struct {} }
|
||||||
|
IN 2: R{ I2 F0 } offset: -1 typ: struct { float64; struct { int64; struct {} }; struct {} }
|
||||||
|
OUT 0: R{ I0 F0 } offset: -1 typ: struct { float64; struct { int64; struct {} }; struct {} }
|
||||||
|
OUT 1: R{ I1 F1 } offset: -1 typ: struct { float64; struct { int64; struct {} }; struct {} }
|
||||||
|
intspill: 3 floatspill: 1 offsetToSpillArea: 0
|
||||||
|
`)
|
||||||
|
|
||||||
|
abitest(t, ft, exp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestABIUtilsSliceString(t *testing.T) {
|
||||||
|
i32 := types.Types[types.TINT32]
|
||||||
|
sli32 := types.NewSlice(i32)
|
||||||
|
str := types.New(types.TSTRING)
|
||||||
|
i8 := types.Types[types.TINT8]
|
||||||
|
i64 := types.Types[types.TINT64]
|
||||||
|
ft := mkFuncType(nil, []*types.Type{sli32, i8, sli32, i8, str, i8, i64, sli32},
|
||||||
|
[]*types.Type{str, i64, str, sli32})
|
||||||
|
|
||||||
|
exp := makeExpectedDump(`
|
||||||
|
IN 0: R{ I0 I1 I2 } offset: -1 typ: []int32
|
||||||
|
IN 1: R{ I3 } offset: -1 typ: int8
|
||||||
|
IN 2: R{ I4 I5 I6 } offset: -1 typ: []int32
|
||||||
|
IN 3: R{ I7 } offset: -1 typ: int8
|
||||||
|
IN 4: R{ } offset: 0 typ: string
|
||||||
|
IN 5: R{ I8 } offset: -1 typ: int8
|
||||||
|
IN 6: R{ } offset: 16 typ: int64
|
||||||
|
IN 7: R{ } offset: 24 typ: []int32
|
||||||
|
OUT 0: R{ I0 I1 } offset: -1 typ: string
|
||||||
|
OUT 1: R{ I2 } offset: -1 typ: int64
|
||||||
|
OUT 2: R{ I3 I4 } offset: -1 typ: string
|
||||||
|
OUT 3: R{ I5 I6 I7 } offset: -1 typ: []int32
|
||||||
|
intspill: 9 floatspill: 0 offsetToSpillArea: 48
|
||||||
|
`)
|
||||||
|
|
||||||
|
abitest(t, ft, exp)
|
||||||
|
}
|
||||||
|
|
||||||
|
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})
|
||||||
|
ps1 := types.NewPtr(s1)
|
||||||
|
a7 := types.NewArray(ps1, 7)
|
||||||
|
ft := mkFuncType(s1, []*types.Type{ps1, a7, f64, i16, i16, i16},
|
||||||
|
[]*types.Type{a7, f64, i64})
|
||||||
|
|
||||||
|
exp := makeExpectedDump(`
|
||||||
|
IN 0: R{ I0 I1 I2 } offset: -1 typ: struct { int16; int16; int16 }
|
||||||
|
IN 1: R{ I3 } offset: -1 typ: *struct { int16; int16; int16 }
|
||||||
|
IN 2: R{ } offset: 0 typ: [7]*struct { int16; int16; int16 }
|
||||||
|
IN 3: R{ F0 } offset: -1 typ: float64
|
||||||
|
IN 4: R{ I4 } offset: -1 typ: int16
|
||||||
|
IN 5: R{ I5 } offset: -1 typ: int16
|
||||||
|
IN 6: R{ I6 } offset: -1 typ: int16
|
||||||
|
OUT 0: R{ } offset: 56 typ: [7]*struct { int16; int16; int16 }
|
||||||
|
OUT 1: R{ F0 } offset: -1 typ: float64
|
||||||
|
OUT 2: R{ I0 } offset: -1 typ: int64
|
||||||
|
intspill: 7 floatspill: 1 offsetToSpillArea: 112
|
||||||
|
`)
|
||||||
|
|
||||||
|
abitest(t, ft, exp)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestABIUtilsInterfaces(t *testing.T) {
|
||||||
|
ei := types.Types[types.TINTER] // interface{}
|
||||||
|
pei := types.NewPtr(ei) // *interface{}
|
||||||
|
fldt := mkFuncType(types.FakeRecvType(), []*types.Type{},
|
||||||
|
[]*types.Type{types.UntypedString})
|
||||||
|
field := types.NewField(src.NoXPos, nil, fldt)
|
||||||
|
// interface{ ...() string }
|
||||||
|
nei := types.NewInterface(types.LocalPkg, []*types.Field{field})
|
||||||
|
|
||||||
|
i16 := types.Types[types.TINT16]
|
||||||
|
tb := types.Types[types.TBOOL]
|
||||||
|
s1 := mkstruct([]*types.Type{i16, i16, tb})
|
||||||
|
|
||||||
|
ft := mkFuncType(nil, []*types.Type{s1, ei, ei, nei, pei, nei, i16},
|
||||||
|
[]*types.Type{ei, nei, pei})
|
||||||
|
|
||||||
|
exp := makeExpectedDump(`
|
||||||
|
IN 0: R{ I0 I1 I2 } offset: -1 typ: struct { int16; int16; bool }
|
||||||
|
IN 1: R{ I3 I4 } offset: -1 typ: interface {}
|
||||||
|
IN 2: R{ I5 I6 } offset: -1 typ: interface {}
|
||||||
|
IN 3: R{ I7 I8 } offset: -1 typ: interface { () untyped string }
|
||||||
|
IN 4: R{ } offset: 0 typ: *interface {}
|
||||||
|
IN 5: R{ } offset: 8 typ: interface { () untyped string }
|
||||||
|
IN 6: R{ } offset: 24 typ: int16
|
||||||
|
OUT 0: R{ I0 I1 } offset: -1 typ: interface {}
|
||||||
|
OUT 1: R{ I2 I3 } offset: -1 typ: interface { () untyped string }
|
||||||
|
OUT 2: R{ I4 } offset: -1 typ: *interface {}
|
||||||
|
intspill: 9 floatspill: 0 offsetToSpillArea: 32
|
||||||
|
`)
|
||||||
|
|
||||||
|
abitest(t, ft, exp)
|
||||||
|
}
|
157
src/cmd/compile/internal/gc/abiutilsaux_test.go
Normal file
157
src/cmd/compile/internal/gc/abiutilsaux_test.go
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
// 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 gc
|
||||||
|
|
||||||
|
// This file contains utility routines and harness infrastructure used
|
||||||
|
// by the ABI tests in "abiutils_test.go".
|
||||||
|
|
||||||
|
import (
|
||||||
|
"cmd/compile/internal/ir"
|
||||||
|
"cmd/compile/internal/types"
|
||||||
|
"cmd/internal/src"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"text/scanner"
|
||||||
|
)
|
||||||
|
|
||||||
|
func mkParamResultField(t *types.Type, s *types.Sym, which ir.Class) *types.Field {
|
||||||
|
field := types.NewField(src.NoXPos, s, t)
|
||||||
|
n := NewName(s)
|
||||||
|
n.SetClass(which)
|
||||||
|
field.Nname = n
|
||||||
|
n.SetType(t)
|
||||||
|
return field
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
fields := make([]*types.Field, len(fieldtypes))
|
||||||
|
for k, t := range fieldtypes {
|
||||||
|
if t == nil {
|
||||||
|
panic("bad -- field has no type")
|
||||||
|
}
|
||||||
|
f := types.NewField(src.NoXPos, nil, t)
|
||||||
|
fields[k] = f
|
||||||
|
}
|
||||||
|
s := types.NewStruct(types.LocalPkg, fields)
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func mkFuncType(rcvr *types.Type, ins []*types.Type, outs []*types.Type) *types.Type {
|
||||||
|
q := lookup("?")
|
||||||
|
inf := []*types.Field{}
|
||||||
|
for _, it := range ins {
|
||||||
|
inf = append(inf, mkParamResultField(it, q, ir.PPARAM))
|
||||||
|
}
|
||||||
|
outf := []*types.Field{}
|
||||||
|
for _, ot := range outs {
|
||||||
|
outf = append(outf, mkParamResultField(ot, q, ir.PPARAMOUT))
|
||||||
|
}
|
||||||
|
var rf *types.Field
|
||||||
|
if rcvr != nil {
|
||||||
|
rf = mkParamResultField(rcvr, q, ir.PPARAM)
|
||||||
|
}
|
||||||
|
return types.NewSignature(types.LocalPkg, rf, inf, outf)
|
||||||
|
}
|
||||||
|
|
||||||
|
type expectedDump struct {
|
||||||
|
dump string
|
||||||
|
file string
|
||||||
|
line int
|
||||||
|
}
|
||||||
|
|
||||||
|
func tokenize(src string) []string {
|
||||||
|
var s scanner.Scanner
|
||||||
|
s.Init(strings.NewReader(src))
|
||||||
|
res := []string{}
|
||||||
|
for tok := s.Scan(); tok != scanner.EOF; tok = s.Scan() {
|
||||||
|
res = append(res, s.TokenText())
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func verifyParamResultOffset(t *testing.T, f *types.Field, r ABIParamAssignment, which string, idx int) int {
|
||||||
|
n := ir.AsNode(f.Nname)
|
||||||
|
if n == nil {
|
||||||
|
panic("not expected")
|
||||||
|
}
|
||||||
|
if n.Offset() != int64(r.Offset) {
|
||||||
|
t.Errorf("%s %d: got offset %d wanted %d t=%v",
|
||||||
|
which, idx, r.Offset, n.Offset(), f.Type)
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeExpectedDump(e string) expectedDump {
|
||||||
|
return expectedDump{dump: e}
|
||||||
|
}
|
||||||
|
|
||||||
|
func difftokens(atoks []string, etoks []string) string {
|
||||||
|
if len(atoks) != len(etoks) {
|
||||||
|
return fmt.Sprintf("expected %d tokens got %d",
|
||||||
|
len(etoks), len(atoks))
|
||||||
|
}
|
||||||
|
for i := 0; i < len(etoks); i++ {
|
||||||
|
if etoks[i] == atoks[i] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Sprintf("diff at token %d: expected %q got %q",
|
||||||
|
i, etoks[i], atoks[i])
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func abitest(t *testing.T, ft *types.Type, exp expectedDump) {
|
||||||
|
|
||||||
|
dowidth(ft)
|
||||||
|
|
||||||
|
// Analyze with full set of registers.
|
||||||
|
regRes := ABIAnalyze(ft, configAMD64)
|
||||||
|
regResString := strings.TrimSpace(regRes.String())
|
||||||
|
|
||||||
|
// Check results.
|
||||||
|
reason := difftokens(tokenize(regResString), tokenize(exp.dump))
|
||||||
|
if reason != "" {
|
||||||
|
t.Errorf("\nexpected:\n%s\ngot:\n%s\nreason: %s",
|
||||||
|
strings.TrimSpace(exp.dump), regResString, reason)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Analyze again with empty register set.
|
||||||
|
empty := ABIConfig{}
|
||||||
|
emptyRes := ABIAnalyze(ft, empty)
|
||||||
|
emptyResString := emptyRes.String()
|
||||||
|
|
||||||
|
// Walk the results and make sure the offsets assigned match
|
||||||
|
// up with those assiged by dowidth. This checks to make sure that
|
||||||
|
// when we have no available registers the ABI assignment degenerates
|
||||||
|
// back to the original ABI0.
|
||||||
|
|
||||||
|
// receiver
|
||||||
|
failed := 0
|
||||||
|
rfsl := ft.Recvs().Fields().Slice()
|
||||||
|
poff := 0
|
||||||
|
if len(rfsl) != 0 {
|
||||||
|
failed |= verifyParamResultOffset(t, rfsl[0], emptyRes.inparams[0], "receiver", 0)
|
||||||
|
poff = 1
|
||||||
|
}
|
||||||
|
// params
|
||||||
|
pfsl := ft.Params().Fields().Slice()
|
||||||
|
for k, f := range pfsl {
|
||||||
|
verifyParamResultOffset(t, f, emptyRes.inparams[k+poff], "param", k)
|
||||||
|
}
|
||||||
|
// results
|
||||||
|
ofsl := ft.Results().Fields().Slice()
|
||||||
|
for k, f := range ofsl {
|
||||||
|
failed |= verifyParamResultOffset(t, f, emptyRes.outparams[k], "result", k)
|
||||||
|
}
|
||||||
|
|
||||||
|
if failed != 0 {
|
||||||
|
t.Logf("emptyres:\n%s\n", emptyResString)
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user