blob: d1cd8b7abed76f843bbb5e04db1e4aeb42e2e498 [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 {
return std::make_unique<VarDeclaration>(&this->var(),
&this->baseType(),
fArraySize,
this->value() ? this->value()->clone() : nullptr);
}
String VarDeclaration::description() const {
String result = this->var().modifiers().description() + this->baseType().description() + " " +
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,
int line,
const Modifiers& modifiers,
const Type* baseType,
Variable::Storage storage) {
if (baseType->matches(*context.fTypes.fInvalid)) {
context.fErrors->error(line, "invalid type");
return;
}
if (baseType->isVoid()) {
context.fErrors->error(line, "variables of type 'void' are not allowed");
return;
}
if (context.fConfig->strictES2Mode() && baseType->isArray()) {
context.fErrors->error(line, "array size must appear after variable name");
}
if (baseType->componentType().isOpaque() && storage != Variable::Storage::kGlobal) {
context.fErrors->error(line,
"variables of type '" + baseType->displayName() + "' must be global");
}
if ((modifiers.fFlags & Modifiers::kIn_Flag) && baseType->isMatrix()) {
context.fErrors->error(line, "'in' variables may not have matrix type");
}
if ((modifiers.fFlags & Modifiers::kIn_Flag) && (modifiers.fFlags & Modifiers::kUniform_Flag)) {
context.fErrors->error(line, "'in uniform' variables not permitted");
}
if (ProgramConfig::IsRuntimeEffect(context.fConfig->fKind)) {
if (modifiers.fFlags & Modifiers::kIn_Flag) {
context.fErrors->error(line, "'in' variables not permitted in runtime effects");
}
}
if (baseType->isEffectChild() && !(modifiers.fFlags & Modifiers::kUniform_Flag)) {
context.fErrors->error(line,
"variables of type '" + baseType->displayName() + "' must be uniform");
}
if (modifiers.fFlags & SkSL::Modifiers::kUniform_Flag &&
(context.fConfig->fKind == ProgramKind::kCustomMeshVertex ||
context.fConfig->fKind == ProgramKind::kCustomMeshFragment)) {
context.fErrors->error(line, "uniforms are not permitted in custom mesh shaders");
}
if (modifiers.fLayout.fFlags & Layout::kColor_Flag) {
if (!ProgramConfig::IsRuntimeEffect(context.fConfig->fKind)) {
context.fErrors->error(line, "'layout(color)' is only permitted in runtime effects");
}
if (!(modifiers.fFlags & Modifiers::kUniform_Flag)) {
context.fErrors->error(line,
"'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) && !(baseType->isArray() &&
validColorXformType(baseType->componentType()))) {
context.fErrors->error(line,
"'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, line, 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.fLine, var.modifiers(), baseType, var.storage());
if (value) {
if (var.type().isOpaque()) {
context.fErrors->error(value->fLine,
"opaque type '" + var.type().name() + "' cannot use initializer expressions");
return false;
}
if (var.modifiers().fFlags & Modifiers::kIn_Flag) {
context.fErrors->error(value->fLine,
"'in' variables cannot use initializer expressions");
return false;
}
if (var.modifiers().fFlags & Modifiers::kUniform_Flag) {
context.fErrors->error(value->fLine,
"'uniform' variables cannot use initializer expressions");
return false;
}
if (var.storage() == Variable::Storage::kInterfaceBlock) {
context.fErrors->error(value->fLine,
"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.fLine, "'const' variables must be initialized");
return false;
}
if (!Analysis::IsConstantExpression(*value)) {
context.fErrors->error(value->fLine,
"'const' variable initializer must be a constant expression");
return false;
}
}
if (var.storage() == Variable::Storage::kInterfaceBlock) {
if (var.type().isOpaque()) {
context.fErrors->error(var.fLine, "opaque type '" + var.type().name() +
"' is not permitted in an interface block");
return false;
}
}
if (var.storage() == Variable::Storage::kGlobal) {
if (value && !Analysis::IsConstantExpression(*value)) {
context.fErrors->error(value->fLine,
"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->fLine, "duplicate definition of 'sk_RTAdjust'");
return nullptr;
}
if (!var->type().matches(*context.fTypes.fFloat4)) {
context.fErrors->error(var->fLine, "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