"*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": "",
--- /dev/null
+// 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)
+ }
+}
--- /dev/null
+// 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)
+}
--- /dev/null
+// 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)
+ }
+}