type SlotID … type VarID … type FuncDebug … type BlockDebug … type liveSlot … func (ls *liveSlot) String() string { … } func (ls liveSlot) absent() bool { … } type StackOffset … func (s StackOffset) onStack() bool { … } func (s StackOffset) stackOffsetValue() int32 { … } type stateAtPC … // reset fills state with the live variables from live. func (state *stateAtPC) reset(live abt.T) { … } func (s *debugState) LocString(loc VarLoc) string { … } type VarLoc … func (loc VarLoc) absent() bool { … } func (loc VarLoc) intersect(other VarLoc) VarLoc { … } var BlockStart … var BlockEnd … var FuncEnd … type RegisterSet … // 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{ … } type debugState … func (state *debugState) initializeCache(f *Func, numVars, numSlots int) { … } func (state *debugState) allocBlock(b *Block) *BlockDebug { … } func (s *debugState) blockEndStateString(b *BlockDebug) string { … } func (s *debugState) stateString(state stateAtPC) string { … } type slotCanonicalizer … func newSlotCanonicalizer() *slotCanonicalizer { … } type SlKeyIdx … const noSlot … type slotKey … // 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) { … } func (sc *slotCanonicalizer) canonSlot(idx SlKeyIdx) LocalSlot { … } // PopulateABIInRegArgOps examines the entry block of the function // and looks for incoming parameters that have missing or partial // OpArg{Int,Float}Reg values, inserting additional values in // cases where they are missing. Example: // // func foo(s string, used int, notused int) int { // return len(s) + used // } // // In the function above, the incoming parameter "used" is fully live, // "notused" is not live, and "s" is partially live (only the length // field of the string is used). At the point where debug value // analysis runs, we might expect to see an entry block with: // // b1: // v4 = ArgIntReg <uintptr> {s+8} [0] : BX // v5 = ArgIntReg <int> {used} [0] : CX // // While this is an accurate picture of the live incoming params, // we also want to have debug locations for non-live params (or // their non-live pieces), e.g. something like // // b1: // v9 = ArgIntReg <*uint8> {s+0} [0] : AX // v4 = ArgIntReg <uintptr> {s+8} [0] : BX // v5 = ArgIntReg <int> {used} [0] : CX // v10 = ArgIntReg <int> {unused} [0] : DI // // This function examines the live OpArg{Int,Float}Reg values and // synthesizes new (dead) values for the non-live params or the // non-live pieces of partially live params. func PopulateABIInRegArgOps(f *Func) { … } // 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) { … } // liveness walks the function in control flow order, calculating the start // and end state of each block. func (state *debugState) liveness() []*BlockDebug { … } // 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) { … } // 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 { … } // varOffset returns the offset of slot within the user variable it was // decomposed from. This has nothing to do with its stack offset. func varOffset(slot LocalSlot) int64 { … } type pendingEntry … func (e *pendingEntry) clear() { … } // canMerge reports whether a new location description is a superset // of the (non-empty) pending location description, if so, the two // can be merged (i.e., pending is still a valid and useful location // description). func canMerge(pending, new VarLoc) bool { … } // firstReg returns the first register in set that is present. func firstReg(set RegisterSet) uint8 { … } // buildLocationLists builds location lists for all the user variables // in state.f, using the information about block state in blockLocs. // The returned location lists are not fully complete. They are in // terms of SSA values rather than PCs, and have no base address/end // entries. They will be finished by PutLocationList. func (state *debugState) buildLocationLists(blockLocs []*BlockDebug) { … } // 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) { … } // writePendingEntry writes out the pending entry for varID, if any, // terminated at endBlock/Value. func (state *debugState) writePendingEntry(varID VarID, endBlock, endValue ID) { … } // 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) { … } // 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) { … } // Unpack a value and block ID encoded by encodeValue. func decodeValue(ctxt *obj.Link, word uint64) (ID, ID) { … } // Append a pointer-sized uint to buf. func appendPtr(ctxt *obj.Link, buf []byte, word uint64) []byte { … } // Write a pointer-sized uint to the beginning of buf. func writePtr(ctxt *obj.Link, buf []byte, word uint64) { … } // Read a pointer-sized uint from the beginning of buf. func readPtr(ctxt *obj.Link, buf []byte) uint64 { … } // 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) { … } // 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 *Func, needCloCtx bool) (ID, *Value) { … } // 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 { … } // 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) { … } // 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 { … }