Commit dc59772e by Shahbaz Youssefi Committed by Angle LUCI CQ

Vulkan: SPIR-V Gen: Support switch

With the infrastructure to support this in place, switch is simply implemented as a conditional with multiple blocks. Each block either ends with a branch to the merge block or the next block, implementing fallthrough. Bug: angleproject:4889 Change-Id: I5831531d918ac06648cced7707d1d48ffeb6b1b0 Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/2983559 Commit-Queue: Shahbaz Youssefi <syoussefi@chromium.org> Reviewed-by: 's avatarTim Van Patten <timvp@google.com> Reviewed-by: 's avatarJamie Madill <jmadill@chromium.org>
parent a8c9cecf
...@@ -976,7 +976,7 @@ void SPIRVBuilder::nextConditionalBlock() ...@@ -976,7 +976,7 @@ void SPIRVBuilder::nextConditionalBlock()
SpirvConditional &conditional = mConditionalStack.back(); SpirvConditional &conditional = mConditionalStack.back();
ASSERT(conditional.nextBlockToWrite < conditional.blockIds.size()); ASSERT(conditional.nextBlockToWrite < conditional.blockIds.size());
spirv::IdRef blockId = conditional.blockIds[conditional.nextBlockToWrite++]; const spirv::IdRef blockId = conditional.blockIds[conditional.nextBlockToWrite++];
// The previous block must have properly terminated. // The previous block must have properly terminated.
ASSERT(isCurrentFunctionBlockTerminated()); ASSERT(isCurrentFunctionBlockTerminated());
...@@ -1290,6 +1290,44 @@ void SPIRVBuilder::writeLoopBodyEnd(spirv::IdRef continueBlock) ...@@ -1290,6 +1290,44 @@ void SPIRVBuilder::writeLoopBodyEnd(spirv::IdRef continueBlock)
nextConditionalBlock(); nextConditionalBlock();
} }
void SPIRVBuilder::writeSwitch(spirv::IdRef conditionValue,
spirv::IdRef defaultBlock,
const spirv::PairLiteralIntegerIdRefList &targetPairList,
spirv::IdRef mergeBlock)
{
// Generate the following:
//
// OpSelectionMerge %mergeBlock None
// OpSwitch %conditionValue %defaultBlock A %ABlock B %BBlock ...
//
spirv::WriteSelectionMerge(getSpirvCurrentFunctionBlock(), mergeBlock,
spv::SelectionControlMaskNone);
spirv::WriteSwitch(getSpirvCurrentFunctionBlock(), conditionValue, defaultBlock,
targetPairList);
terminateCurrentFunctionBlock();
// Start the next case block.
nextConditionalBlock();
}
void SPIRVBuilder::writeSwitchCaseBlockEnd()
{
if (!isCurrentFunctionBlockTerminated())
{
// If a case does not end in branch, insert a branch to the next block, implementing
// fallthrough. For the last block, the branch target would automatically be the merge
// block.
const SpirvConditional *conditional = getCurrentConditional();
const spirv::IdRef nextBlock = conditional->blockIds[conditional->nextBlockToWrite];
spirv::WriteBranch(getSpirvCurrentFunctionBlock(), nextBlock);
terminateCurrentFunctionBlock();
}
// Move on to the next block.
nextConditionalBlock();
}
// This function is nearly identical to getTypeData(), except for row-major matrices. For the // This function is nearly identical to getTypeData(), except for row-major matrices. For the
// purposes of base alignment and size calculations, it swaps the primary and secondary sizes such // purposes of base alignment and size calculations, it swaps the primary and secondary sizes such
// that the look up always assumes column-major matrices. Row-major matrices are only applicable to // that the look up always assumes column-major matrices. Row-major matrices are only applicable to
......
...@@ -313,6 +313,11 @@ class SPIRVBuilder : angle::NonCopyable ...@@ -313,6 +313,11 @@ class SPIRVBuilder : angle::NonCopyable
spirv::IdRef mergeBlock); spirv::IdRef mergeBlock);
void writeLoopContinueEnd(spirv::IdRef headerBlock); void writeLoopContinueEnd(spirv::IdRef headerBlock);
void writeLoopBodyEnd(spirv::IdRef continueBlock); void writeLoopBodyEnd(spirv::IdRef continueBlock);
void writeSwitch(spirv::IdRef conditionValue,
spirv::IdRef defaultBlock,
const spirv::PairLiteralIntegerIdRefList &targetPairList,
spirv::IdRef mergeBlock);
void writeSwitchCaseBlockEnd();
spirv::IdRef getBoolConstant(bool value); spirv::IdRef getBoolConstant(bool value);
spirv::IdRef getUintConstant(uint32_t value); spirv::IdRef getUintConstant(uint32_t value);
......
...@@ -3128,17 +3128,197 @@ bool OutputSPIRVTraverser::visitIfElse(Visit visit, TIntermIfElse *node) ...@@ -3128,17 +3128,197 @@ bool OutputSPIRVTraverser::visitIfElse(Visit visit, TIntermIfElse *node)
bool OutputSPIRVTraverser::visitSwitch(Visit visit, TIntermSwitch *node) bool OutputSPIRVTraverser::visitSwitch(Visit visit, TIntermSwitch *node)
{ {
// TODO: http://anglebug.com/4889 // Take the following switch:
UNIMPLEMENTED(); //
// switch (c)
// {
// case A:
// ABlock;
// break;
// case B:
// default:
// BBlock;
// break;
// case C:
// CBlock;
// // fallthrough
// case D:
// DBlock;
// }
//
// In SPIR-V, this is implemented similarly to the following pseudo-code:
//
// switch c:
// A -> jump %A
// B -> jump %B
// C -> jump %C
// D -> jump %D
// default -> jump %B
//
// %A:
// ABlock
// jump %merge
//
// %B:
// BBlock
// jump %merge
//
// %C:
// CBlock
// jump %D
//
// %D:
// DBlock
// jump %merge
//
// The OpSwitch instruction contains the jump labels for the default and other cases. Each
// block either terminates with a jump to the merge block or the next block as fallthrough.
//
// // pre-switch block
// OpSelectionMerge %merge None
// OpSwitch %cond %C A %A B %B C %C D %D
//
// %A = OpLabel
// ABlock
// OpBranch %merge
//
// %B = OpLabel
// BBlock
// OpBranch %merge
//
// %C = OpLabel
// CBlock
// OpBranch %D
//
// %D = OpLabel
// DBlock
// OpBranch %merge
if (visit == PreVisit)
{
// Don't add an entry to the stack. The condition will create one, which we won't pop.
return true;
}
// If the condition was just visited, evaluate it and create the switch instruction.
if (visit == InVisit)
{
ASSERT(getLastTraversedChildIndex(visit) == 0);
const spirv::IdRef conditionValue =
accessChainLoad(&mNodeData.back(), mBuilder.getDecorations(node->getInit()->getType()));
// First, need to find out how many blocks are there in the switch.
const TIntermSequence &statements = *node->getStatementList()->getSequence();
bool lastWasCase = true;
size_t blockIndex = 0;
size_t defaultBlockIndex = std::numeric_limits<size_t>::max();
TVector<uint32_t> caseValues;
TVector<size_t> caseBlockIndices;
for (TIntermNode *statement : statements)
{
TIntermCase *caseLabel = statement->getAsCaseNode();
const bool isCaseLabel = caseLabel != nullptr;
if (isCaseLabel)
{
// For every case label, remember its block index. This is used later to generate
// the OpSwitch instruction.
if (caseLabel->hasCondition())
{
// All switch conditions are literals.
TIntermConstantUnion *condition =
caseLabel->getCondition()->getAsConstantUnion();
ASSERT(condition != nullptr);
TConstantUnion caseValue;
caseValue.cast(EbtUInt, *condition->getConstantValue());
caseValues.push_back(caseValue.getUConst());
caseBlockIndices.push_back(blockIndex);
}
else
{
// Remember the block index of the default case.
defaultBlockIndex = blockIndex;
}
lastWasCase = true;
}
else if (lastWasCase)
{
// Every time a non-case node is visited and the previous statement was a case node,
// it's a new block.
++blockIndex;
lastWasCase = false;
}
}
// Block count is the number of blocks based on cases + 1 for the merge block.
const size_t blockCount = blockIndex + 1;
mBuilder.startConditional(blockCount, false, true);
// Generate the switch instructions.
const SpirvConditional *conditional = mBuilder.getCurrentConditional();
// Generate the list of caseValue->blockIndex mapping used by the OpSwitch instruction. If
// the switch ends in a number of cases with no statements following them, they will
// naturally jump to the merge block!
spirv::PairLiteralIntegerIdRefList switchTargets;
for (size_t caseIndex = 0; caseIndex < caseValues.size(); ++caseIndex)
{
uint32_t value = caseValues[caseIndex];
size_t caseBlockIndex = caseBlockIndices[caseIndex];
switchTargets.push_back(
{spirv::LiteralInteger(value), conditional->blockIds[caseBlockIndex]});
}
const spirv::IdRef mergeBlock = conditional->blockIds.back();
const spirv::IdRef defaultBlock = defaultBlockIndex < caseValues.size()
? conditional->blockIds[defaultBlockIndex]
: mergeBlock;
mBuilder.writeSwitch(conditionValue, defaultBlock, switchTargets, mergeBlock);
return true;
}
// Terminate the last block if not already and end the conditional.
mBuilder.writeSwitchCaseBlockEnd();
mBuilder.endConditional();
return true; return true;
} }
bool OutputSPIRVTraverser::visitCase(Visit visit, TIntermCase *node) bool OutputSPIRVTraverser::visitCase(Visit visit, TIntermCase *node)
{ {
// TODO: http://anglebug.com/4889 ASSERT(visit == PreVisit);
UNIMPLEMENTED();
mNodeData.emplace_back();
TIntermBlock *parent = getParentNode()->getAsBlock();
const size_t childIndex = getParentChildIndex(PreVisit);
ASSERT(parent);
const TIntermSequence &parentStatements = *parent->getSequence();
// Check the previous statement. If it was not a |case|, then a new block is being started so
// handle fallthrough:
//
// ...
// statement;
// case X: <--- end the previous block here
// case Y:
//
//
if (childIndex > 0 && parentStatements[childIndex - 1]->getAsCaseNode() == nullptr)
{
mBuilder.writeSwitchCaseBlockEnd();
}
// Don't traverse the condition, as it was processed in visitSwitch.
return false; return false;
} }
...@@ -3161,7 +3341,24 @@ bool OutputSPIRVTraverser::visitBlock(Visit visit, TIntermBlock *node) ...@@ -3161,7 +3341,24 @@ bool OutputSPIRVTraverser::visitBlock(Visit visit, TIntermBlock *node)
// Any node that needed to generate code has already done so, just clean up its data. If // Any node that needed to generate code has already done so, just clean up its data. If
// the child node has no effect, it's automatically discarded (such as variable.field[n].x, // the child node has no effect, it's automatically discarded (such as variable.field[n].x,
// side effects of n already having generated code). // side effects of n already having generated code).
mNodeData.pop_back(); //
// Blocks inside blocks like:
//
// {
// statement;
// {
// statement2;
// }
// }
//
// don't generate nodes.
const size_t childIndex = getLastTraversedChildIndex(visit);
const TIntermSequence &statements = *node->getSequence();
if (statements[childIndex]->getAsBlock() == nullptr)
{
mNodeData.pop_back();
}
return true; return true;
} }
......
...@@ -27,8 +27,6 @@ void TIntermTraverser::traverse(T *node) ...@@ -27,8 +27,6 @@ void TIntermTraverser::traverse(T *node)
bool visit = true; bool visit = true;
mCurrentChildIndex = 0;
// Visit the node before children if pre-visiting. // Visit the node before children if pre-visiting.
if (preVisit) if (preVisit)
visit = node->visit(PreVisit, this); visit = node->visit(PreVisit, this);
...@@ -426,8 +424,6 @@ void TIntermTraverser::traverseFunctionDefinition(TIntermFunctionDefinition *nod ...@@ -426,8 +424,6 @@ void TIntermTraverser::traverseFunctionDefinition(TIntermFunctionDefinition *nod
bool visit = true; bool visit = true;
mCurrentChildIndex = 0;
if (preVisit) if (preVisit)
visit = node->visit(PreVisit, this); visit = node->visit(PreVisit, this);
...@@ -464,7 +460,6 @@ void TIntermTraverser::traverseBlock(TIntermBlock *node) ...@@ -464,7 +460,6 @@ void TIntermTraverser::traverseBlock(TIntermBlock *node)
bool visit = true; bool visit = true;
mCurrentChildIndex = 0;
TIntermSequence *sequence = node->getSequence(); TIntermSequence *sequence = node->getSequence();
if (preVisit) if (preVisit)
......
...@@ -19,6 +19,8 @@ ...@@ -19,6 +19,8 @@
4092 WIN OPENGL : BufferDataOverflowTest.VertexBufferIntegerOverflow/* = SKIP 4092 WIN OPENGL : BufferDataOverflowTest.VertexBufferIntegerOverflow/* = SKIP
4092 WIN GLES : BufferDataOverflowTest.VertexBufferIntegerOverflow/* = SKIP 4092 WIN GLES : BufferDataOverflowTest.VertexBufferIntegerOverflow/* = SKIP
6064 WIN D3D11 : SimpleStateChangeTestES31.DrawThenChangeFBOThenDrawThenFlushInAnotherThreadThenDrawIndexed/* = SKIP 6064 WIN D3D11 : SimpleStateChangeTestES31.DrawThenChangeFBOThenDrawThenFlushInAnotherThreadThenDrawIndexed/* = SKIP
6101 WIN OPENGL INTEL : BlitFramebufferTest.OOBWrite/* = SKIP
6123 WIN AMD VULKAN : GLSLTestLoops.DoWhileContinueInSwitch/* = SKIP
// Linux // Linux
6065 LINUX INTEL VULKAN : SimpleStateChangeTestES31.DrawThenUpdateUBOThenDrawThenDrawIndexed/* = SKIP 6065 LINUX INTEL VULKAN : SimpleStateChangeTestES31.DrawThenUpdateUBOThenDrawThenDrawIndexed/* = SKIP
...@@ -28,9 +30,11 @@ ...@@ -28,9 +30,11 @@
// Nvidia // Nvidia
6115 NVIDIA OPENGL : GLSLTestLoops.DoWhileContinue/* = SKIP 6115 NVIDIA OPENGL : GLSLTestLoops.DoWhileContinue/* = SKIP
6115 NVIDIA OPENGL : GLSLTestLoops.DoWhileUnconditionalContinue/* = SKIP 6115 NVIDIA OPENGL : GLSLTestLoops.DoWhileUnconditionalContinue/* = SKIP
6115 NVIDIA OPENGL : GLSLTestLoops.DoWhileContinueInSwitch/* = SKIP
6115 NVIDIA OPENGL : GLSLTestLoops.WhileBreak/* = SKIP 6115 NVIDIA OPENGL : GLSLTestLoops.WhileBreak/* = SKIP
6115 NVIDIA GLES : GLSLTestLoops.DoWhileContinue/* = SKIP 6115 NVIDIA GLES : GLSLTestLoops.DoWhileContinue/* = SKIP
6115 NVIDIA GLES : GLSLTestLoops.DoWhileUnconditionalContinue/* = SKIP 6115 NVIDIA GLES : GLSLTestLoops.DoWhileUnconditionalContinue/* = SKIP
6115 NVIDIA GLES : GLSLTestLoops.DoWhileContinueInSwitch/* = SKIP
6115 NVIDIA GLES : GLSLTestLoops.WhileBreak/* = SKIP 6115 NVIDIA GLES : GLSLTestLoops.WhileBreak/* = SKIP
// Intel Vulkan // Intel Vulkan
...@@ -53,10 +57,11 @@ ...@@ -53,10 +57,11 @@
6025 MAC AMD OPENGL : IndexBufferOffsetTestES3.UseAsUBOThenUpdateThenUInt8IndexSmallUpdates/* = SKIP 6025 MAC AMD OPENGL : IndexBufferOffsetTestES3.UseAsUBOThenUpdateThenUInt8IndexSmallUpdates/* = SKIP
6096 MAC METAL : GLSLTest_ES3.InitGlobalComplexConstant/* = SKIP 6096 MAC METAL : GLSLTest_ES3.InitGlobalComplexConstant/* = SKIP
6060 MAC METAL : BlitFramebufferTest.OOBWrite/* = SKIP 6060 MAC METAL : BlitFramebufferTest.OOBWrite/* = SKIP
6101 WIN OPENGL INTEL : BlitFramebufferTest.OOBWrite/* = SKIP 6124 MAC OPENGL : GLSLTestLoops.*ContinueInSwitch/* = SKIP
// D3D // D3D
6091 WIN D3D11 : GLSLTest_ES3.InitGlobalComplexConstant/* = SKIP 6091 WIN D3D11 : GLSLTest_ES3.InitGlobalComplexConstant/* = SKIP
6122 WIN D3D11 : GLSLTestLoops.*ContinueInSwitch/* = SKIP
// Android // Android
6095 ANDROID GLES : GLSLTest_ES3.InitGlobalComplexConstant/ES3_OpenGLES = SKIP 6095 ANDROID GLES : GLSLTest_ES3.InitGlobalComplexConstant/ES3_OpenGLES = SKIP
......
...@@ -11598,6 +11598,125 @@ void main() ...@@ -11598,6 +11598,125 @@ void main()
runTest(kFS); runTest(kFS);
} }
// Test for loop with continue inside switch.
TEST_P(GLSLTestLoops, ForContinueInSwitch)
{
constexpr char kFS[] = R"(#version 300 es
precision mediump float;
out vec4 color;
void main()
{
int result = 0;
for (int i = 0; i < 10; ++i)
for (int j = 0; j < 8; ++j)
{
switch (j)
{
case 2:
case 3:
case 4:
++result;
// fallthrough
case 5:
case 6:
++result;
break;
default:
continue;
}
}
color = result == 80 ? vec4(0, 1, 0, 1) : vec4(1, 0, 0, 1);
})";
runTest(kFS);
}
// Test while loop with continue inside switch
TEST_P(GLSLTestLoops, WhileContinueInSwitch)
{
constexpr char kFS[] = R"(#version 300 es
precision mediump float;
out vec4 color;
void main()
{
int result = 0;
int i = 0;
while (i < 10)
{
int j = 0;
while (j < 8)
{
switch (j)
{
case 2:
default:
case 3:
case 4:
++j;
++result;
continue;
case 0:
case 1:
case 7:
break;
}
++j;
}
++i;
}
color = result == 50 ? vec4(0, 1, 0, 1) : vec4(1, 0, 0, 1);
})";
runTest(kFS);
}
// Test do-while loops with continue in switch
TEST_P(GLSLTestLoops, DoWhileContinueInSwitch)
{
constexpr char kFS[] = R"(#version 300 es
precision mediump float;
out vec4 color;
void main()
{
int result = 0;
int i = 0;
do
{
int j = 0;
do
{
switch (j)
{
case 0:
++j;
continue;
default:
case 2:
case 3:
case 4:
++j;
++result;
if (j >= 2 && j <= 6)
break;
else
continue;
}
++result;
} while (j < 8);
++i;
} while (i < 10);
color = result == 120 ? vec4(0, 1, 0, 1) : vec4(1, 0, 0, 1);
})";
runTest(kFS);
}
} // anonymous namespace } // anonymous namespace
ANGLE_INSTANTIATE_TEST_ES2_AND_ES3(GLSLTest); ANGLE_INSTANTIATE_TEST_ES2_AND_ES3(GLSLTest);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment