blob: ff432e9a55fe1cadcad7b7a81b8215333408d894 [file]
// 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 ssadebug
import (
"cmp"
"internal/buildcfg"
"slices"
"cmd/compile/internal/abi"
"cmd/compile/internal/ir"
"cmd/compile/internal/ssa"
"cmd/compile/internal/ssa/ssabase"
"cmd/compile/internal/ssa/ssaop"
"cmd/compile/internal/types"
"cmd/internal/dwarf"
"cmd/internal/obj"
"cmd/internal/src"
)
// 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 []ssa.LocalSlot
// The user variables, indexed by VarID.
Vars []*ir.Name
// The slots that make up each variable, indexed by VarID.
VarSlots [][]ssa.SlotID
// The location list data, indexed by VarID. Must be processed by PutLocationList.
LocationLists [][]ssa.LocListEntry
// Register-resident output parameters for the function. This is filled in at
// SSA generation time.
RegOutputParams []*ir.Name
// Variable declarations that were removed during optimization
OptDcl []*ir.Name
// The ssa.Func.EntryID value, used to build location lists for
// return values promoted to heap in later DWARF generation.
EntryID ssa.ID
// Filled in by the user. Translates Block and Value ID to PC.
//
// NOTE: block is only used if value is BlockStart.ID or BlockEnd.ID.
// Otherwise, it is ignored.
GetPC func(block, value ssa.ID) int64
}
// 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 []ssa.LocalSlot
}
func newSlotCanonicalizer() *slotCanonicalizer {
return &slotCanonicalizer{
slmap: make(map[slotKey]SlKeyIdx),
slkeys: []ssa.LocalSlot{ssa.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 ssa.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) ssa.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 *ssa.Func) {
pri := f.ABISelf.ABIAnalyzeFuncType(f.Type)
// 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 *ssa.Value, sl ssa.LocalSlot) {
values, ok := f.NamedValues[sl]
if !ok {
// Haven't seen this slot yet.
f.Names = append(f.Names, sl)
} else {
for _, ev := range values {
if v == ev {
return
}
}
}
values = append(values, v)
f.NamedValues[sl] = values
}
newValues := []*ssa.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 ssa.LocalSlot) *ssa.Value {
aux := &ssa.AuxNameOffset{Name: n, Offset: sl.Off}
op, auxInt := ssa.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 == ssaop.OpArgIntReg || v.Op == ssaop.OpArgFloatReg {
aux := v.Aux.(*ssa.AuxNameOffset)
sl := ssa.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
// 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 := ssa.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 builds debug information for f, placing the results
// in "rval". 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 *ssa.Func, loggingLevel int, stackOffset func(ssa.LocalSlot) int32, rval *FuncDebug) {
if f.RegAlloc == nil {
f.Fatalf("BuildFuncDebug on func %v that has not been fully processed", f)
}
state := &f.Cache.DebugState
state.LoggingLevel = loggingLevel % 1000
// A specific number demands exactly that many iterations. Under
// particular circumstances it make require more than the total of
// 2 passes implied by a single run through liveness and a single
// run through location list generation.
state.ConvergeCount = loggingLevel / 1000
state.F = f
state.Registers = f.Config.Registers
state.StackOffset = stackOffset
state.Ctxt = ctxt
if buildcfg.Experiment.RegabiArgs {
PopulateABIInRegArgOps(f)
}
if state.LoggingLevel > 0 {
state.Logf("Generating location lists for function %q\n", f.Name)
}
if state.VarParts == nil {
state.VarParts = make(map[*ir.Name][]ssa.SlotID)
} else {
clear(state.VarParts)
}
// 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) || !ssa.IsVarWantedForDebug(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], ssa.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 == ssaop.OpVarDef {
n := v.Aux.(*ir.Name)
if ir.IsSynthetic(n) || !ssa.IsVarWantedForDebug(n) {
continue
}
if _, ok := state.VarParts[n]; !ok {
slot := ssa.LocalSlot{N: n, Type: v.Type, Off: 0}
state.Slots = append(state.Slots, slot)
state.VarParts[n] = []ssa.SlotID{ssa.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([][]ssa.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([]ssa.VarID, len(state.Slots))
} else {
state.SlotVars = state.SlotVars[:len(state.Slots)]
}
for varID, n := range state.Vars {
parts := state.VarParts[n]
slices.SortFunc(parts, func(a, b ssa.SlotID) int {
return cmp.Compare(varOffset(state.Slots[a]), varOffset(state.Slots[b]))
})
state.VarSlots[varID] = parts
for _, slotID := range parts {
state.SlotVars[slotID] = ssa.VarID(varID)
}
}
state.InitializeCache(f, len(state.VarParts), len(state.Slots))
for i, slot := range f.Names {
if ir.IsSynthetic(slot.N) || !ssa.IsVarWantedForDebug(slot.N) {
continue
}
for _, value := range f.NamedValues[slot] {
state.ValueNames[value.ID] = append(state.ValueNames[value.ID], ssa.SlotID(i))
}
}
blockLocs := state.Liveness()
state.BuildLocationLists(blockLocs)
// Populate "rval" with what we've computed.
rval.Slots = state.Slots
rval.VarSlots = state.VarSlots
rval.Vars = state.Vars
rval.LocationLists = state.Lists
}
// 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 ssa.LocalSlot) int64 {
offset := slot.Off
s := &slot
for ; s.SplitOf != nil; s = s.SplitOf {
offset += s.SplitOffset
}
return offset
}
// PutLocationList adds entries (a location list in structured form)
// to listSym, encoding it in the appropriate DWARF format.
func (debugInfo *FuncDebug) PutLocationList(entries []ssa.LocListEntry, ctxt *obj.Link, listSym, startPC *obj.LSym) {
if buildcfg.Experiment.Dwarf5 {
debugInfo.PutLocationListDwarf5(entries, ctxt, listSym, startPC)
} else {
debugInfo.PutLocationListDwarf4(entries, ctxt, listSym, startPC)
}
}
// PutLocationListDwarf5 adds entries (a location list in structured form)
// to listSym in DWARF 5 format.
func (debugInfo *FuncDebug) PutLocationListDwarf5(entries []ssa.LocListEntry, ctxt *obj.Link, listSym, startPC *obj.LSym) {
getPC := debugInfo.GetPC
// base address entry
listSym.WriteInt(ctxt, listSym.Size, 1, dwarf.DW_LLE_base_addressx)
listSym.WriteDwTxtAddrx(ctxt, listSym.Size, startPC, ctxt.DwTextCount*2)
var stbuf, enbuf [10]byte
for _, entry := range entries {
begin := getPC(entry.StartBlock, entry.StartValue)
end := getPC(entry.EndBlock, entry.EndValue)
// Write LLE_offset_pair tag followed by payload (ULEB for start
// and then end).
listSym.WriteInt(ctxt, listSym.Size, 1, dwarf.DW_LLE_offset_pair)
stb := stbuf[:0]
enb := enbuf[:0]
stb = dwarf.AppendUleb128(stb, uint64(begin))
enb = dwarf.AppendUleb128(enb, uint64(end))
listSym.WriteBytes(ctxt, listSym.Size, stb)
listSym.WriteBytes(ctxt, listSym.Size, enb)
// DWARF5 uses ULEB128-encoded length for the location expression.
stb = stbuf[:0]
stb = dwarf.AppendUleb128(stb, uint64(len(entry.Expr)))
listSym.WriteBytes(ctxt, listSym.Size, stb)
listSym.WriteBytes(ctxt, listSym.Size, entry.Expr)
}
// Terminator
listSym.WriteInt(ctxt, listSym.Size, 1, dwarf.DW_LLE_end_of_list)
}
// PutLocationListDwarf4 adds entries (a location list in structured form)
// to listSym in DWARF 4 format.
func (debugInfo *FuncDebug) PutLocationListDwarf4(entries []ssa.LocListEntry, 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)
}
for _, entry := range entries {
begin := getPC(entry.StartBlock, entry.StartValue)
end := getPC(entry.EndBlock, entry.EndValue)
// 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, begin)
listSym.WriteInt(ctxt, listSym.Size, ctxt.Arch.PtrSize, end)
} else {
listSym.WriteCURelativeAddr(ctxt, listSym.Size, startPC, begin)
listSym.WriteCURelativeAddr(ctxt, listSym.Size, startPC, end)
}
// Write 2-byte length prefix followed by the location expression.
listSym.WriteInt(ctxt, listSym.Size, 2, int64(len(entry.Expr)))
listSym.WriteBytes(ctxt, listSym.Size, entry.Expr)
}
// End entry.
listSym.WriteInt(ctxt, listSym.Size, ctxt.Arch.PtrSize, 0)
listSym.WriteInt(ctxt, listSym.Size, ctxt.Arch.PtrSize, 0)
}
// 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,
// and (where appropriate) the prolog's lowered closure ptr store inst.
//
// 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 *ssa.Func, needCloCtx bool) (ssa.ID, *ssa.Value) {
// returns true if this instruction looks like it moves an ABI
// register (or context register for rangefunc bodies) to the
// stack, along with the value being stored.
isRegMoveLike := func(v *ssa.Value) (bool, ssa.ID) {
n, ok := v.Aux.(*ir.Name)
var r ssa.ID
if (!ok || n.Class != ir.PPARAM) && !needCloCtx {
return false, r
}
regInputs, memInputs, spInputs := 0, 0, 0
for _, a := range v.Args {
if a.Op == ssaop.OpArgIntReg || a.Op == ssaop.OpArgFloatReg ||
(needCloCtx && a.Op.IsLoweredGetClosurePtr()) {
regInputs++
r = a.ID
} else if a.Type.IsMemory() {
memInputs++
} else if a.Op == ssaop.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([]ssa.ID, 0, 32)
// removeReg tries to remove a value from regArgs, returning true
// if found and removed, or false otherwise.
removeReg := func(r ssa.ID) bool {
for i := 0; i < len(regArgs); i++ {
if regArgs[i] == r {
regArgs = slices.Delete(regArgs, i, 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.
var cloRegStore *ssa.Value
for k, v := range f.Entry.Values {
if v.Op == ssaop.OpArgIntReg || v.Op == ssaop.OpArgFloatReg {
regArgs = append(regArgs, v.ID)
continue
}
if needCloCtx && v.Op.IsLoweredGetClosurePtr() {
regArgs = append(regArgs, v.ID)
cloRegStore = v
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, cloRegStore
}
return ssa.BlockEnd.ID, cloRegStore
}
}
}
if v.Op.IsCall() {
// if we hit a call, we've gone too far.
return v.ID, cloRegStore
}
}
// nothing found
return ssa.ID(-1), cloRegStore
}
// 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
if n.Sym() == nil || n.Sym().IsBlank() {
return false
}
if len(p.Registers) == 0 {
return false
}
return true
}
// BuildFuncDebugNoOptimized populates a FuncDebug object "rval" 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). In addition
// to the register params, here we also build location lists (where
// appropriate for the ".closureptr" compiler-synthesized variable
// needed by the debugger for range func bodies.
func BuildFuncDebugNoOptimized(ctxt *obj.Link, f *ssa.Func, loggingEnabled bool, stackOffset func(ssa.LocalSlot) int32, rval *FuncDebug) {
needCloCtx := f.CloSlot != nil
pri := f.ABISelf.ABIAnalyzeFuncType(f.Type)
// Look to see if we have any named register-promoted parameters,
// and/or whether we need location info for the ".closureptr"
// synthetic variable; if not 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 && !needCloCtx {
return
}
state := ssa.DebugState{F: f}
if loggingEnabled {
state.Logf("generating -N reg param loc lists for func %q\n", f.Name)
}
// cloReg stores the obj register num that the context register
// appears in within the function prolog, where appropriate.
var cloReg int16
extraForCloCtx := 0
if needCloCtx {
extraForCloCtx = 1
}
// Allocate location lists.
rval.LocationLists = make([][]ssa.LocListEntry, numRegParams+extraForCloCtx)
// Locate the value corresponding to the last spill of
// an input register.
afterPrologVal, cloRegStore := locatePrologEnd(f, needCloCtx)
if needCloCtx {
reg, _ := state.F.GetHome(cloRegStore.ID).(*ssabase.Register)
cloReg = reg.ObjNum
if loggingEnabled {
state.Logf("needCloCtx is true for func %q, cloreg=%v\n",
f.Name, reg)
}
}
addVarSlot := func(name *ir.Name, typ *types.Type) {
sl := ssa.LocalSlot{N: name, Type: typ, Off: 0}
rval.Vars = append(rval.Vars, name)
rval.Slots = append(rval.Slots, sl)
slid := len(rval.VarSlots)
rval.VarSlots = append(rval.VarSlots, []ssa.SlotID{ssa.SlotID(slid)})
}
// Make an initial pass to populate the vars/slots for our return
// value, covering first the input parameters and then (if needed)
// the special ".closureptr" var for rangefunc bodies.
params := []abi.ABIParamAssignment{}
for _, inp := range pri.InParams() {
if !isNamedRegParam(inp) {
// will be sorted out elsewhere
continue
}
if !ssa.IsVarWantedForDebug(inp.Name) {
continue
}
addVarSlot(inp.Name, inp.Type)
params = append(params, inp)
}
if needCloCtx {
addVarSlot(f.CloSlot, f.CloSlot.Type())
cloAssign := abi.ABIParamAssignment{
Type: f.CloSlot.Type(),
Name: f.CloSlot,
Registers: []abi.RegIndex{0}, // dummy
}
params = append(params, cloAssign)
}
// Walk the input params again and process the register-resident elements.
pidx := 0
for _, inp := range params {
if !isNamedRegParam(inp) {
// will be sorted out elsewhere
continue
}
if !ssa.IsVarWantedForDebug(inp.Name) {
continue
}
sl := rval.Slots[pidx]
n := rval.Vars[pidx]
if afterPrologVal == ssa.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.
if loggingEnabled {
state.Logf("param %v:\n [<entry>, %d]:\n", n, afterPrologVal)
}
var regExpr []byte
rtypes, _ := inp.RegisterTypesAndOffsets()
padding := make([]uint64, 0, 32)
padding = inp.ComputePadding(padding)
for k, r := range inp.Registers {
var reg int16
if n == f.CloSlot {
reg = cloReg
} else {
reg = ssa.ObjRegForAbiReg(r, f.Config)
}
dwreg := ctxt.Arch.DWARFRegisters[reg]
if dwreg < 32 {
regExpr = append(regExpr, dwarf.DW_OP_reg0+byte(dwreg))
} else {
regExpr = append(regExpr, dwarf.DW_OP_regx)
regExpr = dwarf.AppendUleb128(regExpr, uint64(dwreg))
}
if loggingEnabled {
state.Logf(" piece %d -> dwreg %d", k, dwreg)
}
if len(inp.Registers) > 1 {
regExpr = append(regExpr, dwarf.DW_OP_piece)
ts := rtypes[k].Size()
regExpr = dwarf.AppendUleb128(regExpr, uint64(ts))
if padding[k] > 0 {
if loggingEnabled {
state.Logf(" [pad %d bytes]", padding[k])
}
regExpr = append(regExpr, dwarf.DW_OP_piece)
regExpr = dwarf.AppendUleb128(regExpr, padding[k])
}
}
if loggingEnabled {
state.Logf("\n")
}
}
rval.LocationLists[pidx] = append(rval.LocationLists[pidx], ssa.LocListEntry{
StartBlock: f.Entry.ID,
StartValue: ssa.BlockStart.ID,
EndBlock: f.Entry.ID,
EndValue: afterPrologVal,
Expr: regExpr,
})
// Second entry in the location list will be the stack home
// of the param, once it has been spilled. Emit that now.
var stackExpr []byte
soff := stackOffset(sl)
if soff == 0 {
stackExpr = append(stackExpr, dwarf.DW_OP_call_frame_cfa)
} else {
stackExpr = append(stackExpr, dwarf.DW_OP_fbreg)
stackExpr = dwarf.AppendSleb128(stackExpr, int64(soff))
}
if loggingEnabled {
state.Logf(" [%d, <end>): stackOffset=%d\n", afterPrologVal, soff)
}
rval.LocationLists[pidx] = append(rval.LocationLists[pidx], ssa.LocListEntry{
StartBlock: f.Entry.ID,
StartValue: afterPrologVal,
EndBlock: f.Entry.ID,
EndValue: ssa.FuncEnd.ID,
Expr: stackExpr,
})
pidx++
}
}