blob: b875098fdace38cbe6e14081d78ce59a57128765 [file] [log] [blame]
/*
* Copyright 2020 Google LLC
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#ifndef SkSLAnalysis_DEFINED
#define SkSLAnalysis_DEFINED
#include "include/private/SkSLSampleUsage.h"
#include "include/private/base/SkTArray.h"
#include <cstdint>
#include <memory>
#include <set>
#include <vector>
namespace SkSL {
class Context;
class ErrorReporter;
class Expression;
class FunctionDeclaration;
class FunctionDefinition;
class Position;
class ProgramElement;
class ProgramUsage;
class Statement;
class SymbolTable;
class Variable;
class VariableReference;
enum class VariableRefKind : int8_t;
struct ForLoopPositions;
struct LoopUnrollInfo;
struct Module;
struct Program;
/**
* Provides utilities for analyzing SkSL statically before it's composed into a full program.
*/
namespace Analysis {
/**
* Determines how `program` samples `child`. By default, assumes that the sample coords
* (SK_MAIN_COORDS_BUILTIN) might be modified, so `child.eval(sampleCoords)` is treated as
* Explicit. If writesToSampleCoords is false, treats that as PassThrough, instead.
* If elidedSampleCoordCount is provided, the pointed to value will be incremented by the
* number of sample calls where the above rewrite was performed.
*/
SampleUsage GetSampleUsage(const Program& program,
const Variable& child,
bool writesToSampleCoords = true,
int* elidedSampleCoordCount = nullptr);
bool ReferencesBuiltin(const Program& program, int builtin);
bool ReferencesSampleCoords(const Program& program);
bool ReferencesFragCoords(const Program& program);
bool CallsSampleOutsideMain(const Program& program);
bool CallsColorTransformIntrinsics(const Program& program);
/**
* Determines if `function` always returns an opaque color (a vec4 where the last component is known
* to be 1). This is conservative, and based on constant expression analysis.
*/
bool ReturnsOpaqueColor(const FunctionDefinition& function);
/**
* Checks for recursion or overly-deep function-call chains, and rejects programs which have them.
* Also, computes the size of the program in a completely flattened state--loops fully unrolled,
* function calls inlined--and rejects programs that exceed an arbitrary upper bound. This is
* intended to prevent absurdly large programs from overwhemling SkVM. Only strict-ES2 mode is
* supported; complex control flow is not SkVM-compatible (and this becomes the halting problem)
*/
bool CheckProgramStructure(const Program& program, bool enforceSizeLimit);
/** Determines if `expr` contains a reference to the variable sk_RTAdjust. */
bool ContainsRTAdjust(const Expression& expr);
/** Determines if `expr` has any side effects. (Is the expression state-altering or pure?) */
bool HasSideEffects(const Expression& expr);
/** Determines if `expr` is a compile-time constant (composed of just constructors and literals). */
bool IsCompileTimeConstant(const Expression& expr);
/**
* Determines if `expr` is a dynamically-uniform expression; this returns true if the expression
* could be evaluated at compile time if uniform values were known.
*/
bool IsDynamicallyUniformExpression(const Expression& expr);
/**
* Detect an orphaned variable declaration outside of a scope, e.g. if (true) int a;. Returns
* true if an error was reported.
*/
bool DetectVarDeclarationWithoutScope(const Statement& stmt, ErrorReporter* errors = nullptr);
int NodeCountUpToLimit(const FunctionDefinition& function, int limit);
/**
* Finds unconditional exits from a switch-case. Returns true if this statement unconditionally
* causes an exit from this switch (via continue, break or return).
*/
bool SwitchCaseContainsUnconditionalExit(Statement& stmt);
/**
* Finds conditional exits from a switch-case. Returns true if this statement contains a
* conditional that wraps a potential exit from the switch (via continue, break or return).
*/
bool SwitchCaseContainsConditionalExit(Statement& stmt);
std::unique_ptr<ProgramUsage> GetUsage(const Program& program);
std::unique_ptr<ProgramUsage> GetUsage(const Module& module);
/** Returns true if the passed-in statement might alter `var`. */
bool StatementWritesToVariable(const Statement& stmt, const Variable& var);
/**
* Detects if the passed-in block contains a `continue`, `break` or `return` that could directly
* affect its control flow. (A `continue` or `break` nested inside an inner loop/switch will not
* affect the loop, but a `return` will.)
*/
struct LoopControlFlowInfo {
bool fHasContinue = false;
bool fHasBreak = false;
bool fHasReturn = false;
};
LoopControlFlowInfo GetLoopControlFlowInfo(const Statement& stmt);
/**
* Returns true if the expression can be assigned-into. Pass `info` if you want to know the
* VariableReference that will be written to. Pass `errors` to report an error for expressions that
* are not actually writable.
*/
struct AssignmentInfo {
VariableReference* fAssignedVar = nullptr;
};
bool IsAssignable(Expression& expr, AssignmentInfo* info = nullptr,
ErrorReporter* errors = nullptr);
/**
* Updates the `refKind` field of the VariableReference at the top level of `expr`.
* If `expr` can be assigned to (`IsAssignable`), true is returned and no errors are reported.
* If not, false is returned. and an error is reported if `errors` is non-null.
*/
bool UpdateVariableRefKind(Expression* expr, VariableRefKind kind, ErrorReporter* errors = nullptr);
/**
* A "trivial" expression is one where we'd feel comfortable cloning it multiple times in
* the code, without worrying about incurring a performance penalty. Examples:
* - true
* - 3.14159265
* - myIntVariable
* - myColor.rgb
* - myArray[123]
* - myStruct.myField
* - half4(0)
*
* Trivial-ness is stackable. Somewhat large expressions can occasionally make the cut:
* - half4(myColor.a)
* - myStruct.myArrayField[7].xzy
*/
bool IsTrivialExpression(const Expression& expr);
/**
* Returns true if both expression trees are the same. Used by the optimizer to look for self-
* assignment or self-comparison; won't necessarily catch complex cases. Rejects expressions
* that may cause side effects.
*/
bool IsSameExpressionTree(const Expression& left, const Expression& right);
/**
* Returns true if expr is a constant-expression, as defined by GLSL 1.0, section 5.10.
* A constant expression is one of:
* - A literal value
* - A global or local variable qualified as 'const', excluding function parameters
* - An expression formed by an operator on operands that are constant expressions, including
* getting an element of a constant vector or a constant matrix, or a field of a constant
* structure
* - A constructor whose arguments are all constant expressions
* - A built-in function call whose arguments are all constant expressions, with the exception
* of the texture lookup functions
*/
bool IsConstantExpression(const Expression& expr);
/**
* Returns true if expr is a valid constant-index-expression, as defined by GLSL 1.0, Appendix A,
* Section 5. A constant-index-expression is:
* - A constant-expression
* - Loop indices (as defined in Appendix A, Section 4)
* - Expressions composed of both of the above
*/
bool IsConstantIndexExpression(const Expression& expr,
const std::set<const Variable*>* loopIndices);
/**
* Ensures that a for-loop meets the strict requirements of The OpenGL ES Shading Language 1.00,
* Appendix A, Section 4.
* If the requirements are met, information about the loop's structure is returned.
* If the requirements are not met, the problem is reported via `errors` (if not nullptr), and
* null is returned.
*/
std::unique_ptr<LoopUnrollInfo> GetLoopUnrollInfo(Position pos,
const ForLoopPositions& positions,
const Statement* loopInitializer,
const Expression* loopTest,
const Expression* loopNext,
const Statement* loopStatement,
ErrorReporter* errors);
void ValidateIndexingForES2(const ProgramElement& pe, ErrorReporter& errors);
/** Detects functions that fail to return a value on at least one path. */
bool CanExitWithoutReturningValue(const FunctionDeclaration& funcDecl, const Statement& body);
/** Determines if a given function has multiple and/or early returns. */
enum class ReturnComplexity {
kSingleSafeReturn,
kScopedReturns,
kEarlyReturns,
};
ReturnComplexity GetReturnComplexity(const FunctionDefinition& funcDef);
/**
* Runs at finalization time to perform any last-minute correctness checks:
* - Reports dangling FunctionReference or TypeReference expressions
* - Reports function `out` params which are never written to (structs are currently exempt)
*/
void DoFinalizationChecks(const Program& program);
/**
* Error checks compute shader in/outs and returns a vector containing them ordered by location.
*/
SkTArray<const SkSL::Variable*> GetComputeShaderMainParams(const Context& context,
const Program& program);
/**
* Tracks the symbol table stack, in conjunction with a ProgramVisitor. Inside `visitStatement`,
* pass the current statement and a symbol-table vector to a SymbolTableStackBuilder and the symbol
* table stack will be maintained automatically.
*/
class SymbolTableStackBuilder {
public:
// If the passed-in statement holds a symbol table, adds it to the stack.
SymbolTableStackBuilder(const Statement* stmt,
std::vector<std::shared_ptr<SymbolTable>>* stack);
// If a symbol table was added to the stack earlier, removes it from the stack.
~SymbolTableStackBuilder();
private:
std::vector<std::shared_ptr<SymbolTable>>* fStackToPop = nullptr;
};
} // namespace Analysis
} // namespace SkSL
#endif