blob: 342e68307d45b22b2e5705718ead79a9a8746253 [file] [log] [blame]
/*
* Copyright 2016 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "src/sksl/ir/SkSLType.h"
#include "include/private/base/SkTo.h"
#include "src/base/SkEnumBitMask.h"
#include "src/base/SkMathPriv.h"
#include "src/base/SkSafeMath.h"
#include "src/core/SkTHash.h"
#include "src/sksl/SkSLBuiltinTypes.h"
#include "src/sksl/SkSLConstantFolder.h"
#include "src/sksl/SkSLContext.h"
#include "src/sksl/SkSLErrorReporter.h"
#include "src/sksl/SkSLProgramSettings.h"
#include "src/sksl/SkSLString.h"
#include "src/sksl/ir/SkSLConstructorArrayCast.h"
#include "src/sksl/ir/SkSLConstructorCompoundCast.h"
#include "src/sksl/ir/SkSLConstructorScalarCast.h"
#include "src/sksl/ir/SkSLExpression.h"
#include "src/sksl/ir/SkSLLayout.h"
#include "src/sksl/ir/SkSLModifierFlags.h"
#include "src/sksl/ir/SkSLSymbolTable.h"
#include <algorithm>
#include <cstdint>
#include <limits>
#include <optional>
#include <string_view>
#include <utility>
using namespace skia_private;
namespace SkSL {
static constexpr int kMaxStructDepth = 8;
class AliasType final : public Type {
public:
AliasType(std::string_view name, const Type& targetType)
: INHERITED(name, targetType.abbreviatedName(), targetType.typeKind())
, fTargetType(targetType) {}
const Type& resolve() const override {
return fTargetType;
}
const Type& componentType() const override {
return fTargetType.componentType();
}
NumberKind numberKind() const override {
return fTargetType.numberKind();
}
int priority() const override {
return fTargetType.priority();
}
int columns() const override {
return fTargetType.columns();
}
int rows() const override {
return fTargetType.rows();
}
int bitWidth() const override {
return fTargetType.bitWidth();
}
bool isAllowedInES2() const override {
return fTargetType.isAllowedInES2();
}
bool isOrContainsBool() const override {
return fTargetType.isOrContainsBool();
}
bool isOrContainsAtomic() const override {
return fTargetType.isOrContainsAtomic();
}
size_t slotCount() const override {
return fTargetType.slotCount();
}
const Type& slotType(size_t n) const override {
return fTargetType.slotType(n);
}
SpvDim_ dimensions() const override {
return fTargetType.dimensions();
}
bool isDepth() const override {
return fTargetType.isDepth();
}
bool isArrayedTexture() const override {
return fTargetType.isArrayedTexture();
}
bool isScalar() const override {
return fTargetType.isScalar();
}
bool isLiteral() const override {
return fTargetType.isLiteral();
}
bool isVector() const override {
return fTargetType.isVector();
}
bool isMatrix() const override {
return fTargetType.isMatrix();
}
bool isArray() const override {
return fTargetType.isArray();
}
bool isUnsizedArray() const override {
return fTargetType.isUnsizedArray();
}
bool isStruct() const override {
return fTargetType.isStruct();
}
bool isInterfaceBlock() const override {
return fTargetType.isInterfaceBlock();
}
bool isMultisampled() const override {
return fTargetType.isMultisampled();
}
TextureAccess textureAccess() const override {
return fTargetType.textureAccess();
}
SkSpan<const Type* const> coercibleTypes() const override {
return fTargetType.coercibleTypes();
}
private:
using INHERITED = Type;
const Type& fTargetType;
};
class ArrayType final : public Type {
public:
inline static constexpr TypeKind kTypeKind = TypeKind::kArray;
ArrayType(std::string_view name, const char* abbrev, const Type& componentType, int count,
bool isBuiltin)
: INHERITED(name, abbrev, kTypeKind)
, fComponentType(componentType)
, fCount(count)
, fIsBuiltin(isBuiltin) {
SkASSERT(count > 0 || count == kUnsizedArray);
// Multi-dimensional arrays are disallowed.
SkASSERT(!componentType.is<ArrayType>());
}
bool matches(const Type& otherType) const override {
const Type& that = otherType.resolve();
return that.isArray() && this->columns() == that.columns() &&
this->componentType().matches(that.componentType());
}
bool isArray() const override {
return true;
}
bool isBuiltin() const override {
return fIsBuiltin;
}
bool isOrContainsArray() const override {
return true;
}
bool isOrContainsAtomic() const override {
return fComponentType.isOrContainsAtomic();
}
bool isOrContainsBool() const override {
return fComponentType.isOrContainsBool();
}
bool isUnsizedArray() const override {
return fCount == kUnsizedArray;
}
bool isOrContainsUnsizedArray() const override {
return this->isUnsizedArray() || fComponentType.isOrContainsUnsizedArray();
}
const Type& componentType() const override {
return fComponentType;
}
int columns() const override {
return fCount;
}
int bitWidth() const override {
return fComponentType.bitWidth();
}
bool isAllowedInES2() const override {
return fComponentType.isAllowedInES2();
}
bool isAllowedInUniform(Position* errorPosition) const override {
return fComponentType.isAllowedInUniform(errorPosition);
}
size_t slotCount() const override {
SkASSERT(fCount != kUnsizedArray);
SkASSERT(fCount > 0);
return fCount * fComponentType.slotCount();
}
const Type& slotType(size_t n) const override {
SkASSERT(fCount == kUnsizedArray || n < this->slotCount());
return fComponentType.slotType(n % fComponentType.slotCount());
}
private:
using INHERITED = Type;
const Type& fComponentType;
int fCount;
bool fIsBuiltin;
};
class GenericType final : public Type {
public:
inline static constexpr TypeKind kTypeKind = TypeKind::kGeneric;
GenericType(const char* name, SkSpan<const Type* const> coercibleTypes, const Type* slotType)
: INHERITED(name, "G", kTypeKind)
, fSlotType(slotType) {
fNumTypes = coercibleTypes.size();
SkASSERT(fNumTypes <= std::size(fCoercibleTypes));
std::copy(coercibleTypes.begin(), coercibleTypes.end(), fCoercibleTypes);
}
SkSpan<const Type* const> coercibleTypes() const override {
return SkSpan(fCoercibleTypes, fNumTypes);
}
const Type& slotType(size_t) const override {
return *fSlotType;
}
private:
using INHERITED = Type;
const Type* fCoercibleTypes[9];
const Type* fSlotType;
size_t fNumTypes;
};
class LiteralType : public Type {
public:
inline static constexpr TypeKind kTypeKind = TypeKind::kLiteral;
LiteralType(const char* name, const Type& scalarType, int8_t priority)
: INHERITED(name, "L", kTypeKind)
, fScalarType(scalarType)
, fPriority(priority) {}
const Type& scalarTypeForLiteral() const override {
return fScalarType;
}
int priority() const override {
return fPriority;
}
int columns() const override {
return 1;
}
int rows() const override {
return 1;
}
NumberKind numberKind() const override {
return fScalarType.numberKind();
}
int bitWidth() const override {
return fScalarType.bitWidth();
}
double minimumValue() const override {
return fScalarType.minimumValue();
}
double maximumValue() const override {
return fScalarType.maximumValue();
}
bool isScalar() const override {
return true;
}
bool isLiteral() const override {
return true;
}
bool isOrContainsBool() const override {
return fScalarType.isBoolean();
}
size_t slotCount() const override {
return 1;
}
const Type& slotType(size_t n) const override {
SkASSERT(n == 0);
return fScalarType;
}
private:
using INHERITED = Type;
const Type& fScalarType;
int8_t fPriority;
};
class ScalarType final : public Type {
public:
inline static constexpr TypeKind kTypeKind = TypeKind::kScalar;
ScalarType(std::string_view name, const char* abbrev, NumberKind numberKind, int8_t priority,
int8_t bitWidth)
: INHERITED(name, abbrev, kTypeKind)
, fNumberKind(numberKind)
, fPriority(priority)
, fBitWidth(bitWidth) {}
NumberKind numberKind() const override {
return fNumberKind;
}
int priority() const override {
return fPriority;
}
int bitWidth() const override {
return fBitWidth;
}
int columns() const override {
return 1;
}
int rows() const override {
return 1;
}
bool isScalar() const override {
return true;
}
bool isAllowedInES2() const override {
return fNumberKind != NumberKind::kUnsigned;
}
bool isAllowedInUniform(Position*) const override {
return fNumberKind != NumberKind::kBoolean;
}
bool isOrContainsBool() const override {
return fNumberKind == NumberKind::kBoolean;
}
size_t slotCount() const override {
return 1;
}
const Type& slotType(size_t n) const override {
SkASSERT(n == 0);
return *this;
}
using int_limits = std::numeric_limits<int32_t>;
using short_limits = std::numeric_limits<int16_t>;
using uint_limits = std::numeric_limits<uint32_t>;
using ushort_limits = std::numeric_limits<uint16_t>;
using float_limits = std::numeric_limits<float>;
/** Returns the maximum value that can fit in the type. */
double minimumValue() const override {
switch (this->numberKind()) {
case NumberKind::kSigned:
return this->highPrecision() ? int_limits::lowest()
: short_limits::lowest();
case NumberKind::kUnsigned:
return 0;
case NumberKind::kFloat:
default:
return float_limits::lowest();
}
}
/** Returns the maximum value that can fit in the type. */
double maximumValue() const override {
switch (this->numberKind()) {
case NumberKind::kSigned:
return this->highPrecision() ? int_limits::max()
: short_limits::max();
case NumberKind::kUnsigned:
return this->highPrecision() ? uint_limits::max()
: ushort_limits::max();
case NumberKind::kFloat:
default:
return float_limits::max();
}
}
private:
using INHERITED = Type;
NumberKind fNumberKind;
int8_t fPriority;
int8_t fBitWidth;
};
class AtomicType final : public Type {
public:
inline static constexpr TypeKind kTypeKind = TypeKind::kAtomic;
AtomicType(std::string_view name, const char* abbrev) : INHERITED(name, abbrev, kTypeKind) {}
bool isAllowedInES2() const override { return false; }
bool isAllowedInUniform(Position*) const override { return false; }
bool isOrContainsAtomic() const override { return true; }
const Type& slotType(size_t n) const override {
SkASSERT(n == 0);
return *this;
}
private:
using INHERITED = Type;
};
class MatrixType final : public Type {
public:
inline static constexpr TypeKind kTypeKind = TypeKind::kMatrix;
MatrixType(std::string_view name, const char* abbrev, const Type& componentType,
int8_t columns, int8_t rows)
: INHERITED(name, abbrev, kTypeKind)
, fComponentType(componentType.as<ScalarType>())
, fColumns(columns)
, fRows(rows) {
SkASSERT(columns >= 2 && columns <= 4);
SkASSERT(rows >= 2 && rows <= 4);
}
const ScalarType& componentType() const override {
return fComponentType;
}
int columns() const override {
return fColumns;
}
int rows() const override {
return fRows;
}
int bitWidth() const override {
return this->componentType().bitWidth();
}
bool isMatrix() const override {
return true;
}
bool isAllowedInES2() const override {
return fColumns == fRows;
}
size_t slotCount() const override {
return fColumns * fRows;
}
const Type& slotType(size_t n) const override {
SkASSERT(n < this->slotCount());
return fComponentType;
}
private:
using INHERITED = Type;
const ScalarType& fComponentType;
int8_t fColumns;
int8_t fRows;
};
class TextureType final : public Type {
public:
inline static constexpr TypeKind kTypeKind = TypeKind::kTexture;
TextureType(const char* name, SpvDim_ dimensions, bool isDepth, bool isArrayed,
bool isMultisampled, TextureAccess textureAccess)
: INHERITED(name, "T", kTypeKind)
, fDimensions(dimensions)
, fIsDepth(isDepth)
, fIsArrayed(isArrayed)
, fIsMultisampled(isMultisampled)
, fTextureAccess(textureAccess) {}
SpvDim_ dimensions() const override {
return fDimensions;
}
bool isDepth() const override {
return fIsDepth;
}
bool isArrayedTexture() const override {
return fIsArrayed;
}
bool isMultisampled() const override {
return fIsMultisampled;
}
TextureAccess textureAccess() const override {
return fTextureAccess;
}
const Type& slotType(size_t n) const override {
SkASSERT(n == 0);
return *this;
}
private:
using INHERITED = Type;
SpvDim_ fDimensions;
bool fIsDepth;
bool fIsArrayed;
bool fIsMultisampled;
TextureAccess fTextureAccess;
};
class SamplerType final : public Type {
public:
inline static constexpr TypeKind kTypeKind = TypeKind::kSampler;
SamplerType(const char* name, const Type& textureType)
: INHERITED(name, "Z", kTypeKind)
, fTextureType(textureType.as<TextureType>()) {
// Samplers require sampled texture access.
SkASSERT(this->textureAccess() == TextureAccess::kSample);
// Subpass inputs cannot be sampled.
SkASSERT(this->dimensions() != SpvDimSubpassData);
}
const TextureType& textureType() const override {
return fTextureType;
}
SpvDim_ dimensions() const override {
return fTextureType.dimensions();
}
bool isDepth() const override {
return fTextureType.isDepth();
}
bool isArrayedTexture() const override {
return fTextureType.isArrayedTexture();
}
bool isMultisampled() const override {
return fTextureType.isMultisampled();
}
TextureAccess textureAccess() const override {
return fTextureType.textureAccess();
}
const Type& slotType(size_t n) const override {
SkASSERT(n == 0);
return *this;
}
private:
using INHERITED = Type;
const TextureType& fTextureType;
};
class StructType final : public Type {
public:
inline static constexpr TypeKind kTypeKind = TypeKind::kStruct;
StructType(Position pos, std::string_view name, TArray<Field> fields, int nestingDepth,
bool interfaceBlock, bool isBuiltin)
: INHERITED(std::move(name), "S", kTypeKind, pos)
, fFields(std::move(fields))
, fNestingDepth(nestingDepth)
, fInterfaceBlock(interfaceBlock)
, fIsBuiltin(isBuiltin) {
for (const Field& f : fFields) {
fContainsArray = fContainsArray || f.fType->isOrContainsArray();
fContainsUnsizedArray = fContainsUnsizedArray || f.fType->isOrContainsUnsizedArray();
fContainsAtomic = fContainsAtomic || f.fType->isOrContainsAtomic();
fContainsBool = fContainsBool || f.fType->isOrContainsBool();
fIsAllowedInES2 = fIsAllowedInES2 && f.fType->isAllowedInES2();
}
for (const Field& f : fFields) {
Position errorPosition = f.fPosition;
if (!f.fType->isAllowedInUniform(&errorPosition)) {
fUniformErrorPosition = errorPosition;
break;
}
}
if (!fContainsUnsizedArray) {
for (const Field& f : fFields) {
fSlotCount += f.fType->slotCount();
}
}
}
SkSpan<const Field> fields() const override {
return fFields;
}
bool isStruct() const override {
return true;
}
bool isInterfaceBlock() const override {
return fInterfaceBlock;
}
bool isBuiltin() const override {
return fIsBuiltin;
}
bool isAllowedInES2() const override {
return fIsAllowedInES2;
}
bool isAllowedInUniform(Position* errorPosition) const override {
if (errorPosition != nullptr) {
*errorPosition = fUniformErrorPosition;
}
return !fUniformErrorPosition.valid();
}
bool isOrContainsArray() const override {
return fContainsArray;
}
bool isOrContainsUnsizedArray() const override {
return fContainsUnsizedArray;
}
bool isOrContainsAtomic() const override {
return fContainsAtomic;
}
bool isOrContainsBool() const override {
return fContainsBool;
}
size_t slotCount() const override {
SkASSERT(!fContainsUnsizedArray);
return fSlotCount;
}
int structNestingDepth() const override {
return fNestingDepth;
}
const Type& slotType(size_t n) const override {
for (const Field& field : fFields) {
size_t fieldSlots = field.fType->slotCount();
if (n < fieldSlots) {
return field.fType->slotType(n);
} else {
n -= fieldSlots;
}
}
SkDEBUGFAIL("slot index out of range");
return *this;
}
private:
using INHERITED = Type;
TArray<Field> fFields;
size_t fSlotCount = 0;
int fNestingDepth = 0;
Position fUniformErrorPosition = {};
bool fInterfaceBlock = false;
bool fContainsArray = false;
bool fContainsUnsizedArray = false;
bool fContainsAtomic = false;
bool fContainsBool = false;
bool fIsBuiltin = false;
bool fIsAllowedInES2 = true;
};
class VectorType final : public Type {
public:
inline static constexpr TypeKind kTypeKind = TypeKind::kVector;
VectorType(std::string_view name, const char* abbrev, const Type& componentType,
int8_t columns)
: INHERITED(name, abbrev, kTypeKind)
, fComponentType(componentType.as<ScalarType>())
, fColumns(columns) {
SkASSERT(columns >= 2 && columns <= 4);
}
const ScalarType& componentType() const override {
return fComponentType;
}
int columns() const override {
return fColumns;
}
int rows() const override {
return 1;
}
int bitWidth() const override {
return this->componentType().bitWidth();
}
bool isVector() const override {
return true;
}
bool isAllowedInES2() const override {
return fComponentType.isAllowedInES2();
}
bool isAllowedInUniform(Position* errorPosition) const override {
return fComponentType.isAllowedInUniform(errorPosition);
}
bool isOrContainsBool() const override {
return fComponentType.isOrContainsBool();
}
size_t slotCount() const override {
return fColumns;
}
const Type& slotType(size_t n) const override {
SkASSERT(n < SkToSizeT(fColumns));
return fComponentType;
}
private:
using INHERITED = Type;
const ScalarType& fComponentType;
int8_t fColumns;
};
std::string Type::getArrayName(int arraySize) const {
std::string_view name = this->name();
if (arraySize == kUnsizedArray) {
return String::printf("%.*s[]", (int)name.size(), name.data());
}
return String::printf("%.*s[%d]", (int)name.size(), name.data(), arraySize);
}
std::unique_ptr<Type> Type::MakeAliasType(std::string_view name, const Type& targetType) {
return std::make_unique<AliasType>(std::move(name), targetType);
}
std::unique_ptr<Type> Type::MakeArrayType(const Context& context,
std::string_view name,
const Type& componentType,
int columns) {
return std::make_unique<ArrayType>(std::move(name), componentType.abbreviatedName(),
componentType, columns, context.fConfig->isBuiltinCode());
}
std::unique_ptr<Type> Type::MakeGenericType(const char* name,
SkSpan<const Type* const> types,
const Type* slotType) {
return std::make_unique<GenericType>(name, types, slotType);
}
std::unique_ptr<Type> Type::MakeLiteralType(const char* name, const Type& scalarType,
int8_t priority) {
return std::make_unique<LiteralType>(name, scalarType, priority);
}
std::unique_ptr<Type> Type::MakeMatrixType(std::string_view name, const char* abbrev,
const Type& componentType, int columns, int8_t rows) {
return std::make_unique<MatrixType>(name, abbrev, componentType, columns, rows);
}
std::unique_ptr<Type> Type::MakeSamplerType(const char* name, const Type& textureType) {
return std::make_unique<SamplerType>(name, textureType);
}
std::unique_ptr<Type> Type::MakeSpecialType(const char* name, const char* abbrev,
Type::TypeKind typeKind) {
return std::unique_ptr<Type>(new Type(name, abbrev, typeKind));
}
std::unique_ptr<Type> Type::MakeScalarType(std::string_view name, const char* abbrev,
Type::NumberKind numberKind, int8_t priority,
int8_t bitWidth) {
return std::make_unique<ScalarType>(name, abbrev, numberKind, priority, bitWidth);
}
std::unique_ptr<Type> Type::MakeAtomicType(std::string_view name, const char* abbrev) {
return std::make_unique<AtomicType>(name, abbrev);
}
std::unique_ptr<Type> Type::MakeStructType(const Context& context,
Position pos,
std::string_view name,
TArray<Field> fields,
bool interfaceBlock) {
const char* const structOrIB = interfaceBlock ? "interface block" : "struct";
const char* const aStructOrIB = interfaceBlock ? "an interface block" : "a struct";
if (fields.empty()) {
context.fErrors->error(pos, std::string(structOrIB) + " '" + std::string(name) +
"' must contain at least one field");
}
size_t slots = 0;
THashSet<std::string_view> fieldNames;
for (const Field& field : fields) {
// Add this field name to our set; if the set doesn't grow, we found a duplicate.
int numFieldNames = fieldNames.count();
fieldNames.add(field.fName);
if (fieldNames.count() == numFieldNames) {
context.fErrors->error(field.fPosition, "field '" + std::string(field.fName) +
"' was already defined in the same " +
std::string(structOrIB) + " ('" +
std::string(name) + "')");
}
if (field.fModifierFlags != ModifierFlag::kNone) {
std::string desc = field.fModifierFlags.description();
context.fErrors->error(field.fPosition, "modifier '" + desc + "' is not permitted on " +
std::string(aStructOrIB) + " field");
}
if (field.fLayout.fFlags & LayoutFlag::kBinding) {
context.fErrors->error(field.fPosition, "layout qualifier 'binding' is not permitted "
"on " + std::string(aStructOrIB) + " field");
}
if (field.fLayout.fFlags & LayoutFlag::kSet) {
context.fErrors->error(field.fPosition, "layout qualifier 'set' is not permitted on " +
std::string(aStructOrIB) + " field");
}
if (field.fType->isVoid()) {
context.fErrors->error(field.fPosition, "type 'void' is not permitted in " +
std::string(aStructOrIB));
}
if (field.fType->isOpaque() && !field.fType->isAtomic()) {
context.fErrors->error(field.fPosition, "opaque type '" + field.fType->displayName() +
"' is not permitted in " +
std::string(aStructOrIB));
}
if (interfaceBlock && field.fType->isOrContainsBool()) {
// Reject booleans anywhere in an interface block.
context.fErrors->error(field.fPosition, "type 'bool' is not permitted in an "
"interface block");
}
if (field.fType->isOrContainsUnsizedArray()) {
if (!interfaceBlock) {
// Reject unsized arrays anywhere in structs.
context.fErrors->error(field.fPosition, "unsized arrays are not permitted here");
}
} else {
// If we haven't already exceeded the struct size limit...
if (slots < kVariableSlotLimit) {
// ... see if this field causes us to exceed the size limit.
slots = SkSafeMath::Add(slots, field.fType->slotCount());
if (slots >= kVariableSlotLimit) {
context.fErrors->error(pos, std::string(structOrIB) + " is too large");
}
}
}
}
int nestingDepth = 0;
for (const Field& field : fields) {
nestingDepth = std::max(nestingDepth, field.fType->structNestingDepth());
}
if (nestingDepth >= kMaxStructDepth) {
context.fErrors->error(pos, std::string(structOrIB) + " '" + std::string(name) +
"' is too deeply nested");
}
return std::make_unique<StructType>(pos, name, std::move(fields), nestingDepth + 1,
interfaceBlock, context.fConfig->isBuiltinCode());
}
std::unique_ptr<Type> Type::MakeTextureType(const char* name, SpvDim_ dimensions, bool isDepth,
bool isArrayedTexture, bool isMultisampled,
TextureAccess textureAccess) {
return std::make_unique<TextureType>(name, dimensions, isDepth, isArrayedTexture,
isMultisampled, textureAccess);
}
std::unique_ptr<Type> Type::MakeVectorType(std::string_view name, const char* abbrev,
const Type& componentType, int columns) {
return std::make_unique<VectorType>(name, abbrev, componentType, columns);
}
CoercionCost Type::coercionCost(const Type& other) const {
if (this->matches(other)) {
return CoercionCost::Free();
}
if (this->typeKind() == other.typeKind() &&
(this->isVector() || this->isMatrix() || this->isArray())) {
// Vectors/matrices/arrays of the same size can be coerced if their component type can be.
if (this->isMatrix() && (this->rows() != other.rows())) {
return CoercionCost::Impossible();
}
if (this->columns() != other.columns()) {
return CoercionCost::Impossible();
}
return this->componentType().coercionCost(other.componentType());
}
if (this->isNumber() && other.isNumber()) {
if (this->isLiteral() && this->isInteger()) {
return CoercionCost::Free();
} else if (this->numberKind() != other.numberKind()) {
return CoercionCost::Impossible();
} else if (other.priority() >= this->priority()) {
return CoercionCost::Normal(other.priority() - this->priority());
} else {
return CoercionCost::Narrowing(this->priority() - other.priority());
}
}
if (fTypeKind == TypeKind::kGeneric) {
SkSpan<const Type* const> types = this->coercibleTypes();
for (size_t i = 0; i < types.size(); i++) {
if (types[i]->matches(other)) {
return CoercionCost::Normal((int) i + 1);
}
}
}
return CoercionCost::Impossible();
}
const Type* Type::applyQualifiers(const Context& context,
ModifierFlags* modifierFlags,
Position pos) const {
const Type* type;
type = this->applyPrecisionQualifiers(context, modifierFlags, pos);
type = type->applyAccessQualifiers(context, modifierFlags, pos);
return type;
}
const Type* Type::applyPrecisionQualifiers(const Context& context,
ModifierFlags* modifierFlags,
Position pos) const {
ModifierFlags precisionQualifiers = *modifierFlags & (ModifierFlag::kHighp |
ModifierFlag::kMediump |
ModifierFlag::kLowp);
if (precisionQualifiers == ModifierFlag::kNone) {
// No precision qualifiers here. Return the type as-is.
return this;
}
if (!ProgramConfig::IsRuntimeEffect(context.fConfig->fKind)) {
// We want to discourage precision modifiers internally. Instead, use the type that
// corresponds to the precision you need. (e.g. half vs float, short vs int)
context.fErrors->error(pos, "precision qualifiers are not allowed");
return context.fTypes.fPoison.get();
}
if (SkPopCount(precisionQualifiers.value()) > 1) {
context.fErrors->error(pos, "only one precision qualifier can be used");
return context.fTypes.fPoison.get();
}
// We're going to return a whole new type, so the modifier bits can be cleared out.
*modifierFlags &= ~(ModifierFlag::kHighp |
ModifierFlag::kMediump |
ModifierFlag::kLowp);
const Type& component = this->componentType();
if (component.highPrecision()) {
if (precisionQualifiers & ModifierFlag::kHighp) {
// Type is already high precision, and we are requesting high precision. Return as-is.
return this;
}
// SkSL doesn't support low precision, so `lowp` is interpreted as medium precision.
// Ascertain the mediump equivalent type for this type, if any.
const Type* mediumpType;
switch (component.numberKind()) {
case Type::NumberKind::kFloat:
mediumpType = context.fTypes.fHalf.get();
break;
case Type::NumberKind::kSigned:
mediumpType = context.fTypes.fShort.get();
break;
case Type::NumberKind::kUnsigned:
mediumpType = context.fTypes.fUShort.get();
break;
default:
mediumpType = context.fTypes.fPoison.get();
break;
}
if (mediumpType) {
// Convert the mediump component type into the final vector/matrix/array type as needed.
return this->isArray()
? context.fSymbolTable->addArrayDimension(context, mediumpType, this->columns())
: &mediumpType->toCompound(context, this->columns(), this->rows());
}
}
context.fErrors->error(pos, "type '" + this->displayName() +
"' does not support precision qualifiers");
return context.fTypes.fPoison.get();
}
const Type* Type::applyAccessQualifiers(const Context& context,
ModifierFlags* modifierFlags,
Position pos) const {
ModifierFlags accessQualifiers = *modifierFlags & (ModifierFlag::kReadOnly |
ModifierFlag::kWriteOnly);
// We're going to return a whole new type, so the modifier bits must be cleared out.
*modifierFlags &= ~(ModifierFlag::kReadOnly |
ModifierFlag::kWriteOnly);
if (this->matches(*context.fTypes.fTexture2D)) {
// We require all texture2Ds to be qualified with `readonly` or `writeonly`.
// (Read-write textures are not yet supported in WGSL.)
if (accessQualifiers == ModifierFlag::kReadOnly) {
return context.fTypes.fReadOnlyTexture2D.get();
}
if (accessQualifiers == ModifierFlag::kWriteOnly) {
return context.fTypes.fWriteOnlyTexture2D.get();
}
context.fErrors->error(
pos,
accessQualifiers
? "'readonly' and 'writeonly' qualifiers cannot be combined"
: "'texture2D' requires a 'readonly' or 'writeonly' access qualifier");
return this;
}
if (accessQualifiers) {
context.fErrors->error(pos, "type '" + this->displayName() + "' does not support "
"qualifier '" + accessQualifiers.description() + "'");
}
return this;
}
const Type& Type::toCompound(const Context& context, int columns, int rows) const {
SkASSERT(this->isScalar());
if (columns == 1 && rows == 1) {
return *this;
}
if (this->matches(*context.fTypes.fFloat) || this->matches(*context.fTypes.fFloatLiteral)) {
switch (rows) {
case 1:
switch (columns) {
case 1: return *context.fTypes.fFloat;
case 2: return *context.fTypes.fFloat2;
case 3: return *context.fTypes.fFloat3;
case 4: return *context.fTypes.fFloat4;
default: SK_ABORT("unsupported vector column count (%d)", columns);
}
case 2:
switch (columns) {
case 2: return *context.fTypes.fFloat2x2;
case 3: return *context.fTypes.fFloat3x2;
case 4: return *context.fTypes.fFloat4x2;
default: SK_ABORT("unsupported matrix column count (%d)", columns);
}
case 3:
switch (columns) {
case 2: return *context.fTypes.fFloat2x3;
case 3: return *context.fTypes.fFloat3x3;
case 4: return *context.fTypes.fFloat4x3;
default: SK_ABORT("unsupported matrix column count (%d)", columns);
}
case 4:
switch (columns) {
case 2: return *context.fTypes.fFloat2x4;
case 3: return *context.fTypes.fFloat3x4;
case 4: return *context.fTypes.fFloat4x4;
default: SK_ABORT("unsupported matrix column count (%d)", columns);
}
default: SK_ABORT("unsupported row count (%d)", rows);
}
} else if (this->matches(*context.fTypes.fHalf)) {
switch (rows) {
case 1:
switch (columns) {
case 1: return *context.fTypes.fHalf;
case 2: return *context.fTypes.fHalf2;
case 3: return *context.fTypes.fHalf3;
case 4: return *context.fTypes.fHalf4;
default: SK_ABORT("unsupported vector column count (%d)", columns);
}
case 2:
switch (columns) {
case 2: return *context.fTypes.fHalf2x2;
case 3: return *context.fTypes.fHalf3x2;
case 4: return *context.fTypes.fHalf4x2;
default: SK_ABORT("unsupported matrix column count (%d)", columns);
}
case 3:
switch (columns) {
case 2: return *context.fTypes.fHalf2x3;
case 3: return *context.fTypes.fHalf3x3;
case 4: return *context.fTypes.fHalf4x3;
default: SK_ABORT("unsupported matrix column count (%d)", columns);
}
case 4:
switch (columns) {
case 2: return *context.fTypes.fHalf2x4;
case 3: return *context.fTypes.fHalf3x4;
case 4: return *context.fTypes.fHalf4x4;
default: SK_ABORT("unsupported matrix column count (%d)", columns);
}
default: SK_ABORT("unsupported row count (%d)", rows);
}
} else if (this->matches(*context.fTypes.fInt) || this->matches(*context.fTypes.fIntLiteral)) {
switch (rows) {
case 1:
switch (columns) {
case 1: return *context.fTypes.fInt;
case 2: return *context.fTypes.fInt2;
case 3: return *context.fTypes.fInt3;
case 4: return *context.fTypes.fInt4;
default: SK_ABORT("unsupported vector column count (%d)", columns);
}
default: SK_ABORT("unsupported row count (%d)", rows);
}
} else if (this->matches(*context.fTypes.fShort)) {
switch (rows) {
case 1:
switch (columns) {
case 1: return *context.fTypes.fShort;
case 2: return *context.fTypes.fShort2;
case 3: return *context.fTypes.fShort3;
case 4: return *context.fTypes.fShort4;
default: SK_ABORT("unsupported vector column count (%d)", columns);
}
default: SK_ABORT("unsupported row count (%d)", rows);
}
} else if (this->matches(*context.fTypes.fUInt)) {
switch (rows) {
case 1:
switch (columns) {
case 1: return *context.fTypes.fUInt;
case 2: return *context.fTypes.fUInt2;
case 3: return *context.fTypes.fUInt3;
case 4: return *context.fTypes.fUInt4;
default: SK_ABORT("unsupported vector column count (%d)", columns);
}
default: SK_ABORT("unsupported row count (%d)", rows);
}
} else if (this->matches(*context.fTypes.fUShort)) {
switch (rows) {
case 1:
switch (columns) {
case 1: return *context.fTypes.fUShort;
case 2: return *context.fTypes.fUShort2;
case 3: return *context.fTypes.fUShort3;
case 4: return *context.fTypes.fUShort4;
default: SK_ABORT("unsupported vector column count (%d)", columns);
}
default: SK_ABORT("unsupported row count (%d)", rows);
}
} else if (this->matches(*context.fTypes.fBool)) {
switch (rows) {
case 1:
switch (columns) {
case 1: return *context.fTypes.fBool;
case 2: return *context.fTypes.fBool2;
case 3: return *context.fTypes.fBool3;
case 4: return *context.fTypes.fBool4;
default: SK_ABORT("unsupported vector column count (%d)", columns);
}
default: SK_ABORT("unsupported row count (%d)", rows);
}
}
SkDEBUGFAILF("unsupported toCompound type %s", this->description().c_str());
return *context.fTypes.fVoid;
}
const Type* Type::clone(const Context& context, SymbolTable* symbolTable) const {
// Many types are built-ins, and exist in every SymbolTable by default.
if (this->isInRootSymbolTable()) {
return this;
}
// If we are compiling a program, and the type comes from the program's module, it is safe to
// assume that the type is in-scope anywhere in the program without actually recursing through
// the SymbolTable hierarchy to prove it.
if (!context.fConfig->isBuiltinCode() && this->isBuiltin()) {
return this;
}
// Even if the type isn't a built-in, it might already exist in the SymbolTable. Search by name.
const Symbol* existingSymbol = symbolTable->find(this->name());
if (existingSymbol != nullptr) {
const Type* existingType = &existingSymbol->as<Type>();
SkASSERT(existingType->typeKind() == this->typeKind());
return existingType;
}
// This type actually needs to be cloned into the destination SymbolTable.
switch (this->typeKind()) {
case TypeKind::kArray: {
return symbolTable->addArrayDimension(context, &this->componentType(), this->columns());
}
case TypeKind::kStruct: {
// We are cloning an existing struct, so there's no need to call MakeStructType and
// fully error-check it again.
const std::string* name = symbolTable->takeOwnershipOfString(std::string(this->name()));
SkSpan<const Field> fieldSpan = this->fields();
return symbolTable->add(
context,
std::make_unique<StructType>(this->fPosition,
*name,
TArray<Field>(fieldSpan.data(), fieldSpan.size()),
this->structNestingDepth(),
/*interfaceBlock=*/this->isInterfaceBlock(),
/*isBuiltin=*/context.fConfig->isBuiltinCode()));
}
default:
SkDEBUGFAILF("don't know how to clone type '%s'", this->description().c_str());
return nullptr;
}
}
std::unique_ptr<Expression> Type::coerceExpression(std::unique_ptr<Expression> expr,
const Context& context) const {
if (!expr || expr->isIncomplete(context)) {
return nullptr;
}
if (expr->type().matches(*this)) {
return expr;
}
const Position pos = expr->fPosition;
const ProgramSettings& settings = context.fConfig->fSettings;
if (!expr->coercionCost(*this).isPossible(settings.fAllowNarrowingConversions)) {
context.fErrors->error(pos, "expected '" + this->displayName() + "', but found '" +
expr->type().displayName() + "'");
return nullptr;
}
if (this->isScalar()) {
return ConstructorScalarCast::Make(context, pos, *this, std::move(expr));
}
if (this->isVector() || this->isMatrix()) {
return ConstructorCompoundCast::Make(context, pos, *this, std::move(expr));
}
if (this->isArray()) {
return ConstructorArrayCast::Make(context, pos, *this, std::move(expr));
}
context.fErrors->error(pos, "cannot construct '" + this->displayName() + "'");
return nullptr;
}
bool Type::isAllowedInES2(const Context& context) const {
return !context.fConfig->strictES2Mode() || this->isAllowedInES2();
}
bool Type::checkForOutOfRangeLiteral(const Context& context, const Expression& expr) const {
bool foundError = false;
const Type& baseType = this->componentType();
if (baseType.isNumber()) {
// Replace constant expressions with their corresponding values.
const Expression* valueExpr = ConstantFolder::GetConstantValueForVariable(expr);
// Unsized arrays can't have constants and fails to get a slotCount.
if (valueExpr->supportsConstantValues() && !valueExpr->type().isUnsizedArray()) {
// Iterate over every constant subexpression in the value.
int numSlots = valueExpr->type().slotCount();
for (int slot = 0; slot < numSlots; ++slot) {
std::optional<double> slotVal = valueExpr->getConstantValue(slot);
// Check for Literal values that are out of range for the base type.
if (slotVal.has_value() &&
baseType.checkForOutOfRangeLiteral(context, *slotVal, valueExpr->fPosition)) {
foundError = true;
}
}
}
}
// We don't need range checks for floats or booleans; any matched-type value is acceptable.
return foundError;
}
bool Type::checkForOutOfRangeLiteral(const Context& context, double value, Position pos) const {
SkASSERT(this->isScalar());
if (!this->isNumber()) {
return false;
}
if (value >= this->minimumValue() && value <= this->maximumValue()) {
return false;
}
// We found a value that can't fit in our type. Flag it as an error.
context.fErrors->error(pos, SkSL::String::printf("value is out of range for type '%s': %.0f",
this->displayName().c_str(),
value));
return true;
}
bool Type::checkIfUsableInArray(const Context& context, Position arrayPos) const {
if (this->isArray()) {
context.fErrors->error(arrayPos, "multi-dimensional arrays are not supported");
return false;
}
if (this->isVoid()) {
context.fErrors->error(arrayPos, "type 'void' may not be used in an array");
return false;
}
if (this->isOpaque() && !this->isAtomic()) {
context.fErrors->error(arrayPos, "opaque type '" + std::string(this->name()) +
"' may not be used in an array");
return false;
}
return true;
}
SKSL_INT Type::convertArraySize(const Context& context,
Position arrayPos,
std::unique_ptr<Expression> size) const {
size = context.fTypes.fInt->coerceExpression(std::move(size), context);
if (!size) {
return 0;
}
SKSL_INT count;
if (!ConstantFolder::GetConstantInt(*size, &count)) {
context.fErrors->error(size->fPosition, "array size must be an integer");
return 0;
}
return this->convertArraySize(context, arrayPos, size->fPosition, count);
}
SKSL_INT Type::convertArraySize(const Context& context,
Position arrayPos,
Position sizePos,
SKSL_INT size) const {
if (!this->checkIfUsableInArray(context, arrayPos)) {
// `checkIfUsableInArray` will generate an error for us.
return 0;
}
if (size <= 0) {
context.fErrors->error(sizePos, "array size must be positive");
return 0;
}
// We can't get a meaningful slot count if the interior type contains an unsized array; we'll
// assert if we try. Unsized arrays should only occur in a handful of limited cases (e.g. an
// interface block with a trailing buffer), and will never be valid in a runtime effect.
if (!this->isOrContainsUnsizedArray()) {
if (SkSafeMath::Mul(this->slotCount(), size) > kVariableSlotLimit) {
context.fErrors->error(sizePos, "array size is too large");
return 0;
}
}
return size;
}
std::string Field::description() const {
return fLayout.paddedDescription() + fModifierFlags.paddedDescription() + fType->displayName() +
' ' + std::string(fName) + ';';
}
} // namespace SkSL