// 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 {s+8} [0] : BX // v5 = ArgIntReg {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 {s+8} [0] : BX // v5 = ArgIntReg {used} [0] : CX // v10 = ArgIntReg {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 {p1+0} [0] : AX // ... more arg regs .. // v4 = ArgFloatReg {f1+0} [0] : X0 // v52 = MOVQstore {p1} v2 v3 v1 // ... more stores ... // v68 = MOVSSstore {f4} v2 v67 v66 // v38 = MOVQstoreconst {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 [, %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, ): 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++ } }