blob: 1959a00e9a82b66e9a58f124476358e18981685d [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/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 <string_view>
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<Expression>& 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> VarDeclaration::Convert(const Context& context,
Position overallPos,
const Modifiers& modifiers,
const Type& type,
Position namePos,
std::string_view name,
VariableStorage storage,
std::unique_ptr<Expression> value) {
// Parameter declaration-statements do not exist in the grammar (unlike, say, K&R C).
SkASSERT(storage != VariableStorage::kParameter);
std::unique_ptr<Variable> 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> VarDeclaration::Convert(const Context& context,
std::unique_ptr<Variable> var,
std::unique_ptr<Expression> 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<VarDeclaration> 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> VarDeclaration::Make(const Context& context,
Variable* var,
const Type* baseType,
int arraySize,
std::unique_ptr<Expression> 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<VarDeclaration>(var, baseType, arraySize, std::move(value));
var->setVarDeclaration(result.get());
return result;
}
} // namespace SkSL