Implement gradient operation AST analysis

This will allow narrowing down which usages of
[[flatten]] and [[unroll]] are actually useful.

BUG=angleproject:937
BUG=395048

Change-Id: Ib8d7b98431b8cd3563e1eff8ecc9ed5df1a9b7d6
Reviewed-on: https://chromium-review.googlesource.com/263775
Reviewed-by: Jamie Madill <jmadill@chromium.org>
Tested-by: Corentin Wallez <cwallez@chromium.org>
diff --git a/src/compiler/translator/ASTMetadataHLSL.cpp b/src/compiler/translator/ASTMetadataHLSL.cpp
new file mode 100644
index 0000000..9580667
--- /dev/null
+++ b/src/compiler/translator/ASTMetadataHLSL.cpp
@@ -0,0 +1,181 @@
+//
+// Copyright (c) 2002-2015 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.
+//
+
+// Analysis of the AST needed for HLSL generation
+
+#include "compiler/translator/ASTMetadataHLSL.h"
+
+#include "compiler/translator/CallDAG.h"
+#include "compiler/translator/SymbolTable.h"
+
+namespace
+{
+
+// Class used to traverse the AST of a function definition, checking if the
+// function uses a gradient, and writing the set of control flow using gradients.
+// It assumes that the analysis has already been made for the function's
+// callees.
+class PullGradient : public TIntermTraverser
+{
+  public:
+    PullGradient(MetadataList *metadataList, size_t index, const CallDAG &dag)
+        : TIntermTraverser(true, false, true),
+          mMetadataList(metadataList),
+          mMetadata(&(*metadataList)[index]),
+          mIndex(index),
+          mDag(dag)
+    {
+        ASSERT(index < metadataList->size());
+    }
+
+    void traverse(TIntermAggregate *node)
+    {
+        node->traverse(this);
+        ASSERT(mParents.empty());
+    }
+
+    // Called when a gradient operation or a call to a function using a gradient is found.
+    void onGradient()
+    {
+        mMetadata->mUsesGradient = true;
+        // Mark the latest control flow as using a gradient.
+        if (!mParents.empty())
+        {
+            mMetadata->mControlFlowsContainingGradient.insert(mParents.back());
+        }
+    }
+
+    void visitControlFlow(Visit visit, TIntermNode *node)
+    {
+        if (visit == PreVisit)
+        {
+            mParents.push_back(node);
+        }
+        else if (visit == PostVisit)
+        {
+            ASSERT(mParents.back() == node);
+            mParents.pop_back();
+            // A control flow's using a gradient means its parents are too.
+            if (mMetadata->mControlFlowsContainingGradient.count(node)> 0 && !mParents.empty())
+            {
+                mMetadata->mControlFlowsContainingGradient.insert(mParents.back());
+            }
+        }
+    }
+
+    bool visitLoop(Visit visit, TIntermLoop *loop)
+    {
+        visitControlFlow(visit, loop);
+        return true;
+    }
+
+    bool visitSelection(Visit visit, TIntermSelection *selection)
+    {
+        visitControlFlow(visit, selection);
+        return true;
+    }
+
+    bool visitUnary(Visit visit, TIntermUnary *node) override
+    {
+        if (visit == PreVisit)
+        {
+            switch (node->getOp())
+            {
+              case EOpDFdx:
+              case EOpDFdy:
+                onGradient();
+              default:
+                break;
+            }
+        }
+
+        return true;
+    }
+
+    bool visitAggregate(Visit visit, TIntermAggregate *node) override
+    {
+        if (visit == PreVisit)
+        {
+            if (node->getOp() == EOpFunctionCall)
+            {
+                if (node->isUserDefined())
+                {
+                    size_t calleeIndex = mDag.findIndex(node);
+                    ASSERT(calleeIndex != CallDAG::InvalidIndex && calleeIndex < mIndex);
+
+                    if ((*mMetadataList)[calleeIndex].mUsesGradient) {
+                        onGradient();
+                    }
+                }
+                else
+                {
+                    TString name = TFunction::unmangleName(node->getName());
+
+                    if (name == "texture2D" ||
+                        name == "texture2DProj" ||
+                        name == "textureCube")
+                    {
+                        onGradient();
+                    }
+                }
+            }
+        }
+
+        return true;
+    }
+
+  private:
+    MetadataList *mMetadataList;
+    ASTMetadataHLSL *mMetadata;
+    size_t mIndex;
+    const CallDAG &mDag;
+
+    // Contains a stack of the control flow nodes that are parents of the node being
+    // currently visited. It is used to mark control flows using a gradient.
+    std::vector<TIntermNode*> mParents;
+};
+
+}
+
+bool ASTMetadataHLSL::hasGradientInCallGraph(TIntermSelection *node)
+{
+    return mControlFlowsContainingGradient.count(node) > 0;
+}
+
+bool ASTMetadataHLSL::hasGradientInCallGraph(TIntermLoop *node)
+{
+    return mControlFlowsContainingGradient.count(node) > 0;
+}
+
+MetadataList CreateASTMetadataHLSL(TIntermNode *root, const CallDAG &callDag)
+{
+    MetadataList metadataList(callDag.size());
+
+    // Compute all the information related to when gradient operations are used.
+    // We want to know for each function and control flow operation if they have
+    // a gradient operation in their call graph (shortened to "using a gradient"
+    // in the rest of the file).
+    //
+    // This computation is logically split in three steps:
+    //  1 - For each function compute if it uses a gradient in its body, ignoring
+    // calls to other user-defined functions.
+    //  2 - For each function determine if it uses a gradient in its call graph,
+    // using the result of step 1 and the CallDAG to know its callees.
+    //  3 - For each control flow statement of each function, check if it uses a
+    // gradient in the function's body, or if it calls a user-defined function that
+    // uses a gradient.
+    //
+    // We take advantage of the call graph being a DAG and instead compute 1, 2 and 3
+    // for leaves first, then going down the tree. This is correct because 1 doesn't
+    // depend on other functions, and 2 and 3 depend only on callees.
+    for (size_t i = 0; i < callDag.size(); i++)
+    {
+        PullGradient pull(&metadataList, i, callDag);
+        pull.traverse(callDag.getRecordFromIndex(i).node);
+    }
+
+    return metadataList;
+}
diff --git a/src/compiler/translator/ASTMetadataHLSL.h b/src/compiler/translator/ASTMetadataHLSL.h
new file mode 100644
index 0000000..74d4df0
--- /dev/null
+++ b/src/compiler/translator/ASTMetadataHLSL.h
@@ -0,0 +1,45 @@
+//
+// Copyright (c) 2002-2015 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.
+//
+
+// Defines analyses of the AST needed for HLSL generation
+
+#ifndef COMPILER_TRANSLATOR_ASTMETADATAHLSL_H_
+#define COMPILER_TRANSLATOR_ASTMETADATAHLSL_H_
+
+#include <set>
+#include <vector>
+
+class CallDAG;
+class TIntermNode;
+class TIntermSelection;
+class TIntermLoop;
+
+struct ASTMetadataHLSL
+{
+    ASTMetadataHLSL()
+        : mUsesGradient(false)
+    {
+    }
+
+    // Here "something uses a gradient" means here that it either contains a
+    // gradient operation, or a call to a function that uses a gradient.
+    bool hasGradientInCallGraph(TIntermSelection *node);
+    bool hasGradientInCallGraph(TIntermLoop *node);
+
+    // Does the function use a gradient.
+    bool mUsesGradient;
+
+    // Even if usesGradient is true, some control flow might not use a gradient
+    // so we store the set of all gradient-using control flows.
+    std::set<TIntermNode*> mControlFlowsContainingGradient;
+};
+
+typedef std::vector<ASTMetadataHLSL> MetadataList;
+
+// Return the AST analysis result, in the order defined by the call DAG
+MetadataList CreateASTMetadataHLSL(TIntermNode *root, const CallDAG &callDag);
+
+#endif // COMPILER_TRANSLATOR_ASTMETADATAHLSL_H_
diff --git a/src/compiler/translator/OutputHLSL.cpp b/src/compiler/translator/OutputHLSL.cpp
index d52f3a8..80782e2 100644
--- a/src/compiler/translator/OutputHLSL.cpp
+++ b/src/compiler/translator/OutputHLSL.cpp
@@ -172,6 +172,7 @@
 {
     mContainsLoopDiscontinuity = mShaderType == GL_FRAGMENT_SHADER && containsLoopDiscontinuity(treeRoot);
     mContainsAnyLoop = containsAnyLoop(treeRoot);
+
     const std::vector<TIntermTyped*> &flaggedStructs = FlagStd140ValueStructs(treeRoot);
     makeFlaggedStructMaps(flaggedStructs);
 
@@ -186,6 +187,14 @@
     InitBuiltInFunctionEmulatorForHLSL(&builtInFunctionEmulator);
     builtInFunctionEmulator.MarkBuiltInFunctionsForEmulation(treeRoot);
 
+    // Now that we are done changing the AST, do the analyses need for HLSL generation
+    {
+        CallDAG dag;
+        CallDAG::InitResult success = dag.init(treeRoot, &objSink);
+        ASSERT(success == CallDAG::INITDAG_SUCCESS);
+        mASTAnalyses = CreateASTMetadataHLSL(treeRoot, dag);
+    }
+
     // Output the body and footer first to determine what has to go in the header
     mInfoSinkStack.push(&mBody);
     treeRoot->traverse(this);
diff --git a/src/compiler/translator/OutputHLSL.h b/src/compiler/translator/OutputHLSL.h
index 2356069..62b37c1 100644
--- a/src/compiler/translator/OutputHLSL.h
+++ b/src/compiler/translator/OutputHLSL.h
@@ -13,6 +13,7 @@
 #include <stack>
 
 #include "angle_gl.h"
+#include "compiler/translator/ASTMetadataHLSL.h"
 #include "compiler/translator/IntermNode.h"
 #include "compiler/translator/ParseContext.h"
 
@@ -169,6 +170,7 @@
 
     int mUniqueIndex;   // For creating unique names
 
+    std::vector<ASTMetadataHLSL> mASTAnalyses;
     bool mContainsLoopDiscontinuity;
     bool mContainsAnyLoop;
     bool mOutputLod0Function;