|
5 | 5 |
|
6 | 6 | #include "slang-ir-validate.h"
|
7 | 7 | #include "slang-ir-util.h"
|
| 8 | +#include "slang-ir-dominators.h" |
8 | 9 |
|
9 | 10 | namespace Slang
|
10 | 11 | {
|
@@ -638,6 +639,95 @@ struct CFGNormalizationPass
|
638 | 639 | }
|
639 | 640 | };
|
640 | 641 |
|
| 642 | +static void legalizeDefUse(IRGlobalValueWithCode* func) |
| 643 | +{ |
| 644 | + auto dom = computeDominatorTree(func); |
| 645 | + for (auto block : func->getBlocks()) |
| 646 | + { |
| 647 | + for (auto inst : block->getModifiableChildren()) |
| 648 | + { |
| 649 | + // Inspect all uses of `inst` and find the common dominator of all use sites. |
| 650 | + IRBlock* commonDominator = block; |
| 651 | + for (auto use = inst->firstUse; use; use = use->nextUse) |
| 652 | + { |
| 653 | + auto userBlock = as<IRBlock>(use->getUser()->getParent()); |
| 654 | + if (!userBlock) |
| 655 | + continue; |
| 656 | + while (commonDominator && !dom->dominates(commonDominator, userBlock)) |
| 657 | + { |
| 658 | + commonDominator = dom->getImmediateDominator(commonDominator); |
| 659 | + } |
| 660 | + } |
| 661 | + SLANG_ASSERT(commonDominator); |
| 662 | + |
| 663 | + if (commonDominator == block) |
| 664 | + continue; |
| 665 | + |
| 666 | + // If the common dominator is not `block`, it means we have detected |
| 667 | + // uses that is no longer dominated by the current definition, and need |
| 668 | + // to be fixed. |
| 669 | + |
| 670 | + // Normally, we can simply move the definition to the common dominator. |
| 671 | + // An exception is when the common dominator is the target block of a |
| 672 | + // loop. Note that after normalization, loops are in the form of: |
| 673 | + // ``` |
| 674 | + // loop { if (condition) block; else break; } |
| 675 | + // ``` |
| 676 | + // If we find ourselves needing to make the inst available right before |
| 677 | + // the `if`, it means we are seeing uses of the inst outside the loop. |
| 678 | + // In this case, we should insert a var/move the inst before the loop |
| 679 | + // instead of before the `if`. This situation can occur in the IR if |
| 680 | + // the original code is lowered from a `do-while` loop. |
| 681 | + for (auto use = commonDominator->firstUse; use; use = use->nextUse) |
| 682 | + { |
| 683 | + if (auto loopUser = as<IRLoop>(use->getUser())) |
| 684 | + { |
| 685 | + if (loopUser->getTargetBlock() == commonDominator) |
| 686 | + { |
| 687 | + commonDominator = as<IRBlock>(loopUser->getParent()); |
| 688 | + break; |
| 689 | + } |
| 690 | + } |
| 691 | + } |
| 692 | + // Now we can legalize uses based on the type of `inst`. |
| 693 | + if (auto var = as<IRVar>(inst)) |
| 694 | + { |
| 695 | + // If inst is an var, this is easy, we just move it to the |
| 696 | + // common dominator. |
| 697 | + var->insertBefore(commonDominator->getTerminator()); |
| 698 | + } |
| 699 | + else |
| 700 | + { |
| 701 | + // For all other insts, we need to create a local var for it, |
| 702 | + // and replace all uses with a load from the local var. |
| 703 | + IRBuilder builder(func); |
| 704 | + builder.setInsertBefore(commonDominator->getTerminator()); |
| 705 | + IRVar* tempVar = builder.emitVar(inst->getFullType()); |
| 706 | + auto defaultVal = builder.emitDefaultConstruct(inst->getFullType()); |
| 707 | + builder.emitStore(tempVar, defaultVal); |
| 708 | + |
| 709 | + builder.setInsertAfter(inst); |
| 710 | + builder.emitStore(tempVar, inst); |
| 711 | + |
| 712 | + traverseUses(inst, [&](IRUse* use) |
| 713 | + { |
| 714 | + auto userBlock = as<IRBlock>(use->getUser()->getParent()); |
| 715 | + if (!userBlock) return; |
| 716 | + // Only fix the use of the current definition of `inst` does not |
| 717 | + // dominate it. |
| 718 | + if (!dom->dominates(block, userBlock)) |
| 719 | + { |
| 720 | + // Replace the use with a load of tempVar. |
| 721 | + builder.setInsertBefore(use->getUser()); |
| 722 | + auto load = builder.emitLoad(tempVar); |
| 723 | + builder.replaceOperand(use, load); |
| 724 | + } |
| 725 | + }); |
| 726 | + } |
| 727 | + } |
| 728 | + } |
| 729 | +} |
| 730 | + |
641 | 731 | void normalizeCFG(
|
642 | 732 | IRModule* module, IRGlobalValueWithCode* func, IRCFGNormalizationPass const& options)
|
643 | 733 | {
|
@@ -674,90 +764,17 @@ void normalizeCFG(
|
674 | 764 | }
|
675 | 765 | }
|
676 | 766 |
|
677 |
| - // If we created a new condition block for a loop, the local vars defined in |
678 |
| - // the original loop body will no longer dominate the exit block of the |
679 |
| - // loop. If there are any uses of these variables outside the loop, they |
680 |
| - // will become invalid. Therefore we need to hoist the local variables to |
681 |
| - // the loop header block. |
682 |
| - HashSet<IRBlock*> workListSet; |
683 |
| - for (auto block : func->getBlocks()) |
684 |
| - { |
685 |
| - if (auto loop = as<IRLoop>(block->getTerminator())) |
686 |
| - { |
687 |
| - auto condBlock = loop->getTargetBlock(); |
688 |
| - auto ifElse = as<IRIfElse>(condBlock->getTerminator()); |
689 |
| - auto bodyBlock = ifElse->getTrueBlock(); |
690 |
| - |
691 |
| - // Collect loop body blocks. |
692 |
| - workList.clear(); |
693 |
| - workListSet.clear(); |
694 |
| - workList.add(bodyBlock); |
695 |
| - workListSet.add(bodyBlock); |
696 |
| - for (Index i = 0; i < workList.getCount(); i++) |
697 |
| - { |
698 |
| - auto b = workList[i]; |
699 |
| - for (auto succ : b->getSuccessors()) |
700 |
| - { |
701 |
| - if (succ != loop->getTargetBlock() && succ != loop->getBreakBlock()) |
702 |
| - { |
703 |
| - if (workListSet.add(succ)) |
704 |
| - workList.add(succ); |
705 |
| - } |
706 |
| - } |
707 |
| - } |
708 |
| - auto insertionPoint = loop; |
709 |
| - IRBuilder builder(func); |
710 |
| - for (auto b : workList) |
711 |
| - { |
712 |
| - for (auto inst : b->getModifiableChildren()) |
713 |
| - { |
714 |
| - // If inst has uses outside the loop body, we need to hoist it. |
715 |
| - IRVar* tempVar = nullptr; |
716 |
| - if (auto var = as<IRVar>(inst)) |
717 |
| - { |
718 |
| - for (auto use = inst->firstUse; use; use = use->nextUse) |
719 |
| - { |
720 |
| - // If inst is an var, this is easy, we just move it to the |
721 |
| - // loop header. |
722 |
| - auto userBlock = as<IRBlock>(use->getUser()->getParent()); |
723 |
| - if (userBlock && !workListSet.contains(userBlock)) |
724 |
| - { |
725 |
| - var->insertBefore(insertionPoint); |
726 |
| - break; |
727 |
| - } |
728 |
| - } |
729 |
| - } |
730 |
| - else |
731 |
| - { |
732 |
| - traverseUses(inst, [&](IRUse* use) |
733 |
| - { |
734 |
| - auto userBlock = as<IRBlock>(use->getUser()->getParent()); |
735 |
| - if (userBlock && !workListSet.contains(userBlock)) |
736 |
| - { |
737 |
| - // For all other insts, we need to create a local var for it. |
738 |
| - if (!tempVar) |
739 |
| - { |
740 |
| - builder.setInsertBefore(insertionPoint); |
741 |
| - tempVar = builder.emitVar(inst->getFullType()); |
742 |
| - builder.setInsertAfter(inst); |
743 |
| - builder.emitStore(tempVar, inst); |
744 |
| - } |
745 |
| - // Replace the use with a load of tempVar. |
746 |
| - builder.setInsertBefore(use->getUser()); |
747 |
| - auto load = builder.emitLoad(tempVar); |
748 |
| - builder.replaceOperand(use, load); |
749 |
| - } |
750 |
| - }); |
751 |
| - } |
752 |
| - } |
753 |
| - } |
754 |
| - } |
755 |
| - } |
| 767 | + // After CFG normalization, there may be invalid uses of var/ssa values where the def |
| 768 | + // no longer dominate the use. We fix these up by going through the IR and create temp |
| 769 | + // vars for such uses. |
| 770 | + sortBlocksInFunc(func); |
| 771 | + legalizeDefUse(func); |
| 772 | + |
756 | 773 | disableIRValidationAtInsert();
|
757 | 774 | constructSSA(module, func);
|
758 | 775 | enableIRValidationAtInsert();
|
759 | 776 | #if _DEBUG
|
760 |
| - validateIRInst(func); |
| 777 | + validateIRInst(maybeFindOuterGeneric(func)); |
761 | 778 | #endif
|
762 | 779 | }
|
763 | 780 |
|
|
0 commit comments