github.com/wasilibs/wazerox@v0.0.0-20240124024944-4923be63ab5f/internal/engine/wazevo/ssa/pass_cfg_test.go (about) 1 package ssa 2 3 import ( 4 "sort" 5 "testing" 6 7 "github.com/wasilibs/wazerox/internal/testing/require" 8 ) 9 10 func TestBuilder_passCalculateImmediateDominators(t *testing.T) { 11 for _, tc := range []struct { 12 name string 13 edges edgesCase 14 expDoms map[BasicBlockID]BasicBlockID 15 expLoops map[BasicBlockID]struct{} 16 }{ 17 { 18 name: "linear", 19 // 0 -> 1 -> 2 -> 3 -> 4 20 edges: edgesCase{ 21 0: {1}, 22 1: {2}, 23 2: {3}, 24 3: {4}, 25 }, 26 expDoms: map[BasicBlockID]BasicBlockID{ 27 1: 0, 28 2: 1, 29 3: 2, 30 4: 3, 31 }, 32 }, 33 { 34 name: "diamond", 35 // 0 36 // / \ 37 // 1 2 38 // \ / 39 // 3 40 edges: edgesCase{ 41 0: {1, 2}, 42 1: {3}, 43 2: {3}, 44 }, 45 expDoms: map[BasicBlockID]BasicBlockID{ 46 1: 0, 47 2: 0, 48 3: 0, 49 }, 50 }, 51 { 52 name: "merge", 53 // 0 -> 1 -> 3 54 // | ^ 55 // v | 56 // 2 --------- 57 edges: edgesCase{ 58 0: {1, 2}, 59 1: {3}, 60 2: {3}, 61 }, 62 expDoms: map[BasicBlockID]BasicBlockID{ 63 1: 0, 64 2: 0, 65 3: 0, 66 }, 67 }, 68 { 69 name: "branch", 70 // 0 71 // / \ 72 // 1 2 73 edges: edgesCase{ 74 0: {1, 2}, 75 }, 76 expDoms: map[BasicBlockID]BasicBlockID{ 77 1: 0, 78 2: 0, 79 }, 80 }, 81 { 82 name: "loop", 83 // 0 -> 1 -> 2 84 // ^ | 85 // | v 86 // |--- 3 87 edges: edgesCase{ 88 0: {1}, 89 1: {2}, 90 2: {3}, 91 3: {1}, 92 }, 93 expDoms: map[BasicBlockID]BasicBlockID{ 94 1: 0, 95 2: 1, 96 3: 2, 97 }, 98 expLoops: map[BasicBlockID]struct{}{1: {}}, 99 }, 100 { 101 name: "larger diamond", 102 // 0 103 // / | \ 104 // 1 2 3 105 // \ | / 106 // 4 107 edges: edgesCase{ 108 0: {1, 2, 3}, 109 1: {4}, 110 2: {4}, 111 3: {4}, 112 }, 113 expDoms: map[BasicBlockID]BasicBlockID{ 114 1: 0, 115 2: 0, 116 3: 0, 117 4: 0, 118 }, 119 }, 120 { 121 name: "two independent branches", 122 // 0 123 // / \ 124 // 1 2 125 // | | 126 // 3 4 127 edges: edgesCase{ 128 0: {1, 2}, 129 1: {3}, 130 2: {4}, 131 }, 132 expDoms: map[BasicBlockID]BasicBlockID{ 133 1: 0, 134 2: 0, 135 3: 1, 136 4: 2, 137 }, 138 }, 139 { 140 name: "branch", 141 // 0 -> 1 -> 2 142 // | | 143 // v v 144 // 3 <- 4 145 edges: edgesCase{ 146 0: {1}, 147 1: {2, 3}, 148 2: {4}, 149 4: {3}, 150 }, 151 expDoms: map[BasicBlockID]BasicBlockID{ 152 1: 0, 153 2: 1, 154 3: 1, 155 4: 2, 156 }, 157 }, 158 { 159 name: "branches with merge", 160 // 0 161 // / \ 162 // 1 2 163 // \ / 164 // 3 > 4 165 edges: edgesCase{ 166 0: {1, 2}, 167 1: {3}, 168 2: {4}, 169 3: {4}, 170 }, 171 expDoms: map[BasicBlockID]BasicBlockID{ 172 1: 0, 173 2: 0, 174 3: 1, 175 4: 0, 176 }, 177 }, 178 { 179 name: "cross branches", 180 // 0 181 // / \ 182 // 1 2 183 // |\ /| 184 // | X | 185 // |/ \| 186 // 3 4 187 edges: edgesCase{ 188 0: {1, 2}, 189 1: {3, 4}, 190 2: {3, 4}, 191 }, 192 expDoms: map[BasicBlockID]BasicBlockID{ 193 1: 0, 194 2: 0, 195 3: 0, 196 4: 0, 197 }, 198 }, 199 { 200 // Loop with multiple entries are not loops in the strict sense. 201 // See the comment on basicBlock.loopHeader. 202 // Note that WebAssembly program won't produce such CFGs. TODO: proof! 203 name: "nested loops with multiple entries", 204 // 0 205 // / \ 206 // v v 207 // 1 <> 2 208 // ^ | 209 // | v 210 // 4 <- 3 211 edges: edgesCase{ 212 0: {1, 2}, 213 1: {2}, 214 2: {1, 3}, 215 3: {4}, 216 4: {1}, 217 }, 218 expDoms: map[BasicBlockID]BasicBlockID{ 219 1: 0, 220 2: 0, 221 3: 2, 222 4: 3, 223 }, 224 }, 225 { 226 name: "two intersecting loops", 227 // 0 228 // v 229 // 1 --> 2 --> 3 230 // ^ | | 231 // v v v 232 // 4 <-- 5 <-- 6 233 edges: edgesCase{ 234 0: {1}, 235 1: {2, 4}, 236 2: {3, 5}, 237 3: {6}, 238 4: {1}, 239 5: {4}, 240 6: {5}, 241 }, 242 expDoms: map[BasicBlockID]BasicBlockID{ 243 1: 0, 244 2: 1, 245 3: 2, 246 4: 1, 247 5: 2, 248 6: 3, 249 }, 250 expLoops: map[BasicBlockID]struct{}{1: {}}, 251 }, 252 { 253 name: "loop back edges", 254 // 0 255 // v 256 // 1 --> 2 --> 3 --> 4 257 // ^ | | 258 // v v v 259 // 8 <-------- 6 <-- 5 260 edges: edgesCase{ 261 0: {1}, 262 1: {2, 8}, 263 2: {3}, 264 3: {4, 6}, 265 4: {5}, 266 5: {6}, 267 6: {8}, 268 8: {1}, 269 }, 270 expDoms: map[BasicBlockID]BasicBlockID{ 271 1: 0, 272 2: 1, 273 3: 2, 274 4: 3, 275 5: 4, 276 6: 3, 277 8: 1, 278 }, 279 expLoops: map[BasicBlockID]struct{}{1: {}}, 280 }, 281 { 282 name: "multiple independent paths", 283 // 0 284 // v 285 // 1 --> 2 --> 3 --> 4 --> 5 286 // | ^ ^ 287 // v | | 288 // 6 --> 7 --> 8 --> 9 289 edges: edgesCase{ 290 0: {1}, 291 1: {2, 6}, 292 2: {3}, 293 3: {4}, 294 4: {5}, 295 6: {7}, 296 7: {8}, 297 8: {3, 9}, 298 9: {4}, 299 }, 300 expDoms: map[BasicBlockID]BasicBlockID{ 301 1: 0, 302 2: 1, 303 3: 1, 304 4: 1, 305 5: 4, 306 6: 1, 307 7: 6, 308 8: 7, 309 9: 8, 310 }, 311 }, 312 { 313 name: "double back edges", 314 // 0 315 // v 316 // 1 --> 2 --> 3 --> 4 -> 5 317 // ^ | 318 // v v 319 // 7 <--------------- 6 320 edges: edgesCase{ 321 0: {1}, 322 1: {2, 7}, 323 2: {3}, 324 3: {4}, 325 4: {5, 6}, 326 6: {7}, 327 7: {1}, 328 }, 329 expDoms: map[BasicBlockID]BasicBlockID{ 330 1: 0, 331 2: 1, 332 3: 2, 333 4: 3, 334 5: 4, 335 6: 4, 336 7: 1, 337 }, 338 expLoops: map[BasicBlockID]struct{}{1: {}}, 339 }, 340 { 341 name: "double nested loops with branches", 342 // 0 --> 1 --> 2 --> 3 --> 4 --> 5 --> 6 343 // ^ | | | 344 // v v v | 345 // 9 <-- 8 <--------- 7 <---| 346 edges: edgesCase{ 347 0: {1}, 348 1: {2, 9}, 349 2: {3, 8}, 350 3: {4}, 351 4: {5, 7}, 352 5: {6, 7}, 353 7: {8}, 354 8: {9}, 355 9: {1}, 356 }, 357 expDoms: map[BasicBlockID]BasicBlockID{ 358 1: 0, 359 2: 1, 360 3: 2, 361 4: 3, 362 5: 4, 363 6: 5, 364 7: 4, 365 8: 2, 366 9: 1, 367 }, 368 expLoops: map[BasicBlockID]struct{}{1: {}}, 369 }, 370 { 371 name: "split paths with a loop", 372 // 0 373 // v 374 // 1 375 // / \ 376 // v v 377 // 2<--3 378 // ^ | 379 // | v 380 // 6<--4 381 // | 382 // v 383 // 5 384 edges: edgesCase{ 385 0: {1}, 386 1: {2, 3}, 387 3: {2, 4}, 388 4: {6}, 389 6: {2, 5}, 390 }, 391 expDoms: map[BasicBlockID]BasicBlockID{ 392 1: 0, 393 2: 1, 394 3: 1, 395 4: 3, 396 5: 6, 397 6: 4, 398 }, 399 }, 400 { 401 name: "multiple exits with a loop", 402 // 0 403 // v 404 // 1 405 // / \ 406 // v v 407 // 2<--3 408 // | 409 // v 410 // 4<->5 411 // | 412 // v 413 // 6 414 edges: edgesCase{ 415 0: {1}, 416 1: {2, 3}, 417 2: {4}, 418 3: {2}, 419 4: {5, 6}, 420 5: {4}, 421 }, 422 expDoms: map[BasicBlockID]BasicBlockID{ 423 1: 0, 424 2: 1, 425 3: 1, 426 4: 2, 427 5: 4, 428 6: 4, 429 }, 430 expLoops: map[BasicBlockID]struct{}{4: {}}, 431 }, 432 { 433 name: "parallel loops with merge", 434 // 0 435 // v 436 // 1 437 // / \ 438 // v v 439 // 3<--2 440 // | 441 // v 442 // 4<->5 443 // | | 444 // v v 445 // 7<->6 446 edges: edgesCase{ 447 0: {1}, 448 1: {2, 3}, 449 2: {3}, 450 3: {4}, 451 4: {5, 7}, 452 5: {4, 6}, 453 6: {7}, 454 7: {6}, 455 }, 456 expDoms: map[BasicBlockID]BasicBlockID{ 457 1: 0, 458 2: 1, 459 3: 1, 460 4: 3, 461 5: 4, 462 6: 4, 463 7: 4, 464 }, 465 expLoops: map[BasicBlockID]struct{}{4: {}}, 466 }, 467 { 468 name: "two independent loops", 469 // 0 470 // | 471 // v 472 // 1 --> 2 --> 3 473 // ^ | 474 // v v 475 // 4 <---------5 476 // | 477 // v 478 // 6 --> 7 --> 8 479 // ^ | 480 // v v 481 // 9 <---------10 482 edges: map[BasicBlockID][]BasicBlockID{ 483 0: {1}, 484 1: {2, 4}, 485 2: {3}, 486 3: {5}, 487 4: {1, 6}, 488 5: {4}, 489 6: {7, 9}, 490 7: {8}, 491 8: {10}, 492 9: {6}, 493 10: {9}, 494 }, 495 expDoms: map[BasicBlockID]BasicBlockID{ 496 1: 0, 497 2: 1, 498 3: 2, 499 4: 1, 500 5: 3, 501 6: 4, 502 7: 6, 503 8: 7, 504 9: 6, 505 10: 8, 506 }, 507 expLoops: map[BasicBlockID]struct{}{1: {}, 6: {}}, 508 }, 509 { 510 name: "merge after loop", 511 edges: edgesCase{ 512 0: {3, 1}, 513 1: {2}, 514 2: {1, 3}, 515 3: {4}, 516 }, 517 expDoms: map[BasicBlockID]BasicBlockID{ 518 1: 0, 519 2: 1, 520 3: 0, 521 4: 3, 522 }, 523 expLoops: map[BasicBlockID]struct{}{1: {}}, 524 }, 525 } { 526 tc := tc 527 t.Run(tc.name, func(t *testing.T) { 528 b := constructGraphFromEdges(tc.edges) 529 passCalculateImmediateDominators(b) 530 531 for blockID, expDomID := range tc.expDoms { 532 expBlock := b.basicBlocksPool.View(int(expDomID)) 533 require.Equal(t, expBlock, b.dominators[blockID], 534 "block %d expecting %d, but got %s", blockID, expDomID, b.dominators[blockID]) 535 } 536 537 for blk := b.blockIteratorBegin(); blk != nil; blk = b.blockIteratorNext() { 538 _, expLoop := tc.expLoops[blk.id] 539 require.Equal(t, expLoop, blk.loopHeader, blk.String()) 540 } 541 }) 542 } 543 } 544 545 func TestBuildLoopNestingForest(t *testing.T) { 546 type expLoopNestingForest struct { 547 roots []BasicBlockID 548 children map[BasicBlockID][]BasicBlockID 549 } 550 551 for _, tc := range []struct { 552 name string 553 edges edgesCase 554 expLNF expLoopNestingForest 555 }{ 556 { 557 name: "linear", 558 // 0 -> 1 -> 2 -> 3 -> 4 559 edges: edgesCase{ 560 0: {1}, 561 1: {2}, 562 2: {3}, 563 3: {4}, 564 }, 565 }, 566 { 567 name: "loop", 568 // 0 -> 1 -> 2 569 // ^ | 570 // | v 571 // |--- 3 572 edges: edgesCase{ 573 0: {1}, 574 1: {2}, 575 2: {3}, 576 3: {1}, 577 }, 578 expLNF: expLoopNestingForest{ 579 roots: []BasicBlockID{1}, 580 children: map[BasicBlockID][]BasicBlockID{ 581 1: {2, 3}, 582 }, 583 }, 584 }, 585 { 586 name: "two independent loops", 587 // 0 588 // | 589 // v 590 // 1 --> 2 --> 3 591 // ^ | 592 // v v 593 // 4 <---------5 594 // | 595 // v 596 // 6 --> 7 --> 8 597 // ^ | 598 // v v 599 // 9 <---------10 600 edges: map[BasicBlockID][]BasicBlockID{ 601 0: {1}, 602 1: {2, 4}, 603 2: {3}, 604 3: {5}, 605 4: {1, 6}, 606 5: {4}, 607 6: {7, 9}, 608 7: {8}, 609 8: {10}, 610 9: {6}, 611 10: {9}, 612 }, 613 expLNF: expLoopNestingForest{ 614 roots: []BasicBlockID{1}, 615 children: map[BasicBlockID][]BasicBlockID{ 616 1: {2, 3, 4, 5, 6}, 617 6: {7, 8, 9, 10}, 618 }, 619 }, 620 }, 621 { 622 // 623 // +-----+ 624 // | | 625 // v | 626 // 0 ---> 1 ---> 2 --> 3 ---> 4 627 // ^ | 628 // | | 629 // +------+ 630 // 631 name: "Fig. 9.2", // in "SSA-based Compiler Design". 632 edges: map[BasicBlockID][]BasicBlockID{ 633 0: {1}, 634 1: {2}, 635 2: {1, 3}, 636 3: {2, 4}, 637 }, 638 expLNF: expLoopNestingForest{ 639 roots: []BasicBlockID{1}, 640 children: map[BasicBlockID][]BasicBlockID{ 641 1: {2}, 642 2: {3, 4}, 643 }, 644 }, 645 }, 646 } { 647 tc := tc 648 t.Run(tc.name, func(t *testing.T) { 649 b := constructGraphFromEdges(tc.edges) 650 // buildLoopNestingForest requires passCalculateImmediateDominators to be done. 651 passCalculateImmediateDominators(b) 652 buildLoopNestingForest(b) 653 654 blocks := map[BasicBlockID]*basicBlock{} 655 for blk := b.blockIteratorBegin(); blk != nil; blk = b.blockIteratorNext() { 656 blocks[blk.id] = blk 657 } 658 659 // Check the result of buildLoopNestingForest. 660 var forestRoots []BasicBlockID 661 for _, root := range b.loopNestingForestRoots { 662 forestRoots = append(forestRoots, root.(*basicBlock).id) 663 } 664 sort.Slice(forestRoots, func(i, j int) bool { 665 return forestRoots[i] < forestRoots[j] 666 }) 667 require.Equal(t, tc.expLNF.roots, forestRoots) 668 669 for expBlkID, blk := range blocks { 670 expChildren := tc.expLNF.children[expBlkID] 671 var actualChildren []BasicBlockID 672 for _, child := range blk.loopNestingForestChildren { 673 actualChildren = append(actualChildren, child.(*basicBlock).id) 674 } 675 sort.Slice(actualChildren, func(i, j int) bool { 676 return actualChildren[i] < actualChildren[j] 677 }) 678 require.Equal(t, expChildren, actualChildren, "block %d", expBlkID) 679 } 680 }) 681 } 682 } 683 684 func TestDominatorTree(t *testing.T) { 685 type lowestCommonAncestorCase struct { 686 a, b BasicBlockID 687 exp BasicBlockID 688 } 689 690 for _, tc := range []struct { 691 name string 692 edges edgesCase 693 cases []lowestCommonAncestorCase 694 }{ 695 { 696 name: "linear", 697 // 0 -> 1 -> 2 -> 3 -> 4 698 edges: edgesCase{ 699 0: {1}, 700 1: {2}, 701 2: {3}, 702 3: {4}, 703 }, 704 cases: []lowestCommonAncestorCase{ 705 {a: 0, b: 0, exp: 0}, 706 {a: 0, b: 1, exp: 0}, 707 {a: 0, b: 2, exp: 0}, 708 {a: 0, b: 3, exp: 0}, 709 {a: 0, b: 4, exp: 0}, 710 {a: 1, b: 1, exp: 1}, 711 {a: 1, b: 2, exp: 1}, 712 {a: 1, b: 3, exp: 1}, 713 {a: 1, b: 4, exp: 1}, 714 {a: 2, b: 2, exp: 2}, 715 {a: 2, b: 3, exp: 2}, 716 {a: 2, b: 4, exp: 2}, 717 {a: 3, b: 3, exp: 3}, 718 {a: 3, b: 4, exp: 3}, 719 {a: 4, b: 4, exp: 4}, 720 }, 721 }, 722 { 723 // 724 // +-----+ 725 // | | 726 // v | 727 // 0 ---> 1 ---> 2 --> 3 ---> 4 728 // ^ | 729 // | | 730 // +------+ 731 // 732 name: "two loops", 733 edges: map[BasicBlockID][]BasicBlockID{ 734 0: {1}, 735 1: {2}, 736 2: {1, 3}, 737 3: {2, 4}, 738 }, 739 cases: []lowestCommonAncestorCase{ 740 {a: 0, b: 0, exp: 0}, 741 {a: 0, b: 1, exp: 0}, 742 {a: 0, b: 2, exp: 0}, 743 {a: 0, b: 3, exp: 0}, 744 {a: 0, b: 4, exp: 0}, 745 {a: 1, b: 1, exp: 1}, 746 {a: 1, b: 2, exp: 1}, 747 {a: 1, b: 3, exp: 1}, 748 {a: 1, b: 4, exp: 1}, 749 {a: 2, b: 2, exp: 2}, 750 {a: 2, b: 3, exp: 2}, 751 {a: 2, b: 4, exp: 2}, 752 {a: 3, b: 3, exp: 3}, 753 {a: 3, b: 4, exp: 3}, 754 {a: 4, b: 4, exp: 4}, 755 }, 756 }, 757 { 758 name: "binary", 759 edges: edgesCase{ 760 0: {1, 2}, 761 1: {3, 4}, 762 2: {5, 6}, 763 3: {}, 764 4: {}, 765 5: {}, 766 6: {}, 767 }, 768 cases: []lowestCommonAncestorCase{ 769 {a: 3, b: 4, exp: 1}, 770 {a: 3, b: 5, exp: 0}, 771 {a: 3, b: 6, exp: 0}, 772 {a: 4, b: 5, exp: 0}, 773 {a: 4, b: 6, exp: 0}, 774 {a: 5, b: 6, exp: 2}, 775 {a: 3, b: 1, exp: 1}, 776 {a: 6, b: 2, exp: 2}, 777 }, 778 }, 779 { 780 name: "complex tree", 781 edges: edgesCase{ 782 0: {1, 2}, 783 1: {3, 4, 5}, 784 2: {6, 7}, 785 3: {8, 9}, 786 4: {10}, 787 6: {11, 12, 13}, 788 7: {14}, 789 12: {15}, 790 }, 791 cases: []lowestCommonAncestorCase{ 792 {a: 8, b: 9, exp: 3}, 793 {a: 10, b: 5, exp: 1}, 794 {a: 11, b: 14, exp: 2}, 795 {a: 15, b: 13, exp: 6}, 796 {a: 8, b: 10, exp: 1}, 797 {a: 9, b: 4, exp: 1}, 798 {a: 15, b: 7, exp: 2}, 799 }, 800 }, 801 { 802 name: "complex tree with single and multiple branching", 803 edges: edgesCase{ 804 0: {1, 2}, 805 1: {3}, 806 2: {4, 5}, 807 3: {6, 7, 8}, 808 4: {9, 10}, 809 5: {11}, 810 8: {12, 13}, 811 9: {14, 15}, 812 10: {16}, 813 13: {17, 18}, 814 }, 815 cases: []lowestCommonAncestorCase{ 816 {a: 6, b: 7, exp: 3}, 817 {a: 14, b: 16, exp: 4}, 818 {a: 17, b: 18, exp: 13}, 819 {a: 12, b: 18, exp: 8}, 820 {a: 6, b: 12, exp: 3}, 821 {a: 11, b: 9, exp: 2}, 822 {a: 15, b: 11, exp: 2}, 823 {a: 3, b: 10, exp: 0}, 824 {a: 7, b: 13, exp: 3}, 825 {a: 15, b: 16, exp: 4}, 826 {a: 0, b: 17, exp: 0}, 827 {a: 1, b: 18, exp: 1}, 828 }, 829 }, 830 } { 831 tc := tc 832 t.Run(tc.name, func(t *testing.T) { 833 b := constructGraphFromEdges(tc.edges) 834 // buildDominatorTree requires passCalculateImmediateDominators to be done. 835 passCalculateImmediateDominators(b) 836 buildDominatorTree(b) 837 838 for _, c := range tc.cases { 839 exp := b.sparseTree.findLCA(c.a, c.b) 840 require.Equal(t, exp.id, c.exp, "LCA(%d, %d)", c.a, c.b) 841 } 842 }) 843 } 844 }