Add support for arrays of arrays in AST processing

Data concerning arrays of arrays is added in TType.

Parsing arrays of arrays and support for arrays of arrays in
TPublicType are still left to be implemented later.

ShaderVariable interface for arrays of arrays is also left to be
implemented later.

We rely on existing test coverage to make sure that arrays of arrays
are not accidentally exposed.

BUG=angleproject:2125
TEST=angle_unittests, angle_end2end_tests, angle_deqp_gles31_tests

Change-Id: Ie17d5ac9b8d33958e9126dc0fb40bf1c81ddeec9
Reviewed-on: https://chromium-review.googlesource.com/616146
Reviewed-by: Jamie Madill <jmadill@chromium.org>
Commit-Queue: Olli Etuaho <oetuaho@nvidia.com>
diff --git a/src/compiler/translator/CollectVariables.cpp b/src/compiler/translator/CollectVariables.cpp
index 5815940..abe4592 100644
--- a/src/compiler/translator/CollectVariables.cpp
+++ b/src/compiler/translator/CollectVariables.cpp
@@ -219,8 +219,9 @@
     info->name       = name;
     info->mappedName = name;
     info->type       = GLVariableType(type);
-    info->arraySize  = type.isArray() ? type.getArraySize() : 0;
-    info->precision  = GLVariablePrecision(type);
+    ASSERT(!type.isArrayOfArrays());
+    info->arraySize = type.isArray() ? type.getOutermostArraySize() : 0;
+    info->precision = GLVariablePrecision(type);
 }
 
 void CollectVariablesTraverser::recordBuiltInVaryingUsed(const char *name,
@@ -511,7 +512,11 @@
     }
     variableOut->name       = name.c_str();
     variableOut->mappedName = HashName(name, mHashFunction).c_str();
-    variableOut->arraySize  = type.getArraySize();
+
+    // TODO(oetuaho@nvidia.com): Uniforms can be arrays of arrays, so this assert will need to be
+    // removed.
+    ASSERT(!type.isArrayOfArrays());
+    variableOut->arraySize = type.isArray() ? type.getOutermostArraySize() : 0;
 }
 
 Attribute CollectVariablesTraverser::recordAttribute(const TIntermSymbol &variable) const
@@ -581,7 +586,8 @@
     interfaceBlock->mappedName = HashName(blockType->name().c_str(), mHashFunction).c_str();
     interfaceBlock->instanceName =
         (blockType->hasInstanceName() ? blockType->instanceName().c_str() : "");
-    interfaceBlock->arraySize = interfaceBlockType.getArraySize();
+    ASSERT(!interfaceBlockType.isArrayOfArrays());  // Disallowed by GLSL ES 3.10 section 4.3.9
+    interfaceBlock->arraySize = interfaceBlockType.isArray() ? interfaceBlockType.getOutermostArraySize() : 0;
 
     interfaceBlock->blockType = GetBlockType(interfaceBlockType.getQualifier());
     if (interfaceBlock->blockType == BlockType::BLOCK_UNIFORM ||
diff --git a/src/compiler/translator/Initialize.cpp b/src/compiler/translator/Initialize.cpp
index 61acd4f..52ae411 100644
--- a/src/compiler/translator/Initialize.cpp
+++ b/src/compiler/translator/Initialize.cpp
@@ -816,11 +816,11 @@
             TType fragData(EbtFloat, EbpMedium, EvqFragData, 4);
             if (spec != SH_WEBGL2_SPEC && spec != SH_WEBGL3_SPEC)
             {
-                fragData.setArraySize(resources.MaxDrawBuffers);
+                fragData.makeArray(resources.MaxDrawBuffers);
             }
             else
             {
-                fragData.setArraySize(1u);
+                fragData.makeArray(1u);
             }
             symbolTable.insertVariable(ESSL1_BUILTINS, "gl_FragData", fragData);
 
@@ -829,8 +829,8 @@
                 symbolTable.insertVariableExt(
                     ESSL1_BUILTINS, "GL_EXT_blend_func_extended", "gl_SecondaryFragColorEXT",
                     TType(EbtFloat, EbpMedium, EvqSecondaryFragColorEXT, 4));
-                TType secondaryFragData(EbtFloat, EbpMedium, EvqSecondaryFragDataEXT, 4, 1, true);
-                secondaryFragData.setArraySize(resources.MaxDualSourceDrawBuffers);
+                TType secondaryFragData(EbtFloat, EbpMedium, EvqSecondaryFragDataEXT, 4, 1);
+                secondaryFragData.makeArray(resources.MaxDualSourceDrawBuffers);
                 symbolTable.insertVariableExt(ESSL1_BUILTINS, "GL_EXT_blend_func_extended",
                                               "gl_SecondaryFragDataEXT", secondaryFragData);
             }
@@ -848,8 +848,8 @@
 
             if (resources.EXT_shader_framebuffer_fetch || resources.NV_shader_framebuffer_fetch)
             {
-                TType lastFragData(EbtFloat, EbpMedium, EvqLastFragData, 4, 1, true);
-                lastFragData.setArraySize(resources.MaxDrawBuffers);
+                TType lastFragData(EbtFloat, EbpMedium, EvqLastFragData, 4, 1);
+                lastFragData.makeArray(resources.MaxDrawBuffers);
 
                 if (resources.EXT_shader_framebuffer_fetch)
                 {
@@ -928,8 +928,8 @@
 
             // The array size of gl_in is undefined until we get a valid input primitive
             // declaration.
-            TType glInType(glInBlock, EvqPerVertexIn, TLayoutQualifier::create(), 0);
-            glInType.setArrayUnsized();
+            TType glInType(glInBlock, EvqPerVertexIn, TLayoutQualifier::create());
+            glInType.makeArray(0u);
             symbolTable.insertVariableExt(ESSL3_1_BUILTINS, extension, "gl_in", glInType);
 
             break;
diff --git a/src/compiler/translator/InitializeVariables.cpp b/src/compiler/translator/InitializeVariables.cpp
index 6b015e2..bab1b6a 100644
--- a/src/compiler/translator/InitializeVariables.cpp
+++ b/src/compiler/translator/InitializeVariables.cpp
@@ -68,11 +68,15 @@
     // doesn't have array assignment.
     // Note that it is important to have the array init in the right order to workaround
     // http://crbug.com/709317
-    for (unsigned int i = 0; i < initializedNode->getArraySize(); ++i)
+    for (unsigned int i = 0; i < initializedNode->getOutermostArraySize(); ++i)
     {
         TIntermBinary *element =
             new TIntermBinary(EOpIndexDirect, initializedNode->deepCopy(), CreateIndexNode(i));
-        if (element->getType().isStructureContainingArrays())
+        if (element->isArray())
+        {
+            AddArrayZeroInitSequence(element, initSequenceOut);
+        }
+        else if (element->getType().isStructureContainingArrays())
         {
             AddStructZeroInitSequence(element, initSequenceOut);
         }
diff --git a/src/compiler/translator/IntermNode.cpp b/src/compiler/translator/IntermNode.cpp
index 1d842cd..bf2b8c7 100644
--- a/src/compiler/translator/IntermNode.cpp
+++ b/src/compiler/translator/IntermNode.cpp
@@ -502,7 +502,7 @@
            (declarator->getAsBinaryNode() != nullptr &&
             declarator->getAsBinaryNode()->getOp() == EOpInitialize));
     ASSERT(mDeclarators.empty() ||
-           declarator->getType().sameElementType(mDeclarators.back()->getAsTyped()->getType()));
+           declarator->getType().sameNonArrayType(mDeclarators.back()->getAsTyped()->getType()));
     mDeclarators.push_back(declarator);
 }
 
@@ -1063,7 +1063,7 @@
         case EOpIndexIndirect:
             if (mLeft->isArray())
             {
-                mType.clearArrayness();
+                mType.toArrayElementType();
             }
             else if (mLeft->isMatrix())
             {
@@ -1242,9 +1242,9 @@
 {
     if (isArray())
     {
-        ASSERT(index < static_cast<int>(getType().getArraySize()));
+        ASSERT(index < static_cast<int>(getType().getOutermostArraySize()));
         TType arrayElementType = getType();
-        arrayElementType.clearArrayness();
+        arrayElementType.toArrayElementType();
         size_t arrayElementSize = arrayElementType.getObjectSize();
         return &mUnionArrayPointer[arrayElementSize * index];
     }
diff --git a/src/compiler/translator/IntermNode.h b/src/compiler/translator/IntermNode.h
index 4a906bb..5dd7da8 100644
--- a/src/compiler/translator/IntermNode.h
+++ b/src/compiler/translator/IntermNode.h
@@ -176,7 +176,7 @@
     const char *getBasicString() const { return mType.getBasicString(); }
     TString getCompleteString() const { return mType.getCompleteString(); }
 
-    unsigned int getArraySize() const { return mType.getArraySize(); }
+    unsigned int getOutermostArraySize() const { return mType.getOutermostArraySize(); }
 
     bool isConstructorWithOnlyConstantUnionParameters();
 
diff --git a/src/compiler/translator/IntermNode_util.cpp b/src/compiler/translator/IntermNode_util.cpp
index 4282367..d0ed50a 100644
--- a/src/compiler/translator/IntermNode_util.cpp
+++ b/src/compiler/translator/IntermNode_util.cpp
@@ -116,7 +116,10 @@
         // Void array. This happens only on error condition, similarly to the case above. We don't
         // have a constructor operator for void, so this needs special handling. We'll end up with a
         // value without the array type, but that should not be a problem.
-        constType.clearArrayness();
+        while (constType.isArray())
+        {
+            constType.toArrayElementType();
+        }
         return CreateZeroNode(constType);
     }
 
@@ -125,9 +128,9 @@
     if (type.isArray())
     {
         TType elementType(type);
-        elementType.clearArrayness();
+        elementType.toArrayElementType();
 
-        size_t arraySize = type.getArraySize();
+        size_t arraySize = type.getOutermostArraySize();
         for (size_t i = 0; i < arraySize; ++i)
         {
             arguments->push_back(CreateZeroNode(elementType));
diff --git a/src/compiler/translator/OutputGLSLBase.cpp b/src/compiler/translator/OutputGLSLBase.cpp
index 29da221..e2c5b16 100644
--- a/src/compiler/translator/OutputGLSLBase.cpp
+++ b/src/compiler/translator/OutputGLSLBase.cpp
@@ -10,6 +10,7 @@
 #include "common/debug.h"
 #include "common/mathutil.h"
 #include "compiler/translator/Compiler.h"
+#include "compiler/translator/util.h"
 
 #include <cfloat>
 
@@ -18,13 +19,6 @@
 
 namespace
 {
-TString arrayBrackets(const TType &type)
-{
-    ASSERT(type.isArray());
-    TInfoSinkBase out;
-    out << "[" << type.getArraySize() << "]";
-    return TString(out.c_str());
-}
 
 bool isSingleStatement(TIntermNode *node)
 {
@@ -382,7 +376,7 @@
         if (!arg->getName().getString().empty())
             out << " " << hashName(arg->getName());
         if (type.isArray())
-            out << arrayBrackets(type);
+            out << ArrayString(type);
 
         // Put a comma if this is not the last argument.
         if (iter != args.end() - 1)
@@ -456,7 +450,7 @@
         if (type.isArray())
         {
             out << getTypeName(type);
-            out << arrayBrackets(type);
+            out << ArrayString(type);
             out << "(";
         }
         else
@@ -476,7 +470,7 @@
     out << hashVariableName(node->getName());
 
     if (mDeclaringVariables && node->getType().isArray())
-        out << arrayBrackets(node->getType());
+        out << ArrayString(node->getType());
 }
 
 void TOutputGLSLBase::visitConstantUnion(TIntermConstantUnion *node)
@@ -573,7 +567,7 @@
                     if (left->isArray())
                     {
                         // The shader will fail validation if the array length is not > 0.
-                        maxSize = static_cast<int>(leftType.getArraySize()) - 1;
+                        maxSize = static_cast<int>(leftType.getOutermostArraySize()) - 1;
                     }
                     else
                     {
@@ -931,7 +925,7 @@
     const TType &type = node->getType();
     writeVariableType(type);
     if (type.isArray())
-        out << arrayBrackets(type);
+        out << ArrayString(type);
 
     out << " " << hashFunctionNameIfNeeded(*node->getFunctionSymbolInfo());
 
@@ -1226,7 +1220,7 @@
             out << " ";
         out << getTypeName(*field->type()) << " " << hashName(TName(field->name()));
         if (field->type()->isArray())
-            out << arrayBrackets(*field->type());
+            out << ArrayString(*field->type());
         out << ";\n";
     }
     out << "}";
@@ -1294,7 +1288,7 @@
             out << " ";
         out << getTypeName(*field->type()) << " " << hashName(TName(field->name()));
         if (field->type()->isArray())
-            out << arrayBrackets(*field->type());
+            out << ArrayString(*field->type());
         out << ";\n";
     }
     out << "}";
diff --git a/src/compiler/translator/OutputHLSL.cpp b/src/compiler/translator/OutputHLSL.cpp
index 39f856d..9bcfe8c 100644
--- a/src/compiler/translator/OutputHLSL.cpp
+++ b/src/compiler/translator/OutputHLSL.cpp
@@ -31,6 +31,23 @@
 namespace sh
 {
 
+namespace
+{
+
+TString ArrayHelperFunctionName(const char *prefix, const TType &type)
+{
+    TStringStream fnName;
+    fnName << prefix << "_";
+    for (unsigned int arraySize : type.getArraySizes())
+    {
+        fnName << arraySize << "_";
+    }
+    fnName << TypeString(type);
+    return fnName.str();
+}
+
+}  // anonymous namespace
+
 void OutputHLSL::writeFloat(TInfoSinkBase &out, float f)
 {
     // This is known not to work for NaN on all drivers but make the best effort to output NaNs
@@ -257,14 +274,14 @@
     if (type.isArray())
     {
         init += indentString + "{\n";
-        for (unsigned int arrayIndex = 0u; arrayIndex < type.getArraySize(); ++arrayIndex)
+        for (unsigned int arrayIndex = 0u; arrayIndex < type.getOutermostArraySize(); ++arrayIndex)
         {
             TStringStream indexedString;
             indexedString << name << "[" << arrayIndex << "]";
             TType elementType = type;
-            elementType.clearArrayness();
+            elementType.toArrayElementType();
             init += structInitializerString(indent + 1, elementType, indexedString.str());
-            if (arrayIndex < type.getArraySize() - 1)
+            if (arrayIndex < type.getOutermostArraySize() - 1)
             {
                 init += ",";
             }
@@ -948,6 +965,19 @@
     }
 }
 
+void OutputHLSL::outputAssign(Visit visit, const TType &type, TInfoSinkBase &out)
+{
+    if (type.isArray())
+    {
+        const TString &functionName = addArrayAssignmentFunction(type);
+        outputTriplet(out, visit, (functionName + "(").c_str(), ", ", ")");
+    }
+    else
+    {
+        outputTriplet(out, visit, "(", " = ", ")");
+    }
+}
+
 bool OutputHLSL::ancestorEvaluatesToSamplerInStruct()
 {
     for (unsigned int n = 0u; getAncestorNode(n) != nullptr; ++n)
@@ -1010,7 +1040,7 @@
             outputTriplet(out, visit, "(", ", ", ")");
             break;
         case EOpAssign:
-            if (node->getLeft()->isArray())
+            if (node->isArray())
             {
                 TIntermAggregate *rightAgg = node->getRight()->getAsAggregate();
                 if (rightAgg != nullptr && rightAgg->isConstructor())
@@ -1030,14 +1060,8 @@
                 // ArrayReturnValueToOutParameter should have eliminated expressions where a
                 // function call is assigned.
                 ASSERT(rightAgg == nullptr);
-
-                const TString &functionName = addArrayAssignmentFunction(node->getType());
-                outputTriplet(out, visit, (functionName + "(").c_str(), ", ", ")");
             }
-            else
-            {
-                outputTriplet(out, visit, "(", " = ", ")");
-            }
+            outputAssign(visit, node->getType(), out);
             break;
         case EOpInitialize:
             if (visit == PreVisit)
@@ -2744,31 +2768,33 @@
 {
     // We support writing constant unions and constructors that only take constant unions as
     // parameters as HLSL literals.
-    return expression->getAsConstantUnion() ||
-           expression->isConstructorWithOnlyConstantUnionParameters();
+    return !expression->getType().isArrayOfArrays() &&
+           (expression->getAsConstantUnion() ||
+            expression->isConstructorWithOnlyConstantUnionParameters());
 }
 
 bool OutputHLSL::writeConstantInitialization(TInfoSinkBase &out,
                                              TIntermSymbol *symbolNode,
-                                             TIntermTyped *expression)
+                                             TIntermTyped *initializer)
 {
-    if (canWriteAsHLSLLiteral(expression))
+    if (canWriteAsHLSLLiteral(initializer))
     {
         symbolNode->traverse(this);
-        if (expression->getType().isArray())
+        ASSERT(!symbolNode->getType().isArrayOfArrays());
+        if (symbolNode->getType().isArray())
         {
-            out << "[" << expression->getType().getArraySize() << "]";
+            out << "[" << symbolNode->getType().getOutermostArraySize() << "]";
         }
         out << " = {";
-        if (expression->getAsConstantUnion())
+        if (initializer->getAsConstantUnion())
         {
-            TIntermConstantUnion *nodeConst  = expression->getAsConstantUnion();
+            TIntermConstantUnion *nodeConst  = initializer->getAsConstantUnion();
             const TConstantUnion *constUnion = nodeConst->getUnionArrayPointer();
             writeConstantUnionArray(out, constUnion, nodeConst->getType().getObjectSize());
         }
         else
         {
-            TIntermAggregate *constructor = expression->getAsAggregate();
+            TIntermAggregate *constructor = initializer->getAsAggregate();
             ASSERT(constructor != nullptr);
             for (TIntermNode *&node : *constructor->getSequence())
             {
@@ -2856,33 +2882,31 @@
         }
     }
 
-    const TString &typeName = TypeString(type);
+    TType elementType(type);
+    elementType.toArrayElementType();
 
     ArrayHelperFunction *function = new ArrayHelperFunction();
     function->type                = type;
 
-    TInfoSinkBase fnNameOut;
-    fnNameOut << "angle_eq_" << type.getArraySize() << "_" << typeName;
-    function->functionName = fnNameOut.c_str();
-
-    TType nonArrayType = type;
-    nonArrayType.clearArrayness();
+    function->functionName = ArrayHelperFunctionName("angle_eq", type);
 
     TInfoSinkBase fnOut;
 
-    fnOut << "bool " << function->functionName << "(" << typeName << " a[" << type.getArraySize()
-          << "], " << typeName << " b[" << type.getArraySize() << "])\n"
+    const TString &typeName = TypeString(type);
+    fnOut << "bool " << function->functionName << "(" << typeName << " a" << ArrayString(type)
+          << ", " << typeName << " b" << ArrayString(type) << ")\n"
           << "{\n"
              "    for (int i = 0; i < "
-          << type.getArraySize() << "; ++i)\n"
-                                    "    {\n"
-                                    "        if (";
+          << type.getOutermostArraySize()
+          << "; ++i)\n"
+             "    {\n"
+             "        if (";
 
-    outputEqual(PreVisit, nonArrayType, EOpNotEqual, fnOut);
+    outputEqual(PreVisit, elementType, EOpNotEqual, fnOut);
     fnOut << "a[i]";
-    outputEqual(InVisit, nonArrayType, EOpNotEqual, fnOut);
+    outputEqual(InVisit, elementType, EOpNotEqual, fnOut);
     fnOut << "b[i]";
-    outputEqual(PostVisit, nonArrayType, EOpNotEqual, fnOut);
+    outputEqual(PostVisit, elementType, EOpNotEqual, fnOut);
 
     fnOut << ") { return false; }\n"
              "    }\n"
@@ -2907,26 +2931,35 @@
         }
     }
 
-    const TString &typeName = TypeString(type);
+    TType elementType(type);
+    elementType.toArrayElementType();
 
     ArrayHelperFunction function;
     function.type = type;
 
-    TInfoSinkBase fnNameOut;
-    fnNameOut << "angle_assign_" << type.getArraySize() << "_" << typeName;
-    function.functionName = fnNameOut.c_str();
+    function.functionName = ArrayHelperFunctionName("angle_assign", type);
 
     TInfoSinkBase fnOut;
 
-    fnOut << "void " << function.functionName << "(out " << typeName << " a[" << type.getArraySize()
-          << "], " << typeName << " b[" << type.getArraySize() << "])\n"
+    const TString &typeName = TypeString(type);
+    fnOut << "void " << function.functionName << "(out " << typeName << " a" << ArrayString(type)
+          << ", " << typeName << " b" << ArrayString(type) << ")\n"
           << "{\n"
              "    for (int i = 0; i < "
-          << type.getArraySize() << "; ++i)\n"
-                                    "    {\n"
-                                    "        a[i] = b[i];\n"
-                                    "    }\n"
-                                    "}\n";
+          << type.getOutermostArraySize()
+          << "; ++i)\n"
+             "    {\n"
+             "        ";
+
+    outputAssign(PreVisit, elementType, fnOut);
+    fnOut << "a[i]";
+    outputAssign(InVisit, elementType, fnOut);
+    fnOut << "b[i]";
+    outputAssign(PostVisit, elementType, fnOut);
+
+    fnOut << ";\n"
+             "    }\n"
+             "}\n";
 
     function.functionDefinition = fnOut.c_str();
 
@@ -2945,29 +2978,34 @@
         }
     }
 
-    const TString &typeName = TypeString(type);
+    TType elementType(type);
+    elementType.toArrayElementType();
 
     ArrayHelperFunction function;
     function.type = type;
 
-    TInfoSinkBase fnNameOut;
-    fnNameOut << "angle_construct_into_" << type.getArraySize() << "_" << typeName;
-    function.functionName = fnNameOut.c_str();
+    function.functionName = ArrayHelperFunctionName("angle_construct_into", type);
 
     TInfoSinkBase fnOut;
 
-    fnOut << "void " << function.functionName << "(out " << typeName << " a[" << type.getArraySize()
-          << "]";
-    for (unsigned int i = 0u; i < type.getArraySize(); ++i)
+    const TString &typeName = TypeString(type);
+    fnOut << "void " << function.functionName << "(out " << typeName << " a" << ArrayString(type);
+    for (unsigned int i = 0u; i < type.getOutermostArraySize(); ++i)
     {
-        fnOut << ", " << typeName << " b" << i;
+        fnOut << ", " << typeName << " b" << i << ArrayString(elementType);
     }
     fnOut << ")\n"
              "{\n";
 
-    for (unsigned int i = 0u; i < type.getArraySize(); ++i)
+    for (unsigned int i = 0u; i < type.getOutermostArraySize(); ++i)
     {
-        fnOut << "    a[" << i << "] = b" << i << ";\n";
+        fnOut << "    ";
+        outputAssign(PreVisit, elementType, fnOut);
+        fnOut << "a[" << i << "]";
+        outputAssign(InVisit, elementType, fnOut);
+        fnOut << "b" << i;
+        outputAssign(PostVisit, elementType, fnOut);
+        fnOut << ";\n";
     }
     fnOut << "}\n";
 
diff --git a/src/compiler/translator/OutputHLSL.h b/src/compiler/translator/OutputHLSL.h
index 46a5887..3784282 100644
--- a/src/compiler/translator/OutputHLSL.h
+++ b/src/compiler/translator/OutputHLSL.h
@@ -108,6 +108,7 @@
                                              const TConstantUnion *constUnion);
 
     void outputEqual(Visit visit, const TType &type, TOperator op, TInfoSinkBase &out);
+    void outputAssign(Visit visit, const TType &type, TInfoSinkBase &out);
 
     void writeEmulatedFunctionTriplet(TInfoSinkBase &out, Visit visit, TOperator op);
     void makeFlaggedStructMaps(const std::vector<TIntermTyped *> &flaggedStructs);
diff --git a/src/compiler/translator/ParseContext.cpp b/src/compiler/translator/ParseContext.cpp
index 25ee442..f422f04 100644
--- a/src/compiler/translator/ParseContext.cpp
+++ b/src/compiler/translator/ParseContext.cpp
@@ -680,7 +680,7 @@
     {
         // The size of an unsized constructor should already have been determined.
         ASSERT(!type.isUnsizedArray());
-        if (static_cast<size_t>(type.getArraySize()) != arguments->size())
+        if (static_cast<size_t>(type.getOutermostArraySize()) != arguments->size())
         {
             error(line, "array constructor needs one argument per array element", "constructor");
             return false;
@@ -695,7 +695,7 @@
                 error(line, "constructing from a non-dereferenced array", "constructor");
                 return false;
             }
-            if (!argType.sameElementType(type))
+            if (!argType.isElementTypeOf(type))
             {
                 error(line, "Array constructor argument has an incorrect type", "constructor");
                 return false;
@@ -1057,7 +1057,14 @@
     {
         const TVariable *maxDrawBuffers = static_cast<const TVariable *>(
             symbolTable.findBuiltIn("gl_MaxDrawBuffers", mShaderVersion));
-        if (static_cast<int>(type.getArraySize()) == maxDrawBuffers->getConstPointer()->getIConst())
+        if (type.isArrayOfArrays())
+        {
+            error(line, "redeclaration of gl_LastFragData as an array of arrays",
+                  identifier.c_str());
+            return false;
+        }
+        else if (static_cast<int>(type.getOutermostArraySize()) ==
+                 maxDrawBuffers->getConstPointer()->getIConst())
         {
             if (TSymbol *builtInSymbol = symbolTable.findBuiltIn(identifier, mShaderVersion))
             {
@@ -1389,14 +1396,21 @@
 void TParseContext::checkBindingIsValid(const TSourceLoc &identifierLocation, const TType &type)
 {
     TLayoutQualifier layoutQualifier = type.getLayoutQualifier();
-    int arraySize                    = type.isArray() ? type.getArraySize() : 1;
+    // Note that the ESSL 3.10 section 4.4.5 is not particularly clear on how the binding qualifier
+    // on arrays of arrays should be handled. We interpret the spec so that the binding value is
+    // incremented for each element of the innermost nested arrays. This is in line with how arrays
+    // of arrays of blocks are specified to behave in GLSL 4.50 and a conservative interpretation
+    // when it comes to which shaders are accepted by the compiler.
+    int arrayTotalElementCount = type.getArraySizeProduct();
     if (IsImage(type.getBasicType()))
     {
-        checkImageBindingIsValid(identifierLocation, layoutQualifier.binding, arraySize);
+        checkImageBindingIsValid(identifierLocation, layoutQualifier.binding,
+                                 arrayTotalElementCount);
     }
     else if (IsSampler(type.getBasicType()))
     {
-        checkSamplerBindingIsValid(identifierLocation, layoutQualifier.binding, arraySize);
+        checkSamplerBindingIsValid(identifierLocation, layoutQualifier.binding,
+                                   arrayTotalElementCount);
     }
     else if (IsAtomicCounter(type.getBasicType()))
     {
@@ -1468,10 +1482,12 @@
     }
 }
 
-void TParseContext::checkImageBindingIsValid(const TSourceLoc &location, int binding, int arraySize)
+void TParseContext::checkImageBindingIsValid(const TSourceLoc &location,
+                                             int binding,
+                                             int arrayTotalElementCount)
 {
     // Expects arraySize to be 1 when setting binding for only a single variable.
-    if (binding >= 0 && binding + arraySize > mMaxImageUnits)
+    if (binding >= 0 && binding + arrayTotalElementCount > mMaxImageUnits)
     {
         error(location, "image binding greater than gl_MaxImageUnits", "binding");
     }
@@ -1479,10 +1495,10 @@
 
 void TParseContext::checkSamplerBindingIsValid(const TSourceLoc &location,
                                                int binding,
-                                               int arraySize)
+                                               int arrayTotalElementCount)
 {
     // Expects arraySize to be 1 when setting binding for only a single variable.
-    if (binding >= 0 && binding + arraySize > mMaxCombinedTextureImageUnits)
+    if (binding >= 0 && binding + arrayTotalElementCount > mMaxCombinedTextureImageUnits)
     {
         error(location, "sampler binding greater than maximum texture units", "binding");
     }
@@ -1777,7 +1793,7 @@
     else if (variable->getType().getQualifier() == EvqPerVertexIn)
     {
         TType type(variable->getType());
-        type.setArraySize(mGeometryShaderInputArraySize);
+        type.setArraySize(0, mGeometryShaderInputArraySize);
         node = new TIntermSymbol(variable->getUniqueId(), variable->getName(), type);
     }
     else
@@ -1806,17 +1822,11 @@
     TVariable *variable = nullptr;
     if (type.isUnsizedArray())
     {
-        // We have not checked yet whether the initializer actually is an array or not.
-        if (initializer->isArray())
-        {
-            type.setArraySize(initializer->getArraySize());
-        }
-        else
-        {
-            // Having a non-array initializer for an unsized array will result in an error later,
-            // so we don't generate an error message here.
-            type.setArraySize(1u);
-        }
+        // In case initializer is not an array or type has more dimensions than initializer, this
+        // will default to setting array sizes to 1. We have not checked yet whether the initializer
+        // actually is an array or not. Having a non-array initializer for an unsized array will
+        // result in an error later, so we don't generate an error message here.
+        type.sizeUnsizedArrays(initializer->getType().getArraySizes());
     }
     if (!declareVariable(line, identifier, type, &variable))
     {
@@ -2353,7 +2363,7 @@
     unsigned int size = checkIsValidArraySize(identifierLocation, indexExpression);
     // Make the type an array even if size check failed.
     // This ensures useless error messages regarding the variable's non-arrayness won't follow.
-    arrayType.setArraySize(size);
+    arrayType.makeArray(size);
 
     if (IsAtomicCounter(publicType.getBasicType()))
     {
@@ -2564,7 +2574,7 @@
     {
         TType arrayType   = TType(publicType);
         unsigned int size = checkIsValidArraySize(arrayLocation, indexExpression);
-        arrayType.setArraySize(size);
+        arrayType.makeArray(size);
 
         if (IsAtomicCounter(publicType.getBasicType()))
         {
@@ -3341,6 +3351,45 @@
     return parseParameterDeclarator(*type, identifier, identifierLoc);
 }
 
+bool TParseContext::checkUnsizedArrayConstructorArgumentDimensionality(TIntermSequence *arguments,
+                                                                       TType type,
+                                                                       const TSourceLoc &line)
+{
+    if (arguments->empty())
+    {
+        error(line, "implicitly sized array constructor must have at least one argument", "[]");
+        return false;
+    }
+    for (TIntermNode *arg : *arguments)
+    {
+        TIntermTyped *element = arg->getAsTyped();
+        ASSERT(element);
+        size_t dimensionalityFromElement = element->getType().getArraySizes().size() + 1u;
+        if (dimensionalityFromElement > type.getArraySizes().size())
+        {
+            error(line, "constructing from a non-dereferenced array", "constructor");
+            return false;
+        }
+        else if (dimensionalityFromElement < type.getArraySizes().size())
+        {
+            if (dimensionalityFromElement == 1u)
+            {
+                error(line, "implicitly sized array of arrays constructor argument is not an array",
+                      "constructor");
+            }
+            else
+            {
+                error(line,
+                      "implicitly sized array of arrays constructor argument dimensionality is too "
+                      "low",
+                      "constructor");
+            }
+            return false;
+        }
+    }
+    return true;
+}
+
 // This function is used to test for the correctness of the parameters passed to various constructor
 // functions and also convert them to the right datatype if it is allowed and required.
 //
@@ -3352,13 +3401,23 @@
 {
     if (type.isUnsizedArray())
     {
-        if (arguments->empty())
+        if (!checkUnsizedArrayConstructorArgumentDimensionality(arguments, type, line))
         {
-            error(line, "implicitly sized array constructor must have at least one argument", "[]");
-            type.setArraySize(1u);
+            type.sizeUnsizedArrays(TVector<unsigned int>());
             return CreateZeroNode(type);
         }
-        type.setArraySize(static_cast<unsigned int>(arguments->size()));
+        TIntermTyped *firstElement = arguments->at(0)->getAsTyped();
+        ASSERT(firstElement);
+        type.setArraySize(type.getArraySizes().size() - 1u,
+                          static_cast<unsigned int>(arguments->size()));
+        for (size_t i = 0; i < firstElement->getType().getArraySizes().size(); ++i)
+        {
+            if (type.getArraySizes()[i] == 0u)
+            {
+                type.setArraySize(i, firstElement->getType().getArraySizes().at(i));
+            }
+        }
+        ASSERT(!type.isUnsizedArray());
     }
 
     if (!checkConstructorArguments(line, arguments, type))
@@ -3561,8 +3620,11 @@
 
     TInterfaceBlock *interfaceBlock =
         new TInterfaceBlock(&blockName, fieldList, instanceName, blockLayoutQualifier);
-    TType interfaceBlockType(interfaceBlock, typeQualifier.qualifier, blockLayoutQualifier,
-                             arraySize);
+    TType interfaceBlockType(interfaceBlock, typeQualifier.qualifier, blockLayoutQualifier);
+    if (arrayIndex != nullptr)
+    {
+        interfaceBlockType.makeArray(arraySize);
+    }
 
     TString symbolName = "";
     int symbolId       = 0;
@@ -3768,7 +3830,7 @@
             if (safeIndex < 0)
             {
                 safeIndex = checkIndexOutOfRange(outOfRangeIndexIsError, location, index,
-                                                 baseExpression->getArraySize(),
+                                                 baseExpression->getOutermostArraySize(),
                                                  "array index out of range");
             }
         }
@@ -4408,7 +4470,7 @@
 
     TType *type       = new TType(EbtVoid, EbpUndefined);
     unsigned int size = checkIsValidArraySize(arraySizeLoc, arraySize);
-    type->setArraySize(size);
+    type->makeArray(size);
 
     return new TField(type, identifier, loc);
 }
@@ -4450,46 +4512,36 @@
 }
 
 TFieldList *TParseContext::addStructDeclaratorList(const TPublicType &typeSpecifier,
-                                                   TFieldList *fieldList)
+                                                   TFieldList *declaratorList)
 {
     checkPrecisionSpecified(typeSpecifier.getLine(), typeSpecifier.precision,
                             typeSpecifier.getBasicType());
 
-    checkIsNonVoid(typeSpecifier.getLine(), (*fieldList)[0]->name(), typeSpecifier.getBasicType());
+    checkIsNonVoid(typeSpecifier.getLine(), (*declaratorList)[0]->name(),
+                   typeSpecifier.getBasicType());
 
     checkWorkGroupSizeIsNotSpecified(typeSpecifier.getLine(), typeSpecifier.layoutQualifier);
 
-    for (unsigned int i = 0; i < fieldList->size(); ++i)
+    for (unsigned int i = 0; i < declaratorList->size(); ++i)
     {
-        //
-        // Careful not to replace already known aspects of type, like array-ness
-        //
-        TType *type = (*fieldList)[i]->type();
-        type->setBasicType(typeSpecifier.getBasicType());
-        type->setPrimarySize(typeSpecifier.getPrimarySize());
-        type->setSecondarySize(typeSpecifier.getSecondarySize());
-        type->setPrecision(typeSpecifier.precision);
-        type->setQualifier(typeSpecifier.qualifier);
-        type->setLayoutQualifier(typeSpecifier.layoutQualifier);
-        type->setMemoryQualifier(typeSpecifier.memoryQualifier);
-        type->setInvariant(typeSpecifier.invariant);
-
+        auto declaratorArraySizes = (*declaratorList)[i]->type()->getArraySizes();
         // don't allow arrays of arrays
-        if (type->isArray())
+        if (!declaratorArraySizes.empty())
         {
             checkIsValidTypeForArray(typeSpecifier.getLine(), typeSpecifier);
         }
-        if (typeSpecifier.array)
-            type->setArraySize(static_cast<unsigned int>(typeSpecifier.arraySize));
-        if (typeSpecifier.getUserDef())
+
+        TType *type = (*declaratorList)[i]->type();
+        *type       = TType(typeSpecifier);
+        for (unsigned int arraySize : declaratorArraySizes)
         {
-            type->setStruct(typeSpecifier.getUserDef());
+            type->makeArray(arraySize);
         }
 
-        checkIsBelowStructNestingLimit(typeSpecifier.getLine(), *(*fieldList)[i]);
+        checkIsBelowStructNestingLimit(typeSpecifier.getLine(), *(*declaratorList)[i]);
     }
 
-    return fieldList;
+    return declaratorList;
 }
 
 TTypeSpecifierNonArray TParseContext::addStructure(const TSourceLoc &structLine,
@@ -4800,7 +4852,7 @@
                 return false;
         }
         // At this point, size of implicitly sized arrays should be resolved.
-        if (left->getArraySize() != right->getArraySize())
+        if (left->getType().getArraySizes() != right->getType().getArraySizes())
         {
             error(loc, "array size mismatch", GetOperatorString(op));
             return false;
@@ -5398,7 +5450,7 @@
     }
     else
     {
-        arraySize = typedThis->getArraySize();
+        arraySize = typedThis->getOutermostArraySize();
         if (typedThis->getAsSymbolNode() == nullptr)
         {
             // This code path can be hit with expressions like these:
diff --git a/src/compiler/translator/ParseContext.h b/src/compiler/translator/ParseContext.h
index e9b2c08..52edb4f 100644
--- a/src/compiler/translator/ParseContext.h
+++ b/src/compiler/translator/ParseContext.h
@@ -484,8 +484,12 @@
     void checkBindingIsValid(const TSourceLoc &identifierLocation, const TType &type);
     void checkBindingIsNotSpecified(const TSourceLoc &location, int binding);
     void checkOffsetIsNotSpecified(const TSourceLoc &location, int offset);
-    void checkImageBindingIsValid(const TSourceLoc &location, int binding, int arraySize);
-    void checkSamplerBindingIsValid(const TSourceLoc &location, int binding, int arraySize);
+    void checkImageBindingIsValid(const TSourceLoc &location,
+                                  int binding,
+                                  int arrayTotalElementCount);
+    void checkSamplerBindingIsValid(const TSourceLoc &location,
+                                    int binding,
+                                    int arrayTotalElementCount);
     void checkBlockBindingIsValid(const TSourceLoc &location,
                                   const TQualifier &qualifier,
                                   int binding,
@@ -498,6 +502,10 @@
 
     void checkYuvIsNotSpecified(const TSourceLoc &location, bool yuv);
 
+    bool checkUnsizedArrayConstructorArgumentDimensionality(TIntermSequence *arguments,
+                                                            TType type,
+                                                            const TSourceLoc &line);
+
     TIntermTyped *addBinaryMathInternal(TOperator op,
                                         TIntermTyped *left,
                                         TIntermTyped *right,
diff --git a/src/compiler/translator/StructureHLSL.cpp b/src/compiler/translator/StructureHLSL.cpp
index 6d36a17..1f5199b 100644
--- a/src/compiler/translator/StructureHLSL.cpp
+++ b/src/compiler/translator/StructureHLSL.cpp
@@ -236,7 +236,10 @@
     }
 
     TType ctorType = type;
-    ctorType.clearArrayness();
+    while (ctorType.isArray())
+    {
+        ctorType.toArrayElementType();
+    }
     ctorType.setPrecision(EbpHigh);
     ctorType.setQualifier(EvqTemporary);
 
diff --git a/src/compiler/translator/Types.cpp b/src/compiler/translator/Types.cpp
index d460c86..b252512 100644
--- a/src/compiler/translator/Types.cpp
+++ b/src/compiler/translator/Types.cpp
@@ -122,13 +122,15 @@
       layoutQualifier(p.layoutQualifier),
       primarySize(p.getPrimarySize()),
       secondarySize(p.getSecondarySize()),
-      array(p.array),
-      arraySize(p.arraySize),
       interfaceBlock(0),
       structure(0)
 {
     ASSERT(primarySize <= 4);
     ASSERT(secondarySize <= 4);
+    if (p.array)
+    {
+        makeArray(p.arraySize);
+    }
     if (p.getUserDef())
         structure = p.getUserDef();
 }
@@ -279,8 +281,11 @@
         stream << getQualifierString() << " ";
     if (precision != EbpUndefined)
         stream << getPrecisionString() << " ";
-    if (array)
-        stream << "array[" << getArraySize() << "] of ";
+    for (auto arraySizeIter = mArraySizes.rbegin(); arraySizeIter != mArraySizes.rend();
+         ++arraySizeIter)
+    {
+        stream << "array[" << (*arraySizeIter) << "] of ";
+    }
     if (isMatrix())
         stream << getCols() << "X" << getRows() << " matrix of ";
     else if (isVector())
@@ -442,7 +447,7 @@
         mangledName += static_cast<char>('0' + getNominalSize());
     }
 
-    if (isArray())
+    for (unsigned int arraySize : mArraySizes)
     {
         char buf[20];
         snprintf(buf, sizeof(buf), "%d", arraySize);
@@ -462,16 +467,15 @@
     else
         totalSize = primarySize * secondarySize;
 
-    if (isArray())
-    {
-        if (totalSize == 0)
-            return 0;
+    if (totalSize == 0)
+        return 0;
 
-        size_t currentArraySize = getArraySize();
-        if (currentArraySize > INT_MAX / totalSize)
+    for (size_t arraySize : mArraySizes)
+    {
+        if (arraySize > INT_MAX / totalSize)
             totalSize = INT_MAX;
         else
-            totalSize *= currentArraySize;
+            totalSize *= arraySize;
     }
 
     return totalSize;
@@ -486,27 +490,93 @@
         count = structure->getLocationCount();
     }
 
-    if (isArray())
+    if (count == 0)
     {
-        if (count == 0)
-        {
-            return 0;
-        }
+        return 0;
+    }
 
-        unsigned int currentArraySize = getArraySize();
-        if (currentArraySize > static_cast<unsigned int>(std::numeric_limits<int>::max() / count))
+    for (unsigned int arraySize : mArraySizes)
+    {
+        if (arraySize > static_cast<unsigned int>(std::numeric_limits<int>::max() / count))
         {
             count = std::numeric_limits<int>::max();
         }
         else
         {
-            count *= static_cast<int>(currentArraySize);
+            count *= static_cast<int>(arraySize);
         }
     }
 
     return count;
 }
 
+unsigned int TType::getArraySizeProduct() const
+{
+    unsigned int product = 1u;
+    for (unsigned int arraySize : mArraySizes)
+    {
+        product *= arraySize;
+    }
+    return product;
+}
+
+bool TType::isUnsizedArray() const
+{
+    for (unsigned int arraySize : mArraySizes)
+    {
+        if (arraySize == 0u)
+        {
+            return true;
+        }
+    }
+    return false;
+}
+
+bool TType::sameNonArrayType(const TType &right) const
+{
+    return (type == right.type && primarySize == right.primarySize &&
+            secondarySize == right.secondarySize && structure == right.structure);
+}
+
+bool TType::isElementTypeOf(const TType &arrayType) const
+{
+    if (!sameNonArrayType(arrayType))
+    {
+        return false;
+    }
+    if (arrayType.mArraySizes.size() != mArraySizes.size() + 1u)
+    {
+        return false;
+    }
+    for (size_t i = 0; i < mArraySizes.size(); ++i)
+    {
+        if (mArraySizes[i] != arrayType.mArraySizes[i])
+        {
+            return false;
+        }
+    }
+    return true;
+}
+
+void TType::sizeUnsizedArrays(const TVector<unsigned int> &arraySizes)
+{
+    for (size_t i = 0u; i < mArraySizes.size(); ++i)
+    {
+        if (mArraySizes[i] == 0)
+        {
+            if (i < arraySizes.size())
+            {
+                mArraySizes[i] = arraySizes[i];
+            }
+            else
+            {
+                mArraySizes[i] = 1u;
+            }
+        }
+    }
+    invalidateMangledName();
+}
+
 TStructure::TStructure(TSymbolTable *symbolTable, const TString *name, TFieldList *fields)
     : TFieldListCollection(name, fields),
       mDeepestNesting(0),
@@ -558,8 +628,8 @@
         if (isArray())
         {
             TType elementType(*this);
-            elementType.clearArrayness();
-            for (unsigned int arrayIndex = 0u; arrayIndex < getArraySize(); ++arrayIndex)
+            elementType.toArrayElementType();
+            for (unsigned int arrayIndex = 0u; arrayIndex < getOutermostArraySize(); ++arrayIndex)
             {
                 TStringStream elementName;
                 elementName << namePrefix << "_" << arrayIndex;
diff --git a/src/compiler/translator/Types.h b/src/compiler/translator/Types.h
index 4450a0b..eaa5479 100644
--- a/src/compiler/translator/Types.h
+++ b/src/compiler/translator/Types.h
@@ -193,8 +193,6 @@
           layoutQualifier(TLayoutQualifier::create()),
           primarySize(0),
           secondarySize(0),
-          array(false),
-          arraySize(0),
           interfaceBlock(nullptr),
           structure(nullptr)
     {
@@ -208,8 +206,6 @@
           layoutQualifier(TLayoutQualifier::create()),
           primarySize(ps),
           secondarySize(ss),
-          array(false),
-          arraySize(0),
           interfaceBlock(0),
           structure(0)
     {
@@ -218,8 +214,7 @@
           TPrecision p,
           TQualifier q     = EvqTemporary,
           unsigned char ps = 1,
-          unsigned char ss = 1,
-          bool a           = false)
+          unsigned char ss = 1)
         : type(t),
           precision(p),
           qualifier(q),
@@ -228,8 +223,6 @@
           layoutQualifier(TLayoutQualifier::create()),
           primarySize(ps),
           secondarySize(ss),
-          array(a),
-          arraySize(0),
           interfaceBlock(0),
           structure(0)
     {
@@ -244,16 +237,13 @@
           layoutQualifier(TLayoutQualifier::create()),
           primarySize(1),
           secondarySize(1),
-          array(false),
-          arraySize(0),
           interfaceBlock(0),
           structure(userDef)
     {
     }
     TType(TInterfaceBlock *interfaceBlockIn,
           TQualifier qualifierIn,
-          TLayoutQualifier layoutQualifierIn,
-          unsigned int arraySizeIn)
+          TLayoutQualifier layoutQualifierIn)
         : type(EbtInterfaceBlock),
           precision(EbpUndefined),
           qualifier(qualifierIn),
@@ -262,8 +252,6 @@
           layoutQualifier(layoutQualifierIn),
           primarySize(1),
           secondarySize(1),
-          array(arraySizeIn > 0),
-          arraySize(arraySizeIn),
           interfaceBlock(interfaceBlockIn),
           structure(0)
     {
@@ -337,25 +325,38 @@
 
     bool isMatrix() const { return primarySize > 1 && secondarySize > 1; }
     bool isNonSquareMatrix() const { return isMatrix() && primarySize != secondarySize; }
-    bool isArray() const { return array; }
-    bool isUnsizedArray() const { return array && arraySize == 0u; }
-    unsigned int getArraySize() const { return arraySize; }
-    void setArraySize(unsigned int s)
+    bool isArray() const { return !mArraySizes.empty(); }
+    bool isArrayOfArrays() const { return mArraySizes.size() > 1u; }
+    const TVector<unsigned int> &getArraySizes() const { return mArraySizes; }
+    unsigned int getArraySizeProduct() const;
+    bool isUnsizedArray() const;
+    unsigned int getOutermostArraySize() const { return mArraySizes.back(); }
+    void makeArray(unsigned int s)
     {
-        if (!array || arraySize != s)
+        mArraySizes.push_back(s);
+        invalidateMangledName();
+    }
+    // Here, the array dimension value 0 corresponds to the innermost array.
+    void setArraySize(size_t arrayDimension, unsigned int s)
+    {
+        ASSERT(arrayDimension < mArraySizes.size());
+        if (mArraySizes.at(arrayDimension) != s)
         {
-            array     = true;
-            arraySize = s;
+            mArraySizes[arrayDimension] = s;
             invalidateMangledName();
         }
     }
-    void setArrayUnsized() { setArraySize(0u); }
-    void clearArrayness()
+
+    // Will set unsized array sizes according to arraySizes. In case there are more unsized arrays
+    // than there are sizes in arraySizes, defaults to setting array sizes to 1.
+    void sizeUnsizedArrays(const TVector<unsigned int> &arraySizes);
+
+    // Note that the array element type might still be an array type in GLSL ES version >= 3.10.
+    void toArrayElementType()
     {
-        if (array)
+        if (mArraySizes.size() > 0)
         {
-            array     = false;
-            arraySize = 0u;
+            mArraySizes.pop_back();
             invalidateMangledName();
         }
     }
@@ -372,7 +373,10 @@
     bool isInterfaceBlock() const { return type == EbtInterfaceBlock; }
 
     bool isVector() const { return primarySize > 1 && secondarySize == 1; }
-    bool isScalar() const { return primarySize == 1 && secondarySize == 1 && !structure && !array; }
+    bool isScalar() const
+    {
+        return primarySize == 1 && secondarySize == 1 && !structure && !isArray();
+    }
     bool isScalarFloat() const { return isScalar() && type == EbtFloat; }
     bool isScalarInt() const { return isScalar() && (type == EbtInt || type == EbtUInt); }
 
@@ -399,16 +403,16 @@
         return mangled;
     }
 
-    bool sameElementType(const TType &right) const
-    {
-        return type == right.type && primarySize == right.primarySize &&
-               secondarySize == right.secondarySize && structure == right.structure;
-    }
+    bool sameNonArrayType(const TType &right) const;
+
+    // Returns true if arrayType is an array made of this type.
+    bool isElementTypeOf(const TType &arrayType) const;
+
     bool operator==(const TType &right) const
     {
         return type == right.type && primarySize == right.primarySize &&
-               secondarySize == right.secondarySize && array == right.array &&
-               (!array || arraySize == right.arraySize) && structure == right.structure;
+               secondarySize == right.secondarySize && mArraySizes == right.mArraySizes &&
+               structure == right.structure;
         // don't check the qualifier, it's not ever what's being sought after
     }
     bool operator!=(const TType &right) const { return !operator==(right); }
@@ -420,10 +424,13 @@
             return primarySize < right.primarySize;
         if (secondarySize != right.secondarySize)
             return secondarySize < right.secondarySize;
-        if (array != right.array)
-            return array < right.array;
-        if (arraySize != right.arraySize)
-            return arraySize < right.arraySize;
+        if (mArraySizes.size() != right.mArraySizes.size())
+            return mArraySizes.size() < right.mArraySizes.size();
+        for (size_t i = 0; i < mArraySizes.size(); ++i)
+        {
+            if (mArraySizes[i] != right.mArraySizes[i])
+                return mArraySizes[i] < right.mArraySizes[i];
+        }
         if (structure != right.structure)
             return structure < right.structure;
 
@@ -488,8 +495,10 @@
     TLayoutQualifier layoutQualifier;
     unsigned char primarySize;    // size of vector or cols matrix
     unsigned char secondarySize;  // rows of a matrix
-    bool array;
-    unsigned int arraySize;
+
+    // Used to make an array type. Outermost array size is stored at the end of the vector. Having 0
+    // in this vector means an unsized array.
+    TVector<unsigned int> mArraySizes;
 
     // This is set only in the following two cases:
     // 1) Represents an interface block.
diff --git a/src/compiler/translator/UniformHLSL.cpp b/src/compiler/translator/UniformHLSL.cpp
index 8932301..07badbb 100644
--- a/src/compiler/translator/UniformHLSL.cpp
+++ b/src/compiler/translator/UniformHLSL.cpp
@@ -18,6 +18,9 @@
 namespace sh
 {
 
+namespace
+{
+
 static const char *UniformRegisterPrefix(const TType &type)
 {
     if (IsSampler(type.getBasicType()))
@@ -61,6 +64,34 @@
     return DecoratePrivate(interfaceBlock.name()) + "_type";
 }
 
+void OutputSamplerIndexArrayInitializer(TInfoSinkBase &out,
+                                        const TType &type,
+                                        unsigned int startIndex)
+{
+    out << "{";
+    TType elementType(type);
+    elementType.toArrayElementType();
+    for (unsigned int i = 0u; i < type.getOutermostArraySize(); ++i)
+    {
+        if (i > 0u)
+        {
+            out << ", ";
+        }
+        if (elementType.isArray())
+        {
+            OutputSamplerIndexArrayInitializer(out, elementType,
+                                               startIndex + i * elementType.getArraySizeProduct());
+        }
+        else
+        {
+            out << (startIndex + i);
+        }
+    }
+    out << "}";
+}
+
+}  // anonymous namespace
+
 UniformHLSL::UniformHLSL(StructureHLSL *structureHLSL,
                          ShShaderOutput outputType,
                          const std::vector<Uniform> &uniforms)
@@ -133,7 +164,7 @@
     ASSERT(IsSampler(type.getBasicType()));
     unsigned int registerIndex                     = mSamplerRegister;
     mUniformRegisterMap[std::string(name.c_str())] = registerIndex;
-    unsigned int registerCount                     = type.isArray() ? type.getArraySize() : 1u;
+    unsigned int registerCount = type.isArray() ? type.getArraySizeProduct() : 1u;
     mSamplerRegister += registerCount;
     if (outRegisterCount)
     {
@@ -179,14 +210,9 @@
         if (type.isArray())
         {
             out << "static const uint " << DecorateVariableIfNeeded(uniform->getName())
-                << ArrayString(type) << " = {";
-            for (unsigned int i = 0u; i < type.getArraySize(); ++i)
-            {
-                if (i > 0u)
-                    out << ", ";
-                out << (samplerArrayIndex + i);
-            }
-            out << "};\n";
+                << ArrayString(type) << " = ";
+            OutputSamplerIndexArrayInitializer(out, type, samplerArrayIndex);
+            out << ";\n";
         }
         else
         {
@@ -365,8 +391,11 @@
 
         // nodeType.isInterfaceBlock() == false means the node is a field of a uniform block which
         // doesn't have instance name, so this block cannot be an array.
-        unsigned int interfaceBlockArraySize =
-            nodeType.isInterfaceBlock() ? nodeType.getArraySize() : 0;
+        unsigned int interfaceBlockArraySize = 0u;
+        if (nodeType.isInterfaceBlock() && nodeType.isArray())
+        {
+            interfaceBlockArraySize = nodeType.getOutermostArraySize();
+        }
         unsigned int activeRegister = mUniformBlockRegister;
 
         mUniformBlockRegisterMap[interfaceBlock.name().c_str()] = activeRegister;
diff --git a/src/compiler/translator/ValidateOutputs.cpp b/src/compiler/translator/ValidateOutputs.cpp
index 9ac9c68..e4ff46f 100644
--- a/src/compiler/translator/ValidateOutputs.cpp
+++ b/src/compiler/translator/ValidateOutputs.cpp
@@ -95,7 +95,9 @@
     for (const auto &symbol : mOutputs)
     {
         const TType &type         = symbol->getType();
-        const size_t elementCount = static_cast<size_t>(type.isArray() ? type.getArraySize() : 1u);
+        ASSERT(!type.isArrayOfArrays());  // Disallowed in GLSL ES 3.10 section 4.3.6.
+        const size_t elementCount =
+            static_cast<size_t>(type.isArray() ? type.getOutermostArraySize() : 1u);
         const size_t location     = static_cast<size_t>(type.getLayoutQualifier().location);
 
         ASSERT(type.getLayoutQualifier().location != -1);
diff --git a/src/compiler/translator/util.cpp b/src/compiler/translator/util.cpp
index 2336a7b..22f4ea9 100644
--- a/src/compiler/translator/util.cpp
+++ b/src/compiler/translator/util.cpp
@@ -437,12 +437,14 @@
 
 TString ArrayString(const TType &type)
 {
-    if (!type.isArray())
+    TStringStream arrayString;
+    const TVector<unsigned int> &arraySizes = type.getArraySizes();
+    for (auto arraySizeIter = arraySizes.rbegin(); arraySizeIter != arraySizes.rend();
+         ++arraySizeIter)
     {
-        return "";
+        arrayString << "[" << (*arraySizeIter) << "]";
     }
-
-    return "[" + str(type.getArraySize()) + "]";
+    return arrayString.str();
 }
 
 bool IsVaryingOut(TQualifier qualifier)
diff --git a/src/compiler/translator/util.h b/src/compiler/translator/util.h
index 453b1bc..dd22a70 100644
--- a/src/compiler/translator/util.h
+++ b/src/compiler/translator/util.h
@@ -39,6 +39,9 @@
 bool IsVaryingOut(TQualifier qualifier);
 bool IsVarying(TQualifier qualifier);
 InterpolationType GetInterpolationType(TQualifier qualifier);
+
+// Returns array brackets including size with outermost array size first, as specified in GLSL ES
+// 3.10 section 4.1.9.
 TString ArrayString(const TType &type);
 
 TType GetShaderVariableBasicType(const sh::ShaderVariable &var);
diff --git a/src/tests/compiler_tests/InitOutputVariables_test.cpp b/src/tests/compiler_tests/InitOutputVariables_test.cpp
index d547e69..8cf9aab 100644
--- a/src/tests/compiler_tests/InitOutputVariables_test.cpp
+++ b/src/tests/compiler_tests/InitOutputVariables_test.cpp
@@ -71,7 +71,7 @@
                                             unsigned arraySize)
 {
     ASSERT(elementType.isArray() == false);
-    elementType.setArraySize(arraySize);
+    elementType.makeArray(arraySize);
 
     ExpectedLValues expected(arraySize);
     for (unsigned index = 0u; index < arraySize; ++index)