* 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");
};
if (ProgramConfig::IsRuntimeEffect(context.fConfig->fKind)) {
if (t->isEffectChild()) {
return true;
}
const Type& ct = t->componentType();
if (ct.isSigned() && ct.bitWidth() == 32 && (t->isScalar() || t->isVector())) {
return true;
}
if (ct.isFloat() &&
(t->isScalar() || t->isVector() || (t->isMatrix() && t->rows() == t->columns()))) {
return true;
}
reportError();
return false;
}
Position errorPosition = {};
if (!t->isAllowedInUniform(&errorPosition)) {
reportError();
if (errorPosition.valid()) {
context.fErrors->error(errorPosition, "caused by:");
}
return false;
}
return true;
}
}
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()) {
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) {
permitted |= ModifierFlag::kUniform;
if (!ProgramConfig::IsRuntimeEffect(context.fConfig->fKind)) {
if (baseType->isInterfaceBlock()) {
permitted |= ModifierFlag::kBuffer;
if (modifierFlags.isBuffer()) {
permitted |= ModifierFlag::kReadOnly | ModifierFlag::kWriteOnly;
}
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()) {
permitted |= ModifierFlag::kIn | ModifierFlag::kOut;
}
if (ProgramConfig::IsFragment(context.fConfig->fKind) && baseType->isStruct() &&
!baseType->isInterfaceBlock()) {
permitted |= ModifierFlag::kPixelLocal;
}
if (ProgramConfig::IsCompute(context.fConfig->fKind)) {
if (!baseType->isOpaque() || baseType->isAtomic()) {
permitted |= ModifierFlag::kWorkgroup;
}
} else {
permitted |= ModifierFlag::kFlat | ModifierFlag::kNoPerspective;
}
}
}
LayoutFlags permittedLayoutFlags = LayoutFlag::kAll;
if (baseType->isStorageTexture()) {
if (!(layout.fFlags & LayoutFlag::kAllPixelFormats)) {
context.fErrors->error(pos, "storage textures must declare a pixel format");
}
} else {
permittedLayoutFlags &= ~LayoutFlag::kAllPixelFormats;
}
switch (baseType->typeKind()) {
case Type::TypeKind::kSampler:
break;
case Type::TypeKind::kTexture:
permittedLayoutFlags &= ~LayoutFlag::kSampler;
break;
case Type::TypeKind::kSeparateSampler:
permittedLayoutFlags &= ~LayoutFlag::kTexture;
break;
default:
permittedLayoutFlags &= ~(LayoutFlag::kTexture | LayoutFlag::kSampler);
break;
}
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)) {
permittedLayoutFlags &= LayoutFlag::kColor;
}
if ((layout.fFlags & (LayoutFlag::kSet | LayoutFlag::kBinding)) ||
(modifierFlags & (ModifierFlag::kIn | ModifierFlag::kOut))) {
permittedLayoutFlags &= ~LayoutFlag::kPushConstant;
}
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) {
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) {
if (context.fSymbolTable->find(var->name())) {
context.fErrors->error(var->fPosition,
"symbol '" + std::string(var->name()) + "' was already defined");
return nullptr;
}
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());
SkASSERT(var->storage() != Variable::Storage::kParameter);
SkASSERT(!var->modifierFlags().isConst() || value);
SkASSERT(!var->modifierFlags().isConst() || Analysis::IsConstantExpression(*value));
SkASSERT(!(value && var->storage() == Variable::Storage::kGlobal &&
!Analysis::IsConstantExpression(*value)));
SkASSERT(!(var->storage() == Variable::Storage::kInterfaceBlock && var->type().isOpaque()));
SkASSERT(!(var->storage() == Variable::Storage::kInterfaceBlock && value));
SkASSERT(!(value && var->type().isOpaque()));
SkASSERT(!(value && (var->modifierFlags() & ModifierFlag::kIn)));
SkASSERT(!(value && var->modifierFlags().isUniform()));
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;
}
}