Initialize uninitialized GLSL arrays in a for loop

Previously, creating nodes for initializing each single array element
could result in memory bloat during translation when dealing with
large arrays. The resulting shader could also end up very long.
Initialize most arrays using a simple for loop instead. The loop is
compatible with ESSL 1.00 Appendix A limitations.

An exception is made for fragment outputs, so that they are not
indexed by non-constant values.

On some platforms using the a loop to initialize variables can cause
problems, so we also have a compiler flag for turning this behavior
off. The flag was already added earlier for a staggered rollout of
this functionality.

BUG=chromium:735497
TEST=angle_unittests, angle_end2end_tests, WebGL conformance tests

Change-Id: Iec727821d8137db56b440ddbe007879b1b55f61f
Reviewed-on: https://chromium-review.googlesource.com/707195
Commit-Queue: Olli Etuaho <oetuaho@nvidia.com>
Reviewed-by: Corentin Wallez <cwallez@chromium.org>
Reviewed-by: Jamie Madill <jmadill@chromium.org>
diff --git a/src/compiler/translator/Compiler.cpp b/src/compiler/translator/Compiler.cpp
index 7b3418e..4784171 100644
--- a/src/compiler/translator/Compiler.cpp
+++ b/src/compiler/translator/Compiler.cpp
@@ -595,7 +595,9 @@
     // on ESSL >= 3.00, and the initializers that need to be deferred can only exist in ESSL < 3.00.
     bool initializeLocalsAndGlobals =
         (compileOptions & SH_INITIALIZE_UNINITIALIZED_LOCALS) && !IsOutputHLSL(getOutputType());
-    DeferGlobalInitializers(root, initializeLocalsAndGlobals, &symbolTable);
+    bool canUseLoopsToInitialize = !(compileOptions & SH_DONT_USE_LOOPS_TO_INITIALIZE_VARIABLES);
+    DeferGlobalInitializers(root, initializeLocalsAndGlobals, canUseLoopsToInitialize, &symbolTable);
+
 
     if (initializeLocalsAndGlobals)
     {
@@ -615,7 +617,8 @@
                                    &getSymbolTable(), getShaderVersion());
         }
 
-        InitializeUninitializedLocals(root, getShaderVersion());
+        InitializeUninitializedLocals(root, getShaderVersion(), canUseLoopsToInitialize,
+                                      &getSymbolTable());
     }
 
     if (getShaderType() == GL_VERTEX_SHADER && (compileOptions & SH_CLAMP_POINT_SIZE))
@@ -1055,7 +1058,7 @@
     sh::ShaderVariable var(GL_FLOAT_VEC4);
     var.name = "gl_Position";
     list.push_back(var);
-    InitializeVariables(root, list, symbolTable, shaderVersion, extensionBehavior);
+    InitializeVariables(root, list, &symbolTable, shaderVersion, extensionBehavior, false);
 }
 
 void TCompiler::useAllMembersInUnusedStandardAndSharedBlocks(TIntermBlock *root)
@@ -1097,7 +1100,7 @@
             list.push_back(var);
         }
     }
-    InitializeVariables(root, list, symbolTable, shaderVersion, extensionBehavior);
+    InitializeVariables(root, list, &symbolTable, shaderVersion, extensionBehavior, false);
 }
 
 const TExtensionBehavior &TCompiler::getExtensionBehavior() const
diff --git a/src/compiler/translator/DeferGlobalInitializers.cpp b/src/compiler/translator/DeferGlobalInitializers.cpp
index 5161d51..67d51ea 100644
--- a/src/compiler/translator/DeferGlobalInitializers.cpp
+++ b/src/compiler/translator/DeferGlobalInitializers.cpp
@@ -29,7 +29,9 @@
 
 void GetDeferredInitializers(TIntermDeclaration *declaration,
                              bool initializeUninitializedGlobals,
-                             TIntermSequence *deferredInitializersOut)
+                             bool canUseLoopsToInitialize,
+                             TIntermSequence *deferredInitializersOut,
+                             TSymbolTable *symbolTable)
 {
     // SeparateDeclarations should have already been run.
     ASSERT(declaration->getSequence()->size() == 1);
@@ -81,7 +83,8 @@
 
         if (symbolNode->getQualifier() == EvqGlobal && symbolNode->getSymbol() != "")
         {
-            TIntermSequence *initCode = CreateInitCode(symbolNode);
+            TIntermSequence *initCode =
+                CreateInitCode(symbolNode, canUseLoopsToInitialize, symbolTable);
             deferredInitializersOut->insert(deferredInitializersOut->end(), initCode->begin(),
                                             initCode->end());
         }
@@ -117,6 +120,7 @@
 
 void DeferGlobalInitializers(TIntermBlock *root,
                              bool initializeUninitializedGlobals,
+                             bool canUseLoopsToInitialize,
                              TSymbolTable *symbolTable)
 {
     TIntermSequence *deferredInitializers = new TIntermSequence();
@@ -129,7 +133,7 @@
         if (declaration)
         {
             GetDeferredInitializers(declaration, initializeUninitializedGlobals,
-                                    deferredInitializers);
+                                    canUseLoopsToInitialize, deferredInitializers, symbolTable);
         }
     }
 
diff --git a/src/compiler/translator/DeferGlobalInitializers.h b/src/compiler/translator/DeferGlobalInitializers.h
index 18ec66e..86930a5 100644
--- a/src/compiler/translator/DeferGlobalInitializers.h
+++ b/src/compiler/translator/DeferGlobalInitializers.h
@@ -24,6 +24,7 @@
 
 void DeferGlobalInitializers(TIntermBlock *root,
                              bool initializeUninitializedGlobals,
+                             bool canUseLoopsToInitialize,
                              TSymbolTable *symbolTable);
 
 }  // namespace sh
diff --git a/src/compiler/translator/InitializeVariables.cpp b/src/compiler/translator/InitializeVariables.cpp
index 51f2cc2..aa1a042 100644
--- a/src/compiler/translator/InitializeVariables.cpp
+++ b/src/compiler/translator/InitializeVariables.cpp
@@ -21,7 +21,14 @@
 {
 
 void AddArrayZeroInitSequence(const TIntermTyped *initializedNode,
-                              TIntermSequence *initSequenceOut);
+                              bool canUseLoopsToInitialize,
+                              TIntermSequence *initSequenceOut,
+                              TSymbolTable *symbolTable);
+
+void AddStructZeroInitSequence(const TIntermTyped *initializedNode,
+                               bool canUseLoopsToInitialize,
+                               TIntermSequence *initSequenceOut,
+                               TSymbolTable *symbolTable);
 
 TIntermBinary *CreateZeroInitAssignment(const TIntermTyped *initializedNode)
 {
@@ -29,8 +36,32 @@
     return new TIntermBinary(EOpAssign, initializedNode->deepCopy(), zero);
 }
 
+void AddZeroInitSequence(const TIntermTyped *initializedNode,
+                         bool canUseLoopsToInitialize,
+                         TIntermSequence *initSequenceOut,
+                         TSymbolTable *symbolTable)
+{
+    if (initializedNode->isArray())
+    {
+        AddArrayZeroInitSequence(initializedNode, canUseLoopsToInitialize, initSequenceOut,
+                                 symbolTable);
+    }
+    else if (initializedNode->getType().isStructureContainingArrays() ||
+             initializedNode->getType().isNamelessStruct())
+    {
+        AddStructZeroInitSequence(initializedNode, canUseLoopsToInitialize, initSequenceOut,
+                                  symbolTable);
+    }
+    else
+    {
+        initSequenceOut->push_back(CreateZeroInitAssignment(initializedNode));
+    }
+}
+
 void AddStructZeroInitSequence(const TIntermTyped *initializedNode,
-                               TIntermSequence *initSequenceOut)
+                               bool canUseLoopsToInitialize,
+                               TIntermSequence *initSequenceOut,
+                               TSymbolTable *symbolTable)
 {
     ASSERT(initializedNode->getBasicType() == EbtStruct);
     const TStructure *structType = initializedNode->getType().getStruct();
@@ -38,55 +69,87 @@
     {
         TIntermBinary *element = new TIntermBinary(EOpIndexDirectStruct,
                                                    initializedNode->deepCopy(), CreateIndexNode(i));
-        if (element->isArray())
-        {
-            AddArrayZeroInitSequence(element, initSequenceOut);
-        }
-        else if (element->getType().isStructureContainingArrays())
-        {
-            AddStructZeroInitSequence(element, initSequenceOut);
-        }
-        else
-        {
-            // Structs can't be defined inside structs, so the type of a struct field can't be a
-            // nameless struct.
-            ASSERT(!element->getType().isNamelessStruct());
-            initSequenceOut->push_back(CreateZeroInitAssignment(element));
-        }
+        // Structs can't be defined inside structs, so the type of a struct field can't be a
+        // nameless struct.
+        ASSERT(!element->getType().isNamelessStruct());
+        AddZeroInitSequence(element, canUseLoopsToInitialize, initSequenceOut, symbolTable);
     }
 }
 
-void AddArrayZeroInitSequence(const TIntermTyped *initializedNode, TIntermSequence *initSequenceOut)
+void AddArrayZeroInitStatementList(const TIntermTyped *initializedNode,
+                                   bool canUseLoopsToInitialize,
+                                   TIntermSequence *initSequenceOut,
+                                   TSymbolTable *symbolTable)
 {
-    ASSERT(initializedNode->isArray());
-    // Assign the array elements one by one to keep the AST compatible with ESSL 1.00 which
-    // 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->getOutermostArraySize(); ++i)
     {
         TIntermBinary *element =
             new TIntermBinary(EOpIndexDirect, initializedNode->deepCopy(), CreateIndexNode(i));
-        if (element->isArray())
-        {
-            AddArrayZeroInitSequence(element, initSequenceOut);
-        }
-        else if (element->getType().isStructureContainingArrays())
-        {
-            AddStructZeroInitSequence(element, initSequenceOut);
-        }
-        else
-        {
-            initSequenceOut->push_back(CreateZeroInitAssignment(element));
-        }
+        AddZeroInitSequence(element, canUseLoopsToInitialize, initSequenceOut, symbolTable);
+    }
+}
+
+void AddArrayZeroInitForLoop(const TIntermTyped *initializedNode,
+                             TIntermSequence *initSequenceOut,
+                             TSymbolTable *symbolTable)
+{
+    ASSERT(initializedNode->isArray());
+    TSymbolUniqueId indexSymbol(symbolTable);
+
+    TIntermSymbol *indexSymbolNode = CreateTempSymbolNode(indexSymbol, TType(EbtInt), EvqTemporary);
+    TIntermDeclaration *indexInit =
+        CreateTempInitDeclarationNode(indexSymbol, CreateZeroNode(TType(EbtInt)), EvqTemporary);
+    TIntermConstantUnion *arraySizeNode = CreateIndexNode(initializedNode->getOutermostArraySize());
+    TIntermBinary *indexSmallerThanSize =
+        new TIntermBinary(EOpLessThan, indexSymbolNode->deepCopy(), arraySizeNode);
+    TIntermUnary *indexIncrement = new TIntermUnary(EOpPreIncrement, indexSymbolNode->deepCopy());
+
+    TIntermBlock *forLoopBody       = new TIntermBlock();
+    TIntermSequence *forLoopBodySeq = forLoopBody->getSequence();
+
+    TIntermBinary *element = new TIntermBinary(EOpIndexIndirect, initializedNode->deepCopy(),
+                                               indexSymbolNode->deepCopy());
+    AddZeroInitSequence(element, true, forLoopBodySeq, symbolTable);
+
+    TIntermLoop *forLoop =
+        new TIntermLoop(ELoopFor, indexInit, indexSmallerThanSize, indexIncrement, forLoopBody);
+    initSequenceOut->push_back(forLoop);
+}
+
+void AddArrayZeroInitSequence(const TIntermTyped *initializedNode,
+                              bool canUseLoopsToInitialize,
+                              TIntermSequence *initSequenceOut,
+                              TSymbolTable *symbolTable)
+{
+    // The array elements are assigned one by one to keep the AST compatible with ESSL 1.00 which
+    // doesn't have array assignment. We'll do this either with a for loop or just a list of
+    // statements assigning to each array index. Note that it is important to have the array init in
+    // the right order to workaround http://crbug.com/709317
+    bool isSmallArray = initializedNode->getOutermostArraySize() <= 1u ||
+                        (initializedNode->getBasicType() != EbtStruct &&
+                         !initializedNode->getType().isArrayOfArrays() &&
+                         initializedNode->getOutermostArraySize() <= 3u);
+    if (initializedNode->getQualifier() == EvqFragData ||
+        initializedNode->getQualifier() == EvqFragmentOut || isSmallArray ||
+        !canUseLoopsToInitialize)
+    {
+        // Fragment outputs should not be indexed by non-constant indices.
+        // Also it doesn't make sense to use loops to initialize very small arrays.
+        AddArrayZeroInitStatementList(initializedNode, canUseLoopsToInitialize, initSequenceOut,
+                                      symbolTable);
+    }
+    else
+    {
+        AddArrayZeroInitForLoop(initializedNode, initSequenceOut, symbolTable);
     }
 }
 
 void InsertInitCode(TIntermSequence *mainBody,
                     const InitVariableList &variables,
-                    const TSymbolTable &symbolTable,
+                    TSymbolTable *symbolTable,
                     int shaderVersion,
-                    const TExtensionBehavior &extensionBehavior)
+                    const TExtensionBehavior &extensionBehavior,
+                    bool canUseLoopsToInitialize)
 {
     for (const auto &var : variables)
     {
@@ -100,7 +163,7 @@
         TIntermTyped *initializedSymbol = nullptr;
         if (var.isBuiltIn())
         {
-            initializedSymbol = ReferenceBuiltInVariable(name, symbolTable, shaderVersion);
+            initializedSymbol = ReferenceBuiltInVariable(name, *symbolTable, shaderVersion);
             if (initializedSymbol->getQualifier() == EvqFragData &&
                 !IsExtensionEnabled(extensionBehavior, TExtension::EXT_draw_buffers))
             {
@@ -116,11 +179,12 @@
         }
         else
         {
-            initializedSymbol = ReferenceGlobalVariable(name, symbolTable);
+            initializedSymbol = ReferenceGlobalVariable(name, *symbolTable);
         }
         ASSERT(initializedSymbol != nullptr);
 
-        TIntermSequence *initCode = CreateInitCode(initializedSymbol);
+        TIntermSequence *initCode =
+            CreateInitCode(initializedSymbol, canUseLoopsToInitialize, symbolTable);
         mainBody->insert(mainBody->begin(), initCode->begin(), initCode->end());
     }
 }
@@ -128,8 +192,12 @@
 class InitializeLocalsTraverser : public TIntermTraverser
 {
   public:
-    InitializeLocalsTraverser(int shaderVersion)
-        : TIntermTraverser(true, false, false), mShaderVersion(shaderVersion)
+    InitializeLocalsTraverser(int shaderVersion,
+                              TSymbolTable *symbolTable,
+                              bool canUseLoopsToInitialize)
+        : TIntermTraverser(true, false, false, symbolTable),
+          mShaderVersion(shaderVersion),
+          mCanUseLoopsToInitialize(canUseLoopsToInitialize)
     {
     }
 
@@ -154,6 +222,9 @@
                     mShaderVersion == 100;
                 // Nameless struct constructors can't be referred to, so they also need to be
                 // initialized one element at a time.
+                // TODO(oetuaho): Check if it makes sense to initialize using a loop, even if we
+                // could use an initializer. It could at least reduce code size for very large
+                // arrays, but could hurt runtime performance.
                 if (arrayConstructorUnavailable || symbol->getType().isNamelessStruct())
                 {
                     // SimplifyLoopConditions should have been run so the parent node of this node
@@ -163,7 +234,9 @@
                     // about further declarators in this declaration depending on the effects of
                     // this declarator.
                     ASSERT(node->getSequence()->size() == 1);
-                    insertStatementsInParentBlock(TIntermSequence(), *CreateInitCode(symbol));
+                    insertStatementsInParentBlock(
+                        TIntermSequence(),
+                        *CreateInitCode(symbol, mCanUseLoopsToInitialize, mSymbolTable));
                 }
                 else
                 {
@@ -178,44 +251,40 @@
 
   private:
     int mShaderVersion;
+    bool mCanUseLoopsToInitialize;
 };
 
 }  // namespace anonymous
 
-TIntermSequence *CreateInitCode(const TIntermTyped *initializedSymbol)
+TIntermSequence *CreateInitCode(const TIntermTyped *initializedSymbol,
+                                bool canUseLoopsToInitialize,
+                                TSymbolTable *symbolTable)
 {
     TIntermSequence *initCode = new TIntermSequence();
-    if (initializedSymbol->isArray())
-    {
-        AddArrayZeroInitSequence(initializedSymbol, initCode);
-    }
-    else if (initializedSymbol->getType().isStructureContainingArrays() ||
-             initializedSymbol->getType().isNamelessStruct())
-    {
-        AddStructZeroInitSequence(initializedSymbol, initCode);
-    }
-    else
-    {
-        initCode->push_back(CreateZeroInitAssignment(initializedSymbol));
-    }
+    AddZeroInitSequence(initializedSymbol, canUseLoopsToInitialize, initCode, symbolTable);
     return initCode;
 }
 
-void InitializeUninitializedLocals(TIntermBlock *root, int shaderVersion)
+void InitializeUninitializedLocals(TIntermBlock *root,
+                                   int shaderVersion,
+                                   bool canUseLoopsToInitialize,
+                                   TSymbolTable *symbolTable)
 {
-    InitializeLocalsTraverser traverser(shaderVersion);
+    InitializeLocalsTraverser traverser(shaderVersion, symbolTable, canUseLoopsToInitialize);
     root->traverse(&traverser);
     traverser.updateTree();
 }
 
 void InitializeVariables(TIntermBlock *root,
                          const InitVariableList &vars,
-                         const TSymbolTable &symbolTable,
+                         TSymbolTable *symbolTable,
                          int shaderVersion,
-                         const TExtensionBehavior &extensionBehavior)
+                         const TExtensionBehavior &extensionBehavior,
+                         bool canUseLoopsToInitialize)
 {
     TIntermBlock *body = FindMainBody(root);
-    InsertInitCode(body->getSequence(), vars, symbolTable, shaderVersion, extensionBehavior);
+    InsertInitCode(body->getSequence(), vars, symbolTable, shaderVersion, extensionBehavior,
+                   canUseLoopsToInitialize);
 }
 
 }  // namespace sh
diff --git a/src/compiler/translator/InitializeVariables.h b/src/compiler/translator/InitializeVariables.h
index 536d0cc..1a7b3c0 100644
--- a/src/compiler/translator/InitializeVariables.h
+++ b/src/compiler/translator/InitializeVariables.h
@@ -18,12 +18,20 @@
 
 typedef std::vector<sh::ShaderVariable> InitVariableList;
 
+// For all of the functions below: If canUseLoopsToInitialize is set, for loops are used instead of
+// a large number of initializers where it can make sense, such as for initializing large arrays.
+
 // Return a sequence of assignment operations to initialize "initializedSymbol". initializedSymbol
 // may be an array, struct or any combination of these, as long as it contains only basic types.
-TIntermSequence *CreateInitCode(const TIntermTyped *initializedSymbol);
+TIntermSequence *CreateInitCode(const TIntermTyped *initializedSymbol,
+                                bool canUseLoopsToInitialize,
+                                TSymbolTable *symbolTable);
 
 // Initialize all uninitialized local variables, so that undefined behavior is avoided.
-void InitializeUninitializedLocals(TIntermBlock *root, int shaderVersion);
+void InitializeUninitializedLocals(TIntermBlock *root,
+                                   int shaderVersion,
+                                   bool canUseLoopsToInitialize,
+                                   TSymbolTable *symbolTable);
 
 // This function can initialize all the types that CreateInitCode is able to initialize. All
 // variables must be globals which can be found in the symbol table. For now it is used for the
@@ -35,9 +43,10 @@
 // enabled extensions.
 void InitializeVariables(TIntermBlock *root,
                          const InitVariableList &vars,
-                         const TSymbolTable &symbolTable,
+                         TSymbolTable *symbolTable,
                          int shaderVersion,
-                         const TExtensionBehavior &extensionBehavior);
+                         const TExtensionBehavior &extensionBehavior,
+                         bool canUseLoopsToInitialize);
 
 }  // namespace sh