// 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 { // The SSA block that this tracks. For debug logging only. Block *Block // State at entry to the block. Both this and endState are immutable // once initialized. startState []liveSlot // State at the end of the block if it's fully processed. endState []liveSlot } // A liveSlot is a slot that's live in loc at entry/exit of a block. type liveSlot struct { slot SlotID loc VarLoc } // 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] = live.loc if live.loc.Registers == 0 { continue } for reg, regMask := 0, 1; reg < len(registers); reg, regMask = reg+1, regMask<<1 { if live.loc.Registers&RegisterSet(regMask) != 0 { registers[reg] = append(registers[reg], SlotID(live.slot)) } } } state.slots, state.registers = slots, registers } func (b *BlockDebug) LocString(loc VarLoc) string { if loc.absent() { return "" } registers := b.Block.Func.Config.registers var storage []string if loc.OnStack { storage = append(storage, "stack") } for reg := 0; reg < 64; reg++ { if loc.Registers&(1<slot mappings. state.varSlots = make([][]SlotID, len(state.vars)) state.slotVars = make([]VarID, len(state.slots)) for varID, n := range state.vars { parts := varParts[n] state.varSlots[varID] = parts for _, slotID := range parts { state.slotVars[slotID] = VarID(varID) } sort.Sort(partsByVarOffset{parts, state.slots}) } state.initializeCache() for i, slot := range f.Names { if isSynthetic(&slot) { continue } for _, value := range f.NamedValues[slot] { state.valueNames[value.ID] = append(state.valueNames[value.ID], SlotID(i)) } } blockLocs := state.liveness() lists := state.buildLocationLists(ctxt, stackOffset, blockLocs) return &FuncDebug{ Slots: state.slots, VarSlots: state.varSlots, Vars: state.vars, LocationLists: lists, } } // isSynthetic reports whether if slot represents a compiler-inserted variable, // e.g. an autotmp or an anonymous return value that needed a stack slot. func isSynthetic(slot *LocalSlot) bool { c := slot.N.String()[0] return c == '.' || c == '~' } // 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. locs := state.mergePredecessors(b, blockLocs) changed := false if state.loggingEnabled { state.logf("Processing %v, initial state:\n%v", b, state.stateString(locs, 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(locs, state.currentState)) } if !changed { locs.endState = locs.startState } else { for slotID, slotLoc := range state.currentState.slots { if slotLoc.absent() { continue } state.cache.AppendLiveSlot(liveSlot{SlotID(slotID), slotLoc}) } locs.endState = state.cache.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) *BlockDebug { result := state.allocBlock(b) if state.loggingEnabled { result.Block = b } // Filter out back branches. var preds []*Block for _, pred := range b.Preds { if blockLocs[pred.b.ID] != nil { preds = append(preds, pred.b) } } if state.loggingEnabled { state.logf("Merging %v into %v\n", preds, b) } if len(preds) == 0 { if state.loggingEnabled { } state.currentState.reset(nil) return result } p0 := blockLocs[preds[0].ID].endState if len(preds) == 1 { result.startState = p0 state.currentState.reset(p0) return result } 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] = predSlot.loc 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.loc.OnStack || liveLoc.StackOffset != predSlot.loc.StackOffset { liveLoc.OnStack = false liveLoc.StackOffset = 0 } liveLoc.Registers &= predSlot.loc.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] != predSlot.loc { unchanged = false break } } if unchanged { if state.loggingEnabled { state.logf("After merge, %v matches %v exactly.\n", b, preds[0]) } result.startState = p0 state.currentState.reset(p0) return result } 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 slotID, slotLoc := range slotLocs { // Not seen in any predecessor. if slotLoc.absent() { continue } // Seen in only some predecessors. Clear it out. if state.liveCount[slotID] != len(preds) { slotLocs[slotID] = VarLoc{} continue } // Present in all predecessors. state.cache.AppendLiveSlot(liveSlot{SlotID(slotID), slotLoc}) if slotLoc.Registers == 0 { continue } for reg, regMask := 0, 1; reg < len(state.registers); reg, regMask = reg+1, regMask<<1 { if slotLoc.Registers&RegisterSet(regMask) != 0 { state.currentState.registers[reg] = append(state.currentState.registers[reg], SlotID(slotID)) } } } result.startState = state.cache.GetLiveSlotSlice() return result } // 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[state.slotVars[slot]] = true 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. if clobbers := opcodeTable[v.Op].reg.clobbers; clobbers != 0 { for reg := 0; reg < len(state.registers); reg++ { if clobbers&(1<65K values, // they get incomplete debug info on 32-bit platforms. return } 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] if state.loggingEnabled { var partStrs []string for _, slot := range state.varSlots[varID] { partStrs = append(partStrs, fmt.Sprintf("%v@%v", state.slots[slot], blockLocs[endBlock].LocString(pending.pieces[slot]))) } 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 _, slotID := range state.varSlots[varID] { loc := pending.pieces[slotID] slot := state.slots[slotID] if !loc.absent() { if loc.OnStack { if loc.StackOffset == 0 { list = append(list, dwarf.DW_OP_call_frame_cfa) } else { list = append(list, dwarf.DW_OP_fbreg) list = dwarf.AppendSleb128(list, int64(loc.StackOffset)) } } else { regnum := 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())) } } Ctxt.Arch.ByteOrder.PutUint16(list[sizeIdx:], uint16(len(list)-sizeIdx-2)) lists[varID] = list } // updateVar updates the pending location list entry for varID to // reflect the new locations in curLoc, caused by v. updateVar := func(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 := &pendingEntries[varID] if empty { writePendingEntry(varID, v.Block.ID, v.ID) pending.clear() return } // Extend the previous entry if possible. if pending.present { merge := true for _, slotID := range state.varSlots[varID] { if !canMerge(pending.pieces[slotID], curLoc[slotID]) { merge = false break } } if merge { return } } writePendingEntry(varID, v.Block.ID, v.ID) pending.present = true pending.startBlock = v.Block.ID pending.startValue = v.ID copy(pending.pieces, curLoc) return } // 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 { state.currentState.reset(blockLocs[b.ID].startState) phisPending := 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 v.Op == OpPhi { if changed { phisPending = true } continue } if !changed && !phisPending { continue } phisPending = false for varID := range state.changedVars { if !state.changedVars[varID] { continue } state.changedVars[varID] = false updateVar(VarID(varID), v, state.currentState.slots) } } } if state.loggingEnabled { state.logf("location lists:\n") } // Flush any leftover entries live at the end of the last block. for varID := range lists { writePendingEntry(VarID(varID), state.f.Blocks[len(state.f.Blocks)-1].ID, BlockEnd.ID) list := lists[varID] if len(list) == 0 { continue } if state.loggingEnabled { state.logf("\t%v : %q\n", state.vars[varID], hex.EncodeToString(lists[varID])) } } return lists } // 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); { translate := func() { bv := readPtr(ctxt, list[i:]) pc := getPC(decodeValue(ctxt, bv)) writePtr(ctxt, list[i:], uint64(pc)) i += ctxt.Arch.PtrSize } translate() translate() 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(word) } // Append a pointer-sized uint to buf. func appendPtr(ctxt *obj.Link, buf []byte, word uint64) []byte { if cap(buf) < len(buf)+100 { b := make([]byte, len(buf), 100+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") } }