зеркало из https://github.com/CryptoPro/go.git
[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:
Родитель
8ce37e4110
Коммит
89f38323fa
|
@ -36,6 +36,7 @@ var knownFormats = map[string]string{
|
|||
"*math/big.Int %s": "",
|
||||
"[]cmd/compile/internal/syntax.token %s": "",
|
||||
"cmd/compile/internal/arm.shift %d": "",
|
||||
"cmd/compile/internal/gc.RegIndex %d": "",
|
||||
"cmd/compile/internal/gc.initKind %d": "",
|
||||
"cmd/compile/internal/ir.Class %d": "",
|
||||
"cmd/compile/internal/ir.Node %+v": "",
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче