blob: 8e76c4862c47a803d71fc6b84e141f78b319eda0 [file]
//
// Copyright 2019 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.
//
// RewriteAtomicCounters: Emulate atomic counter buffers with storage buffers.
//
#include "compiler/translator/tree_ops/RewriteAtomicCounters.h"
#include "compiler/translator/ImmutableStringBuilder.h"
#include "compiler/translator/StaticType.h"
#include "compiler/translator/SymbolTable.h"
#include "compiler/translator/tree_util/IntermNode_util.h"
#include "compiler/translator/tree_util/IntermTraverse.h"
namespace sh
{
namespace
{
// DeclareAtomicCountersBuffer adds a storage buffer that's used with atomic counters.
const TVariable *DeclareAtomicCountersBuffer(TIntermBlock *root, TSymbolTable *symbolTable)
{
// Define `uint counters[];` as the only field in the interface block.
TFieldList *fieldList = new TFieldList;
TType *counterType = new TType(EbtUInt);
counterType->makeArray(0);
TField *countersField = new TField(counterType, ImmutableString("counters"), TSourceLoc(),
SymbolType::AngleInternal);
fieldList->push_back(countersField);
TMemoryQualifier coherentMemory = TMemoryQualifier::Create();
coherentMemory.coherent = true;
// Define a storage block "ANGLEAtomicCounters" with instance name "atomicCounters".
return DeclareInterfaceBlock(root, symbolTable, fieldList, EvqBuffer, coherentMemory,
"ANGLEAtomicCounters", "atomicCounters");
}
TIntermBinary *CreateAtomicCounterRef(const TVariable *atomicCounters, TIntermTyped *offset)
{
TIntermSymbol *atomicCountersRef = new TIntermSymbol(atomicCounters);
TConstantUnion *firstFieldIndex = new TConstantUnion;
firstFieldIndex->setIConst(0);
TIntermConstantUnion *firstFieldRef =
new TIntermConstantUnion(firstFieldIndex, *StaticType::GetBasic<EbtUInt>());
TIntermBinary *firstField =
new TIntermBinary(EOpIndexDirectInterfaceBlock, atomicCountersRef, firstFieldRef);
return new TIntermBinary(EOpIndexDirect, firstField, offset);
}
TIntermConstantUnion *CreateUIntConstant(uint32_t value)
{
const TType *constantType = StaticType::GetBasic<EbtUInt, 1>();
TConstantUnion *constantValue = new TConstantUnion;
constantValue->setUConst(value);
return new TIntermConstantUnion(constantValue, *constantType);
}
// Traverser that:
//
// 1. Converts the |atomic_uint| types to |uint|.
// 2. Substitutes the |uniform atomic_uint| declarations with a global declaration that holds the
// offset.
// 3. Substitutes |atomicVar[n]| with |offset + n|.
class RewriteAtomicCountersTraverser : public TIntermTraverser
{
public:
RewriteAtomicCountersTraverser(TSymbolTable *symbolTable, const TVariable *atomicCounters)
: TIntermTraverser(true, true, true, symbolTable),
mAtomicCounters(atomicCounters),
mCurrentAtomicCounterOffset(0),
mCurrentAtomicCounterDecl(nullptr),
mCurrentAtomicCounterDeclParent(nullptr)
{}
bool visitDeclaration(Visit visit, TIntermDeclaration *node) override
{
const TIntermSequence &sequence = *(node->getSequence());
TIntermTyped *variable = sequence.front()->getAsTyped();
const TType &type = variable->getType();
bool isAtomicCounter = type.getQualifier() == EvqUniform && type.isAtomicCounter();
if (visit == PreVisit || visit == InVisit)
{
if (isAtomicCounter)
{
// We only support one atomic counter buffer, so the binding should necessarily be
// 0.
ASSERT(type.getLayoutQualifier().binding == 0);
mCurrentAtomicCounterDecl = node;
mCurrentAtomicCounterDeclParent = getParentNode()->getAsBlock();
mCurrentAtomicCounterOffset = type.getLayoutQualifier().offset;
}
}
else if (visit == PostVisit)
{
mCurrentAtomicCounterDecl = nullptr;
mCurrentAtomicCounterDeclParent = nullptr;
mCurrentAtomicCounterOffset = 0;
}
return true;
}
void visitFunctionPrototype(TIntermFunctionPrototype *node) override
{
const TFunction *function = node->getFunction();
// Go over the parameters and replace the atomic arguments with a uint type. If this is
// the function definition, keep the replaced variable for future encounters.
mAtomicCounterFunctionParams.clear();
for (size_t paramIndex = 0; paramIndex < function->getParamCount(); ++paramIndex)
{
const TVariable *param = function->getParam(paramIndex);
TVariable *replacement = convertFunctionParameter(node, param);
if (replacement)
{
mAtomicCounterFunctionParams[param] = replacement;
}
}
if (mAtomicCounterFunctionParams.empty())
{
return;
}
// Create a new function prototype and replace this with it.
TFunction *replacementFunction = new TFunction(
mSymbolTable, function->name(), SymbolType::UserDefined,
new TType(function->getReturnType()), function->isKnownToNotHaveSideEffects());
for (size_t paramIndex = 0; paramIndex < function->getParamCount(); ++paramIndex)
{
const TVariable *param = function->getParam(paramIndex);
TVariable *replacement = nullptr;
if (param->getType().isAtomicCounter())
{
ASSERT(mAtomicCounterFunctionParams.count(param) != 0);
replacement = mAtomicCounterFunctionParams[param];
}
else
{
replacement = new TVariable(mSymbolTable, param->name(),
new TType(param->getType()), SymbolType::UserDefined);
}
replacementFunction->addParameter(replacement);
}
TIntermFunctionPrototype *replacementPrototype =
new TIntermFunctionPrototype(replacementFunction);
queueReplacement(replacementPrototype, OriginalNode::IS_DROPPED);
mReplacedFunctions[function] = replacementFunction;
}
bool visitAggregate(Visit visit, TIntermAggregate *node) override
{
if (visit == PreVisit)
{
mAtomicCounterFunctionCallArgs.clear();
}
if (visit != PostVisit)
{
return true;
}
if (node->getOp() == EOpCallBuiltInFunction)
{
convertBuiltinFunction(node);
}
else if (node->getOp() == EOpCallFunctionInAST)
{
convertASTFunction(node);
}
return true;
}
void visitSymbol(TIntermSymbol *symbol) override
{
const TVariable *symbolVariable = &symbol->variable();
if (mCurrentAtomicCounterDecl)
{
declareAtomicCounter(symbolVariable);
return;
}
if (!symbol->getType().isAtomicCounter())
{
return;
}
// The symbol is either referencing a global atomic counter, or is a function parameter. In
// either case, it could be an array. The are the following possibilities:
//
// layout(..) uniform atomic_uint ac;
// layout(..) uniform atomic_uint acArray[N];
//
// void func(inout atomic_uint c)
// {
// otherFunc(c);
// }
//
// void funcArray(inout atomic_uint cArray[N])
// {
// otherFuncArray(cArray);
// otherFunc(cArray[n]);
// }
//
// void funcGlobal()
// {
// func(ac);
// func(acArray[n]);
// funcArray(acArray);
// atomicIncrement(ac);
// atomicIncrement(acArray[n]);
// }
//
// This should translate to:
//
// buffer ANGLEAtomicCounters
// {
// uint counters[];
// } atomicCounters;
//
// const uint ac = <offset>;
// const uint acArray = <offset>;
//
// void func(inout uint c)
// {
// otherFunc(c);
// }
//
// void funcArray(inout uint cArray)
// {
// otherFuncArray(cArray);
// otherFunc(cArray + n);
// }
//
// void funcGlobal()
// {
// func(ac);
// func(acArray+n);
// funcArray(acArray);
// atomicAdd(atomicCounters.counters[ac]);
// atomicAdd(atomicCounters.counters[ac+n]);
// }
//
// In all cases, the argument transformation is stored in |mAtomicCounterFunctionCallArgs|.
// In the function call's PostVisit, if it's a builtin, the look up in
// |atomicCounters.counters| is done as well as the builtin function change. Otherwise,
// the transformed argument is passed on as is.
//
TIntermTyped *offset = nullptr;
if (mAtomicCounterOffsets.count(symbolVariable) != 0)
{
offset = new TIntermSymbol(mAtomicCounterOffsets[symbolVariable]);
}
else
{
ASSERT(mAtomicCounterFunctionParams.count(symbolVariable) != 0);
offset = new TIntermSymbol(mAtomicCounterFunctionParams[symbolVariable]);
}
TIntermNode *argument = symbol;
TIntermNode *parent = getParentNode();
ASSERT(parent);
TIntermBinary *arrayExpression = parent->getAsBinaryNode();
if (arrayExpression)
{
ASSERT(arrayExpression->getOp() == EOpIndexDirect ||
arrayExpression->getOp() == EOpIndexIndirect);
offset = new TIntermBinary(EOpAdd, offset, arrayExpression->getRight()->deepCopy());
argument = arrayExpression;
}
mAtomicCounterFunctionCallArgs[argument] = offset;
}
private:
void declareAtomicCounter(const TVariable *symbolVariable)
{
// Create a global variable that contains the offset of this atomic counter declaration.
TType *uintType = new TType(*StaticType::GetBasic<EbtUInt, 1>());
uintType->setQualifier(EvqConst);
TVariable *offset =
new TVariable(mSymbolTable, symbolVariable->name(), uintType, SymbolType::UserDefined);
ASSERT(mCurrentAtomicCounterOffset % 4 == 0);
TIntermConstantUnion *offsetInitValue = CreateIndexNode(mCurrentAtomicCounterOffset / 4);
TIntermSymbol *offsetSymbol = new TIntermSymbol(offset);
TIntermBinary *offsetInit = new TIntermBinary(EOpInitialize, offsetSymbol, offsetInitValue);
TIntermDeclaration *offsetDeclaration = new TIntermDeclaration();
offsetDeclaration->appendDeclarator(offsetInit);
// Replace the atomic_uint declaration with the offset declaration.
TIntermSequence replacement;
replacement.push_back(offsetDeclaration);
mMultiReplacements.emplace_back(mCurrentAtomicCounterDeclParent, mCurrentAtomicCounterDecl,
replacement);
// Remember the offset variable.
mAtomicCounterOffsets[symbolVariable] = offset;
}
TVariable *convertFunctionParameter(TIntermNode *parent, const TVariable *param)
{
if (!param->getType().isAtomicCounter())
{
return nullptr;
}
const TType *newType = StaticType::GetBasic<EbtUInt>();
TVariable *replacementVar =
new TVariable(mSymbolTable, param->name(), newType, SymbolType::UserDefined);
return replacementVar;
}
void convertBuiltinFunction(TIntermAggregate *node)
{
// If the function is |memoryBarrierAtomicCounter|, simply replace it with
// |memoryBarrierBuffer|.
if (node->getFunction()->name() == "memoryBarrierAtomicCounter")
{
TIntermTyped *substituteCall = CreateBuiltInFunctionCallNode(
"memoryBarrierBuffer", new TIntermSequence, *mSymbolTable, 310);
queueReplacement(substituteCall, OriginalNode::IS_DROPPED);
return;
}
// If it's an |atomicCounter*| function, replace the function with an |atomic*| equivalent.
if (!node->getFunction()->isAtomicCounterFunction())
{
return;
}
const ImmutableString &functionName = node->getFunction()->name();
TIntermSequence *arguments = node->getSequence();
// Note: atomicAdd(0) is used for atomic reads.
uint32_t valueChange = 0;
constexpr char kAtomicAddFunction[] = "atomicAdd";
bool isDecrement = false;
if (functionName == "atomicCounterIncrement")
{
valueChange = 1;
}
else if (functionName == "atomicCounterDecrement")
{
// uint values are required to wrap around, so 0xFFFFFFFFu is used as -1.
valueChange = std::numeric_limits<uint32_t>::max();
static_assert(static_cast<uint32_t>(-1) == std::numeric_limits<uint32_t>::max(),
"uint32_t max is not -1");
isDecrement = true;
}
else
{
ASSERT(functionName == "atomicCounter");
}
const TIntermNode *param = (*arguments)[0];
ASSERT(mAtomicCounterFunctionCallArgs.count(param) != 0);
TIntermTyped *offset = mAtomicCounterFunctionCallArgs[param];
TIntermSequence *substituteArguments = new TIntermSequence;
substituteArguments->push_back(CreateAtomicCounterRef(mAtomicCounters, offset));
substituteArguments->push_back(CreateUIntConstant(valueChange));
TIntermTyped *substituteCall = CreateBuiltInFunctionCallNode(
kAtomicAddFunction, substituteArguments, *mSymbolTable, 310);
// Note that atomicCounterDecrement returns the *new* value instead of the prior value,
// unlike atomicAdd. So we need to do a -1 on the result as well.
if (isDecrement)
{
substituteCall = new TIntermBinary(EOpSub, substituteCall, CreateUIntConstant(1));
}
queueReplacement(substituteCall, OriginalNode::IS_DROPPED);
}
void convertASTFunction(TIntermAggregate *node)
{
// See if the function needs replacement at all.
const TFunction *function = node->getFunction();
if (mReplacedFunctions.count(function) == 0)
{
return;
}
// atomic_uint arguments to this call are staged to be replaced at the same time.
TFunction *substituteFunction = mReplacedFunctions[function];
TIntermSequence *substituteArguments = new TIntermSequence;
for (size_t paramIndex = 0; paramIndex < function->getParamCount(); ++paramIndex)
{
TIntermNode *param = node->getChildNode(paramIndex);
TIntermNode *replacement = nullptr;
if (param->getAsTyped()->getType().isAtomicCounter())
{
ASSERT(mAtomicCounterFunctionCallArgs.count(param) != 0);
replacement = mAtomicCounterFunctionCallArgs[param];
}
else
{
replacement = param->getAsTyped()->deepCopy();
}
substituteArguments->push_back(replacement);
}
TIntermTyped *substituteCall =
TIntermAggregate::CreateFunctionCall(*substituteFunction, substituteArguments);
queueReplacement(substituteCall, OriginalNode::IS_DROPPED);
}
private:
const TVariable *mAtomicCounters;
// A map from the atomic_uint variable to the offset declaration.
std::unordered_map<const TVariable *, TVariable *> mAtomicCounterOffsets;
// A map from functions with atomic_uint parameters to one where that's replaced with uint.
std::unordered_map<const TFunction *, TFunction *> mReplacedFunctions;
// A map from atomic_uint function parameters to their replacement uint parameter for the
// current function definition.
std::unordered_map<const TVariable *, TVariable *> mAtomicCounterFunctionParams;
// A map from atomic_uint function call arguments to their replacement for the current
// non-builtin function call.
std::unordered_map<const TIntermNode *, TIntermTyped *> mAtomicCounterFunctionCallArgs;
uint32_t mCurrentAtomicCounterOffset;
TIntermDeclaration *mCurrentAtomicCounterDecl;
TIntermAggregateBase *mCurrentAtomicCounterDeclParent;
};
} // anonymous namespace
void RewriteAtomicCounters(TIntermBlock *root, TSymbolTable *symbolTable)
{
const TVariable *atomicCounters = DeclareAtomicCountersBuffer(root, symbolTable);
RewriteAtomicCountersTraverser traverser(symbolTable, atomicCounters);
root->traverse(&traverser);
traverser.updateTree();
}
} // namespace sh