blob: 685a1aed05ca4f5f1aab3300bc68a13fe065d84e [file] [log] [blame]
//
// Copyright (c) 2002-2014 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/Compiler.h"
#include <sstream>
#include "angle_gl.h"
#include "common/utilities.h"
#include "compiler/translator/CallDAG.h"
#include "compiler/translator/CollectVariables.h"
#include "compiler/translator/Initialize.h"
#include "compiler/translator/IsASTDepthBelowLimit.h"
#include "compiler/translator/OutputTree.h"
#include "compiler/translator/ParseContext.h"
#include "compiler/translator/ValidateLimitations.h"
#include "compiler/translator/ValidateMaxParameters.h"
#include "compiler/translator/ValidateOutputs.h"
#include "compiler/translator/ValidateVaryingLocations.h"
#include "compiler/translator/VariablePacker.h"
#include "compiler/translator/tree_ops/AddAndTrueToLoopCondition.h"
#include "compiler/translator/tree_ops/ClampFragDepth.h"
#include "compiler/translator/tree_ops/ClampPointSize.h"
#include "compiler/translator/tree_ops/DeclareAndInitBuiltinsForInstancedMultiview.h"
#include "compiler/translator/tree_ops/DeferGlobalInitializers.h"
#include "compiler/translator/tree_ops/EmulateGLDrawID.h"
#include "compiler/translator/tree_ops/EmulateGLFragColorBroadcast.h"
#include "compiler/translator/tree_ops/EmulatePrecision.h"
#include "compiler/translator/tree_ops/FoldExpressions.h"
#include "compiler/translator/tree_ops/InitializeVariables.h"
#include "compiler/translator/tree_ops/PruneEmptyCases.h"
#include "compiler/translator/tree_ops/PruneNoOps.h"
#include "compiler/translator/tree_ops/RegenerateStructNames.h"
#include "compiler/translator/tree_ops/RemoveArrayLengthMethod.h"
#include "compiler/translator/tree_ops/RemoveInvariantDeclaration.h"
#include "compiler/translator/tree_ops/RemovePow.h"
#include "compiler/translator/tree_ops/RemoveUnreferencedVariables.h"
#include "compiler/translator/tree_ops/RewriteDoWhile.h"
#include "compiler/translator/tree_ops/RewriteRepeatedAssignToSwizzled.h"
#include "compiler/translator/tree_ops/ScalarizeVecAndMatConstructorArgs.h"
#include "compiler/translator/tree_ops/SeparateDeclarations.h"
#include "compiler/translator/tree_ops/SimplifyLoopConditions.h"
#include "compiler/translator/tree_ops/SplitSequenceOperator.h"
#include "compiler/translator/tree_ops/UnfoldShortCircuitAST.h"
#include "compiler/translator/tree_ops/UseInterfaceBlockFields.h"
#include "compiler/translator/tree_ops/VectorizeVectorScalarArithmetic.h"
#include "compiler/translator/tree_util/BuiltIn_autogen.h"
#include "compiler/translator/tree_util/IntermNodePatternMatcher.h"
#include "compiler/translator/util.h"
#include "third_party/compiler/ArrayBoundsClamper.h"
namespace sh
{
namespace
{
#if defined(ANGLE_ENABLE_FUZZER_CORPUS_OUTPUT)
void DumpFuzzerCase(char const *const *shaderStrings,
size_t numStrings,
uint32_t type,
uint32_t spec,
uint32_t output,
uint64_t options)
{
static int fileIndex = 0;
std::ostringstream o = sh::InitializeStream<std::ostringstream>();
o << "corpus/" << fileIndex++ << ".sample";
std::string s = o.str();
// Must match the input format of the fuzzer
FILE *f = fopen(s.c_str(), "w");
fwrite(&type, sizeof(type), 1, f);
fwrite(&spec, sizeof(spec), 1, f);
fwrite(&output, sizeof(output), 1, f);
fwrite(&options, sizeof(options), 1, f);
char zero[128 - 20] = {0};
fwrite(&zero, 128 - 20, 1, f);
for (size_t i = 0; i < numStrings; i++)
{
fwrite(shaderStrings[i], sizeof(char), strlen(shaderStrings[i]), f);
}
fwrite(&zero, 1, 1, f);
fclose(f);
}
#endif // defined(ANGLE_ENABLE_FUZZER_CORPUS_OUTPUT)
} // anonymous namespace
bool IsGLSL130OrNewer(ShShaderOutput output)
{
return (output == SH_GLSL_130_OUTPUT || output == SH_GLSL_140_OUTPUT ||
output == SH_GLSL_150_CORE_OUTPUT || output == SH_GLSL_330_CORE_OUTPUT ||
output == SH_GLSL_400_CORE_OUTPUT || output == SH_GLSL_410_CORE_OUTPUT ||
output == SH_GLSL_420_CORE_OUTPUT || output == SH_GLSL_430_CORE_OUTPUT ||
output == SH_GLSL_440_CORE_OUTPUT || output == SH_GLSL_450_CORE_OUTPUT);
}
bool IsGLSL420OrNewer(ShShaderOutput output)
{
return (output == SH_GLSL_420_CORE_OUTPUT || output == SH_GLSL_430_CORE_OUTPUT ||
output == SH_GLSL_440_CORE_OUTPUT || output == SH_GLSL_450_CORE_OUTPUT);
}
bool IsGLSL410OrOlder(ShShaderOutput output)
{
return (output == SH_GLSL_130_OUTPUT || output == SH_GLSL_140_OUTPUT ||
output == SH_GLSL_150_CORE_OUTPUT || output == SH_GLSL_330_CORE_OUTPUT ||
output == SH_GLSL_400_CORE_OUTPUT || output == SH_GLSL_410_CORE_OUTPUT);
}
bool RemoveInvariant(sh::GLenum shaderType,
int shaderVersion,
ShShaderOutput outputType,
ShCompileOptions compileOptions)
{
if ((compileOptions & SH_DONT_REMOVE_INVARIANT_FOR_FRAGMENT_INPUT) == 0 &&
shaderType == GL_FRAGMENT_SHADER && IsGLSL420OrNewer(outputType))
return true;
if ((compileOptions & SH_REMOVE_INVARIANT_AND_CENTROID_FOR_ESSL3) != 0 &&
shaderVersion >= 300 && shaderType == GL_VERTEX_SHADER)
return true;
return false;
}
size_t GetGlobalMaxTokenSize(ShShaderSpec spec)
{
// WebGL defines a max token length of 256, while ES2 leaves max token
// size undefined. ES3 defines a max size of 1024 characters.
switch (spec)
{
case SH_WEBGL_SPEC:
return 256;
default:
return 1024;
}
}
int GetMaxUniformVectorsForShaderType(GLenum shaderType, const ShBuiltInResources &resources)
{
switch (shaderType)
{
case GL_VERTEX_SHADER:
return resources.MaxVertexUniformVectors;
case GL_FRAGMENT_SHADER:
return resources.MaxFragmentUniformVectors;
// TODO (jiawei.shao@intel.com): check if we need finer-grained component counting
case GL_COMPUTE_SHADER:
return resources.MaxComputeUniformComponents / 4;
case GL_GEOMETRY_SHADER_EXT:
return resources.MaxGeometryUniformComponents / 4;
default:
UNREACHABLE();
return -1;
}
}
namespace
{
class TScopedPoolAllocator
{
public:
TScopedPoolAllocator(TPoolAllocator *allocator) : mAllocator(allocator)
{
mAllocator->push();
SetGlobalPoolAllocator(mAllocator);
}
~TScopedPoolAllocator()
{
SetGlobalPoolAllocator(nullptr);
mAllocator->pop();
}
private:
TPoolAllocator *mAllocator;
};
class TScopedSymbolTableLevel
{
public:
TScopedSymbolTableLevel(TSymbolTable *table) : mTable(table)
{
ASSERT(mTable->isEmpty());
mTable->push();
}
~TScopedSymbolTableLevel()
{
while (!mTable->isEmpty())
mTable->pop();
}
private:
TSymbolTable *mTable;
};
int MapSpecToShaderVersion(ShShaderSpec spec)
{
switch (spec)
{
case SH_GLES2_SPEC:
case SH_WEBGL_SPEC:
return 100;
case SH_GLES3_SPEC:
case SH_WEBGL2_SPEC:
return 300;
case SH_GLES3_1_SPEC:
case SH_WEBGL3_SPEC:
return 310;
default:
UNREACHABLE();
return 0;
}
}
bool ValidateFragColorAndFragData(GLenum shaderType,
int shaderVersion,
const TSymbolTable &symbolTable,
TDiagnostics *diagnostics)
{
if (shaderVersion > 100 || shaderType != GL_FRAGMENT_SHADER)
{
return true;
}
bool usesFragColor = false;
bool usesFragData = false;
// This validation is a bit stricter than the spec - it's only an error to write to
// both FragData and FragColor. But because it's better not to have reads from undefined
// variables, we always return an error if they are both referenced, rather than only if they
// are written.
if (symbolTable.isStaticallyUsed(*BuiltInVariable::gl_FragColor()) ||
symbolTable.isStaticallyUsed(*BuiltInVariable::gl_SecondaryFragColorEXT()))
{
usesFragColor = true;
}
// Extension variables may not always be initialized (saves some time at symbol table init).
bool secondaryFragDataUsed =
symbolTable.gl_SecondaryFragDataEXT() != nullptr &&
symbolTable.isStaticallyUsed(*symbolTable.gl_SecondaryFragDataEXT());
if (symbolTable.isStaticallyUsed(*symbolTable.gl_FragData()) || secondaryFragDataUsed)
{
usesFragData = true;
}
if (usesFragColor && usesFragData)
{
const char *errorMessage = "cannot use both gl_FragData and gl_FragColor";
if (symbolTable.isStaticallyUsed(*BuiltInVariable::gl_SecondaryFragColorEXT()) ||
secondaryFragDataUsed)
{
errorMessage =
"cannot use both output variable sets (gl_FragData, gl_SecondaryFragDataEXT)"
" and (gl_FragColor, gl_SecondaryFragColorEXT)";
}
diagnostics->globalError(errorMessage);
return false;
}
return true;
}
} // namespace
TShHandleBase::TShHandleBase()
{
allocator.push();
SetGlobalPoolAllocator(&allocator);
}
TShHandleBase::~TShHandleBase()
{
SetGlobalPoolAllocator(nullptr);
allocator.popAll();
}
TCompiler::TCompiler(sh::GLenum type, ShShaderSpec spec, ShShaderOutput output)
: mVariablesCollected(false),
mGLPositionInitialized(false),
mShaderType(type),
mShaderSpec(spec),
mOutputType(output),
mBuiltInFunctionEmulator(),
mDiagnostics(mInfoSink.info),
mSourcePath(nullptr),
mComputeShaderLocalSizeDeclared(false),
mComputeShaderLocalSize(1),
mGeometryShaderMaxVertices(-1),
mGeometryShaderInvocations(0),
mGeometryShaderInputPrimitiveType(EptUndefined),
mGeometryShaderOutputPrimitiveType(EptUndefined)
{}
TCompiler::~TCompiler() {}
bool TCompiler::shouldRunLoopAndIndexingValidation(ShCompileOptions compileOptions) const
{
// If compiling an ESSL 1.00 shader for WebGL, or if its been requested through the API,
// validate loop and indexing as well (to verify that the shader only uses minimal functionality
// of ESSL 1.00 as in Appendix A of the spec).
return (IsWebGLBasedSpec(mShaderSpec) && mShaderVersion == 100) ||
(compileOptions & SH_VALIDATE_LOOP_INDEXING);
}
bool TCompiler::Init(const ShBuiltInResources &resources)
{
SetGlobalPoolAllocator(&allocator);
// Generate built-in symbol table.
if (!initBuiltInSymbolTable(resources))
return false;
mResources = resources;
setResourceString();
InitExtensionBehavior(resources, mExtensionBehavior);
mArrayBoundsClamper.SetClampingStrategy(resources.ArrayIndexClampingStrategy);
return true;
}
TIntermBlock *TCompiler::compileTreeForTesting(const char *const shaderStrings[],
size_t numStrings,
ShCompileOptions compileOptions)
{
return compileTreeImpl(shaderStrings, numStrings, compileOptions);
}
TIntermBlock *TCompiler::compileTreeImpl(const char *const shaderStrings[],
size_t numStrings,
const ShCompileOptions compileOptions)
{
clearResults();
ASSERT(numStrings > 0);
ASSERT(GetGlobalPoolAllocator());
// Reset the extension behavior for each compilation unit.
ResetExtensionBehavior(mExtensionBehavior);
// If gl_DrawID is not supported, remove it from the available extensions
// Currently we only allow emulation of gl_DrawID
const bool glDrawIDSupported = (compileOptions & SH_EMULATE_GL_DRAW_ID) != 0u;
if (!glDrawIDSupported)
{
auto it = mExtensionBehavior.find(TExtension::ANGLE_multi_draw);
if (it != mExtensionBehavior.end())
{
mExtensionBehavior.erase(it);
}
}
// First string is path of source file if flag is set. The actual source follows.
size_t firstSource = 0;
if (compileOptions & SH_SOURCE_PATH)
{
mSourcePath = shaderStrings[0];
++firstSource;
}
TParseContext parseContext(mSymbolTable, mExtensionBehavior, mShaderType, mShaderSpec,
compileOptions, true, &mDiagnostics, getResources());
parseContext.setFragmentPrecisionHighOnESSL1(mResources.FragmentPrecisionHigh == 1);
// We preserve symbols at the built-in level from compile-to-compile.
// Start pushing the user-defined symbols at global level.
TScopedSymbolTableLevel globalLevel(&mSymbolTable);
ASSERT(mSymbolTable.atGlobalLevel());
// Parse shader.
if (PaParseStrings(numStrings - firstSource, &shaderStrings[firstSource], nullptr,
&parseContext) != 0)
{
return nullptr;
}
if (parseContext.getTreeRoot() == nullptr)
{
return nullptr;
}
setASTMetadata(parseContext);
if (!checkShaderVersion(&parseContext))
{
return nullptr;
}
TIntermBlock *root = parseContext.getTreeRoot();
if (!checkAndSimplifyAST(root, parseContext, compileOptions))
{
return nullptr;
}
return root;
}
bool TCompiler::checkShaderVersion(TParseContext *parseContext)
{
if (MapSpecToShaderVersion(mShaderSpec) < mShaderVersion)
{
mDiagnostics.globalError("unsupported shader version");
return false;
}
ASSERT(parseContext);
switch (mShaderType)
{
case GL_COMPUTE_SHADER:
if (mShaderVersion < 310)
{
mDiagnostics.globalError("Compute shader is not supported in this shader version.");
return false;
}
break;
case GL_GEOMETRY_SHADER_EXT:
if (mShaderVersion < 310)
{
mDiagnostics.globalError(
"Geometry shader is not supported in this shader version.");
return false;
}
else
{
ASSERT(mShaderVersion == 310);
if (!parseContext->checkCanUseExtension(sh::TSourceLoc(),
TExtension::EXT_geometry_shader))
{
return false;
}
}
break;
default:
break;
}
return true;
}
void TCompiler::setASTMetadata(const TParseContext &parseContext)
{
mShaderVersion = parseContext.getShaderVersion();
mPragma = parseContext.pragma();
mSymbolTable.setGlobalInvariant(mPragma.stdgl.invariantAll);
mComputeShaderLocalSizeDeclared = parseContext.isComputeShaderLocalSizeDeclared();
mComputeShaderLocalSize = parseContext.getComputeShaderLocalSize();
mNumViews = parseContext.getNumViews();
if (mShaderType == GL_GEOMETRY_SHADER_EXT)
{
mGeometryShaderInputPrimitiveType = parseContext.getGeometryShaderInputPrimitiveType();
mGeometryShaderOutputPrimitiveType = parseContext.getGeometryShaderOutputPrimitiveType();
mGeometryShaderMaxVertices = parseContext.getGeometryShaderMaxVertices();
mGeometryShaderInvocations = parseContext.getGeometryShaderInvocations();
}
}
bool TCompiler::checkAndSimplifyAST(TIntermBlock *root,
const TParseContext &parseContext,
ShCompileOptions compileOptions)
{
// Disallow expressions deemed too complex.
if ((compileOptions & SH_LIMIT_EXPRESSION_COMPLEXITY) && !limitExpressionComplexity(root))
{
return false;
}
if (shouldRunLoopAndIndexingValidation(compileOptions) &&
!ValidateLimitations(root, mShaderType, &mSymbolTable, &mDiagnostics))
{
return false;
}
if (!ValidateFragColorAndFragData(mShaderType, mShaderVersion, mSymbolTable, &mDiagnostics))
{
return false;
}
// Fold expressions that could not be folded before validation that was done as a part of
// parsing.
FoldExpressions(root, &mDiagnostics);
// Folding should only be able to generate warnings.
ASSERT(mDiagnostics.numErrors() == 0);
if (!ValidateAST(root, &mDiagnostics, mValidateASTOptions))
{
return false;
}
// We prune no-ops to work around driver bugs and to keep AST processing and output simple.
// The following kinds of no-ops are pruned:
// 1. Empty declarations "int;".
// 2. Literal statements: "1.0;". The ESSL output doesn't define a default precision
// for float, so float literal statements would end up with no precision which is
// invalid ESSL.
// After this empty declarations are not allowed in the AST.
PruneNoOps(root, &mSymbolTable);
if (!ValidateAST(root, &mDiagnostics, mValidateASTOptions))
{
return false;
}
// Create the function DAG and check there is no recursion
if (!initCallDag(root))
{
return false;
}
if ((compileOptions & SH_LIMIT_CALL_STACK_DEPTH) && !checkCallDepth())
{
return false;
}
// Checks which functions are used and if "main" exists
mFunctionMetadata.clear();
mFunctionMetadata.resize(mCallDag.size());
if (!tagUsedFunctions())
{
return false;
}
if (!(compileOptions & SH_DONT_PRUNE_UNUSED_FUNCTIONS))
{
pruneUnusedFunctions(root);
if (!ValidateAST(root, &mDiagnostics, mValidateASTOptions))
{
return false;
}
}
if (mShaderVersion >= 310 && !ValidateVaryingLocations(root, &mDiagnostics, mShaderType))
{
return false;
}
if (mShaderVersion >= 300 && mShaderType == GL_FRAGMENT_SHADER &&
!ValidateOutputs(root, getExtensionBehavior(), mResources.MaxDrawBuffers, &mDiagnostics))
{
return false;
}
// Fail compilation if precision emulation not supported.
if (getResources().WEBGL_debug_shader_precision && getPragma().debugShaderPrecision &&
!EmulatePrecision::SupportedInLanguage(mOutputType))
{
mDiagnostics.globalError("Precision emulation not supported for this output type.");
return false;
}
// Clamping uniform array bounds needs to happen after validateLimitations pass.
if (compileOptions & SH_CLAMP_INDIRECT_ARRAY_BOUNDS)
{
mArrayBoundsClamper.MarkIndirectArrayBoundsForClamping(root);
}
if ((compileOptions & SH_INITIALIZE_BUILTINS_FOR_INSTANCED_MULTIVIEW) &&
parseContext.isExtensionEnabled(TExtension::OVR_multiview) &&
getShaderType() != GL_COMPUTE_SHADER)
{
DeclareAndInitBuiltinsForInstancedMultiview(root, mNumViews, mShaderType, compileOptions,
mOutputType, &mSymbolTable);
if (!ValidateAST(root, &mDiagnostics, mValidateASTOptions))
{
return false;
}
}
// This pass might emit short circuits so keep it before the short circuit unfolding
if (compileOptions & SH_REWRITE_DO_WHILE_LOOPS)
{
RewriteDoWhile(root, &mSymbolTable);
if (!ValidateAST(root, &mDiagnostics, mValidateASTOptions))
{
return false;
}
}
if (compileOptions & SH_ADD_AND_TRUE_TO_LOOP_CONDITION)
{
AddAndTrueToLoopCondition(root);
if (!ValidateAST(root, &mDiagnostics, mValidateASTOptions))
{
return false;
}
}
if (compileOptions & SH_UNFOLD_SHORT_CIRCUIT)
{
UnfoldShortCircuitAST(root);
if (!ValidateAST(root, &mDiagnostics, mValidateASTOptions))
{
return false;
}
}
if (compileOptions & SH_REMOVE_POW_WITH_CONSTANT_EXPONENT)
{
RemovePow(root, &mSymbolTable);
if (!ValidateAST(root, &mDiagnostics, mValidateASTOptions))
{
return false;
}
}
if (compileOptions & SH_REGENERATE_STRUCT_NAMES)
{
RegenerateStructNames gen(&mSymbolTable);
root->traverse(&gen);
if (!ValidateAST(root, &mDiagnostics, mValidateASTOptions))
{
return false;
}
}
if (mShaderType == GL_VERTEX_SHADER &&
IsExtensionEnabled(mExtensionBehavior, TExtension::ANGLE_multi_draw))
{
if ((compileOptions & SH_EMULATE_GL_DRAW_ID) != 0)
{
EmulateGLDrawID(root, &mSymbolTable, &mUniforms,
shouldCollectVariables(compileOptions));
if (!ValidateAST(root, &mDiagnostics, mValidateASTOptions))
{
return false;
}
}
}
if (mShaderType == GL_FRAGMENT_SHADER && mShaderVersion == 100 && mResources.EXT_draw_buffers &&
mResources.MaxDrawBuffers > 1 &&
IsExtensionEnabled(mExtensionBehavior, TExtension::EXT_draw_buffers))
{
EmulateGLFragColorBroadcast(root, mResources.MaxDrawBuffers, &mOutputVariables,
&mSymbolTable, mShaderVersion);
if (!ValidateAST(root, &mDiagnostics, mValidateASTOptions))
{
return false;
}
}
int simplifyScalarized = (compileOptions & SH_SCALARIZE_VEC_AND_MAT_CONSTRUCTOR_ARGS)
? IntermNodePatternMatcher::kScalarizedVecOrMatConstructor
: 0;
// Split multi declarations and remove calls to array length().
// Note that SimplifyLoopConditions needs to be run before any other AST transformations
// that may need to generate new statements from loop conditions or loop expressions.
SimplifyLoopConditions(root,
IntermNodePatternMatcher::kMultiDeclaration |
IntermNodePatternMatcher::kArrayLengthMethod | simplifyScalarized,
&getSymbolTable());
if (!ValidateAST(root, &mDiagnostics, mValidateASTOptions))
{
return false;
}
// Note that separate declarations need to be run before other AST transformations that
// generate new statements from expressions.
SeparateDeclarations(root);
mValidateASTOptions.validateMultiDeclarations = true;
if (!ValidateAST(root, &mDiagnostics, mValidateASTOptions))
{
return false;
}
SplitSequenceOperator(root, IntermNodePatternMatcher::kArrayLengthMethod | simplifyScalarized,
&getSymbolTable());
if (!ValidateAST(root, &mDiagnostics, mValidateASTOptions))
{
return false;
}
RemoveArrayLengthMethod(root);
if (!ValidateAST(root, &mDiagnostics, mValidateASTOptions))
{
return false;
}
RemoveUnreferencedVariables(root, &mSymbolTable);
if (!ValidateAST(root, &mDiagnostics, mValidateASTOptions))
{
return false;
}
// In case the last case inside a switch statement is a certain type of no-op, GLSL compilers in
// drivers may not accept it. In this case we clean up the dead code from the end of switch
// statements. This is also required because PruneNoOps or RemoveUnreferencedVariables may have
// left switch statements that only contained an empty declaration inside the final case in an
// invalid state. Relies on that PruneNoOps and RemoveUnreferencedVariables have already been
// run.
PruneEmptyCases(root);
if (!ValidateAST(root, &mDiagnostics, mValidateASTOptions))
{
return false;
}
// Built-in function emulation needs to happen after validateLimitations pass.
// TODO(jmadill): Remove global pool allocator.
GetGlobalPoolAllocator()->lock();
initBuiltInFunctionEmulator(&mBuiltInFunctionEmulator, compileOptions);
GetGlobalPoolAllocator()->unlock();
mBuiltInFunctionEmulator.markBuiltInFunctionsForEmulation(root);
bool highPrecisionSupported = mShaderVersion > 100 || mShaderType != GL_FRAGMENT_SHADER ||
mResources.FragmentPrecisionHigh == 1;
if (compileOptions & SH_SCALARIZE_VEC_AND_MAT_CONSTRUCTOR_ARGS)
{
ScalarizeVecAndMatConstructorArgs(root, mShaderType, highPrecisionSupported, &mSymbolTable);
if (!ValidateAST(root, &mDiagnostics, mValidateASTOptions))
{
return false;
}
}
if (shouldCollectVariables(compileOptions))
{
ASSERT(!mVariablesCollected);
CollectVariables(root, &mAttributes, &mOutputVariables, &mUniforms, &mInputVaryings,
&mOutputVaryings, &mUniformBlocks, &mShaderStorageBlocks, &mInBlocks,
mResources.HashFunction, &mSymbolTable, mShaderType, mExtensionBehavior);
collectInterfaceBlocks();
mVariablesCollected = true;
if (compileOptions & SH_USE_UNUSED_STANDARD_SHARED_BLOCKS)
{
useAllMembersInUnusedStandardAndSharedBlocks(root);
}
if (compileOptions & SH_ENFORCE_PACKING_RESTRICTIONS)
{
int maxUniformVectors = GetMaxUniformVectorsForShaderType(mShaderType, mResources);
// Returns true if, after applying the packing rules in the GLSL ES 1.00.17 spec
// Appendix A, section 7, the shader does not use too many uniforms.
if (!CheckVariablesInPackingLimits(maxUniformVectors, mUniforms))
{
mDiagnostics.globalError("too many uniforms");
return false;
}
}
if ((compileOptions & SH_INIT_OUTPUT_VARIABLES) && (mShaderType != GL_COMPUTE_SHADER))
{
initializeOutputVariables(root);
if (!ValidateAST(root, &mDiagnostics, mValidateASTOptions))
{
return false;
}
}
}
// Removing invariant declarations must be done after collecting variables.
// Otherwise, built-in invariant declarations don't apply.
if (RemoveInvariant(mShaderType, mShaderVersion, mOutputType, compileOptions))
{
RemoveInvariantDeclaration(root);
if (!ValidateAST(root, &mDiagnostics, mValidateASTOptions))
{
return false;
}
}
// gl_Position is always written in compatibility output mode.
// It may have been already initialized among other output variables, in that case we don't
// need to initialize it twice.
if (mShaderType == GL_VERTEX_SHADER && !mGLPositionInitialized &&
((compileOptions & SH_INIT_GL_POSITION) || (mOutputType == SH_GLSL_COMPATIBILITY_OUTPUT)))
{
initializeGLPosition(root);
mGLPositionInitialized = true;
if (!ValidateAST(root, &mDiagnostics, mValidateASTOptions))
{
return false;
}
}
// DeferGlobalInitializers needs to be run before other AST transformations that generate new
// statements from expressions. But it's fine to run DeferGlobalInitializers after the above
// SplitSequenceOperator and RemoveArrayLengthMethod since they only have an effect on the AST
// on ESSL >= 3.00, and the initializers that need to be deferred can only exist in ESSL < 3.00.
bool initializeLocalsAndGlobals =
(compileOptions & SH_INITIALIZE_UNINITIALIZED_LOCALS) && !IsOutputHLSL(getOutputType());
bool canUseLoopsToInitialize = !(compileOptions & SH_DONT_USE_LOOPS_TO_INITIALIZE_VARIABLES);
DeferGlobalInitializers(root, initializeLocalsAndGlobals, canUseLoopsToInitialize,
highPrecisionSupported, &mSymbolTable);
if (!ValidateAST(root, &mDiagnostics, mValidateASTOptions))
{
return false;
}
if (initializeLocalsAndGlobals)
{
// Initialize uninitialized local variables.
// In some cases initializing can generate extra statements in the parent block, such as
// when initializing nameless structs or initializing arrays in ESSL 1.00. In that case
// we need to first simplify loop conditions. We've already separated declarations
// earlier, which is also required. If we don't follow the Appendix A limitations, loop
// init statements can declare arrays or nameless structs and have multiple
// declarations.
if (!shouldRunLoopAndIndexingValidation(compileOptions))
{
SimplifyLoopConditions(root,
IntermNodePatternMatcher::kArrayDeclaration |
IntermNodePatternMatcher::kNamelessStructDeclaration,
&getSymbolTable());
if (!ValidateAST(root, &mDiagnostics, mValidateASTOptions))
{
return false;
}
}
InitializeUninitializedLocals(root, getShaderVersion(), canUseLoopsToInitialize,
highPrecisionSupported, &getSymbolTable());
if (!ValidateAST(root, &mDiagnostics, mValidateASTOptions))
{
return false;
}
}
if (getShaderType() == GL_VERTEX_SHADER && (compileOptions & SH_CLAMP_POINT_SIZE))
{
ClampPointSize(root, mResources.MaxPointSize, &getSymbolTable());
if (!ValidateAST(root, &mDiagnostics, mValidateASTOptions))
{
return false;
}
}
if (getShaderType() == GL_FRAGMENT_SHADER && (compileOptions & SH_CLAMP_FRAG_DEPTH))
{
ClampFragDepth(root, &getSymbolTable());
if (!ValidateAST(root, &mDiagnostics, mValidateASTOptions))
{
return false;
}
}
if (compileOptions & SH_REWRITE_REPEATED_ASSIGN_TO_SWIZZLED)
{
sh::RewriteRepeatedAssignToSwizzled(root);
if (!ValidateAST(root, &mDiagnostics, mValidateASTOptions))
{
return false;
}
}
if (compileOptions & SH_REWRITE_VECTOR_SCALAR_ARITHMETIC)
{
VectorizeVectorScalarArithmetic(root, &getSymbolTable());
if (!ValidateAST(root, &mDiagnostics, mValidateASTOptions))
{
return false;
}
}
return true;
}
bool TCompiler::compile(const char *const shaderStrings[],
size_t numStrings,
ShCompileOptions compileOptionsIn)
{
#if defined(ANGLE_ENABLE_FUZZER_CORPUS_OUTPUT)
DumpFuzzerCase(shaderStrings, numStrings, mShaderType, mShaderSpec, mOutputType,
compileOptionsIn);
#endif // defined(ANGLE_ENABLE_FUZZER_CORPUS_OUTPUT)
if (numStrings == 0)
return true;
ShCompileOptions compileOptions = compileOptionsIn;
// Apply key workarounds.
if (shouldFlattenPragmaStdglInvariantAll())
{
// This should be harmless to do in all cases, but for the moment, do it only conditionally.
compileOptions |= SH_FLATTEN_PRAGMA_STDGL_INVARIANT_ALL;
}
TScopedPoolAllocator scopedAlloc(&allocator);
TIntermBlock *root = compileTreeImpl(shaderStrings, numStrings, compileOptions);
if (root)
{
if (compileOptions & SH_INTERMEDIATE_TREE)
OutputTree(root, mInfoSink.info);
if (compileOptions & SH_OBJECT_CODE)
{
PerformanceDiagnostics perfDiagnostics(&mDiagnostics);
translate(root, compileOptions, &perfDiagnostics);
}
if (mShaderType == GL_VERTEX_SHADER &&
IsExtensionEnabled(mExtensionBehavior, TExtension::ANGLE_multi_draw))
{
if ((compileOptions & SH_EMULATE_GL_DRAW_ID) != 0)
{
for (auto &uniform : mUniforms)
{
if (uniform.name == "angle_DrawID" && uniform.mappedName == "angle_DrawID")
{
uniform.name = "gl_DrawID";
break;
}
}
}
}
// The IntermNode tree doesn't need to be deleted here, since the
// memory will be freed in a big chunk by the PoolAllocator.
return true;
}
return false;
}
bool TCompiler::initBuiltInSymbolTable(const ShBuiltInResources &resources)
{
if (resources.MaxDrawBuffers < 1)
{
return false;
}
if (resources.EXT_blend_func_extended && resources.MaxDualSourceDrawBuffers < 1)
{
return false;
}
mSymbolTable.initializeBuiltIns(mShaderType, mShaderSpec, resources);
return true;
}
void TCompiler::setResourceString()
{
std::ostringstream strstream = sh::InitializeStream<std::ostringstream>();
// clang-format off
strstream << ":MaxVertexAttribs:" << mResources.MaxVertexAttribs
<< ":MaxVertexUniformVectors:" << mResources.MaxVertexUniformVectors
<< ":MaxVaryingVectors:" << mResources.MaxVaryingVectors
<< ":MaxVertexTextureImageUnits:" << mResources.MaxVertexTextureImageUnits
<< ":MaxCombinedTextureImageUnits:" << mResources.MaxCombinedTextureImageUnits
<< ":MaxTextureImageUnits:" << mResources.MaxTextureImageUnits
<< ":MaxFragmentUniformVectors:" << mResources.MaxFragmentUniformVectors
<< ":MaxDrawBuffers:" << mResources.MaxDrawBuffers
<< ":OES_standard_derivatives:" << mResources.OES_standard_derivatives
<< ":OES_EGL_image_external:" << mResources.OES_EGL_image_external
<< ":OES_EGL_image_external_essl3:" << mResources.OES_EGL_image_external_essl3
<< ":NV_EGL_stream_consumer_external:" << mResources.NV_EGL_stream_consumer_external
<< ":ARB_texture_rectangle:" << mResources.ARB_texture_rectangle
<< ":EXT_draw_buffers:" << mResources.EXT_draw_buffers
<< ":FragmentPrecisionHigh:" << mResources.FragmentPrecisionHigh
<< ":MaxExpressionComplexity:" << mResources.MaxExpressionComplexity
<< ":MaxCallStackDepth:" << mResources.MaxCallStackDepth
<< ":MaxFunctionParameters:" << mResources.MaxFunctionParameters
<< ":EXT_blend_func_extended:" << mResources.EXT_blend_func_extended
<< ":EXT_frag_depth:" << mResources.EXT_frag_depth
<< ":EXT_shader_texture_lod:" << mResources.EXT_shader_texture_lod
<< ":EXT_shader_framebuffer_fetch:" << mResources.EXT_shader_framebuffer_fetch
<< ":NV_shader_framebuffer_fetch:" << mResources.NV_shader_framebuffer_fetch
<< ":ARM_shader_framebuffer_fetch:" << mResources.ARM_shader_framebuffer_fetch
<< ":OVR_multiview:" << mResources.OVR_multiview
<< ":EXT_YUV_target:" << mResources.EXT_YUV_target
<< ":EXT_geometry_shader:" << mResources.EXT_geometry_shader
<< ":MaxVertexOutputVectors:" << mResources.MaxVertexOutputVectors
<< ":MaxFragmentInputVectors:" << mResources.MaxFragmentInputVectors
<< ":MinProgramTexelOffset:" << mResources.MinProgramTexelOffset
<< ":MaxProgramTexelOffset:" << mResources.MaxProgramTexelOffset
<< ":MaxDualSourceDrawBuffers:" << mResources.MaxDualSourceDrawBuffers
<< ":MaxViewsOVR:" << mResources.MaxViewsOVR
<< ":NV_draw_buffers:" << mResources.NV_draw_buffers
<< ":WEBGL_debug_shader_precision:" << mResources.WEBGL_debug_shader_precision
<< ":ANGLE_multi_draw:" << mResources.ANGLE_multi_draw
<< ":MinProgramTextureGatherOffset:" << mResources.MinProgramTextureGatherOffset
<< ":MaxProgramTextureGatherOffset:" << mResources.MaxProgramTextureGatherOffset
<< ":MaxImageUnits:" << mResources.MaxImageUnits
<< ":MaxVertexImageUniforms:" << mResources.MaxVertexImageUniforms
<< ":MaxFragmentImageUniforms:" << mResources.MaxFragmentImageUniforms
<< ":MaxComputeImageUniforms:" << mResources.MaxComputeImageUniforms
<< ":MaxCombinedImageUniforms:" << mResources.MaxCombinedImageUniforms
<< ":MaxCombinedShaderOutputResources:" << mResources.MaxCombinedShaderOutputResources
<< ":MaxComputeWorkGroupCountX:" << mResources.MaxComputeWorkGroupCount[0]
<< ":MaxComputeWorkGroupCountY:" << mResources.MaxComputeWorkGroupCount[1]
<< ":MaxComputeWorkGroupCountZ:" << mResources.MaxComputeWorkGroupCount[2]
<< ":MaxComputeWorkGroupSizeX:" << mResources.MaxComputeWorkGroupSize[0]
<< ":MaxComputeWorkGroupSizeY:" << mResources.MaxComputeWorkGroupSize[1]
<< ":MaxComputeWorkGroupSizeZ:" << mResources.MaxComputeWorkGroupSize[2]
<< ":MaxComputeUniformComponents:" << mResources.MaxComputeUniformComponents
<< ":MaxComputeTextureImageUnits:" << mResources.MaxComputeTextureImageUnits
<< ":MaxComputeAtomicCounters:" << mResources.MaxComputeAtomicCounters
<< ":MaxComputeAtomicCounterBuffers:" << mResources.MaxComputeAtomicCounterBuffers
<< ":MaxVertexAtomicCounters:" << mResources.MaxVertexAtomicCounters
<< ":MaxFragmentAtomicCounters:" << mResources.MaxFragmentAtomicCounters
<< ":MaxCombinedAtomicCounters:" << mResources.MaxCombinedAtomicCounters
<< ":MaxAtomicCounterBindings:" << mResources.MaxAtomicCounterBindings
<< ":MaxVertexAtomicCounterBuffers:" << mResources.MaxVertexAtomicCounterBuffers
<< ":MaxFragmentAtomicCounterBuffers:" << mResources.MaxFragmentAtomicCounterBuffers
<< ":MaxCombinedAtomicCounterBuffers:" << mResources.MaxCombinedAtomicCounterBuffers
<< ":MaxAtomicCounterBufferSize:" << mResources.MaxAtomicCounterBufferSize
<< ":MaxGeometryUniformComponents:" << mResources.MaxGeometryUniformComponents
<< ":MaxGeometryUniformBlocks:" << mResources.MaxGeometryUniformBlocks
<< ":MaxGeometryInputComponents:" << mResources.MaxGeometryInputComponents
<< ":MaxGeometryOutputComponents:" << mResources.MaxGeometryOutputComponents
<< ":MaxGeometryOutputVertices:" << mResources.MaxGeometryOutputVertices
<< ":MaxGeometryTotalOutputComponents:" << mResources.MaxGeometryTotalOutputComponents
<< ":MaxGeometryTextureImageUnits:" << mResources.MaxGeometryTextureImageUnits
<< ":MaxGeometryAtomicCounterBuffers:" << mResources.MaxGeometryAtomicCounterBuffers
<< ":MaxGeometryAtomicCounters:" << mResources.MaxGeometryAtomicCounters
<< ":MaxGeometryShaderStorageBlocks:" << mResources.MaxGeometryShaderStorageBlocks
<< ":MaxGeometryShaderInvocations:" << mResources.MaxGeometryShaderInvocations
<< ":MaxGeometryImageUniforms:" << mResources.MaxGeometryImageUniforms;
// clang-format on
mBuiltInResourcesString = strstream.str();
}
void TCompiler::collectInterfaceBlocks()
{
ASSERT(mInterfaceBlocks.empty());
mInterfaceBlocks.reserve(mUniformBlocks.size() + mShaderStorageBlocks.size() +
mInBlocks.size());
mInterfaceBlocks.insert(mInterfaceBlocks.end(), mUniformBlocks.begin(), mUniformBlocks.end());
mInterfaceBlocks.insert(mInterfaceBlocks.end(), mShaderStorageBlocks.begin(),
mShaderStorageBlocks.end());
mInterfaceBlocks.insert(mInterfaceBlocks.end(), mInBlocks.begin(), mInBlocks.end());
}
void TCompiler::clearResults()
{
mArrayBoundsClamper.Cleanup();
mInfoSink.info.erase();
mInfoSink.obj.erase();
mInfoSink.debug.erase();
mDiagnostics.resetErrorCount();
mAttributes.clear();
mOutputVariables.clear();
mUniforms.clear();
mInputVaryings.clear();
mOutputVaryings.clear();
mInterfaceBlocks.clear();
mUniformBlocks.clear();
mShaderStorageBlocks.clear();
mInBlocks.clear();
mVariablesCollected = false;
mGLPositionInitialized = false;
mNumViews = -1;
mGeometryShaderInputPrimitiveType = EptUndefined;
mGeometryShaderOutputPrimitiveType = EptUndefined;
mGeometryShaderInvocations = 0;
mGeometryShaderMaxVertices = -1;
mBuiltInFunctionEmulator.cleanup();
mNameMap.clear();
mSourcePath = nullptr;
mSymbolTable.clearCompilationResults();
}
bool TCompiler::initCallDag(TIntermNode *root)
{
mCallDag.clear();
switch (mCallDag.init(root, &mDiagnostics))
{
case CallDAG::INITDAG_SUCCESS:
return true;
case CallDAG::INITDAG_RECURSION:
case CallDAG::INITDAG_UNDEFINED:
// Error message has already been written out.
ASSERT(mDiagnostics.numErrors() > 0);
return false;
}
UNREACHABLE();
return true;
}
bool TCompiler::checkCallDepth()
{
std::vector<int> depths(mCallDag.size());
for (size_t i = 0; i < mCallDag.size(); i++)
{
int depth = 0;
const CallDAG::Record &record = mCallDag.getRecordFromIndex(i);
for (const int &calleeIndex : record.callees)
{
depth = std::max(depth, depths[calleeIndex] + 1);
}
depths[i] = depth;
if (depth >= mResources.MaxCallStackDepth)
{
// Trace back the function chain to have a meaningful info log.
std::stringstream errorStream = sh::InitializeStream<std::stringstream>();
errorStream << "Call stack too deep (larger than " << mResources.MaxCallStackDepth
<< ") with the following call chain: "
<< record.node->getFunction()->name();
int currentFunction = static_cast<int>(i);
int currentDepth = depth;
while (currentFunction != -1)
{
errorStream
<< " -> "
<< mCallDag.getRecordFromIndex(currentFunction).node->getFunction()->name();
int nextFunction = -1;
for (const int &calleeIndex : mCallDag.getRecordFromIndex(currentFunction).callees)
{
if (depths[calleeIndex] == currentDepth - 1)
{
currentDepth--;
nextFunction = calleeIndex;
}
}
currentFunction = nextFunction;
}
std::string errorStr = errorStream.str();
mDiagnostics.globalError(errorStr.c_str());
return false;
}
}
return true;
}
bool TCompiler::tagUsedFunctions()
{
// Search from main, starting from the end of the DAG as it usually is the root.
for (size_t i = mCallDag.size(); i-- > 0;)
{
if (mCallDag.getRecordFromIndex(i).node->getFunction()->isMain())
{
internalTagUsedFunction(i);
return true;
}
}
mDiagnostics.globalError("Missing main()");
return false;
}
void TCompiler::internalTagUsedFunction(size_t index)
{
if (mFunctionMetadata[index].used)
{
return;
}
mFunctionMetadata[index].used = true;
for (int calleeIndex : mCallDag.getRecordFromIndex(index).callees)
{
internalTagUsedFunction(calleeIndex);
}
}
// A predicate for the stl that returns if a top-level node is unused
class TCompiler::UnusedPredicate
{
public:
UnusedPredicate(const CallDAG *callDag, const std::vector<FunctionMetadata> *metadatas)
: mCallDag(callDag), mMetadatas(metadatas)
{}
bool operator()(TIntermNode *node)
{
const TIntermFunctionPrototype *asFunctionPrototype = node->getAsFunctionPrototypeNode();
const TIntermFunctionDefinition *asFunctionDefinition = node->getAsFunctionDefinition();
const TFunction *func = nullptr;
if (asFunctionDefinition)
{
func = asFunctionDefinition->getFunction();
}
else if (asFunctionPrototype)
{
func = asFunctionPrototype->getFunction();
}
if (func == nullptr)
{
return false;
}
size_t callDagIndex = mCallDag->findIndex(func->uniqueId());
if (callDagIndex == CallDAG::InvalidIndex)
{
// This happens only for unimplemented prototypes which are thus unused
ASSERT(asFunctionPrototype);
return true;
}
ASSERT(callDagIndex < mMetadatas->size());
return !(*mMetadatas)[callDagIndex].used;
}
private:
const CallDAG *mCallDag;
const std::vector<FunctionMetadata> *mMetadatas;
};
void TCompiler::pruneUnusedFunctions(TIntermBlock *root)
{
UnusedPredicate isUnused(&mCallDag, &mFunctionMetadata);
TIntermSequence *sequence = root->getSequence();
if (!sequence->empty())
{
sequence->erase(std::remove_if(sequence->begin(), sequence->end(), isUnused),
sequence->end());
}
}
bool TCompiler::limitExpressionComplexity(TIntermBlock *root)
{
if (!IsASTDepthBelowLimit(root, mResources.MaxExpressionComplexity))
{
mDiagnostics.globalError("Expression too complex.");
return false;
}
if (!ValidateMaxParameters(root, mResources.MaxFunctionParameters))
{
mDiagnostics.globalError("Function has too many parameters.");
return false;
}
return true;
}
bool TCompiler::shouldCollectVariables(ShCompileOptions compileOptions)
{
return (compileOptions & SH_VARIABLES) != 0;
}
bool TCompiler::wereVariablesCollected() const
{
return mVariablesCollected;
}
void TCompiler::initializeGLPosition(TIntermBlock *root)
{
InitVariableList list;
sh::ShaderVariable var(GL_FLOAT_VEC4);
var.name = "gl_Position";
list.push_back(var);
InitializeVariables(root, list, &mSymbolTable, mShaderVersion, mExtensionBehavior, false,
false);
}
void TCompiler::useAllMembersInUnusedStandardAndSharedBlocks(TIntermBlock *root)
{
sh::InterfaceBlockList list;
for (const sh::InterfaceBlock &block : mUniformBlocks)
{
if (!block.staticUse &&
(block.layout == sh::BLOCKLAYOUT_STD140 || block.layout == sh::BLOCKLAYOUT_SHARED))
{
list.push_back(block);
}
}
sh::UseInterfaceBlockFields(root, list, mSymbolTable);
}
void TCompiler::initializeOutputVariables(TIntermBlock *root)
{
InitVariableList list;
if (mShaderType == GL_VERTEX_SHADER || mShaderType == GL_GEOMETRY_SHADER_EXT)
{
for (const sh::Varying &var : mOutputVaryings)
{
list.push_back(var);
if (var.name == "gl_Position")
{
ASSERT(!mGLPositionInitialized);
mGLPositionInitialized = true;
}
}
}
else
{
ASSERT(mShaderType == GL_FRAGMENT_SHADER);
for (const sh::OutputVariable &var : mOutputVariables)
{
list.push_back(var);
}
}
InitializeVariables(root, list, &mSymbolTable, mShaderVersion, mExtensionBehavior, false,
false);
}
const TExtensionBehavior &TCompiler::getExtensionBehavior() const
{
return mExtensionBehavior;
}
const char *TCompiler::getSourcePath() const
{
return mSourcePath;
}
const ShBuiltInResources &TCompiler::getResources() const
{
return mResources;
}
const ArrayBoundsClamper &TCompiler::getArrayBoundsClamper() const
{
return mArrayBoundsClamper;
}
ShArrayIndexClampingStrategy TCompiler::getArrayIndexClampingStrategy() const
{
return mResources.ArrayIndexClampingStrategy;
}
const BuiltInFunctionEmulator &TCompiler::getBuiltInFunctionEmulator() const
{
return mBuiltInFunctionEmulator;
}
void TCompiler::writePragma(ShCompileOptions compileOptions)
{
if (!(compileOptions & SH_FLATTEN_PRAGMA_STDGL_INVARIANT_ALL))
{
TInfoSinkBase &sink = mInfoSink.obj;
if (mPragma.stdgl.invariantAll)
sink << "#pragma STDGL invariant(all)\n";
}
}
bool TCompiler::isVaryingDefined(const char *varyingName)
{
ASSERT(mVariablesCollected);
for (size_t ii = 0; ii < mInputVaryings.size(); ++ii)
{
if (mInputVaryings[ii].name == varyingName)
{
return true;
}
}
for (size_t ii = 0; ii < mOutputVaryings.size(); ++ii)
{
if (mOutputVaryings[ii].name == varyingName)
{
return true;
}
}
return false;
}
} // namespace sh