| /* |
| * Copyright 2020 Google LLC. |
| * |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| */ |
| |
| #include "src/sksl/SkSLAnalysis.h" |
| |
| #include "include/core/SkSpan.h" |
| #include "include/core/SkTypes.h" |
| #include "include/private/SkSLDefines.h" |
| #include "include/private/SkSLIRNode.h" |
| #include "include/private/SkSLLayout.h" |
| #include "include/private/SkSLModifiers.h" |
| #include "include/private/SkSLProgramElement.h" |
| #include "include/private/SkSLSampleUsage.h" |
| #include "include/private/SkSLStatement.h" |
| #include "include/private/base/SkTArray.h" |
| #include "include/sksl/SkSLErrorReporter.h" |
| #include "include/sksl/SkSLOperator.h" |
| #include "src/core/SkTHash.h" |
| #include "src/sksl/SkSLBuiltinTypes.h" |
| #include "src/sksl/SkSLCompiler.h" |
| #include "src/sksl/SkSLConstantFolder.h" |
| #include "src/sksl/SkSLContext.h" |
| #include "src/sksl/SkSLIntrinsicList.h" |
| #include "src/sksl/analysis/SkSLNoOpErrorReporter.h" |
| #include "src/sksl/analysis/SkSLProgramUsage.h" |
| #include "src/sksl/analysis/SkSLProgramVisitor.h" |
| #include "src/sksl/ir/SkSLBinaryExpression.h" |
| #include "src/sksl/ir/SkSLBlock.h" |
| #include "src/sksl/ir/SkSLChildCall.h" |
| #include "src/sksl/ir/SkSLConstructor.h" |
| #include "src/sksl/ir/SkSLDoStatement.h" |
| #include "src/sksl/ir/SkSLExpression.h" |
| #include "src/sksl/ir/SkSLExpressionStatement.h" |
| #include "src/sksl/ir/SkSLExternalFunctionCall.h" |
| #include "src/sksl/ir/SkSLFieldAccess.h" |
| #include "src/sksl/ir/SkSLForStatement.h" |
| #include "src/sksl/ir/SkSLFunctionCall.h" |
| #include "src/sksl/ir/SkSLFunctionDeclaration.h" |
| #include "src/sksl/ir/SkSLFunctionDefinition.h" |
| #include "src/sksl/ir/SkSLIfStatement.h" |
| #include "src/sksl/ir/SkSLIndexExpression.h" |
| #include "src/sksl/ir/SkSLPostfixExpression.h" |
| #include "src/sksl/ir/SkSLPrefixExpression.h" |
| #include "src/sksl/ir/SkSLProgram.h" |
| #include "src/sksl/ir/SkSLReturnStatement.h" |
| #include "src/sksl/ir/SkSLSwitchCase.h" |
| #include "src/sksl/ir/SkSLSwitchStatement.h" |
| #include "src/sksl/ir/SkSLSwizzle.h" |
| #include "src/sksl/ir/SkSLTernaryExpression.h" |
| #include "src/sksl/ir/SkSLType.h" |
| #include "src/sksl/ir/SkSLVarDeclarations.h" |
| #include "src/sksl/ir/SkSLVariable.h" |
| #include "src/sksl/ir/SkSLVariableReference.h" |
| #include "src/sksl/transform/SkSLProgramWriter.h" |
| |
| #include <optional> |
| #include <string> |
| #include <string_view> |
| |
| namespace SkSL { |
| |
| namespace { |
| |
| // Visitor that determines the merged SampleUsage for a given child in the program. |
| class MergeSampleUsageVisitor : public ProgramVisitor { |
| public: |
| MergeSampleUsageVisitor(const Context& context, |
| const Variable& child, |
| bool writesToSampleCoords) |
| : fContext(context), fChild(child), fWritesToSampleCoords(writesToSampleCoords) {} |
| |
| SampleUsage visit(const Program& program) { |
| fUsage = SampleUsage(); // reset to none |
| INHERITED::visit(program); |
| return fUsage; |
| } |
| |
| int elidedSampleCoordCount() const { return fElidedSampleCoordCount; } |
| |
| protected: |
| const Context& fContext; |
| const Variable& fChild; |
| const bool fWritesToSampleCoords; |
| SampleUsage fUsage; |
| int fElidedSampleCoordCount = 0; |
| |
| bool visitExpression(const Expression& e) override { |
| // Looking for child(...) |
| if (e.is<ChildCall>() && &e.as<ChildCall>().child() == &fChild) { |
| // Determine the type of call at this site, and merge it with the accumulated state |
| const ExpressionArray& arguments = e.as<ChildCall>().arguments(); |
| SkASSERT(arguments.size() >= 1); |
| |
| const Expression* maybeCoords = arguments[0].get(); |
| if (maybeCoords->type().matches(*fContext.fTypes.fFloat2)) { |
| // If the coords are a direct reference to the program's sample-coords, and those |
| // coords are never modified, we can conservatively turn this into PassThrough |
| // sampling. In all other cases, we consider it Explicit. |
| if (!fWritesToSampleCoords && maybeCoords->is<VariableReference>() && |
| maybeCoords->as<VariableReference>().variable()->modifiers().fLayout.fBuiltin == |
| SK_MAIN_COORDS_BUILTIN) { |
| fUsage.merge(SampleUsage::PassThrough()); |
| ++fElidedSampleCoordCount; |
| } else { |
| fUsage.merge(SampleUsage::Explicit()); |
| } |
| } else { |
| // child(inputColor) or child(srcColor, dstColor) -> PassThrough |
| fUsage.merge(SampleUsage::PassThrough()); |
| } |
| } |
| |
| return INHERITED::visitExpression(e); |
| } |
| |
| using INHERITED = ProgramVisitor; |
| }; |
| |
| // Visitor that searches for child calls from a function other than main() |
| class SampleOutsideMainVisitor : public ProgramVisitor { |
| public: |
| SampleOutsideMainVisitor() {} |
| |
| bool visitExpression(const Expression& e) override { |
| if (e.is<ChildCall>()) { |
| return true; |
| } |
| return INHERITED::visitExpression(e); |
| } |
| |
| bool visitProgramElement(const ProgramElement& p) override { |
| return p.is<FunctionDefinition>() && |
| !p.as<FunctionDefinition>().declaration().isMain() && |
| INHERITED::visitProgramElement(p); |
| } |
| |
| using INHERITED = ProgramVisitor; |
| }; |
| |
| class ReturnsNonOpaqueColorVisitor : public ProgramVisitor { |
| public: |
| ReturnsNonOpaqueColorVisitor() {} |
| |
| bool visitStatement(const Statement& s) override { |
| if (s.is<ReturnStatement>()) { |
| const Expression* e = s.as<ReturnStatement>().expression().get(); |
| bool knownOpaque = e && e->type().slotCount() == 4 && |
| ConstantFolder::GetConstantValueForVariable(*e) |
| ->getConstantValue(/*n=*/3) |
| .value_or(0) == 1; |
| return !knownOpaque; |
| } |
| return INHERITED::visitStatement(s); |
| } |
| |
| bool visitExpression(const Expression& e) override { |
| // No need to recurse into expressions, these can never contain return statements |
| return false; |
| } |
| |
| using INHERITED = ProgramVisitor; |
| using INHERITED::visitProgramElement; |
| }; |
| |
| // Visitor that counts the number of nodes visited |
| class NodeCountVisitor : public ProgramVisitor { |
| public: |
| NodeCountVisitor(int limit) : fLimit(limit) {} |
| |
| int visit(const Statement& s) { |
| this->visitStatement(s); |
| return fCount; |
| } |
| |
| bool visitExpression(const Expression& e) override { |
| ++fCount; |
| return (fCount >= fLimit) || INHERITED::visitExpression(e); |
| } |
| |
| bool visitProgramElement(const ProgramElement& p) override { |
| ++fCount; |
| return (fCount >= fLimit) || INHERITED::visitProgramElement(p); |
| } |
| |
| bool visitStatement(const Statement& s) override { |
| ++fCount; |
| return (fCount >= fLimit) || INHERITED::visitStatement(s); |
| } |
| |
| private: |
| int fCount = 0; |
| int fLimit; |
| |
| using INHERITED = ProgramVisitor; |
| }; |
| |
| class VariableWriteVisitor : public ProgramVisitor { |
| public: |
| VariableWriteVisitor(const Variable* var) |
| : fVar(var) {} |
| |
| bool visit(const Statement& s) { |
| return this->visitStatement(s); |
| } |
| |
| bool visitExpression(const Expression& e) override { |
| if (e.is<VariableReference>()) { |
| const VariableReference& ref = e.as<VariableReference>(); |
| if (ref.variable() == fVar && |
| (ref.refKind() == VariableReference::RefKind::kWrite || |
| ref.refKind() == VariableReference::RefKind::kReadWrite || |
| ref.refKind() == VariableReference::RefKind::kPointer)) { |
| return true; |
| } |
| } |
| return INHERITED::visitExpression(e); |
| } |
| |
| private: |
| const Variable* fVar; |
| |
| using INHERITED = ProgramVisitor; |
| }; |
| |
| // This isn't actually using ProgramVisitor, because it only considers a subset of the fields for |
| // any given expression kind. For instance, when indexing an array (e.g. `x[1]`), we only want to |
| // know if the base (`x`) is assignable; the index expression (`1`) doesn't need to be. |
| class IsAssignableVisitor { |
| public: |
| IsAssignableVisitor(ErrorReporter* errors) : fErrors(errors) {} |
| |
| bool visit(Expression& expr, Analysis::AssignmentInfo* info) { |
| int oldErrorCount = fErrors->errorCount(); |
| this->visitExpression(expr); |
| if (info) { |
| info->fAssignedVar = fAssignedVar; |
| } |
| return fErrors->errorCount() == oldErrorCount; |
| } |
| |
| void visitExpression(Expression& expr, const FieldAccess* fieldAccess = nullptr) { |
| switch (expr.kind()) { |
| case Expression::Kind::kVariableReference: { |
| VariableReference& varRef = expr.as<VariableReference>(); |
| const Variable* var = varRef.variable(); |
| auto fieldName = [&] { |
| return fieldAccess ? fieldAccess->description(OperatorPrecedence::kTopLevel) |
| : std::string(var->name()); |
| }; |
| if (var->modifiers().fFlags & (Modifiers::kConst_Flag | Modifiers::kUniform_Flag)) { |
| fErrors->error(expr.fPosition, |
| "cannot modify immutable variable '" + fieldName() + "'"); |
| } else if (var->storage() == Variable::Storage::kGlobal && |
| (var->modifiers().fFlags & Modifiers::kIn_Flag)) { |
| fErrors->error(expr.fPosition, |
| "cannot modify pipeline input variable '" + fieldName() + "'"); |
| } else { |
| SkASSERT(fAssignedVar == nullptr); |
| fAssignedVar = &varRef; |
| } |
| break; |
| } |
| case Expression::Kind::kFieldAccess: { |
| const FieldAccess& f = expr.as<FieldAccess>(); |
| this->visitExpression(*f.base(), &f); |
| break; |
| } |
| case Expression::Kind::kSwizzle: { |
| const Swizzle& swizzle = expr.as<Swizzle>(); |
| this->checkSwizzleWrite(swizzle); |
| this->visitExpression(*swizzle.base(), fieldAccess); |
| break; |
| } |
| case Expression::Kind::kIndex: |
| this->visitExpression(*expr.as<IndexExpression>().base(), fieldAccess); |
| break; |
| |
| case Expression::Kind::kPoison: |
| break; |
| |
| default: |
| fErrors->error(expr.fPosition, "cannot assign to this expression"); |
| break; |
| } |
| } |
| |
| private: |
| void checkSwizzleWrite(const Swizzle& swizzle) { |
| int bits = 0; |
| for (int8_t idx : swizzle.components()) { |
| SkASSERT(idx >= SwizzleComponent::X && idx <= SwizzleComponent::W); |
| int bit = 1 << idx; |
| if (bits & bit) { |
| fErrors->error(swizzle.fPosition, |
| "cannot write to the same swizzle field more than once"); |
| break; |
| } |
| bits |= bit; |
| } |
| } |
| |
| ErrorReporter* fErrors; |
| VariableReference* fAssignedVar = nullptr; |
| |
| using INHERITED = ProgramVisitor; |
| }; |
| |
| } // namespace |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // Analysis |
| |
| SampleUsage Analysis::GetSampleUsage(const Program& program, |
| const Variable& child, |
| bool writesToSampleCoords, |
| int* elidedSampleCoordCount) { |
| MergeSampleUsageVisitor visitor(*program.fContext, child, writesToSampleCoords); |
| SampleUsage result = visitor.visit(program); |
| if (elidedSampleCoordCount) { |
| *elidedSampleCoordCount += visitor.elidedSampleCoordCount(); |
| } |
| return result; |
| } |
| |
| bool Analysis::ReferencesBuiltin(const Program& program, int builtin) { |
| SkASSERT(program.fUsage); |
| for (const auto& [variable, counts] : program.fUsage->fVariableCounts) { |
| if (counts.fRead > 0 && variable->modifiers().fLayout.fBuiltin == builtin) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| bool Analysis::ReferencesSampleCoords(const Program& program) { |
| return Analysis::ReferencesBuiltin(program, SK_MAIN_COORDS_BUILTIN); |
| } |
| |
| bool Analysis::ReferencesFragCoords(const Program& program) { |
| return Analysis::ReferencesBuiltin(program, SK_FRAGCOORD_BUILTIN); |
| } |
| |
| bool Analysis::CallsSampleOutsideMain(const Program& program) { |
| SampleOutsideMainVisitor visitor; |
| return visitor.visit(program); |
| } |
| |
| bool Analysis::CallsColorTransformIntrinsics(const Program& program) { |
| for (auto [fn, count] : program.usage()->fCallCounts) { |
| if (count != 0 && (fn->intrinsicKind() == k_toLinearSrgb_IntrinsicKind || |
| fn->intrinsicKind() == k_fromLinearSrgb_IntrinsicKind)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| bool Analysis::ReturnsOpaqueColor(const FunctionDefinition& function) { |
| ReturnsNonOpaqueColorVisitor visitor; |
| return !visitor.visitProgramElement(function); |
| } |
| |
| bool Analysis::ContainsRTAdjust(const Expression& expr) { |
| class ContainsRTAdjustVisitor : public ProgramVisitor { |
| public: |
| bool visitExpression(const Expression& expr) override { |
| if (expr.is<VariableReference>() && |
| expr.as<VariableReference>().variable()->name() == Compiler::RTADJUST_NAME) { |
| return true; |
| } |
| return INHERITED::visitExpression(expr); |
| } |
| |
| using INHERITED = ProgramVisitor; |
| }; |
| |
| ContainsRTAdjustVisitor visitor; |
| return visitor.visitExpression(expr); |
| } |
| |
| bool Analysis::IsCompileTimeConstant(const Expression& expr) { |
| class IsCompileTimeConstantVisitor : public ProgramVisitor { |
| public: |
| bool visitExpression(const Expression& expr) override { |
| switch (expr.kind()) { |
| case Expression::Kind::kLiteral: |
| // Literals are compile-time constants. |
| return false; |
| |
| case Expression::Kind::kConstructorArray: |
| case Expression::Kind::kConstructorCompound: |
| case Expression::Kind::kConstructorDiagonalMatrix: |
| case Expression::Kind::kConstructorMatrixResize: |
| case Expression::Kind::kConstructorSplat: |
| case Expression::Kind::kConstructorStruct: |
| // Constructors might be compile-time constants, if they are composed entirely |
| // of literals and constructors. (Casting constructors are intentionally omitted |
| // here. If the value inside was a compile-time constant, we would have not have |
| // generated a cast at all.) |
| return INHERITED::visitExpression(expr); |
| |
| default: |
| // This expression isn't a compile-time constant. |
| fIsConstant = false; |
| return true; |
| } |
| } |
| |
| bool fIsConstant = true; |
| using INHERITED = ProgramVisitor; |
| }; |
| |
| IsCompileTimeConstantVisitor visitor; |
| visitor.visitExpression(expr); |
| return visitor.fIsConstant; |
| } |
| |
| bool Analysis::DetectVarDeclarationWithoutScope(const Statement& stmt, ErrorReporter* errors) { |
| // A variable declaration can create either a lone VarDeclaration or an unscoped Block |
| // containing multiple VarDeclaration statements. We need to detect either case. |
| const Variable* var; |
| if (stmt.is<VarDeclaration>()) { |
| // The single-variable case. No blocks at all. |
| var = stmt.as<VarDeclaration>().var(); |
| } else if (stmt.is<Block>()) { |
| // The multiple-variable case: an unscoped, non-empty block... |
| const Block& block = stmt.as<Block>(); |
| if (block.isScope() || block.children().empty()) { |
| return false; |
| } |
| // ... holding a variable declaration. |
| const Statement& innerStmt = *block.children().front(); |
| if (!innerStmt.is<VarDeclaration>()) { |
| return false; |
| } |
| var = innerStmt.as<VarDeclaration>().var(); |
| } else { |
| // This statement wasn't a variable declaration. No problem. |
| return false; |
| } |
| |
| // Report an error. |
| SkASSERT(var); |
| if (errors) { |
| errors->error(var->fPosition, |
| "variable '" + std::string(var->name()) + "' must be created in a scope"); |
| } |
| return true; |
| } |
| |
| int Analysis::NodeCountUpToLimit(const FunctionDefinition& function, int limit) { |
| return NodeCountVisitor{limit}.visit(*function.body()); |
| } |
| |
| bool Analysis::StatementWritesToVariable(const Statement& stmt, const Variable& var) { |
| return VariableWriteVisitor(&var).visit(stmt); |
| } |
| |
| bool Analysis::IsAssignable(Expression& expr, AssignmentInfo* info, ErrorReporter* errors) { |
| NoOpErrorReporter unusedErrors; |
| return IsAssignableVisitor{errors ? errors : &unusedErrors}.visit(expr, info); |
| } |
| |
| bool Analysis::UpdateVariableRefKind(Expression* expr, |
| VariableReference::RefKind kind, |
| ErrorReporter* errors) { |
| Analysis::AssignmentInfo info; |
| if (!Analysis::IsAssignable(*expr, &info, errors)) { |
| return false; |
| } |
| if (!info.fAssignedVar) { |
| if (errors) { |
| errors->error(expr->fPosition, "can't assign to expression '" + expr->description() + |
| "'"); |
| } |
| return false; |
| } |
| info.fAssignedVar->setRefKind(kind); |
| return true; |
| } |
| |
| class ES2IndexingVisitor : public ProgramVisitor { |
| public: |
| ES2IndexingVisitor(ErrorReporter& errors) : fErrors(errors) {} |
| |
| bool visitStatement(const Statement& s) override { |
| if (s.is<ForStatement>()) { |
| const ForStatement& f = s.as<ForStatement>(); |
| SkASSERT(f.initializer() && f.initializer()->is<VarDeclaration>()); |
| const Variable* var = f.initializer()->as<VarDeclaration>().var(); |
| auto [iter, inserted] = fLoopIndices.insert(var); |
| SkASSERT(inserted); |
| bool result = this->visitStatement(*f.statement()); |
| fLoopIndices.erase(iter); |
| return result; |
| } |
| return INHERITED::visitStatement(s); |
| } |
| |
| bool visitExpression(const Expression& e) override { |
| if (e.is<IndexExpression>()) { |
| const IndexExpression& i = e.as<IndexExpression>(); |
| if (!Analysis::IsConstantIndexExpression(*i.index(), &fLoopIndices)) { |
| fErrors.error(i.fPosition, "index expression must be constant"); |
| return true; |
| } |
| } |
| return INHERITED::visitExpression(e); |
| } |
| |
| using ProgramVisitor::visitProgramElement; |
| |
| private: |
| ErrorReporter& fErrors; |
| std::set<const Variable*> fLoopIndices; |
| using INHERITED = ProgramVisitor; |
| }; |
| |
| void Analysis::ValidateIndexingForES2(const ProgramElement& pe, ErrorReporter& errors) { |
| ES2IndexingVisitor visitor(errors); |
| visitor.visitProgramElement(pe); |
| } |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // ProgramVisitor |
| |
| bool ProgramVisitor::visit(const Program& program) { |
| for (const ProgramElement* pe : program.elements()) { |
| if (this->visitProgramElement(*pe)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| template <typename T> bool TProgramVisitor<T>::visitExpression(typename T::Expression& e) { |
| switch (e.kind()) { |
| case Expression::Kind::kExternalFunctionReference: |
| case Expression::Kind::kFunctionReference: |
| case Expression::Kind::kLiteral: |
| case Expression::Kind::kMethodReference: |
| case Expression::Kind::kPoison: |
| case Expression::Kind::kSetting: |
| case Expression::Kind::kTypeReference: |
| case Expression::Kind::kVariableReference: |
| // Leaf expressions return false |
| return false; |
| |
| case Expression::Kind::kBinary: { |
| auto& b = e.template as<BinaryExpression>(); |
| return (b.left() && this->visitExpressionPtr(b.left())) || |
| (b.right() && this->visitExpressionPtr(b.right())); |
| } |
| case Expression::Kind::kChildCall: { |
| // We don't visit the child variable itself, just the arguments |
| auto& c = e.template as<ChildCall>(); |
| for (auto& arg : c.arguments()) { |
| if (arg && this->visitExpressionPtr(arg)) { return true; } |
| } |
| return false; |
| } |
| case Expression::Kind::kConstructorArray: |
| case Expression::Kind::kConstructorArrayCast: |
| case Expression::Kind::kConstructorCompound: |
| case Expression::Kind::kConstructorCompoundCast: |
| case Expression::Kind::kConstructorDiagonalMatrix: |
| case Expression::Kind::kConstructorMatrixResize: |
| case Expression::Kind::kConstructorScalarCast: |
| case Expression::Kind::kConstructorSplat: |
| case Expression::Kind::kConstructorStruct: { |
| auto& c = e.asAnyConstructor(); |
| for (auto& arg : c.argumentSpan()) { |
| if (this->visitExpressionPtr(arg)) { return true; } |
| } |
| return false; |
| } |
| case Expression::Kind::kExternalFunctionCall: { |
| auto& c = e.template as<ExternalFunctionCall>(); |
| for (auto& arg : c.arguments()) { |
| if (this->visitExpressionPtr(arg)) { return true; } |
| } |
| return false; |
| } |
| case Expression::Kind::kFieldAccess: |
| return this->visitExpressionPtr(e.template as<FieldAccess>().base()); |
| |
| case Expression::Kind::kFunctionCall: { |
| auto& c = e.template as<FunctionCall>(); |
| for (auto& arg : c.arguments()) { |
| if (arg && this->visitExpressionPtr(arg)) { return true; } |
| } |
| return false; |
| } |
| case Expression::Kind::kIndex: { |
| auto& i = e.template as<IndexExpression>(); |
| return this->visitExpressionPtr(i.base()) || this->visitExpressionPtr(i.index()); |
| } |
| case Expression::Kind::kPostfix: |
| return this->visitExpressionPtr(e.template as<PostfixExpression>().operand()); |
| |
| case Expression::Kind::kPrefix: |
| return this->visitExpressionPtr(e.template as<PrefixExpression>().operand()); |
| |
| case Expression::Kind::kSwizzle: { |
| auto& s = e.template as<Swizzle>(); |
| return s.base() && this->visitExpressionPtr(s.base()); |
| } |
| |
| case Expression::Kind::kTernary: { |
| auto& t = e.template as<TernaryExpression>(); |
| return this->visitExpressionPtr(t.test()) || |
| (t.ifTrue() && this->visitExpressionPtr(t.ifTrue())) || |
| (t.ifFalse() && this->visitExpressionPtr(t.ifFalse())); |
| } |
| default: |
| SkUNREACHABLE; |
| } |
| } |
| |
| template <typename T> bool TProgramVisitor<T>::visitStatement(typename T::Statement& s) { |
| switch (s.kind()) { |
| case Statement::Kind::kBreak: |
| case Statement::Kind::kContinue: |
| case Statement::Kind::kDiscard: |
| case Statement::Kind::kNop: |
| // Leaf statements just return false |
| return false; |
| |
| case Statement::Kind::kBlock: |
| for (auto& stmt : s.template as<Block>().children()) { |
| if (stmt && this->visitStatementPtr(stmt)) { |
| return true; |
| } |
| } |
| return false; |
| |
| case Statement::Kind::kSwitchCase: { |
| auto& sc = s.template as<SwitchCase>(); |
| return this->visitStatementPtr(sc.statement()); |
| } |
| case Statement::Kind::kDo: { |
| auto& d = s.template as<DoStatement>(); |
| return this->visitExpressionPtr(d.test()) || this->visitStatementPtr(d.statement()); |
| } |
| case Statement::Kind::kExpression: |
| return this->visitExpressionPtr(s.template as<ExpressionStatement>().expression()); |
| |
| case Statement::Kind::kFor: { |
| auto& f = s.template as<ForStatement>(); |
| return (f.initializer() && this->visitStatementPtr(f.initializer())) || |
| (f.test() && this->visitExpressionPtr(f.test())) || |
| (f.next() && this->visitExpressionPtr(f.next())) || |
| this->visitStatementPtr(f.statement()); |
| } |
| case Statement::Kind::kIf: { |
| auto& i = s.template as<IfStatement>(); |
| return (i.test() && this->visitExpressionPtr(i.test())) || |
| (i.ifTrue() && this->visitStatementPtr(i.ifTrue())) || |
| (i.ifFalse() && this->visitStatementPtr(i.ifFalse())); |
| } |
| case Statement::Kind::kReturn: { |
| auto& r = s.template as<ReturnStatement>(); |
| return r.expression() && this->visitExpressionPtr(r.expression()); |
| } |
| case Statement::Kind::kSwitch: { |
| auto& sw = s.template as<SwitchStatement>(); |
| if (this->visitExpressionPtr(sw.value())) { |
| return true; |
| } |
| for (auto& c : sw.cases()) { |
| if (this->visitStatementPtr(c)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| case Statement::Kind::kVarDeclaration: { |
| auto& v = s.template as<VarDeclaration>(); |
| return v.value() && this->visitExpressionPtr(v.value()); |
| } |
| default: |
| SkUNREACHABLE; |
| } |
| } |
| |
| template <typename T> bool TProgramVisitor<T>::visitProgramElement(typename T::ProgramElement& pe) { |
| switch (pe.kind()) { |
| case ProgramElement::Kind::kExtension: |
| case ProgramElement::Kind::kFunctionPrototype: |
| case ProgramElement::Kind::kInterfaceBlock: |
| case ProgramElement::Kind::kModifiers: |
| case ProgramElement::Kind::kStructDefinition: |
| // Leaf program elements just return false by default |
| return false; |
| |
| case ProgramElement::Kind::kFunction: |
| return this->visitStatementPtr(pe.template as<FunctionDefinition>().body()); |
| |
| case ProgramElement::Kind::kGlobalVar: |
| return this->visitStatementPtr(pe.template as<GlobalVarDeclaration>().declaration()); |
| |
| default: |
| SkUNREACHABLE; |
| } |
| } |
| |
| template class TProgramVisitor<ProgramVisitorTypes>; |
| template class TProgramVisitor<ProgramWriterTypes>; |
| |
| } // namespace SkSL |