blob: e707e9ea9129c72d6634e29c6e4853d14cb868f5 [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/sksl/SkSLErrorReporter.h"
#include "src/sksl/SkSLAnalysis.h"
#include "src/sksl/SkSLCompiler.h"
#include "src/sksl/SkSLContext.h"
#include "src/sksl/SkSLProgramSettings.h"
#include "src/sksl/SkSLThreadContext.h"
namespace SkSL {
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* baseType,
Variable::Storage storage) {
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) && (modifiers.fFlags & Modifiers::kUniform_Flag)) {
context.fErrors->error(pos, "'in uniform' variables not permitted");
}
if (ProgramConfig::IsRuntimeEffect(context.fConfig->fKind)) {
if (modifiers.fFlags & Modifiers::kIn_Flag) {
context.fErrors->error(pos, "'in' variables not permitted in runtime effects");
}
if (modifiers.fFlags & Modifiers::kUniform_Flag) {
auto validUniformType = [](const Type& t) {
const Type& ct = t.componentType();
return t.isEffectChild() ||
((t.isScalar() || t.isVector()) && ct.isSigned() && ct.bitWidth() == 32) ||
((t.isScalar() || t.isVector() || t.isMatrix()) && ct.isFloat());
};
if (!validUniformType(*baseType)) {
context.fErrors->error(
pos,
"variables of type '" + baseType->displayName() + "' may not be uniform");
}
}
}
if (baseType->isEffectChild() && !(modifiers.fFlags & Modifiers::kUniform_Flag)) {
context.fErrors->error(pos,
"variables of type '" + baseType->displayName() + "' must be uniform");
}
if (modifiers.fFlags & Modifiers::kUniform_Flag &&
(context.fConfig->fKind == ProgramKind::kMeshVertex ||
context.fConfig->fKind == ProgramKind::kMeshFragment)) {
context.fErrors->error(pos, "uniforms 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) {
permitted |= Modifiers::kIn_Flag | Modifiers::kOut_Flag | Modifiers::kUniform_Flag |
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;
}
modifiers.checkPermitted(context, modifiersPosition, permitted, permittedLayoutFlags);
}
bool VarDeclaration::ErrorCheckAndCoerce(const Context& context, const Variable& var,
std::unique_ptr<Expression>& value) {
const Type* baseType = &var.type();
if (baseType->isArray()) {
baseType = &baseType->componentType();
}
ErrorCheck(context, var.fPosition, var.modifiersPosition(), var.modifiers(), baseType,
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