Commit e5921f13 by steve-lunarg

HLSL: Fix unary and binary operator type conversion issues

This fixes defects as follows: 1. handleLvalue could be called on a non-L-value, and it shouldn't be. 2. HLSL allows unary negation on non-bool values. TUnaryOperator::promote can now promote other types (e.g, int, float) to bool for this op. 3. HLSL allows binary logical operations (&&, ||) on arbitrary types, similar (2). 4. HLSL allows mod operation on arbitrary types, which will be promoted. E.g, int % float -> float % float.
parent 5d45eade
struct PS_OUTPUT
{
float4 Color : SV_Target0;
};
uniform int ival;
uniform int4 ival4;
uniform float fval;
uniform float4 fval4;
PS_OUTPUT main()
{
if (ival && fval);
if (ival || fval);
PS_OUTPUT psout;
psout.Color = 1.0;
return psout;
}
struct PS_OUTPUT
{
float4 Color : SV_Target0;
};
uniform int ival;
uniform int4 ival4;
uniform float fval;
uniform float4 fval4;
PS_OUTPUT main()
{
!ival; // scalar int
!ival4; // vector int
!fval; // scalar float
!fval4; // vector float
if (ival);
if (fval);
if (!ival);
if (!fval);
PS_OUTPUT psout;
psout.Color = 1.0;
return psout;
}
struct PS_OUTPUT
{
float4 Color : SV_Target0;
};
uniform bool bval;
uniform bool4 bval4;
uniform int ival;
uniform int4 ival4;
uniform float fval;
uniform float4 fval4;
PS_OUTPUT main()
{
ival % fval;
ival4 % fval4;
bval % fval;
bval4 % fval4;
int l_int = 1;
l_int %= fval;
PS_OUTPUT psout;
psout.Color = 0;
return psout;
}
...@@ -52,6 +52,8 @@ ...@@ -52,6 +52,8 @@
namespace glslang { namespace glslang {
class TIntermediate;
// //
// Operators used by the high-level (parse tree) representation. // Operators used by the high-level (parse tree) representation.
// //
...@@ -845,7 +847,7 @@ public: ...@@ -845,7 +847,7 @@ public:
virtual TIntermOperator* getAsOperator() { return this; } virtual TIntermOperator* getAsOperator() { return this; }
virtual const TIntermOperator* getAsOperator() const { return this; } virtual const TIntermOperator* getAsOperator() const { return this; }
TOperator getOp() const { return op; } TOperator getOp() const { return op; }
virtual bool promote() { return true; } virtual bool promote(TIntermediate&) { return true; }
bool modifiesState() const; bool modifiesState() const;
bool isConstructor() const; bool isConstructor() const;
bool isTexture() const { return op > EOpTextureGuardBegin && op < EOpTextureGuardEnd; } bool isTexture() const { return op > EOpTextureGuardBegin && op < EOpTextureGuardEnd; }
...@@ -1024,7 +1026,7 @@ public: ...@@ -1024,7 +1026,7 @@ public:
virtual TIntermTyped* getRight() const { return right; } virtual TIntermTyped* getRight() const { return right; }
virtual TIntermBinary* getAsBinaryNode() { return this; } virtual TIntermBinary* getAsBinaryNode() { return this; }
virtual const TIntermBinary* getAsBinaryNode() const { return this; } virtual const TIntermBinary* getAsBinaryNode() const { return this; }
virtual bool promote(); virtual bool promote(TIntermediate&);
virtual void updatePrecision(); virtual void updatePrecision();
protected: protected:
TIntermTyped* left; TIntermTyped* left;
...@@ -1044,7 +1046,7 @@ public: ...@@ -1044,7 +1046,7 @@ public:
virtual const TIntermTyped* getOperand() const { return operand; } virtual const TIntermTyped* getOperand() const { return operand; }
virtual TIntermUnary* getAsUnaryNode() { return this; } virtual TIntermUnary* getAsUnaryNode() { return this; }
virtual const TIntermUnary* getAsUnaryNode() const { return this; } virtual const TIntermUnary* getAsUnaryNode() const { return this; }
virtual bool promote(); virtual bool promote(TIntermediate&);
virtual void updatePrecision(); virtual void updatePrecision();
protected: protected:
TIntermTyped* operand; TIntermTyped* operand;
......
...@@ -137,7 +137,7 @@ TIntermTyped* TIntermediate::addBinaryMath(TOperator op, TIntermTyped* left, TIn ...@@ -137,7 +137,7 @@ TIntermTyped* TIntermediate::addBinaryMath(TOperator op, TIntermTyped* left, TIn
// one and promote it to the right type. // one and promote it to the right type.
// //
TIntermBinary* node = addBinaryNode(op, left, right, loc); TIntermBinary* node = addBinaryNode(op, left, right, loc);
if (! node->promote()) if (! node->promote(*this))
return 0; return 0;
node->updatePrecision(); node->updatePrecision();
...@@ -246,7 +246,7 @@ TIntermTyped* TIntermediate::addAssign(TOperator op, TIntermTyped* left, TInterm ...@@ -246,7 +246,7 @@ TIntermTyped* TIntermediate::addAssign(TOperator op, TIntermTyped* left, TInterm
// build the node // build the node
TIntermBinary* node = addBinaryNode(op, left, right, loc); TIntermBinary* node = addBinaryNode(op, left, right, loc);
if (! node->promote()) if (! node->promote(*this))
return nullptr; return nullptr;
node->updatePrecision(); node->updatePrecision();
...@@ -282,6 +282,10 @@ TIntermTyped* TIntermediate::addUnaryMath(TOperator op, TIntermTyped* child, TSo ...@@ -282,6 +282,10 @@ TIntermTyped* TIntermediate::addUnaryMath(TOperator op, TIntermTyped* child, TSo
switch (op) { switch (op) {
case EOpLogicalNot: case EOpLogicalNot:
if (source == EShSourceHlsl) {
break; // HLSL can promote logical not
}
if (child->getType().getBasicType() != EbtBool || child->getType().isMatrix() || child->getType().isArray() || child->getType().isVector()) { if (child->getType().getBasicType() != EbtBool || child->getType().isMatrix() || child->getType().isArray() || child->getType().isVector()) {
return 0; return 0;
} }
...@@ -349,7 +353,7 @@ TIntermTyped* TIntermediate::addUnaryMath(TOperator op, TIntermTyped* child, TSo ...@@ -349,7 +353,7 @@ TIntermTyped* TIntermediate::addUnaryMath(TOperator op, TIntermTyped* child, TSo
// //
TIntermUnary* node = addUnaryNode(op, child, loc); TIntermUnary* node = addUnaryNode(op, child, loc);
if (! node->promote()) if (! node->promote(*this))
return 0; return 0;
node->updatePrecision(); node->updatePrecision();
...@@ -555,6 +559,10 @@ TIntermTyped* TIntermediate::addConversion(TOperator op, const TType& type, TInt ...@@ -555,6 +559,10 @@ TIntermTyped* TIntermediate::addConversion(TOperator op, const TType& type, TInt
case EOpAndAssign: case EOpAndAssign:
case EOpInclusiveOrAssign: case EOpInclusiveOrAssign:
case EOpExclusiveOrAssign: case EOpExclusiveOrAssign:
case EOpLogicalNot:
case EOpLogicalAnd:
case EOpLogicalOr:
case EOpLogicalXor:
case EOpFunctionCall: case EOpFunctionCall:
case EOpReturn: case EOpReturn:
...@@ -841,6 +849,10 @@ bool TIntermediate::canImplicitlyPromote(TBasicType from, TBasicType to, TOperat ...@@ -841,6 +849,10 @@ bool TIntermediate::canImplicitlyPromote(TBasicType from, TBasicType to, TOperat
case EOpModAssign: // ... case EOpModAssign: // ...
case EOpReturn: // function returns can also perform arbitrary conversions case EOpReturn: // function returns can also perform arbitrary conversions
case EOpFunctionCall: // conversion of a calling parameter case EOpFunctionCall: // conversion of a calling parameter
case EOpLogicalNot:
case EOpLogicalAnd:
case EOpLogicalOr:
case EOpLogicalXor:
return true; return true;
default: default:
break; break;
...@@ -873,6 +885,8 @@ bool TIntermediate::canImplicitlyPromote(TBasicType from, TBasicType to, TOperat ...@@ -873,6 +885,8 @@ bool TIntermediate::canImplicitlyPromote(TBasicType from, TBasicType to, TOperat
case EbtFloat16: case EbtFloat16:
#endif #endif
return true; return true;
case EbtBool:
return (source == EShSourceHlsl);
default: default:
return false; return false;
} }
...@@ -1716,13 +1730,20 @@ bool TIntermOperator::isConstructor() const ...@@ -1716,13 +1730,20 @@ bool TIntermOperator::isConstructor() const
// //
// Returns false in nothing makes sense. // Returns false in nothing makes sense.
// //
bool TIntermUnary::promote() bool TIntermUnary::promote(TIntermediate& intermediate)
{ {
switch (op) { switch (op) {
case EOpLogicalNot: case EOpLogicalNot:
if (operand->getBasicType() != EbtBool) // Convert operand to a boolean type
if (operand->getBasicType() != EbtBool) {
// Add constructor to boolean type. If that fails, we can't do it, so return false.
TIntermTyped* converted = intermediate.convertToBasicType(op, EbtBool, operand);
if (converted == nullptr)
return false;
return false; // Use the result of converting the node to a bool.
operand = converted;
}
break; break;
case EOpBitwiseNot: case EOpBitwiseNot:
if (operand->getBasicType() != EbtInt && if (operand->getBasicType() != EbtInt &&
...@@ -1774,13 +1795,31 @@ void TIntermUnary::updatePrecision() ...@@ -1774,13 +1795,31 @@ void TIntermUnary::updatePrecision()
} }
} }
// If it is not already, convert this node to the given basic type.
TIntermTyped* TIntermediate::convertToBasicType(TOperator op, TBasicType basicType, TIntermTyped* node) const
{
if (node == nullptr)
return nullptr;
// It's already this basic type: nothing needs to be done, so use the node directly.
if (node->getBasicType() == basicType)
return node;
const TType& type = node->getType();
const TType newType(basicType, type.getQualifier().storage,
type.getVectorSize(), type.getMatrixCols(), type.getMatrixRows(), type.isVector());
// Add constructor to the right vectorness of the right type. If that fails, we can't do it, so return nullptr.
return addConversion(op, newType, node);
}
// //
// Establishes the type of the resultant operation, as well as // Establishes the type of the resultant operation, as well as
// makes the operator the correct one for the operands. // makes the operator the correct one for the operands.
// //
// Returns false if operator can't work on operands. // Returns false if operator can't work on operands.
// //
bool TIntermBinary::promote() bool TIntermBinary::promote(TIntermediate& intermediate)
{ {
// Arrays and structures have to be exact matches. // Arrays and structures have to be exact matches.
if ((left->isArray() || right->isArray() || left->getBasicType() == EbtStruct || right->getBasicType() == EbtStruct) if ((left->isArray() || right->isArray() || left->getBasicType() == EbtStruct || right->getBasicType() == EbtStruct)
...@@ -1843,6 +1882,15 @@ bool TIntermBinary::promote() ...@@ -1843,6 +1882,15 @@ bool TIntermBinary::promote()
case EOpLogicalAnd: case EOpLogicalAnd:
case EOpLogicalOr: case EOpLogicalOr:
case EOpLogicalXor: case EOpLogicalXor:
if (intermediate.getSource() == EShSourceHlsl) {
TIntermTyped* convertedL = intermediate.convertToBasicType(op, EbtBool, left);
TIntermTyped* convertedR = intermediate.convertToBasicType(op, EbtBool, right);
if (convertedL == nullptr || convertedR == nullptr)
return false;
left = convertedL;
right = convertedR;
}
// logical ops operate only on scalar Booleans and will promote to scalar Boolean. // logical ops operate only on scalar Booleans and will promote to scalar Boolean.
if (left->getBasicType() != EbtBool || left->isVector() || left->isMatrix()) if (left->getBasicType() != EbtBool || left->isVector() || left->isMatrix())
return false; return false;
...@@ -1864,6 +1912,9 @@ bool TIntermBinary::promote() ...@@ -1864,6 +1912,9 @@ bool TIntermBinary::promote()
case EOpAndAssign: case EOpAndAssign:
case EOpInclusiveOrAssign: case EOpInclusiveOrAssign:
case EOpExclusiveOrAssign: case EOpExclusiveOrAssign:
if (intermediate.getSource() == EShSourceHlsl)
break;
// Check for integer-only operands. // Check for integer-only operands.
if ((left->getBasicType() != EbtInt && left->getBasicType() != EbtUint && if ((left->getBasicType() != EbtInt && left->getBasicType() != EbtUint &&
left->getBasicType() != EbtInt64 && left->getBasicType() != EbtUint64) || left->getBasicType() != EbtInt64 && left->getBasicType() != EbtUint64) ||
...@@ -2051,6 +2102,7 @@ bool TIntermBinary::promote() ...@@ -2051,6 +2102,7 @@ bool TIntermBinary::promote()
case EOpAndAssign: case EOpAndAssign:
case EOpInclusiveOrAssign: case EOpInclusiveOrAssign:
case EOpExclusiveOrAssign: case EOpExclusiveOrAssign:
if ((left->isMatrix() && right->isVector()) || if ((left->isMatrix() && right->isVector()) ||
(left->isVector() && right->isMatrix()) || (left->isVector() && right->isMatrix()) ||
left->getBasicType() != right->getBasicType()) left->getBasicType() != right->getBasicType())
......
...@@ -246,6 +246,9 @@ public: ...@@ -246,6 +246,9 @@ public:
TIntermUnary* addUnaryNode(TOperator op, TIntermTyped* child, TSourceLoc) const; TIntermUnary* addUnaryNode(TOperator op, TIntermTyped* child, TSourceLoc) const;
TIntermUnary* addUnaryNode(TOperator op, TIntermTyped* child, TSourceLoc, const TType&) const; TIntermUnary* addUnaryNode(TOperator op, TIntermTyped* child, TSourceLoc, const TType&) const;
// Add conversion from node's type to given basic type.
TIntermTyped* convertToBasicType(TOperator op, TBasicType basicType, TIntermTyped* node) const;
// Constant folding (in Constant.cpp) // Constant folding (in Constant.cpp)
TIntermTyped* fold(TIntermAggregate* aggrNode); TIntermTyped* fold(TIntermAggregate* aggrNode);
TIntermTyped* foldConstructor(TIntermAggregate* aggrNode); TIntermTyped* foldConstructor(TIntermAggregate* aggrNode);
...@@ -390,7 +393,7 @@ protected: ...@@ -390,7 +393,7 @@ protected:
bool userOutputUsed() const; bool userOutputUsed() const;
static int getBaseAlignmentScalar(const TType&, int& size); static int getBaseAlignmentScalar(const TType&, int& size);
bool isSpecializationOperation(const TIntermOperator&) const; bool isSpecializationOperation(const TIntermOperator&) const;
const EShLanguage language; // stage, known at construction time const EShLanguage language; // stage, known at construction time
EShSource source; // source language, known a bit later EShSource source; // source language, known a bit later
std::string entryPointName; std::string entryPointName;
......
...@@ -142,6 +142,8 @@ INSTANTIATE_TEST_CASE_P( ...@@ -142,6 +142,8 @@ INSTANTIATE_TEST_CASE_P(
{"hlsl.load.rwtexture.array.dx10.frag", "main"}, {"hlsl.load.rwtexture.array.dx10.frag", "main"},
{"hlsl.load.offset.dx10.frag", "main"}, {"hlsl.load.offset.dx10.frag", "main"},
{"hlsl.load.offsetarray.dx10.frag", "main"}, {"hlsl.load.offsetarray.dx10.frag", "main"},
{"hlsl.logical.unary.frag", "main"},
{"hlsl.logical.binary.frag", "main"},
{"hlsl.multiEntry.vert", "RealEntrypoint"}, {"hlsl.multiEntry.vert", "RealEntrypoint"},
{"hlsl.multiReturn.frag", "main"}, {"hlsl.multiReturn.frag", "main"},
{"hlsl.matrixindex.frag", "main"}, {"hlsl.matrixindex.frag", "main"},
...@@ -149,6 +151,7 @@ INSTANTIATE_TEST_CASE_P( ...@@ -149,6 +151,7 @@ INSTANTIATE_TEST_CASE_P(
{"hlsl.overload.frag", "PixelShaderFunction"}, {"hlsl.overload.frag", "PixelShaderFunction"},
{"hlsl.pp.line.frag", "main"}, {"hlsl.pp.line.frag", "main"},
{"hlsl.precise.frag", "main"}, {"hlsl.precise.frag", "main"},
{"hlsl.promote.binary.frag", "main"},
{"hlsl.promotions.frag", "main"}, {"hlsl.promotions.frag", "main"},
{"hlsl.rw.bracket.frag", "main"}, {"hlsl.rw.bracket.frag", "main"},
{"hlsl.rw.scalar.bracket.frag", "main"}, {"hlsl.rw.scalar.bracket.frag", "main"},
...@@ -179,7 +182,7 @@ INSTANTIATE_TEST_CASE_P( ...@@ -179,7 +182,7 @@ INSTANTIATE_TEST_CASE_P(
{"hlsl.samplelevel.basic.dx10.vert", "main"}, {"hlsl.samplelevel.basic.dx10.vert", "main"},
{"hlsl.samplelevel.offset.dx10.frag", "main"}, {"hlsl.samplelevel.offset.dx10.frag", "main"},
{"hlsl.samplelevel.offsetarray.dx10.frag", "main"}, {"hlsl.samplelevel.offsetarray.dx10.frag", "main"},
{ "hlsl.sample.sub-vec4.dx10.frag", "main"}, {"hlsl.sample.sub-vec4.dx10.frag", "main"},
{"hlsl.semicolons.frag", "main"}, {"hlsl.semicolons.frag", "main"},
{"hlsl.shapeConv.frag", "main"}, {"hlsl.shapeConv.frag", "main"},
{"hlsl.shapeConvRet.frag", "main"}, {"hlsl.shapeConvRet.frag", "main"},
......
...@@ -1935,7 +1935,10 @@ bool HlslGrammar::acceptUnaryExpression(TIntermTyped*& node) ...@@ -1935,7 +1935,10 @@ bool HlslGrammar::acceptUnaryExpression(TIntermTyped*& node)
return true; return true;
node = intermediate.addUnaryMath(unaryOp, node, loc); node = intermediate.addUnaryMath(unaryOp, node, loc);
node = parseContext.handleLvalue(loc, "unary operator", node);
// These unary ops require lvalues
if (unaryOp == EOpPreIncrement || unaryOp == EOpPreDecrement)
node = parseContext.handleLvalue(loc, "unary operator", node);
return node != nullptr; return node != nullptr;
} }
......
...@@ -245,7 +245,6 @@ glslang::TString& AppendTypeName(glslang::TString& s, const char* argOrder, cons ...@@ -245,7 +245,6 @@ glslang::TString& AppendTypeName(glslang::TString& s, const char* argOrder, cons
char order = *argOrder; char order = *argOrder;
if (UseHlslTypes) { if (UseHlslTypes) {
// TODO: handle sub-vec4 returns
switch (type) { switch (type) {
case '-': s += "void"; break; case '-': s += "void"; break;
case 'F': s += "float"; break; case 'F': s += "float"; break;
...@@ -832,8 +831,6 @@ void TBuiltInParseablesHlsl::initialize(int /*version*/, EProfile /*profile*/, c ...@@ -832,8 +831,6 @@ void TBuiltInParseablesHlsl::initialize(int /*version*/, EProfile /*profile*/, c
{ "GatherCmpAlpha", /* O-4 */ "V4", nullptr, "%@,S,V,S,V,,,", "FIU,s,F,,I,,,", EShLangAll }, { "GatherCmpAlpha", /* O-4 */ "V4", nullptr, "%@,S,V,S,V,,,", "FIU,s,F,,I,,,", EShLangAll },
{ "GatherCmpAlpha", /* O-4, status */"V4", nullptr, "%@,S,V,S,V,,,,S","FIU,s,F,,I,,,,U",EShLangAll }, { "GatherCmpAlpha", /* O-4, status */"V4", nullptr, "%@,S,V,S,V,,,,S","FIU,s,F,,I,,,,U",EShLangAll },
// TODO: Cmp forms
// Mark end of list, since we want to avoid a range-based for, as some compilers don't handle it yet. // Mark end of list, since we want to avoid a range-based for, as some compilers don't handle it yet.
{ nullptr, nullptr, nullptr, nullptr, nullptr, 0 }, { nullptr, nullptr, nullptr, nullptr, nullptr, 0 },
}; };
......
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