[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:
Than McIntosh 2020-11-24 18:10:11 -05:00
Родитель 8ce37e4110
Коммит 89f38323fa
4 изменённых файлов: 779 добавлений и 0 удалений

Просмотреть файл

@ -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)
}
}