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