// 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/obj" "fmt" "strings" ) type SlotID 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 []*LocalSlot Variables []VarLocList Registers []Register } // append adds a location to the location list for slot. func (f *FuncDebug) append(slot SlotID, loc *VarLoc) { f.Variables[slot].append(loc) } // lastLoc returns the last VarLoc for slot, or nil if it has none. func (f *FuncDebug) lastLoc(slot SlotID) *VarLoc { return f.Variables[slot].last() } func (f *FuncDebug) String() string { var vars []string for slot, list := range f.Variables { if len(list.Locations) == 0 { continue } vars = append(vars, fmt.Sprintf("%v = %v", f.Slots[slot], list)) } return fmt.Sprintf("{%v}", strings.Join(vars, ", ")) } // A VarLocList contains the locations for a variable, in program text order. // It will often have gaps. type VarLocList struct { Locations []*VarLoc } func (l *VarLocList) append(loc *VarLoc) { l.Locations = append(l.Locations, loc) } // last returns the last location in the list. func (l *VarLocList) last() *VarLoc { if l == nil || len(l.Locations) == 0 { return nil } return l.Locations[len(l.Locations)-1] } // A VarLoc describes a variable's location in a single contiguous range // of program text. It is generated from the SSA representation, but it // refers to the generated machine code, so the Values referenced are better // understood as PCs than actual Values, and the ranges can cross blocks. // The range is defined first by Values, which are then mapped to Progs // during genssa and finally to function PCs after assembly. // A variable can be on the stack and in any number of registers. type VarLoc struct { // Inclusive -- the first SSA value that the range covers. The value // doesn't necessarily have anything to do with the variable; it just // identifies a point in the program text. Start *Value // Exclusive -- the first SSA value after start that the range doesn't // cover. A location with start == end is empty. End *Value // The prog/PCs corresponding to Start and End above. These are for the // convenience of later passes, since code generation isn't done when // BuildFuncDebug runs. StartProg, EndProg *obj.Prog StartPC, EndPC int64 // 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 // Indicates whether the variable is on the stack. The stack position is // stored in the associated gc.Node. OnStack bool // Used only during generation. Indicates whether this location lasts // past the block's end. Without this, there would be no way to distinguish // between a range that ended on the last Value of a block and one that // didn't end at all. survivedBlock bool } // RegisterSet is a bitmap of registers, indexed by Register.num. type RegisterSet uint64 func (v *VarLoc) String() string { var registers []Register if v.Start != nil { registers = v.Start.Block.Func.Config.registers } loc := "" if !v.OnStack && v.Registers == 0 { loc = "!!!no location!!!" } if v.OnStack { loc += "stack," } var regnames []string for reg := 0; reg < 64; reg++ { if v.Registers&(1< 0 { b := work[0] work = work[1:] if blockLocs[b.ID] != nil { continue // already processed } if !state.predecessorsDone(b, blockLocs) { continue // not ready yet } for _, edge := range b.Succs { if blockLocs[edge.Block().ID] != nil { continue } work = append(work, edge.Block()) } // Build the starting state for the block from the final // state of its predecessors. locs := state.mergePredecessors(b, blockLocs) if state.loggingEnabled { state.logf("Processing %v, initial locs %v, regs %v\n", b, locs, state.registerContents) } // Update locs/registers with the effects of each Value. for _, v := range b.Values { slots := 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) } } if source != nil { slots = append(slots, valueNames[source.ID]...) // As of writing, the compiler never uses a load/store as a // source of another load/store, so there's no reason this should // ever be consulted. Update just in case, and so that when // valueNames is cached, we can reuse the memory. valueNames[v.ID] = slots } if len(slots) == 0 { continue } reg, _ := f.getHome(v.ID).(*Register) state.processValue(locs, v, slots, reg) } // The block is done; end the locations for all its slots. for _, locList := range locs.Variables { last := locList.last() if last == nil || last.End != nil { continue } if len(b.Values) != 0 { last.End = b.Values[len(b.Values)-1] } else { // This happens when a value survives into an empty block from its predecessor. // Just carry it forward for liveness's sake. last.End = last.Start } last.survivedBlock = true } if state.loggingEnabled { f.Logf("Block done: locs %v, regs %v. work = %+v\n", locs, state.registerContents, work) } blockLocs[b.ID] = locs } // Build the complete debug info by concatenating each of the blocks' // locations together. info := &FuncDebug{ Variables: make([]VarLocList, len(state.slots)), Slots: state.slots, Registers: f.Config.registers, } for _, b := range f.Blocks { // Ignore empty blocks; there will be some records for liveness // but they're all useless. if len(b.Values) == 0 { continue } if blockLocs[b.ID] == nil { state.unexpected(b.Values[0], "Never processed block %v\n", b) continue } for slot, blockLocList := range blockLocs[b.ID].Variables { for _, loc := range blockLocList.Locations { if !loc.OnStack && loc.Registers == 0 { state.unexpected(loc.Start, "Location for %v with no storage: %+v\n", state.slots[slot], loc) continue // don't confuse downstream with our bugs } if loc.Start == nil || loc.End == nil { state.unexpected(b.Values[0], "Location for %v missing start or end: %v\n", state.slots[slot], loc) continue } info.append(SlotID(slot), loc) } } } if state.loggingEnabled { f.Logf("Final result:\n") for slot, locList := range info.Variables { f.Logf("\t%v => %v\n", state.slots[slot], locList) } } return info } // 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.Name()[0] return c == '.' || c == '~' } // predecessorsDone reports whether block is ready to be processed. func (state *debugState) predecessorsDone(b *Block, blockLocs []*FuncDebug) bool { f := b.Func for _, edge := range b.Preds { // Ignore back branches, e.g. the continuation of a for loop. // This may not work for functions with mutual gotos, which are not // reducible, in which case debug information will be missing for any // code after that point in the control flow. if f.sdom().isAncestorEq(b, edge.b) { if state.loggingEnabled { f.Logf("ignoring back branch from %v to %v\n", edge.b, b) } continue // back branch } if blockLocs[edge.b.ID] == nil { if state.loggingEnabled { f.Logf("%v is not ready because %v isn't done\n", b, edge.b) } return false } } return true } // mergePredecessors takes the end state of each of b's predecessors and // intersects them to form the starting state for b. // The registers slice (the second return value) will be reused for each call to mergePredecessors. func (state *debugState) mergePredecessors(b *Block, blockLocs []*FuncDebug) *FuncDebug { live := make([]VarLocList, len(state.slots)) // Filter out back branches. var preds []*Block for _, pred := range b.Preds { if blockLocs[pred.b.ID] != nil { preds = append(preds, pred.b) } } if len(preds) > 0 { p := preds[0] for slot, locList := range blockLocs[p.ID].Variables { last := locList.last() if last == nil || !last.survivedBlock { continue } // If this block is empty, carry forward the end value for liveness. // It'll be ignored later. start := last.End if len(b.Values) != 0 { start = b.Values[0] } loc := state.cache.NewVarLoc() loc.Start = start loc.OnStack = last.OnStack loc.Registers = last.Registers live[slot].append(loc) } } if state.loggingEnabled && len(b.Preds) > 1 { state.logf("Starting merge with state from %v: %v\n", b.Preds[0].b, blockLocs[b.Preds[0].b.ID]) } for i := 1; i < len(preds); i++ { p := preds[i] if state.loggingEnabled { state.logf("Merging in state from %v: %v &= %v\n", p, live, blockLocs[p.ID]) } for slot, liveVar := range live { liveLoc := liveVar.last() if liveLoc == nil { continue } predLoc := blockLocs[p.ID].lastLoc(SlotID(slot)) // Clear out slots missing/dead in p. if predLoc == nil || !predLoc.survivedBlock { live[slot].Locations = nil continue } // Unify storage locations. liveLoc.OnStack = liveLoc.OnStack && predLoc.OnStack liveLoc.Registers &= predLoc.Registers } } // Create final result. locs := &FuncDebug{Variables: live, Slots: state.slots} for reg := range state.registerContents { state.registerContents[reg] = state.registerContents[reg][:0] } for slot, locList := range live { loc := locList.last() if loc == nil { continue } for reg := 0; reg < state.numRegisters; reg++ { if loc.Registers&(1<