//
// Copyright (c) 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.
//

// RewriteDoWhile.cpp: rewrites do-while loops using another equivalent
// construct.

#include "compiler/translator/RewriteDoWhile.h"

#include "compiler/translator/IntermTraverse.h"

namespace sh
{

namespace
{

// An AST traverser that rewrites loops of the form
//   do {
//     CODE;
//   } while (CONDITION)
//
// to loops of the form
//   bool temp = false;
//   while (true) {
//     if (temp) {
//       if (!CONDITION) {
//         break;
//       }
//     }
//     temp = true;
//     CODE;
//   }
//
// The reason we don't use a simpler form, with for example just (temp && !CONDITION) in the
// while condition, is that short-circuit is often badly supported by driver shader compiler.
// The double if has the same effect, but forces shader compilers to behave.
//
// TODO(cwallez) when UnfoldShortCircuitIntoIf handles loops correctly, revisit this as we might
// be able to use while (temp || CONDITION) with temp initially set to true then run
// UnfoldShortCircuitIntoIf
class DoWhileRewriter : public TIntermTraverser
{
  public:
    DoWhileRewriter() : TIntermTraverser(true, false, false) {}

    bool visitBlock(Visit, TIntermBlock *node) override
    {
        // A well-formed AST can only have do-while inside TIntermBlock. By doing a prefix traversal
        // we are able to replace the do-while in the sequence directly as the content of the
        // do-while will be traversed later.

        TIntermSequence *statements = node->getSequence();

        // The statements vector will have new statements inserted when we encounter a do-while,
        // which prevents us from using a range-based for loop. Using the usual i++ works, as
        // the (two) new statements inserted replace the statement at the current position.
        for (size_t i = 0; i < statements->size(); i++)
        {
            TIntermNode *statement = (*statements)[i];
            TIntermLoop *loop      = statement->getAsLoopNode();

            if (loop == nullptr || loop->getType() != ELoopDoWhile)
            {
                continue;
            }

            TType boolType = TType(EbtBool);

            // bool temp = false;
            TIntermDeclaration *tempDeclaration = nullptr;
            {
                TConstantUnion *falseConstant = new TConstantUnion();
                falseConstant->setBConst(false);
                TIntermTyped *falseValue = new TIntermConstantUnion(falseConstant, boolType);

                tempDeclaration = createTempInitDeclaration(falseValue);
            }

            // temp = true;
            TIntermBinary *assignTrue = nullptr;
            {
                TConstantUnion *trueConstant = new TConstantUnion();
                trueConstant->setBConst(true);
                TIntermTyped *trueValue = new TIntermConstantUnion(trueConstant, boolType);

                assignTrue = createTempAssignment(trueValue);
            }

            // if (temp) {
            //   if (!CONDITION) {
            //     break;
            //   }
            // }
            TIntermIfElse *breakIf = nullptr;
            {
                TIntermBranch *breakStatement = new TIntermBranch(EOpBreak, nullptr);

                TIntermBlock *breakBlock = new TIntermBlock();
                breakBlock->getSequence()->push_back(breakStatement);

                TIntermUnary *negatedCondition =
                    new TIntermUnary(EOpLogicalNot, loop->getCondition());

                TIntermIfElse *innerIf = new TIntermIfElse(negatedCondition, breakBlock, nullptr);

                TIntermBlock *innerIfBlock = new TIntermBlock();
                innerIfBlock->getSequence()->push_back(innerIf);

                breakIf = new TIntermIfElse(createTempSymbol(boolType), innerIfBlock, nullptr);
            }

            // Assemble the replacement loops, reusing the do-while loop's body and inserting our
            // statements at the front.
            TIntermLoop *newLoop = nullptr;
            {
                TConstantUnion *trueConstant = new TConstantUnion();
                trueConstant->setBConst(true);
                TIntermTyped *trueValue = new TIntermConstantUnion(trueConstant, boolType);

                TIntermBlock *body = loop->getBody();
                if (body == nullptr)
                {
                    body = new TIntermBlock();
                }
                auto sequence = body->getSequence();
                sequence->insert(sequence->begin(), assignTrue);
                sequence->insert(sequence->begin(), breakIf);

                newLoop = new TIntermLoop(ELoopWhile, nullptr, trueValue, nullptr, body);
            }

            TIntermSequence replacement;
            replacement.push_back(tempDeclaration);
            replacement.push_back(newLoop);

            node->replaceChildNodeWithMultiple(loop, replacement);

            nextTemporaryId();
        }
        return true;
    }
};

}  // anonymous namespace

void RewriteDoWhile(TIntermNode *root, TSymbolUniqueId *temporaryId)
{
    ASSERT(temporaryId != 0);

    DoWhileRewriter rewriter;
    rewriter.useTemporaryId(temporaryId);

    root->traverse(&rewriter);
}

}  // namespace sh
