github.com/bananabytelabs/wazero@v0.0.0-20240105073314-54b22a776da8/internal/engine/wazevo/ssa/builder.go (about) 1 package ssa 2 3 import ( 4 "fmt" 5 "sort" 6 "strings" 7 8 "github.com/bananabytelabs/wazero/internal/engine/wazevo/wazevoapi" 9 ) 10 11 // Builder is used to builds SSA consisting of Basic Blocks per function. 12 type Builder interface { 13 // Init must be called to reuse this builder for the next function. 14 Init(typ *Signature) 15 16 // Signature returns the Signature of the currently-compiled function. 17 Signature() *Signature 18 19 // BlockIDMax returns the maximum value of BasicBlocksID existing in the currently-compiled function. 20 BlockIDMax() BasicBlockID 21 22 // AllocateBasicBlock creates a basic block in SSA function. 23 AllocateBasicBlock() BasicBlock 24 25 // CurrentBlock returns the currently handled BasicBlock which is set by the latest call to SetCurrentBlock. 26 CurrentBlock() BasicBlock 27 28 // EntryBlock returns the entry BasicBlock of the currently-compiled function. 29 EntryBlock() BasicBlock 30 31 // SetCurrentBlock sets the instruction insertion target to the BasicBlock `b`. 32 SetCurrentBlock(b BasicBlock) 33 34 // DeclareVariable declares a Variable of the given Type. 35 DeclareVariable(Type) Variable 36 37 // DefineVariable defines a variable in the `block` with value. 38 // The defining instruction will be inserted into the `block`. 39 DefineVariable(variable Variable, value Value, block BasicBlock) 40 41 // DefineVariableInCurrentBB is the same as DefineVariable except the definition is 42 // inserted into the current BasicBlock. Alias to DefineVariable(x, y, CurrentBlock()). 43 DefineVariableInCurrentBB(variable Variable, value Value) 44 45 // AllocateInstruction returns a new Instruction. 46 AllocateInstruction() *Instruction 47 48 // InsertInstruction executes BasicBlock.InsertInstruction for the currently handled basic block. 49 InsertInstruction(raw *Instruction) 50 51 // allocateValue allocates an unused Value. 52 allocateValue(typ Type) Value 53 54 // MustFindValue searches the latest definition of the given Variable and returns the result. 55 MustFindValue(variable Variable) Value 56 57 // FindValueInLinearPath tries to find the latest definition of the given Variable in the linear path to the current BasicBlock. 58 // If it cannot find the definition, or it's not sealed yet, it returns ValueInvalid. 59 FindValueInLinearPath(variable Variable) Value 60 61 // Seal declares that we've known all the predecessors to this block and were added via AddPred. 62 // After calling this, AddPred will be forbidden. 63 Seal(blk BasicBlock) 64 65 // AnnotateValue is for debugging purpose. 66 AnnotateValue(value Value, annotation string) 67 68 // DeclareSignature appends the *Signature to be referenced by various instructions (e.g. OpcodeCall). 69 DeclareSignature(signature *Signature) 70 71 // Signatures returns the slice of declared Signatures. 72 Signatures() []*Signature 73 74 // ResolveSignature returns the Signature which corresponds to SignatureID. 75 ResolveSignature(id SignatureID) *Signature 76 77 // RunPasses runs various passes on the constructed SSA function. 78 RunPasses() 79 80 // Format returns the debugging string of the SSA function. 81 Format() string 82 83 // BlockIteratorBegin initializes the state to iterate over all the valid BasicBlock(s) compiled. 84 // Combined with BlockIteratorNext, we can use this like: 85 // 86 // for blk := builder.BlockIteratorBegin(); blk != nil; blk = builder.BlockIteratorNext() { 87 // // ... 88 // } 89 // 90 // The returned blocks are ordered in the order of AllocateBasicBlock being called. 91 BlockIteratorBegin() BasicBlock 92 93 // BlockIteratorNext advances the state for iteration initialized by BlockIteratorBegin. 94 // Returns nil if there's no unseen BasicBlock. 95 BlockIteratorNext() BasicBlock 96 97 // ValueRefCounts returns the map of ValueID to its reference count. 98 // The returned slice must not be modified. 99 ValueRefCounts() []int 100 101 // LayoutBlocks layouts the BasicBlock(s) so that backend can easily generate the code. 102 // During its process, it splits the critical edges in the function. 103 // This must be called after RunPasses. Otherwise, it panics. 104 // 105 // The resulting order is available via BlockIteratorReversePostOrderBegin and BlockIteratorReversePostOrderNext. 106 LayoutBlocks() 107 108 // BlockIteratorReversePostOrderBegin is almost the same as BlockIteratorBegin except it returns the BasicBlock in the reverse post-order. 109 // This is available after RunPasses is run. 110 BlockIteratorReversePostOrderBegin() BasicBlock 111 112 // BlockIteratorReversePostOrderNext is almost the same as BlockIteratorPostOrderNext except it returns the BasicBlock in the reverse post-order. 113 // This is available after RunPasses is run. 114 BlockIteratorReversePostOrderNext() BasicBlock 115 116 // ReturnBlock returns the BasicBlock which is used to return from the function. 117 ReturnBlock() BasicBlock 118 119 // InsertUndefined inserts an undefined instruction at the current position. 120 InsertUndefined() 121 122 // SetCurrentSourceOffset sets the current source offset. The incoming instruction will be annotated with this offset. 123 SetCurrentSourceOffset(line SourceOffset) 124 125 // LoopNestingForestRoots returns the roots of the loop nesting forest. 126 LoopNestingForestRoots() []BasicBlock 127 128 // LowestCommonAncestor returns the lowest common ancestor in the dominator tree of the given BasicBlock(s). 129 LowestCommonAncestor(blk1, blk2 BasicBlock) BasicBlock 130 131 // Idom returns the immediate dominator of the given BasicBlock. 132 Idom(blk BasicBlock) BasicBlock 133 } 134 135 // NewBuilder returns a new Builder implementation. 136 func NewBuilder() Builder { 137 return &builder{ 138 instructionsPool: wazevoapi.NewPool[Instruction](resetInstruction), 139 basicBlocksPool: wazevoapi.NewPool[basicBlock](resetBasicBlock), 140 valueAnnotations: make(map[ValueID]string), 141 signatures: make(map[SignatureID]*Signature), 142 blkVisited: make(map[*basicBlock]int), 143 valueIDAliases: make(map[ValueID]Value), 144 redundantParameterIndexToValue: make(map[int]Value), 145 returnBlk: &basicBlock{id: basicBlockIDReturnBlock}, 146 } 147 } 148 149 // builder implements Builder interface. 150 type builder struct { 151 basicBlocksPool wazevoapi.Pool[basicBlock] 152 instructionsPool wazevoapi.Pool[Instruction] 153 signatures map[SignatureID]*Signature 154 currentSignature *Signature 155 156 // reversePostOrderedBasicBlocks are the BasicBlock(s) ordered in the reverse post-order after passCalculateImmediateDominators. 157 reversePostOrderedBasicBlocks []*basicBlock 158 currentBB *basicBlock 159 returnBlk *basicBlock 160 161 // variables track the types for Variable with the index regarded Variable. 162 variables []Type 163 // nextValueID is used by builder.AllocateValue. 164 nextValueID ValueID 165 // nextVariable is used by builder.AllocateVariable. 166 nextVariable Variable 167 168 valueIDAliases map[ValueID]Value 169 valueAnnotations map[ValueID]string 170 171 // valueRefCounts is used to lower the SSA in backend, and will be calculated 172 // by the last SSA-level optimization pass. 173 valueRefCounts []int 174 175 // dominators stores the immediate dominator of each BasicBlock. 176 // The index is blockID of the BasicBlock. 177 dominators []*basicBlock 178 sparseTree dominatorSparseTree 179 180 // loopNestingForestRoots are the roots of the loop nesting forest. 181 loopNestingForestRoots []BasicBlock 182 183 // The followings are used for optimization passes/deterministic compilation. 184 instStack []*Instruction 185 blkVisited map[*basicBlock]int 186 valueIDToInstruction []*Instruction 187 blkStack []*basicBlock 188 blkStack2 []*basicBlock 189 ints []int 190 redundantParameterIndexToValue map[int]Value 191 vars []Variable 192 193 // blockIterCur is used to implement blockIteratorBegin and blockIteratorNext. 194 blockIterCur int 195 196 // donePasses is true if RunPasses is called. 197 donePasses bool 198 // doneBlockLayout is true if LayoutBlocks is called. 199 doneBlockLayout bool 200 201 currentSourceOffset SourceOffset 202 } 203 204 // ReturnBlock implements Builder.ReturnBlock. 205 func (b *builder) ReturnBlock() BasicBlock { 206 return b.returnBlk 207 } 208 209 // Init implements Builder.Reset. 210 func (b *builder) Init(s *Signature) { 211 b.nextVariable = 0 212 b.currentSignature = s 213 resetBasicBlock(b.returnBlk) 214 b.instructionsPool.Reset() 215 b.basicBlocksPool.Reset() 216 b.donePasses = false 217 for _, sig := range b.signatures { 218 sig.used = false 219 } 220 221 b.ints = b.ints[:0] 222 b.blkStack = b.blkStack[:0] 223 b.blkStack2 = b.blkStack2[:0] 224 b.dominators = b.dominators[:0] 225 b.loopNestingForestRoots = b.loopNestingForestRoots[:0] 226 227 for i := 0; i < b.basicBlocksPool.Allocated(); i++ { 228 blk := b.basicBlocksPool.View(i) 229 delete(b.blkVisited, blk) 230 } 231 b.basicBlocksPool.Reset() 232 233 for v := ValueID(0); v < b.nextValueID; v++ { 234 delete(b.valueAnnotations, v) 235 delete(b.valueIDAliases, v) 236 b.valueRefCounts[v] = 0 237 b.valueIDToInstruction[v] = nil 238 } 239 b.nextValueID = 0 240 b.reversePostOrderedBasicBlocks = b.reversePostOrderedBasicBlocks[:0] 241 b.donePasses = false 242 b.doneBlockLayout = false 243 for i := range b.valueRefCounts { 244 b.valueRefCounts[i] = 0 245 } 246 247 b.currentSourceOffset = sourceOffsetUnknown 248 } 249 250 // Signature implements Builder.Signature. 251 func (b *builder) Signature() *Signature { 252 return b.currentSignature 253 } 254 255 // AnnotateValue implements Builder.AnnotateValue. 256 func (b *builder) AnnotateValue(value Value, a string) { 257 b.valueAnnotations[value.ID()] = a 258 } 259 260 // AllocateInstruction implements Builder.AllocateInstruction. 261 func (b *builder) AllocateInstruction() *Instruction { 262 instr := b.instructionsPool.Allocate() 263 instr.id = b.instructionsPool.Allocated() 264 return instr 265 } 266 267 // DeclareSignature implements Builder.AnnotateValue. 268 func (b *builder) DeclareSignature(s *Signature) { 269 b.signatures[s.ID] = s 270 s.used = false 271 } 272 273 // Signatures implements Builder.Signatures. 274 func (b *builder) Signatures() (ret []*Signature) { 275 for _, sig := range b.signatures { 276 ret = append(ret, sig) 277 } 278 sort.Slice(ret, func(i, j int) bool { 279 return ret[i].ID < ret[j].ID 280 }) 281 return 282 } 283 284 // SetCurrentSourceOffset implements Builder.SetCurrentSourceOffset. 285 func (b *builder) SetCurrentSourceOffset(l SourceOffset) { 286 b.currentSourceOffset = l 287 } 288 289 func (b *builder) usedSignatures() (ret []*Signature) { 290 for _, sig := range b.signatures { 291 if sig.used { 292 ret = append(ret, sig) 293 } 294 } 295 sort.Slice(ret, func(i, j int) bool { 296 return ret[i].ID < ret[j].ID 297 }) 298 return 299 } 300 301 // ResolveSignature implements Builder.ResolveSignature. 302 func (b *builder) ResolveSignature(id SignatureID) *Signature { 303 return b.signatures[id] 304 } 305 306 // AllocateBasicBlock implements Builder.AllocateBasicBlock. 307 func (b *builder) AllocateBasicBlock() BasicBlock { 308 return b.allocateBasicBlock() 309 } 310 311 // allocateBasicBlock allocates a new basicBlock. 312 func (b *builder) allocateBasicBlock() *basicBlock { 313 id := BasicBlockID(b.basicBlocksPool.Allocated()) 314 blk := b.basicBlocksPool.Allocate() 315 blk.id = id 316 blk.lastDefinitions = make(map[Variable]Value) 317 blk.unknownValues = make(map[Variable]Value) 318 return blk 319 } 320 321 // Idom implements Builder.Idom. 322 func (b *builder) Idom(blk BasicBlock) BasicBlock { 323 return b.dominators[blk.ID()] 324 } 325 326 // InsertInstruction implements Builder.InsertInstruction. 327 func (b *builder) InsertInstruction(instr *Instruction) { 328 b.currentBB.InsertInstruction(instr) 329 330 if l := b.currentSourceOffset; l.Valid() { 331 // Emit the source offset info only when the instruction has side effect because 332 // these are the only instructions that are accessed by stack unwinding. 333 // This reduces the significant amount of the offset info in the binary. 334 if instr.sideEffect() != sideEffectNone { 335 instr.annotateSourceOffset(l) 336 } 337 } 338 339 resultTypesFn := instructionReturnTypes[instr.opcode] 340 if resultTypesFn == nil { 341 panic("TODO: " + instr.Format(b)) 342 } 343 344 t1, ts := resultTypesFn(b, instr) 345 if t1.invalid() { 346 return 347 } 348 349 r1 := b.allocateValue(t1) 350 instr.rValue = r1 351 352 tsl := len(ts) 353 if tsl == 0 { 354 return 355 } 356 357 // TODO: reuse slices, though this seems not to be common. 358 instr.rValues = make([]Value, tsl) 359 for i := 0; i < tsl; i++ { 360 instr.rValues[i] = b.allocateValue(ts[i]) 361 } 362 } 363 364 // DefineVariable implements Builder.DefineVariable. 365 func (b *builder) DefineVariable(variable Variable, value Value, block BasicBlock) { 366 if b.variables[variable].invalid() { 367 panic("BUG: trying to define variable " + variable.String() + " but is not declared yet") 368 } 369 370 if b.variables[variable] != value.Type() { 371 panic(fmt.Sprintf("BUG: inconsistent type for variable %d: expected %s but got %s", variable, b.variables[variable], value.Type())) 372 } 373 bb := block.(*basicBlock) 374 bb.lastDefinitions[variable] = value 375 } 376 377 // DefineVariableInCurrentBB implements Builder.DefineVariableInCurrentBB. 378 func (b *builder) DefineVariableInCurrentBB(variable Variable, value Value) { 379 b.DefineVariable(variable, value, b.currentBB) 380 } 381 382 // SetCurrentBlock implements Builder.SetCurrentBlock. 383 func (b *builder) SetCurrentBlock(bb BasicBlock) { 384 b.currentBB = bb.(*basicBlock) 385 } 386 387 // CurrentBlock implements Builder.CurrentBlock. 388 func (b *builder) CurrentBlock() BasicBlock { 389 return b.currentBB 390 } 391 392 // EntryBlock implements Builder.EntryBlock. 393 func (b *builder) EntryBlock() BasicBlock { 394 return b.entryBlk() 395 } 396 397 // DeclareVariable implements Builder.DeclareVariable. 398 func (b *builder) DeclareVariable(typ Type) Variable { 399 v := b.allocateVariable() 400 iv := int(v) 401 if l := len(b.variables); l <= iv { 402 b.variables = append(b.variables, make([]Type, 2*(l+1))...) 403 } 404 b.variables[v] = typ 405 return v 406 } 407 408 // allocateVariable allocates a new variable. 409 func (b *builder) allocateVariable() (ret Variable) { 410 ret = b.nextVariable 411 b.nextVariable++ 412 return 413 } 414 415 // allocateValue implements Builder.AllocateValue. 416 func (b *builder) allocateValue(typ Type) (v Value) { 417 v = Value(b.nextValueID) 418 v = v.setType(typ) 419 b.nextValueID++ 420 return 421 } 422 423 // FindValueInLinearPath implements Builder.FindValueInLinearPath. 424 func (b *builder) FindValueInLinearPath(variable Variable) Value { 425 return b.findValueInLinearPath(variable, b.currentBB) 426 } 427 428 func (b *builder) findValueInLinearPath(variable Variable, blk *basicBlock) Value { 429 if val, ok := blk.lastDefinitions[variable]; ok { 430 return val 431 } else if !blk.sealed { 432 return ValueInvalid 433 } 434 435 if pred := blk.singlePred; pred != nil { 436 // If this block is sealed and have only one predecessor, 437 // we can use the value in that block without ambiguity on definition. 438 return b.findValueInLinearPath(variable, pred) 439 } 440 if len(blk.preds) == 1 { 441 panic("BUG") 442 } 443 return ValueInvalid 444 } 445 446 // MustFindValue implements Builder.MustFindValue. 447 func (b *builder) MustFindValue(variable Variable) Value { 448 typ := b.definedVariableType(variable) 449 return b.findValue(typ, variable, b.currentBB) 450 } 451 452 // findValue recursively tries to find the latest definition of a `variable`. The algorithm is described in 453 // the section 2 of the paper https://link.springer.com/content/pdf/10.1007/978-3-642-37051-9_6.pdf. 454 // 455 // TODO: reimplement this in iterative, not recursive, to avoid stack overflow. 456 func (b *builder) findValue(typ Type, variable Variable, blk *basicBlock) Value { 457 if val, ok := blk.lastDefinitions[variable]; ok { 458 // The value is already defined in this block! 459 return val 460 } else if !blk.sealed { // Incomplete CFG as in the paper. 461 // If this is not sealed, that means it might have additional unknown predecessor later on. 462 // So we temporarily define the placeholder value here (not add as a parameter yet!), 463 // and record it as unknown. 464 // The unknown values are resolved when we call seal this block via BasicBlock.Seal(). 465 value := b.allocateValue(typ) 466 if wazevoapi.SSALoggingEnabled { 467 fmt.Printf("adding unknown value placeholder for %s at %d\n", variable, blk.id) 468 } 469 blk.lastDefinitions[variable] = value 470 blk.unknownValues[variable] = value 471 return value 472 } 473 474 if pred := blk.singlePred; pred != nil { 475 // If this block is sealed and have only one predecessor, 476 // we can use the value in that block without ambiguity on definition. 477 return b.findValue(typ, variable, pred) 478 } else if len(blk.preds) == 0 { 479 panic("BUG: value is not defined for " + variable.String()) 480 } 481 482 // If this block has multiple predecessors, we have to gather the definitions, 483 // and treat them as an argument to this block. 484 // 485 // The first thing is to define a new parameter to this block which may or may not be redundant, but 486 // later we eliminate trivial params in an optimization pass. This must be done before finding the 487 // definitions in the predecessors so that we can break the cycle. 488 paramValue := blk.AddParam(b, typ) 489 b.DefineVariable(variable, paramValue, blk) 490 491 // After the new param is added, we have to manipulate the original branching instructions 492 // in predecessors so that they would pass the definition of `variable` as the argument to 493 // the newly added PHI. 494 for i := range blk.preds { 495 pred := &blk.preds[i] 496 value := b.findValue(typ, variable, pred.blk) 497 pred.branch.addArgumentBranchInst(value) 498 } 499 return paramValue 500 } 501 502 // Seal implements Builder.Seal. 503 func (b *builder) Seal(raw BasicBlock) { 504 blk := raw.(*basicBlock) 505 if len(blk.preds) == 1 { 506 blk.singlePred = blk.preds[0].blk 507 } 508 blk.sealed = true 509 510 // To get the deterministic compilation, 511 // we need to sort the parameters in the order of the variable index. 512 b.vars = b.vars[:0] 513 for v := range blk.unknownValues { 514 b.vars = append(b.vars, v) 515 } 516 sort.Slice(b.vars, func(i, j int) bool { 517 return b.vars[i] < b.vars[j] 518 }) 519 520 for _, variable := range b.vars { 521 phiValue := blk.unknownValues[variable] 522 typ := b.definedVariableType(variable) 523 blk.addParamOn(typ, phiValue) 524 for i := range blk.preds { 525 pred := &blk.preds[i] 526 predValue := b.findValue(typ, variable, pred.blk) 527 if !predValue.Valid() { 528 panic("BUG: value is not defined anywhere in the predecessors in the CFG") 529 } 530 pred.branch.addArgumentBranchInst(predValue) 531 } 532 } 533 } 534 535 // definedVariableType returns the type of the given variable. If the variable is not defined yet, it panics. 536 func (b *builder) definedVariableType(variable Variable) Type { 537 typ := b.variables[variable] 538 if typ.invalid() { 539 panic(fmt.Sprintf("%s is not defined yet", variable)) 540 } 541 return typ 542 } 543 544 // Format implements Builder.Format. 545 func (b *builder) Format() string { 546 str := strings.Builder{} 547 usedSigs := b.usedSignatures() 548 if len(usedSigs) > 0 { 549 str.WriteByte('\n') 550 str.WriteString("signatures:\n") 551 for _, sig := range usedSigs { 552 str.WriteByte('\t') 553 str.WriteString(sig.String()) 554 str.WriteByte('\n') 555 } 556 } 557 558 var iterBegin, iterNext func() *basicBlock 559 if b.doneBlockLayout { 560 iterBegin, iterNext = b.blockIteratorReversePostOrderBegin, b.blockIteratorReversePostOrderNext 561 } else { 562 iterBegin, iterNext = b.blockIteratorBegin, b.blockIteratorNext 563 } 564 for bb := iterBegin(); bb != nil; bb = iterNext() { 565 str.WriteByte('\n') 566 str.WriteString(bb.FormatHeader(b)) 567 str.WriteByte('\n') 568 569 for cur := bb.Root(); cur != nil; cur = cur.Next() { 570 str.WriteByte('\t') 571 str.WriteString(cur.Format(b)) 572 str.WriteByte('\n') 573 } 574 } 575 return str.String() 576 } 577 578 // BlockIteratorNext implements Builder.BlockIteratorNext. 579 func (b *builder) BlockIteratorNext() BasicBlock { 580 if blk := b.blockIteratorNext(); blk == nil { 581 return nil // BasicBlock((*basicBlock)(nil)) != BasicBlock(nil) 582 } else { 583 return blk 584 } 585 } 586 587 // BlockIteratorNext implements Builder.BlockIteratorNext. 588 func (b *builder) blockIteratorNext() *basicBlock { 589 index := b.blockIterCur 590 for { 591 if index == b.basicBlocksPool.Allocated() { 592 return nil 593 } 594 ret := b.basicBlocksPool.View(index) 595 index++ 596 if !ret.invalid { 597 b.blockIterCur = index 598 return ret 599 } 600 } 601 } 602 603 // BlockIteratorBegin implements Builder.BlockIteratorBegin. 604 func (b *builder) BlockIteratorBegin() BasicBlock { 605 return b.blockIteratorBegin() 606 } 607 608 // BlockIteratorBegin implements Builder.BlockIteratorBegin. 609 func (b *builder) blockIteratorBegin() *basicBlock { 610 b.blockIterCur = 0 611 return b.blockIteratorNext() 612 } 613 614 // BlockIteratorReversePostOrderBegin implements Builder.BlockIteratorReversePostOrderBegin. 615 func (b *builder) BlockIteratorReversePostOrderBegin() BasicBlock { 616 return b.blockIteratorReversePostOrderBegin() 617 } 618 619 // BlockIteratorBegin implements Builder.BlockIteratorBegin. 620 func (b *builder) blockIteratorReversePostOrderBegin() *basicBlock { 621 b.blockIterCur = 0 622 return b.blockIteratorReversePostOrderNext() 623 } 624 625 // BlockIteratorReversePostOrderNext implements Builder.BlockIteratorReversePostOrderNext. 626 func (b *builder) BlockIteratorReversePostOrderNext() BasicBlock { 627 if blk := b.blockIteratorReversePostOrderNext(); blk == nil { 628 return nil // BasicBlock((*basicBlock)(nil)) != BasicBlock(nil) 629 } else { 630 return blk 631 } 632 } 633 634 // BlockIteratorNext implements Builder.BlockIteratorNext. 635 func (b *builder) blockIteratorReversePostOrderNext() *basicBlock { 636 if b.blockIterCur >= len(b.reversePostOrderedBasicBlocks) { 637 return nil 638 } else { 639 ret := b.reversePostOrderedBasicBlocks[b.blockIterCur] 640 b.blockIterCur++ 641 return ret 642 } 643 } 644 645 // ValueRefCounts implements Builder.ValueRefCounts. 646 func (b *builder) ValueRefCounts() []int { 647 return b.valueRefCounts 648 } 649 650 // alias records the alias of the given values. The alias(es) will be 651 // eliminated in the optimization pass via resolveArgumentAlias. 652 func (b *builder) alias(dst, src Value) { 653 b.valueIDAliases[dst.ID()] = src 654 } 655 656 // resolveArgumentAlias resolves the alias of the arguments of the given instruction. 657 func (b *builder) resolveArgumentAlias(instr *Instruction) { 658 if instr.v.Valid() { 659 instr.v = b.resolveAlias(instr.v) 660 } 661 662 if instr.v2.Valid() { 663 instr.v2 = b.resolveAlias(instr.v2) 664 } 665 666 if instr.v3.Valid() { 667 instr.v3 = b.resolveAlias(instr.v3) 668 } 669 670 for i, v := range instr.vs { 671 instr.vs[i] = b.resolveAlias(v) 672 } 673 } 674 675 // resolveAlias resolves the alias of the given value. 676 func (b *builder) resolveAlias(v Value) Value { 677 // Some aliases are chained, so we need to resolve them recursively. 678 for { 679 if src, ok := b.valueIDAliases[v.ID()]; ok { 680 v = src 681 } else { 682 break 683 } 684 } 685 return v 686 } 687 688 // entryBlk returns the entry block of the function. 689 func (b *builder) entryBlk() *basicBlock { 690 return b.basicBlocksPool.View(0) 691 } 692 693 // isDominatedBy returns true if the given block `n` is dominated by the given block `d`. 694 // Before calling this, the builder must pass by passCalculateImmediateDominators. 695 func (b *builder) isDominatedBy(n *basicBlock, d *basicBlock) bool { 696 if len(b.dominators) == 0 { 697 panic("BUG: passCalculateImmediateDominators must be called before calling isDominatedBy") 698 } 699 ent := b.entryBlk() 700 doms := b.dominators 701 for n != d && n != ent { 702 n = doms[n.id] 703 } 704 return n == d 705 } 706 707 // BlockIDMax implements Builder.BlockIDMax. 708 func (b *builder) BlockIDMax() BasicBlockID { 709 return BasicBlockID(b.basicBlocksPool.Allocated()) 710 } 711 712 // LayoutBlocks implements Builder.LayoutBlocks. This re-organizes builder.reversePostOrderedBasicBlocks. 713 // 714 // TODO: there are tons of room for improvement here. e.g. LLVM has BlockPlacementPass using BlockFrequencyInfo, 715 // BranchProbabilityInfo, and LoopInfo to do a much better job. Also, if we have the profiling instrumentation 716 // like ball-larus algorithm, then we could do profile-guided optimization. Basically all of them are trying 717 // to maximize the fall-through opportunities which is most efficient. 718 // 719 // Here, fallthrough happens when a block ends with jump instruction whose target is the right next block in the 720 // builder.reversePostOrderedBasicBlocks. 721 // 722 // Currently, we just place blocks using the DFS reverse post-order of the dominator tree with the heuristics: 723 // 1. a split edge trampoline towards a loop header will be placed as a fallthrough. 724 // 2. we invert the brz and brnz if it makes the fallthrough more likely. 725 // 726 // This heuristic is done in maybeInvertBranches function. 727 func (b *builder) LayoutBlocks() { 728 if !b.donePasses { 729 panic("LayoutBlocks must be called after all passes are done") 730 } 731 732 b.clearBlkVisited() 733 734 // We might end up splitting critical edges which adds more basic blocks, 735 // so we store the currently existing basic blocks in nonSplitBlocks temporarily. 736 // That way we can iterate over the original basic blocks while appending new ones into reversePostOrderedBasicBlocks. 737 nonSplitBlocks := b.blkStack[:0] 738 for i, blk := range b.reversePostOrderedBasicBlocks { 739 if !blk.Valid() { 740 continue 741 } 742 nonSplitBlocks = append(nonSplitBlocks, blk) 743 if i != len(b.reversePostOrderedBasicBlocks)-1 { 744 _ = maybeInvertBranches(blk, b.reversePostOrderedBasicBlocks[i+1]) 745 } 746 } 747 748 var trampolines []*basicBlock 749 750 // Reset the order slice since we update on the fly by splitting critical edges. 751 b.reversePostOrderedBasicBlocks = b.reversePostOrderedBasicBlocks[:0] 752 uninsertedTrampolines := b.blkStack2[:0] 753 for _, blk := range nonSplitBlocks { 754 for i := range blk.preds { 755 pred := blk.preds[i].blk 756 if _, ok := b.blkVisited[pred]; ok || !pred.Valid() { 757 continue 758 } else if pred.reversePostOrder < blk.reversePostOrder { 759 // This means the edge is critical, and this pred is the trampoline and yet to be inserted. 760 // Split edge trampolines must come before the destination in reverse post-order. 761 b.reversePostOrderedBasicBlocks = append(b.reversePostOrderedBasicBlocks, pred) 762 b.blkVisited[pred] = 0 // mark as inserted, the value is not used. 763 } 764 } 765 766 // Now that we've already added all the potential trampoline blocks incoming to this block, 767 // we can add this block itself. 768 b.reversePostOrderedBasicBlocks = append(b.reversePostOrderedBasicBlocks, blk) 769 b.blkVisited[blk] = 0 // mark as inserted, the value is not used. 770 771 if len(blk.success) < 2 { 772 // There won't be critical edge originating from this block. 773 continue 774 } else if blk.currentInstr.opcode == OpcodeBrTable { 775 // We don't split critical edges here, because at the construction site of BrTable, we already split the edges. 776 continue 777 } 778 779 for sidx, succ := range blk.success { 780 if !succ.ReturnBlock() && // If the successor is a return block, we need to split the edge any way because we need "epilogue" to be inserted. 781 // Plus if there's no multiple incoming edges to this successor, (pred, succ) is not critical. 782 len(succ.preds) < 2 { 783 continue 784 } 785 786 // Otherwise, we are sure this is a critical edge. To modify the CFG, we need to find the predecessor info 787 // from the successor. 788 var predInfo *basicBlockPredecessorInfo 789 for i := range succ.preds { // This linear search should not be a problem since the number of predecessors should almost always small. 790 pred := &succ.preds[i] 791 if pred.blk == blk { 792 predInfo = pred 793 break 794 } 795 } 796 797 if predInfo == nil { 798 // This must be a bug in somewhere around branch manipulation. 799 panic("BUG: predecessor info not found while the successor exists in successors list") 800 } 801 802 if wazevoapi.SSALoggingEnabled { 803 fmt.Printf("trying to split edge from %d->%d at %s\n", 804 blk.ID(), succ.ID(), predInfo.branch.Format(b)) 805 } 806 807 trampoline := b.splitCriticalEdge(blk, succ, predInfo) 808 // Update the successors slice because the target is no longer the original `succ`. 809 blk.success[sidx] = trampoline 810 811 if wazevoapi.SSAValidationEnabled { 812 trampolines = append(trampolines, trampoline) 813 } 814 815 if wazevoapi.SSALoggingEnabled { 816 fmt.Printf("edge split from %d->%d at %s as %d->%d->%d \n", 817 blk.ID(), succ.ID(), predInfo.branch.Format(b), 818 blk.ID(), trampoline.ID(), succ.ID()) 819 } 820 821 fallthroughBranch := blk.currentInstr 822 if fallthroughBranch.opcode == OpcodeJump && fallthroughBranch.blk == trampoline { 823 // This can be lowered as fallthrough at the end of the block. 824 b.reversePostOrderedBasicBlocks = append(b.reversePostOrderedBasicBlocks, trampoline) 825 b.blkVisited[trampoline] = 0 // mark as inserted, the value is not used. 826 } else { 827 uninsertedTrampolines = append(uninsertedTrampolines, trampoline) 828 } 829 } 830 831 for _, trampoline := range uninsertedTrampolines { 832 if trampoline.success[0].reversePostOrder <= trampoline.reversePostOrder { // "<=", not "<" because the target might be itself. 833 // This means the critical edge was backward, so we insert after the current block immediately. 834 b.reversePostOrderedBasicBlocks = append(b.reversePostOrderedBasicBlocks, trampoline) 835 b.blkVisited[trampoline] = 0 // mark as inserted, the value is not used. 836 } // If the target is forward, we can wait to insert until the target is inserted. 837 } 838 uninsertedTrampolines = uninsertedTrampolines[:0] // Reuse the stack for the next block. 839 } 840 841 if wazevoapi.SSALoggingEnabled { 842 var bs []string 843 for _, blk := range b.reversePostOrderedBasicBlocks { 844 bs = append(bs, blk.Name()) 845 } 846 fmt.Println("ordered blocks: ", strings.Join(bs, ", ")) 847 } 848 849 if wazevoapi.SSAValidationEnabled { 850 for _, trampoline := range trampolines { 851 if _, ok := b.blkVisited[trampoline]; !ok { 852 panic("BUG: trampoline block not inserted: " + trampoline.FormatHeader(b)) 853 } 854 trampoline.validate(b) 855 } 856 } 857 858 // Critical edges are split, so we fix the loop nesting forest. 859 buildLoopNestingForest(b) 860 buildDominatorTree(b) 861 862 // Reuse the stack for the next iteration. 863 b.blkStack2 = uninsertedTrampolines[:0] 864 865 // Now that we know the final placement of the blocks, we can explicitly mark the fallthrough jumps. 866 b.markFallthroughJumps() 867 b.doneBlockLayout = true 868 } 869 870 // markFallthroughJumps finds the fallthrough jumps and marks them as such. 871 func (b *builder) markFallthroughJumps() { 872 l := len(b.reversePostOrderedBasicBlocks) - 1 873 for i, blk := range b.reversePostOrderedBasicBlocks { 874 if i < l { 875 cur := blk.currentInstr 876 if cur.opcode == OpcodeJump && cur.blk == b.reversePostOrderedBasicBlocks[i+1] { 877 cur.AsFallthroughJump() 878 } 879 } 880 } 881 } 882 883 // maybeInvertBranches inverts the branch instructions if it is likely possible to the fallthrough more likely with simple heuristics. 884 // nextInRPO is the next block in the reverse post-order. 885 // 886 // Returns true if the branch is inverted for testing purpose. 887 func maybeInvertBranches(now *basicBlock, nextInRPO *basicBlock) bool { 888 fallthroughBranch := now.currentInstr 889 if fallthroughBranch.opcode == OpcodeBrTable { 890 return false 891 } 892 893 condBranch := fallthroughBranch.prev 894 if condBranch == nil || (condBranch.opcode != OpcodeBrnz && condBranch.opcode != OpcodeBrz) { 895 return false 896 } 897 898 if len(fallthroughBranch.vs) != 0 || len(condBranch.vs) != 0 { 899 // If either one of them has arguments, we don't invert the branches. 900 return false 901 } 902 903 // So this block has two branches (a conditional branch followed by an unconditional branch) at the end. 904 // We can invert the condition of the branch if it makes the fallthrough more likely. 905 906 fallthroughTarget, condTarget := fallthroughBranch.blk.(*basicBlock), condBranch.blk.(*basicBlock) 907 908 if fallthroughTarget.loopHeader { 909 // First, if the tail's target is loopHeader, we don't need to do anything here, 910 // because the edge is likely to be critical edge for complex loops (e.g. loop with branches inside it). 911 // That means, we will split the edge in the end of LayoutBlocks function, and insert the trampoline block 912 // right after this block, which will be fallthrough in any way. 913 return false 914 } else if condTarget.loopHeader { 915 // On the other hand, if the condBranch's target is loopHeader, we invert the condition of the branch 916 // so that we could get the fallthrough to the trampoline block. 917 goto invert 918 } 919 920 if fallthroughTarget == nextInRPO { 921 // Also, if the tail's target is the next block in the reverse post-order, we don't need to do anything here, 922 // because if this is not critical edge, we would end up placing these two blocks adjacent to each other. 923 // Even if it is the critical edge, we place the trampoline block right after this block, which will be fallthrough in any way. 924 return false 925 } else if condTarget == nextInRPO { 926 // If the condBranch's target is the next block in the reverse post-order, we invert the condition of the branch 927 // so that we could get the fallthrough to the block. 928 goto invert 929 } else { 930 return false 931 } 932 933 invert: 934 for i := range fallthroughTarget.preds { 935 pred := &fallthroughTarget.preds[i] 936 if pred.branch == fallthroughBranch { 937 pred.branch = condBranch 938 break 939 } 940 } 941 for i := range condTarget.preds { 942 pred := &condTarget.preds[i] 943 if pred.branch == condBranch { 944 pred.branch = fallthroughBranch 945 break 946 } 947 } 948 949 condBranch.InvertBrx() 950 condBranch.blk = fallthroughTarget 951 fallthroughBranch.blk = condTarget 952 if wazevoapi.SSALoggingEnabled { 953 fmt.Printf("inverting branches at %d->%d and %d->%d\n", 954 now.ID(), fallthroughTarget.ID(), now.ID(), condTarget.ID()) 955 } 956 957 return true 958 } 959 960 // splitCriticalEdge splits the critical edge between the given predecessor (`pred`) and successor (owning `predInfo`). 961 // 962 // - `pred` is the source of the critical edge, 963 // - `succ` is the destination of the critical edge, 964 // - `predInfo` is the predecessor info in the succ.preds slice which represents the critical edge. 965 // 966 // Why splitting critical edges is important? See following links: 967 // 968 // - https://en.wikipedia.org/wiki/Control-flow_graph 969 // - https://nickdesaulniers.github.io/blog/2023/01/27/critical-edge-splitting/ 970 // 971 // The returned basic block is the trampoline block which is inserted to split the critical edge. 972 func (b *builder) splitCriticalEdge(pred, succ *basicBlock, predInfo *basicBlockPredecessorInfo) *basicBlock { 973 // In the following, we convert the following CFG: 974 // 975 // pred --(originalBranch)--> succ 976 // 977 // to the following CFG: 978 // 979 // pred --(newBranch)--> trampoline --(originalBranch)-> succ 980 // 981 // where trampoline is a new basic block which is created to split the critical edge. 982 983 trampoline := b.allocateBasicBlock() 984 if int(trampoline.id) >= len(b.dominators) { 985 b.dominators = append(b.dominators, make([]*basicBlock, trampoline.id+1)...) 986 } 987 b.dominators[trampoline.id] = pred 988 989 originalBranch := predInfo.branch 990 991 // Replace originalBranch with the newBranch. 992 newBranch := b.AllocateInstruction() 993 newBranch.opcode = originalBranch.opcode 994 newBranch.blk = trampoline 995 switch originalBranch.opcode { 996 case OpcodeJump: 997 case OpcodeBrz, OpcodeBrnz: 998 originalBranch.opcode = OpcodeJump // Trampoline consists of one unconditional branch. 999 newBranch.v = originalBranch.v 1000 originalBranch.v = ValueInvalid 1001 default: 1002 panic("BUG: critical edge shouldn't be originated from br_table") 1003 } 1004 swapInstruction(pred, originalBranch, newBranch) 1005 1006 // Replace the original branch with the new branch. 1007 trampoline.rootInstr = originalBranch 1008 trampoline.currentInstr = originalBranch 1009 trampoline.success = append(trampoline.success, succ) // Do not use []*basicBlock{pred} because we might have already allocated the slice. 1010 trampoline.preds = append(trampoline.preds, // same as ^. 1011 basicBlockPredecessorInfo{blk: pred, branch: newBranch}) 1012 b.Seal(trampoline) 1013 1014 // Update the original branch to point to the trampoline. 1015 predInfo.blk = trampoline 1016 predInfo.branch = originalBranch 1017 1018 if wazevoapi.SSAValidationEnabled { 1019 trampoline.validate(b) 1020 } 1021 1022 if len(trampoline.params) > 0 { 1023 panic("trampoline should not have params") 1024 } 1025 1026 // Assign the same order as the original block so that this will be placed before the actual destination. 1027 trampoline.reversePostOrder = pred.reversePostOrder 1028 return trampoline 1029 } 1030 1031 // swapInstruction replaces `old` in the block `blk` with `New`. 1032 func swapInstruction(blk *basicBlock, old, New *Instruction) { 1033 if blk.rootInstr == old { 1034 blk.rootInstr = New 1035 next := old.next 1036 New.next = next 1037 next.prev = New 1038 } else { 1039 if blk.currentInstr == old { 1040 blk.currentInstr = New 1041 } 1042 prev := old.prev 1043 prev.next, New.prev = New, prev 1044 if next := old.next; next != nil { 1045 New.next, next.prev = next, New 1046 } 1047 } 1048 old.prev, old.next = nil, nil 1049 } 1050 1051 // InsertUndefined implements Builder.InsertUndefined. 1052 func (b *builder) InsertUndefined() { 1053 instr := b.AllocateInstruction() 1054 instr.opcode = OpcodeUndefined 1055 b.InsertInstruction(instr) 1056 } 1057 1058 // LoopNestingForestRoots implements Builder.LoopNestingForestRoots. 1059 func (b *builder) LoopNestingForestRoots() []BasicBlock { 1060 return b.loopNestingForestRoots 1061 } 1062 1063 // LowestCommonAncestor implements Builder.LowestCommonAncestor. 1064 func (b *builder) LowestCommonAncestor(blk1, blk2 BasicBlock) BasicBlock { 1065 return b.sparseTree.findLCA(blk1.ID(), blk2.ID()) 1066 }