589 lines
20 KiB
Go
589 lines
20 KiB
Go
// 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 assembler
|
|
|
|
import (
|
|
"fmt"
|
|
"git.gammaspectra.live/WeebDataHoarder/compute-go/assembler/asm/arch"
|
|
"git.gammaspectra.live/WeebDataHoarder/compute-go/assembler/obj"
|
|
"math"
|
|
"reflect"
|
|
)
|
|
|
|
//......................................................................
|
|
//
|
|
// 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
|
|
offsetToSpillArea int64
|
|
spillAreaSize int64
|
|
inRegistersUsed int
|
|
outRegistersUsed int
|
|
config *ABIConfig // to enable String() method
|
|
}
|
|
|
|
func (a *ABIParamResultInfo) Config() *ABIConfig {
|
|
return a.config
|
|
}
|
|
|
|
func (a *ABIParamResultInfo) InParams() []ABIParamAssignment {
|
|
return a.inparams
|
|
}
|
|
|
|
func (a *ABIParamResultInfo) OutParams() []ABIParamAssignment {
|
|
return a.outparams
|
|
}
|
|
|
|
func (a *ABIParamResultInfo) InRegistersUsed() int {
|
|
return a.inRegistersUsed
|
|
}
|
|
|
|
func (a *ABIParamResultInfo) OutRegistersUsed() int {
|
|
return a.outRegistersUsed
|
|
}
|
|
|
|
func (a *ABIParamResultInfo) InParam(i int) *ABIParamAssignment {
|
|
return &a.inparams[i]
|
|
}
|
|
|
|
func (a *ABIParamResultInfo) OutParam(i int) *ABIParamAssignment {
|
|
return &a.outparams[i]
|
|
}
|
|
|
|
func (a *ABIParamResultInfo) SpillAreaOffset() int64 {
|
|
return a.offsetToSpillArea
|
|
}
|
|
|
|
func (a *ABIParamResultInfo) SpillAreaSize() int64 {
|
|
return a.spillAreaSize
|
|
}
|
|
|
|
// ArgWidth returns the amount of stack needed for all the inputs
|
|
// and outputs of a function or method, including ABI-defined parameter
|
|
// slots and ABI-defined spill slots for register-resident parameters.
|
|
// The name is inherited from (*Type).ArgWidth(), which it replaces.
|
|
func (a *ABIParamResultInfo) ArgWidth() int64 {
|
|
return a.spillAreaSize + a.offsetToSpillArea - a.config.LocalsOffset()
|
|
}
|
|
|
|
// 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 *ABIType
|
|
Name string
|
|
Registers []RegIndex
|
|
offset int32
|
|
}
|
|
|
|
// Offset returns the stack offset for addressing the parameter that "a" describes.
|
|
// This will panic if "a" describes a register-allocated parameter.
|
|
func (a *ABIParamAssignment) Offset() int32 {
|
|
if len(a.Registers) > 0 {
|
|
panic("register allocated parameters have no offset")
|
|
}
|
|
return a.offset
|
|
}
|
|
|
|
// RegisterTypes returns a slice of the types of the registers
|
|
// corresponding to a slice of parameters. The returned slice
|
|
// has capacity for one more, likely a memory type.
|
|
func RegisterTypes(apa []ABIParamAssignment) []*ABIType {
|
|
rcount := 0
|
|
for _, pa := range apa {
|
|
rcount += len(pa.Registers)
|
|
}
|
|
if rcount == 0 {
|
|
// Note that this catches top-level struct{} and [0]Foo, which are stack allocated.
|
|
return make([]*ABIType, 0, 1)
|
|
}
|
|
rts := make([]*ABIType, 0, rcount+1)
|
|
for _, pa := range apa {
|
|
if len(pa.Registers) == 0 {
|
|
continue
|
|
}
|
|
rts = appendParamTypes(rts, pa.Type)
|
|
}
|
|
return rts
|
|
}
|
|
|
|
func (pa *ABIParamAssignment) RegisterTypesAndOffsets() ([]*ABIType, []int64) {
|
|
l := len(pa.Registers)
|
|
if l == 0 {
|
|
return nil, nil
|
|
}
|
|
typs := make([]*ABIType, 0, l)
|
|
offs := make([]int64, 0, l)
|
|
offs, _ = appendParamOffsets(offs, 0, pa.Type)
|
|
return appendParamTypes(typs, pa.Type), offs
|
|
}
|
|
|
|
func appendParamTypes(rts []*ABIType, t *ABIType) []*ABIType {
|
|
w := t.Size()
|
|
if w == 0 {
|
|
return rts
|
|
}
|
|
if t.IsScalar() || t.IsPtrShaped() {
|
|
if t.Kind() == reflect.Complex64 {
|
|
return append(rts, NewType[float32](t.name, t.arch), NewType[float32](t.name, t.arch))
|
|
} else if t.Kind() == reflect.Complex128 {
|
|
return append(rts, NewType[float64](t.name, t.arch), NewType[float64](t.name, t.arch))
|
|
} else {
|
|
if int(t.Size()) <= t.arch.RegSize {
|
|
return append(rts, t)
|
|
}
|
|
// assume 64bit int on 32-bit machine
|
|
// TODO endianness? Should high-order (sign bits) word come first?
|
|
if t.IsSigned() {
|
|
rts = append(rts, NewType[int32](t.name, t.arch))
|
|
} else {
|
|
rts = append(rts, NewType[uint32](t.name, t.arch))
|
|
}
|
|
return append(rts, NewType[uint32](t.name, t.arch))
|
|
}
|
|
} else {
|
|
typ := t.Kind()
|
|
switch typ {
|
|
case reflect.Array:
|
|
for i := 0; i < t.reflectType.Len(); i++ { // 0 gets no registers, plus future-proofing.
|
|
rts = appendParamTypes(rts, t.ElemABIType())
|
|
}
|
|
case reflect.Struct:
|
|
panic("not implemented")
|
|
case reflect.Slice:
|
|
panic("not implemented")
|
|
case reflect.String:
|
|
panic("not implemented")
|
|
}
|
|
}
|
|
return rts
|
|
}
|
|
|
|
// appendParamOffsets appends the offset(s) of type t, starting from "at",
|
|
// to input offsets, and returns the longer slice and the next unused offset.
|
|
func appendParamOffsets(offsets []int64, at int64, t *ABIType) ([]int64, int64) {
|
|
at = align(at, t)
|
|
w := t.Size()
|
|
if w == 0 {
|
|
return offsets, at
|
|
}
|
|
if t.IsScalar() || t.IsPtrShaped() {
|
|
if (t.Kind() == reflect.Complex64 || t.Kind() == reflect.Complex128) || int(t.Size()) > t.arch.RegSize { // complex and *int64 on 32-bit
|
|
s := w / 2
|
|
return append(offsets, at, at+s), at + w
|
|
} else {
|
|
return append(offsets, at), at + w
|
|
}
|
|
} else {
|
|
typ := t.Kind()
|
|
switch typ {
|
|
case reflect.Array:
|
|
for i := 0; i < t.reflectType.Len(); i++ {
|
|
offsets, at = appendParamOffsets(offsets, at, t.ElemABIType())
|
|
}
|
|
case reflect.Struct:
|
|
panic("not implemented")
|
|
case reflect.Slice:
|
|
panic("not implemented")
|
|
case reflect.String:
|
|
}
|
|
}
|
|
return offsets, at
|
|
}
|
|
|
|
// FrameOffset returns the frame-pointer-relative location that a function
|
|
// would spill its input or output parameter to, if such a spill slot exists.
|
|
// If there is none defined (e.g., register-allocated outputs) it panics.
|
|
// For register-allocated inputs that is their spill offset reserved for morestack;
|
|
// for stack-allocated inputs and outputs, that is their location on the stack.
|
|
// (In a future version of the ABI, register-resident inputs may lose their defined
|
|
// spill area to help reduce stack sizes.)
|
|
func (a *ABIParamAssignment) FrameOffset(i *ABIParamResultInfo) int64 {
|
|
if a.offset == -1 {
|
|
panic("function parameter has no ABI-defined frame-pointer offset")
|
|
}
|
|
if len(a.Registers) == 0 { // passed on stack
|
|
return int64(a.offset) - i.config.LocalsOffset()
|
|
}
|
|
// spill area for registers
|
|
return int64(a.offset) + i.SpillAreaOffset() - i.config.LocalsOffset()
|
|
}
|
|
|
|
// 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?
|
|
offsetForLocals int64 // e.g., obj.(*Link).Arch.FixedFrameSize -- extra linkage information on some architectures.
|
|
regAmounts RegAmounts
|
|
which obj.ABI
|
|
arch *arch.Arch
|
|
}
|
|
|
|
// NewABIConfig returns a new ABI configuration for an architecture with
|
|
// iRegsCount integer/pointer registers and fRegsCount floating point registers.
|
|
func NewABIConfig(iRegsCount, fRegsCount int, arch *arch.Arch, which obj.ABI) *ABIConfig {
|
|
return &ABIConfig{arch: arch, offsetForLocals: arch.FixedFrameSize, regAmounts: RegAmounts{iRegsCount, fRegsCount}, which: which}
|
|
}
|
|
|
|
// Copy returns config.
|
|
//
|
|
// TODO(mdempsky): Remove.
|
|
func (config *ABIConfig) Copy() *ABIConfig {
|
|
return config
|
|
}
|
|
|
|
// Which returns the ABI number
|
|
func (config *ABIConfig) Which() obj.ABI {
|
|
return config.which
|
|
}
|
|
|
|
// LocalsOffset returns the architecture-dependent offset from SP for args and results.
|
|
// In theory this is only used for debugging; it ought to already be incorporated into
|
|
// results from the ABI-related methods
|
|
func (config *ABIConfig) LocalsOffset() int64 {
|
|
return config.offsetForLocals
|
|
}
|
|
|
|
// FloatIndexFor translates r into an index in the floating point parameter
|
|
// registers. If the result is negative, the input index was actually for the
|
|
// integer parameter registers.
|
|
func (config *ABIConfig) FloatIndexFor(r RegIndex) int64 {
|
|
return int64(r) - int64(config.regAmounts.intRegs)
|
|
}
|
|
|
|
// NumParamRegs returns the total number of registers used to
|
|
// represent a parameter of the given type, which must be register
|
|
// assignable.
|
|
func (config *ABIConfig) NumParamRegs(typ *ABIType) int {
|
|
intRegs, floatRegs := typ.Registers()
|
|
if intRegs == math.MaxUint8 && floatRegs == math.MaxUint8 {
|
|
panic(fmt.Sprintf("cannot represent parameters of type %v in registers", typ))
|
|
}
|
|
return int(intRegs) + int(floatRegs)
|
|
}
|
|
|
|
// ABIAnalyzeTypes takes slices of parameter and result types, and returns an ABIParamResultInfo,
|
|
// based on the given configuration. This is the same result computed by config.ABIAnalyze applied to the
|
|
// corresponding method/function type, except that all the embedded parameter names are nil.
|
|
// This is intended for use by ssagen/ssa.go:(*state).rtcall, for runtime functions that lack a parsed function type.
|
|
func (config *ABIConfig) ABIAnalyzeTypes(params, results []*ABIType) *ABIParamResultInfo {
|
|
s := assignState{
|
|
stackOffset: config.offsetForLocals,
|
|
rTotal: config.regAmounts,
|
|
}
|
|
|
|
assignParams := func(params []*ABIType, isResult bool) []ABIParamAssignment {
|
|
res := make([]ABIParamAssignment, len(params))
|
|
for i, param := range params {
|
|
res[i] = s.assignParam(param, param.name, isResult)
|
|
}
|
|
return res
|
|
}
|
|
|
|
info := &ABIParamResultInfo{config: config}
|
|
|
|
// Inputs
|
|
info.inparams = assignParams(params, false)
|
|
s.stackOffset = roundUp(s.stackOffset, int64(config.arch.RegSize))
|
|
info.inRegistersUsed = s.rUsed.intRegs + s.rUsed.floatRegs
|
|
|
|
// Outputs
|
|
s.rUsed = RegAmounts{}
|
|
info.outparams = assignParams(results, true)
|
|
// The spill area is at a register-aligned offset and its size is rounded up to a register alignment.
|
|
// TODO in theory could align offset only to minimum required by spilled data types.
|
|
info.offsetToSpillArea = alignTo(s.stackOffset, config.arch.RegSize)
|
|
info.spillAreaSize = alignTo(s.spillOffset, config.arch.RegSize)
|
|
info.outRegistersUsed = s.rUsed.intRegs + s.rUsed.floatRegs
|
|
|
|
return info
|
|
}
|
|
|
|
// ABIAnalyzeFuncType takes a function type 'ft' 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 (config *ABIConfig) ABIAnalyzeFuncType(ft *ABIType) *ABIParamResultInfo {
|
|
s := assignState{
|
|
stackOffset: config.offsetForLocals,
|
|
rTotal: config.regAmounts,
|
|
}
|
|
|
|
assignParams := func(params []*ABIType, isResult bool) []ABIParamAssignment {
|
|
res := make([]ABIParamAssignment, len(params))
|
|
for i, param := range params {
|
|
res[i] = s.assignParam(param, "", isResult)
|
|
}
|
|
return res
|
|
}
|
|
|
|
info := &ABIParamResultInfo{config: config}
|
|
|
|
// Inputs
|
|
info.inparams = assignParams(ft.FuncParams(), false)
|
|
s.stackOffset = roundUp(s.stackOffset, int64(config.arch.RegSize))
|
|
info.inRegistersUsed = s.rUsed.intRegs + s.rUsed.floatRegs
|
|
|
|
// Outputs
|
|
s.rUsed = RegAmounts{}
|
|
info.outparams = assignParams(ft.FuncResults(), true)
|
|
// The spill area is at a register-aligned offset and its size is rounded up to a register alignment.
|
|
// TODO in theory could align offset only to minimum required by spilled data types.
|
|
info.offsetToSpillArea = alignTo(s.stackOffset, config.arch.RegSize)
|
|
info.spillAreaSize = alignTo(s.spillOffset, config.arch.RegSize)
|
|
info.outRegistersUsed = s.rUsed.intRegs + s.rUsed.floatRegs
|
|
return info
|
|
}
|
|
|
|
// ABIAnalyze returns the same result as ABIAnalyzeFuncType, but also
|
|
// updates the offsets of all the receiver, input, and output fields.
|
|
// If setNname is true, it also sets the FrameOffset of the Nname for
|
|
// the field(s); this is for use when compiling a function and figuring out
|
|
// spill locations. Doing this for callers can cause races for register
|
|
// outputs because their frame location transitions from BOGUS_FUNARG_OFFSET
|
|
// to zero to an as-if-AUTO offset that has no use for callers.
|
|
func (config *ABIConfig) ABIAnalyze(t *ABIType) *ABIParamResultInfo {
|
|
result := config.ABIAnalyzeFuncType(t)
|
|
|
|
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, extra bool) string {
|
|
regs := "R{"
|
|
offname := "spilloffset" // offset is for spill for register(s)
|
|
if len(ri.Registers) == 0 {
|
|
offname = "offset" // offset is for memory arg
|
|
}
|
|
for _, r := range ri.Registers {
|
|
regs += " " + config.regAmounts.regString(r)
|
|
if extra {
|
|
regs += fmt.Sprintf("(%d)", r)
|
|
}
|
|
}
|
|
if extra {
|
|
regs += fmt.Sprintf(" | #I=%d, #F=%d", config.regAmounts.intRegs, config.regAmounts.floatRegs)
|
|
}
|
|
return fmt.Sprintf("%s } %s: %d typ: %v", regs, offname, ri.offset, ri.Type)
|
|
}
|
|
|
|
// String 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, false))
|
|
}
|
|
for k, r := range ri.outparams {
|
|
res += fmt.Sprintf("OUT %d: %s\n", k, r.ToString(ri.config, false))
|
|
}
|
|
res += fmt.Sprintf("offsetToSpillArea: %d spillAreaSize: %d",
|
|
ri.offsetToSpillArea, ri.spillAreaSize)
|
|
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
|
|
stackOffset int64 // current stack offset
|
|
spillOffset int64 // current spill offset
|
|
}
|
|
|
|
// align returns a rounded up to t's alignment.
|
|
func align(a int64, t *ABIType) int64 {
|
|
return alignTo(a, int(uint8(t.Alignment())))
|
|
}
|
|
|
|
// alignTo returns a rounded up to t, where t must be 0 or a power of 2.
|
|
func alignTo(a int64, t int) int64 {
|
|
if t == 0 {
|
|
return a
|
|
}
|
|
return roundUp(a, int64(t))
|
|
}
|
|
|
|
// nextSlot allocates the next available slot for typ.
|
|
func nextSlot(offsetp *int64, typ *ABIType) int64 {
|
|
offset := align(*offsetp, typ)
|
|
*offsetp = offset + typ.Size()
|
|
return offset
|
|
}
|
|
|
|
// allocateRegs returns an ordered list 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(regs []RegIndex, t *ABIType) []RegIndex {
|
|
if t.Size() == 0 {
|
|
return regs
|
|
}
|
|
ri := state.rUsed.intRegs
|
|
rf := state.rUsed.floatRegs
|
|
if t.IsScalar() || t.IsPtrShaped() {
|
|
if t.Kind() == reflect.Complex64 || t.Kind() == reflect.Complex128 {
|
|
regs = append(regs, RegIndex(rf+state.rTotal.intRegs), RegIndex(rf+1+state.rTotal.intRegs))
|
|
rf += 2
|
|
} else if t.Kind() == reflect.Float32 || t.Kind() == reflect.Float64 {
|
|
regs = append(regs, RegIndex(rf+state.rTotal.intRegs))
|
|
rf += 1
|
|
} else {
|
|
n := (int(t.Size()) + t.arch.RegSize - 1) / t.arch.RegSize
|
|
for i := 0; i < n; i++ { // looking ahead to really big integers
|
|
regs = append(regs, RegIndex(ri))
|
|
ri += 1
|
|
}
|
|
}
|
|
state.rUsed.intRegs = ri
|
|
state.rUsed.floatRegs = rf
|
|
return regs
|
|
} else {
|
|
typ := t.Kind()
|
|
switch typ {
|
|
case reflect.Array:
|
|
for i := 0; i < t.reflectType.Len(); i++ {
|
|
regs = state.allocateRegs(regs, t.ElemABIType())
|
|
}
|
|
return regs
|
|
case reflect.Struct:
|
|
panic("not implemented")
|
|
case reflect.Slice:
|
|
panic("not implemented")
|
|
case reflect.String:
|
|
panic("not implemented")
|
|
}
|
|
}
|
|
panic(fmt.Sprintf("was not expecting type %v", t))
|
|
panic("unreachable")
|
|
}
|
|
|
|
// assignParam processes a given receiver, param, or result
|
|
// of field f 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) assignParam(typ *ABIType, name string, isResult bool) ABIParamAssignment {
|
|
registers := state.tryAllocRegs(typ)
|
|
|
|
var offset int64 = -1
|
|
if registers == nil { // stack allocated; needs stack slot
|
|
offset = nextSlot(&state.stackOffset, typ)
|
|
} else if !isResult { // register-allocated param; needs spill slot
|
|
offset = nextSlot(&state.spillOffset, typ)
|
|
}
|
|
|
|
return ABIParamAssignment{
|
|
Type: typ,
|
|
Name: name,
|
|
Registers: registers,
|
|
offset: int32(offset),
|
|
}
|
|
}
|
|
|
|
// tryAllocRegs attempts to allocate registers to represent a
|
|
// parameter of the given type. If unsuccessful, it returns nil.
|
|
func (state *assignState) tryAllocRegs(typ *ABIType) []RegIndex {
|
|
if typ.Size() == 0 {
|
|
return nil // zero-size parameters are defined as being stack allocated
|
|
}
|
|
|
|
intRegs, floatRegs := typ.Registers()
|
|
if int(intRegs) > state.rTotal.intRegs-state.rUsed.intRegs || int(floatRegs) > state.rTotal.floatRegs-state.rUsed.floatRegs {
|
|
return nil // too few available registers
|
|
}
|
|
|
|
regs := make([]RegIndex, 0, int(intRegs)+int(floatRegs))
|
|
return state.allocateRegs(regs, typ)
|
|
}
|
|
|
|
// ComputePadding returns a list of "post element" padding values in
|
|
// the case where we have a structure being passed in registers. Given
|
|
// a param assignment corresponding to a struct, it returns a list
|
|
// containing padding values for each field, e.g. the Kth element in
|
|
// the list is the amount of padding between field K and the following
|
|
// field. For things that are not structs (or structs without padding)
|
|
// it returns a list of zeros. Example:
|
|
//
|
|
// type small struct {
|
|
// x uint16
|
|
// y uint8
|
|
// z int32
|
|
// w int32
|
|
// }
|
|
//
|
|
// For this struct we would return a list [0, 1, 0, 0], meaning that
|
|
// we have one byte of padding after the second field, and no bytes of
|
|
// padding after any of the other fields. Input parameter "storage" is
|
|
// a slice with enough capacity to accommodate padding elements for
|
|
// the architected register set in question.
|
|
func (pa *ABIParamAssignment) ComputePadding(storage []uint64) []uint64 {
|
|
nr := len(pa.Registers)
|
|
padding := storage[:nr]
|
|
for i := 0; i < nr; i++ {
|
|
padding[i] = 0
|
|
}
|
|
if pa.Type.Kind() != reflect.Struct || nr == 0 {
|
|
return padding
|
|
}
|
|
types := make([]*ABIType, 0, nr)
|
|
types = appendParamTypes(types, pa.Type)
|
|
if len(types) != nr {
|
|
panic("internal error")
|
|
}
|
|
off := int64(0)
|
|
for idx, t := range types {
|
|
ts := t.Size()
|
|
off += int64(ts)
|
|
if idx < len(types)-1 {
|
|
noff := align(off, types[idx+1])
|
|
if noff != off {
|
|
padding[idx] = uint64(noff - off)
|
|
}
|
|
}
|
|
}
|
|
return padding
|
|
}
|