summaryrefslogtreecommitdiffstats
path: root/source
diff options
context:
space:
mode:
Diffstat (limited to 'source')
-rw-r--r--source/slang/slang-ir-legalize-types.cpp222
1 files changed, 192 insertions, 30 deletions
diff --git a/source/slang/slang-ir-legalize-types.cpp b/source/slang/slang-ir-legalize-types.cpp
index 2161b0223..d72117702 100644
--- a/source/slang/slang-ir-legalize-types.cpp
+++ b/source/slang/slang-ir-legalize-types.cpp
@@ -1402,6 +1402,9 @@ static LegalVal legalizeInst(
case kIROp_GlobalParam:
return legalizeGlobalParam(context, cast<IRGlobalParam>(inst));
+ case kIROp_Block:
+ return LegalVal::simple(inst);
+
default:
break;
}
@@ -1515,27 +1518,6 @@ static void addParamType(List<IRType*>& ioParamTypes, LegalType t)
}
}
-static void legalizeInstsInParent(
- IRTypeLegalizationContext* context,
- IRInst* parent)
-{
- IRInst* nextChild = nullptr;
- for(auto child = parent->getFirstDecorationOrChild(); child; child = nextChild)
- {
- nextChild = child->getNextInst();
-
- if (auto block = as<IRBlock>(child))
- {
- legalizeInstsInParent(context, block);
- }
- else
- {
- LegalVal legalVal = legalizeInst(context, child);
- registerLegalizedValue(context, child, legalVal);
- }
- }
-}
-
static LegalVal legalizeFunc(
IRTypeLegalizationContext* context,
IRFunc* irFunc)
@@ -1575,7 +1557,6 @@ static LegalVal legalizeFunc(
context->builder->setDataType(irFunc, newFuncType);
- legalizeInstsInParent(context, irFunc);
return LegalVal::simple(irFunc);
}
@@ -2470,19 +2451,200 @@ static LegalVal legalizeGlobalParam(
}
}
+struct IRTypeLegalizationPass
+{
+ IRTypeLegalizationContext* context;
+
+ // The goal of this pass is to ensure that legalization has been
+ // applied to each instruction in a module. We also want to
+ // ensure that an insturction doesn't get processed until after
+ // all of its operands have been processed.
+ //
+ // The basic idea will be to maintain a work list of instructions
+ // that are able to be processed, and also a set to track which
+ // instructions have ever been added to the work list.
+
+ List<IRInst*> workList;
+ HashSet<IRInst*> addedToWorkListSet;
+
+ // We will add a simple query to check whether an instruciton
+ // has been put on the work list before (or if it should be
+ // treated *as if* it has been placed on the work list).
+ //
+ bool hasBeenAddedToWorkList(IRInst* inst)
+ {
+ // Sometimes we end up with instructions that have a null
+ // operand (mostly as the type field of key instructions
+ // like the module itself).
+ //
+ // We want to treat such null pointers like we would an
+ // already-processed instruction.
+ //
+ if(!inst) return true;
+
+ // HACK(tfoley): In most cases it is structurally invalid for our
+ // IR to have a cycle where following the operands (or type) of
+ // instructions can lead back to the original instruction.
+ //
+ // (Note that circular dependencies are still possible, but they
+ // must generally be from *children* of an instruction back
+ // to the instruction itself. E.g., an instruction in the body
+ // of a function can directly or indirectly depend on that function.)
+ //
+ // The one key counterexample is with interface types, because their
+ // requirements and the expected types of those requirements are
+ // encoded as operands. A typical method on inteface `I` will have a type
+ // that involves a `ThisType<I>` parameter for `this`, and that creates
+ // a cycle back to `I`.
+ //
+ // In our type legalization pass we are going to manually break that
+ // cycle by treating all `IRInterfaceRequirementEntry` instructions
+ // as having already been processed, since there is no particular
+ // need for us to handle them as part of legalization.
+ //
+ if(inst->op == kIROp_InterfaceRequirementEntry) return true;
+
+ return addedToWorkListSet.Contains(inst);
+ }
+
+ // Next we define a convenience routine for adding something to the work list.
+ //
+ void addToWorkList(IRInst* inst)
+ {
+ // We want to avoid adding anything we've already added or processed.
+ //
+ if(addedToWorkListSet.Contains(inst))
+ return;
+
+ workList.add(inst);
+ addedToWorkListSet.Add(inst);
+ }
+
+ void processModule(IRModule* module)
+ {
+ // In order to process an entire module, we start by adding the
+ // root module insturction to our work list, and then we will
+ // proceed to process instructions until the work list goes dry.
+ //
+ addToWorkList(module->getModuleInst());
+ while( workList.getCount() != 0 )
+ {
+ // The order of items in the work list is signficiant;
+ // later entries could depend on earlier ones. As such, we
+ // cannot just do something like the `fastRemoveAt(...)`
+ // operation that could potentially lead to instructions
+ // being processed in a different order than they were added.
+ //
+ // Instead, we will make a copy of the current work list
+ // at each step, and swap in an empty work list to be added
+ // to with any new instructions.
+ //
+ List<IRInst*> workListCopy;
+ Swap(workListCopy, workList);
+
+ // Now we simply process each instruction on the copy of
+ // the work list, knowing that `processInst` may add additional
+ // instructions to the original work list.
+ //
+ for( auto inst : workListCopy )
+ {
+ processInst(inst);
+ }
+ }
+
+ // After we are done, there might be various instructions that
+ // were marked for deletion, but have not yet been cleaned up.
+ //
+ // We will clean up all those unnecessary instructions now.
+ //
+ for (auto& lv : context->replacedInstructions)
+ {
+ lv->removeAndDeallocate();
+ }
+ }
+
+ void processInst(IRInst* inst)
+ {
+ // The main logic for legalizing an instruction is defined
+ // earlier in this file.
+ //
+ // Our primary task here is to legalize the instruction, and then
+ // register the result of legalization as the proper value
+ // for that instruction.
+ //
+ LegalVal legalVal = legalizeInst(context, inst);
+ registerLegalizedValue(context, inst, legalVal);
+
+ // Once the instruction has been processed, we need to consider
+ // whether any other instructions might now be ready to process.
+ //
+ // An instruction `i` might have been blocked by `inst` if:
+ //
+ // * `inst` was an operand (including the type operand) of `i`, or
+ // * `inst` was the parent of `i`.
+ //
+ // To turn those relationships around, we want to check instructions
+ // `i` where:
+ //
+ // * `i` is a user of `inst`, or
+ // * `i` is a child of `inst`.
+ //
+ for( auto use = inst->firstUse; use; use = use->nextUse )
+ {
+ auto user = use->getUser();
+ maybeAddToWorkList(user);
+ }
+ for( auto child : inst->getDecorationsAndChildren() )
+ {
+ maybeAddToWorkList(child);
+ }
+ }
+
+ void maybeAddToWorkList(IRInst* inst)
+ {
+ // Here we have an `inst` that might be ready to go on
+ // the work list, but we need to check that it would
+ // be valid to put it on there.
+ //
+ // First, we don't want to add something if it has
+ // already been added.
+ //
+ if(hasBeenAddedToWorkList(inst))
+ return;
+
+ // Next, we don't want to add something if its parent
+ // hasn't been added already.
+ //
+ if(!hasBeenAddedToWorkList(inst->getParent()))
+ return;
+
+ // Finally, we don't want to add something if its
+ // type and/or operands haven't all been added.
+ //
+ if(!hasBeenAddedToWorkList(inst->getFullType()))
+ return;
+ Index operandCount = (Index) inst->getOperandCount();
+ for( Index i = 0; i < operandCount; ++i )
+ {
+ auto operand = inst->getOperand(i);
+ if(!hasBeenAddedToWorkList(operand))
+ return;
+ }
+
+ // If all those checks pass, then we are ready to
+ // process `inst`, and we will add it to our work list.
+ //
+ addToWorkList(inst);
+ }
+};
static void legalizeTypes(
IRTypeLegalizationContext* context)
{
- // Legalize all the top-level instructions in the module
- auto module = context->module;
- legalizeInstsInParent(context, module->moduleInst);
+ IRTypeLegalizationPass pass;
+ pass.context = context;
- // Clean up after any instructions we replaced along the way.
- for (auto& lv : context->replacedInstructions)
- {
- lv->removeAndDeallocate();
- }
+ pass.processModule(context->module);
}
// We use the same basic type legalization machinery for both simplifying