// 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/abt" "cmd/compile/internal/ir" "cmd/compile/internal/types" "cmd/internal/dwarf" "cmd/internal/obj" "cmd/internal/src" "cmp" "encoding/hex" "fmt" "internal/buildcfg" "math/bits" "slices" "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 // 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 // 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 ID) int64 } type BlockDebug struct { // State at the start and end of the block. These are initialized, // and updated from new information that flows on back edges. startState, endState abt.T // Use these to avoid excess work in the merge. If none of the // predecessors has changed since the last check, the old answer is // still good. lastCheckedTime, lastChangedTime int32 // Whether the block had any changes to user variables at all. relevant bool // false until the block has been processed at least once. This // affects how the merge is done; the goal is to maximize sharing // and avoid allocation. everProcessed bool } // A liveSlot is a slot that's live in loc at entry/exit of a block. type liveSlot struct { VarLoc } func (ls *liveSlot) String() string { return fmt.Sprintf("0x%x.%d.%d", ls.Registers, ls.stackOffsetValue(), int32(ls.StackOffset)&1) } func (ls liveSlot) absent() bool { return ls.Registers == 0 && !ls.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 abt.T) { slots, registers := state.slots, state.registers for i := range slots { slots[i] = VarLoc{} } for i := range registers { registers[i] = registers[i][:0] } for it := live.Iterator(); !it.Done(); { k, d := it.Next() live := d.(*liveSlot) slots[k] = live.VarLoc if live.VarLoc.Registers == 0 { continue } mask := uint64(live.VarLoc.Registers) for { if mask == 0 { break } reg := uint8(bits.TrailingZeros64(mask)) mask &^= 1 << reg registers[reg] = append(registers[reg], SlotID(k)) } } state.slots, state.registers = slots, registers } func (s *debugState) LocString(loc VarLoc) string { if loc.absent() { return "" } var storage []string if loc.onStack() { storage = append(storage, fmt.Sprintf("@%+d", loc.stackOffsetValue())) } 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() } func (loc VarLoc) intersect(other VarLoc) VarLoc { if !loc.onStack() || !other.onStack() || loc.StackOffset != other.StackOffset { loc.StackOffset = 0 } loc.Registers &= other.Registers return loc } 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 loggingLevel int convergeCount int // testing; iterate over block debug state this many times 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 changedVars *sparseSet changedSlots *sparseSet // The pending location list entry for each user variable, indexed by VarID. pendingEntries []pendingEntry varParts map[*ir.Name][]SlotID blockDebug []BlockDebug pendingSlotLocs []VarLoc } 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)] } // A relatively small slice, but used many times as the return from processValue. state.changedVars = newSparseSet(numVars) state.changedSlots = newSparseSet(numSlots) // 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 } } } func (state *debugState) allocBlock(b *Block) *BlockDebug { return &state.blockDebug[b.ID] } 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 {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 *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 *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 // 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 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 *Func, loggingLevel int, stackOffset func(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][]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) || !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], 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 { n := v.Aux.(*ir.Name) if ir.IsSynthetic(n) || !IsVarWantedForDebug(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)] } for varID, n := range state.vars { parts := state.varParts[n] slices.SortFunc(parts, func(a, b SlotID) int { return cmp.Compare(varOffset(state.slots[a]), varOffset(state.slots[b])) }) state.varSlots[varID] = parts for _, slotID := range parts { state.slotVars[slotID] = VarID(varID) } } state.initializeCache(f, len(state.varParts), len(state.slots)) for i, slot := range f.Names { if ir.IsSynthetic(slot.N) || !IsVarWantedForDebug(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) // Populate "rval" with what we've computed. rval.Slots = state.slots rval.VarSlots = state.varSlots rval.Vars = state.vars rval.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()) counterTime := int32(1) // Reverse postorder: visit a block after as many as possible of its // predecessors have been visited. po := state.f.Postorder() converged := false // The iteration rule is that by default, run until converged, but // if a particular iteration count is specified, run that many // iterations, no more, no less. A count is specified as the // thousands digit of the location lists debug flag, // e.g. -d=locationlists=4000 keepGoing := func(k int) bool { if state.convergeCount == 0 { return !converged } return k < state.convergeCount } for k := 0; keepGoing(k); k++ { if state.loggingLevel > 0 { state.logf("Liveness pass %d\n", k) } converged = true for i := len(po) - 1; i >= 0; i-- { b := po[i] locs := blockLocs[b.ID] if locs == nil { locs = state.allocBlock(b) blockLocs[b.ID] = locs } // Build the starting state for the block from the final // state of its predecessors. startState, blockChanged := state.mergePredecessors(b, blockLocs, nil, false) locs.lastCheckedTime = counterTime counterTime++ if state.loggingLevel > 1 { state.logf("Processing %v, block changed %v, initial state:\n%v", b, blockChanged, state.stateString(state.currentState)) } if blockChanged { // If the start did not change, then the old endState is good converged = false changed := false state.changedSlots.clear() // 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.loggingLevel > 1 { 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 && k == 0 { // limit to k == 0 otherwise there are duplicates. 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.loggingLevel > 1 { state.logf("Block %v done, locs:\n%v", b, state.stateString(state.currentState)) } locs.relevant = locs.relevant || changed if !changed { locs.endState = startState } else { for _, id := range state.changedSlots.contents() { slotID := SlotID(id) slotLoc := state.currentState.slots[slotID] if slotLoc.absent() { startState.Delete(int32(slotID)) continue } old := startState.Find(int32(slotID)) // do NOT replace existing values if oldLS, ok := old.(*liveSlot); !ok || oldLS.VarLoc != slotLoc { startState.Insert(int32(slotID), &liveSlot{VarLoc: slotLoc}) } } locs.endState = startState } locs.lastChangedTime = counterTime } counterTime++ } } 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[b.ID].startState, and fills state.currentState with it. // It returns the start state and whether this is changed from the // previously approximated value of startState for this block. After // the first call, subsequent calls can only shrink startState. // // Passing forLocationLists=true enables additional side-effects that // are necessary for building location lists but superfluous while still // iterating to an answer. // // 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. // // Note that mergePredecessors behaves slightly differently between // first and subsequent calls for a block. For the first call, the // starting state is approximated by taking the state from the // predecessor whose state is smallest, and removing any elements not // in all the other predecessors; this makes the smallest number of // changes and shares the most state. On subsequent calls the old // value of startState is adjusted with new information; this is judged // to do the least amount of extra work. // // To improve performance, each block's state information is marked with // lastChanged and lastChecked "times" so unchanged predecessors can be // skipped on after-the-first iterations. Doing this allows extra // iterations by the caller to be almost free. // // It is important to know that the set representation used for // startState, endState, and merges can share data for two sets where // one is a small delta from the other. Doing this does require a // little care in how sets are updated, both in mergePredecessors, and // using its result. func (state *debugState) mergePredecessors(b *Block, blockLocs []*BlockDebug, previousBlock *Block, forLocationLists bool) (abt.T, bool) { // Filter out back branches. var predsBuf [10]*Block preds := predsBuf[:0] locs := blockLocs[b.ID] blockChanged := !locs.everProcessed // the first time it always changes. updating := locs.everProcessed // For the first merge, exclude predecessors that have not been seen yet. // I.e., backedges. for _, pred := range b.Preds { if bl := blockLocs[pred.b.ID]; bl != nil && bl.everProcessed { // crucially, a self-edge has bl != nil, but bl.everProcessed is false the first time. preds = append(preds, pred.b) } } locs.everProcessed = true if state.loggingLevel > 1 { // 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 (changed=%d, checked=%d)\n", preds2, b, locs.lastChangedTime, locs.lastCheckedTime) } state.changedVars.clear() markChangedVars := func(slots, merged abt.T) { if !forLocationLists { return } // Fill changedVars with those that differ between the previous // block (in the emit order, not necessarily a flow predecessor) // and the start state for this block. for it := slots.Iterator(); !it.Done(); { k, v := it.Next() m := merged.Find(k) if m == nil || v.(*liveSlot).VarLoc != m.(*liveSlot).VarLoc { state.changedVars.add(ID(state.slotVars[k])) } } } reset := func(ourStartState abt.T) { if !(forLocationLists || blockChanged) { // there is no change and this is not for location lists, do // not bother to reset currentState because it will not be // examined. return } state.currentState.reset(ourStartState) } // Zero predecessors if len(preds) == 0 { if previousBlock != nil { state.f.Fatalf("Function %v, block %s with no predecessors is not first block, has previous %s", state.f, b.String(), previousBlock.String()) } // startState is empty reset(abt.T{}) return abt.T{}, blockChanged } // One predecessor l0 := blockLocs[preds[0].ID] p0 := l0.endState if len(preds) == 1 { if previousBlock != nil && preds[0].ID != previousBlock.ID { // Change from previous block is its endState minus the predecessor's endState markChangedVars(blockLocs[previousBlock.ID].endState, p0) } locs.startState = p0 blockChanged = blockChanged || l0.lastChangedTime > locs.lastCheckedTime reset(p0) return p0, blockChanged } // More than one predecessor if updating { // After the first approximation, i.e., when updating, results // can only get smaller, because initially backedge // predecessors do not participate in the intersection. This // means that for the update, given the prior approximation of // startState, there is no need to re-intersect with unchanged // blocks. Therefore remove unchanged blocks from the // predecessor list. for i := len(preds) - 1; i >= 0; i-- { pred := preds[i] if blockLocs[pred.ID].lastChangedTime > locs.lastCheckedTime { continue // keep this predecessor } preds[i] = preds[len(preds)-1] preds = preds[:len(preds)-1] if state.loggingLevel > 2 { state.logf("Pruned b%d, lastChanged was %d but b%d lastChecked is %d\n", pred.ID, blockLocs[pred.ID].lastChangedTime, b.ID, locs.lastCheckedTime) } } // Check for an early out; this should always hit for the update // if there are no cycles. if len(preds) == 0 { blockChanged = false reset(locs.startState) if state.loggingLevel > 2 { state.logf("Early out, no predecessors changed since last check\n") } if previousBlock != nil { markChangedVars(blockLocs[previousBlock.ID].endState, locs.startState) } return locs.startState, blockChanged } } baseID := preds[0].ID baseState := p0 // Choose the predecessor with the smallest endState for intersection work for _, pred := range preds[1:] { if blockLocs[pred.ID].endState.Size() < baseState.Size() { baseState = blockLocs[pred.ID].endState baseID = pred.ID } } if state.loggingLevel > 2 { state.logf("Starting %v with state from b%v:\n%v", b, baseID, state.blockEndStateString(blockLocs[baseID])) for _, pred := range preds { if pred.ID == baseID { continue } state.logf("Merging in state from %v:\n%v", pred, state.blockEndStateString(blockLocs[pred.ID])) } } state.currentState.reset(abt.T{}) // The normal logic of "reset" is included in the intersection loop below. slotLocs := state.currentState.slots // If this is the first call, do updates on the "baseState"; if this // is a subsequent call, tweak the startState instead. Note that // these "set" values are values; there are no side effects to // other values as these are modified. newState := baseState if updating { newState = blockLocs[b.ID].startState } for it := newState.Iterator(); !it.Done(); { k, d := it.Next() thisSlot := d.(*liveSlot) x := thisSlot.VarLoc x0 := x // initial value in newState // Intersect this slot with the slot in all the predecessors for _, other := range preds { if !updating && other.ID == baseID { continue } otherSlot := blockLocs[other.ID].endState.Find(k) if otherSlot == nil { x = VarLoc{} break } y := otherSlot.(*liveSlot).VarLoc x = x.intersect(y) if x.absent() { x = VarLoc{} break } } // Delete if necessary, but not otherwise (in order to maximize sharing). if x.absent() { if !x0.absent() { blockChanged = true newState.Delete(k) } slotLocs[k] = VarLoc{} continue } if x != x0 { blockChanged = true newState.Insert(k, &liveSlot{VarLoc: x}) } slotLocs[k] = x mask := uint64(x.Registers) for { if mask == 0 { break } reg := uint8(bits.TrailingZeros64(mask)) mask &^= 1 << reg state.currentState.registers[reg] = append(state.currentState.registers[reg], SlotID(k)) } } if previousBlock != nil { markChangedVars(blockLocs[previousBlock.ID].endState, newState) } locs.startState = newState return newState, blockChanged } // 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.changedSlots.add(ID(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.loggingLevel > 1 { 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: n := v.Aux.(*ir.Name) if ir.IsSynthetic(n) || !IsVarWantedForDebug(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.loggingLevel > 1 { 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.loggingLevel > 1 { 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.loggingLevel > 1 { state.logf("at %v: unexpected spill of unnamed register %s\n", v, vReg) } break } setSlot(slot, VarLoc{last.Registers, StackOffset(stackOffset)}) if state.loggingLevel > 1 { state.logf("at %v: %v spilled to stack location %v@%d\n", v, state.slots[slot], home, state.stackOffset(home)) } } case vReg != nil: if state.loggingLevel > 1 { 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.loggingLevel > 1 { state.logf("at %v: %v now in %s\n", v, state.slots[slot], vReg) } last := locs.slots[slot] setSlot(slot, VarLoc{1< {foo+0} [0] : AX (foo) // v34 = ArgIntReg {bar+0} [0] : BX (bar) // ... // v77 = StoreReg v67 : ctx+8[unsafe.Pointer] // v78 = StoreReg v68 : ctx[unsafe.Pointer] // v79 = Arg <*uint8> {args} : args[*uint8] (args[*uint8]) // v80 = Arg {args} [8] : args+8[int] (args+8[int]) // ... // v1 = InitMem // // We can stop scanning the initial portion of the block when // we either see the InitMem op (for entry blocks) or the // first non-zero-width op (for other blocks). for idx := 0; idx < len(b.Values); idx++ { v := b.Values[idx] if blockPrologComplete(v) { break } // Consider only "lifetime begins at block start" ops. if !mustBeFirst(v) && v.Op != OpArg { continue } slots := state.valueNames[v.ID] reg, _ := state.f.getHome(v.ID).(*Register) changed := state.processValue(v, slots, reg) // changed == added to state.changedVars if changed { for _, varID := range state.changedVars.contents() { state.updateVar(VarID(varID), v.Block, BlockStart) } state.changedVars.clear() } } // Now examine the block again, handling things other than the // "begins at block start" lifetimes. zeroWidthPending := false prologComplete := false // expect to see values in pattern (apc)* (zerowidth|real)* for _, v := range b.Values { if blockPrologComplete(v) { prologComplete = true } 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 prologComplete && 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)) } if changed { if mustBeFirst(v) || v.Op == OpArg { // already taken care of above continue } zeroWidthPending = true } continue } if !changed && !zeroWidthPending { continue } // Not zero-width; i.e., a "real" instruction. zeroWidthPending = false for _, varID := range state.changedVars.contents() { state.updateVar(VarID(varID), v.Block, v) } state.changedVars.clear() } for _, varID := range state.changedVars.contents() { state.updateVar(VarID(varID), b, BlockEnd) } prevBlock = b } if state.loggingLevel > 0 { 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), -1, FuncEnd.ID) list := state.lists[varID] if state.loggingLevel > 0 { 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.loggingLevel > 1 { // Printf not logf so not gated by GOSSAFUNC; this should fire very rarely. // TODO this fires a lot, need to figure out why. state.logf("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.loggingLevel > 1 { 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, // 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 *Func, needCloCtx bool) (ID, *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 *Value) (bool, ID) { n, ok := v.Aux.(*ir.Name) var r 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 == OpArgIntReg || a.Op == OpArgFloatReg || (needCloCtx && a.Op.isLoweredGetClosurePtr()) { 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 = 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 *Value for k, v := range f.Entry.Values { if v.Op == OpArgIntReg || v.Op == 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 BlockEnd.ID, cloRegStore } } } if v.Op.IsCall() { // if we hit a call, we've gone too far. return v.ID, cloRegStore } } // nothing found return 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 *Func, loggingEnabled bool, stackOffset func(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 := 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([][]byte, 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).(*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 := 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, []SlotID{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 !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 !IsVarWantedForDebug(inp.Name) { continue } sl := rval.Slots[pidx] n := rval.Vars[pidx] 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, rval.LocationLists[pidx], BlockStart.ID, afterPrologVal) if list == nil { pidx++ continue } if loggingEnabled { state.logf("param %v:\n [, %d]:\n", n, afterPrologVal) } 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 = 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, ): stackOffset=%d\n", afterPrologVal, soff) } // fill in size ctxt.Arch.ByteOrder.PutUint16(list[sizeIdx:], uint16(len(list)-sizeIdx-2)) rval.LocationLists[pidx] = list pidx++ } } // IsVarWantedForDebug returns true if the debug info for the node should // be generated. // For example, internal variables for range-over-func loops have little // value to users, so we don't generate debug info for them. func IsVarWantedForDebug(n ir.Node) bool { name := n.Sym().Name if len(name) > 0 && name[0] == '&' { name = name[1:] } if len(name) > 0 && name[0] == '#' { // #yield is used by delve. return strings.HasPrefix(name, "#yield") } return true }