Translator: Helpers to derive precision

Bug: angleproject:4889
Bug: angleproject:6132
Change-Id: I75e2084c76139ccb24b266f262d5e5597d8aa4ca
Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/3071599
Commit-Queue: Shahbaz Youssefi <syoussefi@chromium.org>
Reviewed-by: Jamie Madill <jmadill@chromium.org>
Reviewed-by: Tim Van Patten <timvp@google.com>
diff --git a/src/compiler/translator/IntermNode.cpp b/src/compiler/translator/IntermNode.cpp
index 8e2b101..78b42ab 100644
--- a/src/compiler/translator/IntermNode.cpp
+++ b/src/compiler/translator/IntermNode.cpp
@@ -617,35 +617,21 @@
 void TIntermAggregate::setPrecisionAndQualifier()
 {
     mType.setQualifier(EvqTemporary);
-    if (BuiltInGroup::IsBuiltIn(mOp) && !BuiltInGroup::IsMath(mOp))
+    if ((!BuiltInGroup::IsBuiltIn(mOp) && !isFunctionCall()) || BuiltInGroup::IsMath(mOp))
     {
-        setBuiltInFunctionPrecision();
-    }
-    else if (!isFunctionCall())
-    {
-        if (isConstructor())
-        {
-            // Structs should not be precision qualified, the individual members may be.
-            // Built-in types on the other hand should be precision qualified.
-            if (getBasicType() != EbtStruct)
-            {
-                setPrecisionFromChildren();
-            }
-        }
-        else
-        {
-            setPrecisionForMathBuiltInOp();
-        }
         if (areChildrenConstQualified())
         {
             mType.setQualifier(EvqConst);
         }
     }
+
+    const TPrecision precision = derivePrecision();
+    mType.setPrecision(precision);
 }
 
 bool TIntermAggregate::areChildrenConstQualified()
 {
-    for (TIntermNode *&arg : mArguments)
+    for (TIntermNode *arg : mArguments)
     {
         TIntermTyped *typedArg = arg->getAsTyped();
         if (typedArg && typedArg->getQualifier() != EvqConst)
@@ -656,78 +642,71 @@
     return true;
 }
 
-void TIntermAggregate::setPrecisionFromChildren()
+TPrecision TIntermAggregate::derivePrecision() const
 {
-    if (getBasicType() == EbtBool)
+    if (getBasicType() == EbtBool || getBasicType() == EbtVoid || getBasicType() == EbtStruct)
     {
-        mType.setPrecision(EbpUndefined);
-        return;
+        return EbpUndefined;
     }
 
-    TPrecision precision                = EbpUndefined;
-    TIntermSequence::iterator childIter = mArguments.begin();
-    while (childIter != mArguments.end())
+    // For AST function calls, take the qualifier from the declared one.
+    if (isFunctionCall())
     {
-        TIntermTyped *typed = (*childIter)->getAsTyped();
-        if (typed)
-            precision = GetHigherPrecision(typed->getPrecision(), precision);
-        ++childIter;
+        return mType.getPrecision();
     }
-    mType.setPrecision(precision);
-}
 
-void TIntermAggregate::setPrecisionForMathBuiltInOp()
-{
-    ASSERT(BuiltInGroup::IsMath(mOp));
-    if (!setPrecisionForSpecialBuiltInOp())
-    {
-        setPrecisionFromChildren();
-    }
-}
-
-bool TIntermAggregate::setPrecisionForSpecialBuiltInOp()
-{
+    // Some built-ins explicitly specify their precision.
     switch (mOp)
     {
         case EOpBitfieldExtract:
-            mType.setPrecision(mArguments[0]->getAsTyped()->getPrecision());
-            return true;
+            return mArguments[0]->getAsTyped()->getPrecision();
         case EOpBitfieldInsert:
-            mType.setPrecision(GetHigherPrecision(mArguments[0]->getAsTyped()->getPrecision(),
-                                                  mArguments[1]->getAsTyped()->getPrecision()));
-            return true;
+            return GetHigherPrecision(mArguments[0]->getAsTyped()->getPrecision(),
+                                      mArguments[1]->getAsTyped()->getPrecision());
+        case EOpTextureSize:
+        case EOpImageSize:
         case EOpUaddCarry:
         case EOpUsubBorrow:
-            mType.setPrecision(EbpHigh);
-            return true;
+        case EOpUmulExtended:
+        case EOpImulExtended:
+        case EOpFrexp:
+        case EOpLdexp:
+            return EbpHigh;
         default:
-            return false;
-    }
-}
-
-void TIntermAggregate::setBuiltInFunctionPrecision()
-{
-    // All built-ins returning bool are math operations.
-    ASSERT(getBasicType() != EbtBool);
-    ASSERT(!isFunctionCall() && !isConstructor() && !BuiltInGroup::IsMath(mOp));
-
-    TPrecision precision = EbpUndefined;
-    for (TIntermNode *arg : mArguments)
-    {
-        TIntermTyped *typed = arg->getAsTyped();
-        // ESSL spec section 8: texture functions get their precision from the sampler.
-        if (typed && IsSampler(typed->getBasicType()))
-        {
-            precision = typed->getPrecision();
             break;
-        }
     }
-    // ESSL 3.0 spec section 8: textureSize always gets highp precision.
-    // All other functions that take a sampler are assumed to be texture functions.
-    if (mOp == EOpTextureSize)
-        mType.setPrecision(EbpHigh);
-    else
-        mType.setPrecision(precision);
+
+    // The rest of the math operations and constructors get their precision from their
+    // arguments.
+    if (BuiltInGroup::IsMath(mOp) || mOp == EOpConstruct)
+    {
+        TPrecision precision = EbpUndefined;
+        for (TIntermNode *argument : mArguments)
+        {
+            precision = GetHigherPrecision(argument->getAsTyped()->getPrecision(), precision);
+        }
+        return precision;
+    }
+
+    // Image load and atomic operations return highp.
+    if (BuiltInGroup::IsImageLoad(mOp) || BuiltInGroup::IsImageAtomic(mOp) ||
+        BuiltInGroup::IsAtomicCounter(mOp) || BuiltInGroup::IsAtomicMemory(mOp))
+    {
+        return EbpHigh;
+    }
+
+    // Texture functions return the same precision as that of the sampler.  textureSize returns
+    // highp, but that's handled above.  The same is true for dFd*, interpolateAt* and
+    // subpassLoad operations.
+    if (BuiltInGroup::IsTexture(mOp) || BuiltInGroup::IsDerivativesFS(mOp) ||
+        BuiltInGroup::IsInterpolationFS(mOp) || mOp == EOpSubpassLoad)
+    {
+        return mArguments[0]->getAsTyped()->getPrecision();
+    }
+
+    // Every possibility must be explicitly handled, except for desktop-GLSL-specific built-ins
+    // for which precision does't matter.
+    return EbpUndefined;
 }
 
 const char *TIntermAggregate::functionName() const
@@ -1296,7 +1275,7 @@
     if (mOp == EOpArrayLength)
     {
         // Special case: the qualifier of .length() doesn't depend on the operand qualifier.
-        setType(TType(EbtInt, EbpUndefined, EvqConst));
+        setType(TType(EbtInt, EbpHigh, EvqConst));
         return;
     }
 
@@ -1304,74 +1283,115 @@
     if (mOperand->getQualifier() == EvqConst)
         resultQualifier = EvqConst;
 
-    unsigned char operandPrimarySize =
-        static_cast<unsigned char>(mOperand->getType().getNominalSize());
+    TType resultType = mOperand->getType();
+    resultType.setQualifier(resultQualifier);
+
+    // Result is an intermediate value, so make sure it's identified as such.
+    resultType.setInterfaceBlock(nullptr);
+
+    // Override type properties for special built-ins.  Precision is determined later by
+    // derivePrecision.
     switch (mOp)
     {
         case EOpFloatBitsToInt:
-            setType(TType(EbtInt, EbpHigh, resultQualifier, operandPrimarySize));
+            resultType.setBasicType(EbtInt);
             break;
         case EOpFloatBitsToUint:
-            setType(TType(EbtUInt, EbpHigh, resultQualifier, operandPrimarySize));
+            resultType.setBasicType(EbtUInt);
             break;
         case EOpIntBitsToFloat:
         case EOpUintBitsToFloat:
-            setType(TType(EbtFloat, EbpHigh, resultQualifier, operandPrimarySize));
+            resultType.setBasicType(EbtFloat);
             break;
         case EOpPackSnorm2x16:
         case EOpPackUnorm2x16:
         case EOpPackHalf2x16:
         case EOpPackUnorm4x8:
         case EOpPackSnorm4x8:
-            setType(TType(EbtUInt, EbpHigh, resultQualifier));
+            resultType.setBasicType(EbtUInt);
+            resultType.setPrimarySize(1);
             break;
         case EOpUnpackSnorm2x16:
         case EOpUnpackUnorm2x16:
-            setType(TType(EbtFloat, EbpHigh, resultQualifier, 2));
-            break;
         case EOpUnpackHalf2x16:
-            setType(TType(EbtFloat, EbpMedium, resultQualifier, 2));
+            resultType.setBasicType(EbtFloat);
+            resultType.setPrimarySize(2);
             break;
         case EOpUnpackUnorm4x8:
         case EOpUnpackSnorm4x8:
-            setType(TType(EbtFloat, EbpMedium, resultQualifier, 4));
+            resultType.setBasicType(EbtFloat);
+            resultType.setPrimarySize(4);
             break;
         case EOpAny:
         case EOpAll:
-            setType(TType(EbtBool, EbpUndefined, resultQualifier));
+            resultType.setBasicType(EbtBool);
+            resultType.setPrimarySize(1);
             break;
         case EOpLength:
         case EOpDeterminant:
-            setType(TType(EbtFloat, mOperand->getType().getPrecision(), resultQualifier));
+            resultType.setBasicType(EbtFloat);
+            resultType.setPrimarySize(1);
+            resultType.setSecondarySize(1);
             break;
         case EOpTranspose:
-            setType(TType(EbtFloat, mOperand->getType().getPrecision(), resultQualifier,
-                          static_cast<unsigned char>(mOperand->getType().getRows()),
-                          static_cast<unsigned char>(mOperand->getType().getCols())));
+            ASSERT(resultType.getBasicType() == EbtFloat);
+            resultType.setPrimarySize(static_cast<unsigned char>(mOperand->getType().getRows()));
+            resultType.setSecondarySize(static_cast<unsigned char>(mOperand->getType().getCols()));
             break;
         case EOpIsinf:
         case EOpIsnan:
-            setType(TType(EbtBool, EbpUndefined, resultQualifier, operandPrimarySize));
-            break;
-        case EOpBitfieldReverse:
-            setType(TType(mOperand->getBasicType(), EbpHigh, resultQualifier, operandPrimarySize));
+            resultType.setBasicType(EbtBool);
             break;
         case EOpBitCount:
-            setType(TType(EbtInt, EbpLow, resultQualifier, operandPrimarySize));
-            break;
         case EOpFindLSB:
-            setType(TType(EbtInt, EbpLow, resultQualifier, operandPrimarySize));
-            break;
         case EOpFindMSB:
-            setType(TType(EbtInt, EbpLow, resultQualifier, operandPrimarySize));
+            resultType.setBasicType(EbtInt);
             break;
         default:
-            setType(mOperand->getType());
-            mType.setQualifier(resultQualifier);
-            // Result is an intermediate value, so make sure it's identified as such.
-            mType.setInterfaceBlock(nullptr);
             break;
     }
+
+    const TPrecision precision = derivePrecision();
+    resultType.setPrecision(precision);
+    setType(resultType);
+}
+
+TPrecision TIntermUnary::derivePrecision() const
+{
+    // Unary operators generally derive their precision from their operand, except for a few
+    // built-ins where this is overriden.
+    switch (mOp)
+    {
+        case EOpArrayLength:
+        case EOpFloatBitsToInt:
+        case EOpFloatBitsToUint:
+        case EOpIntBitsToFloat:
+        case EOpUintBitsToFloat:
+        case EOpPackSnorm2x16:
+        case EOpPackUnorm2x16:
+        case EOpPackHalf2x16:
+        case EOpPackUnorm4x8:
+        case EOpPackSnorm4x8:
+        case EOpUnpackSnorm2x16:
+        case EOpUnpackUnorm2x16:
+        case EOpBitfieldReverse:
+            return EbpHigh;
+        case EOpUnpackHalf2x16:
+        case EOpUnpackUnorm4x8:
+        case EOpUnpackSnorm4x8:
+            return EbpMedium;
+        case EOpBitCount:
+        case EOpFindLSB:
+        case EOpFindMSB:
+            return EbpLow;
+        case EOpAny:
+        case EOpAll:
+        case EOpIsinf:
+        case EOpIsnan:
+            return EbpUndefined;
+        default:
+            return mOperand->getPrecision();
+    }
 }
 
 TIntermSwizzle::TIntermSwizzle(TIntermTyped *operand, const TVector<int> &swizzleOffsets)
@@ -1439,6 +1459,9 @@
     ASSERT(mFalseExpression);
     getTypePointer()->setQualifier(
         TIntermTernary::DetermineQualifier(cond, trueExpression, falseExpression));
+
+    const TPrecision precision = derivePrecision();
+    mType.setPrecision(precision);
 }
 
 TIntermLoop::TIntermLoop(TLoopType type,
@@ -1512,6 +1535,11 @@
     return EvqTemporary;
 }
 
+TPrecision TIntermTernary::derivePrecision() const
+{
+    return GetHigherPrecision(mTrueExpression->getPrecision(), mFalseExpression->getPrecision());
+}
+
 TIntermTyped *TIntermTernary::fold(TDiagnostics * /* diagnostics */)
 {
     if (mCondition->getAsConstantUnion())
@@ -1535,10 +1563,15 @@
         resultQualifier = EvqConst;
 
     auto numFields = mSwizzleOffsets.size();
-    setType(TType(mOperand->getBasicType(), mOperand->getPrecision(), resultQualifier,
+    setType(TType(mOperand->getBasicType(), derivePrecision(), resultQualifier,
                   static_cast<unsigned char>(numFields)));
 }
 
+TPrecision TIntermSwizzle::derivePrecision() const
+{
+    return mOperand->getPrecision();
+}
+
 bool TIntermSwizzle::hasDuplicateOffsets() const
 {
     if (mHasFoldedDuplicateOffsets)
@@ -1635,6 +1668,13 @@
         getTypePointer()->setQualifier(EvqTemporary);
     }
 
+    // Result is an intermediate value, so make sure it's identified as such.  That's not true for
+    // interface block arrays being indexed.
+    if (mOp != EOpIndexDirect && mOp != EOpIndexIndirect)
+    {
+        getTypePointer()->setInterfaceBlock(nullptr);
+    }
+
     // Handle indexing ops.
     switch (mOp)
     {
@@ -1646,12 +1686,11 @@
             }
             else if (mLeft->isMatrix())
             {
-                setType(TType(mLeft->getBasicType(), mLeft->getPrecision(), resultQualifier,
-                              static_cast<unsigned char>(mLeft->getRows())));
+                mType.toMatrixColumnType();
             }
             else if (mLeft->isVector())
             {
-                setType(TType(mLeft->getBasicType(), mLeft->getPrecision(), resultQualifier));
+                mType.toComponentType();
             }
             else
             {
@@ -1661,16 +1700,16 @@
         case EOpIndexDirectStruct:
         {
             const TFieldList &fields = mLeft->getType().getStruct()->fields();
-            const int i              = mRight->getAsConstantUnion()->getIConst(0);
-            setType(*fields[i]->type());
+            const int fieldIndex     = mRight->getAsConstantUnion()->getIConst(0);
+            setType(*fields[fieldIndex]->type());
             getTypePointer()->setQualifier(resultQualifier);
             return;
         }
         case EOpIndexDirectInterfaceBlock:
         {
             const TFieldList &fields = mLeft->getType().getInterfaceBlock()->fields();
-            const int i              = mRight->getAsConstantUnion()->getIConst(0);
-            setType(*fields[i]->type());
+            const int fieldIndex     = mRight->getAsConstantUnion()->getIConst(0);
+            setType(*fields[fieldIndex]->type());
             getTypePointer()->setQualifier(resultQualifier);
             return;
         }
@@ -1680,51 +1719,8 @@
 
     ASSERT(mLeft->isArray() == mRight->isArray());
 
-    // The result gets promoted to the highest precision.
-    TPrecision higherPrecision = GetHigherPrecision(mLeft->getPrecision(), mRight->getPrecision());
-    getTypePointer()->setPrecision(higherPrecision);
-
     const int nominalSize = std::max(mLeft->getNominalSize(), mRight->getNominalSize());
 
-    //
-    // All scalars or structs. Code after this test assumes this case is removed!
-    //
-    if (nominalSize == 1)
-    {
-        switch (mOp)
-        {
-            //
-            // Promote to conditional
-            //
-            case EOpEqual:
-            case EOpNotEqual:
-            case EOpLessThan:
-            case EOpGreaterThan:
-            case EOpLessThanEqual:
-            case EOpGreaterThanEqual:
-                setType(TType(EbtBool, EbpUndefined, resultQualifier));
-                break;
-
-            //
-            // And and Or operate on conditionals
-            //
-            case EOpLogicalAnd:
-            case EOpLogicalXor:
-            case EOpLogicalOr:
-                ASSERT(mLeft->getBasicType() == EbtBool && mRight->getBasicType() == EbtBool);
-                setType(TType(EbtBool, EbpUndefined, resultQualifier));
-                break;
-
-            default:
-                break;
-        }
-        return;
-    }
-
-    // If we reach here, at least one of the operands is vector or matrix.
-    // The other operand could be a scalar, vector, or matrix.
-    TBasicType basicType = mLeft->getBasicType();
-
     switch (mOp)
     {
         case EOpMul:
@@ -1732,27 +1728,24 @@
         case EOpMatrixTimesScalar:
             if (mRight->isMatrix())
             {
-                setType(TType(basicType, higherPrecision, resultQualifier,
-                              static_cast<unsigned char>(mRight->getCols()),
-                              static_cast<unsigned char>(mRight->getRows())));
+                getTypePointer()->setPrimarySize(static_cast<unsigned char>(mRight->getCols()));
+                getTypePointer()->setSecondarySize(static_cast<unsigned char>(mRight->getRows()));
             }
             break;
         case EOpMatrixTimesVector:
-            setType(TType(basicType, higherPrecision, resultQualifier,
-                          static_cast<unsigned char>(mLeft->getRows()), 1));
+            getTypePointer()->setPrimarySize(static_cast<unsigned char>(mLeft->getRows()));
+            getTypePointer()->setSecondarySize(1);
             break;
         case EOpMatrixTimesMatrix:
-            setType(TType(basicType, higherPrecision, resultQualifier,
-                          static_cast<unsigned char>(mRight->getCols()),
-                          static_cast<unsigned char>(mLeft->getRows())));
+            getTypePointer()->setPrimarySize(static_cast<unsigned char>(mRight->getCols()));
+            getTypePointer()->setSecondarySize(static_cast<unsigned char>(mLeft->getRows()));
             break;
         case EOpVectorTimesScalar:
-            setType(TType(basicType, higherPrecision, resultQualifier,
-                          static_cast<unsigned char>(nominalSize), 1));
+            getTypePointer()->setPrimarySize(static_cast<unsigned char>(nominalSize));
             break;
         case EOpVectorTimesMatrix:
-            setType(TType(basicType, higherPrecision, resultQualifier,
-                          static_cast<unsigned char>(mRight->getCols()), 1));
+            getTypePointer()->setPrimarySize(static_cast<unsigned char>(mRight->getCols()));
+            ASSERT(getType().getSecondarySize() == 1);
             break;
         case EOpMulAssign:
         case EOpVectorTimesScalarAssign:
@@ -1785,12 +1778,11 @@
         case EOpBitwiseXorAssign:
         case EOpBitwiseOrAssign:
         {
+            ASSERT(!mLeft->isArray() && !mRight->isArray());
             const int secondarySize =
                 std::max(mLeft->getSecondarySize(), mRight->getSecondarySize());
-            setType(TType(basicType, higherPrecision, resultQualifier,
-                          static_cast<unsigned char>(nominalSize),
-                          static_cast<unsigned char>(secondarySize)));
-            ASSERT(!mLeft->isArray() && !mRight->isArray());
+            getTypePointer()->setPrimarySize(static_cast<unsigned char>(nominalSize));
+            getTypePointer()->setSecondarySize(static_cast<unsigned char>(secondarySize));
             break;
         }
         case EOpEqual:
@@ -1804,6 +1796,15 @@
             setType(TType(EbtBool, EbpUndefined, resultQualifier));
             break;
 
+        //
+        // And and Or operate on conditionals
+        //
+        case EOpLogicalAnd:
+        case EOpLogicalXor:
+        case EOpLogicalOr:
+            ASSERT(mLeft->getBasicType() == EbtBool && mRight->getBasicType() == EbtBool);
+            break;
+
         case EOpIndexDirect:
         case EOpIndexIndirect:
         case EOpIndexDirectInterfaceBlock:
@@ -1815,6 +1816,55 @@
             UNREACHABLE();
             break;
     }
+
+    const TPrecision precision = derivePrecision();
+    getTypePointer()->setPrecision(precision);
+}
+
+TPrecision TIntermBinary::derivePrecision() const
+{
+    const TPrecision higherPrecision =
+        GetHigherPrecision(mLeft->getPrecision(), mRight->getPrecision());
+
+    switch (mOp)
+    {
+        case EOpComma:
+            // Comma takes the right node's value.
+            return mRight->getPrecision();
+
+        case EOpIndexDirect:
+        case EOpIndexIndirect:
+            // When indexing an array, the precision of the array is preserved.
+            return mLeft->getPrecision();
+
+        case EOpIndexDirectStruct:
+        case EOpIndexDirectInterfaceBlock:
+        {
+            // When selecting the field of a block, the precision is taken from the field's
+            // declaration.
+            const TFieldList &fields = mOp == EOpIndexDirectStruct
+                                           ? mLeft->getType().getStruct()->fields()
+                                           : mLeft->getType().getInterfaceBlock()->fields();
+            const int fieldIndex = mRight->getAsConstantUnion()->getIConst(0);
+            return fields[fieldIndex]->type()->getPrecision();
+        }
+
+        case EOpEqual:
+        case EOpNotEqual:
+        case EOpLessThan:
+        case EOpGreaterThan:
+        case EOpLessThanEqual:
+        case EOpGreaterThanEqual:
+        case EOpLogicalAnd:
+        case EOpLogicalXor:
+        case EOpLogicalOr:
+            // No precision specified on bool results.
+            return EbpUndefined;
+
+        default:
+            // All other operations are evaluated at the higher of the two operands' precisions.
+            return higherPrecision;
+    }
 }
 
 bool TIntermConstantUnion::hasConstantValue() const
diff --git a/src/compiler/translator/IntermNode.h b/src/compiler/translator/IntermNode.h
index 1b3b42d..62a2398 100644
--- a/src/compiler/translator/IntermNode.h
+++ b/src/compiler/translator/IntermNode.h
@@ -475,6 +475,7 @@
 
   private:
     void promote();
+    TPrecision derivePrecision() const;
 
     TIntermSwizzle(const TIntermSwizzle &node);  // Note: not deleted, just private!
 };
@@ -524,6 +525,7 @@
 
   private:
     void promote();
+    TPrecision derivePrecision() const;
 
     static TQualifier GetCommaQualifier(int shaderVersion,
                                         const TIntermTyped *left,
@@ -571,6 +573,7 @@
 
   private:
     void promote();
+    TPrecision derivePrecision() const;
 
     TIntermUnary(const TIntermUnary &node);  // note: not deleted, just private!
 };
@@ -663,19 +666,9 @@
     TIntermAggregate(const TIntermAggregate &node);  // note: not deleted, just private!
 
     void setPrecisionAndQualifier();
+    TPrecision derivePrecision() const;
 
     bool areChildrenConstQualified();
-
-    void setPrecisionFromChildren();
-
-    void setPrecisionForMathBuiltInOp();
-
-    // Returns true if precision was set according to special rules for this built-in.
-    bool setPrecisionForSpecialBuiltInOp();
-
-    // Used for non-math built-in functions. The function name in the symbol info needs to be set
-    // before calling this.
-    void setBuiltInFunctionPrecision();
 };
 
 // A list of statements. Either the root node which contains declarations and function definitions,
@@ -903,6 +896,7 @@
     static TQualifier DetermineQualifier(TIntermTyped *cond,
                                          TIntermTyped *trueExpression,
                                          TIntermTyped *falseExpression);
+    TPrecision derivePrecision() const;
 
     TIntermTyped *mCondition;
     TIntermTyped *mTrueExpression;