diff options
Diffstat (limited to 'gfx/skia/skia/src/sksl/ir/SkSLFunctionDeclaration.cpp')
-rw-r--r-- | gfx/skia/skia/src/sksl/ir/SkSLFunctionDeclaration.cpp | 598 |
1 files changed, 598 insertions, 0 deletions
diff --git a/gfx/skia/skia/src/sksl/ir/SkSLFunctionDeclaration.cpp b/gfx/skia/skia/src/sksl/ir/SkSLFunctionDeclaration.cpp new file mode 100644 index 0000000000..036bfac02e --- /dev/null +++ b/gfx/skia/skia/src/sksl/ir/SkSLFunctionDeclaration.cpp @@ -0,0 +1,598 @@ +/* + * 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/SkSLDefines.h" +#include "include/private/SkSLLayout.h" +#include "include/private/SkSLModifiers.h" +#include "include/private/SkSLProgramKind.h" +#include "include/private/SkSLString.h" +#include "include/private/base/SkTo.h" +#include "include/sksl/SkSLErrorReporter.h" +#include "include/sksl/SkSLPosition.h" +#include "src/base/SkStringView.h" +#include "src/sksl/SkSLBuiltinTypes.h" +#include "src/sksl/SkSLCompiler.h" +#include "src/sksl/SkSLContext.h" +#include "src/sksl/SkSLModifiersPool.h" +#include "src/sksl/SkSLProgramSettings.h" +#include "src/sksl/ir/SkSLExpression.h" +#include "src/sksl/ir/SkSLSymbolTable.h" +#include "src/sksl/ir/SkSLType.h" +#include "src/sksl/ir/SkSLVariable.h" + +#include <algorithm> +#include <cstddef> +#include <utility> + +namespace SkSL { + +static bool check_modifiers(const Context& context, + Position pos, + const Modifiers& modifiers) { + const int permitted = Modifiers::kInline_Flag | + Modifiers::kNoInline_Flag | + (context.fConfig->fIsBuiltinCode ? (Modifiers::kES3_Flag | + Modifiers::kPure_Flag | + Modifiers::kExport_Flag) : 0); + modifiers.checkPermitted(context, pos, permitted, /*permittedLayoutFlags=*/0); + if ((modifiers.fFlags & Modifiers::kInline_Flag) && + (modifiers.fFlags & Modifiers::kNoInline_Flag)) { + 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, + std::vector<std::unique_ptr<Variable>>& parameters, + bool isMain) { + auto typeIsValidForColor = [&](const Type& type) { + return type.matches(*context.fTypes.fHalf4) || type.matches(*context.fTypes.fFloat4); + }; + + // The first color parameter passed to main() is the input color; the second is the dest color. + static constexpr int kBuiltinColorIDs[] = {SK_INPUT_COLOR_BUILTIN, SK_DEST_COLOR_BUILTIN}; + unsigned int builtinColorIndex = 0; + + // Check modifiers on each function parameter. + for (auto& param : parameters) { + const Type& type = param->type(); + int permittedFlags = Modifiers::kConst_Flag | Modifiers::kIn_Flag; + if (!type.isOpaque()) { + permittedFlags |= Modifiers::kOut_Flag; + } + if (type.typeKind() == Type::TypeKind::kTexture) { + permittedFlags |= Modifiers::kReadOnly_Flag | Modifiers::kWriteOnly_Flag; + } + param->modifiers().checkPermitted(context, + param->modifiersPosition(), + permittedFlags, + /*permittedLayoutFlags=*/0); + // 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; + } + + Modifiers m = param->modifiers(); + bool modifiersChanged = false; + + // The `in` modifier on function parameters is implicit, so we can replace `in float x` with + // `float x`. This prevents any ambiguity when matching a function by its param types. + if (Modifiers::kIn_Flag == (m.fFlags & (Modifiers::kOut_Flag | Modifiers::kIn_Flag))) { + m.fFlags &= ~(Modifiers::kOut_Flag | Modifiers::kIn_Flag); + modifiersChanged = true; + } + + if (isMain) { + if (ProgramConfig::IsRuntimeEffect(context.fConfig->fKind) && + context.fConfig->fKind != ProgramKind::kMeshFragment && + context.fConfig->fKind != ProgramKind::kMeshVertex) { + // We verify that the signature is fully correct later. For now, if this is a + // runtime effect of any flavor, a float2 param is supposed to be the coords, and a + // half4/float parameter is supposed to be the input or destination color: + if (type.matches(*context.fTypes.fFloat2)) { + m.fLayout.fBuiltin = SK_MAIN_COORDS_BUILTIN; + modifiersChanged = true; + } else if (typeIsValidForColor(type) && + builtinColorIndex < std::size(kBuiltinColorIDs)) { + m.fLayout.fBuiltin = kBuiltinColorIDs[builtinColorIndex++]; + modifiersChanged = true; + } + } else if (ProgramConfig::IsFragment(context.fConfig->fKind)) { + // 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. + if (type.matches(*context.fTypes.fFloat2)) { + m.fLayout.fBuiltin = SK_MAIN_COORDS_BUILTIN; + modifiersChanged = true; + } + } + } + + if (modifiersChanged) { + param->setModifiers(context.fModifiersPool->add(m)); + } + } + return true; +} + +static bool check_main_signature(const Context& context, Position pos, const Type& returnType, + std::vector<std::unique_ptr<Variable>>& parameters) { + ErrorReporter& errors = *context.fErrors; + ProgramKind kind = context.fConfig->fKind; + + auto typeIsValidForColor = [&](const Type& type) { + return type.matches(*context.fTypes.fHalf4) || type.matches(*context.fTypes.fFloat4); + }; + + 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 p.type().matches(*context.fTypes.fFloat2) && + p.modifiers().fFlags == 0 && + p.modifiers().fLayout.fBuiltin == SK_MAIN_COORDS_BUILTIN; + }; + + auto paramIsBuiltinColor = [&](int idx, int builtinID) { + const Variable& p = *parameters[idx]; + return typeIsValidForColor(p.type()) && + p.modifiers().fFlags == 0 && + p.modifiers().fLayout.fBuiltin == builtinID; + }; + + auto paramIsConstInAttributes = [&](int idx) { + const Variable& p = *parameters[idx]; + return typeIsValidForAttributes(p.type()) && p.modifiers().fFlags == Modifiers::kConst_Flag; + }; + + auto paramIsConstInVaryings = [&](int idx) { + const Variable& p = *parameters[idx]; + return typeIsValidForVaryings(p.type()) && p.modifiers().fFlags == Modifiers::kConst_Flag; + }; + + auto paramIsOutColor = [&](int idx) { + const Variable& p = *parameters[idx]; + return typeIsValidForColor(p.type()) && p.modifiers().fFlags == Modifiers::kOut_Flag; + }; + + auto paramIsInputColor = [&](int n) { return paramIsBuiltinColor(n, SK_INPUT_COLOR_BUILTIN); }; + auto paramIsDestColor = [&](int n) { return paramIsBuiltinColor(n, SK_DEST_COLOR_BUILTIN); }; + + switch (kind) { + case ProgramKind::kRuntimeColorFilter: + case ProgramKind::kPrivateRuntimeColorFilter: { + // (half4|float4) main(half4|float4) + if (!typeIsValidForColor(returnType)) { + errors.error(pos, "'main' must return: 'vec4', 'float4', or 'half4'"); + return false; + } + bool validParams = (parameters.size() == 1 && paramIsInputColor(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 (!typeIsValidForColor(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 (!typeIsValidForColor(returnType)) { + errors.error(pos, "'main' must return: 'vec4', 'float4', or 'half4'"); + return false; + } + if (!(parameters.size() == 2 && + paramIsInputColor(0) && + paramIsDestColor(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 (!returnType.matches(*context.fTypes.fFloat2)) { + 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(const std::vector<std::unique_ptr<Variable>>& params, + const std::vector<Variable*>& 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, + SymbolTable& symbols, + Position pos, + const Modifiers* modifiers, + std::string_view name, + std::vector<std::unique_ptr<Variable>>& parameters, + Position returnTypePos, + const Type* returnType, + FunctionDeclaration** outExistingDecl) { + auto invalidDeclDescription = [&]() -> std::string { + std::vector<Variable*> paramPtrs; + paramPtrs.reserve(parameters.size()); + for (std::unique_ptr<Variable>& param : parameters) { + paramPtrs.push_back(param.get()); + } + return FunctionDeclaration(pos, + modifiers, + name, + std::move(paramPtrs), + returnType, + context.fConfig->fIsBuiltinCode) + .description(); + }; + + ErrorReporter& errors = *context.fErrors; + Symbol* entry = symbols.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 (size_t i = 0; i < parameters.size(); i++) { + if (parameters[i]->modifiers() != other->parameters()[i]->modifiers()) { + errors.error(parameters[i]->fPosition, + "modifiers on parameter " + std::to_string(i + 1) + + " differ between declaration and definition"); + return false; + } + } + if (*modifiers != other->modifiers() || other->definition() || other->isIntrinsic()) { + 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(Position pos, + const Modifiers* modifiers, + std::string_view name, + std::vector<Variable*> parameters, + const Type* returnType, + bool builtin) + : INHERITED(pos, kIRNodeKind, name, /*type=*/nullptr) + , fDefinition(nullptr) + , fModifiers(modifiers) + , fParameters(std::move(parameters)) + , fReturnType(returnType) + , fBuiltin(builtin) + , fIsMain(name == "main") + , fIntrinsicKind(builtin ? FindIntrinsicKind(name) : kNotIntrinsic) { + // None of the parameters are allowed to be be null. + SkASSERT(std::count(fParameters.begin(), fParameters.end(), nullptr) == 0); +} + +FunctionDeclaration* FunctionDeclaration::Convert(const Context& context, + SymbolTable& symbols, + Position pos, + Position modifiersPosition, + const Modifiers* modifiers, + std::string_view name, + std::vector<std::unique_ptr<Variable>> parameters, + Position returnTypePos, + const Type* returnType) { + bool isMain = (name == "main"); + + FunctionDeclaration* decl = nullptr; + if (!check_modifiers(context, modifiersPosition, *modifiers) || + !check_return_type(context, returnTypePos, *returnType) || + !check_parameters(context, parameters, isMain) || + (isMain && !check_main_signature(context, pos, *returnType, parameters)) || + !find_existing_declaration(context, symbols, pos, modifiers, name, parameters, + returnTypePos, returnType, &decl)) { + return nullptr; + } + std::vector<Variable*> finalParameters; + finalParameters.reserve(parameters.size()); + for (std::unique_ptr<Variable>& param : parameters) { + finalParameters.push_back(symbols.takeOwnershipOfSymbol(std::move(param))); + } + if (decl) { + return decl; + } + auto result = std::make_unique<FunctionDeclaration>(pos, + modifiers, + name, + std::move(finalParameters), + returnType, + context.fConfig->fIsBuiltinCode); + return symbols.add(std::move(result)); +} + +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 { + int modifierFlags = this->modifiers().fFlags; + std::string result = + (modifierFlags ? Modifiers::DescribeFlags(modifierFlags) + " " : std::string()) + + this->returnType().displayName() + " " + std::string(this->name()) + "("; + auto separator = SkSL::String::Separator(); + for (const Variable* p : this->parameters()) { + result += separator(); + // We can't just say `p->description()` here, because occasionally might have added layout + // flags onto parameters (like `layout(builtin=10009)`) and don't want to reproduce that. + if (p->modifiers().fFlags) { + result += Modifiers::DescribeFlags(p->modifiers().fFlags) + " "; + } + result += p->type().displayName(); + result += " "; + result += p->name(); + } + result += ")"; + return result; +} + +bool FunctionDeclaration::matches(const FunctionDeclaration& f) const { + if (this->name() != f.name()) { + return false; + } + const std::vector<Variable*>& parameters = this->parameters(); + const std::vector<Variable*>& 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 { + const std::vector<Variable*>& parameters = this->parameters(); + SkASSERT(SkToSizeT(arguments.size()) == parameters.size()); + + outParameterTypes->reserve_back(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 |