| /* |
| * Copyright 2021 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 "src/sksl/SkSLConstantFolder.h" |
| #include "src/sksl/SkSLContext.h" |
| #include "src/sksl/SkSLProgramSettings.h" |
| #include "src/sksl/ir/SkSLBoolLiteral.h" |
| #include "src/sksl/ir/SkSLConstructorCompound.h" |
| #include "src/sksl/ir/SkSLFloatLiteral.h" |
| #include "src/sksl/ir/SkSLFunctionCall.h" |
| #include "src/sksl/ir/SkSLIntLiteral.h" |
| |
| namespace SkSL { |
| |
| static bool has_compile_time_constant_arguments(const ExpressionArray& arguments) { |
| for (const std::unique_ptr<Expression>& arg : arguments) { |
| const Expression* expr = ConstantFolder::GetConstantValueForVariable(*arg); |
| if (!expr->isCompileTimeConstant()) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| static std::unique_ptr<Expression> coalesce_bool_vector( |
| const ExpressionArray& arguments, |
| bool startingState, |
| const std::function<bool(bool, bool)>& coalesce) { |
| SkASSERT(arguments.size() == 1); |
| const Expression* arg = ConstantFolder::GetConstantValueForVariable(*arguments.front()); |
| const Type& type = arg->type(); |
| SkASSERT(type.isVector()); |
| SkASSERT(type.componentType().isBoolean()); |
| |
| bool value = startingState; |
| for (int index = 0; index < type.columns(); ++index) { |
| const Expression* subexpression = arg->getConstantSubexpression(index); |
| SkASSERT(subexpression); |
| value = coalesce(value, subexpression->as<BoolLiteral>().value()); |
| } |
| |
| return BoolLiteral::Make(arg->fOffset, value, &type.componentType()); |
| } |
| |
| template <typename LITERAL, typename FN> |
| static std::unique_ptr<Expression> optimize_comparison_of_type(const Context& context, |
| const Expression& left, |
| const Expression& right, |
| const FN& compare) { |
| const Type& type = left.type(); |
| SkASSERT(type.isVector()); |
| SkASSERT(type.componentType().isNumber()); |
| SkASSERT(type == right.type()); |
| |
| ExpressionArray result; |
| result.reserve_back(type.columns()); |
| |
| for (int index = 0; index < type.columns(); ++index) { |
| const Expression* leftSubexpr = left.getConstantSubexpression(index); |
| const Expression* rightSubexpr = right.getConstantSubexpression(index); |
| SkASSERT(leftSubexpr); |
| SkASSERT(rightSubexpr); |
| bool value = compare(leftSubexpr->as<LITERAL>().value(), |
| rightSubexpr->as<LITERAL>().value()); |
| result.push_back(BoolLiteral::Make(context, leftSubexpr->fOffset, value)); |
| } |
| |
| const Type& bvecType = context.fTypes.fBool->toCompound(context, type.columns(), /*rows=*/1); |
| return ConstructorCompound::Make(context, left.fOffset, bvecType, std::move(result)); |
| } |
| |
| template <typename FN> |
| static std::unique_ptr<Expression> optimize_comparison(const Context& context, |
| const ExpressionArray& arguments, |
| const FN& compare) { |
| SkASSERT(arguments.size() == 2); |
| const Expression* left = ConstantFolder::GetConstantValueForVariable(*arguments[0]); |
| const Expression* right = ConstantFolder::GetConstantValueForVariable(*arguments[1]); |
| const Type& type = left->type().componentType(); |
| |
| if (type.isFloat()) { |
| return optimize_comparison_of_type<FloatLiteral>(context, *left, *right, compare); |
| } |
| if (type.isInteger()) { |
| return optimize_comparison_of_type<IntLiteral>(context, *left, *right, compare); |
| } |
| SkDEBUGFAILF("unsupported type %s", type.description().c_str()); |
| return nullptr; |
| } |
| |
| template <typename LITERAL, typename FN> |
| static std::unique_ptr<Expression> evaluate_intrinsic_1_of_type(const Context& context, |
| const Expression* arg, |
| const FN& evaluate) { |
| const Type& vecType = arg->type(); |
| const Type& type = vecType.componentType(); |
| SkASSERT(type.isScalar()); |
| |
| ExpressionArray result; |
| result.reserve_back(vecType.columns()); |
| |
| for (int index = 0; index < vecType.columns(); ++index) { |
| const Expression* subexpr = arg->getConstantSubexpression(index); |
| SkASSERT(subexpr); |
| auto value = evaluate(subexpr->as<LITERAL>().value()); |
| if constexpr (std::is_floating_point<decltype(value)>::value) { |
| // If evaluation of the intrinsic yields a non-finite value, bail on optimization. |
| if (!isfinite(value)) { |
| return nullptr; |
| } |
| } |
| result.push_back(LITERAL::Make(subexpr->fOffset, value, &type)); |
| } |
| |
| return ConstructorCompound::Make(context, arg->fOffset, vecType, std::move(result)); |
| } |
| |
| template <typename FN, |
| bool kSupportsFloat = true, |
| bool kSupportsInt = true, |
| bool kSupportsBool = false> |
| static std::unique_ptr<Expression> evaluate_intrinsic_generic1(const Context& context, |
| const ExpressionArray& arguments, |
| const FN& evaluate) { |
| SkASSERT(arguments.size() == 1); |
| const Expression* arg = ConstantFolder::GetConstantValueForVariable(*arguments.front()); |
| const Type& type = arg->type().componentType(); |
| |
| if constexpr (kSupportsFloat) { |
| if (type.isFloat()) { |
| return evaluate_intrinsic_1_of_type<FloatLiteral>(context, arg, evaluate); |
| } |
| } |
| if constexpr (kSupportsInt) { |
| if (type.isInteger()) { |
| return evaluate_intrinsic_1_of_type<IntLiteral>(context, arg, evaluate); |
| } |
| } |
| if constexpr (kSupportsBool) { |
| if (type.isBoolean()) { |
| return evaluate_intrinsic_1_of_type<BoolLiteral>(context, arg, evaluate); |
| } |
| } |
| SkDEBUGFAILF("unsupported type %s", type.description().c_str()); |
| return nullptr; |
| } |
| |
| template <typename FN> |
| static std::unique_ptr<Expression> evaluate_intrinsic_float1(const Context& context, |
| const ExpressionArray& arguments, |
| const FN& evaluate) { |
| return evaluate_intrinsic_generic1<FN, |
| /*kSupportsFloat=*/true, |
| /*kSupportsInt=*/false, |
| /*kSupportsBool=*/false>(context, arguments, evaluate); |
| } |
| |
| template <typename FN> |
| static std::unique_ptr<Expression> evaluate_intrinsic_bool1(const Context& context, |
| const ExpressionArray& arguments, |
| const FN& evaluate) { |
| return evaluate_intrinsic_generic1<FN, |
| /*kSupportsFloat=*/false, |
| /*kSupportsInt=*/false, |
| /*kSupportsBool=*/true>(context, arguments, evaluate); |
| } |
| |
| static std::unique_ptr<Expression> optimize_intrinsic_call(const Context& context, |
| IntrinsicKind intrinsic, |
| const ExpressionArray& arguments) { |
| switch (intrinsic) { |
| case k_all_IntrinsicKind: |
| return coalesce_bool_vector(arguments, /*startingState=*/true, |
| [](bool a, bool b) { return a && b; }); |
| case k_any_IntrinsicKind: |
| return coalesce_bool_vector(arguments, /*startingState=*/false, |
| [](bool a, bool b) { return a || b; }); |
| case k_not_IntrinsicKind: |
| return evaluate_intrinsic_bool1(context, arguments, [](bool a) { return !a; }); |
| |
| case k_greaterThan_IntrinsicKind: |
| return optimize_comparison(context, arguments, [](auto a, auto b) { return a > b; }); |
| |
| case k_greaterThanEqual_IntrinsicKind: |
| return optimize_comparison(context, arguments, [](auto a, auto b) { return a >= b; }); |
| |
| case k_lessThan_IntrinsicKind: |
| return optimize_comparison(context, arguments, [](auto a, auto b) { return a < b; }); |
| |
| case k_lessThanEqual_IntrinsicKind: |
| return optimize_comparison(context, arguments, [](auto a, auto b) { return a <= b; }); |
| |
| case k_equal_IntrinsicKind: |
| return optimize_comparison(context, arguments, [](auto a, auto b) { return a == b; }); |
| |
| case k_notEqual_IntrinsicKind: |
| return optimize_comparison(context, arguments, [](auto a, auto b) { return a != b; }); |
| |
| case k_abs_IntrinsicKind: |
| return evaluate_intrinsic_generic1(context, arguments, [](auto a) { return abs(a); }); |
| |
| case k_sign_IntrinsicKind: |
| return evaluate_intrinsic_generic1(context, arguments, |
| [](auto a) { return (a > 0) - (a < 0); }); |
| case k_sin_IntrinsicKind: |
| return evaluate_intrinsic_float1(context, arguments, [](float a) { return sin(a); }); |
| |
| case k_cos_IntrinsicKind: |
| return evaluate_intrinsic_float1(context, arguments, [](float a) { return cos(a); }); |
| |
| case k_tan_IntrinsicKind: |
| return evaluate_intrinsic_float1(context, arguments, [](float a) { return tan(a); }); |
| |
| case k_asin_IntrinsicKind: |
| return evaluate_intrinsic_float1(context, arguments, [](float a) { return asin(a); }); |
| |
| case k_acos_IntrinsicKind: |
| return evaluate_intrinsic_float1(context, arguments, [](float a) { return acos(a); }); |
| |
| case k_sinh_IntrinsicKind: |
| return evaluate_intrinsic_float1(context, arguments, [](float a) { return sinh(a); }); |
| |
| case k_cosh_IntrinsicKind: |
| return evaluate_intrinsic_float1(context, arguments, [](float a) { return cosh(a); }); |
| |
| case k_tanh_IntrinsicKind: |
| return evaluate_intrinsic_float1(context, arguments, [](float a) { return tanh(a); }); |
| |
| case k_ceil_IntrinsicKind: |
| return evaluate_intrinsic_float1(context, arguments, [](float a) { return ceil(a); }); |
| |
| case k_floor_IntrinsicKind: |
| return evaluate_intrinsic_float1(context, arguments, [](float a) { return floor(a); }); |
| |
| case k_fract_IntrinsicKind: |
| return evaluate_intrinsic_float1(context, arguments, |
| [](float a) { return a - floor(a); }); |
| case k_trunc_IntrinsicKind: |
| return evaluate_intrinsic_float1(context, arguments, [](float a) { return trunc(a); }); |
| |
| case k_exp_IntrinsicKind: |
| return evaluate_intrinsic_float1(context, arguments, [](float a) { return exp(a); }); |
| |
| case k_log_IntrinsicKind: |
| return evaluate_intrinsic_float1(context, arguments, [](float a) { return log(a); }); |
| |
| case k_exp2_IntrinsicKind: |
| return evaluate_intrinsic_float1(context, arguments, [](float a) { return exp2(a); }); |
| |
| case k_log2_IntrinsicKind: |
| return evaluate_intrinsic_float1(context, arguments, [](float a) { return log2(a); }); |
| |
| case k_saturate_IntrinsicKind: |
| return evaluate_intrinsic_float1(context, arguments, |
| [](float a) { return (a < 0) ? 0 : (a > 1) ? 1 : a; }); |
| case k_round_IntrinsicKind: // GLSL `round` documents its rounding mode as unspecified |
| case k_roundEven_IntrinsicKind: // and is allowed to behave identically to `roundEven`. |
| return evaluate_intrinsic_float1(context, arguments, |
| [](float a) { return round(a / 2) * 2; }); |
| case k_inversesqrt_IntrinsicKind: |
| return evaluate_intrinsic_float1(context, arguments, |
| [](float a) { return 1 / sqrt(a); }); |
| case k_radians_IntrinsicKind: |
| return evaluate_intrinsic_float1(context, arguments, |
| [](float a) { return a * 0.0174532925; }); |
| case k_degrees_IntrinsicKind: |
| return evaluate_intrinsic_float1(context, arguments, |
| [](float a) { return a * 57.2957795; }); |
| default: |
| return nullptr; |
| } |
| } |
| |
| bool FunctionCall::hasProperty(Property property) const { |
| if (property == Property::kSideEffects && |
| (this->function().modifiers().fFlags & Modifiers::kHasSideEffects_Flag)) { |
| return true; |
| } |
| for (const auto& arg : this->arguments()) { |
| if (arg->hasProperty(property)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| std::unique_ptr<Expression> FunctionCall::clone() const { |
| ExpressionArray cloned; |
| cloned.reserve_back(this->arguments().size()); |
| for (const std::unique_ptr<Expression>& arg : this->arguments()) { |
| cloned.push_back(arg->clone()); |
| } |
| return std::make_unique<FunctionCall>( |
| fOffset, &this->type(), &this->function(), std::move(cloned)); |
| } |
| |
| String FunctionCall::description() const { |
| String result = String(this->function().name()) + "("; |
| String separator; |
| for (const std::unique_ptr<Expression>& arg : this->arguments()) { |
| result += separator; |
| result += arg->description(); |
| separator = ", "; |
| } |
| result += ")"; |
| return result; |
| } |
| |
| std::unique_ptr<Expression> FunctionCall::Convert(const Context& context, |
| int offset, |
| const FunctionDeclaration& function, |
| ExpressionArray arguments) { |
| // Reject function calls with the wrong number of arguments. |
| if (function.parameters().size() != arguments.size()) { |
| String msg = "call to '" + function.name() + "' expected " + |
| to_string((int)function.parameters().size()) + " argument"; |
| if (function.parameters().size() != 1) { |
| msg += "s"; |
| } |
| msg += ", but found " + to_string(arguments.count()); |
| context.fErrors.error(offset, msg); |
| return nullptr; |
| } |
| |
| // GLSL ES 1.0 requires static recursion be rejected by the compiler. Also, our CPU back-end |
| // cannot handle recursion (and is tied to strictES2Mode front-ends). The safest way to reject |
| // all (potentially) recursive code is to disallow calls to functions before they're defined. |
| if (context.fConfig->strictES2Mode() && !function.definition() && !function.isBuiltin()) { |
| context.fErrors.error(offset, "call to undefined function '" + function.name() + "'"); |
| return nullptr; |
| } |
| |
| // Resolve generic types. |
| FunctionDeclaration::ParamTypes types; |
| const Type* returnType; |
| if (!function.determineFinalTypes(arguments, &types, &returnType)) { |
| String msg = "no match for " + function.name() + "("; |
| String separator; |
| for (const std::unique_ptr<Expression>& arg : arguments) { |
| msg += separator; |
| msg += arg->type().displayName(); |
| separator = ", "; |
| } |
| msg += ")"; |
| context.fErrors.error(offset, msg); |
| return nullptr; |
| } |
| |
| for (size_t i = 0; i < arguments.size(); i++) { |
| // Coerce each argument to the proper type. |
| arguments[i] = types[i]->coerceExpression(std::move(arguments[i]), context); |
| if (!arguments[i]) { |
| return nullptr; |
| } |
| // Update the refKind on out-parameters, and ensure that they are actually assignable. |
| const Modifiers& paramModifiers = function.parameters()[i]->modifiers(); |
| if (paramModifiers.fFlags & Modifiers::kOut_Flag) { |
| const VariableRefKind refKind = paramModifiers.fFlags & Modifiers::kIn_Flag |
| ? VariableReference::RefKind::kReadWrite |
| : VariableReference::RefKind::kPointer; |
| if (!Analysis::MakeAssignmentExpr(arguments[i].get(), refKind, &context.fErrors)) { |
| return nullptr; |
| } |
| } |
| } |
| |
| return Make(context, offset, returnType, function, std::move(arguments)); |
| } |
| |
| std::unique_ptr<Expression> FunctionCall::Make(const Context& context, |
| int offset, |
| const Type* returnType, |
| const FunctionDeclaration& function, |
| ExpressionArray arguments) { |
| SkASSERT(function.parameters().size() == arguments.size()); |
| SkASSERT(function.definition() || function.isBuiltin() || !context.fConfig->strictES2Mode()); |
| |
| if (context.fConfig->fSettings.fOptimize) { |
| // We might be able to optimize built-in intrinsics. |
| if (function.isIntrinsic() && has_compile_time_constant_arguments(arguments)) { |
| // The function is an intrinsic and all inputs are compile-time constants. Optimize it. |
| if (std::unique_ptr<Expression> expr = |
| optimize_intrinsic_call(context, function.intrinsicKind(), arguments)) { |
| return expr; |
| } |
| } |
| } |
| |
| return std::make_unique<FunctionCall>(offset, returnType, &function, std::move(arguments)); |
| } |
| |
| } // namespace SkSL |