blob: e135fd782c4e63e418eca9167a5d387a60824c24 [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/private/SkSLLayout.h"
#include "include/private/SkSLModifiers.h"
#include "include/private/SkSLProgramKind.h"
#include "include/private/SkSLString.h"
#include "include/sksl/SkSLErrorReporter.h"
#include "include/sksl/SkSLPosition.h"
#include "src/sksl/SkSLAnalysis.h"
#include "src/sksl/SkSLBuiltinTypes.h"
#include "src/sksl/SkSLCompiler.h"
#include "src/sksl/SkSLContext.h"
#include "src/sksl/SkSLProgramSettings.h"
#include "src/sksl/SkSLThreadContext.h"
#include "src/sksl/ir/SkSLSymbolTable.h"
#include "src/sksl/ir/SkSLType.h"
#include <cstddef>
#include <string_view>
#include <type_traits>
#include <vector>
namespace SkSL {
class Symbol;
namespace {
static bool check_valid_uniform_type(Position pos,
const Type* t,
const Context& context,
bool topLevel = true) {
const Type& ct = t->componentType();
// In RuntimeEffects we only allow a restricted set of types, namely shader/blender/colorFilter,
// 32-bit signed integers, 16-bit and 32-bit floats, and their composites.
{
bool error = false;
if (ProgramConfig::IsRuntimeEffect(context.fConfig->fKind)) {
// `shader`, `blender`, `colorFilter`
if (t->isEffectChild()) {
return true;
}
// `int`, `int2`, `int3`, `int4`
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.
error = true;
}
// We disallow boolean uniforms in SkSL since they are not well supported by backend
// platforms and drivers.
if (error || (ct.isBoolean() && (t->isScalar() || t->isVector()))) {
context.fErrors->error(
pos, "variables of type '" + t->displayName() + "' may not be uniform");
return false;
}
}
// In non-RTE SkSL we allow structs and interface blocks to be uniforms but we must make sure
// their fields are allowed.
if (t->isStruct()) {
for (const Type::Field& field : t->fields()) {
if (!check_valid_uniform_type(
field.fPosition, field.fType, context, /*topLevel=*/false)) {
// Emit a "caused by" line only for the top-level uniform type and not for any
// nested structs.
if (topLevel) {
context.fErrors->error(pos, "caused by:");
}
return false;
}
}
}
return true;
}
} // namespace
std::unique_ptr<Statement> VarDeclaration::clone() const {
// Cloning a VarDeclaration is inherently problematic, as we normally expect a one-to-one
// mapping between Variables and VarDeclarations and a straightforward clone would violate this
// assumption. We could of course theoretically clone the Variable as well, but that would
// require additional context and tracking, since for the whole process to work we would also
// have to fixup any subsequent VariableReference clones to point to the newly cloned Variables
// instead of the originals.
//
// Since the only reason we ever clone VarDeclarations is to support tests of clone() and we do
// not expect to ever need to do so otherwise, a full solution to this issue is unnecessary at
// the moment. We instead just keep track of whether a VarDeclaration is a clone so we can
// handle its cleanup properly. This allows clone() to work in the simple case that a
// VarDeclaration's clone does not outlive the original, which is adequate for testing. Since
// this leaves a sharp edge in place - destroying the original could cause a use-after-free in
// some circumstances - we also disable cloning altogether unless the
// fAllowVarDeclarationCloneForTesting ProgramSetting is enabled.
if (ThreadContext::Settings().fAllowVarDeclarationCloneForTesting) {
return std::make_unique<VarDeclaration>(&this->var(),
&this->baseType(),
fArraySize,
this->value() ? this->value()->clone() : nullptr,
/*isClone=*/true);
} else {
SkDEBUGFAIL("VarDeclaration::clone() is unsupported");
return nullptr;
}
}
std::string VarDeclaration::description() const {
std::string result = this->var().modifiers().description() + 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 Modifiers& modifiers,
const Type* type,
Variable::Storage storage) {
const Type* baseType = type;
if (baseType->isArray()) {
baseType = &baseType->componentType();
}
SkASSERT(!baseType->isArray());
if (baseType->matches(*context.fTypes.fInvalid)) {
context.fErrors->error(pos, "invalid type");
return;
}
if (baseType->isVoid()) {
context.fErrors->error(pos, "variables of type 'void' are not allowed");
return;
}
if (baseType->componentType().isOpaque() && storage != Variable::Storage::kGlobal) {
context.fErrors->error(pos,
"variables of type '" + baseType->displayName() + "' must be global");
}
if ((modifiers.fFlags & Modifiers::kIn_Flag) && baseType->isMatrix()) {
context.fErrors->error(pos, "'in' variables may not have matrix type");
}
if ((modifiers.fFlags & Modifiers::kIn_Flag) && type->isUnsizedArray()) {
context.fErrors->error(pos, "'in' variables may not have unsized array type");
}
if ((modifiers.fFlags & Modifiers::kOut_Flag) && type->isUnsizedArray()) {
context.fErrors->error(pos, "'out' variables may not have unsized array type");
}
if ((modifiers.fFlags & Modifiers::kIn_Flag) && (modifiers.fFlags & Modifiers::kUniform_Flag)) {
context.fErrors->error(pos, "'in uniform' variables not permitted");
}
if ((modifiers.fFlags & Modifiers::kReadOnly_Flag) &&
(modifiers.fFlags & Modifiers::kWriteOnly_Flag)) {
context.fErrors->error(pos, "'readonly' and 'writeonly' qualifiers cannot be combined");
}
if ((modifiers.fFlags & Modifiers::kUniform_Flag) &&
(modifiers.fFlags & Modifiers::kBuffer_Flag)) {
context.fErrors->error(pos, "'uniform buffer' variables not permitted");
}
if ((modifiers.fFlags & Modifiers::kThreadgroup_Flag) &&
(modifiers.fFlags & (Modifiers::kIn_Flag | Modifiers::kOut_Flag))) {
context.fErrors->error(pos, "in / out variables may not be declared threadgroup");
}
if ((modifiers.fFlags & Modifiers::kUniform_Flag)) {
check_valid_uniform_type(pos, baseType, context);
}
if (baseType->isEffectChild() && !(modifiers.fFlags & Modifiers::kUniform_Flag)) {
context.fErrors->error(pos,
"variables of type '" + baseType->displayName() + "' must be uniform");
}
if (baseType->isEffectChild() && (context.fConfig->fKind == ProgramKind::kMeshVertex ||
context.fConfig->fKind == ProgramKind::kMeshFragment)) {
context.fErrors->error(pos, "effects are not permitted in custom mesh shaders");
}
if (modifiers.fLayout.fFlags & Layout::kColor_Flag) {
if (!ProgramConfig::IsRuntimeEffect(context.fConfig->fKind)) {
context.fErrors->error(pos, "'layout(color)' is only permitted in runtime effects");
}
if (!(modifiers.fFlags & Modifiers::kUniform_Flag)) {
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() + "'");
}
}
int permitted = Modifiers::kConst_Flag | Modifiers::kHighp_Flag | Modifiers::kMediump_Flag |
Modifiers::kLowp_Flag;
if (storage == Variable::Storage::kGlobal) {
// Uniforms are allowed in all programs
permitted |= Modifiers::kUniform_Flag;
if (baseType->isInterfaceBlock()) {
permitted |= Modifiers::kBuffer_Flag;
// 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 size_t illegalRangeEnd =
fields.size() - ((modifiers.fFlags & Modifiers::kBuffer_Flag) ? 1 : 0);
for (size_t 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");
}
}
}
// No other modifiers are allowed in runtime effects
if (!ProgramConfig::IsRuntimeEffect(context.fConfig->fKind)) {
if (baseType->isInterfaceBlock() && (modifiers.fFlags & Modifiers::kBuffer_Flag)) {
// Only storage blocks allow `readonly` and `writeonly`.
// (`readonly` and `writeonly` textures are converted to separate types via
// applyAccessQualifiers.)
permitted |= Modifiers::kReadOnly_Flag | Modifiers::kWriteOnly_Flag;
}
if (!baseType->isOpaque()) {
// Only non-opaque types allow `in` and `out`.
permitted |= Modifiers::kIn_Flag | Modifiers::kOut_Flag;
}
if (ProgramConfig::IsCompute(context.fConfig->fKind)) {
// Only compute shaders allow `threadgroup`.
if (!baseType->isOpaque()) {
permitted |= Modifiers::kThreadgroup_Flag;
}
} else {
// Only vertex/fragment shaders allow `flat` and `noperspective`.
permitted |= Modifiers::kFlat_Flag | Modifiers::kNoPerspective_Flag;
}
}
}
// TODO(skbug.com/11301): Migrate above checks into building a mask of permitted layout flags
int permittedLayoutFlags = ~0;
// 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 ||
((modifiers.fFlags & Modifiers::kUniform_Flag) && !permitBindingAndSet)) {
permittedLayoutFlags &= ~Layout::kBinding_Flag;
permittedLayoutFlags &= ~Layout::kSet_Flag;
}
if (ProgramConfig::IsRuntimeEffect(context.fConfig->fKind)) {
// Disallow all layout flags except 'color' in runtime effects
permittedLayoutFlags &= Layout::kColor_Flag;
}
modifiers.checkPermitted(context, modifiersPosition, permitted, permittedLayoutFlags);
}
bool VarDeclaration::ErrorCheckAndCoerce(const Context& context, const Variable& var,
std::unique_ptr<Expression>& value) {
ErrorCheck(context, var.fPosition, var.modifiersPosition(), var.modifiers(), &var.type(),
var.storage());
if (value) {
if (var.type().isOpaque()) {
context.fErrors->error(value->fPosition, "opaque type '" + var.type().displayName() +
"' cannot use initializer expressions");
return false;
}
if (var.modifiers().fFlags & Modifiers::kIn_Flag) {
context.fErrors->error(value->fPosition,
"'in' variables cannot use initializer expressions");
return false;
}
if (var.modifiers().fFlags & Modifiers::kUniform_Flag) {
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;
}
value = var.type().coerceExpression(std::move(value), context);
if (!value) {
return false;
}
}
if (var.modifiers().fFlags & Modifiers::kConst_Flag) {
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<Statement> VarDeclaration::Convert(const Context& context,
std::unique_ptr<Variable> var, std::unique_ptr<Expression> value, bool addToSymbolTable) {
if (!ErrorCheckAndCoerce(context, *var, value)) {
return nullptr;
}
const Type* baseType = &var->type();
int arraySize = 0;
if (baseType->isArray()) {
arraySize = baseType->columns();
baseType = &baseType->componentType();
}
std::unique_ptr<Statement> varDecl = VarDeclaration::Make(context, var.get(), baseType,
arraySize, std::move(value));
if (!varDecl) {
return nullptr;
}
// Detect the declaration of magical variables.
if ((var->storage() == Variable::Storage::kGlobal) && var->name() == Compiler::FRAGCOLOR_NAME) {
// Silently ignore duplicate definitions of `sk_FragColor`.
const Symbol* symbol = (*ThreadContext::SymbolTable())[var->name()];
if (symbol) {
return nullptr;
}
} else if ((var->storage() == Variable::Storage::kGlobal ||
var->storage() == Variable::Storage::kInterfaceBlock) &&
var->name() == Compiler::RTADJUST_NAME) {
// `sk_RTAdjust` is special, and makes the IR generator emit position-fixup expressions.
if (ThreadContext::RTAdjustState().fVar || ThreadContext::RTAdjustState().fInterfaceBlock) {
context.fErrors->error(var->fPosition, "duplicate definition of 'sk_RTAdjust'");
return nullptr;
}
if (!var->type().matches(*context.fTypes.fFloat4)) {
context.fErrors->error(var->fPosition, "sk_RTAdjust must have type 'float4'");
return nullptr;
}
ThreadContext::RTAdjustState().fVar = var.get();
}
if (addToSymbolTable) {
ThreadContext::SymbolTable()->add(std::move(var));
} else {
ThreadContext::SymbolTable()->takeOwnershipOfSymbol(std::move(var));
}
return varDecl;
}
std::unique_ptr<Statement> 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->modifiers().fFlags & Modifiers::kConst_Flag) || value);
// 'const' variable initializer must be a constant expression
SkASSERT(!(var->modifiers().fFlags & Modifiers::kConst_Flag) ||
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->modifiers().fFlags & Modifiers::kIn_Flag)));
// 'uniform' variables cannot use initializer expressions
SkASSERT(!(value && (var->modifiers().fFlags & Modifiers::kUniform_Flag)));
auto result = std::make_unique<VarDeclaration>(var, baseType, arraySize, std::move(value));
var->setDeclaration(result.get());
return std::move(result);
}
} // namespace SkSL