Skip to content

Commit a7ed48b

Browse files
authored
Fix def-use legalization in CFG normalization. (shader-slang#2909)
Co-authored-by: Yong He <yhe@nvidia.com>
1 parent 02bb741 commit a7ed48b

File tree

1 file changed

+97
-80
lines changed

1 file changed

+97
-80
lines changed

source/slang/slang-ir-autodiff-cfg-norm.cpp

+97-80
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
#include "slang-ir-validate.h"
77
#include "slang-ir-util.h"
8+
#include "slang-ir-dominators.h"
89

910
namespace Slang
1011
{
@@ -638,6 +639,95 @@ struct CFGNormalizationPass
638639
}
639640
};
640641

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+
641731
void normalizeCFG(
642732
IRModule* module, IRGlobalValueWithCode* func, IRCFGNormalizationPass const& options)
643733
{
@@ -674,90 +764,17 @@ void normalizeCFG(
674764
}
675765
}
676766

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+
756773
disableIRValidationAtInsert();
757774
constructSSA(module, func);
758775
enableIRValidationAtInsert();
759776
#if _DEBUG
760-
validateIRInst(func);
777+
validateIRInst(maybeFindOuterGeneric(func));
761778
#endif
762779
}
763780

0 commit comments

Comments
 (0)