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