]> Cypherpunks repositories - gostls13.git/commitdiff
[dev.regabi] cmd/compile: add register ABI analysis utilities
authorThan McIntosh <thanm@google.com>
Tue, 24 Nov 2020 23:10:11 +0000 (18:10 -0500)
committerThan McIntosh <thanm@google.com>
Mon, 14 Dec 2020 17:34:36 +0000 (17:34 +0000)
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>

src/cmd/compile/fmtmap_test.go
src/cmd/compile/internal/gc/abiutils.go [new file with mode: 0644]
src/cmd/compile/internal/gc/abiutils_test.go [new file with mode: 0644]
src/cmd/compile/internal/gc/abiutilsaux_test.go [new file with mode: 0644]

index e62b9613e18400fa2ac0f531e7df73eb06053687..9bc059c2e44f9bd72eaf20ac2a6b09ff84a82299 100644 (file)
@@ -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":             "",
diff --git a/src/cmd/compile/internal/gc/abiutils.go b/src/cmd/compile/internal/gc/abiutils.go
new file mode 100644 (file)
index 0000000..19de14d
--- /dev/null
@@ -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)
+       }
+}
diff --git a/src/cmd/compile/internal/gc/abiutils_test.go b/src/cmd/compile/internal/gc/abiutils_test.go
new file mode 100644 (file)
index 0000000..16bd787
--- /dev/null
@@ -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)
+}
diff --git a/src/cmd/compile/internal/gc/abiutilsaux_test.go b/src/cmd/compile/internal/gc/abiutilsaux_test.go
new file mode 100644 (file)
index 0000000..d90d1d4
--- /dev/null
@@ -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)
+       }
+}