Skip to content

Commit 59a4c0c

Browse files
author
Tim Foley
authored
IR: add support for switch statements (shader-slang#278)
* IR: add support for `switch` statements Fixes shader-slang#273 This is just something we hadn't gotten to yet on the IR. The actual design of the instruction is unsurprising (once you take into consideration the requirement for structured control flow). A `switch` instruction takes the form: switch <condition> <breakLabel> <defaultLabel> [<caseVal> <caseLabel>]* Where `condition` is the value to switch on, `breakLabel` is the "join point" after the original `switch` statement, `defaultLabel` is where to go if the value doesn't match any case, and each pair of `caseVal` and `caseLabel` is what to do on a particular value. It is required that `caseVal` be a literal, but this isn't currently being enforced in the IR (the front-end should be making a check and constant-folding the case labels). For structured control flow, we also make the assumption that the cases are in order: cases with the same label must be grouped together, and any case that falls through to another must come right before it. Given this representation, the emit logic can reconstruct a `switch` statement with relative ease, given the machinery we already have. It makes sure to group together case values with the same label (again, assuming they are contiguous), and will insert the `default:` label in with whatever group it belongs to. Actually emitting code for a `switch` statement seems superficially simple, until you realize that a complete implementation needs to handle stuff like "Duff's Device." The current implementation makes the assumption that all `case` and `default` statements are directly nested under a `switch`, and that there is no way for control flow to enter a case except by the `switch` itself, or fall-through. In order to facilitate the grouping of cases in the IR-to-HLSL emit logic, the AST-to-IR lowering logic tries to detect cases where there are multiple `case`s in a row, and emit only a single label for them. One big/annoying gotcha is that we don't properly handle the case where a `default:` case has a non-trivial fall-throguh to another case. That seems fine for now since HLSL doesn't support fall-through anyway, but it probably needs to get detected somewhere in the Slang compiler (e.g., maybe we should add a diagnostic pass over the IR that detects target-specific problems like that and emits errors). * IR: Add support for empty statements. - Add empty statement in `lower-to-ir.cpp` - Go ahead and eliminate the statement catch-all and explicitly enumerate the cases we don't support - Fix up parser for block statements so that it doesn't leave a null statement as the body of a `{}` - Add an empty statement to one of the cases for the `switch` test, to ensure we are testing empty statements
1 parent 7c4ad87 commit 59a4c0c

9 files changed

+516
-5
lines changed

source/slang/emit.cpp

+135-1
Original file line numberDiff line numberDiff line change
@@ -2896,7 +2896,7 @@ struct EmitVisitor
28962896
}
28972897
else if (auto defaultStmt = stmt.As<DefaultStmt>())
28982898
{
2899-
Emit("default:{}\n");
2899+
Emit("default:\n");
29002900
return;
29012901
}
29022902
else if (auto breakStmt = stmt.As<BreakStmt>())
@@ -5643,8 +5643,142 @@ emitDeclImpl(decl, nullptr);
56435643
break;
56445644

56455645
case kIROp_conditionalBranch:
5646+
// Note: We currently do not generate any plain
5647+
// `conditionalBranch` instructions when lowering
5648+
// to IR, because these would not have the annotations
5649+
// needed to be able to emit high-level control
5650+
// flow from them.
56465651
SLANG_UNEXPECTED("terminator inst");
56475652
return;
5653+
5654+
5655+
case kIROp_switch:
5656+
{
5657+
// A `switch` instruction will always translate
5658+
// to a `switch` statement, but we need to
5659+
// take some care to emit the `case`s in ways
5660+
// that avoid code duplication.
5661+
5662+
// TODO: Eventually, the "right" way to handle `switch`
5663+
// statements while being more robust about Duff's Device, etc.
5664+
// would be to register each of the case labels in a lookup
5665+
// table, and then walk the blocks in the region between
5666+
// the `switch` and the `break` and then whenever we see a block
5667+
// that matches one of the registered labels, emit the appropriate
5668+
// `case ...:` or `default:` label.
5669+
5670+
auto t = (IRSwitch*) terminator;
5671+
5672+
// Extract the fixed arguments.
5673+
auto conditionVal = t->getCondition();
5674+
auto breakLabel = t->getBreakLabel();
5675+
auto defaultLabel = t->getDefaultLabel();
5676+
5677+
// We need to track whether we've dealt with
5678+
// the `default` case already.
5679+
bool defaultLabelHandled = false;
5680+
5681+
// If the `default` case just branches to
5682+
// the join point, then we don't need to
5683+
// do anything with it.
5684+
if(defaultLabel == breakLabel)
5685+
defaultLabelHandled = true;
5686+
5687+
// Emit the start of our statement.
5688+
emit("switch(");
5689+
emitIROperand(ctx, conditionVal);
5690+
emit(")\n{\n");
5691+
5692+
// Now iterate over the `case`s of the branch
5693+
UInt caseIndex = 0;
5694+
UInt caseCount = t->getCaseCount();
5695+
while(caseIndex < caseCount)
5696+
{
5697+
// We are going to extract one case here,
5698+
// but we might need to fold additional
5699+
// cases into it, if they share the
5700+
// same label.
5701+
//
5702+
// Note: this makes assumptions that the
5703+
// IR code generator orders cases such
5704+
// that: (1) cases with the same label
5705+
// are consecutive, and (2) any case
5706+
// that "falls through" to another must
5707+
// come right before it in the list.
5708+
auto caseVal = t->getCaseValue(caseIndex);
5709+
auto caseLabel = t->getCaseLabel(caseIndex);
5710+
caseIndex++;
5711+
5712+
// Emit the `case ...:` for this case, and any
5713+
// others that share the same label
5714+
for(;;)
5715+
{
5716+
emit("case ");
5717+
emitIROperand(ctx, caseVal);
5718+
emit(":\n");
5719+
5720+
if(caseIndex >= caseCount)
5721+
break;
5722+
5723+
auto nextCaseLabel = t->getCaseLabel(caseIndex);
5724+
if(nextCaseLabel != caseLabel)
5725+
break;
5726+
5727+
caseVal = t->getCaseValue(caseIndex);
5728+
caseIndex++;
5729+
}
5730+
5731+
// The label for the current `case` might also
5732+
// be the label used by the `default` case, so
5733+
// check for that here.
5734+
if(caseLabel == defaultLabel)
5735+
{
5736+
emit("default:\n");
5737+
defaultLabelHandled = true;
5738+
}
5739+
5740+
// Now we need to emit the statements that make
5741+
// up this case. The 99% case will be that it
5742+
// will terminate with a `break` (or a `return`,
5743+
// `continue`, etc.) and so we can pass in
5744+
// `nullptr` for the ending block.
5745+
IRBlock* caseEndLabel = nullptr;
5746+
5747+
// However, there is also the possibility that
5748+
// this case will fall through to the next, and
5749+
// so we need to prepare for that possibility here.
5750+
//
5751+
// If there is a next case, then we will set its
5752+
// label up as the "end" label when emitting
5753+
// the statements inside the block.
5754+
if(caseIndex < caseCount)
5755+
{
5756+
caseEndLabel = t->getCaseLabel(caseIndex);
5757+
}
5758+
5759+
// Now emit the statements for this case.
5760+
emit("{\n");
5761+
emitIRStmtsForBlocks(ctx, caseLabel, caseEndLabel);
5762+
emit("}\n");
5763+
}
5764+
5765+
// If we've gone through all the cases and haven't
5766+
// managed to encounter the `default:` label,
5767+
// then assume it is a distinct case and handle it here.
5768+
if(!defaultLabelHandled)
5769+
{
5770+
emit("default:\n");
5771+
emit("{\n");
5772+
emitIRStmtsForBlocks(ctx, defaultLabel, breakLabel);
5773+
emit("break;\n");
5774+
emit("}\n");
5775+
}
5776+
5777+
emit("}\n");
5778+
block = breakLabel;
5779+
5780+
}
5781+
break;
56485782
}
56495783

56505784
// If we reach this point, then we've emitted

source/slang/ir-inst-defs.h

+2
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,8 @@ INST(if, if, 3, 0)
181181
INST(ifElse, ifElse, 4, 0)
182182
INST(loopTest, loopTest, 3, 0)
183183

184+
INST(switch, switch, 3, 0)
185+
184186
INST(discard, discard, 0, 0)
185187

186188
INST(Add, add, 2, 0)

source/slang/ir-insts.h

+26
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,24 @@ struct IRIfElse : IRConditionalBranch
209209
IRBlock* getAfterBlock() { return (IRBlock*)afterBlock.usedValue; }
210210
};
211211

212+
// A multi-way branch that represents a source-level `switch`
213+
struct IRSwitch : IRTerminatorInst
214+
{
215+
IRUse condition;
216+
IRUse breakLabel;
217+
IRUse defaultLabel;
218+
219+
IRValue* getCondition() { return condition.usedValue; }
220+
IRBlock* getBreakLabel() { return (IRBlock*) breakLabel.usedValue; }
221+
IRBlock* getDefaultLabel() { return (IRBlock*) defaultLabel.usedValue; }
222+
223+
// remaining args are: caseVal, caseLabel, ...
224+
225+
UInt getCaseCount() { return (getArgCount() - 3) / 2; }
226+
IRValue* getCaseValue(UInt index) { return getArg(3 + index*2 + 0); }
227+
IRBlock* getCaseLabel(UInt index) { return (IRBlock*) getArg(3 + index*2 + 1); }
228+
};
229+
212230
struct IRSwizzle : IRReturn
213231
{
214232
IRUse base;
@@ -504,6 +522,14 @@ struct IRBuilder
504522
IRBlock* bodyBlock,
505523
IRBlock* breakBlock);
506524

525+
IRInst* emitSwitch(
526+
IRValue* val,
527+
IRBlock* breakLabel,
528+
IRBlock* defaultLabel,
529+
UInt caseArgCount,
530+
IRValue* const* caseArgs);
531+
532+
507533
IRDecoration* addDecorationImpl(
508534
IRValue* value,
509535
UInt decorationSize,

source/slang/ir.cpp

+23
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,7 @@ namespace Slang
167167
case kIROp_ifElse:
168168
case kIROp_loopTest:
169169
case kIROp_discard:
170+
case kIROp_switch:
170171
return true;
171172
}
172173
}
@@ -1289,6 +1290,28 @@ namespace Slang
12891290
return inst;
12901291
}
12911292

1293+
IRInst* IRBuilder::emitSwitch(
1294+
IRValue* val,
1295+
IRBlock* breakLabel,
1296+
IRBlock* defaultLabel,
1297+
UInt caseArgCount,
1298+
IRValue* const* caseArgs)
1299+
{
1300+
IRValue* fixedArgs[] = { val, breakLabel, defaultLabel };
1301+
UInt fixedArgCount = sizeof(fixedArgs) / sizeof(fixedArgs[0]);
1302+
1303+
auto inst = createInstWithTrailingArgs<IRSwitch>(
1304+
this,
1305+
kIROp_switch,
1306+
nullptr,
1307+
fixedArgCount,
1308+
fixedArgs,
1309+
caseArgCount,
1310+
caseArgs);
1311+
addInst(inst);
1312+
return inst;
1313+
}
1314+
12921315
IRDecoration* IRBuilder::addDecorationImpl(
12931316
IRValue* inst,
12941317
UInt decorationSize,

0 commit comments

Comments
 (0)