Fix precision tracking for built-in function return values

Previously, the type of the return value of all function calls was set to
the type of the return value in the function signature. This did not
carry precision information.

This patch changes this so that the return value precision is set
correctly for built-in functions. For single-argument math functions, it
mostly depends on that addUnaryMath sets the type of the return value to
be the same as the type of the operand. The type is replaced but the
precision information from the operand type is retained when needed. For
multi-argument math functions, precision is determined based on all the
nodes in the aggregate after the type has been set. For texture
functions, the precision is set according the sampler type as per ESSL
1.0 spec. For textureSize, the precision is always highp as per ESSL 3.0
spec.

BUG=angle:787

Change-Id: I48448e3ffe38656b91177dee9b60dd07a03cd095
Reviewed-on: https://chromium-review.googlesource.com/224951
Reviewed-by: Jamie Madill <jmadill@chromium.org>
Reviewed-by: Nicolas Capens <capn@chromium.org>
Tested-by: Olli Etuaho <oetuaho@nvidia.com>
diff --git a/src/compiler/translator/IntermNode.cpp b/src/compiler/translator/IntermNode.cpp
index bf8649c..aa0f31d 100644
--- a/src/compiler/translator/IntermNode.cpp
+++ b/src/compiler/translator/IntermNode.cpp
@@ -133,6 +133,14 @@
 //
 ////////////////////////////////////////////////////////////////
 
+void TIntermTyped::setTypePreservePrecision(const TType &t)
+{
+    TPrecision precision = getPrecision();
+    mType = t;
+    ASSERT(mType.getBasicType() != EbtBool || precision == EbpUndefined);
+    mType.setPrecision(precision);
+}
+
 #define REPLACE_IF_IS(node, type, original, replacement) \
     if (node == original) { \
         node = static_cast<type *>(replacement); \
@@ -237,6 +245,52 @@
     }
 }
 
+void TIntermAggregate::setPrecisionFromChildren()
+{
+    if (getBasicType() == EbtBool)
+    {
+        mType.setPrecision(EbpUndefined);
+        return;
+    }
+
+    TPrecision precision = EbpUndefined;
+    TIntermSequence::iterator childIter = mSequence.begin();
+    while (childIter != mSequence.end())
+    {
+        TIntermTyped *typed = (*childIter)->getAsTyped();
+        if (typed)
+            precision = GetHigherPrecision(typed->getPrecision(), precision);
+        ++childIter;
+    }
+    mType.setPrecision(precision);
+}
+
+void TIntermAggregate::setBuiltInFunctionPrecision()
+{
+    // All built-ins returning bool should be handled as ops, not functions.
+    ASSERT(getBasicType() != EbtBool);
+
+    TPrecision precision = EbpUndefined;
+    TIntermSequence::iterator childIter = mSequence.begin();
+    while (childIter != mSequence.end())
+    {
+        TIntermTyped *typed = (*childIter)->getAsTyped();
+        // ESSL spec section 8: texture functions get their precision from the sampler.
+        if (typed && IsSampler(typed->getBasicType()))
+        {
+            precision = typed->getPrecision();
+            break;
+        }
+        ++childIter;
+    }
+    // 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 (mName.find("textureSize") == 0)
+        mType.setPrecision(EbpHigh);
+    else
+        mType.setPrecision(precision);
+}
+
 bool TIntermSelection::replaceChildNode(
     TIntermNode *original, TIntermNode *replacement)
 {
diff --git a/src/compiler/translator/IntermNode.h b/src/compiler/translator/IntermNode.h
index 0f2fec5..32c70f4 100644
--- a/src/compiler/translator/IntermNode.h
+++ b/src/compiler/translator/IntermNode.h
@@ -266,6 +266,7 @@
     virtual bool hasSideEffects() const = 0;
 
     void setType(const TType &t) { mType = t; }
+    void setTypePreservePrecision(const TType &t);
     const TType &getType() const { return mType; }
     TType *getTypePointer() { return &mType; }
 
@@ -614,6 +615,9 @@
 
     virtual void enqueueChildren(std::queue<TIntermNode *> *nodeQueue) const;
 
+    void setPrecisionFromChildren();
+    void setBuiltInFunctionPrecision();
+
   protected:
     TIntermAggregate(const TIntermAggregate &); // disallow copy constructor
     TIntermAggregate &operator=(const TIntermAggregate &); // disallow assignment operator
diff --git a/src/compiler/translator/glslang.y b/src/compiler/translator/glslang.y
index f8c4115..ba2df93 100644
--- a/src/compiler/translator/glslang.y
+++ b/src/compiler/translator/glslang.y
@@ -354,6 +354,15 @@
                         // Treat it like a built-in unary operator.
                         //
                         $$ = context->intermediate.addUnaryMath(op, $1.intermNode, @1);
+                        const TType& returnType = fnCandidate->getReturnType();
+                        if (returnType.getBasicType() == EbtBool) {
+                            // Bool types should not have precision, so we'll override any precision
+                            // that might have been set by addUnaryMath.
+                            $$->setType(returnType);
+                        } else {
+                            // addUnaryMath has set the precision of the node based on the operand.
+                            $$->setTypePreservePrecision(returnType);
+                        }
                         if ($$ == 0)  {
                             std::stringstream extraInfoStream;
                             extraInfoStream << "built in unary operator function.  Type: " << static_cast<TIntermTyped*>($1.intermNode)->getCompleteString();
@@ -362,20 +371,29 @@
                             YYERROR;
                         }
                     } else {
-                        $$ = context->intermediate.setAggregateOperator($1.intermAggregate, op, @1);
+                        TIntermAggregate *aggregate = context->intermediate.setAggregateOperator($1.intermAggregate, op, @1);
+                        aggregate->setType(fnCandidate->getReturnType());
+                        aggregate->setPrecisionFromChildren();
+                        $$ = aggregate;
                     }
                 } else {
                     // This is a real function call
 
-                    $$ = context->intermediate.setAggregateOperator($1.intermAggregate, EOpFunctionCall, @1);
-                    $$->setType(fnCandidate->getReturnType());
+                    TIntermAggregate *aggregate = context->intermediate.setAggregateOperator($1.intermAggregate, EOpFunctionCall, @1);
+                    aggregate->setType(fnCandidate->getReturnType());
 
                     // this is how we know whether the given function is a builtIn function or a user defined function
                     // if builtIn == false, it's a userDefined -> could be an overloaded builtIn function also
                     // if builtIn == true, it's definitely a builtIn function with EOpNull
                     if (!builtIn)
-                        $$->getAsAggregate()->setUserDefined();
-                    $$->getAsAggregate()->setName(fnCandidate->getMangledName());
+                        aggregate->setUserDefined();
+                    aggregate->setName(fnCandidate->getMangledName());
+
+                    // This needs to happen after the name is set
+                    if (builtIn)
+                        aggregate->setBuiltInFunctionPrecision();
+
+                    $$ = aggregate;
 
                     TQualifier qual;
                     for (size_t i = 0; i < fnCandidate->getParamCount(); ++i) {
@@ -388,7 +406,6 @@
                         }
                     }
                 }
-                $$->setType(fnCandidate->getReturnType());
             } else {
                 // error message was put out by PaFindFunction()
                 // Put on a dummy node for error recovery
diff --git a/src/compiler/translator/glslang_tab.cpp b/src/compiler/translator/glslang_tab.cpp
index 567ba43..06960c0 100644
--- a/src/compiler/translator/glslang_tab.cpp
+++ b/src/compiler/translator/glslang_tab.cpp
@@ -1,4 +1,4 @@
-/* A Bison parser, made by GNU Bison 2.7.1.  */
+/* A Bison parser, made by GNU Bison 2.7.12-4996.  */
 
 /* Bison implementation for Yacc-like parsers in C
    
@@ -44,7 +44,7 @@
 #define YYBISON 1
 
 /* Bison version.  */
-#define YYBISON_VERSION "2.7.1"
+#define YYBISON_VERSION "2.7.12-4996"
 
 /* Skeleton name.  */
 #define YYSKELETON_NAME "yacc.c"
@@ -796,31 +796,31 @@
 static const yytype_uint16 yyrline[] =
 {
        0,   206,   206,   207,   210,   234,   237,   242,   247,   252,
-     257,   263,   266,   269,   272,   275,   285,   298,   306,   406,
-     409,   417,   420,   426,   430,   437,   443,   452,   460,   463,
-     473,   476,   486,   496,   517,   518,   519,   524,   525,   533,
-     544,   545,   553,   564,   568,   569,   579,   589,   599,   612,
-     613,   623,   636,   640,   644,   648,   649,   662,   663,   676,
-     677,   690,   691,   708,   709,   722,   723,   724,   725,   726,
-     730,   733,   744,   752,   760,   787,   793,   804,   808,   812,
-     816,   823,   879,   882,   889,   897,   918,   939,   949,   977,
-     982,   992,   997,  1007,  1010,  1013,  1016,  1022,  1029,  1032,
-    1036,  1040,  1044,  1051,  1055,  1059,  1066,  1070,  1074,  1081,
-    1090,  1096,  1099,  1105,  1111,  1118,  1127,  1136,  1144,  1147,
-    1154,  1158,  1165,  1168,  1172,  1176,  1185,  1194,  1202,  1212,
-    1224,  1227,  1230,  1236,  1243,  1246,  1252,  1255,  1258,  1264,
-    1267,  1282,  1286,  1290,  1294,  1298,  1302,  1307,  1312,  1317,
-    1322,  1327,  1332,  1337,  1342,  1347,  1352,  1357,  1362,  1367,
-    1372,  1377,  1382,  1387,  1392,  1397,  1402,  1407,  1411,  1415,
-    1419,  1423,  1427,  1431,  1435,  1439,  1443,  1447,  1451,  1455,
-    1459,  1463,  1467,  1475,  1483,  1487,  1500,  1500,  1503,  1503,
-    1509,  1512,  1528,  1531,  1540,  1544,  1550,  1557,  1572,  1576,
-    1580,  1581,  1587,  1588,  1589,  1590,  1591,  1595,  1596,  1596,
-    1596,  1606,  1607,  1611,  1611,  1612,  1612,  1617,  1620,  1630,
-    1633,  1639,  1640,  1644,  1652,  1656,  1666,  1671,  1688,  1688,
-    1693,  1693,  1700,  1700,  1708,  1711,  1717,  1720,  1726,  1730,
-    1737,  1744,  1751,  1758,  1769,  1778,  1782,  1789,  1792,  1798,
-    1798
+     257,   263,   266,   269,   272,   275,   285,   298,   306,   423,
+     426,   434,   437,   443,   447,   454,   460,   469,   477,   480,
+     490,   493,   503,   513,   535,   536,   537,   542,   543,   551,
+     562,   563,   571,   582,   586,   587,   597,   607,   617,   630,
+     631,   641,   654,   658,   662,   666,   667,   680,   681,   694,
+     695,   708,   709,   726,   727,   740,   741,   742,   743,   744,
+     748,   751,   762,   770,   778,   805,   811,   822,   826,   830,
+     834,   841,   897,   900,   907,   915,   936,   957,   967,   995,
+    1000,  1010,  1015,  1025,  1028,  1031,  1034,  1040,  1047,  1050,
+    1054,  1058,  1062,  1069,  1073,  1077,  1084,  1088,  1092,  1099,
+    1108,  1114,  1117,  1123,  1129,  1136,  1145,  1154,  1162,  1165,
+    1172,  1176,  1183,  1186,  1190,  1194,  1203,  1212,  1220,  1230,
+    1242,  1245,  1248,  1254,  1261,  1264,  1270,  1273,  1276,  1282,
+    1285,  1300,  1304,  1308,  1312,  1316,  1320,  1325,  1330,  1335,
+    1340,  1345,  1350,  1355,  1360,  1365,  1370,  1375,  1380,  1385,
+    1390,  1395,  1400,  1405,  1410,  1415,  1420,  1425,  1429,  1433,
+    1437,  1441,  1445,  1449,  1453,  1457,  1461,  1465,  1469,  1473,
+    1477,  1481,  1485,  1493,  1501,  1505,  1518,  1518,  1521,  1521,
+    1527,  1530,  1546,  1549,  1558,  1562,  1568,  1575,  1590,  1594,
+    1598,  1599,  1605,  1606,  1607,  1608,  1609,  1613,  1614,  1614,
+    1614,  1624,  1625,  1629,  1629,  1630,  1630,  1635,  1638,  1648,
+    1651,  1657,  1658,  1662,  1670,  1674,  1684,  1689,  1706,  1706,
+    1711,  1711,  1718,  1718,  1726,  1729,  1735,  1738,  1744,  1748,
+    1755,  1762,  1769,  1776,  1787,  1796,  1800,  1807,  1810,  1816,
+    1816
 };
 #endif
 
@@ -2736,6 +2736,15 @@
                         // Treat it like a built-in unary operator.
                         //
                         (yyval.interm.intermTypedNode) = context->intermediate.addUnaryMath(op, (yyvsp[(1) - (1)].interm).intermNode, (yylsp[(1) - (1)]));
+                        const TType& returnType = fnCandidate->getReturnType();
+                        if (returnType.getBasicType() == EbtBool) {
+                            // Bool types should not have precision, so we'll override any precision
+                            // that might have been set by addUnaryMath.
+                            (yyval.interm.intermTypedNode)->setType(returnType);
+                        } else {
+                            // addUnaryMath has set the precision of the node based on the operand.
+                            (yyval.interm.intermTypedNode)->setTypePreservePrecision(returnType);
+                        }
                         if ((yyval.interm.intermTypedNode) == 0)  {
                             std::stringstream extraInfoStream;
                             extraInfoStream << "built in unary operator function.  Type: " << static_cast<TIntermTyped*>((yyvsp[(1) - (1)].interm).intermNode)->getCompleteString();
@@ -2744,20 +2753,29 @@
                             YYERROR;
                         }
                     } else {
-                        (yyval.interm.intermTypedNode) = context->intermediate.setAggregateOperator((yyvsp[(1) - (1)].interm).intermAggregate, op, (yylsp[(1) - (1)]));
+                        TIntermAggregate *aggregate = context->intermediate.setAggregateOperator((yyvsp[(1) - (1)].interm).intermAggregate, op, (yylsp[(1) - (1)]));
+                        aggregate->setType(fnCandidate->getReturnType());
+                        aggregate->setPrecisionFromChildren();
+                        (yyval.interm.intermTypedNode) = aggregate;
                     }
                 } else {
                     // This is a real function call
 
-                    (yyval.interm.intermTypedNode) = context->intermediate.setAggregateOperator((yyvsp[(1) - (1)].interm).intermAggregate, EOpFunctionCall, (yylsp[(1) - (1)]));
-                    (yyval.interm.intermTypedNode)->setType(fnCandidate->getReturnType());
+                    TIntermAggregate *aggregate = context->intermediate.setAggregateOperator((yyvsp[(1) - (1)].interm).intermAggregate, EOpFunctionCall, (yylsp[(1) - (1)]));
+                    aggregate->setType(fnCandidate->getReturnType());
 
                     // this is how we know whether the given function is a builtIn function or a user defined function
                     // if builtIn == false, it's a userDefined -> could be an overloaded builtIn function also
                     // if builtIn == true, it's definitely a builtIn function with EOpNull
                     if (!builtIn)
-                        (yyval.interm.intermTypedNode)->getAsAggregate()->setUserDefined();
-                    (yyval.interm.intermTypedNode)->getAsAggregate()->setName(fnCandidate->getMangledName());
+                        aggregate->setUserDefined();
+                    aggregate->setName(fnCandidate->getMangledName());
+
+                    // This needs to happen after the name is set
+                    if (builtIn)
+                        aggregate->setBuiltInFunctionPrecision();
+
+                    (yyval.interm.intermTypedNode) = aggregate;
 
                     TQualifier qual;
                     for (size_t i = 0; i < fnCandidate->getParamCount(); ++i) {
@@ -2770,7 +2788,6 @@
                         }
                     }
                 }
-                (yyval.interm.intermTypedNode)->setType(fnCandidate->getReturnType());
             } else {
                 // error message was put out by PaFindFunction()
                 // Put on a dummy node for error recovery
diff --git a/src/compiler/translator/glslang_tab.h b/src/compiler/translator/glslang_tab.h
index c6a61dc..41a188c 100644
--- a/src/compiler/translator/glslang_tab.h
+++ b/src/compiler/translator/glslang_tab.h
@@ -1,4 +1,4 @@
-/* A Bison parser, made by GNU Bison 2.7.1.  */
+/* A Bison parser, made by GNU Bison 2.7.12-4996.  */
 
 /* Bison interface for Yacc-like parsers in C
    
diff --git a/tests/compiler_tests/TypeTracking_test.cpp b/tests/compiler_tests/TypeTracking_test.cpp
new file mode 100644
index 0000000..838ac93
--- /dev/null
+++ b/tests/compiler_tests/TypeTracking_test.cpp
@@ -0,0 +1,199 @@
+//
+// Copyright (c) 2014 The ANGLE Project Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+// TypeTracking_test.cpp:
+//   Test for tracking types resulting from math operations, including their
+//   precision.
+//
+
+#include "angle_gl.h"
+#include "gtest/gtest.h"
+#include "GLSLANG/ShaderLang.h"
+#include "compiler/translator/TranslatorESSL.h"
+
+class TypeTrackingTest : public testing::Test
+{
+  public:
+    TypeTrackingTest() {}
+
+  protected:
+    virtual void SetUp()
+    {
+        ShBuiltInResources resources;
+        ShInitBuiltInResources(&resources);
+
+        mTranslator = new TranslatorESSL(GL_FRAGMENT_SHADER, SH_GLES2_SPEC);
+        ASSERT_TRUE(mTranslator->Init(resources));
+    }
+
+    virtual void TearDown()
+    {
+        delete mTranslator;
+    }
+
+    void compile(const std::string& shaderString)
+    {
+        const char *shaderStrings[] = { shaderString.c_str() };
+        bool compilationSuccess = mTranslator->compile(shaderStrings, 1, SH_INTERMEDIATE_TREE);
+        TInfoSink &infoSink = mTranslator->getInfoSink();
+        mInfoLog = infoSink.info.c_str();
+        if (!compilationSuccess)
+            FAIL() << "Shader compilation failed " << mInfoLog;
+    }
+
+    bool foundInIntermediateTree(const char* stringToFind)
+    {
+        return mInfoLog.find(stringToFind) != std::string::npos;
+    }
+
+  private:
+    TranslatorESSL *mTranslator;
+    std::string mInfoLog;
+};
+
+TEST_F(TypeTrackingTest, BuiltInFunctionResultPrecision)
+{
+    const std::string &shaderString =
+        "precision mediump float;\n"
+        "uniform float f;\n"
+        "void main() {\n"
+        "   float ff = sin(f);\n"
+        "   gl_FragColor = vec4(ff);\n"
+        "}\n";
+    compile(shaderString);
+    ASSERT_TRUE(foundInIntermediateTree("sine (mediump float)"));
+};
+
+TEST_F(TypeTrackingTest, BinaryMathResultPrecision)
+{
+    const std::string &shaderString =
+        "precision mediump float;\n"
+        "uniform float f;\n"
+        "void main() {\n"
+        "   float ff = f * 0.5;\n"
+        "   gl_FragColor = vec4(ff);\n"
+        "}\n";
+    compile(shaderString);
+    ASSERT_TRUE(foundInIntermediateTree("multiply (mediump float)"));
+};
+
+TEST_F(TypeTrackingTest, BuiltInVecFunctionResultTypeAndPrecision)
+{
+    const std::string &shaderString =
+        "precision mediump float;\n"
+        "uniform vec2 a;\n"
+        "void main() {\n"
+        "   float b = length(a);\n"
+        "   float c = dot(a, vec2(0.5));\n"
+        "   float d = distance(vec2(0.5), a);\n"
+        "   gl_FragColor = vec4(b, c, d, 1.0);\n"
+        "}\n";
+    compile(shaderString);
+    ASSERT_TRUE(foundInIntermediateTree("length (mediump float)"));
+    ASSERT_TRUE(foundInIntermediateTree("dot-product (mediump float)"));
+    ASSERT_TRUE(foundInIntermediateTree("distance (mediump float)"));
+};
+
+TEST_F(TypeTrackingTest, BuiltInFunctionChoosesHigherPrecision)
+{
+    const std::string &shaderString =
+        "precision lowp float;\n"
+        "uniform mediump vec2 a;\n"
+        "uniform lowp vec2 b;\n"
+        "void main() {\n"
+        "   float c = dot(a, b);\n"
+        "   float d = distance(b, a);\n"
+        "   gl_FragColor = vec4(c, d, 0.0, 1.0);\n"
+        "}\n";
+    compile(shaderString);
+    ASSERT_TRUE(foundInIntermediateTree("dot-product (mediump float)"));
+    ASSERT_TRUE(foundInIntermediateTree("distance (mediump float)"));
+};
+
+TEST_F(TypeTrackingTest, BuiltInBoolFunctionResultType)
+{
+    const std::string &shaderString =
+        "uniform bvec4 bees;\n"
+        "void main() {\n"
+        "   bool b = any(bees);\n"
+        "   bool c = all(bees);\n"
+        "   bvec4 d = not(bees);\n"
+        "   gl_FragColor = vec4(b ? 1.0 : 0.0, c ? 1.0 : 0.0, d.x ? 1.0 : 0.0, 1.0);\n"
+        "}\n";
+    compile(shaderString);
+    ASSERT_TRUE(foundInIntermediateTree("any (bool)"));
+    ASSERT_TRUE(foundInIntermediateTree("all (bool)"));
+    ASSERT_TRUE(foundInIntermediateTree("Negate conditional (4-component vector of bool)"));
+};
+
+TEST_F(TypeTrackingTest, BuiltInVecToBoolFunctionResultType)
+{
+    const std::string &shaderString =
+        "precision mediump float;\n"
+        "uniform vec2 apples;\n"
+        "uniform vec2 oranges;\n"
+        "uniform ivec2 foo;\n"
+        "uniform ivec2 bar;\n"
+        "void main() {\n"
+        "   bvec2 a = lessThan(apples, oranges);\n"
+        "   bvec2 b = greaterThan(foo, bar);\n"
+        "   gl_FragColor = vec4(any(a) ? 1.0 : 0.0, any(b) ? 1.0 : 0.0, 0.0, 1.0);\n"
+        "}\n";
+    compile(shaderString);
+    ASSERT_TRUE(foundInIntermediateTree("Less Than (2-component vector of bool)"));
+    ASSERT_TRUE(foundInIntermediateTree("Greater Than (2-component vector of bool)"));
+};
+
+TEST_F(TypeTrackingTest, Texture2DResultTypeAndPrecision)
+{
+    // ESSL spec section 4.5.3: sampler2D and samplerCube are lowp by default
+    // ESSL spec section 8: For the texture functions, the precision of the return type matches the precision of the sampler type.
+    const std::string &shaderString =
+        "precision mediump float;\n"
+        "uniform sampler2D s;\n"
+        "uniform vec2 a;\n"
+        "void main() {\n"
+        "   vec4 c = texture2D(s, a);\n"
+        "   gl_FragColor = c;\n"
+        "}\n";
+    compile(shaderString);
+    ASSERT_TRUE(foundInIntermediateTree("texture2D(s21;vf2; (lowp 4-component vector of float)"));
+};
+
+TEST_F(TypeTrackingTest, TextureCubeResultTypeAndPrecision)
+{
+    // ESSL spec section 4.5.3: sampler2D and samplerCube are lowp by default
+    // ESSL spec section 8: For the texture functions, the precision of the return type matches the precision of the sampler type.
+    const std::string &shaderString =
+        "precision mediump float;\n"
+        "uniform samplerCube sc;\n"
+        "uniform vec3 a;\n"
+        "void main() {\n"
+        "   vec4 c = textureCube(sc, a);\n"
+        "   gl_FragColor = c;\n"
+        "}\n";
+    compile(shaderString);
+    ASSERT_TRUE(foundInIntermediateTree("textureCube(sC1;vf3; (lowp 4-component vector of float)"));
+};
+
+TEST_F(TypeTrackingTest, TextureSizeResultTypeAndPrecision)
+{
+    // ESSL 3.0 spec section 8: textureSize has predefined precision highp
+    const std::string &shaderString =
+        "#version 300 es\n"
+        "precision mediump float;\n"
+        "out vec4 my_FragColor;\n"
+        "uniform sampler2D s;\n"
+        "void main() {\n"
+        "   ivec2 size = textureSize(s, 0);\n"
+        "   if (size.x > 100) {\n"
+        "       my_FragColor = vec4(0.0, 1.0, 0.0, 1.0);\n"
+        "   } else {\n"
+        "       my_FragColor = vec4(1.0, 0.0, 0.0, 1.0);\n"
+        "   }\n"
+        "}\n";
+    compile(shaderString);
+    ASSERT_TRUE(foundInIntermediateTree("textureSize(s21;i1; (highp 2-component vector of int)"));
+};