Commit 8c3d5b4b by David Neto

SPIR-V: Aggressively prune unreachable merge, continue target

More aggressively prune unreachable code as follows. When no control flow edges reach a merge block or continue target: - delete their contents so that: - a merge block becomes OpLabel, then OpUnreachable - a continue target becomes OpLabel, then an OpBranch back to the loop header - any basic block which is dominated by such a merge block or continue target is removed as well. - decorations targeting the removed instructions are removed. Enables the SPIR-V builder post-processing step the GLSLANG_WEB case.
parent b131630e
...@@ -1630,11 +1630,11 @@ void TGlslangToSpvTraverser::finishSpv() ...@@ -1630,11 +1630,11 @@ void TGlslangToSpvTraverser::finishSpv()
for (auto it = iOSet.cbegin(); it != iOSet.cend(); ++it) for (auto it = iOSet.cbegin(); it != iOSet.cend(); ++it)
entryPoint->addIdOperand(*it); entryPoint->addIdOperand(*it);
#ifndef GLSLANG_WEB // Add capabilities, extensions, remove unneeded decorations, etc.,
// Add capabilities, extensions, remove unneeded decorations, etc.,
// based on the resulting SPIR-V. // based on the resulting SPIR-V.
// Note: WebGPU code generation must have the opportunity to aggressively
// prune unreachable merge blocks and continue targets.
builder.postProcess(); builder.postProcess();
#endif
} }
// Write the SPV into 'out'. // Write the SPV into 'out'.
......
...@@ -61,17 +61,22 @@ namespace { ...@@ -61,17 +61,22 @@ namespace {
// Use by calling visit() on the root block. // Use by calling visit() on the root block.
class ReadableOrderTraverser { class ReadableOrderTraverser {
public: public:
explicit ReadableOrderTraverser(std::function<void(Block*)> callback) : callback_(callback) {} ReadableOrderTraverser(std::function<void(Block*, spv::ReachReason, Block*)> callback)
: callback_(callback) {}
// Visits the block if it hasn't been visited already and isn't currently // Visits the block if it hasn't been visited already and isn't currently
// being delayed. Invokes callback(block), then descends into its // being delayed. Invokes callback(block, why, header), then descends into its
// successors. Delays merge-block and continue-block processing until all // successors. Delays merge-block and continue-block processing until all
// the branches have been completed. // the branches have been completed. If |block| is an unreachable merge block or
void visit(Block* block) // an unreachable continue target, then |header| is the corresponding header block.
void visit(Block* block, spv::ReachReason why, Block* header)
{ {
assert(block); assert(block);
if (why == spv::ReachViaControlFlow) {
reachableViaControlFlow_.insert(block);
}
if (visited_.count(block) || delayed_.count(block)) if (visited_.count(block) || delayed_.count(block))
return; return;
callback_(block); callback_(block, why, header);
visited_.insert(block); visited_.insert(block);
Block* mergeBlock = nullptr; Block* mergeBlock = nullptr;
Block* continueBlock = nullptr; Block* continueBlock = nullptr;
...@@ -87,27 +92,40 @@ public: ...@@ -87,27 +92,40 @@ public:
delayed_.insert(continueBlock); delayed_.insert(continueBlock);
} }
} }
const auto successors = block->getSuccessors(); if (why == spv::ReachViaControlFlow) {
for (auto it = successors.cbegin(); it != successors.cend(); ++it) const auto& successors = block->getSuccessors();
visit(*it); for (auto it = successors.cbegin(); it != successors.cend(); ++it)
visit(*it, why, nullptr);
}
if (continueBlock) { if (continueBlock) {
const spv::ReachReason continueWhy =
(reachableViaControlFlow_.count(continueBlock) > 0)
? spv::ReachViaControlFlow
: spv::ReachDeadContinue;
delayed_.erase(continueBlock); delayed_.erase(continueBlock);
visit(continueBlock); visit(continueBlock, continueWhy, block);
} }
if (mergeBlock) { if (mergeBlock) {
const spv::ReachReason mergeWhy =
(reachableViaControlFlow_.count(mergeBlock) > 0)
? spv::ReachViaControlFlow
: spv::ReachDeadMerge;
delayed_.erase(mergeBlock); delayed_.erase(mergeBlock);
visit(mergeBlock); visit(mergeBlock, mergeWhy, block);
} }
} }
private: private:
std::function<void(Block*)> callback_; std::function<void(Block*, spv::ReachReason, Block*)> callback_;
// Whether a block has already been visited or is being delayed. // Whether a block has already been visited or is being delayed.
std::unordered_set<Block *> visited_, delayed_; std::unordered_set<Block *> visited_, delayed_;
// The set of blocks that actually are reached via control flow.
std::unordered_set<Block *> reachableViaControlFlow_;
}; };
} }
void spv::inReadableOrder(Block* root, std::function<void(Block*)> callback) void spv::inReadableOrder(Block* root, std::function<void(Block*, spv::ReachReason, Block*)> callback)
{ {
ReadableOrderTraverser(callback).visit(root); ReadableOrderTraverser(callback).visit(root, spv::ReachViaControlFlow, nullptr);
} }
...@@ -683,14 +683,12 @@ public: ...@@ -683,14 +683,12 @@ public:
// based on the type of the base and the chain of dereferences. // based on the type of the base and the chain of dereferences.
Id accessChainGetInferredType(); Id accessChainGetInferredType();
// Add capabilities, extensions, remove unneeded decorations, etc., // Add capabilities, extensions, remove unneeded decorations, etc.,
// based on the resulting SPIR-V. // based on the resulting SPIR-V.
void postProcess(); void postProcess();
// Hook to visit each instruction in a block in a function // Hook to visit each instruction in a block in a function
void postProcess(Instruction&); void postProcess(Instruction&);
// Hook to visit each instruction in a reachable block in a function.
void postProcessReachable(const Instruction&);
// Hook to visit each non-32-bit sized float/int operation in a block. // Hook to visit each non-32-bit sized float/int operation in a block.
void postProcessType(const Instruction&, spv::Id typeId); void postProcessType(const Instruction&, spv::Id typeId);
......
...@@ -39,6 +39,7 @@ ...@@ -39,6 +39,7 @@
#include <cassert> #include <cassert>
#include <cstdlib> #include <cstdlib>
#include <unordered_map>
#include <unordered_set> #include <unordered_set>
#include <algorithm> #include <algorithm>
...@@ -319,16 +320,14 @@ void Builder::postProcess(Instruction& inst) ...@@ -319,16 +320,14 @@ void Builder::postProcess(Instruction& inst)
} }
} }
// Called for each instruction in a reachable block.
void Builder::postProcessReachable(const Instruction&)
{
// did have code here, but questionable to do so without deleting the instructions
}
// comment in header // comment in header
void Builder::postProcess() void Builder::postProcess()
{ {
// reachableBlocks is the set of blockss reached via control flow, or which are
// unreachable continue targert or unreachable merge.
std::unordered_set<const Block*> reachableBlocks; std::unordered_set<const Block*> reachableBlocks;
std::unordered_map<Block*, Block*> headerForUnreachableContinue;
std::unordered_set<Block*> unreachableMerges;
std::unordered_set<Id> unreachableDefinitions; std::unordered_set<Id> unreachableDefinitions;
// Collect IDs defined in unreachable blocks. For each function, label the // Collect IDs defined in unreachable blocks. For each function, label the
// reachable blocks first. Then for each unreachable block, collect the // reachable blocks first. Then for each unreachable block, collect the
...@@ -336,16 +335,41 @@ void Builder::postProcess() ...@@ -336,16 +335,41 @@ void Builder::postProcess()
for (auto fi = module.getFunctions().cbegin(); fi != module.getFunctions().cend(); fi++) { for (auto fi = module.getFunctions().cbegin(); fi != module.getFunctions().cend(); fi++) {
Function* f = *fi; Function* f = *fi;
Block* entry = f->getEntryBlock(); Block* entry = f->getEntryBlock();
inReadableOrder(entry, [&reachableBlocks](const Block* b) { reachableBlocks.insert(b); }); inReadableOrder(entry,
[&reachableBlocks, &unreachableMerges, &headerForUnreachableContinue]
(Block* b, ReachReason why, Block* header) {
reachableBlocks.insert(b);
if (why == ReachDeadContinue) headerForUnreachableContinue[b] = header;
if (why == ReachDeadMerge) unreachableMerges.insert(b);
});
for (auto bi = f->getBlocks().cbegin(); bi != f->getBlocks().cend(); bi++) { for (auto bi = f->getBlocks().cbegin(); bi != f->getBlocks().cend(); bi++) {
Block* b = *bi; Block* b = *bi;
if (reachableBlocks.count(b) == 0) { if (unreachableMerges.count(b) != 0 || headerForUnreachableContinue.count(b) != 0) {
for (auto ii = b->getInstructions().cbegin(); ii != b->getInstructions().cend(); ii++) auto ii = b->getInstructions().cbegin();
++ii; // Keep potential decorations on the label.
for (; ii != b->getInstructions().cend(); ++ii)
unreachableDefinitions.insert(ii->get()->getResultId());
} else if (reachableBlocks.count(b) == 0) {
// The normal case for unreachable code. All definitions are considered dead.
for (auto ii = b->getInstructions().cbegin(); ii != b->getInstructions().cend(); ++ii)
unreachableDefinitions.insert(ii->get()->getResultId()); unreachableDefinitions.insert(ii->get()->getResultId());
} }
} }
} }
// Modify unreachable merge blocks and unreachable continue targets.
// Delete their contents.
for (auto mergeIter = unreachableMerges.begin(); mergeIter != unreachableMerges.end(); ++mergeIter) {
(*mergeIter)->rewriteAsCanonicalUnreachableMerge();
}
for (auto continueIter = headerForUnreachableContinue.begin();
continueIter != headerForUnreachableContinue.end();
++continueIter) {
Block* continue_target = continueIter->first;
Block* header = continueIter->second;
continue_target->rewriteAsCanonicalUnreachableContinue(header);
}
// Remove unneeded decorations, for unreachable instructions // Remove unneeded decorations, for unreachable instructions
decorations.erase(std::remove_if(decorations.begin(), decorations.end(), decorations.erase(std::remove_if(decorations.begin(), decorations.end(),
[&unreachableDefinitions](std::unique_ptr<Instruction>& I) -> bool { [&unreachableDefinitions](std::unique_ptr<Instruction>& I) -> bool {
...@@ -374,13 +398,6 @@ void Builder::postProcess() ...@@ -374,13 +398,6 @@ void Builder::postProcess()
} }
} }
// process all reachable instructions...
for (auto bi = reachableBlocks.cbegin(); bi != reachableBlocks.cend(); ++bi) {
const Block* block = *bi;
const auto function = [this](const std::unique_ptr<Instruction>& inst) { postProcessReachable(*inst.get()); };
std::for_each(block->getInstructions().begin(), block->getInstructions().end(), function);
}
// process all block-contained instructions // process all block-contained instructions
for (auto fi = module.getFunctions().cbegin(); fi != module.getFunctions().cend(); fi++) { for (auto fi = module.getFunctions().cbegin(); fi != module.getFunctions().cend(); fi++) {
Function* f = *fi; Function* f = *fi;
......
...@@ -226,6 +226,36 @@ public: ...@@ -226,6 +226,36 @@ public:
return nullptr; return nullptr;
} }
// Change this block into a canonical dead merge block. Delete instructions
// as necessary. A canonical dead merge block has only an OpLabel and an
// OpUnreachable.
void rewriteAsCanonicalUnreachableMerge() {
assert(localVariables.empty());
// Delete all instructions except for the label.
assert(instructions.size() > 0);
instructions.resize(1);
successors.clear();
Instruction* unreachable = new Instruction(OpUnreachable);
addInstruction(std::unique_ptr<Instruction>(unreachable));
}
// Change this block into a canonical dead continue target branching to the
// given header ID. Delete instructions as necessary. A canonical dead continue
// target has only an OpLabel and an unconditional branch back to the corresponding
// header.
void rewriteAsCanonicalUnreachableContinue(Block* header) {
assert(localVariables.empty());
// Delete all instructions except for the label.
assert(instructions.size() > 0);
instructions.resize(1);
successors.clear();
// Add OpBranch back to the header.
assert(header != nullptr);
Instruction* branch = new Instruction(OpBranch);
branch->addIdOperand(header->getId());
addInstruction(std::move(std::unique_ptr<Instruction>(branch)));
successors.push_back(header);
}
bool isTerminated() const bool isTerminated() const
{ {
switch (instructions.back()->getOpCode()) { switch (instructions.back()->getOpCode()) {
...@@ -235,6 +265,7 @@ public: ...@@ -235,6 +265,7 @@ public:
case OpKill: case OpKill:
case OpReturn: case OpReturn:
case OpReturnValue: case OpReturnValue:
case OpUnreachable:
return true; return true;
default: default:
return false; return false;
...@@ -268,10 +299,24 @@ protected: ...@@ -268,10 +299,24 @@ protected:
bool unreachable; bool unreachable;
}; };
// The different reasons for reaching a block in the inReadableOrder traversal.
typedef enum ReachReason {
// Reachable from the entry block via transfers of control, i.e. branches.
ReachViaControlFlow = 0,
// A continue target that is not reachable via control flow.
ReachDeadContinue,
// A merge block that is not reachable via control flow.
ReachDeadMerge
};
// Traverses the control-flow graph rooted at root in an order suited for // Traverses the control-flow graph rooted at root in an order suited for
// readable code generation. Invokes callback at every node in the traversal // readable code generation. Invokes callback at every node in the traversal
// order. // order. The callback arguments are:
void inReadableOrder(Block* root, std::function<void(Block*)> callback); // - the block,
// - the reason we reached the block,
// - if the reason was that block is an unreachable continue or unreachable merge block
// then the last parameter is the corresponding header block.
void inReadableOrder(Block* root, std::function<void(Block*, ReachReason, Block* header)> callback);
// //
// SPIR-V IR Function. // SPIR-V IR Function.
...@@ -321,7 +366,7 @@ public: ...@@ -321,7 +366,7 @@ public:
parameterInstructions[p]->dump(out); parameterInstructions[p]->dump(out);
// Blocks // Blocks
inReadableOrder(blocks[0], [&out](const Block* b) { b->dump(out); }); inReadableOrder(blocks[0], [&out](const Block* b, ReachReason, Block*) { b->dump(out); });
Instruction end(0, 0, OpFunctionEnd); Instruction end(0, 0, OpFunctionEnd);
end.dump(out); end.dump(out);
} }
......
...@@ -988,7 +988,7 @@ void CompileAndLinkShaderUnits(std::vector<ShaderCompUnit> compUnits) ...@@ -988,7 +988,7 @@ void CompileAndLinkShaderUnits(std::vector<ShaderCompUnit> compUnits)
// Set base bindings // Set base bindings
shader->setShiftBinding(res, baseBinding[res][compUnit.stage]); shader->setShiftBinding(res, baseBinding[res][compUnit.stage]);
// Set bindings for particular resource sets // Set bindings for particular resource sets
// TODO: use a range based for loop here, when available in all environments. // TODO: use a range based for loop here, when available in all environments.
for (auto i = baseBindingForSet[res][compUnit.stage].begin(); for (auto i = baseBindingForSet[res][compUnit.stage].begin();
......
...@@ -240,6 +240,5 @@ Validation failed ...@@ -240,6 +240,5 @@ Validation failed
60: 7(fvec4) CompositeConstruct 59 59 59 59 60: 7(fvec4) CompositeConstruct 59 59 59 59
ReturnValue 60 ReturnValue 60
30: Label 30: Label
62: 7(fvec4) Undef Unreachable
ReturnValue 62
FunctionEnd FunctionEnd
...@@ -305,6 +305,5 @@ gl_FragCoord origin is upper left ...@@ -305,6 +305,5 @@ gl_FragCoord origin is upper left
66: 9(fvec4) CompositeConstruct 65 65 65 65 66: 9(fvec4) CompositeConstruct 65 65 65 65
ReturnValue 66 ReturnValue 66
45: Label 45: Label
68: 9(fvec4) Undef Unreachable
ReturnValue 68
FunctionEnd FunctionEnd
...@@ -344,6 +344,5 @@ gl_FragCoord origin is upper left ...@@ -344,6 +344,5 @@ gl_FragCoord origin is upper left
84: 9(fvec4) CompositeConstruct 83 83 83 83 84: 9(fvec4) CompositeConstruct 83 83 83 83
ReturnValue 84 ReturnValue 84
53: Label 53: Label
86: 9(fvec4) Undef Unreachable
ReturnValue 86
FunctionEnd FunctionEnd
...@@ -303,6 +303,5 @@ gl_FragCoord origin is upper left ...@@ -303,6 +303,5 @@ gl_FragCoord origin is upper left
66: 9(fvec4) CompositeConstruct 65 65 65 65 66: 9(fvec4) CompositeConstruct 65 65 65 65
ReturnValue 66 ReturnValue 66
45: Label 45: Label
68: 9(fvec4) Undef Unreachable
ReturnValue 68
FunctionEnd FunctionEnd
...@@ -118,6 +118,5 @@ gl_FragCoord origin is upper left ...@@ -118,6 +118,5 @@ gl_FragCoord origin is upper left
23: Label 23: Label
ReturnValue 24 ReturnValue 24
16: Label 16: Label
26: 7(fvec4) Undef Unreachable
ReturnValue 26
FunctionEnd FunctionEnd
...@@ -88,7 +88,7 @@ remap.similar_1a.everything.frag ...@@ -88,7 +88,7 @@ remap.similar_1a.everything.frag
22102: 649(ptr) Variable Function 22102: 649(ptr) Variable Function
24151: 12(int) Load 4408 24151: 12(int) Load 4408
13868: 9(bool) SGreaterThan 24151 2577 13868: 9(bool) SGreaterThan 24151 2577
SelectionMerge 22309 None SelectionMerge 14966 None
BranchConditional 13868 9492 17416 BranchConditional 13868 9492 17416
9492: Label 9492: Label
15624: 12(int) Load 4408 15624: 12(int) Load 4408
...@@ -109,7 +109,6 @@ remap.similar_1a.everything.frag ...@@ -109,7 +109,6 @@ remap.similar_1a.everything.frag
10505: 12(int) IAdd 11462 21176 10505: 12(int) IAdd 11462 21176
14626: 13(float) ConvertSToF 10505 14626: 13(float) ConvertSToF 10505
ReturnValue 14626 ReturnValue 14626
22309: Label 14966: Label
6429: 13(float) Undef Unreachable
ReturnValue 6429
FunctionEnd FunctionEnd
...@@ -124,6 +124,5 @@ remap.similar_1a.none.frag ...@@ -124,6 +124,5 @@ remap.similar_1a.none.frag
68: 8(float) ConvertSToF 67 68: 8(float) ConvertSToF 67
ReturnValue 68 ReturnValue 68
43: Label 43: Label
70: 8(float) Undef Unreachable
ReturnValue 70
FunctionEnd FunctionEnd
...@@ -93,7 +93,7 @@ remap.similar_1b.everything.frag ...@@ -93,7 +93,7 @@ remap.similar_1b.everything.frag
22102: 649(ptr) Variable Function 22102: 649(ptr) Variable Function
24151: 12(int) Load 4408 24151: 12(int) Load 4408
13868: 9(bool) SGreaterThan 24151 2577 13868: 9(bool) SGreaterThan 24151 2577
SelectionMerge 22309 None SelectionMerge 14966 None
BranchConditional 13868 10822 17416 BranchConditional 13868 10822 17416
10822: Label 10822: Label
22680: 12(int) Load 4408 22680: 12(int) Load 4408
...@@ -115,7 +115,6 @@ remap.similar_1b.everything.frag ...@@ -115,7 +115,6 @@ remap.similar_1b.everything.frag
10505: 12(int) IAdd 11462 21176 10505: 12(int) IAdd 11462 21176
14626: 13(float) ConvertSToF 10505 14626: 13(float) ConvertSToF 10505
ReturnValue 14626 ReturnValue 14626
22309: Label 14966: Label
6429: 13(float) Undef Unreachable
ReturnValue 6429
FunctionEnd FunctionEnd
...@@ -130,6 +130,5 @@ remap.similar_1b.none.frag ...@@ -130,6 +130,5 @@ remap.similar_1b.none.frag
73: 8(float) ConvertSToF 72 73: 8(float) ConvertSToF 72
ReturnValue 73 ReturnValue 73
46: Label 46: Label
75: 8(float) Undef Unreachable
ReturnValue 75
FunctionEnd FunctionEnd
spv.dead-after-continue.vert
// Module Version 10000
// Generated by (magic number): 80007
// Id's are bound by 29
Capability Shader
1: ExtInstImport "GLSL.std.450"
MemoryModel Logical GLSL450
EntryPoint Vertex 4 "main" 20 28
Source GLSL 450
Name 4 "main"
Name 8 "i"
Name 20 "o"
Name 28 "c"
Decorate 20(o) Location 0
Decorate 28(c) Location 0
2: TypeVoid
3: TypeFunction 2
6: TypeInt 32 1
7: TypePointer Function 6(int)
9: 6(int) Constant 0
16: 6(int) Constant 5
17: TypeBool
19: TypePointer Output 6(int)
20(o): 19(ptr) Variable Output
21: 6(int) Constant 1
23: 6(int) Constant 2
26: 6(int) Constant 3
27: TypePointer Input 6(int)
28(c): 27(ptr) Variable Input
4(main): 2 Function None 3
5: Label
8(i): 7(ptr) Variable Function
Store 8(i) 9
Branch 10
10: Label
LoopMerge 12 13 None
Branch 14
14: Label
15: 6(int) Load 8(i)
18: 17(bool) SLessThan 15 16
BranchConditional 18 11 12
11: Label
Store 20(o) 21
Branch 13
13: Label
24: 6(int) Load 8(i)
25: 6(int) IAdd 24 21
Store 8(i) 25
Branch 10
12: Label
Store 20(o) 26
Return
FunctionEnd
spv.dead-after-discard.frag
// Module Version 10000
// Generated by (magic number): 80007
// Id's are bound by 15
Capability Shader
1: ExtInstImport "GLSL.std.450"
MemoryModel Logical GLSL450
EntryPoint Fragment 4 "main" 8 14
ExecutionMode 4 OriginUpperLeft
Source GLSL 450
Name 4 "main"
Name 8 "o"
Name 14 "c"
Decorate 8(o) Location 0
Decorate 14(c) Location 0
2: TypeVoid
3: TypeFunction 2
6: TypeInt 32 1
7: TypePointer Output 6(int)
8(o): 7(ptr) Variable Output
9: 6(int) Constant 1
11: 6(int) Constant 3
12: TypeFloat 32
13: TypePointer Input 12(float)
14(c): 13(ptr) Variable Input
4(main): 2 Function None 3
5: Label
Store 8(o) 9
Kill
FunctionEnd
spv.dead-after-loop-break.vert
// Module Version 10000
// Generated by (magic number): 80007
// Id's are bound by 36
Capability Shader
1: ExtInstImport "GLSL.std.450"
MemoryModel Logical GLSL450
EntryPoint Vertex 4 "main" 8 25
Source GLSL 450
Name 4 "main"
Name 8 "o"
Name 11 "i"
Name 25 "c"
Decorate 8(o) Location 0
Decorate 25(c) Location 0
2: TypeVoid
3: TypeFunction 2
6: TypeInt 32 1
7: TypePointer Output 6(int)
8(o): 7(ptr) Variable Output
9: 6(int) Constant 1
10: TypePointer Function 6(int)
12: 6(int) Constant 0
19: 6(int) Constant 5
20: TypeBool
22: 6(int) Constant 2
24: TypePointer Input 6(int)
25(c): 24(ptr) Variable Input
30: 6(int) Constant 3
32: 6(int) Constant 4
35: 6(int) Constant 6
4(main): 2 Function None 3
5: Label
11(i): 10(ptr) Variable Function
Store 8(o) 9
Store 11(i) 12
Branch 13
13: Label
LoopMerge 15 16 None
Branch 17
17: Label
18: 6(int) Load 11(i)
21: 20(bool) SLessThan 18 19
BranchConditional 21 14 15
14: Label
Store 8(o) 22
23: 6(int) Load 11(i)
26: 6(int) Load 25(c)
27: 20(bool) IEqual 23 26
SelectionMerge 29 None
BranchConditional 27 28 29
28: Label
Store 8(o) 30
Branch 15
29: Label
Store 8(o) 19
Branch 16
16: Label
33: 6(int) Load 11(i)
34: 6(int) IAdd 33 9
Store 11(i) 34
Branch 13
15: Label
Store 8(o) 35
Return
FunctionEnd
spv.dead-after-return.vert
// Module Version 10000
// Generated by (magic number): 80007
// Id's are bound by 14
Capability Shader
1: ExtInstImport "GLSL.std.450"
MemoryModel Logical GLSL450
EntryPoint Vertex 4 "main" 8 13
Source GLSL 450
Name 4 "main"
Name 8 "o"
Name 13 "c"
Decorate 8(o) Location 0
Decorate 13(c) Location 0
2: TypeVoid
3: TypeFunction 2
6: TypeInt 32 1
7: TypePointer Output 6(int)
8(o): 7(ptr) Variable Output
9: 6(int) Constant 1
11: 6(int) Constant 3
12: TypePointer Input 6(int)
13(c): 12(ptr) Variable Input
4(main): 2 Function None 3
5: Label
Store 8(o) 9
Return
FunctionEnd
spv.dead-after-switch-break.vert
// Module Version 10000
// Generated by (magic number): 80007
// Id's are bound by 21
Capability Shader
1: ExtInstImport "GLSL.std.450"
MemoryModel Logical GLSL450
EntryPoint Vertex 4 "main" 8 14
Source GLSL 450
Name 4 "main"
Name 8 "c"
Name 14 "o"
Decorate 8(c) Location 0
Decorate 14(o) Location 0
2: TypeVoid
3: TypeFunction 2
6: TypeInt 32 1
7: TypePointer Input 6(int)
8(c): 7(ptr) Variable Input
13: TypePointer Output 6(int)
14(o): 13(ptr) Variable Output
15: 6(int) Constant 1
17: 6(int) Constant 2
20: 6(int) Constant 3
4(main): 2 Function None 3
5: Label
9: 6(int) Load 8(c)
SelectionMerge 12 None
Switch 9 11
case 0: 10
11: Label
Branch 12
10: Label
Store 14(o) 15
Branch 12
12: Label
Store 14(o) 20
Return
FunctionEnd
spv.dead-complex-continue-after-return.vert
// Module Version 10000
// Generated by (magic number): 80007
// Id's are bound by 31
Capability Shader
1: ExtInstImport "GLSL.std.450"
MemoryModel Logical GLSL450
EntryPoint Vertex 4 "main" 11 30
Source GLSL 450
Name 4 "main"
Name 8 "i"
Name 11 "o"
Name 30 "c"
Decorate 11(o) Location 0
Decorate 30(c) Location 0
2: TypeVoid
3: TypeFunction 2
6: TypeInt 32 1
7: TypePointer Function 6(int)
9: 6(int) Constant 0
10: TypePointer Output 6(int)
11(o): 10(ptr) Variable Output
12: 6(int) Constant 1
19: 6(int) Constant 5
20: TypeBool
22: 6(int) Constant 2
24: 6(int) Constant 3
27: 6(int) Constant 99
28: 6(int) Constant 4
29: TypePointer Input 6(int)
30(c): 29(ptr) Variable Input
4(main): 2 Function None 3
5: Label
8(i): 7(ptr) Variable Function
Store 8(i) 9
Store 11(o) 12
Store 8(i) 9
Branch 13
13: Label
LoopMerge 15 16 None
Branch 17
17: Label
18: 6(int) Load 8(i)
21: 20(bool) SLessThan 18 19
BranchConditional 21 14 15
14: Label
Store 11(o) 22
Return
16: Label
Branch 13
15: Label
Store 11(o) 28
Return
FunctionEnd
spv.dead-complex-merge-after-return.vert
// Module Version 10000
// Generated by (magic number): 80007
// Id's are bound by 36
Capability Shader
1: ExtInstImport "GLSL.std.450"
MemoryModel Logical GLSL450
EntryPoint Vertex 4 "main" 11 27
Source GLSL 450
Name 4 "main"
Name 8 "i"
Name 11 "o"
Name 27 "c"
Decorate 11(o) Location 0
Decorate 27(c) Location 0
2: TypeVoid
3: TypeFunction 2
6: TypeInt 32 1
7: TypePointer Function 6(int)
9: 6(int) Constant 0
10: TypePointer Output 6(int)
11(o): 10(ptr) Variable Output
12: 6(int) Constant 1
17: 6(int) Constant 2
19: 6(int) Constant 3
22: 6(int) Constant 5
23: TypeBool
25: 6(int) Constant 4
26: TypePointer Input 6(int)
27(c): 26(ptr) Variable Input
32: 6(int) Constant 100
34: 6(int) Constant 200
35: 6(int) Constant 300
4(main): 2 Function None 3
5: Label
8(i): 7(ptr) Variable Function
Store 8(i) 9
Store 11(o) 12
Branch 13
13: Label
LoopMerge 15 16 None
Branch 14
14: Label
Store 11(o) 17
Return
16: Label
Branch 13
15: Label
Unreachable
FunctionEnd
...@@ -160,7 +160,7 @@ spv.earlyReturnDiscard.frag ...@@ -160,7 +160,7 @@ spv.earlyReturnDiscard.frag
102: Label 102: Label
Return Return
100: Label 100: Label
Branch 67 Unreachable
67: Label 67: Label
106: 7(fvec4) Load 9(color) 106: 7(fvec4) Load 9(color)
107: 7(fvec4) Load 13(color2) 107: 7(fvec4) Load 13(color2)
......
...@@ -38,5 +38,5 @@ spv.for-notest.vert ...@@ -38,5 +38,5 @@ spv.for-notest.vert
Store 8(i) 19 Store 8(i) 19
Branch 10 Branch 10
12: Label 12: Label
Return Unreachable
FunctionEnd FunctionEnd
...@@ -99,8 +99,7 @@ spv.forwardFun.frag ...@@ -99,8 +99,7 @@ spv.forwardFun.frag
45: Label 45: Label
ReturnValue 46 ReturnValue 46
42: Label 42: Label
48: 8(float) Undef Unreachable
ReturnValue 48
FunctionEnd FunctionEnd
16(foo(vf4;): 8(float) Function None 14 16(foo(vf4;): 8(float) Function None 14
15(bar): 13(ptr) FunctionParameter 15(bar): 13(ptr) FunctionParameter
......
...@@ -105,8 +105,7 @@ WARNING: 0:5: varying deprecated in version 130; may be removed in future releas ...@@ -105,8 +105,7 @@ WARNING: 0:5: varying deprecated in version 130; may be removed in future releas
44: Label 44: Label
ReturnValue 45 ReturnValue 45
41: Label 41: Label
47: 6(float) Undef Unreachable
ReturnValue 47
FunctionEnd FunctionEnd
18(missingReturn(): 6(float) Function None 15 18(missingReturn(): 6(float) Function None 15
19: Label 19: Label
......
...@@ -37,5 +37,5 @@ spv.merge-unreachable.frag ...@@ -37,5 +37,5 @@ spv.merge-unreachable.frag
23: Label 23: Label
Return Return
21: Label 21: Label
Return Unreachable
FunctionEnd FunctionEnd
float4 PixelShaderFunction(float input) : COLOR0 void f0() {
{
[unroll] do {} while (false); [unroll] do {} while (false);
}
void f1() {
[unroll] do {;} while (false); [unroll] do {;} while (false);
}
float f2(float input) {
do { return (float4)input; } while (input > 2.0); do { return (float4)input; } while (input > 2.0);
}
void f3(float input) {
do ++input; while (input < 10.0); do ++input; while (input < 10.0);
}
void f4(float input) {
do while (++input < 10.0); while (++input < 10.0); // nest while inside do-while do while (++input < 10.0); while (++input < 10.0); // nest while inside do-while
}
float4 PixelShaderFunction(float input) : COLOR0
{
f0();
f1();
f2(input);
f3(input);
f4(input);
return (float4)input; return (float4)input;
} }
float4 PixelShaderFunction(float4 input) : COLOR0 void f0() {
{
for (;;) ; for (;;) ;
}
void f1(float4 input) {
for (++input; ; ) ; for (++input; ; ) ;
}
void f2(float4 input) {
[unroll] for (; any(input != input); ) {} [unroll] for (; any(input != input); ) {}
}
float f3(float4 input) {
for (; any(input != input); ) { return -input; } for (; any(input != input); ) { return -input; }
}
float f4(float4 input) {
for (--input; any(input != input); input += 2) { return -input; } for (--input; any(input != input); input += 2) { return -input; }
}
void f5(float4 input) {
for (;;) if (input.x > 2.0) break; for (;;) if (input.x > 2.0) break;
}
void f6(float4 input) {
for (;;) if (input.x > 2.0) continue; for (;;) if (input.x > 2.0) continue;
}
void f99() {
for (int first = 0, second = 1; ;) first + second;
}
void f100(float ii) {
for (--ii, --ii, --ii;;) ii;
}
float4 PixelShaderFunction(float4 input) : COLOR0
{
f0();
f1(input);
f2(input);
f3(input);
f4(input);
f5(input);
f6(input);
float ii; float ii;
for (int ii = -1; ii < 3; ++ii) if (ii == 2) continue; for (int ii = -1; ii < 3; ++ii) if (ii == 2) continue;
--ii; --ii;
for (int first = 0, second = 1; ;) first + second;
f99();
for ( int i = 0, count = int(ii); i < count; i++ ); for ( int i = 0, count = int(ii); i < count; i++ );
for (float first = 0, second[2], third; first < second[0]; ++second[1]) first + second[1] + third; for (float first = 0, second[2], third; first < second[0]; ++second[1]) first + second[1] + third;
for (--ii, --ii, --ii;;) ii;
f100(ii);
return input;
} }
float4 PixelShaderFunction(float4 input) : COLOR0 float4 f0(float4 input) {
{
if (all(input == input)) if (all(input == input))
return input; return input;
else
return -input;
}
if (all(input == input)) float4 f1(float4 input) {
if (all(input == input)) {
return input; return input;
else } else {
return -input; return -input;
}
}
float4 PixelShaderFunction(float4 input) : COLOR0
{
if (all(input == input))
return input;
f0(input);
if (all(input == input)) if (all(input == input))
; ;
...@@ -20,11 +32,7 @@ float4 PixelShaderFunction(float4 input) : COLOR0 ...@@ -20,11 +32,7 @@ float4 PixelShaderFunction(float4 input) : COLOR0
return input; return input;
} }
if (all(input == input)) { f1(input);
return input;
} else {
return -input;
}
int ii; int ii;
if (float ii = input.z) if (float ii = input.z)
......
...@@ -4,11 +4,18 @@ ...@@ -4,11 +4,18 @@
bool cond; bool cond;
void f0() {
[[loop]] for (;;) { }
}
void f1() {
[[dont_unroll]] while(true) { }
}
void main() void main()
{ {
[[unroll]] for (int i = 0; i < 8; ++i) { } [[unroll]] for (int i = 0; i < 8; ++i) { }
[[loop]] for (;;) { } f0();
[[dont_unroll]] while(true) { }
[[dependency_infinite]] do { } while(true); [[dependency_infinite]] do { } while(true);
[[dependency_length(1+3)]] for (int i = 0; i < 8; ++i) { } [[dependency_length(1+3)]] for (int i = 0; i < 8; ++i) { }
[[flatten]] if (cond) { } else { } [[flatten]] if (cond) { } else { }
......
#version 450
layout(location =0 ) in int c;
layout(location =0 ) out int o;
void main() {
int i;
for (i=0; i < 5; i++) {
o = 1;
continue;
o = 2;
}
o = 3;
}
#version 450
layout(location =0 ) in float c;
layout(location =0 ) out int o;
void main() {
o = 1;
discard;
o = 3;
}
#version 450
layout(location =0 ) in int c;
layout(location =0 ) out int o;
void main() {
int i;
o = 1;
for (i=0; i < 5; i++) {
o = 2;
if (i==c) {
o = 3;
break;
o = 4;
}
o = 5;
}
o = 6;
}
#version 450
layout(location =0 ) in int c;
layout(location =0 ) out int o;
void main() {
o = 1;
return;
o = 3;
}
#version 450
layout(location =0 ) in int c;
layout(location =0 ) out int o;
void main() {
int i;
switch(c) {
case 0: o=1;
break;
o=2;
default: break;
}
o = 3;
}
#version 450
layout(location =0 ) in int c;
layout(location =0 ) out int o;
void main() {
int i = 0;
o = 1;
// This has non-trivial continue target.
for (i=0; i < 5; ++i, o=99) {
o = 2;
return;
o = 3;
}
// This is considered reachable since Glslang codegen will
// create a conditional branch in the header, and one arm
// of that branch reaches this merge block.
o = 4;
}
#version 450
layout(location =0 ) in int c;
layout(location =0 ) out int o;
void main() {
int i = 0;
o = 1;
do {
o = 2;
return;
o = 3;
} while(i++ < 5);
// All this is a dead merge block.
o = 4;
if (c==4) {
o = 100;
} else {
o = 200;
}
o = 300;
}
...@@ -63,6 +63,7 @@ std::string FileNameAsCustomTestSuffixIoMap( ...@@ -63,6 +63,7 @@ std::string FileNameAsCustomTestSuffixIoMap(
} }
using CompileVulkanToSpirvTest = GlslangTest<::testing::TestWithParam<std::string>>; using CompileVulkanToSpirvTest = GlslangTest<::testing::TestWithParam<std::string>>;
using CompileVulkanToSpirvDeadCodeElimTest = GlslangTest<::testing::TestWithParam<std::string>>;
using CompileVulkanToDebugSpirvTest = GlslangTest<::testing::TestWithParam<std::string>>; using CompileVulkanToDebugSpirvTest = GlslangTest<::testing::TestWithParam<std::string>>;
using CompileVulkan1_1ToSpirvTest = GlslangTest<::testing::TestWithParam<std::string>>; using CompileVulkan1_1ToSpirvTest = GlslangTest<::testing::TestWithParam<std::string>>;
using CompileToSpirv14Test = GlslangTest<::testing::TestWithParam<std::string>>; using CompileToSpirv14Test = GlslangTest<::testing::TestWithParam<std::string>>;
...@@ -85,6 +86,13 @@ TEST_P(CompileVulkanToSpirvTest, FromFile) ...@@ -85,6 +86,13 @@ TEST_P(CompileVulkanToSpirvTest, FromFile)
Target::Spv); Target::Spv);
} }
TEST_P(CompileVulkanToSpirvDeadCodeElimTest, FromFile)
{
loadFileCompileAndCheck(GlobalTestSettings.testRoot, GetParam(),
Source::GLSL, Semantics::Vulkan, glslang::EShTargetVulkan_1_0, glslang::EShTargetSpv_1_0,
Target::Spv);
}
// Compiling GLSL to SPIR-V with debug info under Vulkan semantics. Expected // Compiling GLSL to SPIR-V with debug info under Vulkan semantics. Expected
// to successfully generate SPIR-V. // to successfully generate SPIR-V.
TEST_P(CompileVulkanToDebugSpirvTest, FromFile) TEST_P(CompileVulkanToDebugSpirvTest, FromFile)
...@@ -417,6 +425,23 @@ INSTANTIATE_TEST_CASE_P( ...@@ -417,6 +425,23 @@ INSTANTIATE_TEST_CASE_P(
FileNameAsCustomTestSuffix FileNameAsCustomTestSuffix
); );
// Cases with deliberately unreachable code.
// By default the compiler will aggressively eliminate
// unreachable merges and continues.
INSTANTIATE_TEST_CASE_P(
GlslWithDeadCode, CompileVulkanToSpirvDeadCodeElimTest,
::testing::ValuesIn(std::vector<std::string>({
"spv.dead-after-continue.vert",
"spv.dead-after-discard.frag",
"spv.dead-after-return.vert",
"spv.dead-after-loop-break.vert",
"spv.dead-after-switch-break.vert",
"spv.dead-complex-continue-after-return.vert",
"spv.dead-complex-merge-after-return.vert",
})),
FileNameAsCustomTestSuffix
);
// clang-format off // clang-format off
INSTANTIATE_TEST_CASE_P( INSTANTIATE_TEST_CASE_P(
Glsl, CompileVulkanToDebugSpirvTest, Glsl, CompileVulkanToDebugSpirvTest,
......
...@@ -113,7 +113,7 @@ public: ...@@ -113,7 +113,7 @@ public:
forceVersionProfile(false), forceVersionProfile(false),
isForwardCompatible(false) { isForwardCompatible(false) {
// Perform validation by default. // Perform validation by default.
validatorOptions.validate = true; spirvOptions.validate = true;
} }
// Tries to load the contents from the file at the given |path|. On success, // Tries to load the contents from the file at the given |path|. On success,
...@@ -693,14 +693,14 @@ public: ...@@ -693,14 +693,14 @@ public:
expectedOutputFname, result.spirvWarningsErrors); expectedOutputFname, result.spirvWarningsErrors);
} }
glslang::SpvOptions& options() { return validatorOptions; } glslang::SpvOptions& options() { return spirvOptions; }
private: private:
const int defaultVersion; const int defaultVersion;
const EProfile defaultProfile; const EProfile defaultProfile;
const bool forceVersionProfile; const bool forceVersionProfile;
const bool isForwardCompatible; const bool isForwardCompatible;
glslang::SpvOptions validatorOptions; glslang::SpvOptions spirvOptions;
}; };
} // namespace glslangtest } // namespace glslangtest
......
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