lift.go (19285B)
1 // Copyright 2013 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 5 package ssa 6 7 // This file defines the lifting pass which tries to "lift" Alloc 8 // cells (new/local variables) into SSA registers, replacing loads 9 // with the dominating stored value, eliminating loads and stores, and 10 // inserting φ-nodes as needed. 11 12 // Cited papers and resources: 13 // 14 // Ron Cytron et al. 1991. Efficiently computing SSA form... 15 // http://doi.acm.org/10.1145/115372.115320 16 // 17 // Cooper, Harvey, Kennedy. 2001. A Simple, Fast Dominance Algorithm. 18 // Software Practice and Experience 2001, 4:1-10. 19 // http://www.hipersoft.rice.edu/grads/publications/dom14.pdf 20 // 21 // Daniel Berlin, llvmdev mailing list, 2012. 22 // http://lists.cs.uiuc.edu/pipermail/llvmdev/2012-January/046638.html 23 // (Be sure to expand the whole thread.) 24 25 // TODO(adonovan): opt: there are many optimizations worth evaluating, and 26 // the conventional wisdom for SSA construction is that a simple 27 // algorithm well engineered often beats those of better asymptotic 28 // complexity on all but the most egregious inputs. 29 // 30 // Danny Berlin suggests that the Cooper et al. algorithm for 31 // computing the dominance frontier is superior to Cytron et al. 32 // Furthermore he recommends that rather than computing the DF for the 33 // whole function then renaming all alloc cells, it may be cheaper to 34 // compute the DF for each alloc cell separately and throw it away. 35 // 36 // Consider exploiting liveness information to avoid creating dead 37 // φ-nodes which we then immediately remove. 38 // 39 // Also see many other "TODO: opt" suggestions in the code. 40 41 import ( 42 "fmt" 43 "go/token" 44 "math/big" 45 "os" 46 "slices" 47 48 "golang.org/x/tools/internal/typeparams" 49 ) 50 51 // If true, show diagnostic information at each step of lifting. 52 // Very verbose. 53 const debugLifting = false 54 55 // domFrontier maps each block to the set of blocks in its dominance 56 // frontier. The outer slice is conceptually a map keyed by 57 // Block.Index. The inner slice is conceptually a set, possibly 58 // containing duplicates. 59 // 60 // TODO(adonovan): opt: measure impact of dups; consider a packed bit 61 // representation, e.g. big.Int, and bitwise parallel operations for 62 // the union step in the Children loop. 63 // 64 // domFrontier's methods mutate the slice's elements but not its 65 // length, so their receivers needn't be pointers. 66 type domFrontier [][]*BasicBlock 67 68 func (df domFrontier) add(u, v *BasicBlock) { 69 p := &df[u.Index] 70 *p = append(*p, v) 71 } 72 73 // build builds the dominance frontier df for the dominator (sub)tree 74 // rooted at u, using the Cytron et al. algorithm. 75 // 76 // TODO(adonovan): opt: consider Berlin approach, computing pruned SSA 77 // by pruning the entire IDF computation, rather than merely pruning 78 // the DF -> IDF step. 79 func (df domFrontier) build(u *BasicBlock) { 80 // Encounter each node u in postorder of dom tree. 81 for _, child := range u.dom.children { 82 df.build(child) 83 } 84 for _, vb := range u.Succs { 85 if v := vb.dom; v.idom != u { 86 df.add(u, vb) 87 } 88 } 89 for _, w := range u.dom.children { 90 for _, vb := range df[w.Index] { 91 // TODO(adonovan): opt: use word-parallel bitwise union. 92 if v := vb.dom; v.idom != u { 93 df.add(u, vb) 94 } 95 } 96 } 97 } 98 99 func buildDomFrontier(fn *Function) domFrontier { 100 df := make(domFrontier, len(fn.Blocks)) 101 df.build(fn.Blocks[0]) 102 if fn.Recover != nil { 103 df.build(fn.Recover) 104 } 105 return df 106 } 107 108 func removeInstr(refs []Instruction, instr Instruction) []Instruction { 109 return slices.DeleteFunc(refs, func(i Instruction) bool { return i == instr }) 110 } 111 112 // lift replaces local and new Allocs accessed only with 113 // load/store by SSA registers, inserting φ-nodes where necessary. 114 // The result is a program in classical pruned SSA form. 115 // 116 // Preconditions: 117 // - fn has no dead blocks (blockopt has run). 118 // - Def/use info (Operands and Referrers) is up-to-date. 119 // - The dominator tree is up-to-date. 120 func lift(fn *Function) { 121 // TODO(adonovan): opt: lots of little optimizations may be 122 // worthwhile here, especially if they cause us to avoid 123 // buildDomFrontier. For example: 124 // 125 // - Alloc never loaded? Eliminate. 126 // - Alloc never stored? Replace all loads with a zero constant. 127 // - Alloc stored once? Replace loads with dominating store; 128 // don't forget that an Alloc is itself an effective store 129 // of zero. 130 // - Alloc used only within a single block? 131 // Use degenerate algorithm avoiding φ-nodes. 132 // - Consider synergy with scalar replacement of aggregates (SRA). 133 // e.g. *(&x.f) where x is an Alloc. 134 // Perhaps we'd get better results if we generated this as x.f 135 // i.e. Field(x, .f) instead of Load(FieldIndex(x, .f)). 136 // Unclear. 137 // 138 // But we will start with the simplest correct code. 139 df := buildDomFrontier(fn) 140 141 if debugLifting { 142 title := false 143 for i, blocks := range df { 144 if blocks != nil { 145 if !title { 146 fmt.Fprintf(os.Stderr, "Dominance frontier of %s:\n", fn) 147 title = true 148 } 149 fmt.Fprintf(os.Stderr, "\t%s: %s\n", fn.Blocks[i], blocks) 150 } 151 } 152 } 153 154 newPhis := make(newPhiMap) 155 156 // During this pass we will replace some BasicBlock.Instrs 157 // (allocs, loads and stores) with nil, keeping a count in 158 // BasicBlock.gaps. At the end we will reset Instrs to the 159 // concatenation of all non-dead newPhis and non-nil Instrs 160 // for the block, reusing the original array if space permits. 161 162 // While we're here, we also eliminate 'rundefers' 163 // instructions and ssa:deferstack() in functions that contain no 164 // 'defer' instructions. For now, we also eliminate 165 // 's = ssa:deferstack()' calls if s doesn't escape, replacing s 166 // with nil in Defer{DeferStack: s}. This has the same meaning, 167 // but allows eliminating the intrinsic function `ssa:deferstack()` 168 // (unless it is needed due to range-over-func instances). This gives 169 // ssa users more time to support range-over-func. 170 usesDefer := false 171 deferstackAlloc, deferstackCall := deferstackPreamble(fn) 172 eliminateDeferStack := deferstackAlloc != nil && !deferstackAlloc.Heap 173 174 // A counter used to generate ~unique ids for Phi nodes, as an 175 // aid to debugging. We use large numbers to make them highly 176 // visible. All nodes are renumbered later. 177 fresh := 1000 178 179 // Determine which allocs we can lift and number them densely. 180 // The renaming phase uses this numbering for compact maps. 181 numAllocs := 0 182 for _, b := range fn.Blocks { 183 b.gaps = 0 184 b.rundefers = 0 185 for _, instr := range b.Instrs { 186 switch instr := instr.(type) { 187 case *Alloc: 188 index := -1 189 if liftAlloc(df, instr, newPhis, &fresh) { 190 index = numAllocs 191 numAllocs++ 192 } 193 instr.index = index 194 case *Defer: 195 usesDefer = true 196 if eliminateDeferStack { 197 // Clear DeferStack and remove references to loads 198 if instr.DeferStack != nil { 199 if refs := instr.DeferStack.Referrers(); refs != nil { 200 *refs = removeInstr(*refs, instr) 201 } 202 instr.DeferStack = nil 203 } 204 } 205 case *RunDefers: 206 b.rundefers++ 207 } 208 } 209 } 210 211 // renaming maps an alloc (keyed by index) to its replacement 212 // value. Initially the renaming contains nil, signifying the 213 // zero constant of the appropriate type; we construct the 214 // Const lazily at most once on each path through the domtree. 215 // TODO(adonovan): opt: cache per-function not per subtree. 216 renaming := make([]Value, numAllocs) 217 218 // Renaming. 219 rename(fn.Blocks[0], renaming, newPhis) 220 221 // Eliminate dead φ-nodes. 222 removeDeadPhis(fn.Blocks, newPhis) 223 224 // Eliminate ssa:deferstack() call. 225 if eliminateDeferStack { 226 b := deferstackCall.block 227 for i, instr := range b.Instrs { 228 if instr == deferstackCall { 229 b.Instrs[i] = nil 230 b.gaps++ 231 break 232 } 233 } 234 } 235 236 // Prepend remaining live φ-nodes to each block. 237 for _, b := range fn.Blocks { 238 nps := newPhis[b] 239 j := len(nps) 240 241 rundefersToKill := b.rundefers 242 if usesDefer { 243 rundefersToKill = 0 244 } 245 246 if j+b.gaps+rundefersToKill == 0 { 247 continue // fast path: no new phis or gaps 248 } 249 250 // Compact nps + non-nil Instrs into a new slice. 251 // TODO(adonovan): opt: compact in situ (rightwards) 252 // if Instrs has sufficient space or slack. 253 dst := make([]Instruction, len(b.Instrs)+j-b.gaps-rundefersToKill) 254 for i, np := range nps { 255 dst[i] = np.phi 256 } 257 for _, instr := range b.Instrs { 258 if instr == nil { 259 continue 260 } 261 if !usesDefer { 262 if _, ok := instr.(*RunDefers); ok { 263 continue 264 } 265 } 266 dst[j] = instr 267 j++ 268 } 269 b.Instrs = dst 270 } 271 272 // Remove any fn.Locals that were lifted. 273 j := 0 274 for _, l := range fn.Locals { 275 if l.index < 0 { 276 fn.Locals[j] = l 277 j++ 278 } 279 } 280 // Nil out fn.Locals[j:] to aid GC. 281 for i := j; i < len(fn.Locals); i++ { 282 fn.Locals[i] = nil 283 } 284 fn.Locals = fn.Locals[:j] 285 } 286 287 // removeDeadPhis removes φ-nodes not transitively needed by a 288 // non-Phi, non-DebugRef instruction. 289 func removeDeadPhis(blocks []*BasicBlock, newPhis newPhiMap) { 290 // First pass: find the set of "live" φ-nodes: those reachable 291 // from some non-Phi instruction. 292 // 293 // We compute reachability in reverse, starting from each φ, 294 // rather than forwards, starting from each live non-Phi 295 // instruction, because this way visits much less of the 296 // Value graph. 297 livePhis := make(map[*Phi]bool) 298 for _, npList := range newPhis { 299 for _, np := range npList { 300 phi := np.phi 301 if !livePhis[phi] && phiHasDirectReferrer(phi) { 302 markLivePhi(livePhis, phi) 303 } 304 } 305 } 306 307 // Existing φ-nodes due to && and || operators 308 // are all considered live (see Go issue 19622). 309 for _, b := range blocks { 310 for _, phi := range b.phis() { 311 markLivePhi(livePhis, phi.(*Phi)) 312 } 313 } 314 315 // Second pass: eliminate unused phis from newPhis. 316 for block, npList := range newPhis { 317 j := 0 318 for _, np := range npList { 319 if livePhis[np.phi] { 320 npList[j] = np 321 j++ 322 } else { 323 // discard it, first removing it from referrers 324 for _, val := range np.phi.Edges { 325 if refs := val.Referrers(); refs != nil { 326 *refs = removeInstr(*refs, np.phi) 327 } 328 } 329 np.phi.block = nil 330 } 331 } 332 newPhis[block] = npList[:j] 333 } 334 } 335 336 // markLivePhi marks phi, and all φ-nodes transitively reachable via 337 // its Operands, live. 338 func markLivePhi(livePhis map[*Phi]bool, phi *Phi) { 339 livePhis[phi] = true 340 for _, rand := range phi.Operands(nil) { 341 if q, ok := (*rand).(*Phi); ok { 342 if !livePhis[q] { 343 markLivePhi(livePhis, q) 344 } 345 } 346 } 347 } 348 349 // phiHasDirectReferrer reports whether phi is directly referred to by 350 // a non-Phi instruction. Such instructions are the 351 // roots of the liveness traversal. 352 func phiHasDirectReferrer(phi *Phi) bool { 353 for _, instr := range *phi.Referrers() { 354 if _, ok := instr.(*Phi); !ok { 355 return true 356 } 357 } 358 return false 359 } 360 361 type blockSet struct{ big.Int } // (inherit methods from Int) 362 363 // add adds b to the set and returns true if the set changed. 364 func (s *blockSet) add(b *BasicBlock) bool { 365 i := b.Index 366 if s.Bit(i) != 0 { 367 return false 368 } 369 s.SetBit(&s.Int, i, 1) 370 return true 371 } 372 373 // take removes an arbitrary element from a set s and 374 // returns its index, or returns -1 if empty. 375 func (s *blockSet) take() int { 376 l := s.BitLen() 377 for i := range l { 378 if s.Bit(i) == 1 { 379 s.SetBit(&s.Int, i, 0) 380 return i 381 } 382 } 383 return -1 384 } 385 386 // newPhi is a pair of a newly introduced φ-node and the lifted Alloc 387 // it replaces. 388 type newPhi struct { 389 phi *Phi 390 alloc *Alloc 391 } 392 393 // newPhiMap records for each basic block, the set of newPhis that 394 // must be prepended to the block. 395 type newPhiMap map[*BasicBlock][]newPhi 396 397 // liftAlloc determines whether alloc can be lifted into registers, 398 // and if so, it populates newPhis with all the φ-nodes it may require 399 // and returns true. 400 // 401 // fresh is a source of fresh ids for phi nodes. 402 func liftAlloc(df domFrontier, alloc *Alloc, newPhis newPhiMap, fresh *int) bool { 403 // Don't lift result values in functions that defer 404 // calls that may recover from panic. 405 if fn := alloc.Parent(); fn.Recover != nil { 406 if slices.Contains(fn.results, alloc) { 407 return false 408 } 409 } 410 411 // Compute defblocks, the set of blocks containing a 412 // definition of the alloc cell. 413 var defblocks blockSet 414 for _, instr := range *alloc.Referrers() { 415 // Bail out if we discover the alloc is not liftable; 416 // the only operations permitted to use the alloc are 417 // loads/stores into the cell, and DebugRef. 418 switch instr := instr.(type) { 419 case *Store: 420 if instr.Val == alloc { 421 return false // address used as value 422 } 423 if instr.Addr != alloc { 424 panic("Alloc.Referrers is inconsistent") 425 } 426 defblocks.add(instr.Block()) 427 case *UnOp: 428 if instr.Op != token.MUL { 429 return false // not a load 430 } 431 if instr.X != alloc { 432 panic("Alloc.Referrers is inconsistent") 433 } 434 case *DebugRef: 435 // ok 436 default: 437 return false // some other instruction 438 } 439 } 440 // The Alloc itself counts as a (zero) definition of the cell. 441 defblocks.add(alloc.Block()) 442 443 if debugLifting { 444 fmt.Fprintln(os.Stderr, "\tlifting ", alloc, alloc.Name()) 445 } 446 447 fn := alloc.Parent() 448 449 // Φ-insertion. 450 // 451 // What follows is the body of the main loop of the insert-φ 452 // function described by Cytron et al, but instead of using 453 // counter tricks, we just reset the 'hasAlready' and 'work' 454 // sets each iteration. These are bitmaps so it's pretty cheap. 455 // 456 // TODO(adonovan): opt: recycle slice storage for W, 457 // hasAlready, defBlocks across liftAlloc calls. 458 var hasAlready blockSet 459 460 // Initialize W and work to defblocks. 461 var work blockSet = defblocks // blocks seen 462 var W blockSet // blocks to do 463 W.Set(&defblocks.Int) 464 465 // Traverse iterated dominance frontier, inserting φ-nodes. 466 for i := W.take(); i != -1; i = W.take() { 467 u := fn.Blocks[i] 468 for _, v := range df[u.Index] { 469 if hasAlready.add(v) { 470 // Create φ-node. 471 // It will be prepended to v.Instrs later, if needed. 472 phi := &Phi{ 473 Edges: make([]Value, len(v.Preds)), 474 Comment: alloc.Comment, 475 } 476 // This is merely a debugging aid: 477 phi.setNum(*fresh) 478 *fresh++ 479 480 phi.pos = alloc.Pos() 481 phi.setType(typeparams.MustDeref(alloc.Type())) 482 phi.block = v 483 if debugLifting { 484 fmt.Fprintf(os.Stderr, "\tplace %s = %s at block %s\n", phi.Name(), phi, v) 485 } 486 newPhis[v] = append(newPhis[v], newPhi{phi, alloc}) 487 488 if work.add(v) { 489 W.add(v) 490 } 491 } 492 } 493 } 494 495 return true 496 } 497 498 // replaceAll replaces all intraprocedural uses of x with y, 499 // updating x.Referrers and y.Referrers. 500 // Precondition: x.Referrers() != nil, i.e. x must be local to some function. 501 func replaceAll(x, y Value) { 502 var rands []*Value 503 pxrefs := x.Referrers() 504 pyrefs := y.Referrers() 505 for _, instr := range *pxrefs { 506 rands = instr.Operands(rands[:0]) // recycle storage 507 for _, rand := range rands { 508 if *rand != nil { 509 if *rand == x { 510 *rand = y 511 } 512 } 513 } 514 if pyrefs != nil { 515 *pyrefs = append(*pyrefs, instr) // dups ok 516 } 517 } 518 *pxrefs = nil // x is now unreferenced 519 } 520 521 // renamed returns the value to which alloc is being renamed, 522 // constructing it lazily if it's the implicit zero initialization. 523 func renamed(renaming []Value, alloc *Alloc) Value { 524 v := renaming[alloc.index] 525 if v == nil { 526 v = zeroConst(typeparams.MustDeref(alloc.Type())) 527 renaming[alloc.index] = v 528 } 529 return v 530 } 531 532 // rename implements the (Cytron et al) SSA renaming algorithm, a 533 // preorder traversal of the dominator tree replacing all loads of 534 // Alloc cells with the value stored to that cell by the dominating 535 // store instruction. For lifting, we need only consider loads, 536 // stores and φ-nodes. 537 // 538 // renaming is a map from *Alloc (keyed by index number) to its 539 // dominating stored value; newPhis[x] is the set of new φ-nodes to be 540 // prepended to block x. 541 func rename(u *BasicBlock, renaming []Value, newPhis newPhiMap) { 542 // Each φ-node becomes the new name for its associated Alloc. 543 for _, np := range newPhis[u] { 544 phi := np.phi 545 alloc := np.alloc 546 renaming[alloc.index] = phi 547 } 548 549 // Rename loads and stores of allocs. 550 for i, instr := range u.Instrs { 551 switch instr := instr.(type) { 552 case *Alloc: 553 if instr.index >= 0 { // store of zero to Alloc cell 554 // Replace dominated loads by the zero value. 555 renaming[instr.index] = nil 556 if debugLifting { 557 fmt.Fprintf(os.Stderr, "\tkill alloc %s\n", instr) 558 } 559 // Delete the Alloc. 560 u.Instrs[i] = nil 561 u.gaps++ 562 } 563 564 case *Store: 565 if alloc, ok := instr.Addr.(*Alloc); ok && alloc.index >= 0 { // store to Alloc cell 566 // Replace dominated loads by the stored value. 567 renaming[alloc.index] = instr.Val 568 if debugLifting { 569 fmt.Fprintf(os.Stderr, "\tkill store %s; new value: %s\n", 570 instr, instr.Val.Name()) 571 } 572 // Remove the store from the referrer list of the stored value. 573 if refs := instr.Val.Referrers(); refs != nil { 574 *refs = removeInstr(*refs, instr) 575 } 576 // Delete the Store. 577 u.Instrs[i] = nil 578 u.gaps++ 579 } 580 581 case *UnOp: 582 if instr.Op == token.MUL { 583 if alloc, ok := instr.X.(*Alloc); ok && alloc.index >= 0 { // load of Alloc cell 584 newval := renamed(renaming, alloc) 585 if debugLifting { 586 fmt.Fprintf(os.Stderr, "\tupdate load %s = %s with %s\n", 587 instr.Name(), instr, newval.Name()) 588 } 589 // Replace all references to 590 // the loaded value by the 591 // dominating stored value. 592 replaceAll(instr, newval) 593 // Delete the Load. 594 u.Instrs[i] = nil 595 u.gaps++ 596 } 597 } 598 599 case *DebugRef: 600 if alloc, ok := instr.X.(*Alloc); ok && alloc.index >= 0 { // ref of Alloc cell 601 if instr.IsAddr { 602 instr.X = renamed(renaming, alloc) 603 instr.IsAddr = false 604 605 // Add DebugRef to instr.X's referrers. 606 if refs := instr.X.Referrers(); refs != nil { 607 *refs = append(*refs, instr) 608 } 609 } else { 610 // A source expression denotes the address 611 // of an Alloc that was optimized away. 612 instr.X = nil 613 614 // Delete the DebugRef. 615 u.Instrs[i] = nil 616 u.gaps++ 617 } 618 } 619 } 620 } 621 622 // For each φ-node in a CFG successor, rename the edge. 623 for _, v := range u.Succs { 624 phis := newPhis[v] 625 if len(phis) == 0 { 626 continue 627 } 628 i := v.predIndex(u) 629 for _, np := range phis { 630 phi := np.phi 631 alloc := np.alloc 632 newval := renamed(renaming, alloc) 633 if debugLifting { 634 fmt.Fprintf(os.Stderr, "\tsetphi %s edge %s -> %s (#%d) (alloc=%s) := %s\n", 635 phi.Name(), u, v, i, alloc.Name(), newval.Name()) 636 } 637 phi.Edges[i] = newval 638 if prefs := newval.Referrers(); prefs != nil { 639 *prefs = append(*prefs, phi) 640 } 641 } 642 } 643 644 // Continue depth-first recursion over domtree, pushing a 645 // fresh copy of the renaming map for each subtree. 646 for i, v := range u.dom.children { 647 r := renaming 648 if i < len(u.dom.children)-1 { 649 // On all but the final iteration, we must make 650 // a copy to avoid destructive update. 651 r = make([]Value, len(renaming)) 652 copy(r, renaming) 653 } 654 rename(v, r, newPhis) 655 } 656 657 } 658 659 // deferstackPreamble returns the *Alloc and ssa:deferstack() call for fn.deferstack. 660 func deferstackPreamble(fn *Function) (*Alloc, *Call) { 661 if alloc, _ := fn.vars[fn.deferstack].(*Alloc); alloc != nil { 662 for _, ref := range *alloc.Referrers() { 663 if ref, _ := ref.(*Store); ref != nil && ref.Addr == alloc { 664 if call, _ := ref.Val.(*Call); call != nil { 665 return alloc, call 666 } 667 } 668 } 669 } 670 return nil, nil 671 }