github.com/Filosottile/go@v0.0.0-20170906193555-dbed9972d994/src/cmd/compile/internal/ssa/debug.go (about) 1 // Copyright 2017 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 package ssa 5 6 import ( 7 "cmd/internal/obj" 8 "fmt" 9 "strings" 10 ) 11 12 type SlotID int32 13 14 // A FuncDebug contains all the debug information for the variables in a 15 // function. Variables are identified by their LocalSlot, which may be the 16 // result of decomposing a larger variable. 17 type FuncDebug struct { 18 Slots []*LocalSlot 19 Variables []VarLocList 20 Registers []Register 21 } 22 23 // append adds a location to the location list for slot. 24 func (f *FuncDebug) append(slot SlotID, loc *VarLoc) { 25 f.Variables[slot].append(loc) 26 } 27 28 // lastLoc returns the last VarLoc for slot, or nil if it has none. 29 func (f *FuncDebug) lastLoc(slot SlotID) *VarLoc { 30 return f.Variables[slot].last() 31 } 32 33 func (f *FuncDebug) String() string { 34 var vars []string 35 for slot, list := range f.Variables { 36 if len(list.Locations) == 0 { 37 continue 38 } 39 vars = append(vars, fmt.Sprintf("%v = %v", f.Slots[slot], list)) 40 } 41 return fmt.Sprintf("{%v}", strings.Join(vars, ", ")) 42 } 43 44 // A VarLocList contains the locations for a variable, in program text order. 45 // It will often have gaps. 46 type VarLocList struct { 47 Locations []*VarLoc 48 } 49 50 func (l *VarLocList) append(loc *VarLoc) { 51 l.Locations = append(l.Locations, loc) 52 } 53 54 // last returns the last location in the list. 55 func (l *VarLocList) last() *VarLoc { 56 if l == nil || len(l.Locations) == 0 { 57 return nil 58 } 59 return l.Locations[len(l.Locations)-1] 60 } 61 62 // A VarLoc describes a variable's location in a single contiguous range 63 // of program text. It is generated from the SSA representation, but it 64 // refers to the generated machine code, so the Values referenced are better 65 // understood as PCs than actual Values, and the ranges can cross blocks. 66 // The range is defined first by Values, which are then mapped to Progs 67 // during genssa and finally to function PCs after assembly. 68 // A variable can be on the stack and in any number of registers. 69 type VarLoc struct { 70 // Inclusive -- the first SSA value that the range covers. The value 71 // doesn't necessarily have anything to do with the variable; it just 72 // identifies a point in the program text. 73 Start *Value 74 // Exclusive -- the first SSA value after start that the range doesn't 75 // cover. A location with start == end is empty. 76 End *Value 77 // The prog/PCs corresponding to Start and End above. These are for the 78 // convenience of later passes, since code generation isn't done when 79 // BuildFuncDebug runs. 80 StartProg, EndProg *obj.Prog 81 StartPC, EndPC int64 82 83 // The registers this variable is available in. There can be more than 84 // one in various situations, e.g. it's being moved between registers. 85 Registers RegisterSet 86 // Indicates whether the variable is on the stack. The stack position is 87 // stored in the associated gc.Node. 88 OnStack bool 89 90 // Used only during generation. Indicates whether this location lasts 91 // past the block's end. Without this, there would be no way to distinguish 92 // between a range that ended on the last Value of a block and one that 93 // didn't end at all. 94 survivedBlock bool 95 } 96 97 // RegisterSet is a bitmap of registers, indexed by Register.num. 98 type RegisterSet uint64 99 100 func (v *VarLoc) String() string { 101 var registers []Register 102 if v.Start != nil { 103 registers = v.Start.Block.Func.Config.registers 104 } 105 loc := "" 106 if !v.OnStack && v.Registers == 0 { 107 loc = "!!!no location!!!" 108 } 109 if v.OnStack { 110 loc += "stack," 111 } 112 var regnames []string 113 for reg := 0; reg < 64; reg++ { 114 if v.Registers&(1<<uint8(reg)) == 0 { 115 continue 116 } 117 if registers != nil { 118 regnames = append(regnames, registers[reg].String()) 119 } else { 120 regnames = append(regnames, fmt.Sprintf("reg%d", reg)) 121 } 122 } 123 loc += strings.Join(regnames, ",") 124 pos := func(v *Value, p *obj.Prog, pc int64) string { 125 if v == nil { 126 return "?" 127 } 128 if p == nil { 129 return fmt.Sprintf("v%v", v.ID) 130 } 131 return fmt.Sprintf("v%v/%x", v.ID, pc) 132 } 133 surv := "" 134 if v.survivedBlock { 135 surv = "+" 136 } 137 return fmt.Sprintf("%v-%v%s@%s", pos(v.Start, v.StartProg, v.StartPC), pos(v.End, v.EndProg, v.EndPC), surv, loc) 138 } 139 140 // unexpected is used to indicate an inconsistency or bug in the debug info 141 // generation process. These are not fixable by users. At time of writing, 142 // changing this to a Fprintf(os.Stderr) and running make.bash generates 143 // thousands of warnings. 144 func (s *debugState) unexpected(v *Value, msg string, args ...interface{}) { 145 s.f.Logf("unexpected at "+fmt.Sprint(v.ID)+":"+msg, args...) 146 } 147 148 func (s *debugState) logf(msg string, args ...interface{}) { 149 s.f.Logf(msg, args...) 150 } 151 152 type debugState struct { 153 loggingEnabled bool 154 slots []*LocalSlot 155 f *Func 156 cache *Cache 157 numRegisters int 158 159 // working storage for BuildFuncDebug, reused between blocks. 160 registerContents [][]SlotID 161 } 162 163 // BuildFuncDebug returns debug information for f. 164 // f must be fully processed, so that each Value is where it will be when 165 // machine code is emitted. 166 func BuildFuncDebug(f *Func, loggingEnabled bool) *FuncDebug { 167 if f.RegAlloc == nil { 168 f.Fatalf("BuildFuncDebug on func %v that has not been fully processed", f) 169 } 170 state := &debugState{ 171 loggingEnabled: loggingEnabled, 172 slots: make([]*LocalSlot, len(f.Names)), 173 cache: f.Cache, 174 f: f, 175 numRegisters: len(f.Config.registers), 176 registerContents: make([][]SlotID, len(f.Config.registers)), 177 } 178 // TODO: consider storing this in Cache and reusing across functions. 179 valueNames := make([][]SlotID, f.NumValues()) 180 181 for i, slot := range f.Names { 182 slot := slot 183 state.slots[i] = &slot 184 185 if isSynthetic(&slot) { 186 continue 187 } 188 for _, value := range f.NamedValues[slot] { 189 valueNames[value.ID] = append(valueNames[value.ID], SlotID(i)) 190 } 191 } 192 193 if state.loggingEnabled { 194 var names []string 195 for i, name := range f.Names { 196 names = append(names, fmt.Sprintf("%d = %s", i, name)) 197 } 198 state.logf("Name table: %v\n", strings.Join(names, ", ")) 199 } 200 201 // Build up block states, starting with the first block, then 202 // processing blocks once their predecessors have been processed. 203 204 // TODO: use a reverse post-order traversal instead of the work queue. 205 206 // Location list entries for each block. 207 blockLocs := make([]*FuncDebug, f.NumBlocks()) 208 209 // Work queue of blocks to visit. Some of them may already be processed. 210 work := []*Block{f.Entry} 211 212 for len(work) > 0 { 213 b := work[0] 214 work = work[1:] 215 if blockLocs[b.ID] != nil { 216 continue // already processed 217 } 218 if !state.predecessorsDone(b, blockLocs) { 219 continue // not ready yet 220 } 221 222 for _, edge := range b.Succs { 223 if blockLocs[edge.Block().ID] != nil { 224 continue 225 } 226 work = append(work, edge.Block()) 227 } 228 229 // Build the starting state for the block from the final 230 // state of its predecessors. 231 locs := state.mergePredecessors(b, blockLocs) 232 if state.loggingEnabled { 233 state.logf("Processing %v, initial locs %v, regs %v\n", b, locs, state.registerContents) 234 } 235 // Update locs/registers with the effects of each Value. 236 for _, v := range b.Values { 237 slots := valueNames[v.ID] 238 239 // Loads and stores inherit the names of their sources. 240 var source *Value 241 switch v.Op { 242 case OpStoreReg: 243 source = v.Args[0] 244 case OpLoadReg: 245 switch a := v.Args[0]; a.Op { 246 case OpArg: 247 source = a 248 case OpStoreReg: 249 source = a.Args[0] 250 default: 251 state.unexpected(v, "load with unexpected source op %v", a) 252 } 253 } 254 if source != nil { 255 slots = append(slots, valueNames[source.ID]...) 256 // As of writing, the compiler never uses a load/store as a 257 // source of another load/store, so there's no reason this should 258 // ever be consulted. Update just in case, and so that when 259 // valueNames is cached, we can reuse the memory. 260 valueNames[v.ID] = slots 261 } 262 263 if len(slots) == 0 { 264 continue 265 } 266 267 reg, _ := f.getHome(v.ID).(*Register) 268 state.processValue(locs, v, slots, reg) 269 270 } 271 272 // The block is done; end the locations for all its slots. 273 for _, locList := range locs.Variables { 274 last := locList.last() 275 if last == nil || last.End != nil { 276 continue 277 } 278 if len(b.Values) != 0 { 279 last.End = b.Values[len(b.Values)-1] 280 } else { 281 // This happens when a value survives into an empty block from its predecessor. 282 // Just carry it forward for liveness's sake. 283 last.End = last.Start 284 } 285 last.survivedBlock = true 286 } 287 if state.loggingEnabled { 288 f.Logf("Block done: locs %v, regs %v. work = %+v\n", locs, state.registerContents, work) 289 } 290 blockLocs[b.ID] = locs 291 } 292 293 // Build the complete debug info by concatenating each of the blocks' 294 // locations together. 295 info := &FuncDebug{ 296 Variables: make([]VarLocList, len(state.slots)), 297 Slots: state.slots, 298 Registers: f.Config.registers, 299 } 300 for _, b := range f.Blocks { 301 // Ignore empty blocks; there will be some records for liveness 302 // but they're all useless. 303 if len(b.Values) == 0 { 304 continue 305 } 306 if blockLocs[b.ID] == nil { 307 state.unexpected(b.Values[0], "Never processed block %v\n", b) 308 continue 309 } 310 for slot, blockLocList := range blockLocs[b.ID].Variables { 311 for _, loc := range blockLocList.Locations { 312 if !loc.OnStack && loc.Registers == 0 { 313 state.unexpected(loc.Start, "Location for %v with no storage: %+v\n", state.slots[slot], loc) 314 continue // don't confuse downstream with our bugs 315 } 316 if loc.Start == nil || loc.End == nil { 317 state.unexpected(b.Values[0], "Location for %v missing start or end: %v\n", state.slots[slot], loc) 318 continue 319 } 320 info.append(SlotID(slot), loc) 321 } 322 } 323 } 324 if state.loggingEnabled { 325 f.Logf("Final result:\n") 326 for slot, locList := range info.Variables { 327 f.Logf("\t%v => %v\n", state.slots[slot], locList) 328 } 329 } 330 return info 331 } 332 333 // isSynthetic reports whether if slot represents a compiler-inserted variable, 334 // e.g. an autotmp or an anonymous return value that needed a stack slot. 335 func isSynthetic(slot *LocalSlot) bool { 336 c := slot.String()[0] 337 return c == '.' || c == '~' 338 } 339 340 // predecessorsDone reports whether block is ready to be processed. 341 func (state *debugState) predecessorsDone(b *Block, blockLocs []*FuncDebug) bool { 342 f := b.Func 343 for _, edge := range b.Preds { 344 // Ignore back branches, e.g. the continuation of a for loop. 345 // This may not work for functions with mutual gotos, which are not 346 // reducible, in which case debug information will be missing for any 347 // code after that point in the control flow. 348 if f.sdom().isAncestorEq(b, edge.b) { 349 if state.loggingEnabled { 350 f.Logf("ignoring back branch from %v to %v\n", edge.b, b) 351 } 352 continue // back branch 353 } 354 if blockLocs[edge.b.ID] == nil { 355 if state.loggingEnabled { 356 f.Logf("%v is not ready because %v isn't done\n", b, edge.b) 357 } 358 return false 359 } 360 } 361 return true 362 } 363 364 // mergePredecessors takes the end state of each of b's predecessors and 365 // intersects them to form the starting state for b. 366 // The registers slice (the second return value) will be reused for each call to mergePredecessors. 367 func (state *debugState) mergePredecessors(b *Block, blockLocs []*FuncDebug) *FuncDebug { 368 live := make([]VarLocList, len(state.slots)) 369 370 // Filter out back branches. 371 var preds []*Block 372 for _, pred := range b.Preds { 373 if blockLocs[pred.b.ID] != nil { 374 preds = append(preds, pred.b) 375 } 376 } 377 378 if len(preds) > 0 { 379 p := preds[0] 380 for slot, locList := range blockLocs[p.ID].Variables { 381 last := locList.last() 382 if last == nil || !last.survivedBlock { 383 continue 384 } 385 // If this block is empty, carry forward the end value for liveness. 386 // It'll be ignored later. 387 start := last.End 388 if len(b.Values) != 0 { 389 start = b.Values[0] 390 } 391 loc := state.cache.NewVarLoc() 392 loc.Start = start 393 loc.OnStack = last.OnStack 394 loc.Registers = last.Registers 395 live[slot].append(loc) 396 } 397 } 398 if state.loggingEnabled && len(b.Preds) > 1 { 399 state.logf("Starting merge with state from %v: %v\n", b.Preds[0].b, blockLocs[b.Preds[0].b.ID]) 400 } 401 for i := 1; i < len(preds); i++ { 402 p := preds[i] 403 if state.loggingEnabled { 404 state.logf("Merging in state from %v: %v &= %v\n", p, live, blockLocs[p.ID]) 405 } 406 407 for slot, liveVar := range live { 408 liveLoc := liveVar.last() 409 if liveLoc == nil { 410 continue 411 } 412 413 predLoc := blockLocs[p.ID].lastLoc(SlotID(slot)) 414 // Clear out slots missing/dead in p. 415 if predLoc == nil || !predLoc.survivedBlock { 416 live[slot].Locations = nil 417 continue 418 } 419 420 // Unify storage locations. 421 liveLoc.OnStack = liveLoc.OnStack && predLoc.OnStack 422 liveLoc.Registers &= predLoc.Registers 423 } 424 } 425 426 // Create final result. 427 locs := &FuncDebug{Variables: live, Slots: state.slots} 428 for reg := range state.registerContents { 429 state.registerContents[reg] = state.registerContents[reg][:0] 430 } 431 for slot, locList := range live { 432 loc := locList.last() 433 if loc == nil { 434 continue 435 } 436 for reg := 0; reg < state.numRegisters; reg++ { 437 if loc.Registers&(1<<uint8(reg)) != 0 { 438 state.registerContents[reg] = append(state.registerContents[reg], SlotID(slot)) 439 } 440 } 441 } 442 return locs 443 } 444 445 // processValue updates locs and state.registerContents to reflect v, a value with 446 // the names in vSlots and homed in vReg. 447 func (state *debugState) processValue(locs *FuncDebug, v *Value, vSlots []SlotID, vReg *Register) { 448 switch { 449 case v.Op == OpRegKill: 450 if state.loggingEnabled { 451 existingSlots := make([]bool, len(state.slots)) 452 for _, slot := range state.registerContents[vReg.num] { 453 existingSlots[slot] = true 454 } 455 for _, slot := range vSlots { 456 if existingSlots[slot] { 457 existingSlots[slot] = false 458 } else { 459 state.unexpected(v, "regkill of unassociated name %v\n", state.slots[slot]) 460 } 461 } 462 for slot, live := range existingSlots { 463 if live { 464 state.unexpected(v, "leftover register name: %v\n", state.slots[slot]) 465 } 466 } 467 } 468 state.registerContents[vReg.num] = nil 469 470 for _, slot := range vSlots { 471 last := locs.lastLoc(slot) 472 if last == nil { 473 state.unexpected(v, "regkill of already dead %s, %+v\n", vReg, state.slots[slot]) 474 continue 475 } 476 if state.loggingEnabled { 477 state.logf("at %v: %v regkilled out of %s\n", v.ID, state.slots[slot], vReg) 478 } 479 if last.End != nil { 480 state.unexpected(v, "regkill of dead slot, died at %v\n", last.End) 481 } 482 last.End = v 483 484 regs := last.Registers &^ (1 << uint8(vReg.num)) 485 if !last.OnStack && regs == 0 { 486 continue 487 } 488 loc := state.cache.NewVarLoc() 489 loc.Start = v 490 loc.OnStack = last.OnStack 491 loc.Registers = regs 492 locs.append(slot, loc) 493 } 494 case v.Op == OpArg: 495 for _, slot := range vSlots { 496 if state.loggingEnabled { 497 state.logf("at %v: %v now on stack from arg\n", v.ID, state.slots[slot]) 498 } 499 loc := state.cache.NewVarLoc() 500 loc.Start = v 501 loc.OnStack = true 502 locs.append(slot, loc) 503 } 504 505 case v.Op == OpStoreReg: 506 for _, slot := range vSlots { 507 if state.loggingEnabled { 508 state.logf("at %v: %v spilled to stack\n", v.ID, state.slots[slot]) 509 } 510 last := locs.lastLoc(slot) 511 if last == nil { 512 state.unexpected(v, "spill of unnamed register %s\n", vReg) 513 break 514 } 515 last.End = v 516 loc := state.cache.NewVarLoc() 517 loc.Start = v 518 loc.OnStack = true 519 loc.Registers = last.Registers 520 locs.append(slot, loc) 521 } 522 523 case vReg != nil: 524 if state.loggingEnabled { 525 newSlots := make([]bool, len(state.slots)) 526 for _, slot := range vSlots { 527 newSlots[slot] = true 528 } 529 530 for _, slot := range state.registerContents[vReg.num] { 531 if !newSlots[slot] { 532 state.unexpected(v, "%v clobbered\n", state.slots[slot]) 533 } 534 } 535 } 536 537 for _, slot := range vSlots { 538 if state.loggingEnabled { 539 state.logf("at %v: %v now in %s\n", v.ID, state.slots[slot], vReg) 540 } 541 last := locs.lastLoc(slot) 542 if last != nil && last.End == nil { 543 last.End = v 544 } 545 state.registerContents[vReg.num] = append(state.registerContents[vReg.num], slot) 546 loc := state.cache.NewVarLoc() 547 loc.Start = v 548 if last != nil { 549 loc.OnStack = last.OnStack 550 loc.Registers = last.Registers 551 } 552 loc.Registers |= 1 << uint8(vReg.num) 553 locs.append(slot, loc) 554 } 555 default: 556 state.unexpected(v, "named value with no reg\n") 557 } 558 559 }