// 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 }