blob: 74145400ebc63a7b94a3c357824fa89fc2c7f11e [file] [log] [blame]
//
// 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.
//
// SimplifyLoopConditions is an AST traverser that converts loop conditions and loop expressions
// to regular statements inside the loop. This way further transformations that generate statements
// from loop conditions and loop expressions work correctly.
//
#include "compiler/translator/tree_ops/SimplifyLoopConditions.h"
#include "compiler/translator/StaticType.h"
#include "compiler/translator/tree_util/IntermNodePatternMatcher.h"
#include "compiler/translator/tree_util/IntermNode_util.h"
#include "compiler/translator/tree_util/IntermTraverse.h"
namespace sh
{
namespace
{
class SimplifyLoopConditionsTraverser : public TLValueTrackingTraverser
{
public:
SimplifyLoopConditionsTraverser(unsigned int conditionsToSimplifyMask,
TSymbolTable *symbolTable);
void traverseLoop(TIntermLoop *node) override;
bool visitUnary(Visit visit, TIntermUnary *node) override;
bool visitBinary(Visit visit, TIntermBinary *node) override;
bool visitAggregate(Visit visit, TIntermAggregate *node) override;
bool visitTernary(Visit visit, TIntermTernary *node) override;
bool visitDeclaration(Visit visit, TIntermDeclaration *node) override;
bool foundLoopToChange() const { return mFoundLoopToChange; }
protected:
// Marked to true once an operation that needs to be hoisted out of a loop expression has been
// found.
bool mFoundLoopToChange;
bool mInsideLoopInitConditionOrExpression;
IntermNodePatternMatcher mConditionsToSimplify;
};
SimplifyLoopConditionsTraverser::SimplifyLoopConditionsTraverser(
unsigned int conditionsToSimplifyMask,
TSymbolTable *symbolTable)
: TLValueTrackingTraverser(true, false, false, symbolTable),
mFoundLoopToChange(false),
mInsideLoopInitConditionOrExpression(false),
mConditionsToSimplify(conditionsToSimplifyMask)
{}
// If we're inside a loop initialization, condition, or expression, we check for expressions that
// should be moved out of the loop condition or expression. If one is found, the loop is
// transformed.
// If we're not inside loop initialization, condition, or expression, we only need to traverse nodes
// that may contain loops.
bool SimplifyLoopConditionsTraverser::visitUnary(Visit visit, TIntermUnary *node)
{
if (!mInsideLoopInitConditionOrExpression)
return false;
if (mFoundLoopToChange)
return false; // Already decided to change this loop.
mFoundLoopToChange = mConditionsToSimplify.match(node);
return !mFoundLoopToChange;
}
bool SimplifyLoopConditionsTraverser::visitBinary(Visit visit, TIntermBinary *node)
{
if (!mInsideLoopInitConditionOrExpression)
return false;
if (mFoundLoopToChange)
return false; // Already decided to change this loop.
mFoundLoopToChange = mConditionsToSimplify.match(node, getParentNode(), isLValueRequiredHere());
return !mFoundLoopToChange;
}
bool SimplifyLoopConditionsTraverser::visitAggregate(Visit visit, TIntermAggregate *node)
{
if (!mInsideLoopInitConditionOrExpression)
return false;
if (mFoundLoopToChange)
return false; // Already decided to change this loop.
mFoundLoopToChange = mConditionsToSimplify.match(node, getParentNode());
return !mFoundLoopToChange;
}
bool SimplifyLoopConditionsTraverser::visitTernary(Visit visit, TIntermTernary *node)
{
if (!mInsideLoopInitConditionOrExpression)
return false;
if (mFoundLoopToChange)
return false; // Already decided to change this loop.
mFoundLoopToChange = mConditionsToSimplify.match(node);
return !mFoundLoopToChange;
}
bool SimplifyLoopConditionsTraverser::visitDeclaration(Visit visit, TIntermDeclaration *node)
{
if (!mInsideLoopInitConditionOrExpression)
return false;
if (mFoundLoopToChange)
return false; // Already decided to change this loop.
mFoundLoopToChange = mConditionsToSimplify.match(node);
return !mFoundLoopToChange;
}
void SimplifyLoopConditionsTraverser::traverseLoop(TIntermLoop *node)
{
// Mark that we're inside a loop condition or expression, and determine if the loop needs to be
// transformed.
ScopedNodeInTraversalPath addToPath(this, node);
mInsideLoopInitConditionOrExpression = true;
mFoundLoopToChange = false;
if (!mFoundLoopToChange && node->getInit())
{
node->getInit()->traverse(this);
}
if (!mFoundLoopToChange && node->getCondition())
{
node->getCondition()->traverse(this);
}
if (!mFoundLoopToChange && node->getExpression())
{
node->getExpression()->traverse(this);
}
mInsideLoopInitConditionOrExpression = false;
if (mFoundLoopToChange)
{
const TType *boolType = StaticType::Get<EbtBool, EbpUndefined, EvqTemporary, 1, 1>();
TVariable *conditionVariable = CreateTempVariable(mSymbolTable, boolType);
// Replace the loop condition with a boolean variable that's updated on each iteration.
TLoopType loopType = node->getType();
if (loopType == ELoopWhile)
{
// Transform:
// while (expr) { body; }
// into
// bool s0 = expr;
// while (s0) { { body; } s0 = expr; }
TIntermDeclaration *tempInitDeclaration =
CreateTempInitDeclarationNode(conditionVariable, node->getCondition()->deepCopy());
insertStatementInParentBlock(tempInitDeclaration);
TIntermBlock *newBody = new TIntermBlock();
if (node->getBody())
{
newBody->getSequence()->push_back(node->getBody());
}
newBody->getSequence()->push_back(
CreateTempAssignmentNode(conditionVariable, node->getCondition()->deepCopy()));
// Can't use queueReplacement to replace old body, since it may have been nullptr.
// It's safe to do the replacements in place here - the new body will still be
// traversed, but that won't create any problems.
node->setBody(newBody);
node->setCondition(CreateTempSymbolNode(conditionVariable));
}
else if (loopType == ELoopDoWhile)
{
// Transform:
// do {
// body;
// } while (expr);
// into
// bool s0 = true;
// do {
// { body; }
// s0 = expr;
// } while (s0);
TIntermDeclaration *tempInitDeclaration =
CreateTempInitDeclarationNode(conditionVariable, CreateBoolNode(true));
insertStatementInParentBlock(tempInitDeclaration);
TIntermBlock *newBody = new TIntermBlock();
if (node->getBody())
{
newBody->getSequence()->push_back(node->getBody());
}
newBody->getSequence()->push_back(
CreateTempAssignmentNode(conditionVariable, node->getCondition()->deepCopy()));
// Can't use queueReplacement to replace old body, since it may have been nullptr.
// It's safe to do the replacements in place here - the new body will still be
// traversed, but that won't create any problems.
node->setBody(newBody);
node->setCondition(CreateTempSymbolNode(conditionVariable));
}
else if (loopType == ELoopFor)
{
// Move the loop condition inside the loop.
// Transform:
// for (init; expr; exprB) { body; }
// into
// {
// init;
// bool s0 = expr;
// while (s0) {
// { body; }
// exprB;
// s0 = expr;
// }
// }
TIntermBlock *loopScope = new TIntermBlock();
TIntermSequence *loopScopeSequence = loopScope->getSequence();
// Insert "init;"
if (node->getInit())
{
loopScopeSequence->push_back(node->getInit());
}
// Insert "bool s0 = expr;" if applicable, "bool s0 = true;" otherwise
TIntermTyped *conditionInitializer = nullptr;
if (node->getCondition())
{
conditionInitializer = node->getCondition()->deepCopy();
}
else
{
conditionInitializer = CreateBoolNode(true);
}
loopScopeSequence->push_back(
CreateTempInitDeclarationNode(conditionVariable, conditionInitializer));
// Insert "{ body; }" in the while loop
TIntermBlock *whileLoopBody = new TIntermBlock();
if (node->getBody())
{
whileLoopBody->getSequence()->push_back(node->getBody());
}
// Insert "exprB;" in the while loop
if (node->getExpression())
{
whileLoopBody->getSequence()->push_back(node->getExpression());
}
// Insert "s0 = expr;" in the while loop
if (node->getCondition())
{
whileLoopBody->getSequence()->push_back(
CreateTempAssignmentNode(conditionVariable, node->getCondition()->deepCopy()));
}
// Create "while(s0) { whileLoopBody }"
TIntermLoop *whileLoop =
new TIntermLoop(ELoopWhile, nullptr, CreateTempSymbolNode(conditionVariable),
nullptr, whileLoopBody);
loopScope->getSequence()->push_back(whileLoop);
queueReplacement(loopScope, OriginalNode::IS_DROPPED);
// After this the old body node will be traversed and loops inside it may be
// transformed. This is fine, since the old body node will still be in the AST after the
// transformation that's queued here, and transforming loops inside it doesn't need to
// know the exact post-transform path to it.
}
}
mFoundLoopToChange = false;
// We traverse the body of the loop even if the loop is transformed.
if (node->getBody())
node->getBody()->traverse(this);
}
} // namespace
bool SimplifyLoopConditions(TCompiler *compiler,
TIntermNode *root,
unsigned int conditionsToSimplifyMask,
TSymbolTable *symbolTable)
{
SimplifyLoopConditionsTraverser traverser(conditionsToSimplifyMask, symbolTable);
root->traverse(&traverser);
return traverser.updateTree(compiler, root);
}
} // namespace sh