SPV: Emit OpSelect when a selection node is simple enough.
Also, ensures it has a type, no disallowed side effects,
or performance trade offs.
diff --git a/SPIRV/GlslangToSpv.cpp b/SPIRV/GlslangToSpv.cpp
index b15a19f..bf26cdc 100755
--- a/SPIRV/GlslangToSpv.cpp
+++ b/SPIRV/GlslangToSpv.cpp
@@ -1748,42 +1748,94 @@
}
}
+// This path handles both if-then-else and ?:
+// The if-then-else has a node type of void, while
+// ?: has either a void or a non-void node type
+//
+// Leaving the result, when not void:
+// GLSL only has r-values as the result of a :?, but
+// if we have an l-value, that can be more efficient if it will
+// become the base of a complex r-value expression, because the
+// next layer copies r-values into memory to use the access-chain mechanism
bool TGlslangToSpvTraverser::visitSelection(glslang::TVisit /* visit */, glslang::TIntermSelection* node)
{
- // This path handles both if-then-else and ?:
- // The if-then-else has a node type of void, while
- // ?: has a non-void node type
- spv::Id result = 0;
- if (node->getBasicType() != glslang::EbtVoid) {
- // don't handle this as just on-the-fly temporaries, because there will be two names
- // and better to leave SSA to later passes
- result = builder.createVariable(spv::StorageClassFunction, convertGlslangToSpvType(node->getType()));
+ // See if it simple and safe to generate OpSelect instead of using control flow.
+ // Crucially, side effects must be avoided, and there are performance trade-offs.
+ // Return true if good idea (and safe) for OpSelect, false otherwise.
+ const auto selectPolicy = [&]() -> bool {
+ if (node->getBasicType() == glslang::EbtVoid)
+ return false;
+
+ if (node->getTrueBlock() == nullptr ||
+ node->getFalseBlock() == nullptr)
+ return false;
+
+ assert(node->getType() == node->getTrueBlock() ->getAsTyped()->getType() &&
+ node->getType() == node->getFalseBlock()->getAsTyped()->getType());
+
+ // return true if a single operand to ? : is okay for OpSelect
+ const auto operandOkay = [](glslang::TIntermTyped* node) {
+ return node->getAsSymbolNode() || node->getAsConstantUnion();
+ };
+
+ return operandOkay(node->getTrueBlock() ->getAsTyped()) &&
+ operandOkay(node->getFalseBlock()->getAsTyped());
+ };
+
+ // Emit OpSelect for this selection.
+ const auto handleAsOpSelect = [&]() {
+ node->getCondition()->traverse(this);
+ spv::Id condition = accessChainLoad(node->getCondition()->getType());
+ node->getTrueBlock()->traverse(this);
+ spv::Id trueValue = accessChainLoad(node->getTrueBlock()->getAsTyped()->getType());
+ node->getFalseBlock()->traverse(this);
+ spv::Id falseValue = accessChainLoad(node->getTrueBlock()->getAsTyped()->getType());
+
+ spv::Id select = builder.createTriOp(spv::OpSelect, convertGlslangToSpvType(node->getType()), condition, trueValue, falseValue);
+ builder.clearAccessChain();
+ builder.setAccessChainRValue(select);
+ };
+
+ // Try for OpSelect
+
+ if (selectPolicy()) {
+ handleAsOpSelect();
+ return false;
}
+ // Instead, emit control flow...
+
+ // Don't handle results as temporaries, because there will be two names
+ // and better to leave SSA to later passes.
+ spv::Id result = (node->getBasicType() == glslang::EbtVoid)
+ ? spv::NoResult
+ : builder.createVariable(spv::StorageClassFunction, convertGlslangToSpvType(node->getType()));
+
// emit the condition before doing anything with selection
node->getCondition()->traverse(this);
// make an "if" based on the value created by the condition
spv::Builder::If ifBuilder(accessChainLoad(node->getCondition()->getType()), builder);
- if (node->getTrueBlock()) {
- // emit the "then" statement
+ // emit the "then" statement
+ if (node->getTrueBlock() != nullptr) {
node->getTrueBlock()->traverse(this);
- if (result)
- builder.createStore(accessChainLoad(node->getTrueBlock()->getAsTyped()->getType()), result);
+ if (result != spv::NoResult)
+ builder.createStore(accessChainLoad(node->getTrueBlock()->getAsTyped()->getType()), result);
}
- if (node->getFalseBlock()) {
+ if (node->getFalseBlock() != nullptr) {
ifBuilder.makeBeginElse();
// emit the "else" statement
node->getFalseBlock()->traverse(this);
- if (result)
+ if (result != spv::NoResult)
builder.createStore(accessChainLoad(node->getFalseBlock()->getAsTyped()->getType()), result);
}
+ // finish off the control flow
ifBuilder.makeEndIf();
- if (result) {
+ if (result != spv::NoResult) {
// GLSL only has r-values as the result of a :?, but
// if we have an l-value, that can be more efficient if it will
// become the base of a complex r-value expression, because the