Commit d10cf69e by Olli Etuaho Committed by Commit Bot

Remove repeated "success" check from compileTreeImpl

This refactoring simplifies the compilation code by putting AST checks and simplification into a separate function. This function will immediately return false when an error is encountered. This is easier to maintain than repeated checking of a "success" boolean. BUG=angleproject:2068 TEST=WebGL conformance tests, angle_unittests, angle_end2end_tests Change-Id: I1ae1c8def3625ada1482104a6babe605405229ef Reviewed-on: https://chromium-review.googlesource.com/750085Reviewed-by: 's avatarJamie Madill <jmadill@chromium.org> Reviewed-by: 's avatarCorentin Wallez <cwallez@chromium.org> Commit-Queue: Olli Etuaho <oetuaho@nvidia.com>
parent 58f67be0
...@@ -335,275 +335,286 @@ TIntermBlock *TCompiler::compileTreeImpl(const char *const shaderStrings[], ...@@ -335,275 +335,286 @@ TIntermBlock *TCompiler::compileTreeImpl(const char *const shaderStrings[],
TScopedSymbolTableLevel scopedSymbolLevel(&symbolTable); TScopedSymbolTableLevel scopedSymbolLevel(&symbolTable);
// Parse shader. // Parse shader.
bool success = (PaParseStrings(numStrings - firstSource, &shaderStrings[firstSource], nullptr, if (PaParseStrings(numStrings - firstSource, &shaderStrings[firstSource], nullptr,
&parseContext) == 0) && &parseContext) != 0)
(parseContext.getTreeRoot() != nullptr); {
return nullptr;
}
shaderVersion = parseContext.getShaderVersion(); if (parseContext.getTreeRoot() == nullptr)
if (success && MapSpecToShaderVersion(shaderSpec) < shaderVersion)
{ {
mDiagnostics.globalError("unsupported shader version"); return nullptr;
success = false;
} }
TIntermBlock *root = nullptr; setASTMetadata(parseContext);
if (success) if (MapSpecToShaderVersion(shaderSpec) < shaderVersion)
{ {
mPragma = parseContext.pragma(); mDiagnostics.globalError("unsupported shader version");
symbolTable.setGlobalInvariant(mPragma.stdgl.invariantAll); return nullptr;
}
mComputeShaderLocalSizeDeclared = parseContext.isComputeShaderLocalSizeDeclared(); TIntermBlock *root = parseContext.getTreeRoot();
mComputeShaderLocalSize = parseContext.getComputeShaderLocalSize(); if (!checkAndSimplifyAST(root, parseContext, compileOptions))
{
return nullptr;
}
mNumViews = parseContext.getNumViews(); return root;
}
root = parseContext.getTreeRoot(); void TCompiler::setASTMetadata(const TParseContext &parseContext)
{
shaderVersion = parseContext.getShaderVersion();
// Highp might have been auto-enabled based on shader version mPragma = parseContext.pragma();
fragmentPrecisionHigh = parseContext.getFragmentPrecisionHigh(); symbolTable.setGlobalInvariant(mPragma.stdgl.invariantAll);
if (success && shaderType == GL_GEOMETRY_SHADER_OES) mComputeShaderLocalSizeDeclared = parseContext.isComputeShaderLocalSizeDeclared();
{ mComputeShaderLocalSize = parseContext.getComputeShaderLocalSize();
mGeometryShaderInputPrimitiveType = parseContext.getGeometryShaderInputPrimitiveType();
mGeometryShaderOutputPrimitiveType =
parseContext.getGeometryShaderOutputPrimitiveType();
mGeometryShaderMaxVertices = parseContext.getGeometryShaderMaxVertices();
mGeometryShaderInvocations = parseContext.getGeometryShaderInvocations();
}
// Disallow expressions deemed too complex. mNumViews = parseContext.getNumViews();
if (success && (compileOptions & SH_LIMIT_EXPRESSION_COMPLEXITY))
success = limitExpressionComplexity(root);
// 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.
if (success)
PruneNoOps(root);
// 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 may have left switch
// statements that only contained an empty declaration inside the final case in an invalid
// state. Relies on that PruneNoOps has already been run.
if (success)
RemoveNoOpCasesFromEndOfSwitchStatements(root, &symbolTable);
// Remove empty switch statements - this makes output simpler.
if (success)
RemoveEmptySwitchStatements(root);
// Create the function DAG and check there is no recursion
if (success)
success = initCallDag(root);
if (success && (compileOptions & SH_LIMIT_CALL_STACK_DEPTH))
success = checkCallDepth();
// Checks which functions are used and if "main" exists
if (success)
{
functionMetadata.clear();
functionMetadata.resize(mCallDag.size());
success = tagUsedFunctions();
}
if (success && !(compileOptions & SH_DONT_PRUNE_UNUSED_FUNCTIONS)) // Highp might have been auto-enabled based on shader version
success = pruneUnusedFunctions(root); fragmentPrecisionHigh = parseContext.getFragmentPrecisionHigh();
if (success && shaderVersion >= 310) if (shaderType == GL_GEOMETRY_SHADER_OES)
{ {
success = ValidateVaryingLocations(root, &mDiagnostics, shaderType); mGeometryShaderInputPrimitiveType = parseContext.getGeometryShaderInputPrimitiveType();
} mGeometryShaderOutputPrimitiveType = parseContext.getGeometryShaderOutputPrimitiveType();
mGeometryShaderMaxVertices = parseContext.getGeometryShaderMaxVertices();
mGeometryShaderInvocations = parseContext.getGeometryShaderInvocations();
}
}
if (success && shaderVersion >= 300 && shaderType == GL_FRAGMENT_SHADER) bool TCompiler::checkAndSimplifyAST(TIntermBlock *root,
{ const TParseContext &parseContext,
success = ValidateOutputs(root, getExtensionBehavior(), compileResources.MaxDrawBuffers, ShCompileOptions compileOptions)
&mDiagnostics); {
} // Disallow expressions deemed too complex.
if ((compileOptions & SH_LIMIT_EXPRESSION_COMPLEXITY) && !limitExpressionComplexity(root))
{
return false;
}
if (success && shouldRunLoopAndIndexingValidation(compileOptions)) // We prune no-ops to work around driver bugs and to keep AST processing and output simple.
success = // The following kinds of no-ops are pruned:
ValidateLimitations(root, shaderType, &symbolTable, shaderVersion, &mDiagnostics); // 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);
// 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 may have left switch
// statements that only contained an empty declaration inside the final case in an invalid
// state. Relies on that PruneNoOps has already been run.
RemoveNoOpCasesFromEndOfSwitchStatements(root, &symbolTable);
// Remove empty switch statements - this makes output simpler.
RemoveEmptySwitchStatements(root);
// Create the function DAG and check there is no recursion
if (!initCallDag(root))
{
return false;
}
// Fail compilation if precision emulation not supported. if ((compileOptions & SH_LIMIT_CALL_STACK_DEPTH) && !checkCallDepth())
if (success && getResources().WEBGL_debug_shader_precision && {
getPragma().debugShaderPrecision) return false;
{ }
if (!EmulatePrecision::SupportedInLanguage(outputType))
{
mDiagnostics.globalError("Precision emulation not supported for this output type.");
success = false;
}
}
// Built-in function emulation needs to happen after validateLimitations pass. // Checks which functions are used and if "main" exists
if (success) functionMetadata.clear();
{ functionMetadata.resize(mCallDag.size());
// TODO(jmadill): Remove global pool allocator. if (!tagUsedFunctions())
GetGlobalPoolAllocator()->lock(); {
initBuiltInFunctionEmulator(&builtInFunctionEmulator, compileOptions); return false;
GetGlobalPoolAllocator()->unlock(); }
builtInFunctionEmulator.markBuiltInFunctionsForEmulation(root);
}
// Clamping uniform array bounds needs to happen after validateLimitations pass. if (!(compileOptions & SH_DONT_PRUNE_UNUSED_FUNCTIONS))
if (success && (compileOptions & SH_CLAMP_INDIRECT_ARRAY_BOUNDS)) {
arrayBoundsClamper.MarkIndirectArrayBoundsForClamping(root); pruneUnusedFunctions(root);
}
if (success && (compileOptions & SH_INITIALIZE_BUILTINS_FOR_INSTANCED_MULTIVIEW) && if (shaderVersion >= 310 && !ValidateVaryingLocations(root, &mDiagnostics, shaderType))
parseContext.isExtensionEnabled(TExtension::OVR_multiview) && {
getShaderType() != GL_COMPUTE_SHADER) return false;
{ }
DeclareAndInitBuiltinsForInstancedMultiview(root, mNumViews, shaderType, compileOptions,
outputType, &symbolTable);
}
// This pass might emit short circuits so keep it before the short circuit unfolding if (shaderVersion >= 300 && shaderType == GL_FRAGMENT_SHADER &&
if (success && (compileOptions & SH_REWRITE_DO_WHILE_LOOPS)) !ValidateOutputs(root, getExtensionBehavior(), compileResources.MaxDrawBuffers,
RewriteDoWhile(root, &symbolTable); &mDiagnostics))
{
return false;
}
if (success && (compileOptions & SH_ADD_AND_TRUE_TO_LOOP_CONDITION)) if (shouldRunLoopAndIndexingValidation(compileOptions) &&
sh::AddAndTrueToLoopCondition(root); !ValidateLimitations(root, shaderType, &symbolTable, shaderVersion, &mDiagnostics))
{
return false;
}
if (success && (compileOptions & SH_UNFOLD_SHORT_CIRCUIT)) // Fail compilation if precision emulation not supported.
{ if (getResources().WEBGL_debug_shader_precision && getPragma().debugShaderPrecision &&
UnfoldShortCircuitAST unfoldShortCircuit; !EmulatePrecision::SupportedInLanguage(outputType))
root->traverse(&unfoldShortCircuit); {
unfoldShortCircuit.updateTree(); mDiagnostics.globalError("Precision emulation not supported for this output type.");
} return false;
}
if (success && (compileOptions & SH_REMOVE_POW_WITH_CONSTANT_EXPONENT)) // Built-in function emulation needs to happen after validateLimitations pass.
// TODO(jmadill): Remove global pool allocator.
GetGlobalPoolAllocator()->lock();
initBuiltInFunctionEmulator(&builtInFunctionEmulator, compileOptions);
GetGlobalPoolAllocator()->unlock();
builtInFunctionEmulator.markBuiltInFunctionsForEmulation(root);
// Clamping uniform array bounds needs to happen after validateLimitations pass.
if (compileOptions & SH_CLAMP_INDIRECT_ARRAY_BOUNDS)
{
arrayBoundsClamper.MarkIndirectArrayBoundsForClamping(root);
}
if ((compileOptions & SH_INITIALIZE_BUILTINS_FOR_INSTANCED_MULTIVIEW) &&
parseContext.isExtensionEnabled(TExtension::OVR_multiview) &&
getShaderType() != GL_COMPUTE_SHADER)
{
DeclareAndInitBuiltinsForInstancedMultiview(root, mNumViews, shaderType, compileOptions,
outputType, &symbolTable);
}
// This pass might emit short circuits so keep it before the short circuit unfolding
if (compileOptions & SH_REWRITE_DO_WHILE_LOOPS)
RewriteDoWhile(root, &symbolTable);
if (compileOptions & SH_ADD_AND_TRUE_TO_LOOP_CONDITION)
sh::AddAndTrueToLoopCondition(root);
if (compileOptions & SH_UNFOLD_SHORT_CIRCUIT)
{
UnfoldShortCircuitAST unfoldShortCircuit;
root->traverse(&unfoldShortCircuit);
unfoldShortCircuit.updateTree();
}
if (compileOptions & SH_REMOVE_POW_WITH_CONSTANT_EXPONENT)
{
RemovePow(root);
}
if (shouldCollectVariables(compileOptions))
{
ASSERT(!variablesCollected);
CollectVariables(root, &attributes, &outputVariables, &uniforms, &inputVaryings,
&outputVaryings, &uniformBlocks, &shaderStorageBlocks, &inBlocks,
hashFunction, &symbolTable, shaderVersion, shaderType, extensionBehavior);
collectInterfaceBlocks();
variablesCollected = true;
if (compileOptions & SH_USE_UNUSED_STANDARD_SHARED_BLOCKS)
{ {
RemovePow(root); useAllMembersInUnusedStandardAndSharedBlocks(root);
} }
if (compileOptions & SH_ENFORCE_PACKING_RESTRICTIONS)
if (success && shouldCollectVariables(compileOptions))
{ {
ASSERT(!variablesCollected); // Returns true if, after applying the packing rules in the GLSL ES 1.00.17 spec
CollectVariables(root, &attributes, &outputVariables, &uniforms, &inputVaryings, // Appendix A, section 7, the shader does not use too many uniforms.
&outputVaryings, &uniformBlocks, &shaderStorageBlocks, &inBlocks, if (!CheckVariablesInPackingLimits(maxUniformVectors, uniforms))
hashFunction, &symbolTable, shaderVersion, shaderType,
extensionBehavior);
collectInterfaceBlocks();
variablesCollected = true;
if (compileOptions & SH_USE_UNUSED_STANDARD_SHARED_BLOCKS)
{
useAllMembersInUnusedStandardAndSharedBlocks(root);
}
if (compileOptions & SH_ENFORCE_PACKING_RESTRICTIONS)
{
// 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.
success = CheckVariablesInPackingLimits(maxUniformVectors, uniforms);
if (!success)
{
mDiagnostics.globalError("too many uniforms");
}
}
if (success && (compileOptions & SH_INIT_OUTPUT_VARIABLES))
{ {
initializeOutputVariables(root); mDiagnostics.globalError("too many uniforms");
return false;
} }
} }
if (compileOptions & SH_INIT_OUTPUT_VARIABLES)
// 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 (success && shaderType == GL_VERTEX_SHADER && !mGLPositionInitialized &&
((compileOptions & SH_INIT_GL_POSITION) ||
(outputType == SH_GLSL_COMPATIBILITY_OUTPUT)))
{ {
initializeGLPosition(root); initializeOutputVariables(root);
mGLPositionInitialized = true;
} }
}
// Removing invariant declarations must be done after collecting variables. // gl_Position is always written in compatibility output mode.
// Otherwise, built-in invariant declarations don't apply. // It may have been already initialized among other output variables, in that case we don't
if (success && RemoveInvariant(shaderType, shaderVersion, outputType, compileOptions)) // need to initialize it twice.
sh::RemoveInvariantDeclaration(root); if (shaderType == GL_VERTEX_SHADER && !mGLPositionInitialized &&
((compileOptions & SH_INIT_GL_POSITION) || (outputType == SH_GLSL_COMPATIBILITY_OUTPUT)))
{
initializeGLPosition(root);
mGLPositionInitialized = true;
}
if (success && (compileOptions & SH_SCALARIZE_VEC_AND_MAT_CONSTRUCTOR_ARGS)) // Removing invariant declarations must be done after collecting variables.
{ // Otherwise, built-in invariant declarations don't apply.
ScalarizeVecAndMatConstructorArgs(root, shaderType, fragmentPrecisionHigh, if (RemoveInvariant(shaderType, shaderVersion, outputType, compileOptions))
&symbolTable); {
} sh::RemoveInvariantDeclaration(root);
}
if (success && (compileOptions & SH_REGENERATE_STRUCT_NAMES)) if (compileOptions & SH_SCALARIZE_VEC_AND_MAT_CONSTRUCTOR_ARGS)
{ {
RegenerateStructNames gen(&symbolTable, shaderVersion); ScalarizeVecAndMatConstructorArgs(root, shaderType, fragmentPrecisionHigh, &symbolTable);
root->traverse(&gen); }
}
if (success && shaderType == GL_FRAGMENT_SHADER && shaderVersion == 100 && if (compileOptions & SH_REGENERATE_STRUCT_NAMES)
compileResources.EXT_draw_buffers && compileResources.MaxDrawBuffers > 1 && {
IsExtensionEnabled(extensionBehavior, TExtension::EXT_draw_buffers)) RegenerateStructNames gen(&symbolTable, shaderVersion);
{ root->traverse(&gen);
EmulateGLFragColorBroadcast(root, compileResources.MaxDrawBuffers, &outputVariables, }
&symbolTable, shaderVersion);
}
if (success) if (shaderType == GL_FRAGMENT_SHADER && shaderVersion == 100 &&
{ compileResources.EXT_draw_buffers && compileResources.MaxDrawBuffers > 1 &&
DeferGlobalInitializers(root, needToInitializeGlobalsInAST(), &symbolTable); IsExtensionEnabled(extensionBehavior, TExtension::EXT_draw_buffers))
} {
EmulateGLFragColorBroadcast(root, compileResources.MaxDrawBuffers, &outputVariables,
&symbolTable, shaderVersion);
}
// Split multi declarations and remove calls to array length(). DeferGlobalInitializers(root, needToInitializeGlobalsInAST(), &symbolTable);
if (success)
{
// 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,
&getSymbolTable(), getShaderVersion());
// Note that separate declarations need to be run before other AST transformations that // Split multi declarations and remove calls to array length().
// generate new statements from expressions. // Note that SimplifyLoopConditions needs to be run before any other AST transformations
SeparateDeclarations(root); // that may need to generate new statements from loop conditions or loop expressions.
SimplifyLoopConditions(
root,
IntermNodePatternMatcher::kMultiDeclaration | IntermNodePatternMatcher::kArrayLengthMethod,
&getSymbolTable(), getShaderVersion());
SplitSequenceOperator(root, IntermNodePatternMatcher::kArrayLengthMethod, // Note that separate declarations need to be run before other AST transformations that
&getSymbolTable(), getShaderVersion()); // generate new statements from expressions.
SeparateDeclarations(root);
RemoveArrayLengthMethod(root); SplitSequenceOperator(root, IntermNodePatternMatcher::kArrayLengthMethod, &getSymbolTable(),
} getShaderVersion());
if (success && (compileOptions & SH_INITIALIZE_UNINITIALIZED_LOCALS) && getOutputType()) RemoveArrayLengthMethod(root);
{
// 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(), getShaderVersion());
}
InitializeUninitializedLocals(root, getShaderVersion());
}
if (success && getShaderType() == GL_VERTEX_SHADER && if ((compileOptions & SH_INITIALIZE_UNINITIALIZED_LOCALS) && getOutputType())
(compileOptions & SH_CLAMP_POINT_SIZE)) {
// 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))
{ {
ClampPointSize(root, compileResources.MaxPointSize, &getSymbolTable()); SimplifyLoopConditions(root,
IntermNodePatternMatcher::kArrayDeclaration |
IntermNodePatternMatcher::kNamelessStructDeclaration,
&getSymbolTable(), getShaderVersion());
} }
InitializeUninitializedLocals(root, getShaderVersion());
} }
if (success) if (getShaderType() == GL_VERTEX_SHADER && (compileOptions & SH_CLAMP_POINT_SIZE))
return root; {
ClampPointSize(root, compileResources.MaxPointSize, &getSymbolTable());
}
return nullptr; return true;
} }
bool TCompiler::compile(const char *const shaderStrings[], bool TCompiler::compile(const char *const shaderStrings[],
...@@ -985,7 +996,7 @@ class TCompiler::UnusedPredicate ...@@ -985,7 +996,7 @@ class TCompiler::UnusedPredicate
const std::vector<FunctionMetadata> *mMetadatas; const std::vector<FunctionMetadata> *mMetadatas;
}; };
bool TCompiler::pruneUnusedFunctions(TIntermBlock *root) void TCompiler::pruneUnusedFunctions(TIntermBlock *root)
{ {
UnusedPredicate isUnused(&mCallDag, &functionMetadata); UnusedPredicate isUnused(&mCallDag, &functionMetadata);
TIntermSequence *sequence = root->getSequence(); TIntermSequence *sequence = root->getSequence();
...@@ -995,8 +1006,6 @@ bool TCompiler::pruneUnusedFunctions(TIntermBlock *root) ...@@ -995,8 +1006,6 @@ bool TCompiler::pruneUnusedFunctions(TIntermBlock *root)
sequence->erase(std::remove_if(sequence->begin(), sequence->end(), isUnused), sequence->erase(std::remove_if(sequence->begin(), sequence->end(), isUnused),
sequence->end()); sequence->end());
} }
return true;
} }
bool TCompiler::limitExpressionComplexity(TIntermBlock *root) bool TCompiler::limitExpressionComplexity(TIntermBlock *root)
......
...@@ -30,6 +30,7 @@ namespace sh ...@@ -30,6 +30,7 @@ namespace sh
{ {
class TCompiler; class TCompiler;
class TParseContext;
#ifdef ANGLE_ENABLE_HLSL #ifdef ANGLE_ENABLE_HLSL
class TranslatorHLSL; class TranslatorHLSL;
#endif // ANGLE_ENABLE_HLSL #endif // ANGLE_ENABLE_HLSL
...@@ -218,12 +219,21 @@ class TCompiler : public TShHandleBase ...@@ -218,12 +219,21 @@ class TCompiler : public TShHandleBase
// Removes unused function declarations and prototypes from the AST // Removes unused function declarations and prototypes from the AST
class UnusedPredicate; class UnusedPredicate;
bool pruneUnusedFunctions(TIntermBlock *root); void pruneUnusedFunctions(TIntermBlock *root);
TIntermBlock *compileTreeImpl(const char *const shaderStrings[], TIntermBlock *compileTreeImpl(const char *const shaderStrings[],
size_t numStrings, size_t numStrings,
const ShCompileOptions compileOptions); const ShCompileOptions compileOptions);
// Fetches and stores shader metadata that is not stored within the AST itself, such as shader
// version.
void setASTMetadata(const TParseContext &parseContext);
// Does checks that need to be run after parsing is complete and returns true if they pass.
bool checkAndSimplifyAST(TIntermBlock *root,
const TParseContext &parseContext,
ShCompileOptions compileOptions);
sh::GLenum shaderType; sh::GLenum shaderType;
ShShaderSpec shaderSpec; ShShaderSpec shaderSpec;
ShShaderOutput outputType; ShShaderOutput outputType;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment