mirror of
https://github.com/golang/go.git
synced 2025-12-08 06:10:04 +00:00
The cycle hacks existed because gc needed to import ssa which need to know about gc.Node. But now that's ir.Node, and there's no cycle anymore. Don't know how much it matters but LocalSlot is now one word shorter than before, because it holds a pointer instead of an interface for the *Node. That won't last long. Now that they're not necessary for interface satisfaction, IsSynthetic and IsAutoTmp can move to top-level ir functions. Change-Id: Ie511e93466cfa2b17d9a91afc4bd8d53fdb80453 Reviewed-on: https://go-review.googlesource.com/c/go/+/272931 Trust: Russ Cox <rsc@golang.org> Run-TryBot: Russ Cox <rsc@golang.org> TryBot-Result: Go Bot <gobot@golang.org> Reviewed-by: Matthew Dempsky <mdempsky@google.com>
1188 lines
34 KiB
Go
1188 lines
34 KiB
Go
// 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/ir"
|
|
"cmd/internal/dwarf"
|
|
"cmd/internal/obj"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"math/bits"
|
|
"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 []*ir.Node
|
|
// 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(bits.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(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()
|
|
}
|
|
|
|
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
|
|
|
|
// 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.Node
|
|
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[*ir.Node][]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.loggingEnabled {
|
|
state.logf("Generating location lists for function %q\n", f.Name)
|
|
}
|
|
|
|
if state.varParts == nil {
|
|
state.varParts = make(map[*ir.Node][]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 ir.IsSynthetic(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 || v.Op == OpVarKill {
|
|
n := v.Aux.(*ir.Node)
|
|
if ir.IsSynthetic(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)]
|
|
}
|
|
|
|
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 ir.IsSynthetic(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)
|
|
|
|
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, nil)
|
|
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, OpPhi:
|
|
source = a
|
|
case OpStoreReg:
|
|
source = a.Args[0]
|
|
default:
|
|
if state.loggingEnabled {
|
|
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 {
|
|
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 puts that state in
|
|
// blockLocs, and fills state.currentState with it. If convenient, it returns
|
|
// a reused []liveSlot, true that represents the starting state.
|
|
// 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.
|
|
func (state *debugState) mergePredecessors(b *Block, blockLocs []*BlockDebug, previousBlock *Block) ([]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)
|
|
}
|
|
|
|
// TODO all the calls to this are overkill; only need to do this for slots that are not present in the merge.
|
|
markChangedVars := func(slots []liveSlot) {
|
|
for _, live := range slots {
|
|
state.changedVars.add(ID(state.slotVars[live.slot]))
|
|
}
|
|
}
|
|
|
|
if len(preds) == 0 {
|
|
if previousBlock != nil {
|
|
// Mark everything in previous block as changed because it is not a predecessor.
|
|
markChangedVars(blockLocs[previousBlock.ID].endState)
|
|
}
|
|
state.currentState.reset(nil)
|
|
return nil, true
|
|
}
|
|
|
|
p0 := blockLocs[preds[0].ID].endState
|
|
if len(preds) == 1 {
|
|
if previousBlock != nil && preds[0].ID != previousBlock.ID {
|
|
// Mark everything in previous block as changed because it is not a predecessor.
|
|
markChangedVars(blockLocs[previousBlock.ID].endState)
|
|
}
|
|
state.currentState.reset(p0)
|
|
return p0, true
|
|
}
|
|
|
|
baseID := preds[0].ID
|
|
baseState := p0
|
|
|
|
// If previous block is not a predecessor, its location information changes at boundary with this block.
|
|
previousBlockIsNotPredecessor := previousBlock != nil // If it's nil, no info to change.
|
|
|
|
if previousBlock != nil {
|
|
// Try to use previousBlock as the base state
|
|
// if possible.
|
|
for _, pred := range preds[1:] {
|
|
if pred.ID == previousBlock.ID {
|
|
baseID = pred.ID
|
|
baseState = blockLocs[pred.ID].endState
|
|
previousBlockIsNotPredecessor = false
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
if state.loggingEnabled {
|
|
state.logf("Starting %v with state from b%v:\n%v", b, baseID, state.blockEndStateString(blockLocs[baseID]))
|
|
}
|
|
|
|
slotLocs := state.currentState.slots
|
|
for _, predSlot := range baseState {
|
|
slotLocs[predSlot.slot] = VarLoc{predSlot.Registers, predSlot.StackOffset}
|
|
state.liveCount[predSlot.slot] = 1
|
|
}
|
|
for _, pred := range preds {
|
|
if pred.ID == baseID {
|
|
continue
|
|
}
|
|
if state.loggingEnabled {
|
|
state.logf("Merging in state from %v:\n%v", pred, state.blockEndStateString(blockLocs[pred.ID]))
|
|
}
|
|
for _, predSlot := range blockLocs[pred.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 baseState {
|
|
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 b%v exactly.\n", b, baseID)
|
|
}
|
|
if previousBlockIsNotPredecessor {
|
|
// Mark everything in previous block as changed because it is not a predecessor.
|
|
markChangedVars(blockLocs[previousBlock.ID].endState)
|
|
}
|
|
state.currentState.reset(baseState)
|
|
return baseState, 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 baseState {
|
|
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(bits.TrailingZeros64(mask))
|
|
mask &^= 1 << reg
|
|
state.currentState.registers[reg] = append(state.currentState.registers[reg], predSlot.slot)
|
|
}
|
|
}
|
|
|
|
if previousBlockIsNotPredecessor {
|
|
// Mark everything in previous block as changed because it is not a predecessor.
|
|
markChangedVars(blockLocs[previousBlock.ID].endState)
|
|
|
|
}
|
|
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(bits.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, 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.(*ir.Node)
|
|
if ir.IsSynthetic(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.loggingEnabled {
|
|
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.loggingEnabled {
|
|
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.loggingEnabled {
|
|
state.logf("at %v: unexpected spill of unnamed register %s\n", v, vReg)
|
|
}
|
|
break
|
|
}
|
|
|
|
setSlot(slot, VarLoc{last.Registers, StackOffset(stackOffset)})
|
|
if state.loggingEnabled {
|
|
state.logf("at %v: %v spilled to stack location %v\n", v, 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, 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[j]])
|
|
}
|
|
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 reports whether 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(bits.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.
|
|
|
|
var prevBlock *Block
|
|
for _, b := range state.f.Blocks {
|
|
state.mergePredecessors(b, blockLocs, prevBlock)
|
|
|
|
if !blockLocs[b.ID].relevant {
|
|
// Handle any differences among predecessor blocks and previous block (perhaps not a predecessor)
|
|
for _, varID := range state.changedVars.contents() {
|
|
state.updateVar(VarID(varID), b, BlockStart)
|
|
}
|
|
continue
|
|
}
|
|
|
|
zeroWidthPending := false
|
|
apcChangedSize := 0 // size of changedVars for leading Args, Phi, ClosurePtr
|
|
// expect to see values in pattern (apc)* (zerowidth|real)*
|
|
for _, v := range b.Values {
|
|
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 changed {
|
|
if v.Op == OpArg || v.Op == OpPhi || v.Op.isLoweredGetClosurePtr() {
|
|
// These ranges begin at true beginning of block, not after first instruction
|
|
if zeroWidthPending {
|
|
b.Func.Fatalf("Unexpected op mixed with OpArg/OpPhi/OpLoweredGetClosurePtr at beginning of block %s in %s\n%s", b, b.Func.Name, b.Func)
|
|
}
|
|
apcChangedSize = len(state.changedVars.contents())
|
|
continue
|
|
}
|
|
// Other zero-width ops must wait on a "real" op.
|
|
zeroWidthPending = true
|
|
}
|
|
continue
|
|
}
|
|
|
|
if !changed && !zeroWidthPending {
|
|
continue
|
|
}
|
|
// Not zero-width; i.e., a "real" instruction.
|
|
|
|
zeroWidthPending = false
|
|
for i, varID := range state.changedVars.contents() {
|
|
if i < apcChangedSize { // buffered true start-of-block changes
|
|
state.updateVar(VarID(varID), v.Block, BlockStart)
|
|
} else {
|
|
state.updateVar(VarID(varID), v.Block, v)
|
|
}
|
|
}
|
|
state.changedVars.clear()
|
|
apcChangedSize = 0
|
|
}
|
|
for i, varID := range state.changedVars.contents() {
|
|
if i < apcChangedSize { // buffered true start-of-block changes
|
|
state.updateVar(VarID(varID), b, BlockStart)
|
|
} else {
|
|
state.updateVar(VarID(varID), b, BlockEnd)
|
|
}
|
|
}
|
|
|
|
prevBlock = b
|
|
}
|
|
|
|
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 state.loggingEnabled {
|
|
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.loggingEnabled {
|
|
// Printf not logf so not gated by GOSSAFUNC; this should fire very rarely.
|
|
fmt.Printf("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.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
|
|
|
|
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 ~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")
|
|
}
|
|
|
|
}
|