Defer executing if statements in the global scope

Unfolding of short-circuiting operators (ternary and logical operators) may
create if statements in the global scope, which is not valid HLSL. Use existing
deferred global initialization function to defer execution of if statements in
the global scope.

TEST=WebGL conformance tests
BUG=angleproject:819

Change-Id: I2b0afcc6824dab6bb87eb6abed609e75b1384dab
Reviewed-on: https://chromium-review.googlesource.com/270461
Reviewed-by: Jamie Madill <jmadill@chromium.org>
Tested-by: Olli Etuaho <oetuaho@nvidia.com>
diff --git a/src/compiler/translator/OutputHLSL.cpp b/src/compiler/translator/OutputHLSL.cpp
index 903f6e3..e0d3ce1 100644
--- a/src/compiler/translator/OutputHLSL.cpp
+++ b/src/compiler/translator/OutputHLSL.cpp
@@ -1502,8 +1502,12 @@
             if (symbolNode->getQualifier() == EvqGlobal && expression->getQualifier() != EvqConst)
             {
                 // For variables which are not constant, defer their real initialization until
-                // after we initialize other globals: uniforms, attributes and varyings.
-                mDeferredGlobalInitializers.push_back(std::make_pair(symbolNode, expression));
+                // after we initialize uniforms.
+                TIntermBinary *deferredInit = new TIntermBinary(EOpAssign);
+                deferredInit->setLeft(node->getLeft());
+                deferredInit->setRight(node->getRight());
+                deferredInit->setType(node->getType());
+                mDeferredGlobalInitializers.push_back(deferredInit);
                 const TString &initString = initializer(node->getType());
                 node->setRight(new TIntermRaw(node->getType(), initString));
             }
@@ -2272,24 +2276,10 @@
     return true;
 }
 
-bool OutputHLSL::visitSelection(Visit visit, TIntermSelection *node)
+void OutputHLSL::writeSelection(TIntermSelection *node)
 {
     TInfoSinkBase &out = getInfoSink();
 
-    ASSERT(!node->usesTernaryOperator());
-
-    // D3D errors when there is a gradient operation in a loop in an unflattened if.
-    // We check for null mCurrentFunctionMetadata to prevent crashing in the case that the translator has generated if
-    // statements in the global scope when unfolding global initializers. This is a bug that should be addressed by
-    // moving the unfolded global initializers into a function.
-    if (mShaderType == GL_FRAGMENT_SHADER
-        && mCurrentFunctionMetadata != nullptr
-        && mCurrentFunctionMetadata->hasDiscontinuousLoop(node)
-        && mCurrentFunctionMetadata->hasGradientInCallGraph(node))
-    {
-        out << "FLATTEN ";
-    }
-
     out << "if (";
 
     node->getCondition()->traverse(this);
@@ -2334,6 +2324,30 @@
     {
         mUsesDiscardRewriting = true;
     }
+}
+
+bool OutputHLSL::visitSelection(Visit visit, TIntermSelection *node)
+{
+    TInfoSinkBase &out = getInfoSink();
+
+    ASSERT(!node->usesTernaryOperator());
+
+    if (!mInsideFunction)
+    {
+        // This is part of unfolded global initialization.
+        mDeferredGlobalInitializers.push_back(node);
+        return false;
+    }
+
+    // D3D errors when there is a gradient operation in a loop in an unflattened if.
+    if (mShaderType == GL_FRAGMENT_SHADER &&
+        mCurrentFunctionMetadata->hasDiscontinuousLoop(node) &&
+        mCurrentFunctionMetadata->hasGradientInCallGraph(node))
+    {
+        out << "FLATTEN ";
+    }
+
+    writeSelection(node);
 
     return false;
 }
@@ -2954,20 +2968,33 @@
 
     for (const auto &deferredGlobal : mDeferredGlobalInitializers)
     {
-        TIntermSymbol *symbol = deferredGlobal.first;
-        TIntermTyped *expression = deferredGlobal.second;
-        ASSERT(symbol);
-        ASSERT(symbol->getQualifier() == EvqGlobal && expression->getQualifier() != EvqConst);
+        TIntermBinary *binary = deferredGlobal->getAsBinaryNode();
+        TIntermSelection *selection = deferredGlobal->getAsSelectionNode();
+        if (binary != nullptr)
+        {
+            TIntermSymbol *symbol = binary->getLeft()->getAsSymbolNode();
+            TIntermTyped *expression = binary->getRight();
+            ASSERT(symbol);
+            ASSERT(symbol->getQualifier() == EvqGlobal && expression->getQualifier() != EvqConst);
 
-        out << "    " << Decorate(symbol->getSymbol()) << " = ";
+            out << "    " << Decorate(symbol->getSymbol()) << " = ";
 
-        if (!writeSameSymbolInitializer(out, symbol, expression))
+            if (!writeSameSymbolInitializer(out, symbol, expression))
+            {
+                ASSERT(mInfoSinkStack.top() == &out);
+                expression->traverse(this);
+            }
+            out << ";\n";
+        }
+        else if (selection != nullptr)
         {
             ASSERT(mInfoSinkStack.top() == &out);
-            expression->traverse(this);
+            writeSelection(selection);
         }
-
-        out << ";\n";
+        else
+        {
+            UNREACHABLE();
+        }
     }
 
     out << "}\n"
diff --git a/src/compiler/translator/OutputHLSL.h b/src/compiler/translator/OutputHLSL.h
index ba1d2ad..ab5ddde 100644
--- a/src/compiler/translator/OutputHLSL.h
+++ b/src/compiler/translator/OutputHLSL.h
@@ -85,6 +85,7 @@
     // Returns true if it found a 'same symbol' initializer (initializer that references the variable it's initting)
     bool writeSameSymbolInitializer(TInfoSinkBase &out, TIntermSymbol *symbolNode, TIntermTyped *expression);
     void writeDeferredGlobalInitializers(TInfoSinkBase &out);
+    void writeSelection(TIntermSelection *node);
 
     // Returns the function name
     TString addStructEqualityFunction(const TStructure &structure);
@@ -185,11 +186,10 @@
     std::map<TIntermTyped*, TString> mFlaggedStructMappedNames;
     std::map<TIntermTyped*, TString> mFlaggedStructOriginalNames;
 
-    // Some initializers use varyings, uniforms or attributes, thus we can't evaluate some variables
-    // at global static scope in HLSL. These variables depend on values which we retrieve from the
-    // shader input structure, which we set in the D3D main function. Instead, we can initialize
-    // these static globals after we initialize our other globals.
-    std::vector<std::pair<TIntermSymbol*, TIntermTyped*>> mDeferredGlobalInitializers;
+    // Some initializers may have been unfolded into if statements, thus we can't evaluate all initializers
+    // at global static scope in HLSL. Instead, we can initialize these static globals inside a helper function.
+    // This also enables initialization of globals with uniforms.
+    TIntermSequence mDeferredGlobalInitializers;
 
     struct HelperFunction
     {