blob: e78eb5c0e4b6868010b104c4660a841082730f72 [file] [log] [blame]
// Copyright 2017 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 ssa
import (
"cmd/compile/internal/abi"
"cmd/compile/internal/ir"
"cmd/compile/internal/types"
"cmd/internal/dwarf"
"cmd/internal/obj"
"cmd/internal/src"
"encoding/hex"
"fmt"
"internal/buildcfg"
"math/bits"
"sort"
"strings"
)
type SlotID int32
type VarID int32
// A FuncDebug contains all the debug information for the variables in a
// function. Variables are identified by their LocalSlot, which may be the
// result of decomposing a larger variable.
type FuncDebug struct {
// Slots is all the slots used in the debug info, indexed by their SlotID.
Slots []LocalSlot
// The user variables, indexed by VarID.
Vars []*ir.Name
// The slots that make up each variable, indexed by VarID.
VarSlots [][]SlotID
// The location list data, indexed by VarID. Must be processed by PutLocationList.
LocationLists [][]byte
// Filled in by the user. Translates Block and Value ID to PC.
GetPC func(ID, ID) int64
}
type BlockDebug struct {
// Whether the block had any changes to user variables at all.
relevant bool
// State at the end of the block if it's fully processed. Immutable once initialized.
endState []liveSlot
}
// A liveSlot is a slot that's live in loc at entry/exit of a block.
type liveSlot struct {
// An inlined VarLoc, so it packs into 16 bytes instead of 20.
Registers RegisterSet
StackOffset
slot SlotID
}
func (loc liveSlot) absent() bool {
return loc.Registers == 0 && !loc.onStack()
}
// StackOffset encodes whether a value is on the stack and if so, where. It is
// a 31-bit integer followed by a presence flag at the low-order bit.
type StackOffset int32
func (s StackOffset) onStack() bool {
return s != 0
}
func (s StackOffset) stackOffsetValue() int32 {
return int32(s) >> 1
}
// stateAtPC is the current state of all variables at some point.
type stateAtPC struct {
// The location of each known slot, indexed by SlotID.
slots []VarLoc
// The slots present in each register, indexed by register number.
registers [][]SlotID
}
// reset fills state with the live variables from live.
func (state *stateAtPC) reset(live []liveSlot) {
slots, registers := state.slots, state.registers
for i := range slots {
slots[i] = VarLoc{}
}
for i := range registers {
registers[i] = registers[i][:0]
}
for _, live := range live {
slots[live.slot] = VarLoc{live.Registers, live.StackOffset}
if live.Registers == 0 {
continue
}
mask := uint64(live.Registers)
for {
if mask == 0 {
break
}
reg := uint8(bits.TrailingZeros64(mask))
mask &^= 1 << reg
registers[reg] = append(registers[reg], live.slot)
}
}
state.slots, state.registers = slots, registers
}
func (s *debugState) LocString(loc VarLoc) string {
if loc.absent() {
return "<nil>"
}
var storage []string
if loc.onStack() {
storage = append(storage, "stack")
}
mask := uint64(loc.Registers)
for {
if mask == 0 {
break
}
reg := uint8(bits.TrailingZeros64(mask))
mask &^= 1 << reg
storage = append(storage, s.registers[reg].String())
}
return strings.Join(storage, ",")
}
// A VarLoc describes the storage for part of a user variable.
type VarLoc struct {
// The registers this variable is available in. There can be more than
// one in various situations, e.g. it's being moved between registers.
Registers RegisterSet
StackOffset
}
func (loc VarLoc) absent() bool {
return loc.Registers == 0 && !loc.onStack()
}
var BlockStart = &Value{
ID: -10000,
Op: OpInvalid,
Aux: StringToAux("BlockStart"),
}
var BlockEnd = &Value{
ID: -20000,
Op: OpInvalid,
Aux: StringToAux("BlockEnd"),
}
var FuncEnd = &Value{
ID: -30000,
Op: OpInvalid,
Aux: StringToAux("FuncEnd"),
}
// RegisterSet is a bitmap of registers, indexed by Register.num.
type RegisterSet uint64
// logf prints debug-specific logging to stdout (always stdout) if the current
// function is tagged by GOSSAFUNC (for ssa output directed either to stdout or html).
func (s *debugState) logf(msg string, args ...interface{}) {
if s.f.PrintOrHtmlSSA {
fmt.Printf(msg, args...)
}
}
type debugState struct {
// See FuncDebug.
slots []LocalSlot
vars []*ir.Name
varSlots [][]SlotID
lists [][]byte
// The user variable that each slot rolls up to, indexed by SlotID.
slotVars []VarID
f *Func
loggingEnabled bool
registers []Register
stackOffset func(LocalSlot) int32
ctxt *obj.Link
// The names (slots) associated with each value, indexed by Value ID.
valueNames [][]SlotID
// The current state of whatever analysis is running.
currentState stateAtPC
liveCount []int
changedVars *sparseSet
// The pending location list entry for each user variable, indexed by VarID.
pendingEntries []pendingEntry
varParts map[*ir.Name][]SlotID
blockDebug []BlockDebug
pendingSlotLocs []VarLoc
liveSlots []liveSlot
liveSlotSliceBegin int
partsByVarOffset sort.Interface
}
func (state *debugState) initializeCache(f *Func, numVars, numSlots int) {
// One blockDebug per block. Initialized in allocBlock.
if cap(state.blockDebug) < f.NumBlocks() {
state.blockDebug = make([]BlockDebug, f.NumBlocks())
} else {
// This local variable, and the ones like it below, enable compiler
// optimizations. Don't inline them.
b := state.blockDebug[:f.NumBlocks()]
for i := range b {
b[i] = BlockDebug{}
}
}
// A list of slots per Value. Reuse the previous child slices.
if cap(state.valueNames) < f.NumValues() {
old := state.valueNames
state.valueNames = make([][]SlotID, f.NumValues())
copy(state.valueNames, old)
}
vn := state.valueNames[:f.NumValues()]
for i := range vn {
vn[i] = vn[i][:0]
}
// Slot and register contents for currentState. Cleared by reset().
if cap(state.currentState.slots) < numSlots {
state.currentState.slots = make([]VarLoc, numSlots)
} else {
state.currentState.slots = state.currentState.slots[:numSlots]
}
if cap(state.currentState.registers) < len(state.registers) {
state.currentState.registers = make([][]SlotID, len(state.registers))
} else {
state.currentState.registers = state.currentState.registers[:len(state.registers)]
}
// Used many times by mergePredecessors.
if cap(state.liveCount) < numSlots {
state.liveCount = make([]int, numSlots)
} else {
state.liveCount = state.liveCount[:numSlots]
}
// A relatively small slice, but used many times as the return from processValue.
state.changedVars = newSparseSet(numVars)
// A pending entry per user variable, with space to track each of its pieces.
numPieces := 0
for i := range state.varSlots {
numPieces += len(state.varSlots[i])
}
if cap(state.pendingSlotLocs) < numPieces {
state.pendingSlotLocs = make([]VarLoc, numPieces)
} else {
psl := state.pendingSlotLocs[:numPieces]
for i := range psl {
psl[i] = VarLoc{}
}
}
if cap(state.pendingEntries) < numVars {
state.pendingEntries = make([]pendingEntry, numVars)
}
pe := state.pendingEntries[:numVars]
freePieceIdx := 0
for varID, slots := range state.varSlots {
pe[varID] = pendingEntry{
pieces: state.pendingSlotLocs[freePieceIdx : freePieceIdx+len(slots)],
}
freePieceIdx += len(slots)
}
state.pendingEntries = pe
if cap(state.lists) < numVars {
state.lists = make([][]byte, numVars)
} else {
state.lists = state.lists[:numVars]
for i := range state.lists {
state.lists[i] = nil
}
}
state.liveSlots = state.liveSlots[:0]
state.liveSlotSliceBegin = 0
}
func (state *debugState) allocBlock(b *Block) *BlockDebug {
return &state.blockDebug[b.ID]
}
func (state *debugState) appendLiveSlot(ls liveSlot) {
state.liveSlots = append(state.liveSlots, ls)
}
func (state *debugState) getLiveSlotSlice() []liveSlot {
s := state.liveSlots[state.liveSlotSliceBegin:]
state.liveSlotSliceBegin = len(state.liveSlots)
return s
}
func (s *debugState) blockEndStateString(b *BlockDebug) string {
endState := stateAtPC{slots: make([]VarLoc, len(s.slots)), registers: make([][]SlotID, len(s.registers))}
endState.reset(b.endState)
return s.stateString(endState)
}
func (s *debugState) stateString(state stateAtPC) string {
var strs []string
for slotID, loc := range state.slots {
if !loc.absent() {
strs = append(strs, fmt.Sprintf("\t%v = %v\n", s.slots[slotID], s.LocString(loc)))
}
}
strs = append(strs, "\n")
for reg, slots := range state.registers {
if len(slots) != 0 {
var slotStrs []string
for _, slot := range slots {
slotStrs = append(slotStrs, s.slots[slot].String())
}
strs = append(strs, fmt.Sprintf("\t%v = %v\n", &s.registers[reg], slotStrs))
}
}
if len(strs) == 1 {
return "(no vars)\n"
}
return strings.Join(strs, "")
}
// slotCanonicalizer is a table used to lookup and canonicalize
// LocalSlot's in a type insensitive way (e.g. taking into account the
// base name, offset, and width of the slot, but ignoring the slot
// type).
type slotCanonicalizer struct {
slmap map[slotKey]SlKeyIdx
slkeys []LocalSlot
}
func newSlotCanonicalizer() *slotCanonicalizer {
return &slotCanonicalizer{
slmap: make(map[slotKey]SlKeyIdx),
slkeys: []LocalSlot{LocalSlot{N: nil}},
}
}
type SlKeyIdx uint32
const noSlot = SlKeyIdx(0)
// slotKey is a type-insensitive encapsulation of a LocalSlot; it
// is used to key a map within slotCanonicalizer.
type slotKey struct {
name *ir.Name
offset int64
width int64
splitOf SlKeyIdx // idx in slkeys slice in slotCanonicalizer
splitOffset int64
}
// lookup looks up a LocalSlot in the slot canonicalizer "sc", returning
// a canonical index for the slot, and adding it to the table if need
// be. Return value is the canonical slot index, and a boolean indicating
// whether the slot was found in the table already (TRUE => found).
func (sc *slotCanonicalizer) lookup(ls LocalSlot) (SlKeyIdx, bool) {
split := noSlot
if ls.SplitOf != nil {
split, _ = sc.lookup(*ls.SplitOf)
}
k := slotKey{
name: ls.N, offset: ls.Off, width: ls.Type.Size(),
splitOf: split, splitOffset: ls.SplitOffset,
}
if idx, ok := sc.slmap[k]; ok {
return idx, true
}
rv := SlKeyIdx(len(sc.slkeys))
sc.slkeys = append(sc.slkeys, ls)
sc.slmap[k] = rv
return rv, false
}
func (sc *slotCanonicalizer) canonSlot(idx SlKeyIdx) LocalSlot {
return sc.slkeys[idx]
}
// PopulateABIInRegArgOps examines the entry block of the function
// and looks for incoming parameters that have missing or partial
// OpArg{Int,Float}Reg values, inserting additional values in
// cases where they are missing. Example:
//
// func foo(s string, used int, notused int) int {
// return len(s) + used
// }
//
// In the function above, the incoming parameter "used" is fully live,
// "notused" is not live, and "s" is partially live (only the length
// field of the string is used). At the point where debug value
// analysis runs, we might expect to see an entry block with:
//
// b1:
// v4 = ArgIntReg <uintptr> {s+8} [0] : BX
// v5 = ArgIntReg <int> {used} [0] : CX
//
// While this is an accurate picture of the live incoming params,
// we also want to have debug locations for non-live params (or
// their non-live pieces), e.g. something like
//
// b1:
// v9 = ArgIntReg <*uint8> {s+0} [0] : AX
// v4 = ArgIntReg <uintptr> {s+8} [0] : BX
// v5 = ArgIntReg <int> {used} [0] : CX
// v10 = ArgIntReg <int> {unused} [0] : DI
//
// This function examines the live OpArg{Int,Float}Reg values and
// synthesizes new (dead) values for the non-live params or the
// non-live pieces of partially live params.
//
func PopulateABIInRegArgOps(f *Func) {
pri := f.ABISelf.ABIAnalyzeFuncType(f.Type.FuncType())
// When manufacturing new slots that correspond to splits of
// composite parameters, we want to avoid creating a new sub-slot
// that differs from some existing sub-slot only by type, since
// the debug location analysis will treat that slot as a separate
// entity. To achieve this, create a lookup table of existing
// slots that is type-insenstitive.
sc := newSlotCanonicalizer()
for _, sl := range f.Names {
sc.lookup(*sl)
}
// Add slot -> value entry to f.NamedValues if not already present.
addToNV := func(v *Value, sl LocalSlot) {
values, ok := f.NamedValues[sl]
if !ok {
// Haven't seen this slot yet.
sla := f.localSlotAddr(sl)
f.Names = append(f.Names, sla)
} else {
for _, ev := range values {
if v == ev {
return
}
}
}
values = append(values, v)
f.NamedValues[sl] = values
}
newValues := []*Value{}
abiRegIndexToRegister := func(reg abi.RegIndex) int8 {
i := f.ABISelf.FloatIndexFor(reg)
if i >= 0 { // float PR
return f.Config.floatParamRegs[i]
} else {
return f.Config.intParamRegs[reg]
}
}
// Helper to construct a new OpArg{Float,Int}Reg op value.
var pos src.XPos
if len(f.Entry.Values) != 0 {
pos = f.Entry.Values[0].Pos
}
synthesizeOpIntFloatArg := func(n *ir.Name, t *types.Type, reg abi.RegIndex, sl LocalSlot) *Value {
aux := &AuxNameOffset{n, sl.Off}
op, auxInt := ArgOpAndRegisterFor(reg, f.ABISelf)
v := f.newValueNoBlock(op, t, pos)
v.AuxInt = auxInt
v.Aux = aux
v.Args = nil
v.Block = f.Entry
newValues = append(newValues, v)
addToNV(v, sl)
f.setHome(v, &f.Config.registers[abiRegIndexToRegister(reg)])
return v
}
// Make a pass through the entry block looking for
// OpArg{Int,Float}Reg ops. Record the slots they use in a table
// ("sc"). We use a type-insensitive lookup for the slot table,
// since the type we get from the ABI analyzer won't always match
// what the compiler uses when creating OpArg{Int,Float}Reg ops.
for _, v := range f.Entry.Values {
if v.Op == OpArgIntReg || v.Op == OpArgFloatReg {
aux := v.Aux.(*AuxNameOffset)
sl := LocalSlot{N: aux.Name, Type: v.Type, Off: aux.Offset}
// install slot in lookup table
idx, _ := sc.lookup(sl)
// add to f.NamedValues if not already present
addToNV(v, sc.canonSlot(idx))
} else if v.Op.IsCall() {
// if we hit a call, we've gone too far.
break
}
}
// Now make a pass through the ABI in-params, looking for params
// or pieces of params that we didn't encounter in the loop above.
for _, inp := range pri.InParams() {
if !isNamedRegParam(inp) {
continue
}
n := inp.Name.(*ir.Name)
// Param is spread across one or more registers. Walk through
// each piece to see whether we've seen an arg reg op for it.
types, offsets := inp.RegisterTypesAndOffsets()
for k, t := range types {
// Note: this recipe for creating a LocalSlot is designed
// to be compatible with the one used in expand_calls.go
// as opposed to decompose.go. The expand calls code just
// takes the base name and creates an offset into it,
// without using the SplitOf/SplitOffset fields. The code
// in decompose.go does the opposite -- it creates a
// LocalSlot object with "Off" set to zero, but with
// SplitOf pointing to a parent slot, and SplitOffset
// holding the offset into the parent object.
pieceSlot := LocalSlot{N: n, Type: t, Off: offsets[k]}
// Look up this piece to see if we've seen a reg op
// for it. If not, create one.
_, found := sc.lookup(pieceSlot)
if !found {
// This slot doesn't appear in the map, meaning it
// corresponds to an in-param that is not live, or
// a portion of an in-param that is not live/used.
// Add a new dummy OpArg{Int,Float}Reg for it.
synthesizeOpIntFloatArg(n, t, inp.Registers[k],
pieceSlot)
}
}
}
// Insert the new values into the head of the block.
f.Entry.Values = append(newValues, f.Entry.Values...)
}
// BuildFuncDebug returns debug information for f.
// f must be fully processed, so that each Value is where it will be when
// machine code is emitted.
func BuildFuncDebug(ctxt *obj.Link, f *Func, loggingEnabled bool, stackOffset func(LocalSlot) int32) *FuncDebug {
if f.RegAlloc == nil {
f.Fatalf("BuildFuncDebug on func %v that has not been fully processed", f)
}
state := &f.Cache.debugState
state.loggingEnabled = loggingEnabled
state.f = f
state.registers = f.Config.registers
state.stackOffset = stackOffset
state.ctxt = ctxt
if buildcfg.Experiment.RegabiArgs {
PopulateABIInRegArgOps(f)
}
if state.loggingEnabled {
state.logf("Generating location lists for function %q\n", f.Name)
}
if state.varParts == nil {
state.varParts = make(map[*ir.Name][]SlotID)
} else {
for n := range state.varParts {
delete(state.varParts, n)
}
}
// Recompose any decomposed variables, and establish the canonical
// IDs for each var and slot by filling out state.vars and state.slots.
state.slots = state.slots[:0]
state.vars = state.vars[:0]
for i, slot := range f.Names {
state.slots = append(state.slots, *slot)
if ir.IsSynthetic(slot.N) {
continue
}
topSlot := slot
for topSlot.SplitOf != nil {
topSlot = topSlot.SplitOf
}
if _, ok := state.varParts[topSlot.N]; !ok {
state.vars = append(state.vars, topSlot.N)
}
state.varParts[topSlot.N] = append(state.varParts[topSlot.N], SlotID(i))
}
// Recreate the LocalSlot for each stack-only variable.
// This would probably be better as an output from stackframe.
for _, b := range f.Blocks {
for _, v := range b.Values {
if v.Op == OpVarDef || v.Op == OpVarKill {
n := v.Aux.(*ir.Name)
if ir.IsSynthetic(n) {
continue
}
if _, ok := state.varParts[n]; !ok {
slot := LocalSlot{N: n, Type: v.Type, Off: 0}
state.slots = append(state.slots, slot)
state.varParts[n] = []SlotID{SlotID(len(state.slots) - 1)}
state.vars = append(state.vars, n)
}
}
}
}
// Fill in the var<->slot mappings.
if cap(state.varSlots) < len(state.vars) {
state.varSlots = make([][]SlotID, len(state.vars))
} else {
state.varSlots = state.varSlots[:len(state.vars)]
for i := range state.varSlots {
state.varSlots[i] = state.varSlots[i][:0]
}
}
if cap(state.slotVars) < len(state.slots) {
state.slotVars = make([]VarID, len(state.slots))
} else {
state.slotVars = state.slotVars[:len(state.slots)]
}
if state.partsByVarOffset == nil {
state.partsByVarOffset = &partsByVarOffset{}
}
for varID, n := range state.vars {
parts := state.varParts[n]
state.varSlots[varID] = parts
for _, slotID := range parts {
state.slotVars[slotID] = VarID(varID)
}
*state.partsByVarOffset.(*partsByVarOffset) = partsByVarOffset{parts, state.slots}
sort.Sort(state.partsByVarOffset)
}
state.initializeCache(f, len(state.varParts), len(state.slots))
for i, slot := range f.Names {
if ir.IsSynthetic(slot.N) {
continue
}
for _, value := range f.NamedValues[*slot] {
state.valueNames[value.ID] = append(state.valueNames[value.ID], SlotID(i))
}
}
blockLocs := state.liveness()
state.buildLocationLists(blockLocs)
return &FuncDebug{
Slots: state.slots,
VarSlots: state.varSlots,
Vars: state.vars,
LocationLists: state.lists,
}
}
// liveness walks the function in control flow order, calculating the start
// and end state of each block.
func (state *debugState) liveness() []*BlockDebug {
blockLocs := make([]*BlockDebug, state.f.NumBlocks())
// Reverse postorder: visit a block after as many as possible of its
// predecessors have been visited.
po := state.f.Postorder()
for i := len(po) - 1; i >= 0; i-- {
b := po[i]
// Build the starting state for the block from the final
// state of its predecessors.
startState, startValid := state.mergePredecessors(b, blockLocs, nil)
changed := false
if state.loggingEnabled {
state.logf("Processing %v, initial state:\n%v", b, state.stateString(state.currentState))
}
// Update locs/registers with the effects of each Value.
for _, v := range b.Values {
slots := state.valueNames[v.ID]
// Loads and stores inherit the names of their sources.
var source *Value
switch v.Op {
case OpStoreReg:
source = v.Args[0]
case OpLoadReg:
switch a := v.Args[0]; a.Op {
case OpArg, OpPhi:
source = a
case OpStoreReg:
source = a.Args[0]
default:
if state.loggingEnabled {
state.logf("at %v: load with unexpected source op: %v (%v)\n", v, a.Op, a)
}
}
}
// Update valueNames with the source so that later steps
// don't need special handling.
if source != nil {
slots = append(slots, state.valueNames[source.ID]...)
state.valueNames[v.ID] = slots
}
reg, _ := state.f.getHome(v.ID).(*Register)
c := state.processValue(v, slots, reg)
changed = changed || c
}
if state.loggingEnabled {
state.f.Logf("Block %v done, locs:\n%v", b, state.stateString(state.currentState))
}
locs := state.allocBlock(b)
locs.relevant = changed
if !changed && startValid {
locs.endState = startState
} else {
for slotID, slotLoc := range state.currentState.slots {
if slotLoc.absent() {
continue
}
state.appendLiveSlot(liveSlot{slot: SlotID(slotID), Registers: slotLoc.Registers, StackOffset: slotLoc.StackOffset})
}
locs.endState = state.getLiveSlotSlice()
}
blockLocs[b.ID] = locs
}
return blockLocs
}
// mergePredecessors takes the end state of each of b's predecessors and
// intersects them to form the starting state for b. It puts that state in
// blockLocs, and fills state.currentState with it. If convenient, it returns
// a reused []liveSlot, true that represents the starting state.
// If previousBlock is non-nil, it registers changes vs. that block's end
// state in state.changedVars. Note that previousBlock will often not be a
// predecessor.
func (state *debugState) mergePredecessors(b *Block, blockLocs []*BlockDebug, previousBlock *Block) ([]liveSlot, bool) {
// Filter out back branches.
var predsBuf [10]*Block
preds := predsBuf[:0]
for _, pred := range b.Preds {
if blockLocs[pred.b.ID] != nil {
preds = append(preds, pred.b)
}
}
if state.loggingEnabled {
// The logf below would cause preds to be heap-allocated if
// it were passed directly.
preds2 := make([]*Block, len(preds))
copy(preds2, preds)
state.logf("Merging %v into %v\n", preds2, b)
}
// TODO all the calls to this are overkill; only need to do this for slots that are not present in the merge.
markChangedVars := func(slots []liveSlot) {
for _, live := range slots {
state.changedVars.add(ID(state.slotVars[live.slot]))
}
}
if len(preds) == 0 {
if previousBlock != nil {
// Mark everything in previous block as changed because it is not a predecessor.
markChangedVars(blockLocs[previousBlock.ID].endState)
}
state.currentState.reset(nil)
return nil, true
}
p0 := blockLocs[preds[0].ID].endState
if len(preds) == 1 {
if previousBlock != nil && preds[0].ID != previousBlock.ID {
// Mark everything in previous block as changed because it is not a predecessor.
markChangedVars(blockLocs[previousBlock.ID].endState)
}
state.currentState.reset(p0)
return p0, true
}
baseID := preds[0].ID
baseState := p0
// If previous block is not a predecessor, its location information changes at boundary with this block.
previousBlockIsNotPredecessor := previousBlock != nil // If it's nil, no info to change.
if previousBlock != nil {
// Try to use previousBlock as the base state
// if possible.
for _, pred := range preds[1:] {
if pred.ID == previousBlock.ID {
baseID = pred.ID
baseState = blockLocs[pred.ID].endState
previousBlockIsNotPredecessor = false
break
}
}
}
if state.loggingEnabled {
state.logf("Starting %v with state from b%v:\n%v", b, baseID, state.blockEndStateString(blockLocs[baseID]))
}
slotLocs := state.currentState.slots
for _, predSlot := range baseState {
slotLocs[predSlot.slot] = VarLoc{predSlot.Registers, predSlot.StackOffset}
state.liveCount[predSlot.slot] = 1
}
for _, pred := range preds {
if pred.ID == baseID {
continue
}
if state.loggingEnabled {
state.logf("Merging in state from %v:\n%v", pred, state.blockEndStateString(blockLocs[pred.ID]))
}
for _, predSlot := range blockLocs[pred.ID].endState {
state.liveCount[predSlot.slot]++
liveLoc := slotLocs[predSlot.slot]
if !liveLoc.onStack() || !predSlot.onStack() || liveLoc.StackOffset != predSlot.StackOffset {
liveLoc.StackOffset = 0
}
liveLoc.Registers &= predSlot.Registers
slotLocs[predSlot.slot] = liveLoc
}
}
// Check if the final state is the same as the first predecessor's
// final state, and reuse it if so. In principle it could match any,
// but it's probably not worth checking more than the first.
unchanged := true
for _, predSlot := range baseState {
if state.liveCount[predSlot.slot] != len(preds) ||
slotLocs[predSlot.slot].Registers != predSlot.Registers ||
slotLocs[predSlot.slot].StackOffset != predSlot.StackOffset {
unchanged = false
break
}
}
if unchanged {
if state.loggingEnabled {
state.logf("After merge, %v matches b%v exactly.\n", b, baseID)
}
if previousBlockIsNotPredecessor {
// Mark everything in previous block as changed because it is not a predecessor.
markChangedVars(blockLocs[previousBlock.ID].endState)
}
state.currentState.reset(baseState)
return baseState, true
}
for reg := range state.currentState.registers {
state.currentState.registers[reg] = state.currentState.registers[reg][:0]
}
// A slot is live if it was seen in all predecessors, and they all had
// some storage in common.
for _, predSlot := range baseState {
slotLoc := slotLocs[predSlot.slot]
if state.liveCount[predSlot.slot] != len(preds) {
// Seen in only some predecessors. Clear it out.
slotLocs[predSlot.slot] = VarLoc{}
continue
}
// Present in all predecessors.
mask := uint64(slotLoc.Registers)
for {
if mask == 0 {
break
}
reg := uint8(bits.TrailingZeros64(mask))
mask &^= 1 << reg
state.currentState.registers[reg] = append(state.currentState.registers[reg], predSlot.slot)
}
}
if previousBlockIsNotPredecessor {
// Mark everything in previous block as changed because it is not a predecessor.
markChangedVars(blockLocs[previousBlock.ID].endState)
}
return nil, false
}
// processValue updates locs and state.registerContents to reflect v, a value with
// the names in vSlots and homed in vReg. "v" becomes visible after execution of
// the instructions evaluating it. It returns which VarIDs were modified by the
// Value's execution.
func (state *debugState) processValue(v *Value, vSlots []SlotID, vReg *Register) bool {
locs := state.currentState
changed := false
setSlot := func(slot SlotID, loc VarLoc) {
changed = true
state.changedVars.add(ID(state.slotVars[slot]))
state.currentState.slots[slot] = loc
}
// Handle any register clobbering. Call operations, for example,
// clobber all registers even though they don't explicitly write to
// them.
clobbers := uint64(opcodeTable[v.Op].reg.clobbers)
for {
if clobbers == 0 {
break
}
reg := uint8(bits.TrailingZeros64(clobbers))
clobbers &^= 1 << reg
for _, slot := range locs.registers[reg] {
if state.loggingEnabled {
state.logf("at %v: %v clobbered out of %v\n", v, state.slots[slot], &state.registers[reg])
}
last := locs.slots[slot]
if last.absent() {
state.f.Fatalf("at %v: slot %v in register %v with no location entry", v, state.slots[slot], &state.registers[reg])
continue
}
regs := last.Registers &^ (1 << reg)
setSlot(slot, VarLoc{regs, last.StackOffset})
}
locs.registers[reg] = locs.registers[reg][:0]
}
switch {
case v.Op == OpVarDef, v.Op == OpVarKill:
n := v.Aux.(*ir.Name)
if ir.IsSynthetic(n) {
break
}
slotID := state.varParts[n][0]
var stackOffset StackOffset
if v.Op == OpVarDef {
stackOffset = StackOffset(state.stackOffset(state.slots[slotID])<<1 | 1)
}
setSlot(slotID, VarLoc{0, stackOffset})
if state.loggingEnabled {
if v.Op == OpVarDef {
state.logf("at %v: stack-only var %v now live\n", v, state.slots[slotID])
} else {
state.logf("at %v: stack-only var %v now dead\n", v, state.slots[slotID])
}
}
case v.Op == OpArg:
home := state.f.getHome(v.ID).(LocalSlot)
stackOffset := state.stackOffset(home)<<1 | 1
for _, slot := range vSlots {
if state.loggingEnabled {
state.logf("at %v: arg %v now on stack in location %v\n", v, state.slots[slot], home)
if last := locs.slots[slot]; !last.absent() {
state.logf("at %v: unexpected arg op on already-live slot %v\n", v, state.slots[slot])
}
}
setSlot(slot, VarLoc{0, StackOffset(stackOffset)})
}
case v.Op == OpStoreReg:
home := state.f.getHome(v.ID).(LocalSlot)
stackOffset := state.stackOffset(home)<<1 | 1
for _, slot := range vSlots {
last := locs.slots[slot]
if last.absent() {
if state.loggingEnabled {
state.logf("at %v: unexpected spill of unnamed register %s\n", v, vReg)
}
break
}
setSlot(slot, VarLoc{last.Registers, StackOffset(stackOffset)})
if state.loggingEnabled {
state.logf("at %v: %v spilled to stack location %v\n", v, state.slots[slot], home)
}
}
case vReg != nil:
if state.loggingEnabled {
newSlots := make([]bool, len(state.slots))
for _, slot := range vSlots {
newSlots[slot] = true
}
for _, slot := range locs.registers[vReg.num] {
if !newSlots[slot] {
state.logf("at %v: overwrote %v in register %v\n", v, state.slots[slot], vReg)
}
}
}
for _, slot := range locs.registers[vReg.num] {
last := locs.slots[slot]
setSlot(slot, VarLoc{last.Registers &^ (1 << uint8(vReg.num)), last.StackOffset})
}
locs.registers[vReg.num] = locs.registers[vReg.num][:0]
locs.registers[vReg.num] = append(locs.registers[vReg.num], vSlots...)
for _, slot := range vSlots {
if state.loggingEnabled {
state.logf("at %v: %v now in %s\n", v, state.slots[slot], vReg)
}
last := locs.slots[slot]
setSlot(slot, VarLoc{1<<uint8(vReg.num) | last.Registers, last.StackOffset})
}
}
return changed
}
// varOffset returns the offset of slot within the user variable it was
// decomposed from. This has nothing to do with its stack offset.
func varOffset(slot LocalSlot) int64 {
offset := slot.Off
s := &slot
for ; s.SplitOf != nil; s = s.SplitOf {
offset += s.SplitOffset
}
return offset
}
type partsByVarOffset struct {
slotIDs []SlotID
slots []LocalSlot
}
func (a partsByVarOffset) Len() int { return len(a.slotIDs) }
func (a partsByVarOffset) Less(i, j int) bool {
return varOffset(a.slots[a.slotIDs[i]]) < varOffset(a.slots[a.slotIDs[j]])
}
func (a partsByVarOffset) Swap(i, j int) { a.slotIDs[i], a.slotIDs[j] = a.slotIDs[j], a.slotIDs[i] }
// A pendingEntry represents the beginning of a location list entry, missing
// only its end coordinate.
type pendingEntry struct {
present bool
startBlock, startValue ID
// The location of each piece of the variable, in the same order as the
// SlotIDs in varParts.
pieces []VarLoc
}
func (e *pendingEntry) clear() {
e.present = false
e.startBlock = 0
e.startValue = 0
for i := range e.pieces {
e.pieces[i] = VarLoc{}
}
}
// canMerge reports whether the location description for new is the same as
// pending.
func canMerge(pending, new VarLoc) bool {
if pending.absent() && new.absent() {
return true
}
if pending.absent() || new.absent() {
return false
}
if pending.onStack() {
return pending.StackOffset == new.StackOffset
}
if pending.Registers != 0 && new.Registers != 0 {
return firstReg(pending.Registers) == firstReg(new.Registers)
}
return false
}
// firstReg returns the first register in set that is present.
func firstReg(set RegisterSet) uint8 {
if set == 0 {
// This is wrong, but there seem to be some situations where we
// produce locations with no storage.
return 0
}
return uint8(bits.TrailingZeros64(uint64(set)))
}
// buildLocationLists builds location lists for all the user variables in
// state.f, using the information about block state in blockLocs.
// The returned location lists are not fully complete. They are in terms of
// SSA values rather than PCs, and have no base address/end entries. They will
// be finished by PutLocationList.
func (state *debugState) buildLocationLists(blockLocs []*BlockDebug) {
// Run through the function in program text order, building up location
// lists as we go. The heavy lifting has mostly already been done.
var prevBlock *Block
for _, b := range state.f.Blocks {
state.mergePredecessors(b, blockLocs, prevBlock)
if !blockLocs[b.ID].relevant {
// Handle any differences among predecessor blocks and previous block (perhaps not a predecessor)
for _, varID := range state.changedVars.contents() {
state.updateVar(VarID(varID), b, BlockStart)
}
continue
}
mustBeFirst := func(v *Value) bool {
return v.Op == OpPhi || v.Op.isLoweredGetClosurePtr() ||
v.Op == OpArgIntReg || v.Op == OpArgFloatReg
}
zeroWidthPending := false
blockPrologComplete := false // set to true at first non-zero-width op
apcChangedSize := 0 // size of changedVars for leading Args, Phi, ClosurePtr
// expect to see values in pattern (apc)* (zerowidth|real)*
for _, v := range b.Values {
slots := state.valueNames[v.ID]
reg, _ := state.f.getHome(v.ID).(*Register)
changed := state.processValue(v, slots, reg) // changed == added to state.changedVars
if opcodeTable[v.Op].zeroWidth {
if changed {
if mustBeFirst(v) || v.Op == OpArg {
// These ranges begin at true beginning of block, not after first instruction
if blockPrologComplete && mustBeFirst(v) {
panic(fmt.Errorf("Unexpected placement of op '%s' appearing after non-pseudo-op at beginning of block %s in %s\n%s", v.LongString(), b, b.Func.Name, b.Func))
}
apcChangedSize = len(state.changedVars.contents())
// Other zero-width ops must wait on a "real" op.
zeroWidthPending = true
continue
}
}
continue
}
if !changed && !zeroWidthPending {
continue
}
// Not zero-width; i.e., a "real" instruction.
zeroWidthPending = false
blockPrologComplete = true
for i, varID := range state.changedVars.contents() {
if i < apcChangedSize { // buffered true start-of-block changes
state.updateVar(VarID(varID), v.Block, BlockStart)
} else {
state.updateVar(VarID(varID), v.Block, v)
}
}
state.changedVars.clear()
apcChangedSize = 0
}
for i, varID := range state.changedVars.contents() {
if i < apcChangedSize { // buffered true start-of-block changes
state.updateVar(VarID(varID), b, BlockStart)
} else {
state.updateVar(VarID(varID), b, BlockEnd)
}
}
prevBlock = b
}
if state.loggingEnabled {
state.logf("location lists:\n")
}
// Flush any leftover entries live at the end of the last block.
for varID := range state.lists {
state.writePendingEntry(VarID(varID), state.f.Blocks[len(state.f.Blocks)-1].ID, FuncEnd.ID)
list := state.lists[varID]
if state.loggingEnabled {
if len(list) == 0 {
state.logf("\t%v : empty list\n", state.vars[varID])
} else {
state.logf("\t%v : %q\n", state.vars[varID], hex.EncodeToString(state.lists[varID]))
}
}
}
}
// updateVar updates the pending location list entry for varID to
// reflect the new locations in curLoc, beginning at v in block b.
// v may be one of the special values indicating block start or end.
func (state *debugState) updateVar(varID VarID, b *Block, v *Value) {
curLoc := state.currentState.slots
// Assemble the location list entry with whatever's live.
empty := true
for _, slotID := range state.varSlots[varID] {
if !curLoc[slotID].absent() {
empty = false
break
}
}
pending := &state.pendingEntries[varID]
if empty {
state.writePendingEntry(varID, b.ID, v.ID)
pending.clear()
return
}
// Extend the previous entry if possible.
if pending.present {
merge := true
for i, slotID := range state.varSlots[varID] {
if !canMerge(pending.pieces[i], curLoc[slotID]) {
merge = false
break
}
}
if merge {
return
}
}
state.writePendingEntry(varID, b.ID, v.ID)
pending.present = true
pending.startBlock = b.ID
pending.startValue = v.ID
for i, slot := range state.varSlots[varID] {
pending.pieces[i] = curLoc[slot]
}
}
// writePendingEntry writes out the pending entry for varID, if any,
// terminated at endBlock/Value.
func (state *debugState) writePendingEntry(varID VarID, endBlock, endValue ID) {
pending := state.pendingEntries[varID]
if !pending.present {
return
}
// Pack the start/end coordinates into the start/end addresses
// of the entry, for decoding by PutLocationList.
start, startOK := encodeValue(state.ctxt, pending.startBlock, pending.startValue)
end, endOK := encodeValue(state.ctxt, endBlock, endValue)
if !startOK || !endOK {
// If someone writes a function that uses >65K values,
// they get incomplete debug info on 32-bit platforms.
return
}
if start == end {
if state.loggingEnabled {
// Printf not logf so not gated by GOSSAFUNC; this should fire very rarely.
fmt.Printf("Skipping empty location list for %v in %s\n", state.vars[varID], state.f.Name)
}
return
}
list := state.lists[varID]
list = appendPtr(state.ctxt, list, start)
list = appendPtr(state.ctxt, list, end)
// Where to write the length of the location description once
// we know how big it is.
sizeIdx := len(list)
list = list[:len(list)+2]
if state.loggingEnabled {
var partStrs []string
for i, slot := range state.varSlots[varID] {
partStrs = append(partStrs, fmt.Sprintf("%v@%v", state.slots[slot], state.LocString(pending.pieces[i])))
}
state.logf("Add entry for %v: \tb%vv%v-b%vv%v = \t%v\n", state.vars[varID], pending.startBlock, pending.startValue, endBlock, endValue, strings.Join(partStrs, " "))
}
for i, slotID := range state.varSlots[varID] {
loc := pending.pieces[i]
slot := state.slots[slotID]
if !loc.absent() {
if loc.onStack() {
if loc.stackOffsetValue() == 0 {
list = append(list, dwarf.DW_OP_call_frame_cfa)
} else {
list = append(list, dwarf.DW_OP_fbreg)
list = dwarf.AppendSleb128(list, int64(loc.stackOffsetValue()))
}
} else {
regnum := state.ctxt.Arch.DWARFRegisters[state.registers[firstReg(loc.Registers)].ObjNum()]
if regnum < 32 {
list = append(list, dwarf.DW_OP_reg0+byte(regnum))
} else {
list = append(list, dwarf.DW_OP_regx)
list = dwarf.AppendUleb128(list, uint64(regnum))
}
}
}
if len(state.varSlots[varID]) > 1 {
list = append(list, dwarf.DW_OP_piece)
list = dwarf.AppendUleb128(list, uint64(slot.Type.Size()))
}
}
state.ctxt.Arch.ByteOrder.PutUint16(list[sizeIdx:], uint16(len(list)-sizeIdx-2))
state.lists[varID] = list
}
// PutLocationList adds list (a location list in its intermediate representation) to listSym.
func (debugInfo *FuncDebug) PutLocationList(list []byte, ctxt *obj.Link, listSym, startPC *obj.LSym) {
getPC := debugInfo.GetPC
if ctxt.UseBASEntries {
listSym.WriteInt(ctxt, listSym.Size, ctxt.Arch.PtrSize, ^0)
listSym.WriteAddr(ctxt, listSym.Size, ctxt.Arch.PtrSize, startPC, 0)
}
// Re-read list, translating its address from block/value ID to PC.
for i := 0; i < len(list); {
begin := getPC(decodeValue(ctxt, readPtr(ctxt, list[i:])))
end := getPC(decodeValue(ctxt, readPtr(ctxt, list[i+ctxt.Arch.PtrSize:])))
// Horrible hack. If a range contains only zero-width
// instructions, e.g. an Arg, and it's at the beginning of the
// function, this would be indistinguishable from an
// end entry. Fudge it.
if begin == 0 && end == 0 {
end = 1
}
if ctxt.UseBASEntries {
listSym.WriteInt(ctxt, listSym.Size, ctxt.Arch.PtrSize, int64(begin))
listSym.WriteInt(ctxt, listSym.Size, ctxt.Arch.PtrSize, int64(end))
} else {
listSym.WriteCURelativeAddr(ctxt, listSym.Size, startPC, int64(begin))
listSym.WriteCURelativeAddr(ctxt, listSym.Size, startPC, int64(end))
}
i += 2 * ctxt.Arch.PtrSize
datalen := 2 + int(ctxt.Arch.ByteOrder.Uint16(list[i:]))
listSym.WriteBytes(ctxt, listSym.Size, list[i:i+datalen]) // copy datalen and location encoding
i += datalen
}
// Location list contents, now with real PCs.
// End entry.
listSym.WriteInt(ctxt, listSym.Size, ctxt.Arch.PtrSize, 0)
listSym.WriteInt(ctxt, listSym.Size, ctxt.Arch.PtrSize, 0)
}
// Pack a value and block ID into an address-sized uint, returning encoded
// value and boolean indicating whether the encoding succeeded. For
// 32-bit architectures the process may fail for very large procedures
// (the theory being that it's ok to have degraded debug quality in
// this case).
func encodeValue(ctxt *obj.Link, b, v ID) (uint64, bool) {
if ctxt.Arch.PtrSize == 8 {
result := uint64(b)<<32 | uint64(uint32(v))
//ctxt.Logf("b %#x (%d) v %#x (%d) -> %#x\n", b, b, v, v, result)
return result, true
}
if ctxt.Arch.PtrSize != 4 {
panic("unexpected pointer size")
}
if ID(int16(b)) != b || ID(int16(v)) != v {
return 0, false
}
return uint64(b)<<16 | uint64(uint16(v)), true
}
// Unpack a value and block ID encoded by encodeValue.
func decodeValue(ctxt *obj.Link, word uint64) (ID, ID) {
if ctxt.Arch.PtrSize == 8 {
b, v := ID(word>>32), ID(word)
//ctxt.Logf("%#x -> b %#x (%d) v %#x (%d)\n", word, b, b, v, v)
return b, v
}
if ctxt.Arch.PtrSize != 4 {
panic("unexpected pointer size")
}
return ID(word >> 16), ID(int16(word))
}
// Append a pointer-sized uint to buf.
func appendPtr(ctxt *obj.Link, buf []byte, word uint64) []byte {
if cap(buf) < len(buf)+20 {
b := make([]byte, len(buf), 20+cap(buf)*2)
copy(b, buf)
buf = b
}
writeAt := len(buf)
buf = buf[0 : len(buf)+ctxt.Arch.PtrSize]
writePtr(ctxt, buf[writeAt:], word)
return buf
}
// Write a pointer-sized uint to the beginning of buf.
func writePtr(ctxt *obj.Link, buf []byte, word uint64) {
switch ctxt.Arch.PtrSize {
case 4:
ctxt.Arch.ByteOrder.PutUint32(buf, uint32(word))
case 8:
ctxt.Arch.ByteOrder.PutUint64(buf, word)
default:
panic("unexpected pointer size")
}
}
// Read a pointer-sized uint from the beginning of buf.
func readPtr(ctxt *obj.Link, buf []byte) uint64 {
switch ctxt.Arch.PtrSize {
case 4:
return uint64(ctxt.Arch.ByteOrder.Uint32(buf))
case 8:
return ctxt.Arch.ByteOrder.Uint64(buf)
default:
panic("unexpected pointer size")
}
}
// setupLocList creates the initial portion of a location list for a
// user variable. It emits the encoded start/end of the range and a
// placeholder for the size. Return value is the new list plus the
// slot in the list holding the size (to be updated later).
func setupLocList(ctxt *obj.Link, f *Func, list []byte, st, en ID) ([]byte, int) {
start, startOK := encodeValue(ctxt, f.Entry.ID, st)
end, endOK := encodeValue(ctxt, f.Entry.ID, en)
if !startOK || !endOK {
// This could happen if someone writes a function that uses
// >65K values on a 32-bit platform. Hopefully a degraded debugging
// experience is ok in that case.
return nil, 0
}
list = appendPtr(ctxt, list, start)
list = appendPtr(ctxt, list, end)
// Where to write the length of the location description once
// we know how big it is.
sizeIdx := len(list)
list = list[:len(list)+2]
return list, sizeIdx
}
// locatePrologEnd walks the entry block of a function with incoming
// register arguments and locates the last instruction in the prolog
// that spills a register arg. It returns the ID of that instruction
// Example:
//
// b1:
// v3 = ArgIntReg <int> {p1+0} [0] : AX
// ... more arg regs ..
// v4 = ArgFloatReg <float32> {f1+0} [0] : X0
// v52 = MOVQstore <mem> {p1} v2 v3 v1
// ... more stores ...
// v68 = MOVSSstore <mem> {f4} v2 v67 v66
// v38 = MOVQstoreconst <mem> {blob} [val=0,off=0] v2 v32
//
// Important: locatePrologEnd is expected to work properly only with
// optimization turned off (e.g. "-N"). If optimization is enabled
// we can't be assured of finding all input arguments spilled in the
// entry block prolog.
func locatePrologEnd(f *Func) ID {
// returns true if this instruction looks like it moves an ABI
// register to the stack, along with the value being stored.
isRegMoveLike := func(v *Value) (bool, ID) {
n, ok := v.Aux.(*ir.Name)
var r ID
if !ok || n.Class != ir.PPARAM {
return false, r
}
regInputs, memInputs, spInputs := 0, 0, 0
for _, a := range v.Args {
if a.Op == OpArgIntReg || a.Op == OpArgFloatReg {
regInputs++
r = a.ID
} else if a.Type.IsMemory() {
memInputs++
} else if a.Op == OpSP {
spInputs++
} else {
return false, r
}
}
return v.Type.IsMemory() && memInputs == 1 &&
regInputs == 1 && spInputs == 1, r
}
// OpArg*Reg values we've seen so far on our forward walk,
// for which we have not yet seen a corresponding spill.
regArgs := make([]ID, 0, 32)
// removeReg tries to remove a value from regArgs, returning true
// if found and removed, or false otherwise.
removeReg := func(r ID) bool {
for i := 0; i < len(regArgs); i++ {
if regArgs[i] == r {
regArgs = append(regArgs[:i], regArgs[i+1:]...)
return true
}
}
return false
}
// Walk forwards through the block. When we see OpArg*Reg, record
// the value it produces in the regArgs list. When see a store that uses
// the value, remove the entry. When we hit the last store (use)
// then we've arrived at the end of the prolog.
for k, v := range f.Entry.Values {
if v.Op == OpArgIntReg || v.Op == OpArgFloatReg {
regArgs = append(regArgs, v.ID)
continue
}
if ok, r := isRegMoveLike(v); ok {
if removed := removeReg(r); removed {
if len(regArgs) == 0 {
// Found our last spill; return the value after
// it. Note that it is possible that this spill is
// the last instruction in the block. If so, then
// return the "end of block" sentinel.
if k < len(f.Entry.Values)-1 {
return f.Entry.Values[k+1].ID
}
return BlockEnd.ID
}
}
}
if v.Op.IsCall() {
// if we hit a call, we've gone too far.
return v.ID
}
}
// nothing found
return ID(-1)
}
// isNamedRegParam returns true if the param corresponding to "p"
// is a named, non-blank input parameter assigned to one or more
// registers.
func isNamedRegParam(p abi.ABIParamAssignment) bool {
if p.Name == nil {
return false
}
n := p.Name.(*ir.Name)
if n.Sym() == nil || n.Sym().IsBlank() {
return false
}
if len(p.Registers) == 0 {
return false
}
return true
}
// BuildFuncDebugNoOptimized constructs a FuncDebug object with
// entries corresponding to the register-resident input parameters for
// the function "f"; it is used when we are compiling without
// optimization but the register ABI is enabled. For each reg param,
// it constructs a 2-element location list: the first element holds
// the input register, and the second element holds the stack location
// of the param (the assumption being that when optimization is off,
// each input param reg will be spilled in the prolog.
func BuildFuncDebugNoOptimized(ctxt *obj.Link, f *Func, loggingEnabled bool, stackOffset func(LocalSlot) int32) *FuncDebug {
fd := FuncDebug{}
pri := f.ABISelf.ABIAnalyzeFuncType(f.Type.FuncType())
// Look to see if we have any named register-promoted parameters.
// If there are none, bail early and let the caller sort things
// out for the remainder of the params/locals.
numRegParams := 0
for _, inp := range pri.InParams() {
if isNamedRegParam(inp) {
numRegParams++
}
}
if numRegParams == 0 {
return &fd
}
state := debugState{f: f}
if loggingEnabled {
state.logf("generating -N reg param loc lists for func %q\n", f.Name)
}
// Allocate location lists.
fd.LocationLists = make([][]byte, numRegParams)
// Locate the value corresponding to the last spill of
// an input register.
afterPrologVal := locatePrologEnd(f)
// Walk the input params again and process the register-resident elements.
pidx := 0
for _, inp := range pri.InParams() {
if !isNamedRegParam(inp) {
// will be sorted out elsewhere
continue
}
n := inp.Name.(*ir.Name)
sl := LocalSlot{N: n, Type: inp.Type, Off: 0}
fd.Vars = append(fd.Vars, n)
fd.Slots = append(fd.Slots, sl)
slid := len(fd.VarSlots)
fd.VarSlots = append(fd.VarSlots, []SlotID{SlotID(slid)})
if afterPrologVal == ID(-1) {
// This can happen for degenerate functions with infinite
// loops such as that in issue 45948. In such cases, leave
// the var/slot set up for the param, but don't try to
// emit a location list.
if loggingEnabled {
state.logf("locatePrologEnd failed, skipping %v\n", n)
}
pidx++
continue
}
// Param is arriving in one or more registers. We need a 2-element
// location expression for it. First entry in location list
// will correspond to lifetime in input registers.
list, sizeIdx := setupLocList(ctxt, f, fd.LocationLists[pidx],
BlockStart.ID, afterPrologVal)
if list == nil {
pidx++
continue
}
if loggingEnabled {
state.logf("param %v:\n [<entry>, %d]:\n", n, afterPrologVal)
}
rtypes, _ := inp.RegisterTypesAndOffsets()
padding := make([]uint64, 0, 32)
padding = inp.ComputePadding(padding)
for k, r := range inp.Registers {
reg := ObjRegForAbiReg(r, f.Config)
dwreg := ctxt.Arch.DWARFRegisters[reg]
if dwreg < 32 {
list = append(list, dwarf.DW_OP_reg0+byte(dwreg))
} else {
list = append(list, dwarf.DW_OP_regx)
list = dwarf.AppendUleb128(list, uint64(dwreg))
}
if loggingEnabled {
state.logf(" piece %d -> dwreg %d", k, dwreg)
}
if len(inp.Registers) > 1 {
list = append(list, dwarf.DW_OP_piece)
ts := rtypes[k].Size()
list = dwarf.AppendUleb128(list, uint64(ts))
if padding[k] > 0 {
if loggingEnabled {
state.logf(" [pad %d bytes]", padding[k])
}
list = append(list, dwarf.DW_OP_piece)
list = dwarf.AppendUleb128(list, padding[k])
}
}
if loggingEnabled {
state.logf("\n")
}
}
// fill in length of location expression element
ctxt.Arch.ByteOrder.PutUint16(list[sizeIdx:], uint16(len(list)-sizeIdx-2))
// Second entry in the location list will be the stack home
// of the param, once it has been spilled. Emit that now.
list, sizeIdx = setupLocList(ctxt, f, list,
afterPrologVal, FuncEnd.ID)
if list == nil {
pidx++
continue
}
soff := stackOffset(sl)
if soff == 0 {
list = append(list, dwarf.DW_OP_call_frame_cfa)
} else {
list = append(list, dwarf.DW_OP_fbreg)
list = dwarf.AppendSleb128(list, int64(soff))
}
if loggingEnabled {
state.logf(" [%d, <end>): stackOffset=%d\n", afterPrologVal, soff)
}
// fill in size
ctxt.Arch.ByteOrder.PutUint16(list[sizeIdx:], uint16(len(list)-sizeIdx-2))
fd.LocationLists[pidx] = list
pidx++
}
return &fd
}