summaryrefslogtreecommitdiffstats
path: root/source/slang/emit.cpp
diff options
context:
space:
mode:
authorTim Foley <tfoleyNV@users.noreply.github.com>2018-05-24 19:20:11 -0700
committerGitHub <noreply@github.com>2018-05-24 19:20:11 -0700
commit18709fbaa03fe0ef0727a802d864fae6c5163fc0 (patch)
treea7bfddb97aaff016d38b5772ef7929e5c36572c5 /source/slang/emit.cpp
parentd7515c30209cc995573d9b0258de1716c30c6012 (diff)
A bunch of work to resolve #569 (#576)
* render-test should not fail on HLSL compiler *warnings* The logic in `render-test` that invokes `D3DCompile` was causing a test to fail if it produced any warnings (not just if compilation fails). Warning output can be dealt with by the test runner, since it will compare output between runs anyway, and it is useful to be able to run something through `render-test` that compiles with warnings. * Be more careful about deleting IR instructions There was an `IRInst::deallocate()` method that had a precondition that the instruction should already be removed from its parent and clear out all its operands before calling, but it wasn't checking this and the few call sites weren't doing things right either. I consolidated things on `IRInst::removeAndDeallocate()` which does all the things: removes from the parent, clear out operands, and then deallocates. I also made sure to clear out the type operand. This clears up some crashing issues where passes were removing instructions but those instructions would still show up as users of other instructions. * Don't emit bitwise not for non-Boolean types It seems like the logic in `emit.cpp` messed things up and decided that `Not` (the IR instruction that is equivalent to `!` in the AST) should emit as `!` for Boolean types and `~` for other types, but this makes no sense (e.g., `~(a & 1)` is very different from `!(a & 1)`, even when interpreted as a condition). It seems like this logic was intended for the `BitNot` case, where `~a` and `!a` are actually equivalent for Boolean values (but a target language might not like `~a` on `bool` values). Maybe the original plan was that the `Not` instruction should only apply to Boolean values in the first place, and that other values should be converted to `bool` (or a vector of `bool`) before applying `Not`, but even in that case the emit logic makes no sense. This caused an actual problem for one of my test cases, so it was important to fix it now. * Fix issue with cached resolution for overoaded operators The basic problem was that the lookup logic was forming a key based on the *first* definition it found for the overloaded operator, but that means that when processing a prefix `++a` call we might look up the *postfix* definition of `operator++` and decide to use its opcode as the key. This "fixes" the logic by looking for the first definition with a "compatible" definition (e.g., a `__prefix` function if we are checking a `PrefixExpr`), and then uses its opcode. A better fix in the long run would be to make the cache just be keyed on the operator name and the "fixity" of the expression (prefix, postfix, or infix). * Introduce an intermediate structured control-flow representation The code previously used a single function called `emitIRStmtsForBlocks` in `emit.cpp` that would take a logical sub-graph of the CFG and emit it as high-level statements. It would do this by recognizing operations like coniditional branches that it could turn into high-level `if` statements, etc. The main problem with this function was that it mixed together the logic for how we restructure the program with the logic for how we emit high-level code from that structure. This change splits those two parts of the algorithm by introducing an intermediate data structure: a tree of `Region`s, which represent single-entry regions of the CFG. There are subclasses of `Region` corresponding to various structured control-flow constructs, and then a leaf case that wraps a single `IRBlock`. The new function `generateRegionsForIRBlocks()` (in `ir-restructure.cpp`) now handles the restructuring work, by building one or more `Region`s to represent a sub-graph, while `emitRegion()` handles emitting HLSL/GLSL source code from a region. Splitting things in this way opens up some opportunities for future changes: * We can expand the set of IR control-flow constructs allowed, so long as we can still generate structure `Region`s from them, without having to mess with the emit logic (e.g., we could start to support multi-level `break` by introducing temporaries as needed). In the limit we can generate our `Region`s using something like the "Relooper" algorithm. * We can emit to other representations while retaining the same control-flow restructuring support. E.g., if we drop the structured information from the IR, then emitting to SPIR-V for Vulkan would require us to use the strucured control-flow information from these `Region`s. * We can do analysis that needs to understand `Region` structure. This is relevant to issue #569, which was what prompted me to start on this work. Now that we have a representation of the nesting of `Region`s, we can use it to reason about visibility of values between blocks. During development of this change I ran into a gotcha, in that I had been assuming each IR block would map to a single `Region`, forgetting that our current lowering of "continue clauses" in `for` loops leads to them being duplicated. The `Region` representation handles this by having a linked-list struct mapping IR blocks to the `SimpleRegion`s that represent them. I added a test case that includes a `for` loop with a continue clause that is reached along multiple paths just to make sure that we continue to support that case. The compiler output should not change as a result of this work; this is supposed to be a pure refactoring change. * Add a pass to resolve scoping issues in generated code Fixes #569 The basic problem arises because the structured control flow that we output in high-level HLSL/GLSL doesn't match the "scoping" rules of an SSA IR. In particular, SSA says that a value can be used in any block that is dominated by the definition, but in the presence of `break` and `continue` statements it is easy to construct cases where a block dominates something that is not in its scope for structured control flow. Consider: ```hlsl for(;;) { int a = xyz; if(a) { int b = a; break; } int c = a; } int d = b; ``` This program is invalid as HLSL, because the variable `b` is referenced outside of its scope, but if we look at the CFG for this function, it is clear that the block that computes `b` dominated the block that computes `d`. IR optimizations can easily create code like this, so we need to be ready for it. The previous change added an explicit `Region` structure to represent the structured control flow that we re-form out of the IR, and this change adds a pass that exploits the structuring information to detect cases like the above and introduce temporaries to fix the scoping issue. For example, the pass would change the earlier code block into something like: ```hlsl int tmp; for(;;) { int a = xyz; if(a) { int b = a; tmp = b; break; } int c = a; } int d = tmp; ``` That is, we introduce a new `tmp` variable at a scope "above" both the definition and use of `b`, and then we copy `b` into that temporary right where it is computed, and then use the temporary instead of the original `b` at the use site. A few details that came up during the implementation: * Downstream compilers may get confused by code like the above, and complain that `tmp` may be used before it is initialized, even though the very definition of dominators in a CFG means we don't have to worry about it. Still, I introduced some one-off code to initialize the temporaries just to silence spurious warnings coming from fxc. * We need to be careful not to apply this logic to "phi nodes" (the parameters of basic blocks) since they will already be turned into temporaries by the emit logic, and trying to introduce temporaries with this pass led to broken code (I still need to investigate why). It may be that a future version of this pass should also take the code out of SSA form, so that we can introduce both kinds of temporaries in a single pass (and maybe eliminate some unnecessary variables by doing basic register allocation). There is another transformation that could fix some issues of this kind, by moving code out of a structured control-flow construct and to the "join point" after it. For example, we could turn our loop from the start of this commit message into: ```hlsl for(;;) { int a = xyz; if(a) { break; } int c = a; } int b = a; int d = b; ``` Moving the definition of `b` to after the loop is possible because there is no way to get out of the loop without executing that code anyway. Now the scoping issue for `d`'s use of `b` has gone away, but of course we've introduced a *new* scoping issue for `a`, when it gets used by `b`. Adding a pass to re-arrange control flow like this could reduce the cases where we have to apply the current pass, but it wouldn't eliminate them entirely. That means such a pass can be deferred to future work. This change includes a test case the reproduces the original issue, so that we can confirm the fix works.
Diffstat (limited to 'source/slang/emit.cpp')
-rw-r--r--source/slang/emit.cpp618
1 files changed, 202 insertions, 416 deletions
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");
}