blob: bd385b1aaaab21875f6e620624cc0b089861e26d [file] [log] [blame]
/*
* 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