| /* |
| * 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/ir/SkSLFunctionDeclaration.h" |
| |
| #include "include/core/SkSpan.h" |
| #include "include/core/SkTypes.h" |
| #include "include/private/base/SkTo.h" |
| #include "src/base/SkEnumBitMask.h" |
| #include "src/base/SkStringView.h" |
| #include "src/sksl/SkSLBuiltinTypes.h" |
| #include "src/sksl/SkSLContext.h" |
| #include "src/sksl/SkSLDefines.h" |
| #include "src/sksl/SkSLErrorReporter.h" |
| #include "src/sksl/SkSLPosition.h" |
| #include "src/sksl/SkSLProgramKind.h" |
| #include "src/sksl/SkSLProgramSettings.h" |
| #include "src/sksl/SkSLString.h" |
| #include "src/sksl/ir/SkSLExpression.h" |
| #include "src/sksl/ir/SkSLLayout.h" |
| #include "src/sksl/ir/SkSLModifierFlags.h" |
| #include "src/sksl/ir/SkSLModifiers.h" |
| #include "src/sksl/ir/SkSLSymbolTable.h" |
| #include "src/sksl/ir/SkSLType.h" |
| #include "src/sksl/ir/SkSLVariable.h" |
| |
| #include <cstddef> |
| #include <utility> |
| |
| using namespace skia_private; |
| |
| namespace SkSL { |
| |
| static bool check_modifiers(const Context& context, Position pos, ModifierFlags modifierFlags) { |
| const ModifierFlags permitted = ModifierFlag::kInline | |
| ModifierFlag::kNoInline | |
| (context.fConfig->fIsBuiltinCode ? ModifierFlag::kES3 | |
| ModifierFlag::kPure | |
| ModifierFlag::kExport |
| : ModifierFlag::kNone); |
| modifierFlags.checkPermittedFlags(context, pos, permitted); |
| if (modifierFlags.isInline() && modifierFlags.isNoInline()) { |
| context.fErrors->error(pos, "functions cannot be both 'inline' and 'noinline'"); |
| return false; |
| } |
| return true; |
| } |
| |
| static bool check_return_type(const Context& context, Position pos, const Type& returnType) { |
| ErrorReporter& errors = *context.fErrors; |
| if (returnType.isArray()) { |
| errors.error(pos, "functions may not return type '" + returnType.displayName() + "'"); |
| return false; |
| } |
| if (context.fConfig->strictES2Mode() && returnType.isOrContainsArray()) { |
| errors.error(pos, "functions may not return structs containing arrays"); |
| return false; |
| } |
| if (!context.fConfig->fIsBuiltinCode && returnType.componentType().isOpaque()) { |
| errors.error(pos, "functions may not return opaque type '" + returnType.displayName() + |
| "'"); |
| return false; |
| } |
| return true; |
| } |
| |
| static bool check_parameters(const Context& context, |
| TArray<std::unique_ptr<Variable>>& parameters, |
| ModifierFlags modifierFlags, |
| IntrinsicKind intrinsicKind) { |
| // Check modifiers on each function parameter. |
| for (auto& param : parameters) { |
| const Type& type = param->type(); |
| ModifierFlags permittedFlags = ModifierFlag::kConst | ModifierFlag::kIn; |
| LayoutFlags permittedLayoutFlags = LayoutFlag::kNone; |
| if (!type.isOpaque()) { |
| permittedFlags |= ModifierFlag::kOut; |
| } |
| if (type.isStorageTexture()) { |
| // We allow `readonly`, `writeonly` and `layout(pixel-format)` on storage textures. |
| permittedFlags |= ModifierFlag::kReadOnly | ModifierFlag::kWriteOnly; |
| permittedLayoutFlags |= LayoutFlag::kAllPixelFormats; |
| |
| // Intrinsics are allowed to accept any pixel format, but user code must explicitly |
| // specify a pixel format like `layout(rgba32f)`. |
| if (intrinsicKind == kNotIntrinsic && |
| !(param->layout().fFlags & LayoutFlag::kAllPixelFormats)) { |
| context.fErrors->error(param->fPosition, "storage texture parameters must specify " |
| "a pixel format layout-qualifier"); |
| return false; |
| } |
| } |
| param->modifierFlags().checkPermittedFlags(context, param->modifiersPosition(), |
| permittedFlags); |
| param->layout().checkPermittedLayout(context, param->modifiersPosition(), |
| permittedLayoutFlags); |
| // Only the (builtin) declarations of 'sample' are allowed to have shader/colorFilter or FP |
| // parameters. You can pass other opaque types to functions safely; this restriction is |
| // specific to "child" objects. |
| if (type.isEffectChild() && !context.fConfig->fIsBuiltinCode) { |
| context.fErrors->error(param->fPosition, "parameters of type '" + type.displayName() + |
| "' not allowed"); |
| return false; |
| } |
| |
| // Pure functions should not change any state, and should be safe to eliminate if their |
| // result is not used; this is incompatible with out-parameters, so we forbid it here. |
| // (We don't exhaustively guard against pure functions changing global state in other ways, |
| // though, since they aren't allowed in user code.) |
| if (modifierFlags.isPure() && (param->modifierFlags() & ModifierFlag::kOut)) { |
| context.fErrors->error(param->modifiersPosition(), |
| "pure functions cannot have out parameters"); |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| static bool type_is_valid_for_color(const Type& type) { |
| return type.isVector() && type.columns() == 4 && type.componentType().isFloat(); |
| } |
| |
| static bool type_is_valid_for_coords(const Type& type) { |
| return type.isVector() && type.highPrecision() && type.columns() == 2 && |
| type.componentType().isFloat(); |
| } |
| |
| static bool check_main_signature(const Context& context, Position pos, const Type& returnType, |
| TArray<std::unique_ptr<Variable>>& parameters) { |
| ErrorReporter& errors = *context.fErrors; |
| ProgramKind kind = context.fConfig->fKind; |
| |
| auto typeIsValidForAttributes = [](const Type& type) { |
| return type.isStruct() && type.name() == "Attributes"; |
| }; |
| |
| auto typeIsValidForVaryings = [](const Type& type) { |
| return type.isStruct() && type.name() == "Varyings"; |
| }; |
| |
| auto paramIsCoords = [&](int idx) { |
| const Variable& p = *parameters[idx]; |
| return type_is_valid_for_coords(p.type()) && p.modifierFlags() == ModifierFlag::kNone; |
| }; |
| |
| auto paramIsColor = [&](int idx) { |
| const Variable& p = *parameters[idx]; |
| return type_is_valid_for_color(p.type()) && p.modifierFlags() == ModifierFlag::kNone; |
| }; |
| |
| auto paramIsConstInAttributes = [&](int idx) { |
| const Variable& p = *parameters[idx]; |
| return typeIsValidForAttributes(p.type()) && p.modifierFlags() == ModifierFlag::kConst; |
| }; |
| |
| auto paramIsConstInVaryings = [&](int idx) { |
| const Variable& p = *parameters[idx]; |
| return typeIsValidForVaryings(p.type()) && p.modifierFlags() == ModifierFlag::kConst; |
| }; |
| |
| auto paramIsOutColor = [&](int idx) { |
| const Variable& p = *parameters[idx]; |
| return type_is_valid_for_color(p.type()) && p.modifierFlags() == ModifierFlag::kOut; |
| }; |
| |
| switch (kind) { |
| case ProgramKind::kRuntimeColorFilter: |
| case ProgramKind::kPrivateRuntimeColorFilter: { |
| // (half4|float4) main(half4|float4) |
| if (!type_is_valid_for_color(returnType)) { |
| errors.error(pos, "'main' must return: 'vec4', 'float4', or 'half4'"); |
| return false; |
| } |
| bool validParams = (parameters.size() == 1 && paramIsColor(0)); |
| if (!validParams) { |
| errors.error(pos, "'main' parameter must be 'vec4', 'float4', or 'half4'"); |
| return false; |
| } |
| break; |
| } |
| case ProgramKind::kRuntimeShader: |
| case ProgramKind::kPrivateRuntimeShader: { |
| // (half4|float4) main(float2) |
| if (!type_is_valid_for_color(returnType)) { |
| errors.error(pos, "'main' must return: 'vec4', 'float4', or 'half4'"); |
| return false; |
| } |
| if (!(parameters.size() == 1 && paramIsCoords(0))) { |
| errors.error(pos, "'main' parameter must be 'float2' or 'vec2'"); |
| return false; |
| } |
| break; |
| } |
| case ProgramKind::kRuntimeBlender: |
| case ProgramKind::kPrivateRuntimeBlender: { |
| // (half4|float4) main(half4|float4, half4|float4) |
| if (!type_is_valid_for_color(returnType)) { |
| errors.error(pos, "'main' must return: 'vec4', 'float4', or 'half4'"); |
| return false; |
| } |
| if (!(parameters.size() == 2 && paramIsColor(0) && paramIsColor(1))) { |
| errors.error(pos, "'main' parameters must be (vec4|float4|half4, " |
| "vec4|float4|half4)"); |
| return false; |
| } |
| break; |
| } |
| case ProgramKind::kMeshVertex: { |
| // Varyings main(const Attributes) |
| if (!typeIsValidForVaryings(returnType)) { |
| errors.error(pos, "'main' must return 'Varyings'."); |
| return false; |
| } |
| if (!(parameters.size() == 1 && paramIsConstInAttributes(0))) { |
| errors.error(pos, "'main' parameter must be 'const Attributes'."); |
| return false; |
| } |
| break; |
| } |
| case ProgramKind::kMeshFragment: { |
| // float2 main(const Varyings) -or- float2 main(const Varyings, out half4|float4) |
| if (!type_is_valid_for_coords(returnType)) { |
| errors.error(pos, "'main' must return: 'vec2' or 'float2'"); |
| return false; |
| } |
| if (!((parameters.size() == 1 && paramIsConstInVaryings(0)) || |
| (parameters.size() == 2 && paramIsConstInVaryings(0) && paramIsOutColor(1)))) { |
| errors.error(pos, |
| "'main' parameters must be (const Varyings, (out (half4|float4))?)"); |
| return false; |
| } |
| break; |
| } |
| case ProgramKind::kFragment: |
| case ProgramKind::kGraphiteFragment: { |
| bool validParams = (parameters.size() == 0) || |
| (parameters.size() == 1 && paramIsCoords(0)); |
| if (!validParams) { |
| errors.error(pos, "shader 'main' must be main() or main(float2)"); |
| return false; |
| } |
| break; |
| } |
| case ProgramKind::kVertex: |
| case ProgramKind::kGraphiteVertex: |
| case ProgramKind::kCompute: |
| if (!returnType.matches(*context.fTypes.fVoid)) { |
| errors.error(pos, "'main' must return 'void'"); |
| return false; |
| } |
| if (parameters.size()) { |
| errors.error(pos, "shader 'main' must have zero parameters"); |
| return false; |
| } |
| break; |
| } |
| return true; |
| } |
| |
| /** |
| * Given a concrete type (`float3`) and a generic type (`$genType`), returns the index of the |
| * concrete type within the generic type's typelist. Returns -1 if there is no match. |
| */ |
| static int find_generic_index(const Type& concreteType, |
| const Type& genericType, |
| bool allowNarrowing) { |
| SkSpan<const Type* const> genericTypes = genericType.coercibleTypes(); |
| for (size_t index = 0; index < genericTypes.size(); ++index) { |
| if (concreteType.canCoerceTo(*genericTypes[index], allowNarrowing)) { |
| return index; |
| } |
| } |
| return -1; |
| } |
| |
| /** Returns true if the types match, or if `concreteType` can be found in `maybeGenericType`. */ |
| static bool type_generically_matches(const Type& concreteType, const Type& maybeGenericType) { |
| return maybeGenericType.isGeneric() |
| ? find_generic_index(concreteType, maybeGenericType, /*allowNarrowing=*/false) != -1 |
| : concreteType.matches(maybeGenericType); |
| } |
| |
| /** |
| * Checks a parameter list (params) against the parameters of a function that was declared earlier |
| * (otherParams). Returns true if they match, even if the parameters in `otherParams` contain |
| * generic types. |
| */ |
| static bool parameters_match(SkSpan<const std::unique_ptr<Variable>> params, |
| SkSpan<Variable* const> otherParams) { |
| // If the param lists are different lengths, they're definitely not a match. |
| if (params.size() != otherParams.size()) { |
| return false; |
| } |
| |
| // Figure out a consistent generic index (or bail if we find a contradiction). |
| int genericIndex = -1; |
| for (size_t i = 0; i < params.size(); ++i) { |
| const Type* paramType = ¶ms[i]->type(); |
| const Type* otherParamType = &otherParams[i]->type(); |
| |
| if (otherParamType->isGeneric()) { |
| int genericIndexForThisParam = find_generic_index(*paramType, *otherParamType, |
| /*allowNarrowing=*/false); |
| if (genericIndexForThisParam == -1) { |
| // The type wasn't a match for this generic at all; these params can't be a match. |
| return false; |
| } |
| if (genericIndex != -1 && genericIndex != genericIndexForThisParam) { |
| // The generic index mismatches from what we determined on a previous parameter. |
| return false; |
| } |
| genericIndex = genericIndexForThisParam; |
| } |
| } |
| |
| // Now that we've determined a generic index (if we needed one), do a parameter check. |
| for (size_t i = 0; i < params.size(); i++) { |
| const Type* paramType = ¶ms[i]->type(); |
| const Type* otherParamType = &otherParams[i]->type(); |
| |
| // Make generic types concrete. |
| if (otherParamType->isGeneric()) { |
| SkASSERT(genericIndex != -1); |
| SkASSERT(genericIndex < (int)otherParamType->coercibleTypes().size()); |
| otherParamType = otherParamType->coercibleTypes()[genericIndex]; |
| } |
| // Detect type mismatches. |
| if (!paramType->matches(*otherParamType)) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| /** |
| * Checks for a previously existing declaration of this function, reporting errors if there is an |
| * incompatible symbol. Returns true and sets outExistingDecl to point to the existing declaration |
| * (or null if none) on success, returns false on error. |
| */ |
| static bool find_existing_declaration(const Context& context, |
| Position pos, |
| ModifierFlags modifierFlags, |
| IntrinsicKind intrinsicKind, |
| std::string_view name, |
| TArray<std::unique_ptr<Variable>>& parameters, |
| Position returnTypePos, |
| const Type* returnType, |
| FunctionDeclaration** outExistingDecl) { |
| auto invalidDeclDescription = [&]() -> std::string { |
| TArray<Variable*> paramPtrs; |
| paramPtrs.reserve_exact(parameters.size()); |
| for (std::unique_ptr<Variable>& param : parameters) { |
| paramPtrs.push_back(param.get()); |
| } |
| return FunctionDeclaration(context, |
| pos, |
| modifierFlags, |
| name, |
| std::move(paramPtrs), |
| returnType, |
| intrinsicKind) |
| .description(); |
| }; |
| |
| ErrorReporter& errors = *context.fErrors; |
| Symbol* entry = context.fSymbolTable->findMutable(name); |
| *outExistingDecl = nullptr; |
| if (entry) { |
| if (!entry->is<FunctionDeclaration>()) { |
| errors.error(pos, "symbol '" + std::string(name) + "' was already defined"); |
| return false; |
| } |
| for (FunctionDeclaration* other = &entry->as<FunctionDeclaration>(); other; |
| other = other->mutableNextOverload()) { |
| SkASSERT(name == other->name()); |
| if (!parameters_match(parameters, other->parameters())) { |
| continue; |
| } |
| if (!type_generically_matches(*returnType, other->returnType())) { |
| errors.error(returnTypePos, "functions '" + invalidDeclDescription() + "' and '" + |
| other->description() + "' differ only in return type"); |
| return false; |
| } |
| for (int i = 0; i < parameters.size(); i++) { |
| if (parameters[i]->modifierFlags() != other->parameters()[i]->modifierFlags() || |
| parameters[i]->layout() != other->parameters()[i]->layout()) { |
| errors.error(parameters[i]->fPosition, |
| "modifiers on parameter " + std::to_string(i + 1) + |
| " differ between declaration and definition"); |
| return false; |
| } |
| } |
| if (other->definition() || other->isIntrinsic() || |
| modifierFlags != other->modifierFlags()) { |
| errors.error(pos, "duplicate definition of '" + invalidDeclDescription() + "'"); |
| return false; |
| } |
| *outExistingDecl = other; |
| break; |
| } |
| if (!*outExistingDecl && entry->as<FunctionDeclaration>().isMain()) { |
| errors.error(pos, "duplicate definition of 'main'"); |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| FunctionDeclaration::FunctionDeclaration(const Context& context, |
| Position pos, |
| ModifierFlags modifierFlags, |
| std::string_view name, |
| TArray<Variable*> parameters, |
| const Type* returnType, |
| IntrinsicKind intrinsicKind) |
| : INHERITED(pos, kIRNodeKind, name, /*type=*/nullptr) |
| , fDefinition(nullptr) |
| , fParameters(std::move(parameters)) |
| , fReturnType(returnType) |
| , fModifierFlags(modifierFlags) |
| , fIntrinsicKind(intrinsicKind) |
| , fBuiltin(context.fConfig->fIsBuiltinCode) |
| , fIsMain(name == "main") { |
| int builtinColorIndex = 0; |
| for (const Variable* param : fParameters) { |
| // None of the parameters are allowed to be be null. |
| SkASSERT(param); |
| |
| // Keep track of arguments to main for runtime effects. |
| if (fIsMain) { |
| if (ProgramConfig::IsRuntimeShader(context.fConfig->fKind) || |
| ProgramConfig::IsFragment(context.fConfig->fKind)) { |
| // If this is a runtime shader, a float2 param is supposed to be the coords. |
| // For testing purposes, we have .sksl inputs that are treated as both runtime |
| // effects and fragment shaders. To make that work, fragment shaders are allowed to |
| // have a coords parameter as well. |
| if (type_is_valid_for_coords(param->type())) { |
| fHasMainCoordsParameter = true; |
| } |
| } else if (ProgramConfig::IsRuntimeColorFilter(context.fConfig->fKind) || |
| ProgramConfig::IsRuntimeBlender(context.fConfig->fKind)) { |
| // If this is a runtime color filter or blender, the params are an input color, |
| // followed by a destination color for blenders. |
| if (type_is_valid_for_color(param->type())) { |
| switch (builtinColorIndex++) { |
| case 0: fHasMainInputColorParameter = true; break; |
| case 1: fHasMainDestColorParameter = true; break; |
| default: /* unknown color parameter */ break; |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| FunctionDeclaration* FunctionDeclaration::Convert(const Context& context, |
| Position pos, |
| const Modifiers& modifiers, |
| std::string_view name, |
| TArray<std::unique_ptr<Variable>> parameters, |
| Position returnTypePos, |
| const Type* returnType) { |
| // No layout flag is permissible on a function. |
| modifiers.fLayout.checkPermittedLayout(context, pos, |
| /*permittedLayoutFlags=*/LayoutFlag::kNone); |
| |
| // If requested, apply the `noinline` modifier to every function. This allows us to test Runtime |
| // Effects without any inlining, even when the code is later added to a paint. |
| ModifierFlags modifierFlags = modifiers.fFlags; |
| if (context.fConfig->fSettings.fForceNoInline) { |
| modifierFlags &= ~ModifierFlag::kInline; |
| modifierFlags |= ModifierFlag::kNoInline; |
| } |
| |
| bool isMain = (name == "main"); |
| IntrinsicKind intrinsicKind = context.fConfig->fIsBuiltinCode ? FindIntrinsicKind(name) |
| : kNotIntrinsic; |
| FunctionDeclaration* decl = nullptr; |
| if (!check_modifiers(context, modifiers.fPosition, modifierFlags) || |
| !check_return_type(context, returnTypePos, *returnType) || |
| !check_parameters(context, parameters, modifierFlags, intrinsicKind) || |
| (isMain && !check_main_signature(context, pos, *returnType, parameters)) || |
| !find_existing_declaration(context, pos, modifierFlags, intrinsicKind, name, parameters, |
| returnTypePos, returnType, &decl)) { |
| return nullptr; |
| } |
| TArray<Variable*> finalParameters; |
| finalParameters.reserve_exact(parameters.size()); |
| for (std::unique_ptr<Variable>& param : parameters) { |
| finalParameters.push_back(context.fSymbolTable->takeOwnershipOfSymbol(std::move(param))); |
| } |
| if (decl) { |
| return decl; |
| } |
| return context.fSymbolTable->add( |
| std::make_unique<FunctionDeclaration>(context, |
| pos, |
| modifierFlags, |
| name, |
| std::move(finalParameters), |
| returnType, |
| intrinsicKind)); |
| } |
| |
| void FunctionDeclaration::addParametersToSymbolTable(const Context& context) { |
| for (Variable* param : fParameters) { |
| context.fSymbolTable->addWithoutOwnership(param); |
| } |
| } |
| |
| std::string FunctionDeclaration::mangledName() const { |
| if ((this->isBuiltin() && !this->definition()) || this->isMain()) { |
| // Builtins without a definition (like `sin` or `sqrt`) must use their real names. |
| return std::string(this->name()); |
| } |
| // Built-in functions can have a $ prefix, which will fail to compile in GLSL. Remove the |
| // $ and add a unique mangling specifier, so user code can't conflict with the name. |
| std::string_view name = this->name(); |
| const char* builtinMarker = ""; |
| if (skstd::starts_with(name, '$')) { |
| name.remove_prefix(1); |
| builtinMarker = "Q"; // a unique, otherwise-unused mangle character |
| } |
| // Rename function to `funcname_returntypeparamtypes`. |
| std::string result = std::string(name) + "_" + builtinMarker + |
| this->returnType().abbreviatedName(); |
| for (const Variable* p : this->parameters()) { |
| result += p->type().abbreviatedName(); |
| } |
| return result; |
| } |
| |
| std::string FunctionDeclaration::description() const { |
| std::string result = (fModifierFlags ? fModifierFlags.description() + " " : std::string()) + |
| this->returnType().displayName() + " " + std::string(this->name()) + "("; |
| auto separator = SkSL::String::Separator(); |
| for (const Variable* p : this->parameters()) { |
| result += separator(); |
| result += p->description(); |
| } |
| result += ")"; |
| return result; |
| } |
| |
| bool FunctionDeclaration::matches(const FunctionDeclaration& f) const { |
| if (this->name() != f.name()) { |
| return false; |
| } |
| SkSpan<Variable* const> parameters = this->parameters(); |
| SkSpan<Variable* const> otherParameters = f.parameters(); |
| if (parameters.size() != otherParameters.size()) { |
| return false; |
| } |
| for (size_t i = 0; i < parameters.size(); i++) { |
| if (!parameters[i]->type().matches(otherParameters[i]->type())) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| bool FunctionDeclaration::determineFinalTypes(const ExpressionArray& arguments, |
| ParamTypes* outParameterTypes, |
| const Type** outReturnType) const { |
| SkSpan<Variable* const> parameters = this->parameters(); |
| SkASSERT(SkToSizeT(arguments.size()) == parameters.size()); |
| |
| outParameterTypes->reserve_exact(arguments.size()); |
| int genericIndex = -1; |
| for (int i = 0; i < arguments.size(); i++) { |
| // Non-generic parameters are final as-is. |
| const Type& parameterType = parameters[i]->type(); |
| if (!parameterType.isGeneric()) { |
| outParameterTypes->push_back(¶meterType); |
| continue; |
| } |
| // We use the first generic parameter we find to lock in the generic index; |
| // e.g. if we find `float3` here, all `$genType`s will be assumed to be `float3`. |
| if (genericIndex == -1) { |
| genericIndex = find_generic_index(arguments[i]->type(), parameterType, |
| /*allowNarrowing=*/true); |
| if (genericIndex == -1) { |
| // The passed-in type wasn't a match for ANY of the generic possibilities. |
| // This function isn't a match at all. |
| return false; |
| } |
| } |
| outParameterTypes->push_back(parameterType.coercibleTypes()[genericIndex]); |
| } |
| // Apply the generic index to our return type. |
| const Type& returnType = this->returnType(); |
| if (returnType.isGeneric()) { |
| if (genericIndex == -1) { |
| // We don't support functions with a generic return type and no other generics. |
| return false; |
| } |
| *outReturnType = returnType.coercibleTypes()[genericIndex]; |
| } else { |
| *outReturnType = &returnType; |
| } |
| return true; |
| } |
| |
| } // namespace SkSL |