blob: c5748a8ffaf87d33a943132e1ca79af321375d7e [file] [log] [blame]
//
// Copyright 2002 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.
//
#include "compiler/translator/tree_ops/InitializeVariables.h"
#include "angle_gl.h"
#include "common/debug.h"
#include "compiler/translator/Compiler.h"
#include "compiler/translator/StaticType.h"
#include "compiler/translator/SymbolTable.h"
#include "compiler/translator/tree_util/FindMain.h"
#include "compiler/translator/tree_util/IntermNode_util.h"
#include "compiler/translator/tree_util/IntermTraverse.h"
#include "compiler/translator/util.h"
namespace sh
{
namespace
{
void AddArrayZeroInitSequence(const TIntermTyped *initializedNode,
bool canUseLoopsToInitialize,
bool highPrecisionSupported,
TIntermSequence *initSequenceOut,
TSymbolTable *symbolTable);
void AddStructZeroInitSequence(const TIntermTyped *initializedNode,
bool canUseLoopsToInitialize,
bool highPrecisionSupported,
TIntermSequence *initSequenceOut,
TSymbolTable *symbolTable);
TIntermBinary *CreateZeroInitAssignment(const TIntermTyped *initializedNode)
{
TIntermTyped *zero = CreateZeroNode(initializedNode->getType());
return new TIntermBinary(EOpAssign, initializedNode->deepCopy(), zero);
}
void AddZeroInitSequence(const TIntermTyped *initializedNode,
bool canUseLoopsToInitialize,
bool highPrecisionSupported,
TIntermSequence *initSequenceOut,
TSymbolTable *symbolTable)
{
if (initializedNode->isArray())
{
AddArrayZeroInitSequence(initializedNode, canUseLoopsToInitialize, highPrecisionSupported,
initSequenceOut, symbolTable);
}
else if (initializedNode->getType().isStructureContainingArrays() ||
initializedNode->getType().isNamelessStruct())
{
AddStructZeroInitSequence(initializedNode, canUseLoopsToInitialize, highPrecisionSupported,
initSequenceOut, symbolTable);
}
else
{
initSequenceOut->push_back(CreateZeroInitAssignment(initializedNode));
}
}
void AddStructZeroInitSequence(const TIntermTyped *initializedNode,
bool canUseLoopsToInitialize,
bool highPrecisionSupported,
TIntermSequence *initSequenceOut,
TSymbolTable *symbolTable)
{
ASSERT(initializedNode->getBasicType() == EbtStruct);
const TStructure *structType = initializedNode->getType().getStruct();
for (int i = 0; i < static_cast<int>(structType->fields().size()); ++i)
{
TIntermBinary *element = new TIntermBinary(EOpIndexDirectStruct,
initializedNode->deepCopy(), CreateIndexNode(i));
// Structs can't be defined inside structs, so the type of a struct field can't be a
// nameless struct.
ASSERT(!element->getType().isNamelessStruct());
AddZeroInitSequence(element, canUseLoopsToInitialize, highPrecisionSupported,
initSequenceOut, symbolTable);
}
}
void AddArrayZeroInitStatementList(const TIntermTyped *initializedNode,
bool canUseLoopsToInitialize,
bool highPrecisionSupported,
TIntermSequence *initSequenceOut,
TSymbolTable *symbolTable)
{
for (unsigned int i = 0; i < initializedNode->getOutermostArraySize(); ++i)
{
TIntermBinary *element =
new TIntermBinary(EOpIndexDirect, initializedNode->deepCopy(), CreateIndexNode(i));
AddZeroInitSequence(element, canUseLoopsToInitialize, highPrecisionSupported,
initSequenceOut, symbolTable);
}
}
void AddArrayZeroInitForLoop(const TIntermTyped *initializedNode,
bool highPrecisionSupported,
TIntermSequence *initSequenceOut,
TSymbolTable *symbolTable)
{
ASSERT(initializedNode->isArray());
const TType *mediumpIndexType = StaticType::Get<EbtInt, EbpMedium, EvqTemporary, 1, 1>();
const TType *highpIndexType = StaticType::Get<EbtInt, EbpHigh, EvqTemporary, 1, 1>();
TVariable *indexVariable =
CreateTempVariable(symbolTable, highPrecisionSupported ? highpIndexType : mediumpIndexType);
TIntermSymbol *indexSymbolNode = CreateTempSymbolNode(indexVariable);
TIntermDeclaration *indexInit =
CreateTempInitDeclarationNode(indexVariable, CreateZeroNode(indexVariable->getType()));
TIntermConstantUnion *arraySizeNode = CreateIndexNode(initializedNode->getOutermostArraySize());
TIntermBinary *indexSmallerThanSize =
new TIntermBinary(EOpLessThan, indexSymbolNode->deepCopy(), arraySizeNode);
TIntermUnary *indexIncrement =
new TIntermUnary(EOpPreIncrement, indexSymbolNode->deepCopy(), nullptr);
TIntermBlock *forLoopBody = new TIntermBlock();
TIntermSequence *forLoopBodySeq = forLoopBody->getSequence();
TIntermBinary *element = new TIntermBinary(EOpIndexIndirect, initializedNode->deepCopy(),
indexSymbolNode->deepCopy());
AddZeroInitSequence(element, true, highPrecisionSupported, forLoopBodySeq, symbolTable);
TIntermLoop *forLoop =
new TIntermLoop(ELoopFor, indexInit, indexSmallerThanSize, indexIncrement, forLoopBody);
initSequenceOut->push_back(forLoop);
}
void AddArrayZeroInitSequence(const TIntermTyped *initializedNode,
bool canUseLoopsToInitialize,
bool highPrecisionSupported,
TIntermSequence *initSequenceOut,
TSymbolTable *symbolTable)
{
// The array elements are assigned one by one to keep the AST compatible with ESSL 1.00 which
// doesn't have array assignment. We'll do this either with a for loop or just a list of
// statements assigning to each array index. Note that it is important to have the array init in
// the right order to workaround http://crbug.com/709317
bool isSmallArray = initializedNode->getOutermostArraySize() <= 1u ||
(initializedNode->getBasicType() != EbtStruct &&
!initializedNode->getType().isArrayOfArrays() &&
initializedNode->getOutermostArraySize() <= 3u);
if (initializedNode->getQualifier() == EvqFragData ||
initializedNode->getQualifier() == EvqFragmentOut || isSmallArray ||
!canUseLoopsToInitialize)
{
// Fragment outputs should not be indexed by non-constant indices.
// Also it doesn't make sense to use loops to initialize very small arrays.
AddArrayZeroInitStatementList(initializedNode, canUseLoopsToInitialize,
highPrecisionSupported, initSequenceOut, symbolTable);
}
else
{
AddArrayZeroInitForLoop(initializedNode, highPrecisionSupported, initSequenceOut,
symbolTable);
}
}
void InsertInitCode(TCompiler *compiler,
TIntermSequence *mainBody,
const InitVariableList &variables,
TSymbolTable *symbolTable,
int shaderVersion,
const TExtensionBehavior &extensionBehavior,
bool canUseLoopsToInitialize,
bool highPrecisionSupported)
{
for (const auto &var : variables)
{
// Note that tempVariableName will reference a short-lived char array here - that's fine
// since we're only using it to find symbols.
ImmutableString tempVariableName(var.name.c_str(), var.name.length());
TIntermTyped *initializedSymbol = nullptr;
if (var.isBuiltIn())
{
initializedSymbol =
ReferenceBuiltInVariable(tempVariableName, *symbolTable, shaderVersion);
if (initializedSymbol->getQualifier() == EvqFragData &&
!IsExtensionEnabled(extensionBehavior, TExtension::EXT_draw_buffers))
{
// If GL_EXT_draw_buffers is disabled, only the 0th index of gl_FragData can be
// written to.
// TODO(oetuaho): This is a bit hacky and would be better to remove, if we came up
// with a good way to do it. Right now "gl_FragData" in symbol table is initialized
// to have the array size of MaxDrawBuffers, and the initialization happens before
// the shader sets the extensions it is using.
initializedSymbol =
new TIntermBinary(EOpIndexDirect, initializedSymbol, CreateIndexNode(0));
}
}
else
{
initializedSymbol = ReferenceGlobalVariable(tempVariableName, *symbolTable);
}
ASSERT(initializedSymbol != nullptr);
TIntermSequence *initCode = CreateInitCode(initializedSymbol, canUseLoopsToInitialize,
highPrecisionSupported, symbolTable);
mainBody->insert(mainBody->begin(), initCode->begin(), initCode->end());
}
}
class InitializeLocalsTraverser : public TIntermTraverser
{
public:
InitializeLocalsTraverser(int shaderVersion,
TSymbolTable *symbolTable,
bool canUseLoopsToInitialize,
bool highPrecisionSupported)
: TIntermTraverser(true, false, false, symbolTable),
mShaderVersion(shaderVersion),
mCanUseLoopsToInitialize(canUseLoopsToInitialize),
mHighPrecisionSupported(highPrecisionSupported)
{}
protected:
bool visitDeclaration(Visit visit, TIntermDeclaration *node) override
{
for (TIntermNode *declarator : *node->getSequence())
{
if (!mInGlobalScope && !declarator->getAsBinaryNode())
{
TIntermSymbol *symbol = declarator->getAsSymbolNode();
ASSERT(symbol);
if (symbol->variable().symbolType() == SymbolType::Empty)
{
continue;
}
// Arrays may need to be initialized one element at a time, since ESSL 1.00 does not
// support array constructors or assigning arrays.
bool arrayConstructorUnavailable =
(symbol->isArray() || symbol->getType().isStructureContainingArrays()) &&
mShaderVersion == 100;
// Nameless struct constructors can't be referred to, so they also need to be
// initialized one element at a time.
// TODO(oetuaho): Check if it makes sense to initialize using a loop, even if we
// could use an initializer. It could at least reduce code size for very large
// arrays, but could hurt runtime performance.
if (arrayConstructorUnavailable || symbol->getType().isNamelessStruct())
{
// SimplifyLoopConditions should have been run so the parent node of this node
// should not be a loop.
ASSERT(getParentNode()->getAsLoopNode() == nullptr);
// SeparateDeclarations should have already been run, so we don't need to worry
// about further declarators in this declaration depending on the effects of
// this declarator.
ASSERT(node->getSequence()->size() == 1);
insertStatementsInParentBlock(
TIntermSequence(), *CreateInitCode(symbol, mCanUseLoopsToInitialize,
mHighPrecisionSupported, mSymbolTable));
}
else
{
TIntermBinary *init =
new TIntermBinary(EOpInitialize, symbol, CreateZeroNode(symbol->getType()));
queueReplacementWithParent(node, symbol, init, OriginalNode::BECOMES_CHILD);
}
}
}
return false;
}
private:
int mShaderVersion;
bool mCanUseLoopsToInitialize;
bool mHighPrecisionSupported;
};
} // namespace
TIntermSequence *CreateInitCode(const TIntermTyped *initializedSymbol,
bool canUseLoopsToInitialize,
bool highPrecisionSupported,
TSymbolTable *symbolTable)
{
TIntermSequence *initCode = new TIntermSequence();
AddZeroInitSequence(initializedSymbol, canUseLoopsToInitialize, highPrecisionSupported,
initCode, symbolTable);
return initCode;
}
bool InitializeUninitializedLocals(TCompiler *compiler,
TIntermBlock *root,
int shaderVersion,
bool canUseLoopsToInitialize,
bool highPrecisionSupported,
TSymbolTable *symbolTable)
{
InitializeLocalsTraverser traverser(shaderVersion, symbolTable, canUseLoopsToInitialize,
highPrecisionSupported);
root->traverse(&traverser);
return traverser.updateTree(compiler, root);
}
bool InitializeVariables(TCompiler *compiler,
TIntermBlock *root,
const InitVariableList &vars,
TSymbolTable *symbolTable,
int shaderVersion,
const TExtensionBehavior &extensionBehavior,
bool canUseLoopsToInitialize,
bool highPrecisionSupported)
{
TIntermBlock *body = FindMainBody(root);
InsertInitCode(compiler, body->getSequence(), vars, symbolTable, shaderVersion,
extensionBehavior, canUseLoopsToInitialize, highPrecisionSupported);
return compiler->validateAST(root);
}
} // namespace sh