summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--source/slang/check.cpp54
-rw-r--r--source/slang/diagnostic-defs.h2
-rw-r--r--source/slang/emit.cpp618
-rw-r--r--source/slang/ir-legalize-types.cpp2
-rw-r--r--source/slang/ir-restructure-scoping.cpp434
-rw-r--r--source/slang/ir-restructure-scoping.h24
-rw-r--r--source/slang/ir-restructure.cpp662
-rw-r--r--source/slang/ir-restructure.h261
-rw-r--r--source/slang/ir.cpp29
-rw-r--r--source/slang/ir.h10
-rw-r--r--source/slang/slang.vcxproj4
-rw-r--r--source/slang/slang.vcxproj.filters10
-rw-r--r--tests/bugs/gh-569.slang36
-rw-r--r--tests/bugs/gh-569.slang.expected.txt4
-rw-r--r--tests/compute/multiple-continue-sites.slang39
-rw-r--r--tests/compute/multiple-continue-sites.slang.expected.txt4
-rw-r--r--tools/render-test/d3d-util.cpp1
17 files changed, 1749 insertions, 445 deletions
diff --git a/source/slang/check.cpp b/source/slang/check.cpp
index 67c747861..19e349807 100644
--- a/source/slang/check.cpp
+++ b/source/slang/check.cpp
@@ -131,30 +131,58 @@ namespace Slang
}
bool fromOperatorExpr(OperatorExpr* opExpr)
{
+ // First, lets see if the argument types are ones
+ // that we can encode in our space of keys.
args[0].aggVal = 0;
args[1].aggVal = 0;
if (opExpr->Arguments.Count() > 2)
return false;
+
+ for (UInt i = 0; i < opExpr->Arguments.Count(); i++)
+ {
+ if (!args[i].fromType(opExpr->Arguments[i]->type.Ptr()))
+ return false;
+ }
+
+ // Next, lets see if we can find an intrinsic opcode
+ // attached to an overloaded definition (filtered for
+ // definitions that could conceivably apply to us).
+ //
+ // TODO: This should really be pased on the operator name
+ // plus fixity, rather than the intrinsic opcode...
+ //
+ // We will need to reject postfix definitions for prefix
+ // operators, and vice versa, to ensure things work.
+ //
+ auto prefixExpr = opExpr->As<PrefixExpr>();
+ auto postfixExpr = opExpr->As<PostfixExpr>();
+
if (auto overloadedBase = opExpr->FunctionExpr->As<OverloadedExpr>())
{
- Decl* funcDecl = overloadedBase->lookupResult2.item.declRef.decl;
- if (auto genDecl = funcDecl->As<GenericDecl>())
- funcDecl = genDecl->inner.Ptr();
- if (auto intrinsicOp = funcDecl->FindModifier<IntrinsicOpModifier>())
+ for(auto item : overloadedBase->lookupResult2 )
{
- operatorName = intrinsicOp->op;
- for (UInt i = 0; i < opExpr->Arguments.Count(); i++)
+ // Look at a candidate definition to be called and
+ // see if it gives us a key to work with.
+ //
+ Decl* funcDecl = overloadedBase->lookupResult2.item.declRef.decl;
+ if (auto genDecl = funcDecl->As<GenericDecl>())
+ funcDecl = genDecl->inner.Ptr();
+
+ // Reject definitions that have the wrong fixity.
+ //
+ if(prefixExpr && !funcDecl->FindModifier<PrefixModifier>())
+ continue;
+ if(postfixExpr && !funcDecl->FindModifier<PostfixModifier>())
+ continue;
+
+ if (auto intrinsicOp = funcDecl->FindModifier<IntrinsicOpModifier>())
{
- if (!args[i].fromType(opExpr->Arguments[i]->type.Ptr()))
- return false;
+ operatorName = intrinsicOp->op;
+ return true;
}
}
- else
- {
- return false;
- }
}
- return true;
+ return false;
}
};
diff --git a/source/slang/diagnostic-defs.h b/source/slang/diagnostic-defs.h
index f8e048bdb..f531ae851 100644
--- a/source/slang/diagnostic-defs.h
+++ b/source/slang/diagnostic-defs.h
@@ -352,6 +352,8 @@ DIAGNOSTIC(51090, Error, cannotGenerateCodeForExternComponentType, "cannot gener
DIAGNOSTIC(51091, Error, typeCannotBePlacedInATexture, "type '$0' cannot be placed in a texture.")
DIAGNOSTIC(51092, Error, stageDoesntHaveInputWorld, "'$0' doesn't appear to have any input world");
+DIAGNOSTIC(52000, Error, multiLevelBreakUnsupported, "control flow appears to require multi-level `break`, which Slang does not yet support");
+
// 99999 - Internal compiler errors, and not-yet-classified diagnostics.
diff --git a/source/slang/emit.cpp b/source/slang/emit.cpp
index a6460bf10..3e601e119 100644
--- a/source/slang/emit.cpp
+++ b/source/slang/emit.cpp
@@ -2,6 +2,8 @@
#include "emit.h"
#include "ir-insts.h"
+#include "ir-restructure.h"
+#include "ir-restructure-scoping.h"
#include "ir-ssa.h"
#include "ir-validate.h"
#include "legalize-types.h"
@@ -144,12 +146,16 @@ struct SharedEmitContext
// Map an IR instruction to the name that we've decided
// to use for it when emitting code.
Dictionary<IRInst*, String> mapInstToName;
+
+ DiagnosticSink* getSink() { return &entryPoint->compileRequest->mSink; }
};
struct EmitContext
{
// The shared context that is in effect
SharedEmitContext* shared;
+
+ DiagnosticSink* getSink() { return shared->getSink(); }
};
//
@@ -3340,14 +3346,7 @@ struct EmitVisitor
case kIROp_Not:
{
- if (as<IRBoolType>(inst->getDataType()))
- {
- emit("!");
- }
- else
- {
- emit("~");
- }
+ emit("!");
emitIROperand(ctx, inst->getOperand(0), mode);
}
break;
@@ -3360,7 +3359,14 @@ struct EmitVisitor
break;
case kIROp_BitNot:
{
- emit("~");
+ if (as<IRBoolType>(inst->getDataType()))
+ {
+ emit("!");
+ }
+ else
+ {
+ emit("~");
+ }
emitIROperand(ctx, inst->getOperand(0), mode);
}
break;
@@ -3776,203 +3782,173 @@ struct EmitVisitor
}
}
- enum LabelOp
- {
- kLabelOp_break,
- kLabelOp_continue,
-
- kLabelOpCount,
- };
-
- struct LabelStack
- {
- LabelStack* parent;
- IRBlock* block;
- LabelOp op;
- };
-
- // We want to emit a range of code in the IR, represented
- // by the blocks that are logically in the interval [begin, end)
- // which we consider as a single-entry multiple-exit region.
- //
- // Note: because there are multiple exists, control flow
- // may exit this region with operations that do *not* branch
- // to `end`, but such non-local control flow will hopefully
- // be captured.
- //
- void emitIRStmtsForBlocks(
+ /// Emit high-level language statements from a structrured region.
+ void emitRegion(
EmitContext* ctx,
- IRBlock* begin,
- IRBlock* end,
-
- // Labels to use at the start
- LabelStack* initialLabels,
-
- // Labels to switch to after emitting first basic block
- LabelStack* labels = nullptr)
+ Region* inRegion)
{
- if(!labels)
- labels = initialLabels;
-
- auto useLabels = initialLabels;
-
- IRBlock* block = begin;
- while(block != end)
+ // We will use a loop so that we can process sequential (simple)
+ // regions iteratively rather than recursively.
+ // This is effectively an emulation of tail recursion.
+ Region* region = inRegion;
+ while(region)
{
- // If the block we are trying to emit has been registered as a
- // destination label (e.g. for a loop or `switch`) then we
- // may need to emit a `break` or `continue` as needed.
- //
- // TODO: we eventually need to handle the possibility of
- // multi-level break/continue targets, which could be challenging.
-
- // First, figure out which block has been registered as
- // the current `break` and `continue` target.
- IRBlock* registeredBlock[kLabelOpCount] = {};
- for( auto ll = labels; ll; ll = ll->parent )
- {
- if(!registeredBlock[ll->op])
- {
- registeredBlock[ll->op] = ll->block;
- }
- }
-
- // Next, search in the active labels we are allowed to use,
- // and see if the block we are trying to branch to is an
- // available break/continue target.
- for(auto ll = useLabels; ll; ll = ll->parent)
+ // What flavor of region are we trying to emit?
+ switch(region->getFlavor())
{
- if(ll->block == block)
+ case Region::Flavor::Simple:
{
- // We are trying to go to a block that has been regsitered as a label.
+ // A simple region consists of a basic block followed
+ // by another region.
+ //
+ auto simpleRegion = (SimpleRegion*) region;
- if(block != registeredBlock[ll->op])
+ // We start by outputting all of the non-terminator
+ // instructions in the block.
+ //
+ auto block = simpleRegion->block;
+ auto terminator = block->getTerminator();
+ for (auto inst = block->getFirstInst(); inst != terminator; inst = inst->getNextInst())
{
- // ERROR: need support for multi-level break/continue to pull this one off!
+ emitIRInst(ctx, inst, IREmitMode::Default);
}
- switch(ll->op)
+ // Next we have to deal with the terminator instruction
+ // itself. In many cases, the terminator will have been
+ // turned into a block of its own, but certain cases
+ // of terminators are simple enough that we just fold
+ // them into the current block.
+ //
+ advanceToSourceLocation(terminator->sourceLoc);
+ switch(terminator->op)
{
- case kLabelOp_break:
- emit("break;\n");
+ default:
+ // Don't do anything with the terminator, and assume
+ // its behavior has been folded into the next region.
break;
- case kLabelOp_continue:
- emit("continue;\n");
+ case kIROp_ReturnVal:
+ case kIROp_ReturnVoid:
+ case kIROp_discard:
+ // For extremely simple terminators, we just handle
+ // them here, so that we don't have to allocate
+ // separate `Region`s for them.
+ emitIRInst(ctx, terminator, IREmitMode::Default);
break;
- }
-
- return;
- }
- }
- // Start by emitting the non-terminator instructions in the block.
- auto terminator = block->getLastInst();
- SLANG_ASSERT(as<IRTerminatorInst>(terminator));
- for (auto inst = block->getFirstInst(); inst != terminator; inst = inst->getNextInst())
- {
- emitIRInst(ctx, inst, IREmitMode::Default);
- }
-
- // Now look at the terminator instruction, which will tell us what we need to emit next.
-
- advanceToSourceLocation(terminator->sourceLoc);
+ // We will also handle any unconditional branches
+ // here, since they may have arguments to pass
+ // to the target block (our encoding of SSA
+ // "phi" operations).
+ //
+ // TODO: A better approach would be to move out of SSA
+ // as an IR pass, and introduce explicit variables to
+ // replace any "phi nodes." This would avoid possible
+ // complications if we ever end up in the bad case where
+ // one of the block arguments on a branch is also
+ // a paremter of the target block, so that the order
+ // of operations is important.
+ //
+ case kIROp_unconditionalBranch:
+ {
+ auto t = (IRUnconditionalBranch*)terminator;
+ UInt argCount = t->getOperandCount();
+ static const UInt kFixedArgCount = 1;
+ emitPhiVarAssignments(
+ ctx,
+ argCount - kFixedArgCount,
+ t->getOperands() + kFixedArgCount,
+ t->getTargetBlock());
+ }
+ break;
+ case kIROp_loop:
+ {
+ auto t = (IRLoop*) terminator;
+ UInt argCount = t->getOperandCount();
+ static const UInt kFixedArgCount = 3;
+ emitPhiVarAssignments(
+ ctx,
+ argCount - kFixedArgCount,
+ t->getOperands() + kFixedArgCount,
+ t->getTargetBlock());
- switch (terminator->op)
- {
- default:
- SLANG_UNEXPECTED("terminator inst");
- return;
+ }
+ break;
+ }
- case kIROp_unreachable:
- return;
+ // If the terminator required a full region to represent
+ // its behavior in a structured form, then we will move
+ // along to that region now.
+ //
+ // We do this iteratively rather than recursively, by
+ // jumping back to the top of our loop with a new
+ // value for `region`.
+ //
+ region = simpleRegion->nextRegion;
+ continue;
+ }
- case kIROp_ReturnVal:
- case kIROp_ReturnVoid:
- case kIROp_discard:
- emitIRInst(ctx, terminator, IREmitMode::Default);
- return;
+ // Break and continue regions are trivial to handle, as long as we
+ // don't need to consider multi-level break/continue (which we
+ // don't for now).
+ case Region::Flavor::Break:
+ emit("break;\n");
+ break;
+ case Region::Flavor::Continue:
+ emit("continue;\n");
+ break;
- case kIROp_ifElse:
+ case Region::Flavor::If:
{
- // Two-sided `if` statement
- auto t = (IRIfElse*)terminator;
-
- auto trueBlock = t->getTrueBlock();
- auto falseBlock = t->getFalseBlock();
- auto afterBlock = t->getAfterBlock();
+ auto ifRegion = (IfRegion*) region;
// TODO: consider simplifying the code in
- // the case where `trueBlock == afterBlock`
- // so that we output `if(!cond) { falseBlock }`
- // instead of the current `if(cond) {} else {falseBlock}`
+ // the case where `ifRegion == null`
+ // so that we output `if(!condition) { elseRegion }`
+ // instead of the current `if(condition) {} else { elseRegion }`
emit("if(");
- emitIROperand(ctx, t->getCondition(), IREmitMode::Default);
+ emitIROperand(ctx, ifRegion->condition, IREmitMode::Default);
emit(")\n{\n");
indent();
- emitIRStmtsForBlocks(
- ctx,
- trueBlock,
- afterBlock,
- labels);
+ emitRegion(ctx, ifRegion->thenRegion);
dedent();
emit("}\n");
- // Don't emit the false block if it would be empty
- if(falseBlock != afterBlock)
+
+ // Don't emit the `else` region if it would be empty
+ //
+ if(auto elseRegion = ifRegion->elseRegion)
{
emit("else\n{\n");
indent();
- emitIRStmtsForBlocks(
- ctx,
- falseBlock,
- afterBlock,
- labels);
+ emitRegion(ctx, elseRegion);
dedent();
emit("}\n");
}
- // Continue with the block after the `if`
- block = afterBlock;
+ // Continue with the region after the `if`.
+ //
+ // TODO: consider just constructing a `SimpleRegion`
+ // around an `IfRegion` to handle this sequencing,
+ // rather than making `IfRegion` serve as both a
+ // conditional and a sequence.
+ //
+ region = ifRegion->nextRegion;
+ continue;
}
break;
- case kIROp_loop:
+ case Region::Flavor::Loop:
{
- // Header for a `while` or `for` loop
- auto t = (IRLoop*)terminator;
-
- auto targetBlock = t->getTargetBlock();
- auto breakBlock = t->getBreakBlock();
-
- UInt argCount = t->getOperandCount();
- static const UInt kFixedArgCount = 3;
- emitPhiVarAssignments(
- ctx,
- argCount - kFixedArgCount,
- t->getOperands() + kFixedArgCount,
- targetBlock);
-
- // Set up entries on our label stack for break/continue
-
- LabelStack subBreakLabel;
- subBreakLabel.parent = labels;
- subBreakLabel.block = breakBlock;
- subBreakLabel.op = kLabelOp_break;
-
- // Note: when forming the `continue` label, we don't
- // actually point at the "continue block" from the loop
- // statement, because we aren't actually going to
- // generate an ordinary continue caluse in a `for` loop.
+ auto loopRegion = (LoopRegion*) region;
+ auto loopInst = loopRegion->loopInst;
+
+ // If the user applied an explicit decoration to the loop,
+ // to control its unrolling behavior, then pass that
+ // along in the output code (if the target language
+ // supports the semantics of the decoration).
//
- // Instead, our `continue` label will always be the
- // loop header.
- LabelStack subContinueLabel;
- subContinueLabel.parent = &subBreakLabel;
- subContinueLabel.block = targetBlock;
- subContinueLabel.op = kLabelOp_continue;
-
- if (auto loopControlDecoration = t->findDecoration<IRLoopControlDecoration>())
+ if (auto loopControlDecoration = loopInst->findDecoration<IRLoopControlDecoration>())
{
switch (loopControlDecoration->mode)
{
@@ -3991,289 +3967,67 @@ struct EmitVisitor
emit("for(;;)\n{\n");
indent();
- emitIRStmtsForBlocks(
- ctx,
- targetBlock,
- nullptr,
- // For the first block, we only want the `break` label active
- &subBreakLabel,
- // After the first block, we can safely use the `continue` label too
- &subContinueLabel);
+ emitRegion(ctx, loopRegion->body);
dedent();
emit("}\n");
- // Continue with the block after the loop
- block = breakBlock;
- }
- break;
-
-#if 0
- case kIROp_break:
- {
- auto t = (IRBreak*)terminator;
- auto targetBlock = t->getTargetBlock();
-
- UInt argCount = t->getArgCount();
- static const UInt kFixedArgCount = 1;
- emitPhiVarAssignments(
- ctx,
- argCount - kFixedArgCount,
- t->getArgs() + kFixedArgCount,
- targetBlock);
-
- emit("break;\n");
- }
- return;
-
- case kIROp_continue:
- // With out current strategy for outputting loops,
- // just outputting an AST-level `continue` here won't
- // actually execute the statements in the continue block.
- //
- // Instead, we have to manually output those statements
- // directly here, and *then* do an AST-level `continue`.
- //
- // This leads to code duplication when we have multiple
- // `continue` sites in the original program, but it avoids
- // introducing additional temporaries for control flow.
- {
- auto continueInst = (IRContinue*) terminator;
- auto targetBlock = continueInst->getTargetBlock();
-
- UInt argCount = continueInst->getArgCount();
- static const UInt kFixedArgCount = 1;
- emitPhiVarAssignments(
- ctx,
- argCount - kFixedArgCount,
- continueInst->getArgs() + kFixedArgCount,
- targetBlock);
-
- IRBlock* loopHead = nullptr;
- ctx->shared->irMapContinueTargetToLoopHead.TryGetValue(targetBlock, loopHead);
- SLANG_ASSERT(loopHead);
- emitIRStmtsForBlocks(
- ctx,
- targetBlock,
- loopHead);
- emit("continue;\n");
- }
- return;
-
- case kIROp_loopTest:
- {
- // Loop condition being tested
- auto t = (IRLoopTest*)terminator;
-
- auto afterBlock = t->getTrueBlock();
- auto exitBlock = t->getFalseBlock();
-
- emit("if(");
- emitIROperand(ctx, t->getCondition());
- emit(")\n{} else {\n");
- emitIRStmtsForBlocks(
- ctx,
- exitBlock,
- nullptr);
- emit("}\n");
-
- // Continue with the block after the test
- block = afterBlock;
- }
- break;
-#endif
-
- case kIROp_unconditionalBranch:
- {
- // Unconditional branch as part of normal
- // control flow. This is either a forward
- // edge to the "next" block in an ordinary
- // block, or a backward edge to the top
- // of a loop.
- auto t = (IRUnconditionalBranch*)terminator;
- auto targetBlock = t->getTargetBlock();
-
- UInt argCount = t->getOperandCount();
- static const UInt kFixedArgCount = 1;
- emitPhiVarAssignments(
- ctx,
- argCount - kFixedArgCount,
- t->getOperands() + kFixedArgCount,
- targetBlock);
-
- block = t->getTargetBlock();
+ // Continue with the region after the loop
+ region = loopRegion->nextRegion;
+ continue;
}
- break;
-
- case kIROp_conditionalBranch:
- // Note: We currently do not generate any plain
- // `conditionalBranch` instructions when lowering
- // to IR, because these would not have the annotations
- // needed to be able to emit high-level control
- // flow from them.
- SLANG_UNEXPECTED("terminator inst");
- return;
-
- case kIROp_switch:
+ case Region::Flavor::Switch:
{
- // A `switch` instruction will always translate
- // to a `switch` statement, but we need to
- // take some care to emit the `case`s in ways
- // that avoid code duplication.
-
- // TODO: Eventually, the "right" way to handle `switch`
- // statements while being more robust about Duff's Device, etc.
- // would be to register each of the case labels in a lookup
- // table, and then walk the blocks in the region between
- // the `switch` and the `break` and then whenever we see a block
- // that matches one of the registered labels, emit the appropriate
- // `case ...:` or `default:` label.
-
- auto t = (IRSwitch*) terminator;
-
- // Extract the fixed arguments.
- auto conditionVal = t->getCondition();
- auto breakLabel = t->getBreakLabel();
- auto defaultLabel = t->getDefaultLabel();
-
- // Register the block to be used for our `break` target
- LabelStack subLabels;
- subLabels.parent = labels;
- subLabels.op = kLabelOp_break;
- subLabels.block = breakLabel;
-
- // We need to track whether we've dealt with
- // the `default` case already.
- bool defaultLabelHandled = false;
-
- // If the `default` case just branches to
- // the join point, then we don't need to
- // do anything with it.
- if(defaultLabel == breakLabel)
- defaultLabelHandled = true;
+ auto switchRegion = (SwitchRegion*) region;
// Emit the start of our statement.
emit("switch(");
- emitIROperand(ctx, conditionVal, IREmitMode::Default);
+ emitIROperand(ctx, switchRegion->condition, IREmitMode::Default);
emit(")\n{\n");
- // Now iterate over the `case`s of the branch
- UInt caseIndex = 0;
- UInt caseCount = t->getCaseCount();
- while(caseIndex < caseCount)
+ auto defaultCase = switchRegion->defaultCase;
+ for(auto currentCase : switchRegion->cases)
{
- // We are going to extract one case here,
- // but we might need to fold additional
- // cases into it, if they share the
- // same label.
- //
- // Note: this makes assumptions that the
- // IR code generator orders cases such
- // that: (1) cases with the same label
- // are consecutive, and (2) any case
- // that "falls through" to another must
- // come right before it in the list.
- auto caseVal = t->getCaseValue(caseIndex);
- auto caseLabel = t->getCaseLabel(caseIndex);
- caseIndex++;
-
- // Emit the `case ...:` for this case, and any
- // others that share the same label
- for(;;)
+ for(auto caseVal : currentCase->values)
{
emit("case ");
emitIROperand(ctx, caseVal, IREmitMode::Default);
emit(":\n");
-
- if(caseIndex >= caseCount)
- break;
-
- auto nextCaseLabel = t->getCaseLabel(caseIndex);
- if(nextCaseLabel != caseLabel)
- break;
-
- caseVal = t->getCaseValue(caseIndex);
- caseIndex++;
}
-
- // The label for the current `case` might also
- // be the label used by the `default` case, so
- // check for that here.
- if(caseLabel == defaultLabel)
+ if(currentCase.Ptr() == defaultCase)
{
emit("default:\n");
- defaultLabelHandled = true;
- }
-
- // Now we need to emit the statements that make
- // up this case. The 99% case will be that it
- // will terminate with a `break` (or a `return`,
- // `continue`, etc.) and so we can pass in
- // `nullptr` for the ending block.
- IRBlock* caseEndLabel = nullptr;
-
- // However, there is also the possibility that
- // this case will fall through to the next, and
- // so we need to prepare for that possibility here.
- //
- // If there is a next case, then we will set its
- // label up as the "end" label when emitting
- // the statements inside the block.
- if(caseIndex < caseCount)
- {
- caseEndLabel = t->getCaseLabel(caseIndex);
}
- // Now emit the statements for this case.
indent();
emit("{\n");
indent();
- emitIRStmtsForBlocks(ctx, caseLabel, caseEndLabel, &subLabels);
- dedent();
- emit("}\n");
- dedent();
- }
-
- // If we've gone through all the cases and haven't
- // managed to encounter the `default:` label,
- // then assume it is a distinct case and handle it here.
- if(!defaultLabelHandled)
- {
- emit("default:\n");
- indent();
- emit("{\n");
- indent();
- emitIRStmtsForBlocks(ctx, defaultLabel, breakLabel, &subLabels);
- emit("break;\n");
+ emitRegion(ctx, currentCase->body);
dedent();
emit("}\n");
dedent();
}
emit("}\n");
- block = breakLabel;
+ // Continue with the region after the `switch`
+ region = switchRegion->nextRegion;
+ continue;
}
break;
}
-
- // After we've emitted the first block, we are safe from accidental
- // cases where we'd emit an entire loop body as a single `continue`,
- // so we can safely switch in whatever labels are intended to be used.
- useLabels = labels;
-
- // If we reach this point, then we've emitted
- // one block, and we have a new block where
- // control flow continues.
- //
- // We need to handle a special case here,
- // when control flow jumps back to the
- // starting block of the range we were
- // asked to work with:
- if (block == begin) return;
+ break;
}
}
+ /// Emit high-level language statements from a structrured region tree.
+ void emitRegionTree(
+ EmitContext* ctx,
+ RegionTree* regionTree)
+ {
+ emitRegion(ctx, regionTree->rootRegion);
+ }
+
// Is an IR function a definition? (otherwise it is a declaration)
bool isDefinition(IRFunc* func)
{
@@ -4527,6 +4281,39 @@ struct EmitVisitor
}
}
+ /// Emit high-level statements for the body of a function.
+ void emitIRFunctionBody(
+ EmitContext* ctx,
+ IRGlobalValueWithCode* code)
+ {
+ // Compute a structured region tree that can represent
+ // the control flow of our function.
+ //
+ RefPtr<RegionTree> regionTree = generateRegionTreeForFunc(
+ code,
+ ctx->getSink());
+
+ // Now that we've computed the region tree, we have
+ // an opportunity to perform some last-minute transformations
+ // on the code to make sure it follows our rules.
+ //
+ // TODO: it would be better to do these transformations earlier,
+ // so that we can, e.g., dump the final IR code *before* emission
+ // starts, but that gets a bit compilcated because we also want
+ // to have the region tree avalable without having to recompute it.
+ //
+ // For now we are just going to do things the expedient way, but
+ // eventually we should allow an IR module to have side-band
+ // storage for dervied structured like the region tree (and logic
+ // for invalidating them when a transformation would break them).
+ //
+ fixValueScoping(regionTree);
+
+ // Now emit high-level code from that structured region tree.
+ //
+ emitRegionTree(ctx, regionTree);
+ }
+
void emitIRSimpleFunc(
EmitContext* ctx,
IRFunc* func)
@@ -4591,8 +4378,7 @@ struct EmitVisitor
emitPhiVarDecls(ctx, func);
// Need to emit the operations in the blocks of the function
-
- emitIRStmtsForBlocks(ctx, func->getFirstBlock(), nullptr, nullptr);
+ emitIRFunctionBody(ctx, func);
dedent();
emit("}\n\n");
@@ -5457,7 +5243,7 @@ struct EmitVisitor
emitIRType(ctx, varType, initFuncName);
Emit("()\n{\n");
indent();
- emitIRStmtsForBlocks(ctx, varDecl->getFirstBlock(), nullptr, nullptr);
+ emitIRFunctionBody(ctx, varDecl);
dedent();
Emit("}\n");
}
diff --git a/source/slang/ir-legalize-types.cpp b/source/slang/ir-legalize-types.cpp
index 969e3eb96..86907dfde 100644
--- a/source/slang/ir-legalize-types.cpp
+++ b/source/slang/ir-legalize-types.cpp
@@ -1462,7 +1462,7 @@ static void legalizeTypes(
// Clean up after any instructions we replaced along the way.
for (auto& lv : context->replacedInstructions)
{
- lv->deallocate();
+ lv->removeAndDeallocate();
}
}
diff --git a/source/slang/ir-restructure-scoping.cpp b/source/slang/ir-restructure-scoping.cpp
new file mode 100644
index 000000000..61b56e190
--- /dev/null
+++ b/source/slang/ir-restructure-scoping.cpp
@@ -0,0 +1,434 @@
+// ir-restructure-scoping.cpp
+#include "ir-restructure-scoping.h"
+
+#include "ir.h"
+#include "ir-insts.h"
+#include "ir-restructure.h"
+
+namespace Slang
+{
+
+/// Try to find the first structured region that represents `block`
+///
+/// In general the same block may appear as multiple regions,
+/// so this will return the first region in the linked list.
+static SimpleRegion* getFirstRegionForBlock(
+ RegionTree* regionTree,
+ IRBlock* block)
+{
+ SimpleRegion* region = nullptr;
+ if( regionTree->mapBlockToRegion.TryGetValue(block, region) )
+ {
+ return region;
+ }
+ return nullptr;
+}
+
+/// Try to find the first structured region that contains `inst`.
+static SimpleRegion* getFirstRegionForInst(
+ RegionTree* regionTree,
+ IRInst* inst)
+{
+ auto ii = inst;
+ while(ii)
+ {
+ if(auto block = as<IRBlock>(ii))
+ return getFirstRegionForBlock(regionTree, block);
+
+ ii = ii->getParent();
+ }
+
+ return nullptr;
+}
+
+/// Compute the depth of a node in the region tree.
+///
+/// This is the number of nodes (including `region`)
+/// on a path from `region` to the root.
+///
+static Int computeDepth(Region* region)
+{
+ Int depth = 0;
+ for( Region* rr = region; rr; rr = rr->getParent() )
+ {
+ depth++;
+ }
+ return depth;
+}
+
+/// Get the `n`th ancestor of `region`.
+///
+/// When `n` is zero, this returns `region`.
+/// When `n` is one, this returns the parent of `region`, and so forth.
+///
+static Region* getAncestor(Region* region, Int n)
+{
+ Region* rr = region;
+ for( Int ii = 0; ii < n; ++ii )
+ {
+ SLANG_ASSERT(rr);
+ rr = rr->getParent();
+ }
+ return rr;
+}
+
+/// Find a region that is an ancestor of both `left` and `right`.
+static Region* findCommonAncestorRegion(
+ Region* left,
+ Region* right)
+{
+ // Rather than blinding search through each ancestor of `left`
+ // and see if it is also an ancestor of `right` and vice-versa,
+ // let's try to be smart about this.
+ //
+ // We will start by computing the depth of `left` and `right`:
+ //
+ Int leftDepth = computeDepth(left);
+ Int rightDepth = computeDepth(right);
+
+ // Whatever the common ancestor is, it can't be any deeper
+ // than the minimum of these two depths.
+ //
+ Int minDepth = Math::Min(leftDepth, rightDepth);
+
+ // Let's fetch the ancestor of each of `left` and `right`
+ // corresponding to that depth:
+ //
+ Region* leftAncestor = getAncestor(left, leftDepth - minDepth);
+ Region* rightAncestor = getAncestor(right, rightDepth - minDepth);
+
+ // Now we know that `leftAncestor` and `rightAncestor`
+ // must have the same depth. Let's go ahead and assert
+ // it just to be safe:
+ //
+ SLANG_ASSERT(computeDepth(leftAncestor) == minDepth);
+ SLANG_ASSERT(computeDepth(rightAncestor) == minDepth);
+
+ // If `leftAncestor` and `rightAncestor` are the same node,
+ // then we've found a common ancestor, otherwise we should
+ // look at their parents. Because the depth must match
+ // on both sides, we will never risk missing an ancestor.
+ //
+ while( leftAncestor != rightAncestor )
+ {
+ leftAncestor = leftAncestor->getParent();
+ rightAncestor = rightAncestor->getParent();
+ }
+
+ // Okay, we've found a common ancestor.
+ //
+ Region* commonAncestor = leftAncestor;
+ return commonAncestor;
+}
+
+/// Find a simple region that is an ancestor of both `left` and `right`.
+static SimpleRegion* findSimpleCommonAncestorRegion(
+ Region* left,
+ Region* right)
+{
+ // Start by finding a common ancestor without worrying about it being simple.
+ Region* ancestor = findCommonAncestorRegion(left, right);
+
+ // Now search for a simple region up the tree.
+ while( ancestor )
+ {
+ if(ancestor->getFlavor() == Region::Flavor::Simple)
+ return (SimpleRegion*) ancestor;
+
+ ancestor = ancestor->getParent();
+ }
+
+ // This shouldn't ever occur. The root of the region tree should
+ // be a simple regions that represents the entry block of the
+ // function.
+ //
+ SLANG_UNEXPECTED("no common ancestor found in region tree");
+ UNREACHABLE_RETURN(nullptr);
+}
+
+IRInst* getDefaultInitVal(
+ IRBuilder* builder,
+ IRType* type)
+{
+ switch( type->op )
+ {
+ default:
+ return nullptr;
+
+ case kIROp_BoolType:
+ return builder->getBoolValue(false);
+
+ case kIROp_IntType:
+ case kIROp_UIntType:
+ case kIROp_UInt64Type:
+ return builder->getIntValue(type, 0);
+
+ case kIROp_HalfType:
+ case kIROp_FloatType:
+ case kIROp_DoubleType:
+ return builder->getFloatValue(type, 0.0);
+
+ // TODO: handle vector/matrix types here, by
+ // creating an appropriate scalar value and
+ // then "splatting" it.
+ }
+}
+
+/// Initialize a variable to a sane default value, if possible.
+void defaultInitializeVar(
+ IRBuilder* builder,
+ IRVar* var,
+ IRType* type)
+{
+ IRInst* initVal = nullptr;
+ switch( type->op )
+ {
+ case kIROp_VoidType:
+ default:
+ // By default, see if we can synthesize an IR value
+ // to be used as the default, and allow the logic
+ // below to store it into the variable.
+ initVal = getDefaultInitVal(builder, type);
+ break;
+
+ // TODO: Handle aggregate types (structures, arrays)
+ // explicitly here, since they need to be careful about
+ // the cases where an element/field type might not
+ // be something we can default-initialize.
+ }
+
+ if( initVal )
+ {
+ builder->emitStore(var, initVal);
+ }
+}
+
+/// Detect and fix any structured scoping issues for a given `def` instruction.
+///
+/// The `defRegion` should be the region that contains `def`, and `regionTree`
+/// should be the region tree for the function that contains `def`.
+///
+static void fixValueScopingForInst(
+ IRInst* def,
+ SimpleRegion* defRegion,
+ RegionTree* regionTree)
+{
+ // This algorithm should not consider "phi nodes" for now,
+ // because the emit logic will already create variables for them.
+ // We could consider folding the logic to move out of SSA form
+ // into this function, but that would add a lot of complexity for now.
+ if(def->op == kIROp_Param)
+ return;
+
+ // We would have a scoping violation if there exists some
+ // use `u` of `def` such that the region containing `u`
+ // (call it `useRegion`) is not a descendent of `defRegion`
+ // in the region tree.
+ //
+ // If there are no scoping violations, we don't want to do
+ // anything. If there *are* any scoping violations, then
+ // we ill need to introduce a temporary `tmp`, store into
+ // it right after `def`, and then load from it at any "bad"
+ // use sites.
+ //
+ // Of course, for the whole thing to work, we also need
+ // to put `tmp` into a block somwhere, and it needs to
+ // be a block that is visible to all of the uses, or we
+ // are just back int the same mess again.
+ //
+ // The right block to use for inserting `tmp` is the least
+ // common ancestor of `def` and all the "bad" uses, so
+ // we will get a bit "clever" and fold in the search for
+ // bad uses with the computation of the region we should
+ // insert `tmp` into (to avoid looping over the uses
+ // twice).
+ //
+ SimpleRegion* insertRegion = defRegion;
+ IRVar* tmp = nullptr;
+
+ // If we end up needing to insert code we'll need an IR builder,
+ // so we will go ahead and create one now.
+ //
+ // TODO: the logic to compute `module` here could be hoisted
+ // out earlier, rather than being done per-instruction.
+ //
+ IRModule* module = regionTree->irCode->getModule();
+
+ SharedIRBuilder sharedBuilder;
+ sharedBuilder.session = module->session;
+ sharedBuilder.module = module;
+
+ IRBuilder builder;
+ builder.sharedBuilder = &sharedBuilder;
+
+ // Because we will be changing some of the uses of `def`
+ // to use other values while we iterate the list, we
+ // need to be a bit careful and extract the next use
+ // in the linked list *before* we operator on `u`.
+ //
+ IRUse* nextUse = nullptr;
+ for( auto u = def->firstUse; u; u = nextUse )
+ {
+ nextUse = u->nextUse;
+
+ // Looking at the use site `u`, we'd like to check if
+ // it violates our scoping rules.
+ //
+ // As a simple early-exit case, if the user is in
+ // the same block as the definition, there are no problems.
+ //
+ IRInst* user = u->getUser();
+ if(user->getParent() == defRegion->block)
+ continue;
+
+ // Otherwise, let's find the structures control-flow
+ // region that holds the user. We expect to always
+ // find one, because the use site must be in the same
+ // function.
+ //
+ // TODO: Double check that logic if we ever introduce
+ // things like nested function.
+ //
+ SimpleRegion* useRegion = getFirstRegionForInst(regionTree, user);
+
+ // If there is no region associated with the use, then
+ // the use must be in unreachable code (part of the CFG,
+ // but not part of the region tree). We will skip
+ // such uses for now, since they won't even appear in
+ // the output.
+ //
+ if(!useRegion)
+ continue;
+
+ // Now we want to check if `useRegion` is a child/descendent
+ // of a region that has the same block as `defRegion`.
+ // If it is, then there is no scoping problem with this use.
+ //
+ if(useRegion->isDescendentOf(defRegion->block))
+ continue;
+
+ // If we've gotten this far, we know that `u` is a "bad"
+ // use of `def`, and needs fixing.
+ //
+ // We will create the `tmp` variable on demand, so
+ // that we create it when the first "bad" use is encountered,
+ // and then re-use it for subsequent bad uses.
+ //
+ if( !tmp )
+ {
+ // We will create a temporary to represent `def`,
+ // and insert a `store` into it right after `def`.
+ //
+ // Note: we are inserting the new variable right
+ // after `def` for now, just because we don't
+ // yet know the final region that it should be
+ // placed into. We will move it to the correct
+ // location when we are done.
+ //
+ builder.setInsertBefore(def->getNextInst());
+ tmp = builder.emitVar(def->getDataType());
+ builder.emitStore(tmp, def);
+ }
+
+ // In order to know where `tmp` should be defined
+ // at the end of the algorithm, we need to compute
+ // a valid `insertRegion` that is an ancestor of
+ // all of the use sites (and it also a simple region
+ // so that we can insert into its IR block).
+ //
+ // We need to deal with one complexity in our restructuring
+ // process, which is that a block may be duplicated into
+ // one or more regions, so we loop over all the regions
+ // for the same block as `useRegion`.
+ //
+ for(auto rr = useRegion; rr; rr = rr->nextSimpleRegionForSameBlock)
+ {
+ insertRegion = findSimpleCommonAncestorRegion(
+ insertRegion,
+ rr);
+ }
+
+ // To fix up the use `u`, we will need to change
+ // it from using `def` to using a load from `tmp`
+ //
+ builder.setInsertBefore(user);
+ IRInst* tmpVal = builder.emitLoad(tmp);
+
+ // We are clobbering the value used by the `IRUse` `u`,
+ // while will cut it out of the list of uses for `def`.
+ // We need to be careful when doing this to not disrupt
+ // our iteration of the uses of `def`, so we carefully
+ // used the `nextUse` temporary at the start of the loop.
+ //
+ u->set(tmpVal);
+ }
+
+ // At the end of the loop, the `tmp` variable will have
+ // been created if and only if we fixed up anything.
+ //
+ if( tmp )
+ {
+ // If we created a temporary, then now we need to move
+ // its definition to the right place, which is the
+ // `insertRegion` that we computed during the loop.
+ //
+ // We'd like to insert our temporary near the top
+ // of the region, since that is the conventional
+ // place for local variables to go.
+ //
+ tmp->insertBefore(
+ insertRegion->block->getFirstOrdinaryInst());
+
+ // The whole point of the transformation we are doing
+ // here is that `def` is not on the "obvious" control
+ // flow path to one or more uses (which are now using
+ // `tmp`), but that means that it might not be "obvious"
+ // to a downstream compiler that `tmp` always gets
+ // initialized (by the code we inserted after `def`)
+ // before each of these use sites.
+ //
+ // We *know* that things are valid as long as our
+ // dominator tree was valid - there is no way to
+ // get to the block that loads from `tmp` without passing
+ // through the block that computes `def` (and then
+ // stores it into `tmp`) first.
+ //
+ // To avoid warnings/errros, we will go ahead and try
+ // to emit logic to "default initialize" the `tmp`
+ // variable if possible.
+ //
+ builder.setInsertBefore(tmp->getNextInst());
+ defaultInitializeVar(&builder, tmp, def->getDataType());
+ }
+}
+
+void fixValueScoping(RegionTree* regionTree)
+{
+ // We are going to have to walk through every instruction
+ // in the code of the function to detect an bad cases.
+ //
+ auto code = regionTree->irCode;
+ for(auto block : code->getBlocks())
+ {
+ // All of the instruction in `block` will have the same
+ // parent region, so we will look it up now rather than
+ // have to re-do this work on a per-instruction basis.
+ //
+ auto parentRegion = getFirstRegionForBlock(regionTree, block);
+
+ // If a block has no region then it must be unreachable,
+ // so we will skip it entirely for this pass.
+ //
+ // TODO: we should be eliminating unrechable blocks anyway.
+ //
+ if(!parentRegion)
+ continue;
+
+ for(auto inst : block->getChildren())
+ {
+ fixValueScopingForInst(inst, parentRegion, regionTree);
+ }
+ }
+}
+
+}
diff --git a/source/slang/ir-restructure-scoping.h b/source/slang/ir-restructure-scoping.h
new file mode 100644
index 000000000..7840dda80
--- /dev/null
+++ b/source/slang/ir-restructure-scoping.h
@@ -0,0 +1,24 @@
+// ir-restructure-scoping.h
+#pragma once
+
+namespace Slang
+{
+
+class RegionTree;
+
+/// Fix cases where a value might be used in a non-nested region.
+///
+/// There can be cases where an IR value V in block A is used in
+/// some block B, where A dominates B, *but* when we constructed
+/// the region tree, the block B is not in a child/descendent
+/// region of A's region, so that it won't be visible through the
+/// scoping rules of a target language.
+///
+/// This function detects such cases, and fixes them up by inserting
+/// new temporaries into the IR code so that values that need
+/// to survive across blocks are communicated through variables
+/// declared at a sufficiently broad scope.
+///
+void fixValueScoping(RegionTree* regionTree);
+
+}
diff --git a/source/slang/ir-restructure.cpp b/source/slang/ir-restructure.cpp
new file mode 100644
index 000000000..98311b11b
--- /dev/null
+++ b/source/slang/ir-restructure.cpp
@@ -0,0 +1,662 @@
+// ir-restructure.cpp
+#include "ir-restructure.h"
+
+#include "ir.h"
+#include "ir-insts.h"
+
+namespace Slang
+{
+ bool Region::isDescendentOf(Region* other)
+ {
+ Region* rr = this;
+ while( rr )
+ {
+ if(rr == other)
+ return true;
+
+ rr = rr->getParent();
+ }
+ return false;
+ }
+
+ bool Region::isDescendentOf(IRBlock* block)
+ {
+ Region* rr = this;
+ while( rr )
+ {
+ if( rr->getFlavor() == Region::Flavor::Simple )
+ {
+ SimpleRegion* simpleRegion = (SimpleRegion*) rr;
+ if(simpleRegion->block == block)
+ return true;
+ }
+
+ rr = rr->getParent();
+ }
+ return false;
+ }
+
+ /// An "active" label during control flow (re)structuring.
+ struct LabelStack
+ {
+ /// Possible operations associated with labels.
+ enum class Op
+ {
+ Break,
+ Continue,
+
+ CountOf,
+ };
+
+ /// What kind of operation does a branch to this label represent?
+ Op op;
+
+ /// The next label down on the stack
+ LabelStack* parent;
+
+ /// The block the represents this label in the IR control flow graph.
+ IRBlock* block;
+
+ /// The region that represents this label in the structured program
+ Region* region;
+ };
+
+ /// State used when restructuring control flow.
+ struct ControlFlowRestructuringContext
+ {
+ /// Sink to use when diagnosing errors in control-flow restructuring.
+ ///
+ /// The restructuring pass should be able to handle anything the front-end
+ /// throws at it, so these errors will all be unexpected. Still, we need
+ /// a way to report them cleanly without crashing the process.
+ ///
+ DiagnosticSink* sink = nullptr;
+ DiagnosticSink* getSink() { return sink; }
+
+ /// The region tree we are in the process of building.
+ RegionTree* regionTree = nullptr;
+ };
+
+ /// Convert a range of blocks in the IR CFG into a region.
+ ///
+ /// We want to generate a region that stands in for the
+ /// blocks that are logically in the internal [begin, end)
+ /// which we consider as representing a single-entry multiple-exit
+ /// sub-graph. Note that `end` is *not* part of the sub-graph,
+ /// but instead points to a block that is logically "after"
+ /// the sub-graph. `end` can be `null` to indicate that the
+ /// sub-graph extends as far as possible.
+ ///
+ /// Because there can be multiple exits, control flow may
+ /// exit the sub-graph without branching to `end`, any
+ /// such "non-local" branching should be to one of the
+ /// blocks stored in the current `LabelStack`.
+ ///
+ // TODO: Eventually we should replace all of this logic with
+ // a variation on the "Relooper" algorithm as it is used
+ // in Emscripten.
+ //
+ static RefPtr<Region> generateRegionsForIRBlocks(
+ ControlFlowRestructuringContext* ctx,
+ Region* inParentRegion,
+ IRBlock* begin,
+ IRBlock* end,
+ LabelStack* initialLabels, // Labels to use at the start
+ LabelStack* labels = nullptr) // Labels to switch to after emitting first basic block
+ {
+ if(!labels)
+ labels = initialLabels;
+ auto useLabels = initialLabels;
+
+ //
+ // We will try to build up as long of a sequential/simple region
+ // as possible, to avoid deep recursion in this algorithm.
+ //
+ RefPtr<Region> resultRegion = nullptr;
+ RefPtr<Region>* resultLink = &resultRegion;
+
+ // As we move along, the parent region to use for regions
+ // we create will shift, so we need a temporary to track
+ // the current parent region.
+ //
+ Region* parentRegion = inParentRegion;
+
+ //
+ // We will start with the `begin` block, and try to proceed
+ // sequentially until we see the `end` block, or run into
+ // an edge that exits teh region.
+ //
+ IRBlock* block = begin;
+ while(block != end)
+ {
+ // If the block we are trying to emit has been registered as a
+ // destination label (e.g. for a loop or `switch`) then we
+ // need to exit the current region, which amounts to generating
+ // a `break` or `continue` operation.
+ //
+ // TODO: we eventually need to handle the possibility of
+ // multi-level break/continue targets, which could be challenging.
+
+ // Because we will only support single-level break/continue, we
+ // want to resolve what is the most recent label that is "active"
+ // for the given operation (`break` or `continue`).
+ //
+ // We will do this with a naive loop, just to keep things simple.
+ // We start with no block "regsitered" as the target for each
+ // operation.
+ //
+ IRBlock* registeredBlock[(int)LabelStack::Op::CountOf] = {};
+ for( auto ll = useLabels; ll; ll = ll->parent )
+ {
+ // For each active label, see if it is the first one
+ // we encounter for the given op.
+ //
+ if(!registeredBlock[(int)ll->op])
+ {
+ registeredBlock[(int)ll->op] = ll->block;
+ }
+ }
+
+ // Next we will search through *all* of the registered labels,
+ // and see if one of them matches the current `block`.
+ //
+ for(auto ll = useLabels; ll; ll = ll->parent)
+ {
+ // Does this label match the block we are trying to translate?
+ if(ll->block != block)
+ continue;
+
+ // Okay, the block we are trying to generate code for is a label
+ // that we should branch to (we shouldn't just emit the code here
+ // and now...)
+ //
+ // We should first confirm that the block is the inner-most label
+ // registered for the given control-flow op (`break` or `continue`)
+ // because if it *isn't* we currently can't generate code.
+ //
+ if(block != registeredBlock[(int)ll->op])
+ {
+ ctx->getSink()->diagnose(block, Diagnostics::multiLevelBreakUnsupported);
+ }
+
+ // Now we need to create a structured `break` or `continue` operation
+ // to match the operation associated with the target.
+ //
+ switch(ll->op)
+ {
+ case LabelStack::Op::Break:
+ {
+ auto outerRegion = (BreakableRegion*) ll->region;
+ RefPtr<BreakRegion> breakRegion = new BreakRegion(parentRegion, outerRegion);
+
+ *resultLink = breakRegion;
+ resultLink = nullptr;
+ }
+ break;
+
+ case LabelStack::Op::Continue:
+ {
+ auto outerRegion = (LoopRegion*) ll->region;
+ RefPtr<ContinueRegion> continueRegion = new ContinueRegion(parentRegion, outerRegion);
+
+ *resultLink = continueRegion;
+ resultLink = nullptr;
+ }
+ break;
+ }
+
+ // If the `block` matched an active label, then we should have
+ // created a branch, and there is nothing to be done here.
+ return resultRegion;
+ }
+
+ // We now know that the given `block` is part of our control-flow region,
+ // so we need to output a simple region that executes the code in that block.
+ //
+ RefPtr<SimpleRegion> simpleRegion = new SimpleRegion(parentRegion, block);
+
+ // We need to register the mapping from `block` to this region, but in
+ // general this isn't a one-to-one mapping, but rather one-to-many.
+ // This is because a "continue clause" in a `for` loop might get duplicated
+ // at each `continue` site in the output code. To deal with this
+ // we build a singly-linked list of regions for each block.
+ //
+ // TODO: confirm that continue clauses are the only case that leads
+ // to duplication.
+ //
+ // TODO: remove this workaround once we have a more powerful restructuring
+ // pass that avoids duplicating blocks (by introducing new temporaries...)
+ //
+ SimpleRegion* nextSimpleRegionForSameBlock = nullptr;
+ ctx->regionTree->mapBlockToRegion.TryGetValue(block, nextSimpleRegionForSameBlock);
+ ctx->regionTree->mapBlockToRegion[block] = simpleRegion;
+
+ *resultLink = simpleRegion;
+ resultLink = &simpleRegion->nextRegion;
+ parentRegion = simpleRegion;
+
+ // The simple region we created will represent all of the non-terminator
+ // instructions in the `block`, so now we need to figure out what to
+ // create to represent that terminator.
+ //
+ auto terminator = block->getTerminator();
+ SLANG_ASSERT(terminator != nullptr);
+ switch (terminator->op)
+ {
+ default:
+ case kIROp_conditionalBranch:
+ // Note: we don't currently generate ordinary `conditionalBranch` instructions,
+ // and instead only generate `ifElse` instructions, which include additional
+ // information that can inform our control-flow restructuring pass.
+ //
+ SLANG_UNEXPECTED("unhandled terminator instruction opcode");
+ // fall through to:
+ case kIROp_unreachable:
+ case kIROp_ReturnVal:
+ case kIROp_ReturnVoid:
+ case kIROp_discard:
+ // These cases are all simple terminators that can be handled as-is
+ // without needing to construct a separate `Region` to encapsulate them.
+ //
+ // We will cap off the current sequence of simple regions and return.
+ //
+ *resultLink = nullptr;
+ return resultRegion;
+
+ case kIROp_ifElse:
+ {
+ // Here we have a two-way branch, so that we will construct a
+ // region representing an `if` statement.
+ //
+ auto ifInst = (IRIfElse*)terminator;
+ auto condition = ifInst->getCondition();
+ auto trueBlock = ifInst->getTrueBlock();
+ auto falseBlock = ifInst->getFalseBlock();
+ auto afterBlock = ifInst->getAfterBlock();
+
+
+ RefPtr<IfRegion> ifRegion = new IfRegion(parentRegion, condition);
+
+ // The region for the "then" part of things will consist of
+ // the range of blocks `[trueBlock, afterBlock)`.
+ //
+ // This logic assumes that `afterBlock` is a valid structured
+ // "join point" such that any branch out of the sub-region
+ // either leads to `afterBlock` *or* one of the labels
+ // that is already present on our label stack.
+ //
+ ifRegion->thenRegion = generateRegionsForIRBlocks(
+ ctx,
+ ifRegion,
+ trueBlock,
+ afterBlock,
+ labels);
+
+ // Generating a region for the `else` part is similar.
+ // Note that it is possible for this to be a `null`
+ // region, if `falseBlock == afterBlock`.
+ //
+ ifRegion->elseRegion = generateRegionsForIRBlocks(
+ ctx,
+ ifRegion,
+ falseBlock,
+ afterBlock,
+ labels);
+
+ *resultLink = ifRegion;
+ resultLink = &ifRegion->nextRegion;
+ parentRegion = ifRegion;
+
+ // Continue with the block after the `ifElse` instruction.
+ block = afterBlock;
+ }
+ break;
+
+ case kIROp_loop:
+ {
+ // The terminator in this case is the header for a structured loop.
+ //
+ auto loopInst = (IRLoop*) terminator;
+ auto bodyBlock = loopInst->getTargetBlock();
+ auto afterBlock = loopInst->getBreakBlock();
+
+ RefPtr<LoopRegion> loopRegion = new LoopRegion(parentRegion, loopInst);
+
+ // We will need to set up entries on our label stack to
+ // represent the targets for `break` or `continue`
+ // operations inside the loop.
+ //
+ // First we set up the stack entry for the `break` label,
+ // which will refer to the block *after* the loop.
+ //
+ // The region we specify for the label will still be
+ // the loop region, though, because the loop is what
+ // we are breaking out of.
+ //
+ LabelStack loopBreakLabelStack;
+ loopBreakLabelStack.parent = labels;
+ loopBreakLabelStack.block = afterBlock;
+ loopBreakLabelStack.region = loopRegion;
+ loopBreakLabelStack.op = LabelStack::Op::Break;
+
+ //
+ // The `continue` label warrants a bit more careful explanation,
+ // because it will *not* refer to the block that was regsitered
+ // as the continue target in the IR `loop` instruction. This
+ // is because we will always emit our loops as `for(;;) { ... }`
+ // with no continue clause at all, so that a `continue` in
+ // the output code will always refer to the top of the loop.
+ //
+ // This means that the `continue` label for the purposes of
+ // structured control flow will be the start of the loop body:
+ //
+ LabelStack loopContinueLabelStack;
+ loopContinueLabelStack.parent = &loopBreakLabelStack;
+ loopContinueLabelStack.block = bodyBlock;
+ loopContinueLabelStack.region = loopRegion;
+ loopContinueLabelStack.op = LabelStack::Op::Continue;
+ //
+ // Note: by ignoring the original continue block from the
+ // high-level loop, we create a situation where that code
+ // might get emitted more than once (once per implicit
+ // or explicit `continue` site in the original program).
+ //
+ // That is an acceptable trade-off for now, because continue
+ // blocks will usually be small (and fxc makes the same choice),
+ // but it could lead to Bad Things if somebody were to call
+ // a function in their continue clause, and that function does
+ // a compute shader barrier operation.
+ //
+ // A better long-term fix is to take a high-level loop like:
+ //
+ // for(A; B; C) { ... continue; ... break; ... }
+ //
+ // and translate it into something like the following (assuming
+ // we have labeled statements and multi-level `break`):
+ //
+ // A;
+ // Outer: for(;;) {
+ // Inner: for(;;) {
+ // if(B) {} else break Outer;
+ // ...
+ // break Inner; // `continue` becomes break of inner loop
+ // ...
+ // break Outer; // `break` becomes break of outer loop
+ // ...
+ // break; // inner loop unconditionally breaks at the end
+ // }
+ // C; // continue clause comes after inner loop
+ // }
+ //
+ // If you draw up a control flow graph for that code, you'll find
+ // it is equivalent to the orignal `for` loop, but now supports
+ // arbitrary code (not just a single expression) for the continue clause.
+ // Unlike the current code-duplication solution, `C` appears only once
+ // in the output, and seems to clearly be at a "joint point" for control
+ // flow so that it is clear that a barrier there is valid in GLSL.
+ //
+ // Anyway, back our regularly scheduled programming.
+ //
+ // With the label stack stuff set up, we want to take the region
+ // of the CFG defined by `[bodyBlock, afterBlock)` and turn it into
+ // the body region for our loop.
+ //
+ // The only thing we want to be a little bit careful about is
+ // that we don't want the logic at the top of this function
+ // that looks for a block it can translate into a `continue`
+ // to trigger on `bodyBlock`, since that means we'd just turn
+ // the whole body into a single `continue`.
+ //
+ // To avoid this problem, we pass in two different label stacks:
+ // one to use for the first block, and one to use for subsequent
+ // blocks.
+ //
+ loopRegion->body = generateRegionsForIRBlocks(
+ ctx,
+ loopRegion,
+ bodyBlock,
+ // TODO: should we pass `afterBlock` here instead of `null`?
+ nullptr,
+ // For the first block, we only want the `break` label active
+ &loopBreakLabelStack,
+ // After the first block, we can safely use the `continue` label too
+ &loopContinueLabelStack);
+
+ *resultLink = loopRegion;
+ resultLink = &loopRegion->nextRegion;
+ parentRegion = loopRegion;
+
+ // Continue with the block after the loop
+ block = afterBlock;
+ }
+ break;
+
+ case kIROp_unconditionalBranch:
+ {
+ // Here we have an unconditional branch that was
+ // not covered by one of our labels for non-local
+ // branches (`break` or `continue`).
+ //
+ // We will thus assume that the target of the
+ // branch is part of the same region we are building,
+ // and continue with the target block;
+ //
+ auto branchInst = (IRUnconditionalBranch*) terminator;
+ block = branchInst->getTargetBlock();
+ }
+ break;
+
+ case kIROp_switch:
+ {
+ // A `switch` instruction will always translate
+ // to a `SwitchRegion` and then to a `switch` statement.
+ //
+ // We will need to take care to emit `case`s in ways
+ // that avoid code duplication.
+ //
+ // The logic here isn't going to be robust in edge cases
+ // (please don't write Duff's Device in Slang just yet).
+ // Doing significantly better than what is here would
+ // require something like the Relooper algorithm, though.
+ //
+ auto switchInst = (IRSwitch*) terminator;
+ auto condition = switchInst->getCondition();
+ auto breakLabel = switchInst->getBreakLabel();
+ auto defaultLabel = switchInst->getDefaultLabel();
+
+ RefPtr<SwitchRegion> switchRegion = new SwitchRegion(parentRegion, condition);
+
+ // A direct branch to the block after the `switch` can
+ // be emitted as a `break` statement, so we will register
+ // the appropriate label on a label stack:
+ //
+ LabelStack switchBreakLabelStack;
+ switchBreakLabelStack.parent = labels;
+ switchBreakLabelStack.op = LabelStack::Op::Break;
+ switchBreakLabelStack.block = breakLabel;
+ switchBreakLabelStack.region = switchRegion;
+
+ // We need to track whether we've dealt with
+ // the `default` case already.
+ //
+ bool defaultLabelHandled = false;
+
+ // If the `default` case just branches to
+ // the join point, then we don't need to
+ // do anything with it.
+ //
+ if(defaultLabel == breakLabel)
+ defaultLabelHandled = true;
+
+ // We will now iterate over the different `case`s, and
+ // try to group them together to minimize the number of
+ // sub-regions we have to create.
+ //
+ UInt caseIndex = 0;
+ UInt caseCount = switchInst->getCaseCount();
+ while(caseIndex < caseCount)
+ {
+ // We are going to extract one case here,
+ // but we might need to fold additional
+ // cases into it, if they share the
+ // same label.
+ //
+ // Note: this makes assumptions that the
+ // IR code generator orders cases such
+ // that: (1) cases with the same label
+ // are consecutive, and (2) any case
+ // that "falls through" to another must
+ // come right before it in the list.
+
+ auto caseVal = switchInst->getCaseValue(caseIndex);
+ auto caseLabel = switchInst->getCaseLabel(caseIndex);
+ caseIndex++;
+
+ RefPtr<SwitchRegion::Case> currentCase = new SwitchRegion::Case();
+ switchRegion->cases.Add(currentCase);
+
+ // Add the case value for this case, and any
+ // others that share the same label
+ //
+ for(;;)
+ {
+ currentCase->values.Add(caseVal);
+
+ // Are there any more `case`s left?
+ //
+ if(caseIndex >= caseCount)
+ break;
+
+ // Does the next `case` share the same target label?
+ auto nextCaseLabel = switchInst->getCaseLabel(caseIndex);
+ if(nextCaseLabel != caseLabel)
+ break;
+
+ // If those checks passed, then we will fold
+ // the next `case` into the same region, and
+ // keep looking.
+ caseVal = switchInst->getCaseValue(caseIndex);
+ caseIndex++;
+ }
+
+ // The label for the current `case` might also
+ // be the label used by the `default` case, so
+ // check for that here.
+ //
+ if(caseLabel == defaultLabel)
+ {
+ switchRegion->defaultCase = currentCase;
+ defaultLabelHandled = true;
+ }
+
+ // Now we need to generate a region for the instructions
+ // that make up this case. The 99% case will be that it
+ // will terminate with a `break` (or a `return`,
+ // `continue`, etc.) and so we can pass in `nullptr`
+ // for the ending block.
+ //
+ IRBlock* caseEndLabel = nullptr;
+
+ // However, there is also the possibility that
+ // this `case` will fall through to the next, and
+ // so we need to prepare for that possibility here.
+ //
+ // If there *is* a next `case`, then we will set its
+ // label up as the "end" label when emitting
+ // the statements inside the block.
+ if(caseIndex < caseCount)
+ {
+ caseEndLabel = switchInst->getCaseLabel(caseIndex);
+ }
+
+ // Now we can actually generate the region.
+ //
+ currentCase->body = generateRegionsForIRBlocks(
+ ctx,
+ switchRegion,
+ caseLabel,
+ caseEndLabel,
+ &switchBreakLabelStack);
+ }
+
+ // If we've gone through all the cases and haven't
+ // managed to encounter the `default:` label,
+ // then assume it is a distinct case and handle it here.
+ if(!defaultLabelHandled)
+ {
+ RefPtr<SwitchRegion::Case> defaultCase = new SwitchRegion::Case();
+ switchRegion->cases.Add(defaultCase);
+
+ // Note: we use `null` instead of `breakLabel` as the end block
+ // here, to ensure that the `default` region will end with an
+ // explicit `break` rather than just falling off the end.
+
+ defaultCase->body = generateRegionsForIRBlocks(
+ ctx,
+ switchRegion,
+ defaultLabel,
+ nullptr,
+ &switchBreakLabelStack);
+
+ switchRegion->defaultCase = defaultCase;
+ }
+
+ *resultLink = switchRegion;
+ resultLink = &switchRegion->nextRegion;
+ parentRegion = switchRegion;
+
+ // Continue with the block after the `switch`
+ block = breakLabel;
+ }
+ break;
+ }
+
+ // After we've emitted the first block, we are safe from accidental
+ // cases where we'd emit an entire loop body as a single `continue`,
+ // so we can safely switch in whatever labels are intended to be used.
+ useLabels = labels;
+
+ // If we reach this point, then we've emitted
+ // one block, and we have a new block where
+ // control flow continues.
+ //
+ // We need to handle a special case here,
+ // when control flow jumps back to the
+ // starting block of the range we were
+ // asked to work with:
+ if (block == begin)
+ {
+ break;
+ }
+ }
+
+ // We seem to have reached the rend of the region
+ // without anything special happening. This means
+ // we should cap off the current sequence of regions
+ // and return what we have.
+ //
+ *resultLink = nullptr;
+ return resultRegion;
+ }
+
+ RefPtr<RegionTree> generateRegionTreeForFunc(
+ IRGlobalValueWithCode* code,
+ DiagnosticSink* sink)
+ {
+ RefPtr<RegionTree> regionTree = new RegionTree();
+ regionTree->irCode = code;
+
+ ControlFlowRestructuringContext restructuringContext;
+ restructuringContext.sink = sink;
+ restructuringContext.regionTree = regionTree;
+
+ regionTree->rootRegion = generateRegionsForIRBlocks(
+ &restructuringContext,
+ nullptr,
+ code->getFirstBlock(),
+ nullptr,
+ nullptr);
+
+ return regionTree;
+ }
+}
diff --git a/source/slang/ir-restructure.h b/source/slang/ir-restructure.h
new file mode 100644
index 000000000..d27f7dbc8
--- /dev/null
+++ b/source/slang/ir-restructure.h
@@ -0,0 +1,261 @@
+// ir-restructure.h
+#pragma once
+
+#include "../core/basic.h"
+
+namespace Slang
+{
+ class DiagnosticSink;
+ struct IRBlock;
+ struct IRGlobalValueWithCode;
+ struct IRInst;
+ struct IRLoop;
+
+ /// A structured control-flow region.
+ ///
+ /// A `Region` is used to layer structured control flow information
+ /// over an existing IR control flow graph (CFG). Each `Region`
+ /// represents a sub-graph of the CFG such that control always
+ /// enters at the start of the region.
+ ///
+ class Region : public RefObject
+ {
+ public:
+ enum class Flavor
+ {
+ Simple,
+ If,
+ Break,
+ Continue,
+ Loop,
+ Switch,
+ };
+
+ Flavor getFlavor() { return flavor; }
+
+ Region* getParent() { return parent; }
+
+ /// Is this region a descendent of `other`?
+ ///
+ /// For the purpose of this query, a region
+ /// is a descendent of itself.
+ bool isDescendentOf(Region* other);
+
+ /// Is this region a descendent of `block`?
+ ///
+ /// This tests is the region is a descendent
+ /// of any simple region for `block`.
+ bool isDescendentOf(IRBlock* block);
+
+ protected:
+ Region(Flavor flavor, Region* parent)
+ : flavor(flavor)
+ , parent(parent)
+ {}
+
+ /// What kind of region is this?
+ Flavor flavor;
+
+ /// The parent region of this region.
+ Region* parent;
+ };
+
+ /// Base type for regions that have a "next" region.
+ ///
+ /// While we think of it as a region to execute
+ /// after this region, the `nextRegion` is actually
+ /// a *child* region, in that it can see local
+ /// values that were defined in this parent region
+ /// (and any other ancestor regions).
+ class SeqRegion : public Region
+ {
+ protected:
+ SeqRegion(Flavor flavor, Region* parent)
+ : Region(flavor, parent)
+ {}
+
+ public:
+ /// The (child) region to execute after this one.
+ RefPtr<Region> nextRegion;
+ };
+
+ /// A simple region that encapsulates a basic block.
+ ///
+ class SimpleRegion : public SeqRegion
+ {
+ public:
+ SimpleRegion(Region* parent, IRBlock* block)
+ : SeqRegion(Region::Flavor::Simple, parent)
+ , block(block)
+ {}
+
+ /// The basic block for this region.
+ IRBlock* block = nullptr;
+
+ /// The next simple region for the same block
+ ///
+ /// A single IR basic block may turn into multiple regions,
+ /// if the restructuring pass has to duplicate it (this
+ /// currently happens for the continue clause in a `for`
+ /// loop if it has multiple `continue` sites.
+ ///
+ SimpleRegion* nextSimpleRegionForSameBlock = nullptr;
+ };
+
+ /// A conditional region, corresponding to an `if`
+ ///
+ class IfRegion : public SeqRegion
+ {
+ public:
+ IfRegion(Region* parent, IRInst* condition)
+ : SeqRegion(Region::Flavor::If, parent)
+ , condition(condition)
+ {}
+
+ /// The IR value that controls the conditional branch
+ IRInst* condition;
+
+ /// The region to execute if the `condition` is `true`
+ RefPtr<Region> thenRegion;
+
+ /// The region to execute if the `condition` is `false`
+ RefPtr<Region> elseRegion;
+ };
+
+ /// Base type for regions that execution can `break` out of
+ class BreakableRegion : public SeqRegion
+ {
+ protected:
+ BreakableRegion(Flavor flavor, Region* parent)
+ : SeqRegion(flavor, parent)
+ {}
+ };
+
+ /// A region that expresses a `break` out of nested control flow.
+ ///
+ class BreakRegion : public Region
+ {
+ public:
+ BreakRegion(Region* parent, BreakableRegion* outerRegion)
+ : Region(Region::Flavor::Break, parent)
+ , outerRegion(outerRegion)
+ {}
+
+ BreakableRegion* outerRegion;
+ };
+
+ /// A structured loop
+ class LoopRegion : public BreakableRegion
+ {
+ public:
+ LoopRegion(Region* parent, IRLoop* loopInst)
+ : BreakableRegion(Region::Flavor::Loop, parent)
+ , loopInst(loopInst)
+ {}
+
+ /// The IR instruction that represents the branch into the loop.
+ /// We keep this instruction around because it may have decorations
+ /// that need to influence how we emit this loop.
+ ///
+ IRLoop* loopInst;
+
+ /// The code inside the loop.
+ ///
+ /// The body region may include `break` or `continue` operations for this loop.
+ RefPtr<Region> body;
+ };
+
+ /// A region that expresses a `continue` for a structured loop.
+ ///
+ class ContinueRegion : public Region
+ {
+ public:
+ ContinueRegion(Region* parent, LoopRegion* outerRegion)
+ : Region(Region::Flavor::Continue, parent)
+ , outerRegion(outerRegion)
+ {}
+
+ LoopRegion* outerRegion;
+ };
+
+ /// A structured `switch` statement.
+ class SwitchRegion : public BreakableRegion
+ {
+ public:
+ SwitchRegion(Region* parent, IRInst* condition)
+ : BreakableRegion(Region::Flavor::Switch, parent)
+ , condition(condition)
+ {}
+
+ /// The IR value that controls the conditional branch
+ IRInst* condition;
+
+ /// A collection of `case`s that share the same code.
+ class Case : public RefObject
+ {
+ public:
+ /// The various values that should branch to this case.
+ ///
+ /// It is possible for this list to be empty if this
+ /// is the `default` case and has no explicit values
+ /// that map to it.
+ ///
+ List<IRInst*> values;
+
+ /// The region to execute if this case is selected.
+ RefPtr<Region> body;
+ };
+
+ /// All of the cases for the `switch`.
+ ///
+ /// This includes any `default` cases.
+ ///
+ /// As an invariant, a case that "falls through" to another
+ /// should immediately precede its target in this list.
+ ///
+ List<RefPtr<Case>> cases;
+
+ /// The default case, if any.
+ ///
+ /// It is valid for this to be `null` if there is no `default` case,
+ /// in which case the default behavior should be to branch to the region
+ /// after the `switch`.
+ ///
+ /// The default case must also be present in `cases`.
+ Case* defaultCase;
+ };
+
+ /// Container for all of the regions in a function.
+ ///
+ /// A `RegionTree` owns the `Region` objects associated with a function,
+ /// along with a mapping from basic blocks in the IR function to regions
+ /// in the tree.
+ ///
+ class RegionTree : public RefObject
+ {
+ public:
+ /// Type for the mapping from IR blocks to regions.
+ typedef Dictionary<IRBlock*, SimpleRegion*> MapBlockToRegion;
+
+ /// A dictionary to map from IR blocks to regions.
+ MapBlockToRegion mapBlockToRegion;
+
+ /// The root region of the region tree.
+ RefPtr<Region> rootRegion;
+
+ /// The IR function that was used to compute the region tree.
+ IRGlobalValueWithCode* irCode = nullptr;
+ };
+
+ /// Construct structrured regions to represent the control flow in an IR function.
+ ///
+ /// The resulting `RegionTree` will encode a structured (statement-like)
+ /// form for the control flow graph (CFG) of `code`.
+ /// In cases where our current restructuring approach is now powerful
+ /// enough to handle something in the input CFG, diagnostic messages
+ /// will be output to the given `sink`.
+ ///
+ RefPtr<RegionTree> generateRegionTreeForFunc(
+ IRGlobalValueWithCode* code,
+ DiagnosticSink* sink);
+}
diff --git a/source/slang/ir.cpp b/source/slang/ir.cpp
index 408f4257d..92801ec9a 100644
--- a/source/slang/ir.cpp
+++ b/source/slang/ir.cpp
@@ -1564,6 +1564,7 @@ namespace Slang
nullptr,
nullptr);
module->moduleInst = moduleInst;
+ moduleInst->module = module;
return module;
}
@@ -2891,12 +2892,6 @@ namespace Slang
ff->debugValidate();
}
- void IRInst::deallocate()
- {
- // Run destructor to be sure...
- this->~IRInst();
- }
-
void IRInst::dispose()
{
IRObject::dispose();
@@ -3055,6 +3050,7 @@ namespace Slang
void IRInst::removeArguments()
{
+ typeUse.clear();
for( UInt aa = 0; aa < operandCount; ++aa )
{
IRUse& use = getOperands()[aa];
@@ -3068,7 +3064,9 @@ namespace Slang
{
removeFromParent();
removeArguments();
- deallocate();
+
+ // Run destructor to be sure...
+ this->~IRInst();
}
bool IRInst::mightHaveSideEffects()
@@ -3144,6 +3142,20 @@ namespace Slang
}
}
+ IRModule* IRInst::getModule()
+ {
+ IRInst* ii = this;
+ while(ii)
+ {
+ if(auto moduleInst = as<IRModuleInst>(ii))
+ return moduleInst->module;
+
+ ii = ii->getParent();
+ }
+ return nullptr;
+ }
+
+
//
// Legalization of entry points for GLSL:
//
@@ -4563,8 +4575,7 @@ namespace Slang
for( auto pp = firstBlock->getFirstParam(); pp; )
{
auto next = pp->getNextParam();
- pp->removeFromParent();
- pp->deallocate();
+ pp->removeAndDeallocate();
pp = next;
}
}
diff --git a/source/slang/ir.h b/source/slang/ir.h
index af08b9491..91ca377f2 100644
--- a/source/slang/ir.h
+++ b/source/slang/ir.h
@@ -247,10 +247,6 @@ struct IRInst : public IRObject
// that this value will now have no uses.
void replaceUsesWith(IRInst* other);
- // Free a value (which needs to have been removed
- // from its parent, had its uses eliminated, etc.)
- void deallocate();
-
// Clean up any non-pool resources used by this instruction
virtual void dispose() override;
@@ -297,6 +293,12 @@ struct IRInst : public IRObject
// RTTI support
static bool isaImpl(IROp) { return true; }
+
+ /// Find the module that this instruction is nested under.
+ ///
+ /// If this instruction is transitively nested inside some IR module,
+ /// this function will return it, and will otherwise return `null`.
+ IRModule* getModule();
};
// `dynamic_cast` equivalent
diff --git a/source/slang/slang.vcxproj b/source/slang/slang.vcxproj
index f32355e83..284907018 100644
--- a/source/slang/slang.vcxproj
+++ b/source/slang/slang.vcxproj
@@ -185,6 +185,8 @@
<ClInclude Include="ir-dominators.h" />
<ClInclude Include="ir-inst-defs.h" />
<ClInclude Include="ir-insts.h" />
+ <ClInclude Include="ir-restructure-scoping.h" />
+ <ClInclude Include="ir-restructure.h" />
<ClInclude Include="ir-ssa.h" />
<ClInclude Include="ir-validate.h" />
<ClInclude Include="ir.h" />
@@ -229,6 +231,8 @@
<ClCompile Include="ir-constexpr.cpp" />
<ClCompile Include="ir-dominators.cpp" />
<ClCompile Include="ir-legalize-types.cpp" />
+ <ClCompile Include="ir-restructure-scoping.cpp" />
+ <ClCompile Include="ir-restructure.cpp" />
<ClCompile Include="ir-ssa.cpp" />
<ClCompile Include="ir-validate.cpp" />
<ClCompile Include="ir.cpp" />
diff --git a/source/slang/slang.vcxproj.filters b/source/slang/slang.vcxproj.filters
index 991fe9c44..cb0235e61 100644
--- a/source/slang/slang.vcxproj.filters
+++ b/source/slang/slang.vcxproj.filters
@@ -1,4 +1,4 @@
-<?xml version="1.0" encoding="utf-8"?>
+<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="Header Files">
@@ -153,6 +153,10 @@
<ClInclude Include="vm.h">
<Filter>Header Files</Filter>
</ClInclude>
+ <ClInclude Include="ir-restructure.h">
+ <Filter>Header Files</Filter>
+ </ClInclude>
+ <ClInclude Include="ir-restructure-scoping.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="bytecode.cpp">
@@ -254,6 +258,10 @@
<ClCompile Include="vm.cpp">
<Filter>Source Files</Filter>
</ClCompile>
+ <ClCompile Include="ir-restructure.cpp">
+ <Filter>Source Files</Filter>
+ </ClCompile>
+ <ClCompile Include="ir-restructure-scoping.cpp" />
</ItemGroup>
<ItemGroup>
<None Include="slang.natvis">
diff --git a/tests/bugs/gh-569.slang b/tests/bugs/gh-569.slang
new file mode 100644
index 000000000..fa7525d45
--- /dev/null
+++ b/tests/bugs/gh-569.slang
@@ -0,0 +1,36 @@
+// gh-569.slang
+//TEST(compute):COMPARE_COMPUTE:
+
+// Test that correct scoping is used in generated HLSL/GLSL,
+// even when dominator tree and structured control flow disagree.
+
+uint test(uint inVal)
+{
+ uint tmp = inVal;
+ for(;;)
+ {
+ if(tmp < 4)
+ {
+ tmp++;
+ }
+ else
+ {
+ break;
+ }
+ }
+ return tmp + inVal;
+}
+
+//TEST_INPUT:ubuffer(data=[0 0 0 0], stride=4):dxbinding(0),glbinding(0),out
+RWStructuredBuffer<uint> gBuffer;
+
+[numthreads(4, 1, 1)]
+void computeMain(uint3 dispatchThreadID : SV_DispatchThreadID)
+{
+ uint tid = dispatchThreadID.x;
+
+ uint val = tid;
+ val = test(val);
+
+ gBuffer[tid] = val;
+}
diff --git a/tests/bugs/gh-569.slang.expected.txt b/tests/bugs/gh-569.slang.expected.txt
new file mode 100644
index 000000000..3195a6aa5
--- /dev/null
+++ b/tests/bugs/gh-569.slang.expected.txt
@@ -0,0 +1,4 @@
+4
+5
+6
+7
diff --git a/tests/compute/multiple-continue-sites.slang b/tests/compute/multiple-continue-sites.slang
new file mode 100644
index 000000000..5a5b78d0c
--- /dev/null
+++ b/tests/compute/multiple-continue-sites.slang
@@ -0,0 +1,39 @@
+//TEST(compute):COMPARE_COMPUTE:
+//TEST_INPUT:ubuffer(data=[0 1 2 3], stride=4):dxbinding(0),glbinding(0),out
+
+// Test that a loop with multiple `continue` sites works.
+//
+// The current Slang codegen strategy for `continue` ends
+// up duplicating the "continue clause" for a `for` loop
+// at each `continue` site, so it will stress-test any
+// code that assumes a given instruction/block only
+// appears once in the region tree.
+//
+
+int test(int inVal)
+{
+ int ii = inVal;
+ for(;!(ii & 0x20); ii += 0x10)
+ {
+ if(ii == 2)
+ {
+ continue;
+ }
+
+ ii += 0x100;
+ // there is an implicit `continue` here
+ }
+
+ return ii;
+}
+
+RWStructuredBuffer<int> outputBuffer : register(u0);
+
+[numthreads(4, 1, 1)]
+void computeMain(uint3 dispatchThreadID : SV_DispatchThreadID)
+{
+ uint tid = dispatchThreadID.x;
+ int inVal = outputBuffer[tid];
+ int outVal = test(inVal);
+ outputBuffer[tid] = outVal;
+} \ No newline at end of file
diff --git a/tests/compute/multiple-continue-sites.slang.expected.txt b/tests/compute/multiple-continue-sites.slang.expected.txt
new file mode 100644
index 000000000..6752a80e0
--- /dev/null
+++ b/tests/compute/multiple-continue-sites.slang.expected.txt
@@ -0,0 +1,4 @@
+220
+221
+122
+223
diff --git a/tools/render-test/d3d-util.cpp b/tools/render-test/d3d-util.cpp
index 5cc0dc69b..fb3cb49bf 100644
--- a/tools/render-test/d3d-util.cpp
+++ b/tools/render-test/d3d-util.cpp
@@ -269,7 +269,6 @@ bool D3DUtil::isTypeless(DXGI_FORMAT format)
::fputs((const char*)errorBlob->GetBufferPointer(), stderr);
::fflush(stderr);
::OutputDebugStringA((const char*)errorBlob->GetBufferPointer());
- return SLANG_FAIL;
}
SLANG_RETURN_ON_FAIL(hr);