gitee.com/wgliang/goreporter@v0.0.0-20180902115603-df1b20f7c5d0/linters/simpler/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 return removeInstr(refs, instr) 114 } 115 116 func removeInstr(refs []Instruction, instr Instruction) []Instruction { 117 i := 0 118 for _, ref := range refs { 119 if ref == instr { 120 continue 121 } 122 refs[i] = ref 123 i++ 124 } 125 for j := i; j != len(refs); j++ { 126 refs[j] = nil // aid GC 127 } 128 return refs[:i] 129 } 130 131 // lift attempts to replace local and new Allocs accessed only with 132 // load/store by SSA registers, inserting φ-nodes where necessary. 133 // The result is a program in classical pruned SSA form. 134 // 135 // Preconditions: 136 // - fn has no dead blocks (blockopt has run). 137 // - Def/use info (Operands and Referrers) is up-to-date. 138 // - The dominator tree is up-to-date. 139 // 140 func lift(fn *Function) { 141 // TODO(adonovan): opt: lots of little optimizations may be 142 // worthwhile here, especially if they cause us to avoid 143 // buildDomFrontier. For example: 144 // 145 // - Alloc never loaded? Eliminate. 146 // - Alloc never stored? Replace all loads with a zero constant. 147 // - Alloc stored once? Replace loads with dominating store; 148 // don't forget that an Alloc is itself an effective store 149 // of zero. 150 // - Alloc used only within a single block? 151 // Use degenerate algorithm avoiding φ-nodes. 152 // - Consider synergy with scalar replacement of aggregates (SRA). 153 // e.g. *(&x.f) where x is an Alloc. 154 // Perhaps we'd get better results if we generated this as x.f 155 // i.e. Field(x, .f) instead of Load(FieldIndex(x, .f)). 156 // Unclear. 157 // 158 // But we will start with the simplest correct code. 159 df := buildDomFrontier(fn) 160 161 if debugLifting { 162 title := false 163 for i, blocks := range df { 164 if blocks != nil { 165 if !title { 166 fmt.Fprintf(os.Stderr, "Dominance frontier of %s:\n", fn) 167 title = true 168 } 169 fmt.Fprintf(os.Stderr, "\t%s: %s\n", fn.Blocks[i], blocks) 170 } 171 } 172 } 173 174 newPhis := make(newPhiMap) 175 176 // During this pass we will replace some BasicBlock.Instrs 177 // (allocs, loads and stores) with nil, keeping a count in 178 // BasicBlock.gaps. At the end we will reset Instrs to the 179 // concatenation of all non-dead newPhis and non-nil Instrs 180 // for the block, reusing the original array if space permits. 181 182 // While we're here, we also eliminate 'rundefers' 183 // instructions in functions that contain no 'defer' 184 // instructions. 185 usesDefer := false 186 187 // Determine which allocs we can lift and number them densely. 188 // The renaming phase uses this numbering for compact maps. 189 numAllocs := 0 190 for _, b := range fn.Blocks { 191 b.gaps = 0 192 b.rundefers = 0 193 for _, instr := range b.Instrs { 194 switch instr := instr.(type) { 195 case *Alloc: 196 index := -1 197 if liftAlloc(df, instr, newPhis) { 198 index = numAllocs 199 numAllocs++ 200 } 201 instr.index = index 202 case *Defer: 203 usesDefer = true 204 case *RunDefers: 205 b.rundefers++ 206 } 207 } 208 } 209 210 // renaming maps an alloc (keyed by index) to its replacement 211 // value. Initially the renaming contains nil, signifying the 212 // zero constant of the appropriate type; we construct the 213 // Const lazily at most once on each path through the domtree. 214 // TODO(adonovan): opt: cache per-function not per subtree. 215 renaming := make([]Value, numAllocs) 216 217 // Renaming. 218 rename(fn.Blocks[0], renaming, newPhis) 219 220 // Eliminate dead new phis, then prepend the live ones to each block. 221 for _, b := range fn.Blocks { 222 223 // Compress the newPhis slice to eliminate unused phis. 224 // TODO(adonovan): opt: compute liveness to avoid 225 // placing phis in blocks for which the alloc cell is 226 // not live. 227 nps := newPhis[b] 228 j := 0 229 for _, np := range nps { 230 if !phiIsLive(np.phi) { 231 // discard it, first removing it from referrers 232 for _, newval := range np.phi.Edges { 233 if refs := newval.Referrers(); refs != nil { 234 *refs = removeInstr(*refs, np.phi) 235 } 236 } 237 continue 238 } 239 nps[j] = np 240 j++ 241 } 242 nps = nps[:j] 243 244 rundefersToKill := b.rundefers 245 if usesDefer { 246 rundefersToKill = 0 247 } 248 249 if j+b.gaps+rundefersToKill == 0 { 250 continue // fast path: no new phis or gaps 251 } 252 253 // Compact nps + non-nil Instrs into a new slice. 254 // TODO(adonovan): opt: compact in situ if there is 255 // sufficient space or slack in the slice. 256 dst := make([]Instruction, len(b.Instrs)+j-b.gaps-rundefersToKill) 257 for i, np := range nps { 258 dst[i] = np.phi 259 } 260 for _, instr := range b.Instrs { 261 if instr == nil { 262 continue 263 } 264 if !usesDefer { 265 if _, ok := instr.(*RunDefers); ok { 266 continue 267 } 268 } 269 dst[j] = instr 270 j++ 271 } 272 for i, np := range nps { 273 dst[i] = np.phi 274 } 275 b.Instrs = dst 276 } 277 278 // Remove any fn.Locals that were lifted. 279 j := 0 280 for _, l := range fn.Locals { 281 if l.index < 0 { 282 fn.Locals[j] = l 283 j++ 284 } 285 } 286 // Nil out fn.Locals[j:] to aid GC. 287 for i := j; i < len(fn.Locals); i++ { 288 fn.Locals[i] = nil 289 } 290 fn.Locals = fn.Locals[:j] 291 } 292 293 func phiIsLive(phi *Phi) bool { 294 for _, instr := range *phi.Referrers() { 295 if instr == phi { 296 continue // self-refs don't count 297 } 298 if _, ok := instr.(*DebugRef); ok { 299 continue // debug refs don't count 300 } 301 return true 302 } 303 return false 304 } 305 306 type blockSet struct{ big.Int } // (inherit methods from Int) 307 308 // add adds b to the set and returns true if the set changed. 309 func (s *blockSet) add(b *BasicBlock) bool { 310 i := b.Index 311 if s.Bit(i) != 0 { 312 return false 313 } 314 s.SetBit(&s.Int, i, 1) 315 return true 316 } 317 318 // take removes an arbitrary element from a set s and 319 // returns its index, or returns -1 if empty. 320 func (s *blockSet) take() int { 321 l := s.BitLen() 322 for i := 0; i < l; i++ { 323 if s.Bit(i) == 1 { 324 s.SetBit(&s.Int, i, 0) 325 return i 326 } 327 } 328 return -1 329 } 330 331 // newPhi is a pair of a newly introduced φ-node and the lifted Alloc 332 // it replaces. 333 type newPhi struct { 334 phi *Phi 335 alloc *Alloc 336 } 337 338 // newPhiMap records for each basic block, the set of newPhis that 339 // must be prepended to the block. 340 type newPhiMap map[*BasicBlock][]newPhi 341 342 // liftAlloc determines whether alloc can be lifted into registers, 343 // and if so, it populates newPhis with all the φ-nodes it may require 344 // and returns true. 345 // 346 func liftAlloc(df domFrontier, alloc *Alloc, newPhis newPhiMap) bool { 347 // Don't lift aggregates into registers, because we don't have 348 // a way to express their zero-constants. 349 switch deref(alloc.Type()).Underlying().(type) { 350 case *types.Array, *types.Struct: 351 return false 352 } 353 354 // Don't lift named return values in functions that defer 355 // calls that may recover from panic. 356 if fn := alloc.Parent(); fn.Recover != nil { 357 for _, nr := range fn.namedResults { 358 if nr == alloc { 359 return false 360 } 361 } 362 } 363 364 // Compute defblocks, the set of blocks containing a 365 // definition of the alloc cell. 366 var defblocks blockSet 367 for _, instr := range *alloc.Referrers() { 368 // Bail out if we discover the alloc is not liftable; 369 // the only operations permitted to use the alloc are 370 // loads/stores into the cell, and DebugRef. 371 switch instr := instr.(type) { 372 case *Store: 373 if instr.Val == alloc { 374 return false // address used as value 375 } 376 if instr.Addr != alloc { 377 panic("Alloc.Referrers is inconsistent") 378 } 379 defblocks.add(instr.Block()) 380 case *UnOp: 381 if instr.Op != token.MUL { 382 return false // not a load 383 } 384 if instr.X != alloc { 385 panic("Alloc.Referrers is inconsistent") 386 } 387 case *DebugRef: 388 // ok 389 default: 390 return false // some other instruction 391 } 392 } 393 // The Alloc itself counts as a (zero) definition of the cell. 394 defblocks.add(alloc.Block()) 395 396 if debugLifting { 397 fmt.Fprintln(os.Stderr, "\tlifting ", alloc, alloc.Name()) 398 } 399 400 fn := alloc.Parent() 401 402 // Φ-insertion. 403 // 404 // What follows is the body of the main loop of the insert-φ 405 // function described by Cytron et al, but instead of using 406 // counter tricks, we just reset the 'hasAlready' and 'work' 407 // sets each iteration. These are bitmaps so it's pretty cheap. 408 // 409 // TODO(adonovan): opt: recycle slice storage for W, 410 // hasAlready, defBlocks across liftAlloc calls. 411 var hasAlready blockSet 412 413 // Initialize W and work to defblocks. 414 var work blockSet = defblocks // blocks seen 415 var W blockSet // blocks to do 416 W.Set(&defblocks.Int) 417 418 // Traverse iterated dominance frontier, inserting φ-nodes. 419 for i := W.take(); i != -1; i = W.take() { 420 u := fn.Blocks[i] 421 for _, v := range df[u.Index] { 422 if hasAlready.add(v) { 423 // Create φ-node. 424 // It will be prepended to v.Instrs later, if needed. 425 phi := &Phi{ 426 Edges: make([]Value, len(v.Preds)), 427 Comment: alloc.Comment, 428 } 429 phi.pos = alloc.Pos() 430 phi.setType(deref(alloc.Type())) 431 phi.block = v 432 if debugLifting { 433 fmt.Fprintf(os.Stderr, "\tplace %s = %s at block %s\n", phi.Name(), phi, v) 434 } 435 newPhis[v] = append(newPhis[v], newPhi{phi, alloc}) 436 437 if work.add(v) { 438 W.add(v) 439 } 440 } 441 } 442 } 443 444 return true 445 } 446 447 func ReplaceAll(x, y Value) { 448 replaceAll(x, y) 449 } 450 451 // replaceAll replaces all intraprocedural uses of x with y, 452 // updating x.Referrers and y.Referrers. 453 // Precondition: x.Referrers() != nil, i.e. x must be local to some function. 454 // 455 func replaceAll(x, y Value) { 456 var rands []*Value 457 pxrefs := x.Referrers() 458 pyrefs := y.Referrers() 459 for _, instr := range *pxrefs { 460 rands = instr.Operands(rands[:0]) // recycle storage 461 for _, rand := range rands { 462 if *rand != nil { 463 if *rand == x { 464 *rand = y 465 } 466 } 467 } 468 if pyrefs != nil { 469 *pyrefs = append(*pyrefs, instr) // dups ok 470 } 471 } 472 *pxrefs = nil // x is now unreferenced 473 } 474 475 // renamed returns the value to which alloc is being renamed, 476 // constructing it lazily if it's the implicit zero initialization. 477 // 478 func renamed(renaming []Value, alloc *Alloc) Value { 479 v := renaming[alloc.index] 480 if v == nil { 481 v = zeroConst(deref(alloc.Type())) 482 renaming[alloc.index] = v 483 } 484 return v 485 } 486 487 // rename implements the (Cytron et al) SSA renaming algorithm, a 488 // preorder traversal of the dominator tree replacing all loads of 489 // Alloc cells with the value stored to that cell by the dominating 490 // store instruction. For lifting, we need only consider loads, 491 // stores and φ-nodes. 492 // 493 // renaming is a map from *Alloc (keyed by index number) to its 494 // dominating stored value; newPhis[x] is the set of new φ-nodes to be 495 // prepended to block x. 496 // 497 func rename(u *BasicBlock, renaming []Value, newPhis newPhiMap) { 498 // Each φ-node becomes the new name for its associated Alloc. 499 for _, np := range newPhis[u] { 500 phi := np.phi 501 alloc := np.alloc 502 renaming[alloc.index] = phi 503 } 504 505 // Rename loads and stores of allocs. 506 for i, instr := range u.Instrs { 507 switch instr := instr.(type) { 508 case *Alloc: 509 if instr.index >= 0 { // store of zero to Alloc cell 510 // Replace dominated loads by the zero value. 511 renaming[instr.index] = nil 512 if debugLifting { 513 fmt.Fprintf(os.Stderr, "\tkill alloc %s\n", instr) 514 } 515 // Delete the Alloc. 516 u.Instrs[i] = nil 517 u.gaps++ 518 } 519 520 case *Store: 521 if alloc, ok := instr.Addr.(*Alloc); ok && alloc.index >= 0 { // store to Alloc cell 522 // Replace dominated loads by the stored value. 523 renaming[alloc.index] = instr.Val 524 if debugLifting { 525 fmt.Fprintf(os.Stderr, "\tkill store %s; new value: %s\n", 526 instr, instr.Val.Name()) 527 } 528 // Remove the store from the referrer list of the stored value. 529 if refs := instr.Val.Referrers(); refs != nil { 530 *refs = removeInstr(*refs, instr) 531 } 532 // Delete the Store. 533 u.Instrs[i] = nil 534 u.gaps++ 535 } 536 537 case *UnOp: 538 if instr.Op == token.MUL { 539 if alloc, ok := instr.X.(*Alloc); ok && alloc.index >= 0 { // load of Alloc cell 540 newval := renamed(renaming, alloc) 541 if debugLifting { 542 fmt.Fprintf(os.Stderr, "\tupdate load %s = %s with %s\n", 543 instr.Name(), instr, newval.Name()) 544 } 545 // Replace all references to 546 // the loaded value by the 547 // dominating stored value. 548 replaceAll(instr, newval) 549 // Delete the Load. 550 u.Instrs[i] = nil 551 u.gaps++ 552 } 553 } 554 555 case *DebugRef: 556 if alloc, ok := instr.X.(*Alloc); ok && alloc.index >= 0 { // ref of Alloc cell 557 if instr.IsAddr { 558 instr.X = renamed(renaming, alloc) 559 instr.IsAddr = false 560 561 // Add DebugRef to instr.X's referrers. 562 if refs := instr.X.Referrers(); refs != nil { 563 *refs = append(*refs, instr) 564 } 565 } else { 566 // A source expression denotes the address 567 // of an Alloc that was optimized away. 568 instr.X = nil 569 570 // Delete the DebugRef. 571 u.Instrs[i] = nil 572 u.gaps++ 573 } 574 } 575 } 576 } 577 578 // For each φ-node in a CFG successor, rename the edge. 579 for _, v := range u.Succs { 580 phis := newPhis[v] 581 if len(phis) == 0 { 582 continue 583 } 584 i := v.predIndex(u) 585 for _, np := range phis { 586 phi := np.phi 587 alloc := np.alloc 588 newval := renamed(renaming, alloc) 589 if debugLifting { 590 fmt.Fprintf(os.Stderr, "\tsetphi %s edge %s -> %s (#%d) (alloc=%s) := %s\n", 591 phi.Name(), u, v, i, alloc.Name(), newval.Name()) 592 } 593 phi.Edges[i] = newval 594 if prefs := newval.Referrers(); prefs != nil { 595 *prefs = append(*prefs, phi) 596 } 597 } 598 } 599 600 // Continue depth-first recursion over domtree, pushing a 601 // fresh copy of the renaming map for each subtree. 602 for _, v := range u.dom.children { 603 // TODO(adonovan): opt: avoid copy on final iteration; use destructive update. 604 r := make([]Value, len(renaming)) 605 copy(r, renaming) 606 rename(v, r, newPhis) 607 } 608 }