/* * 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/SkSLVarDeclarations.h" #include "include/core/SkSpan.h" #include "include/private/base/SkTo.h" #include "src/base/SkEnumBitMask.h" #include "src/sksl/SkSLAnalysis.h" #include "src/sksl/SkSLBuiltinTypes.h" #include "src/sksl/SkSLCompiler.h" #include "src/sksl/SkSLContext.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/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 namespace SkSL { namespace { static bool check_valid_uniform_type(Position pos, const Type* t, const Context& context, bool topLevel = true) { auto reportError = [&]() { context.fErrors->error(pos, "variables of type '" + t->displayName() + "' may not be uniform"); }; // In Runtime Effects we only allow a restricted set of types: shader, blender, colorFilter, // 32-bit signed integers, 16-bit and 32-bit floats, and their vector/square-matrix composites. if (ProgramConfig::IsRuntimeEffect(context.fConfig->fKind)) { // `shader`, `blender`, `colorFilter` if (t->isEffectChild()) { return true; } // `int`, `int2`, `int3`, `int4` const Type& ct = t->componentType(); if (ct.isSigned() && ct.bitWidth() == 32 && (t->isScalar() || t->isVector())) { return true; } // `float`, `float2`, `float3`, `float4`, `float2x2`, `float3x3`, `float4x4` // `half`, `half2`, `half3`, `half4`, `half2x2`, `half3x3`, `half4x4` if (ct.isFloat() && (t->isScalar() || t->isVector() || (t->isMatrix() && t->rows() == t->columns()))) { return true; } // Everything else is an error. reportError(); return false; } Position errorPosition = {}; if (!t->isAllowedInUniform(&errorPosition)) { reportError(); if (errorPosition.valid()) { context.fErrors->error(errorPosition, "caused by:"); } return false; } return true; } } // namespace std::string VarDeclaration::description() const { std::string result = this->var()->layout().paddedDescription() + this->var()->modifierFlags().paddedDescription() + this->baseType().description() + ' ' + std::string(this->var()->name()); if (this->arraySize() > 0) { String::appendf(&result, "[%d]", this->arraySize()); } if (this->value()) { result += " = " + this->value()->description(); } result += ";"; return result; } void VarDeclaration::ErrorCheck(const Context& context, Position pos, Position modifiersPosition, const Layout& layout, ModifierFlags modifierFlags, const Type* type, const Type* baseType, Variable::Storage storage) { SkASSERT(type->isArray() ? baseType->matches(type->componentType()) : baseType->matches(*type)); if (baseType->componentType().isOpaque() && !baseType->componentType().isAtomic() && storage != Variable::Storage::kGlobal) { context.fErrors->error(pos, "variables of type '" + baseType->displayName() + "' must be global"); } if ((modifierFlags & ModifierFlag::kIn) && baseType->isMatrix()) { context.fErrors->error(pos, "'in' variables may not have matrix type"); } if ((modifierFlags & ModifierFlag::kIn) && type->isUnsizedArray()) { context.fErrors->error(pos, "'in' variables may not have unsized array type"); } if ((modifierFlags & ModifierFlag::kOut) && type->isUnsizedArray()) { context.fErrors->error(pos, "'out' variables may not have unsized array type"); } if ((modifierFlags & ModifierFlag::kIn) && modifierFlags.isUniform()) { context.fErrors->error(pos, "'in uniform' variables not permitted"); } if (modifierFlags.isReadOnly() && modifierFlags.isWriteOnly()) { context.fErrors->error(pos, "'readonly' and 'writeonly' qualifiers cannot be combined"); } if (modifierFlags.isUniform() && modifierFlags.isBuffer()) { context.fErrors->error(pos, "'uniform buffer' variables not permitted"); } if (modifierFlags.isWorkgroup() && (modifierFlags & (ModifierFlag::kIn | ModifierFlag::kOut))) { context.fErrors->error(pos, "in / out variables may not be declared workgroup"); } if (modifierFlags.isUniform()) { check_valid_uniform_type(pos, baseType, context); } if (baseType->isEffectChild() && !modifierFlags.isUniform()) { context.fErrors->error(pos, "variables of type '" + baseType->displayName() + "' must be uniform"); } if (baseType->isEffectChild() && context.fConfig->fKind == ProgramKind::kMeshVertex) { context.fErrors->error(pos, "effects are not permitted in mesh vertex shaders"); } if (baseType->isOrContainsAtomic()) { // An atomic variable (or a struct or an array that contains an atomic member) must be // either: // a. Declared as a workgroup-shared variable, OR // b. Declared as the member of writable storage buffer block (i.e. has no readonly // restriction). // // The checks below will enforce these two rules on all declarations. If the variable is not // declared with the workgroup modifier, then it must be declared in the interface block // storage. If this is the declaration for an interface block that contains an atomic // member, then it must have the `buffer` modifier and no `readonly` modifier. bool isBlockMember = (storage == Variable::Storage::kInterfaceBlock); bool isWritableStorageBuffer = modifierFlags.isBuffer() && !modifierFlags.isReadOnly(); if (!modifierFlags.isWorkgroup() && !(baseType->isInterfaceBlock() ? isWritableStorageBuffer : isBlockMember)) { context.fErrors->error(pos, "atomics are only permitted in workgroup variables and " "writable storage blocks"); } } if (layout.fFlags & LayoutFlag::kColor) { if (!ProgramConfig::IsRuntimeEffect(context.fConfig->fKind)) { context.fErrors->error(pos, "'layout(color)' is only permitted in runtime effects"); } if (!modifierFlags.isUniform()) { context.fErrors->error(pos, "'layout(color)' is only permitted on 'uniform' variables"); } auto validColorXformType = [](const Type& t) { return t.isVector() && t.componentType().isFloat() && (t.columns() == 3 || t.columns() == 4); }; if (!validColorXformType(*baseType)) { context.fErrors->error(pos, "'layout(color)' is not permitted on variables of type '" + baseType->displayName() + "'"); } } ModifierFlags permitted = ModifierFlag::kConst | ModifierFlag::kHighp | ModifierFlag::kMediump | ModifierFlag::kLowp; if (storage == Variable::Storage::kGlobal) { // Uniforms are allowed in all programs permitted |= ModifierFlag::kUniform; // No other modifiers are allowed in runtime effects. if (!ProgramConfig::IsRuntimeEffect(context.fConfig->fKind)) { if (baseType->isInterfaceBlock()) { // Interface blocks allow `buffer`. permitted |= ModifierFlag::kBuffer; if (modifierFlags.isBuffer()) { // Only storage blocks allow `readonly` and `writeonly`. // (`readonly` and `writeonly` textures are converted to separate types via // applyAccessQualifiers.) permitted |= ModifierFlag::kReadOnly | ModifierFlag::kWriteOnly; } // It is an error for an unsized array to appear anywhere but the last member of a // "buffer" block. const auto& fields = baseType->fields(); const int illegalRangeEnd = SkToInt(fields.size()) - (modifierFlags.isBuffer() ? 1 : 0); for (int i = 0; i < illegalRangeEnd; ++i) { if (fields[i].fType->isUnsizedArray()) { context.fErrors->error( fields[i].fPosition, "unsized array must be the last member of a storage block"); } } } if (!baseType->isOpaque()) { // Only non-opaque types allow `in` and `out`. permitted |= ModifierFlag::kIn | ModifierFlag::kOut; } if (ProgramConfig::IsFragment(context.fConfig->fKind) && baseType->isStruct() && !baseType->isInterfaceBlock()) { // Only structs in fragment shaders allow `pixel_local`. permitted |= ModifierFlag::kPixelLocal; } if (ProgramConfig::IsCompute(context.fConfig->fKind)) { // Only compute shaders allow `workgroup`. if (!baseType->isOpaque() || baseType->isAtomic()) { permitted |= ModifierFlag::kWorkgroup; } } else { // Only vertex/fragment shaders allow `flat` and `noperspective`. permitted |= ModifierFlag::kFlat | ModifierFlag::kNoPerspective; } } } LayoutFlags permittedLayoutFlags = LayoutFlag::kAll; // Pixel format modifiers are required on storage textures, and forbidden on other types. if (baseType->isStorageTexture()) { if (!(layout.fFlags & LayoutFlag::kAllPixelFormats)) { context.fErrors->error(pos, "storage textures must declare a pixel format"); } } else { permittedLayoutFlags &= ~LayoutFlag::kAllPixelFormats; } // The `texture` and `sampler` modifiers can be present respectively on a texture and sampler or // simultaneously on a combined image-sampler but they are not permitted on any other type. switch (baseType->typeKind()) { case Type::TypeKind::kSampler: // Both texture and sampler flags are permitted break; case Type::TypeKind::kTexture: permittedLayoutFlags &= ~LayoutFlag::kSampler; break; case Type::TypeKind::kSeparateSampler: permittedLayoutFlags &= ~LayoutFlag::kTexture; break; default: permittedLayoutFlags &= ~(LayoutFlag::kTexture | LayoutFlag::kSampler); break; } // We don't allow 'binding' or 'set' on normal uniform variables, only on textures, samplers, // and interface blocks (holding uniform variables). They're also only allowed at global scope, // not on interface block fields (or locals/parameters). bool permitBindingAndSet = baseType->typeKind() == Type::TypeKind::kSampler || baseType->typeKind() == Type::TypeKind::kSeparateSampler || baseType->typeKind() == Type::TypeKind::kTexture || baseType->isInterfaceBlock(); if (storage != Variable::Storage::kGlobal || (modifierFlags.isUniform() && !permitBindingAndSet)) { permittedLayoutFlags &= ~LayoutFlag::kBinding; permittedLayoutFlags &= ~LayoutFlag::kSet; permittedLayoutFlags &= ~LayoutFlag::kAllBackends; } if (ProgramConfig::IsRuntimeEffect(context.fConfig->fKind)) { // Disallow all layout flags except 'color' in runtime effects permittedLayoutFlags &= LayoutFlag::kColor; } // The `push_constant` flag isn't allowed on in-variables, out-variables, bindings or sets. if ((layout.fFlags & (LayoutFlag::kSet | LayoutFlag::kBinding)) || (modifierFlags & (ModifierFlag::kIn | ModifierFlag::kOut))) { permittedLayoutFlags &= ~LayoutFlag::kPushConstant; } // The `builtin` layout flag is only allowed in modules. if (!context.fConfig->isBuiltinCode()) { permittedLayoutFlags &= ~LayoutFlag::kBuiltin; } modifierFlags.checkPermittedFlags(context, modifiersPosition, permitted); layout.checkPermittedLayout(context, modifiersPosition, permittedLayoutFlags); } bool VarDeclaration::ErrorCheckAndCoerce(const Context& context, const Variable& var, const Type* baseType, std::unique_ptr& value) { if (baseType->matches(*context.fTypes.fInvalid)) { context.fErrors->error(var.fPosition, "invalid type"); return false; } if (baseType->isVoid()) { context.fErrors->error(var.fPosition, "variables of type 'void' are not allowed"); return false; } ErrorCheck(context, var.fPosition, var.modifiersPosition(), var.layout(), var.modifierFlags(), &var.type(), baseType, var.storage()); if (value) { if (var.type().isOpaque() || var.type().isOrContainsAtomic()) { context.fErrors->error(value->fPosition, "opaque type '" + var.type().displayName() + "' cannot use initializer expressions"); return false; } if (var.modifierFlags() & ModifierFlag::kIn) { context.fErrors->error(value->fPosition, "'in' variables cannot use initializer expressions"); return false; } if (var.modifierFlags().isUniform()) { context.fErrors->error(value->fPosition, "'uniform' variables cannot use initializer expressions"); return false; } if (var.storage() == Variable::Storage::kInterfaceBlock) { context.fErrors->error(value->fPosition, "initializers are not permitted on interface block fields"); return false; } if (context.fConfig->strictES2Mode() && var.type().isOrContainsArray()) { context.fErrors->error(value->fPosition, "initializers are not permitted on arrays " "(or structs containing arrays)"); return false; } value = var.type().coerceExpression(std::move(value), context); if (!value) { return false; } } if (var.modifierFlags().isConst()) { if (!value) { context.fErrors->error(var.fPosition, "'const' variables must be initialized"); return false; } if (!Analysis::IsConstantExpression(*value)) { context.fErrors->error(value->fPosition, "'const' variable initializer must be a constant expression"); return false; } } if (var.storage() == Variable::Storage::kInterfaceBlock) { if (var.type().isOpaque()) { context.fErrors->error(var.fPosition, "opaque type '" + var.type().displayName() + "' is not permitted in an interface block"); return false; } } if (var.storage() == Variable::Storage::kGlobal) { if (value && !Analysis::IsConstantExpression(*value)) { context.fErrors->error(value->fPosition, "global variable initializer must be a constant expression"); return false; } } return true; } std::unique_ptr VarDeclaration::Convert(const Context& context, Position overallPos, const Modifiers& modifiers, const Type& type, Position namePos, std::string_view name, VariableStorage storage, std::unique_ptr value) { // Parameter declaration-statements do not exist in the grammar (unlike, say, K&R C). SkASSERT(storage != VariableStorage::kParameter); std::unique_ptr var = Variable::Convert(context, overallPos, modifiers.fPosition, modifiers.fLayout, modifiers.fFlags, &type, namePos, name, storage); if (!var) { return nullptr; } return VarDeclaration::Convert(context, std::move(var), std::move(value)); } std::unique_ptr VarDeclaration::Convert(const Context& context, std::unique_ptr var, std::unique_ptr value) { const Type* baseType = &var->type(); int arraySize = 0; if (baseType->isArray()) { arraySize = baseType->columns(); baseType = &baseType->componentType(); } if (!ErrorCheckAndCoerce(context, *var, baseType, value)) { return nullptr; } std::unique_ptr varDecl = VarDeclaration::Make(context, var.get(), baseType, arraySize, std::move(value)); if (!varDecl) { return nullptr; } if (var->storage() == Variable::Storage::kGlobal || var->storage() == Variable::Storage::kInterfaceBlock) { // Check if this globally-scoped variable name overlaps an existing symbol name. if (context.fSymbolTable->find(var->name())) { context.fErrors->error(var->fPosition, "symbol '" + std::string(var->name()) + "' was already defined"); return nullptr; } // `sk_RTAdjust` is special, and makes the IR generator emit position-fixup expressions. if (var->name() == Compiler::RTADJUST_NAME) { if (!var->type().matches(*context.fTypes.fFloat4)) { context.fErrors->error(var->fPosition, "sk_RTAdjust must have type 'float4'"); return nullptr; } } } context.fSymbolTable->add(context, std::move(var)); return varDecl; } std::unique_ptr VarDeclaration::Make(const Context& context, Variable* var, const Type* baseType, int arraySize, std::unique_ptr value) { SkASSERT(!baseType->isArray()); // function parameters cannot have variable declarations SkASSERT(var->storage() != Variable::Storage::kParameter); // 'const' variables must be initialized SkASSERT(!var->modifierFlags().isConst() || value); // 'const' variable initializer must be a constant expression SkASSERT(!var->modifierFlags().isConst() || Analysis::IsConstantExpression(*value)); // global variable initializer must be a constant expression SkASSERT(!(value && var->storage() == Variable::Storage::kGlobal && !Analysis::IsConstantExpression(*value))); // opaque type not permitted on an interface block SkASSERT(!(var->storage() == Variable::Storage::kInterfaceBlock && var->type().isOpaque())); // initializers are not permitted on interface block fields SkASSERT(!(var->storage() == Variable::Storage::kInterfaceBlock && value)); // opaque type cannot use initializer expressions SkASSERT(!(value && var->type().isOpaque())); // 'in' variables cannot use initializer expressions SkASSERT(!(value && (var->modifierFlags() & ModifierFlag::kIn))); // 'uniform' variables cannot use initializer expressions SkASSERT(!(value && var->modifierFlags().isUniform())); // in strict-ES2 mode, is-or-contains-array types cannot use initializer expressions SkASSERT(!(value && var->type().isOrContainsArray() && context.fConfig->strictES2Mode())); auto result = std::make_unique(var, baseType, arraySize, std::move(value)); var->setVarDeclaration(result.get()); return result; } } // namespace SkSL