| // 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/internal/dwarf" | |
| "cmd/internal/obj" | |
| "encoding/hex" | |
| "fmt" | |
| "sort" | |
| "strings" | |
| ) | |
| type SlotID int32 | |
| type VarID int32 | |
| // A FuncDebug contains all the debug information for the variables in a | |
| // function. Variables are identified by their LocalSlot, which may be the | |
| // result of decomposing a larger variable. | |
| type FuncDebug struct { | |
| // Slots is all the slots used in the debug info, indexed by their SlotID. | |
| Slots []LocalSlot | |
| // The user variables, indexed by VarID. | |
| Vars []GCNode | |
| // The slots that make up each variable, indexed by VarID. | |
| VarSlots [][]SlotID | |
| // The location list data, indexed by VarID. Must be processed by PutLocationList. | |
| LocationLists [][]byte | |
| // Filled in by the user. Translates Block and Value ID to PC. | |
| GetPC func(ID, ID) int64 | |
| } | |
| type BlockDebug struct { | |
| // Whether the block had any changes to user variables at all. | |
| relevant bool | |
| // State at the end of the block if it's fully processed. Immutable once initialized. | |
| endState []liveSlot | |
| } | |
| // A liveSlot is a slot that's live in loc at entry/exit of a block. | |
| type liveSlot struct { | |
| // An inlined VarLoc, so it packs into 16 bytes instead of 20. | |
| Registers RegisterSet | |
| StackOffset | |
| slot SlotID | |
| } | |
| func (loc liveSlot) absent() bool { | |
| return loc.Registers == 0 && !loc.onStack() | |
| } | |
| // StackOffset encodes whether a value is on the stack and if so, where. It is | |
| // a 31-bit integer followed by a presence flag at the low-order bit. | |
| type StackOffset int32 | |
| func (s StackOffset) onStack() bool { | |
| return s != 0 | |
| } | |
| func (s StackOffset) stackOffsetValue() int32 { | |
| return int32(s) >> 1 | |
| } | |
| // stateAtPC is the current state of all variables at some point. | |
| type stateAtPC struct { | |
| // The location of each known slot, indexed by SlotID. | |
| slots []VarLoc | |
| // The slots present in each register, indexed by register number. | |
| registers [][]SlotID | |
| } | |
| // reset fills state with the live variables from live. | |
| func (state *stateAtPC) reset(live []liveSlot) { | |
| slots, registers := state.slots, state.registers | |
| for i := range slots { | |
| slots[i] = VarLoc{} | |
| } | |
| for i := range registers { | |
| registers[i] = registers[i][:0] | |
| } | |
| for _, live := range live { | |
| slots[live.slot] = VarLoc{live.Registers, live.StackOffset} | |
| if live.Registers == 0 { | |
| continue | |
| } | |
| mask := uint64(live.Registers) | |
| for { | |
| if mask == 0 { | |
| break | |
| } | |
| reg := uint8(TrailingZeros64(mask)) | |
| mask &^= 1 << reg | |
| registers[reg] = append(registers[reg], live.slot) | |
| } | |
| } | |
| state.slots, state.registers = slots, registers | |
| } | |
| func (s *debugState) LocString(loc VarLoc) string { | |
| if loc.absent() { | |
| return "<nil>" | |
| } | |
| var storage []string | |
| if loc.onStack() { | |
| storage = append(storage, "stack") | |
| } | |
| mask := uint64(loc.Registers) | |
| for { | |
| if mask == 0 { | |
| break | |
| } | |
| reg := uint8(TrailingZeros64(mask)) | |
| mask &^= 1 << reg | |
| storage = append(storage, s.registers[reg].String()) | |
| } | |
| return strings.Join(storage, ",") | |
| } | |
| // A VarLoc describes the storage for part of a user variable. | |
| type VarLoc struct { | |
| // The registers this variable is available in. There can be more than | |
| // one in various situations, e.g. it's being moved between registers. | |
| Registers RegisterSet | |
| StackOffset | |
| } | |
| func (loc VarLoc) absent() bool { | |
| return loc.Registers == 0 && !loc.onStack() | |
| } | |
| var BlockStart = &Value{ | |
| ID: -10000, | |
| Op: OpInvalid, | |
| Aux: "BlockStart", | |
| } | |
| var BlockEnd = &Value{ | |
| ID: -20000, | |
| Op: OpInvalid, | |
| Aux: "BlockEnd", | |
| } | |
| // RegisterSet is a bitmap of registers, indexed by Register.num. | |
| type RegisterSet uint64 | |
| // unexpected is used to indicate an inconsistency or bug in the debug info | |
| // generation process. These are not fixable by users. At time of writing, | |
| // changing this to a Fprintf(os.Stderr) and running make.bash generates | |
| // thousands of warnings. | |
| func (s *debugState) unexpected(v *Value, msg string, args ...interface{}) { | |
| s.f.Logf("unexpected at "+fmt.Sprint(v.ID)+":"+msg, args...) | |
| } | |
| func (s *debugState) logf(msg string, args ...interface{}) { | |
| s.f.Logf(msg, args...) | |
| } | |
| type debugState struct { | |
| // See FuncDebug. | |
| slots []LocalSlot | |
| vars []GCNode | |
| varSlots [][]SlotID | |
| lists [][]byte | |
| // The user variable that each slot rolls up to, indexed by SlotID. | |
| slotVars []VarID | |
| f *Func | |
| loggingEnabled bool | |
| registers []Register | |
| stackOffset func(LocalSlot) int32 | |
| ctxt *obj.Link | |
| // The names (slots) associated with each value, indexed by Value ID. | |
| valueNames [][]SlotID | |
| // The current state of whatever analysis is running. | |
| currentState stateAtPC | |
| liveCount []int | |
| changedVars *sparseSet | |
| // The pending location list entry for each user variable, indexed by VarID. | |
| pendingEntries []pendingEntry | |
| varParts map[GCNode][]SlotID | |
| blockDebug []BlockDebug | |
| pendingSlotLocs []VarLoc | |
| liveSlots []liveSlot | |
| liveSlotSliceBegin int | |
| partsByVarOffset sort.Interface | |
| } | |
| func (state *debugState) initializeCache(f *Func, numVars, numSlots int) { | |
| // One blockDebug per block. Initialized in allocBlock. | |
| if cap(state.blockDebug) < f.NumBlocks() { | |
| state.blockDebug = make([]BlockDebug, f.NumBlocks()) | |
| } else { | |
| // This local variable, and the ones like it below, enable compiler | |
| // optimizations. Don't inline them. | |
| b := state.blockDebug[:f.NumBlocks()] | |
| for i := range b { | |
| b[i] = BlockDebug{} | |
| } | |
| } | |
| // A list of slots per Value. Reuse the previous child slices. | |
| if cap(state.valueNames) < f.NumValues() { | |
| old := state.valueNames | |
| state.valueNames = make([][]SlotID, f.NumValues()) | |
| copy(state.valueNames, old) | |
| } | |
| vn := state.valueNames[:f.NumValues()] | |
| for i := range vn { | |
| vn[i] = vn[i][:0] | |
| } | |
| // Slot and register contents for currentState. Cleared by reset(). | |
| if cap(state.currentState.slots) < numSlots { | |
| state.currentState.slots = make([]VarLoc, numSlots) | |
| } else { | |
| state.currentState.slots = state.currentState.slots[:numSlots] | |
| } | |
| if cap(state.currentState.registers) < len(state.registers) { | |
| state.currentState.registers = make([][]SlotID, len(state.registers)) | |
| } else { | |
| state.currentState.registers = state.currentState.registers[:len(state.registers)] | |
| } | |
| // Used many times by mergePredecessors. | |
| if cap(state.liveCount) < numSlots { | |
| state.liveCount = make([]int, numSlots) | |
| } else { | |
| state.liveCount = state.liveCount[:numSlots] | |
| } | |
| // A relatively small slice, but used many times as the return from processValue. | |
| state.changedVars = newSparseSet(numVars) | |
| // A pending entry per user variable, with space to track each of its pieces. | |
| numPieces := 0 | |
| for i := range state.varSlots { | |
| numPieces += len(state.varSlots[i]) | |
| } | |
| if cap(state.pendingSlotLocs) < numPieces { | |
| state.pendingSlotLocs = make([]VarLoc, numPieces) | |
| } else { | |
| psl := state.pendingSlotLocs[:numPieces] | |
| for i := range psl { | |
| psl[i] = VarLoc{} | |
| } | |
| } | |
| if cap(state.pendingEntries) < numVars { | |
| state.pendingEntries = make([]pendingEntry, numVars) | |
| } | |
| pe := state.pendingEntries[:numVars] | |
| freePieceIdx := 0 | |
| for varID, slots := range state.varSlots { | |
| pe[varID] = pendingEntry{ | |
| pieces: state.pendingSlotLocs[freePieceIdx : freePieceIdx+len(slots)], | |
| } | |
| freePieceIdx += len(slots) | |
| } | |
| state.pendingEntries = pe | |
| if cap(state.lists) < numVars { | |
| state.lists = make([][]byte, numVars) | |
| } else { | |
| state.lists = state.lists[:numVars] | |
| for i := range state.lists { | |
| state.lists[i] = nil | |
| } | |
| } | |
| state.liveSlots = state.liveSlots[:0] | |
| state.liveSlotSliceBegin = 0 | |
| } | |
| func (state *debugState) allocBlock(b *Block) *BlockDebug { | |
| return &state.blockDebug[b.ID] | |
| } | |
| func (state *debugState) appendLiveSlot(ls liveSlot) { | |
| state.liveSlots = append(state.liveSlots, ls) | |
| } | |
| func (state *debugState) getLiveSlotSlice() []liveSlot { | |
| s := state.liveSlots[state.liveSlotSliceBegin:] | |
| state.liveSlotSliceBegin = len(state.liveSlots) | |
| return s | |
| } | |
| func (s *debugState) blockEndStateString(b *BlockDebug) string { | |
| endState := stateAtPC{slots: make([]VarLoc, len(s.slots)), registers: make([][]SlotID, len(s.registers))} | |
| endState.reset(b.endState) | |
| return s.stateString(endState) | |
| } | |
| func (s *debugState) stateString(state stateAtPC) string { | |
| var strs []string | |
| for slotID, loc := range state.slots { | |
| if !loc.absent() { | |
| strs = append(strs, fmt.Sprintf("\t%v = %v\n", s.slots[slotID], s.LocString(loc))) | |
| } | |
| } | |
| strs = append(strs, "\n") | |
| for reg, slots := range state.registers { | |
| if len(slots) != 0 { | |
| var slotStrs []string | |
| for _, slot := range slots { | |
| slotStrs = append(slotStrs, s.slots[slot].String()) | |
| } | |
| strs = append(strs, fmt.Sprintf("\t%v = %v\n", &s.registers[reg], slotStrs)) | |
| } | |
| } | |
| if len(strs) == 1 { | |
| return "(no vars)\n" | |
| } | |
| return strings.Join(strs, "") | |
| } | |
| // BuildFuncDebug returns debug information for f. | |
| // f must be fully processed, so that each Value is where it will be when | |
| // machine code is emitted. | |
| func BuildFuncDebug(ctxt *obj.Link, f *Func, loggingEnabled bool, stackOffset func(LocalSlot) int32) *FuncDebug { | |
| if f.RegAlloc == nil { | |
| f.Fatalf("BuildFuncDebug on func %v that has not been fully processed", f) | |
| } | |
| state := &f.Cache.debugState | |
| state.loggingEnabled = loggingEnabled | |
| state.f = f | |
| state.registers = f.Config.registers | |
| state.stackOffset = stackOffset | |
| state.ctxt = ctxt | |
| if state.varParts == nil { | |
| state.varParts = make(map[GCNode][]SlotID) | |
| } else { | |
| for n := range state.varParts { | |
| delete(state.varParts, n) | |
| } | |
| } | |
| // Recompose any decomposed variables, and establish the canonical | |
| // IDs for each var and slot by filling out state.vars and state.slots. | |
| state.slots = state.slots[:0] | |
| state.vars = state.vars[:0] | |
| for i, slot := range f.Names { | |
| state.slots = append(state.slots, slot) | |
| if slot.N.IsSynthetic() { | |
| continue | |
| } | |
| topSlot := &slot | |
| for topSlot.SplitOf != nil { | |
| topSlot = topSlot.SplitOf | |
| } | |
| if _, ok := state.varParts[topSlot.N]; !ok { | |
| state.vars = append(state.vars, topSlot.N) | |
| } | |
| state.varParts[topSlot.N] = append(state.varParts[topSlot.N], SlotID(i)) | |
| } | |
| // Recreate the LocalSlot for each stack-only variable. | |
| // This would probably be better as an output from stackframe. | |
| for _, b := range f.Blocks { | |
| for _, v := range b.Values { | |
| if v.Op == OpVarDef || v.Op == OpVarKill { | |
| n := v.Aux.(GCNode) | |
| if n.IsSynthetic() { | |
| continue | |
| } | |
| if _, ok := state.varParts[n]; !ok { | |
| slot := LocalSlot{N: n, Type: v.Type, Off: 0} | |
| state.slots = append(state.slots, slot) | |
| state.varParts[n] = []SlotID{SlotID(len(state.slots) - 1)} | |
| state.vars = append(state.vars, n) | |
| } | |
| } | |
| } | |
| } | |
| // Fill in the var<->slot mappings. | |
| if cap(state.varSlots) < len(state.vars) { | |
| state.varSlots = make([][]SlotID, len(state.vars)) | |
| } else { | |
| state.varSlots = state.varSlots[:len(state.vars)] | |
| for i := range state.varSlots { | |
| state.varSlots[i] = state.varSlots[i][:0] | |
| } | |
| } | |
| if cap(state.slotVars) < len(state.slots) { | |
| state.slotVars = make([]VarID, len(state.slots)) | |
| } else { | |
| state.slotVars = state.slotVars[:len(state.slots)] | |
| } | |
| if state.partsByVarOffset == nil { | |
| state.partsByVarOffset = &partsByVarOffset{} | |
| } | |
| for varID, n := range state.vars { | |
| parts := state.varParts[n] | |
| state.varSlots[varID] = parts | |
| for _, slotID := range parts { | |
| state.slotVars[slotID] = VarID(varID) | |
| } | |
| *state.partsByVarOffset.(*partsByVarOffset) = partsByVarOffset{parts, state.slots} | |
| sort.Sort(state.partsByVarOffset) | |
| } | |
| state.initializeCache(f, len(state.varParts), len(state.slots)) | |
| for i, slot := range f.Names { | |
| if slot.N.IsSynthetic() { | |
| continue | |
| } | |
| for _, value := range f.NamedValues[slot] { | |
| state.valueNames[value.ID] = append(state.valueNames[value.ID], SlotID(i)) | |
| } | |
| } | |
| blockLocs := state.liveness() | |
| state.buildLocationLists(blockLocs) | |
| return &FuncDebug{ | |
| Slots: state.slots, | |
| VarSlots: state.varSlots, | |
| Vars: state.vars, | |
| LocationLists: state.lists, | |
| } | |
| } | |
| // liveness walks the function in control flow order, calculating the start | |
| // and end state of each block. | |
| func (state *debugState) liveness() []*BlockDebug { | |
| blockLocs := make([]*BlockDebug, state.f.NumBlocks()) | |
| // Reverse postorder: visit a block after as many as possible of its | |
| // predecessors have been visited. | |
| po := state.f.Postorder() | |
| for i := len(po) - 1; i >= 0; i-- { | |
| b := po[i] | |
| // Build the starting state for the block from the final | |
| // state of its predecessors. | |
| startState, startValid := state.mergePredecessors(b, blockLocs) | |
| changed := false | |
| if state.loggingEnabled { | |
| state.logf("Processing %v, initial state:\n%v", b, state.stateString(state.currentState)) | |
| } | |
| // Update locs/registers with the effects of each Value. | |
| for _, v := range b.Values { | |
| slots := state.valueNames[v.ID] | |
| // Loads and stores inherit the names of their sources. | |
| var source *Value | |
| switch v.Op { | |
| case OpStoreReg: | |
| source = v.Args[0] | |
| case OpLoadReg: | |
| switch a := v.Args[0]; a.Op { | |
| case OpArg: | |
| source = a | |
| case OpStoreReg: | |
| source = a.Args[0] | |
| default: | |
| state.unexpected(v, "load with unexpected source op %v", a) | |
| } | |
| } | |
| // Update valueNames with the source so that later steps | |
| // don't need special handling. | |
| if source != nil { | |
| slots = append(slots, state.valueNames[source.ID]...) | |
| state.valueNames[v.ID] = slots | |
| } | |
| reg, _ := state.f.getHome(v.ID).(*Register) | |
| c := state.processValue(v, slots, reg) | |
| changed = changed || c | |
| } | |
| if state.loggingEnabled { | |
| state.f.Logf("Block %v done, locs:\n%v", b, state.stateString(state.currentState)) | |
| } | |
| locs := state.allocBlock(b) | |
| locs.relevant = changed | |
| if !changed && startValid { | |
| locs.endState = startState | |
| } else { | |
| for slotID, slotLoc := range state.currentState.slots { | |
| if slotLoc.absent() { | |
| continue | |
| } | |
| state.appendLiveSlot(liveSlot{slot: SlotID(slotID), Registers: slotLoc.Registers, StackOffset: slotLoc.StackOffset}) | |
| } | |
| locs.endState = state.getLiveSlotSlice() | |
| } | |
| blockLocs[b.ID] = locs | |
| } | |
| return blockLocs | |
| } | |
| // mergePredecessors takes the end state of each of b's predecessors and | |
| // intersects them to form the starting state for b. It returns that state in | |
| // the BlockDebug, and fills state.currentState with it. | |
| func (state *debugState) mergePredecessors(b *Block, blockLocs []*BlockDebug) ([]liveSlot, bool) { | |
| // Filter out back branches. | |
| var predsBuf [10]*Block | |
| preds := predsBuf[:0] | |
| for _, pred := range b.Preds { | |
| if blockLocs[pred.b.ID] != nil { | |
| preds = append(preds, pred.b) | |
| } | |
| } | |
| if state.loggingEnabled { | |
| // The logf below would cause preds to be heap-allocated if | |
| // it were passed directly. | |
| preds2 := make([]*Block, len(preds)) | |
| copy(preds2, preds) | |
| state.logf("Merging %v into %v\n", preds2, b) | |
| } | |
| if len(preds) == 0 { | |
| if state.loggingEnabled { | |
| } | |
| state.currentState.reset(nil) | |
| return nil, true | |
| } | |
| p0 := blockLocs[preds[0].ID].endState | |
| if len(preds) == 1 { | |
| state.currentState.reset(p0) | |
| return p0, true | |
| } | |
| if state.loggingEnabled { | |
| state.logf("Starting %v with state from %v:\n%v", b, preds[0], state.blockEndStateString(blockLocs[preds[0].ID])) | |
| } | |
| slotLocs := state.currentState.slots | |
| for _, predSlot := range p0 { | |
| slotLocs[predSlot.slot] = VarLoc{predSlot.Registers, predSlot.StackOffset} | |
| state.liveCount[predSlot.slot] = 1 | |
| } | |
| for i := 1; i < len(preds); i++ { | |
| if state.loggingEnabled { | |
| state.logf("Merging in state from %v:\n%v", preds[i], state.blockEndStateString(blockLocs[preds[i].ID])) | |
| } | |
| for _, predSlot := range blockLocs[preds[i].ID].endState { | |
| state.liveCount[predSlot.slot]++ | |
| liveLoc := slotLocs[predSlot.slot] | |
| if !liveLoc.onStack() || !predSlot.onStack() || liveLoc.StackOffset != predSlot.StackOffset { | |
| liveLoc.StackOffset = 0 | |
| } | |
| liveLoc.Registers &= predSlot.Registers | |
| slotLocs[predSlot.slot] = liveLoc | |
| } | |
| } | |
| // Check if the final state is the same as the first predecessor's | |
| // final state, and reuse it if so. In principle it could match any, | |
| // but it's probably not worth checking more than the first. | |
| unchanged := true | |
| for _, predSlot := range p0 { | |
| if state.liveCount[predSlot.slot] != len(preds) || | |
| slotLocs[predSlot.slot].Registers != predSlot.Registers || | |
| slotLocs[predSlot.slot].StackOffset != predSlot.StackOffset { | |
| unchanged = false | |
| break | |
| } | |
| } | |
| if unchanged { | |
| if state.loggingEnabled { | |
| state.logf("After merge, %v matches %v exactly.\n", b, preds[0]) | |
| } | |
| state.currentState.reset(p0) | |
| return p0, true | |
| } | |
| for reg := range state.currentState.registers { | |
| state.currentState.registers[reg] = state.currentState.registers[reg][:0] | |
| } | |
| // A slot is live if it was seen in all predecessors, and they all had | |
| // some storage in common. | |
| for _, predSlot := range p0 { | |
| slotLoc := slotLocs[predSlot.slot] | |
| if state.liveCount[predSlot.slot] != len(preds) { | |
| // Seen in only some predecessors. Clear it out. | |
| slotLocs[predSlot.slot] = VarLoc{} | |
| continue | |
| } | |
| // Present in all predecessors. | |
| mask := uint64(slotLoc.Registers) | |
| for { | |
| if mask == 0 { | |
| break | |
| } | |
| reg := uint8(TrailingZeros64(mask)) | |
| mask &^= 1 << reg | |
| state.currentState.registers[reg] = append(state.currentState.registers[reg], predSlot.slot) | |
| } | |
| } | |
| return nil, false | |
| } | |
| // processValue updates locs and state.registerContents to reflect v, a value with | |
| // the names in vSlots and homed in vReg. "v" becomes visible after execution of | |
| // the instructions evaluating it. It returns which VarIDs were modified by the | |
| // Value's execution. | |
| func (state *debugState) processValue(v *Value, vSlots []SlotID, vReg *Register) bool { | |
| locs := state.currentState | |
| changed := false | |
| setSlot := func(slot SlotID, loc VarLoc) { | |
| changed = true | |
| state.changedVars.add(ID(state.slotVars[slot])) | |
| state.currentState.slots[slot] = loc | |
| } | |
| // Handle any register clobbering. Call operations, for example, | |
| // clobber all registers even though they don't explicitly write to | |
| // them. | |
| clobbers := uint64(opcodeTable[v.Op].reg.clobbers) | |
| for { | |
| if clobbers == 0 { | |
| break | |
| } | |
| reg := uint8(TrailingZeros64(clobbers)) | |
| clobbers &^= 1 << reg | |
| for _, slot := range locs.registers[reg] { | |
| if state.loggingEnabled { | |
| state.logf("at %v: %v clobbered out of %v\n", v.ID, state.slots[slot], &state.registers[reg]) | |
| } | |
| last := locs.slots[slot] | |
| if last.absent() { | |
| state.f.Fatalf("at %v: slot %v in register %v with no location entry", v, state.slots[slot], &state.registers[reg]) | |
| continue | |
| } | |
| regs := last.Registers &^ (1 << reg) | |
| setSlot(slot, VarLoc{regs, last.StackOffset}) | |
| } | |
| locs.registers[reg] = locs.registers[reg][:0] | |
| } | |
| switch { | |
| case v.Op == OpVarDef, v.Op == OpVarKill: | |
| n := v.Aux.(GCNode) | |
| if n.IsSynthetic() { | |
| break | |
| } | |
| slotID := state.varParts[n][0] | |
| var stackOffset StackOffset | |
| if v.Op == OpVarDef { | |
| stackOffset = StackOffset(state.stackOffset(state.slots[slotID])<<1 | 1) | |
| } | |
| setSlot(slotID, VarLoc{0, stackOffset}) | |
| if state.loggingEnabled { | |
| if v.Op == OpVarDef { | |
| state.logf("at %v: stack-only var %v now live\n", v.ID, state.slots[slotID]) | |
| } else { | |
| state.logf("at %v: stack-only var %v now dead\n", v.ID, state.slots[slotID]) | |
| } | |
| } | |
| case v.Op == OpArg: | |
| home := state.f.getHome(v.ID).(LocalSlot) | |
| stackOffset := state.stackOffset(home)<<1 | 1 | |
| for _, slot := range vSlots { | |
| if state.loggingEnabled { | |
| state.logf("at %v: arg %v now on stack in location %v\n", v.ID, state.slots[slot], home) | |
| if last := locs.slots[slot]; !last.absent() { | |
| state.unexpected(v, "Arg op on already-live slot %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() { | |
| state.unexpected(v, "spill of unnamed register %s\n", vReg) | |
| break | |
| } | |
| setSlot(slot, VarLoc{last.Registers, StackOffset(stackOffset)}) | |
| if state.loggingEnabled { | |
| state.logf("at %v: %v spilled to stack location %v\n", v.ID, state.slots[slot], home) | |
| } | |
| } | |
| case vReg != nil: | |
| if state.loggingEnabled { | |
| newSlots := make([]bool, len(state.slots)) | |
| for _, slot := range vSlots { | |
| newSlots[slot] = true | |
| } | |
| for _, slot := range locs.registers[vReg.num] { | |
| if !newSlots[slot] { | |
| state.logf("at %v: overwrote %v in register %v\n", v, state.slots[slot], vReg) | |
| } | |
| } | |
| } | |
| for _, slot := range locs.registers[vReg.num] { | |
| last := locs.slots[slot] | |
| setSlot(slot, VarLoc{last.Registers &^ (1 << uint8(vReg.num)), last.StackOffset}) | |
| } | |
| locs.registers[vReg.num] = locs.registers[vReg.num][:0] | |
| locs.registers[vReg.num] = append(locs.registers[vReg.num], vSlots...) | |
| for _, slot := range vSlots { | |
| if state.loggingEnabled { | |
| state.logf("at %v: %v now in %s\n", v.ID, state.slots[slot], vReg) | |
| } | |
| last := locs.slots[slot] | |
| setSlot(slot, VarLoc{1<<uint8(vReg.num) | last.Registers, last.StackOffset}) | |
| } | |
| } | |
| return changed | |
| } | |
| // varOffset returns the offset of slot within the user variable it was | |
| // decomposed from. This has nothing to do with its stack offset. | |
| func varOffset(slot LocalSlot) int64 { | |
| offset := slot.Off | |
| s := &slot | |
| for ; s.SplitOf != nil; s = s.SplitOf { | |
| offset += s.SplitOffset | |
| } | |
| return offset | |
| } | |
| type partsByVarOffset struct { | |
| slotIDs []SlotID | |
| slots []LocalSlot | |
| } | |
| func (a partsByVarOffset) Len() int { return len(a.slotIDs) } | |
| func (a partsByVarOffset) Less(i, j int) bool { | |
| return varOffset(a.slots[a.slotIDs[i]]) < varOffset(a.slots[a.slotIDs[i]]) | |
| } | |
| func (a partsByVarOffset) Swap(i, j int) { a.slotIDs[i], a.slotIDs[j] = a.slotIDs[j], a.slotIDs[i] } | |
| // A pendingEntry represents the beginning of a location list entry, missing | |
| // only its end coordinate. | |
| type pendingEntry struct { | |
| present bool | |
| startBlock, startValue ID | |
| // The location of each piece of the variable, in the same order as the | |
| // SlotIDs in varParts. | |
| pieces []VarLoc | |
| } | |
| func (e *pendingEntry) clear() { | |
| e.present = false | |
| e.startBlock = 0 | |
| e.startValue = 0 | |
| for i := range e.pieces { | |
| e.pieces[i] = VarLoc{} | |
| } | |
| } | |
| // canMerge returns true if the location description for new is the same as | |
| // pending. | |
| func canMerge(pending, new VarLoc) bool { | |
| if pending.absent() && new.absent() { | |
| return true | |
| } | |
| if pending.absent() || new.absent() { | |
| return false | |
| } | |
| if pending.onStack() { | |
| return pending.StackOffset == new.StackOffset | |
| } | |
| if pending.Registers != 0 && new.Registers != 0 { | |
| return firstReg(pending.Registers) == firstReg(new.Registers) | |
| } | |
| return false | |
| } | |
| // firstReg returns the first register in set that is present. | |
| func firstReg(set RegisterSet) uint8 { | |
| if set == 0 { | |
| // This is wrong, but there seem to be some situations where we | |
| // produce locations with no storage. | |
| return 0 | |
| } | |
| return uint8(TrailingZeros64(uint64(set))) | |
| } | |
| // buildLocationLists builds location lists for all the user variables in | |
| // state.f, using the information about block state in blockLocs. | |
| // The returned location lists are not fully complete. They are in terms of | |
| // SSA values rather than PCs, and have no base address/end entries. They will | |
| // be finished by PutLocationList. | |
| func (state *debugState) buildLocationLists(blockLocs []*BlockDebug) { | |
| // Run through the function in program text order, building up location | |
| // lists as we go. The heavy lifting has mostly already been done. | |
| for _, b := range state.f.Blocks { | |
| if !blockLocs[b.ID].relevant { | |
| continue | |
| } | |
| state.mergePredecessors(b, blockLocs) | |
| zeroWidthPending := false | |
| for _, v := range b.Values { | |
| slots := state.valueNames[v.ID] | |
| reg, _ := state.f.getHome(v.ID).(*Register) | |
| changed := state.processValue(v, slots, reg) | |
| if opcodeTable[v.Op].zeroWidth { | |
| if changed { | |
| zeroWidthPending = true | |
| } | |
| continue | |
| } | |
| if !changed && !zeroWidthPending { | |
| continue | |
| } | |
| zeroWidthPending = false | |
| for _, varID := range state.changedVars.contents() { | |
| state.updateVar(VarID(varID), v, state.currentState.slots) | |
| } | |
| state.changedVars.clear() | |
| } | |
| } | |
| if state.loggingEnabled { | |
| state.logf("location lists:\n") | |
| } | |
| // Flush any leftover entries live at the end of the last block. | |
| for varID := range state.lists { | |
| state.writePendingEntry(VarID(varID), state.f.Blocks[len(state.f.Blocks)-1].ID, BlockEnd.ID) | |
| list := state.lists[varID] | |
| if len(list) == 0 { | |
| state.logf("\t%v : empty list\n", state.vars[varID]) | |
| } | |
| if state.loggingEnabled { | |
| 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, caused by v. | |
| func (state *debugState) updateVar(varID VarID, v *Value, curLoc []VarLoc) { | |
| // 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, v.Block.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, v.Block.ID, v.ID) | |
| pending.present = true | |
| pending.startBlock = v.Block.ID | |
| pending.startValue = v.ID | |
| for i, slot := range state.varSlots[varID] { | |
| pending.pieces[i] = curLoc[slot] | |
| } | |
| return | |
| } | |
| // 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 | |
| } | |
| list := state.lists[varID] | |
| list = appendPtr(state.ctxt, list, start) | |
| list = appendPtr(state.ctxt, list, end) | |
| // Where to write the length of the location description once | |
| // we know how big it is. | |
| sizeIdx := len(list) | |
| list = list[:len(list)+2] | |
| if state.loggingEnabled { | |
| var partStrs []string | |
| for i, slot := range state.varSlots[varID] { | |
| partStrs = append(partStrs, fmt.Sprintf("%v@%v", state.slots[slot], state.LocString(pending.pieces[i]))) | |
| } | |
| state.logf("Add entry for %v: \tb%vv%v-b%vv%v = \t%v\n", state.vars[varID], pending.startBlock, pending.startValue, endBlock, endValue, strings.Join(partStrs, " ")) | |
| } | |
| for i, slotID := range state.varSlots[varID] { | |
| loc := pending.pieces[i] | |
| slot := state.slots[slotID] | |
| if !loc.absent() { | |
| if loc.onStack() { | |
| if loc.stackOffsetValue() == 0 { | |
| list = append(list, dwarf.DW_OP_call_frame_cfa) | |
| } else { | |
| list = append(list, dwarf.DW_OP_fbreg) | |
| list = dwarf.AppendSleb128(list, int64(loc.stackOffsetValue())) | |
| } | |
| } else { | |
| regnum := state.ctxt.Arch.DWARFRegisters[state.registers[firstReg(loc.Registers)].ObjNum()] | |
| if regnum < 32 { | |
| list = append(list, dwarf.DW_OP_reg0+byte(regnum)) | |
| } else { | |
| list = append(list, dwarf.DW_OP_regx) | |
| list = dwarf.AppendUleb128(list, uint64(regnum)) | |
| } | |
| } | |
| } | |
| if len(state.varSlots[varID]) > 1 { | |
| list = append(list, dwarf.DW_OP_piece) | |
| list = dwarf.AppendUleb128(list, uint64(slot.Type.Size())) | |
| } | |
| } | |
| state.ctxt.Arch.ByteOrder.PutUint16(list[sizeIdx:], uint16(len(list)-sizeIdx-2)) | |
| state.lists[varID] = list | |
| } | |
| // PutLocationList adds list (a location list in its intermediate representation) to listSym. | |
| func (debugInfo *FuncDebug) PutLocationList(list []byte, ctxt *obj.Link, listSym, startPC *obj.LSym) { | |
| getPC := debugInfo.GetPC | |
| // 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 | |
| } | |
| writePtr(ctxt, list[i:], uint64(begin)) | |
| writePtr(ctxt, list[i+ctxt.Arch.PtrSize:], uint64(end)) | |
| i += 2 * ctxt.Arch.PtrSize | |
| i += 2 + int(ctxt.Arch.ByteOrder.Uint16(list[i:])) | |
| } | |
| // Base address entry. | |
| listSym.WriteInt(ctxt, listSym.Size, ctxt.Arch.PtrSize, ^0) | |
| listSym.WriteAddr(ctxt, listSym.Size, ctxt.Arch.PtrSize, startPC, 0) | |
| // Location list contents, now with real PCs. | |
| listSym.WriteBytes(ctxt, listSym.Size, list) | |
| // 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 ~0 if they | |
| // don't fit. | |
| 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") | |
| } | |
| } |