blob: 04d4d9f026a14e0b1a2635c40e6e2c66fa4cebd9 [file] [log] [blame]
//
// Copyright 2018 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.
//
// Implementation of the function RewriteAtomicFunctionExpressions.
// See the header for more details.
#include "RewriteAtomicFunctionExpressions.h"
#include "compiler/translator/tree_util/IntermNodePatternMatcher.h"
#include "compiler/translator/tree_util/IntermNode_util.h"
#include "compiler/translator/tree_util/IntermTraverse.h"
#include "compiler/translator/util.h"
namespace sh
{
namespace
{
// Traverser that simplifies all the atomic function expressions into the ones that can be directly
// translated into HLSL.
//
// case 1 (only for atomicExchange and atomicCompSwap):
// original:
// atomicExchange(counter, newValue);
// new:
// tempValue = atomicExchange(counter, newValue);
//
// case 2 (atomic function, temporary variable required):
// original:
// value = atomicAdd(counter, 1) * otherValue;
// someArray[atomicAdd(counter, 1)] = someOtherValue;
// new:
// value = ((tempValue = atomicAdd(counter, 1)), tempValue) * otherValue;
// someArray[((tempValue = atomicAdd(counter, 1)), tempValue)] = someOtherValue;
//
// case 3 (atomic function used directly initialize a variable):
// original:
// int value = atomicAdd(counter, 1);
// new:
// tempValue = atomicAdd(counter, 1);
// int value = tempValue;
//
class RewriteAtomicFunctionExpressionsTraverser : public TIntermTraverser
{
public:
RewriteAtomicFunctionExpressionsTraverser(TSymbolTable *symbolTable, int shaderVersion);
bool visitAggregate(Visit visit, TIntermAggregate *node) override;
bool visitBlock(Visit visit, TIntermBlock *node) override;
private:
static bool IsAtomicExchangeOrCompSwapNoReturnValue(TIntermAggregate *node,
TIntermNode *parentNode);
static bool IsAtomicFunctionInsideExpression(TIntermAggregate *node, TIntermNode *parentNode);
void rewriteAtomicFunctionCallNode(TIntermAggregate *oldAtomicFunctionNode);
const TVariable *getTempVariable(const TType *type);
int mShaderVersion;
TIntermSequence mTempVariables;
};
RewriteAtomicFunctionExpressionsTraverser::RewriteAtomicFunctionExpressionsTraverser(
TSymbolTable *symbolTable,
int shaderVersion)
: TIntermTraverser(false, false, true, symbolTable), mShaderVersion(shaderVersion)
{}
void RewriteAtomicFunctionExpressionsTraverser::rewriteAtomicFunctionCallNode(
TIntermAggregate *oldAtomicFunctionNode)
{
ASSERT(oldAtomicFunctionNode);
const TVariable *returnVariable = getTempVariable(&oldAtomicFunctionNode->getType());
TIntermBinary *rewrittenNode = new TIntermBinary(
TOperator::EOpAssign, CreateTempSymbolNode(returnVariable), oldAtomicFunctionNode);
auto *parentNode = getParentNode();
auto *parentBinary = parentNode->getAsBinaryNode();
if (parentBinary && parentBinary->getOp() == EOpInitialize)
{
insertStatementInParentBlock(rewrittenNode);
queueReplacement(CreateTempSymbolNode(returnVariable), OriginalNode::IS_DROPPED);
}
else
{
// As all atomic function assignment will be converted to the last argument of an
// interlocked function, if we need the return value, assignment needs to be wrapped with
// the comma operator and the temporary variables.
if (!parentNode->getAsBlock())
{
rewrittenNode = TIntermBinary::CreateComma(
rewrittenNode, new TIntermSymbol(returnVariable), mShaderVersion);
}
queueReplacement(rewrittenNode, OriginalNode::IS_DROPPED);
}
}
const TVariable *RewriteAtomicFunctionExpressionsTraverser::getTempVariable(const TType *type)
{
TIntermDeclaration *variableDeclaration;
TVariable *returnVariable =
DeclareTempVariable(mSymbolTable, type, EvqTemporary, &variableDeclaration);
mTempVariables.push_back(variableDeclaration);
return returnVariable;
}
bool RewriteAtomicFunctionExpressionsTraverser::IsAtomicExchangeOrCompSwapNoReturnValue(
TIntermAggregate *node,
TIntermNode *parentNode)
{
ASSERT(node);
return (node->getOp() == EOpAtomicExchange || node->getOp() == EOpAtomicCompSwap) &&
parentNode && parentNode->getAsBlock();
}
bool RewriteAtomicFunctionExpressionsTraverser::IsAtomicFunctionInsideExpression(
TIntermAggregate *node,
TIntermNode *parentNode)
{
ASSERT(node);
// We only need to handle atomic functions with a parent that it is not block nodes. If the
// parent node is block, it means that the atomic function is not inside an expression.
if (!IsAtomicFunction(node->getOp()) || parentNode->getAsBlock())
{
return false;
}
auto *parentAsBinary = parentNode->getAsBinaryNode();
// Assignments are handled in OutputHLSL
return !parentAsBinary || parentAsBinary->getOp() != EOpAssign;
}
bool RewriteAtomicFunctionExpressionsTraverser::visitAggregate(Visit visit, TIntermAggregate *node)
{
ASSERT(visit == PostVisit);
// Skip atomic memory functions for SSBO. They will be processed in the OutputHLSL traverser.
if (IsAtomicFunction(node->getOp()) &&
IsInShaderStorageBlock((*node->getSequence())[0]->getAsTyped()))
{
return false;
}
TIntermNode *parentNode = getParentNode();
if (IsAtomicExchangeOrCompSwapNoReturnValue(node, parentNode) ||
IsAtomicFunctionInsideExpression(node, parentNode))
{
rewriteAtomicFunctionCallNode(node);
}
return true;
}
bool RewriteAtomicFunctionExpressionsTraverser::visitBlock(Visit visit, TIntermBlock *node)
{
ASSERT(visit == PostVisit);
if (!mTempVariables.empty() && getParentNode()->getAsFunctionDefinition())
{
insertStatementsInBlockAtPosition(node, 0, mTempVariables, TIntermSequence());
mTempVariables.clear();
}
return true;
}
} // anonymous namespace
bool RewriteAtomicFunctionExpressions(TCompiler *compiler,
TIntermNode *root,
TSymbolTable *symbolTable,
int shaderVersion)
{
RewriteAtomicFunctionExpressionsTraverser traverser(symbolTable, shaderVersion);
traverser.traverse(root);
return traverser.updateTree(compiler, root);
}
} // namespace sh