HLSL: implement [unroll] and [loop] attributes

This adds infrastructure suitable for any front end to create SPIR-V loop
control flags.  The only current front end doing so is HLSL.

[unroll] turns into spv::LoopControlUnrollMask
[loop] turns into spv::LoopControlDontUnrollMask
no specification means spv::LoopControlMaskNone
This commit is contained in:
steve-lunarg 2017-05-02 20:14:50 -06:00
parent de1cc06c1d
commit f1709e7146
17 changed files with 334 additions and 17 deletions

View File

@ -122,6 +122,7 @@ protected:
spv::Decoration TranslateAuxiliaryStorageDecoration(const glslang::TQualifier& qualifier);
spv::BuiltIn TranslateBuiltInDecoration(glslang::TBuiltInVariable, bool memberDeclaration);
spv::ImageFormat TranslateImageFormat(const glslang::TType& type);
spv::LoopControlMask TranslateLoopControl(glslang::TLoopControl) const;
spv::Id createSpvVariable(const glslang::TIntermSymbol*);
spv::Id getSampledType(const glslang::TSampler&);
spv::Id getInvertedSwizzleType(const glslang::TIntermTyped&);
@ -767,6 +768,18 @@ spv::ImageFormat TGlslangToSpvTraverser::TranslateImageFormat(const glslang::TTy
}
}
spv::LoopControlMask TGlslangToSpvTraverser::TranslateLoopControl(glslang::TLoopControl loopControl) const
{
switch (loopControl) {
case glslang::ELoopControlNone: return spv::LoopControlMaskNone;
case glslang::ELoopControlUnroll: return spv::LoopControlUnrollMask;
case glslang::ELoopControlDontUnroll: return spv::LoopControlDontUnrollMask;
// TODO: DependencyInfinite
// TODO: DependencyLength
default: return spv::LoopControlMaskNone;
}
}
// Return whether or not the given type is something that should be tied to a
// descriptor set.
bool IsDescriptorResource(const glslang::TType& type)
@ -1960,6 +1973,12 @@ bool TGlslangToSpvTraverser::visitLoop(glslang::TVisit /* visit */, glslang::TIn
{
auto blocks = builder.makeNewLoop();
builder.createBranch(&blocks.head);
// Loop control:
const spv::LoopControlMask control = TranslateLoopControl(node->getLoopControl());
// TODO: dependency length
// Spec requires back edges to target header blocks, and every header block
// must dominate its merge block. Make a header block first to ensure these
// conditions are met. By definition, it will contain OpLoopMerge, followed
@ -1967,7 +1986,7 @@ bool TGlslangToSpvTraverser::visitLoop(glslang::TVisit /* visit */, glslang::TIn
// instructions in it, since the body/test may have arbitrary instructions,
// including merges of its own.
builder.setBuildPoint(&blocks.head);
builder.createLoopMerge(&blocks.merge, &blocks.continue_target, spv::LoopControlMaskNone);
builder.createLoopMerge(&blocks.merge, &blocks.continue_target, control);
if (node->testFirst() && node->getTest()) {
spv::Block& test = builder.makeNewBlock();
builder.createBranch(&test);

View File

@ -132,7 +132,7 @@ local_size = (4, 6, 8)
Store 13(x) 14
Branch 15
15: Label
LoopMerge 17 18 None
LoopMerge 17 18 Unroll
Branch 19
19: Label
20: 11(int) Load 13(x)

View File

@ -194,7 +194,7 @@ gl_FragCoord origin is upper left
12: Label
Branch 13
13: Label
LoopMerge 15 16 None
LoopMerge 15 16 Unroll
Branch 14
14: Label
Branch 16
@ -203,7 +203,7 @@ gl_FragCoord origin is upper left
15: Label
Branch 19
19: Label
LoopMerge 21 22 None
LoopMerge 21 22 Unroll
Branch 20
20: Label
Branch 22

View File

@ -338,7 +338,7 @@ gl_FragCoord origin is upper left
23: Label
Branch 25
25: Label
LoopMerge 27 28 None
LoopMerge 27 28 Unroll
Branch 29
29: Label
30: 7(fvec4) Load 10(input)

View File

@ -0,0 +1,233 @@
hlsl.loopattr.frag
Shader version: 500
gl_FragCoord origin is upper left
0:? Sequence
0:3 Function Definition: @main( ( temp 4-component vector of float)
0:3 Function Parameters:
0:? Sequence
0:5 Sequence
0:5 move second child to first child ( temp int)
0:5 'x' ( temp int)
0:5 Constant:
0:5 0 (const int)
0:5 Loop with condition tested first
0:5 Loop Condition
0:5 Compare Less Than ( temp bool)
0:5 'x' ( temp int)
0:5 Constant:
0:5 5 (const int)
0:5 No loop body
0:5 Loop Terminal Expression
0:5 Pre-Increment ( temp int)
0:5 'x' ( temp int)
0:8 Sequence
0:8 move second child to first child ( temp int)
0:8 'y' ( temp int)
0:8 Constant:
0:8 0 (const int)
0:8 Loop with condition tested first
0:8 Loop Condition
0:8 Compare Less Than ( temp bool)
0:8 'y' ( temp int)
0:8 Constant:
0:8 5 (const int)
0:8 No loop body
0:8 Loop Terminal Expression
0:8 Pre-Increment ( temp int)
0:8 'y' ( temp int)
0:11 Sequence
0:11 move second child to first child ( temp int)
0:11 'z' ( temp int)
0:11 Constant:
0:11 0 (const int)
0:11 Loop with condition tested first
0:11 Loop Condition
0:11 Compare Less Than ( temp bool)
0:11 'z' ( temp int)
0:11 Constant:
0:11 5 (const int)
0:11 No loop body
0:11 Loop Terminal Expression
0:11 Pre-Increment ( temp int)
0:11 'z' ( temp int)
0:13 Branch: Return with expression
0:13 Constant:
0:13 0.000000
0:13 0.000000
0:13 0.000000
0:13 0.000000
0:3 Function Definition: main( ( temp void)
0:3 Function Parameters:
0:? Sequence
0:3 move second child to first child ( temp 4-component vector of float)
0:? '@entryPointOutput' (layout( location=0) out 4-component vector of float)
0:3 Function Call: @main( ( temp 4-component vector of float)
0:? Linker Objects
0:? '@entryPointOutput' (layout( location=0) out 4-component vector of float)
Linked fragment stage:
Shader version: 500
gl_FragCoord origin is upper left
0:? Sequence
0:3 Function Definition: @main( ( temp 4-component vector of float)
0:3 Function Parameters:
0:? Sequence
0:5 Sequence
0:5 move second child to first child ( temp int)
0:5 'x' ( temp int)
0:5 Constant:
0:5 0 (const int)
0:5 Loop with condition tested first
0:5 Loop Condition
0:5 Compare Less Than ( temp bool)
0:5 'x' ( temp int)
0:5 Constant:
0:5 5 (const int)
0:5 No loop body
0:5 Loop Terminal Expression
0:5 Pre-Increment ( temp int)
0:5 'x' ( temp int)
0:8 Sequence
0:8 move second child to first child ( temp int)
0:8 'y' ( temp int)
0:8 Constant:
0:8 0 (const int)
0:8 Loop with condition tested first
0:8 Loop Condition
0:8 Compare Less Than ( temp bool)
0:8 'y' ( temp int)
0:8 Constant:
0:8 5 (const int)
0:8 No loop body
0:8 Loop Terminal Expression
0:8 Pre-Increment ( temp int)
0:8 'y' ( temp int)
0:11 Sequence
0:11 move second child to first child ( temp int)
0:11 'z' ( temp int)
0:11 Constant:
0:11 0 (const int)
0:11 Loop with condition tested first
0:11 Loop Condition
0:11 Compare Less Than ( temp bool)
0:11 'z' ( temp int)
0:11 Constant:
0:11 5 (const int)
0:11 No loop body
0:11 Loop Terminal Expression
0:11 Pre-Increment ( temp int)
0:11 'z' ( temp int)
0:13 Branch: Return with expression
0:13 Constant:
0:13 0.000000
0:13 0.000000
0:13 0.000000
0:13 0.000000
0:3 Function Definition: main( ( temp void)
0:3 Function Parameters:
0:? Sequence
0:3 move second child to first child ( temp 4-component vector of float)
0:? '@entryPointOutput' (layout( location=0) out 4-component vector of float)
0:3 Function Call: @main( ( temp 4-component vector of float)
0:? Linker Objects
0:? '@entryPointOutput' (layout( location=0) out 4-component vector of float)
// Module Version 10000
// Generated by (magic number): 80001
// Id's are bound by 54
Capability Shader
1: ExtInstImport "GLSL.std.450"
MemoryModel Logical GLSL450
EntryPoint Fragment 4 "main" 52
ExecutionMode 4 OriginUpperLeft
Source HLSL 500
Name 4 "main"
Name 9 "@main("
Name 13 "x"
Name 27 "y"
Name 37 "z"
Name 52 "@entryPointOutput"
Decorate 52(@entryPointOutput) Location 0
2: TypeVoid
3: TypeFunction 2
6: TypeFloat 32
7: TypeVector 6(float) 4
8: TypeFunction 7(fvec4)
11: TypeInt 32 1
12: TypePointer Function 11(int)
14: 11(int) Constant 0
21: 11(int) Constant 5
22: TypeBool
25: 11(int) Constant 1
47: 6(float) Constant 0
48: 7(fvec4) ConstantComposite 47 47 47 47
51: TypePointer Output 7(fvec4)
52(@entryPointOutput): 51(ptr) Variable Output
4(main): 2 Function None 3
5: Label
53: 7(fvec4) FunctionCall 9(@main()
Store 52(@entryPointOutput) 53
Return
FunctionEnd
9(@main(): 7(fvec4) Function None 8
10: Label
13(x): 12(ptr) Variable Function
27(y): 12(ptr) Variable Function
37(z): 12(ptr) Variable Function
Store 13(x) 14
Branch 15
15: Label
LoopMerge 17 18 Unroll
Branch 19
19: Label
20: 11(int) Load 13(x)
23: 22(bool) SLessThan 20 21
BranchConditional 23 16 17
16: Label
Branch 18
18: Label
24: 11(int) Load 13(x)
26: 11(int) IAdd 24 25
Store 13(x) 26
Branch 15
17: Label
Store 27(y) 14
Branch 28
28: Label
LoopMerge 30 31 DontUnroll
Branch 32
32: Label
33: 11(int) Load 27(y)
34: 22(bool) SLessThan 33 21
BranchConditional 34 29 30
29: Label
Branch 31
31: Label
35: 11(int) Load 27(y)
36: 11(int) IAdd 35 25
Store 27(y) 36
Branch 28
30: Label
Store 37(z) 14
Branch 38
38: Label
LoopMerge 40 41 None
Branch 42
42: Label
43: 11(int) Load 37(z)
44: 22(bool) SLessThan 43 21
BranchConditional 44 39 40
39: Label
Branch 41
41: Label
45: 11(int) Load 37(z)
46: 11(int) IAdd 45 25
Store 37(z) 46
Branch 38
40: Label
ReturnValue 48
FunctionEnd

View File

@ -171,7 +171,7 @@ gl_FragCoord origin is upper left
28: Label
Branch 32
32: Label
LoopMerge 34 35 None
LoopMerge 34 35 Unroll
Branch 36
36: Label
BranchConditional 31 33 34

14
Test/hlsl.loopattr.frag Normal file
View File

@ -0,0 +1,14 @@
float4 main() : SV_Target0
{
// Unroll hint
[unroll(5) ] for (int x=0; x<5; ++x);
// Don't unroll hint
[loop] for (int y=0; y<5; ++y);
// No hint
for (int z=0; z<5; ++z);
return 0;
}

View File

@ -758,6 +758,15 @@ protected:
TType type;
};
//
// Loop control hints
//
enum TLoopControl {
ELoopControlNone,
ELoopControlUnroll,
ELoopControlDontUnroll,
};
//
// Handle for, do-while, and while loops.
//
@ -767,17 +776,25 @@ public:
body(aBody),
test(aTest),
terminal(aTerminal),
first(testFirst) { }
first(testFirst),
control(ELoopControlNone)
{ }
virtual void traverse(TIntermTraverser*);
TIntermNode* getBody() const { return body; }
TIntermTyped* getTest() const { return test; }
TIntermTyped* getTerminal() const { return terminal; }
bool testFirst() const { return first; }
void setLoopControl(TLoopControl c) { control = c; }
TLoopControl getLoopControl() const { return control; }
protected:
TIntermNode* body; // code to loop over
TIntermTyped* test; // exit condition associated with loop, could be 0 for 'for' loops
TIntermTyped* terminal; // exists for for-loops
bool first; // true for while and for, not for do-while
TLoopControl control; // loop control hint
};
//

View File

@ -1630,10 +1630,11 @@ const TIntermTyped* TIntermediate::findLValueBase(const TIntermTyped* node, bool
//
// Create while and do-while loop nodes.
//
TIntermLoop* TIntermediate::addLoop(TIntermNode* body, TIntermTyped* test, TIntermTyped* terminal, bool testFirst, const TSourceLoc& loc)
TIntermLoop* TIntermediate::addLoop(TIntermNode* body, TIntermTyped* test, TIntermTyped* terminal, bool testFirst, const TSourceLoc& loc, TLoopControl control)
{
TIntermLoop* node = new TIntermLoop(body, test, terminal, testFirst);
node->setLoc(loc);
node->setLoopControl(control);
return node;
}
@ -1641,10 +1642,11 @@ TIntermLoop* TIntermediate::addLoop(TIntermNode* body, TIntermTyped* test, TInte
//
// Create a for-loop sequence.
//
TIntermAggregate* TIntermediate::addForLoop(TIntermNode* body, TIntermNode* initializer, TIntermTyped* test, TIntermTyped* terminal, bool testFirst, const TSourceLoc& loc)
TIntermAggregate* TIntermediate::addForLoop(TIntermNode* body, TIntermNode* initializer, TIntermTyped* test, TIntermTyped* terminal, bool testFirst, const TSourceLoc& loc, TLoopControl control)
{
TIntermLoop* node = new TIntermLoop(body, test, terminal, testFirst);
node->setLoc(loc);
node->setLoopControl(control);
// make a sequence of the initializer and statement
TIntermAggregate* loopSequence = makeAggregate(initializer, loc);

View File

@ -283,8 +283,8 @@ public:
TIntermConstantUnion* addConstantUnion(const TString*, const TSourceLoc&, bool literal = false) const;
TIntermTyped* promoteConstantUnion(TBasicType, TIntermConstantUnion*) const;
bool parseConstTree(TIntermNode*, TConstUnionArray, TOperator, const TType&, bool singleConstantParam = false);
TIntermLoop* addLoop(TIntermNode*, TIntermTyped*, TIntermTyped*, bool testFirst, const TSourceLoc&);
TIntermAggregate* addForLoop(TIntermNode*, TIntermNode*, TIntermTyped*, TIntermTyped*, bool testFirst, const TSourceLoc&);
TIntermLoop* addLoop(TIntermNode*, TIntermTyped*, TIntermTyped*, bool testFirst, const TSourceLoc&, TLoopControl = ELoopControlNone);
TIntermAggregate* addForLoop(TIntermNode*, TIntermNode*, TIntermTyped*, TIntermTyped*, bool testFirst, const TSourceLoc&, TLoopControl = ELoopControlNone);
TIntermBranch* addBranch(TOperator, const TSourceLoc&);
TIntermBranch* addBranch(TOperator, TIntermTyped*, const TSourceLoc&);
template<typename selectorType> TIntermTyped* addSwizzle(TSwizzleSelectors<selectorType>&, const TSourceLoc&);

View File

@ -175,6 +175,7 @@ INSTANTIATE_TEST_CASE_P(
{"hlsl.logical.binary.vec.frag", "main"},
{"hlsl.logicalConvert.frag", "main"},
{"hlsl.logical.unary.frag", "main"},
{"hlsl.loopattr.frag", "main"},
{"hlsl.namespace.frag", "main"},
{"hlsl.nonint-index.frag", "main"},
{"hlsl.matNx1.frag", "main"},

View File

@ -80,6 +80,8 @@ namespace glslang {
return EatPatchConstantFunc;
else if (lowername == "unroll")
return EatUnroll;
else if (lowername == "loop")
return EatLoop;
else
return EatNone;
}
@ -107,4 +109,10 @@ namespace glslang {
return (entry == attributes.end()) ? nullptr : entry->second;
}
// True if entry exists in map (even if value is nullptr)
bool TAttributeMap::contains(TAttributeType attr) const
{
return attributes.find(attr) != attributes.end();
}
} // end namespace glslang

View File

@ -62,6 +62,7 @@ namespace glslang {
EatPatchConstantFunc,
EatPatchSize,
EatUnroll,
EatLoop,
};
}
@ -86,6 +87,9 @@ namespace glslang {
// Const lookup: search for (but do not modify) the attribute in the map.
const TIntermAggregate* operator[](TAttributeType) const;
// True if entry exists in map (even if value is nullptr)
bool contains(TAttributeType) const;
protected:
// Find an attribute enum given its name.
static TAttributeType attributeFromName(const TString&);

View File

@ -3127,7 +3127,7 @@ bool HlslGrammar::acceptStatement(TIntermNode*& statement)
case EHTokFor:
case EHTokDo:
case EHTokWhile:
return acceptIterationStatement(statement);
return acceptIterationStatement(statement, attributes);
case EHTokContinue:
case EHTokBreak:
@ -3336,7 +3336,7 @@ bool HlslGrammar::acceptSwitchStatement(TIntermNode*& statement)
// | FOR LEFT_PAREN for_init_statement for_rest_statement RIGHT_PAREN statement
//
// Non-speculative, only call if it needs to be found; WHILE or DO or FOR already seen.
bool HlslGrammar::acceptIterationStatement(TIntermNode*& statement)
bool HlslGrammar::acceptIterationStatement(TIntermNode*& statement, const TAttributeMap& attributes)
{
TSourceLoc loc = token.loc;
TIntermTyped* condition = nullptr;
@ -3347,6 +3347,8 @@ bool HlslGrammar::acceptIterationStatement(TIntermNode*& statement)
// WHILE or DO or FOR
advanceToken();
const TLoopControl control = parseContext.handleLoopControl(attributes);
switch (loop) {
case EHTokWhile:
// so that something declared in the condition is scoped to the lifetime
@ -3370,7 +3372,7 @@ bool HlslGrammar::acceptIterationStatement(TIntermNode*& statement)
parseContext.unnestLooping();
parseContext.popScope();
statement = intermediate.addLoop(statement, condition, nullptr, true, loc);
statement = intermediate.addLoop(statement, condition, nullptr, true, loc, control);
return true;
@ -3402,7 +3404,7 @@ bool HlslGrammar::acceptIterationStatement(TIntermNode*& statement)
parseContext.unnestLooping();
statement = intermediate.addLoop(statement, condition, 0, false, loc);
statement = intermediate.addLoop(statement, condition, 0, false, loc, control);
return true;
@ -3451,7 +3453,7 @@ bool HlslGrammar::acceptIterationStatement(TIntermNode*& statement)
return false;
}
statement = intermediate.addForLoop(statement, initNode, condition, iterator, true, loc);
statement = intermediate.addForLoop(statement, initNode, condition, iterator, true, loc, control);
parseContext.popScope();
parseContext.unnestLooping();

View File

@ -116,7 +116,7 @@ namespace glslang {
void acceptAttributes(TAttributeMap&);
bool acceptSelectionStatement(TIntermNode*&);
bool acceptSwitchStatement(TIntermNode*&);
bool acceptIterationStatement(TIntermNode*&);
bool acceptIterationStatement(TIntermNode*&, const TAttributeMap&);
bool acceptJumpStatement(TIntermNode*&);
bool acceptCaseLabel(TIntermNode*&);
bool acceptDefaultLabel(TIntermNode*&);

View File

@ -7568,6 +7568,20 @@ bool HlslParseContext::handleOutputGeometry(const TSourceLoc& loc, const TLayout
return true;
}
//
// Loop hints
//
TLoopControl HlslParseContext::handleLoopControl(const TAttributeMap& attributes) const
{
if (attributes.contains(EatUnroll))
return ELoopControlUnroll;
else if (attributes.contains(EatLoop))
return ELoopControlDontUnroll;
else
return ELoopControlNone;
}
//
// Updating default qualifier for the case of a declaration with just a qualifier,
// no type, block, or identifier.

View File

@ -192,6 +192,9 @@ public:
bool handleOutputGeometry(const TSourceLoc&, const TLayoutGeometry& geometry);
bool handleInputGeometry(const TSourceLoc&, const TLayoutGeometry& geometry);
// Determine loop control from attributes
TLoopControl handleLoopControl(const TAttributeMap& attributes) const;
// Potentially rename shader entry point function
void renameShaderFunction(const TString*& name) const;