//
// Copyright 2016 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.
//

// BreakVariableAliasingInInnerLoops.h: To optimize simple assignments, the HLSL compiler frontend
//      may record a variable as aliasing another. Sometimes the alias information gets garbled
//      so we work around this issue by breaking the aliasing chain in inner loops.

#include "compiler/translator/tree_ops/d3d/BreakVariableAliasingInInnerLoops.h"

#include "compiler/translator/Compiler.h"
#include "compiler/translator/tree_util/IntermNode_util.h"
#include "compiler/translator/tree_util/IntermTraverse.h"

// A HLSL compiler developer gave us more details on the root cause and the workaround needed:
//     The root problem is that if the HLSL compiler is applying aliasing information even on
//     incomplete simulations (in this case, a single pass). The bug is triggered by an assignment
//     that comes from a series of assignments, possibly with swizzled or ternary operators with
//     known conditionals, where the source is before the loop.
//     So, a workaround is to add a +0 term to variables the first time they are assigned to in
//     an inner loop (if they are declared in an outside scope, otherwise there is no need).
//     This will break the aliasing chain.

// For simplicity here we add a +0 to any assignment that is in at least two nested loops. Because
// the bug only shows up with swizzles, and ternary assignment, whole array or whole structure
// assignment don't need a workaround.

namespace sh
{

namespace
{

class AliasingBreaker : public TIntermTraverser
{
  public:
    AliasingBreaker() : TIntermTraverser(true, false, true) {}

  protected:
    bool visitBinary(Visit visit, TIntermBinary *binary) override
    {
        if (visit != PreVisit)
        {
            return false;
        }

        if (mLoopLevel < 2 || !binary->isAssignment())
        {
            return true;
        }

        TIntermTyped *B = binary->getRight();
        TType type      = B->getType();

        if (!type.isScalar() && !type.isVector() && !type.isMatrix())
        {
            return true;
        }

        if (type.isArray() || IsSampler(type.getBasicType()))
        {
            return true;
        }

        // We have a scalar / vector / matrix assignment with loop depth 2.
        // Transform it from
        //    A = B
        // to
        //    A = (B + typeof<B>(0));

        TIntermBinary *bPlusZero = new TIntermBinary(EOpAdd, B, CreateZeroNode(type));
        bPlusZero->setLine(B->getLine());

        binary->replaceChildNode(B, bPlusZero);

        return true;
    }

    bool visitLoop(Visit visit, TIntermLoop *loop) override
    {
        if (visit == PreVisit)
        {
            mLoopLevel++;
        }
        else
        {
            ASSERT(mLoopLevel > 0);
            mLoopLevel--;
        }

        return true;
    }

  private:
    int mLoopLevel = 0;
};

}  // anonymous namespace

bool BreakVariableAliasingInInnerLoops(TCompiler *compiler, TIntermNode *root)
{
    AliasingBreaker breaker;
    root->traverse(&breaker);

    return compiler->validateAST(root);
}

}  // namespace sh
