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.
This commit is contained in:
steve-lunarg 2016-10-15 10:29:58 -06:00
parent 5d45eadedc
commit e5921f1309
12 changed files with 946 additions and 16 deletions

View file

@ -52,6 +52,8 @@
namespace glslang {
class TIntermediate;
//
// Operators used by the high-level (parse tree) representation.
//
@ -845,7 +847,7 @@ public:
virtual TIntermOperator* getAsOperator() { return this; }
virtual const TIntermOperator* getAsOperator() const { return this; }
TOperator getOp() const { return op; }
virtual bool promote() { return true; }
virtual bool promote(TIntermediate&) { return true; }
bool modifiesState() const;
bool isConstructor() const;
bool isTexture() const { return op > EOpTextureGuardBegin && op < EOpTextureGuardEnd; }
@ -1024,7 +1026,7 @@ public:
virtual TIntermTyped* getRight() const { return right; }
virtual TIntermBinary* getAsBinaryNode() { return this; }
virtual const TIntermBinary* getAsBinaryNode() const { return this; }
virtual bool promote();
virtual bool promote(TIntermediate&);
virtual void updatePrecision();
protected:
TIntermTyped* left;
@ -1044,7 +1046,7 @@ public:
virtual const TIntermTyped* getOperand() const { return operand; }
virtual TIntermUnary* getAsUnaryNode() { return this; }
virtual const TIntermUnary* getAsUnaryNode() const { return this; }
virtual bool promote();
virtual bool promote(TIntermediate&);
virtual void updatePrecision();
protected:
TIntermTyped* operand;

View file

@ -137,7 +137,7 @@ TIntermTyped* TIntermediate::addBinaryMath(TOperator op, TIntermTyped* left, TIn
// one and promote it to the right type.
//
TIntermBinary* node = addBinaryNode(op, left, right, loc);
if (! node->promote())
if (! node->promote(*this))
return 0;
node->updatePrecision();
@ -246,7 +246,7 @@ TIntermTyped* TIntermediate::addAssign(TOperator op, TIntermTyped* left, TInterm
// build the node
TIntermBinary* node = addBinaryNode(op, left, right, loc);
if (! node->promote())
if (! node->promote(*this))
return nullptr;
node->updatePrecision();
@ -282,6 +282,10 @@ TIntermTyped* TIntermediate::addUnaryMath(TOperator op, TIntermTyped* child, TSo
switch (op) {
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()) {
return 0;
}
@ -349,7 +353,7 @@ TIntermTyped* TIntermediate::addUnaryMath(TOperator op, TIntermTyped* child, TSo
//
TIntermUnary* node = addUnaryNode(op, child, loc);
if (! node->promote())
if (! node->promote(*this))
return 0;
node->updatePrecision();
@ -555,6 +559,10 @@ TIntermTyped* TIntermediate::addConversion(TOperator op, const TType& type, TInt
case EOpAndAssign:
case EOpInclusiveOrAssign:
case EOpExclusiveOrAssign:
case EOpLogicalNot:
case EOpLogicalAnd:
case EOpLogicalOr:
case EOpLogicalXor:
case EOpFunctionCall:
case EOpReturn:
@ -841,6 +849,10 @@ bool TIntermediate::canImplicitlyPromote(TBasicType from, TBasicType to, TOperat
case EOpModAssign: // ...
case EOpReturn: // function returns can also perform arbitrary conversions
case EOpFunctionCall: // conversion of a calling parameter
case EOpLogicalNot:
case EOpLogicalAnd:
case EOpLogicalOr:
case EOpLogicalXor:
return true;
default:
break;
@ -873,6 +885,8 @@ bool TIntermediate::canImplicitlyPromote(TBasicType from, TBasicType to, TOperat
case EbtFloat16:
#endif
return true;
case EbtBool:
return (source == EShSourceHlsl);
default:
return false;
}
@ -1716,13 +1730,20 @@ bool TIntermOperator::isConstructor() const
//
// Returns false in nothing makes sense.
//
bool TIntermUnary::promote()
bool TIntermUnary::promote(TIntermediate& intermediate)
{
switch (op) {
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;
case EOpBitwiseNot:
if (operand->getBasicType() != EbtInt &&
@ -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
// makes the operator the correct one for the 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.
if ((left->isArray() || right->isArray() || left->getBasicType() == EbtStruct || right->getBasicType() == EbtStruct)
@ -1843,6 +1882,15 @@ bool TIntermBinary::promote()
case EOpLogicalAnd:
case EOpLogicalOr:
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.
if (left->getBasicType() != EbtBool || left->isVector() || left->isMatrix())
return false;
@ -1864,6 +1912,9 @@ bool TIntermBinary::promote()
case EOpAndAssign:
case EOpInclusiveOrAssign:
case EOpExclusiveOrAssign:
if (intermediate.getSource() == EShSourceHlsl)
break;
// Check for integer-only operands.
if ((left->getBasicType() != EbtInt && left->getBasicType() != EbtUint &&
left->getBasicType() != EbtInt64 && left->getBasicType() != EbtUint64) ||
@ -2051,6 +2102,7 @@ bool TIntermBinary::promote()
case EOpAndAssign:
case EOpInclusiveOrAssign:
case EOpExclusiveOrAssign:
if ((left->isMatrix() && right->isVector()) ||
(left->isVector() && right->isMatrix()) ||
left->getBasicType() != right->getBasicType())

View file

@ -246,6 +246,9 @@ public:
TIntermUnary* addUnaryNode(TOperator op, TIntermTyped* child, TSourceLoc) 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)
TIntermTyped* fold(TIntermAggregate* aggrNode);
TIntermTyped* foldConstructor(TIntermAggregate* aggrNode);
@ -390,7 +393,7 @@ protected:
bool userOutputUsed() const;
static int getBaseAlignmentScalar(const TType&, int& size);
bool isSpecializationOperation(const TIntermOperator&) const;
const EShLanguage language; // stage, known at construction time
EShSource source; // source language, known a bit later
std::string entryPointName;