blob: 15cb8371d7345ea58372bf4da29892d48c15321a [file] [log] [blame]
// Copyright 2023 The Dawn & Tint Authors
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
// 1. Redistributions of source code must retain the above copyright notice, this
// list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright notice,
// this list of conditions and the following disclaimer in the documentation
// and/or other materials provided with the distribution.
//
// 3. Neither the name of the copyright holder nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#ifndef SRC_TINT_LANG_CORE_INTRINSIC_TABLE_DATA_H_
#define SRC_TINT_LANG_CORE_INTRINSIC_TABLE_DATA_H_
#include <stdint.h>
#include <limits>
#include <string>
#include "src/tint/lang/core/constant/eval.h"
#include "src/tint/lang/core/evaluation_stage.h"
#include "src/tint/lang/core/parameter_usage.h"
#include "src/tint/utils/containers/enum_set.h"
#include "src/tint/utils/containers/slice.h"
#include "src/tint/utils/text/styled_text.h"
#include "src/tint/utils/text/text_style.h"
/// Forward declaration
namespace tint::core::intrinsic {
struct TableData;
} // namespace tint::core::intrinsic
namespace tint::core::intrinsic {
/// An enumerator of index namespaces.
enum class TableIndexNamespace : uint8_t {
kTemplate,
kMatcher,
kMatcherIndices,
kTypeMatcher,
kNumberMatcher,
kParameter,
kOverload,
kConstEvalFunction,
};
/// A wrapper around an integer type, used to index intrinsic table data
/// @tparam T the index data type
/// @tparam N the index namespace
template <TableIndexNamespace N, typename T>
struct TableIndex {
static_assert(std::is_integral_v<T> && std::is_unsigned_v<T>,
"T must be an unsigned integer type");
/// The value used to represent an invalid index
static constexpr T kInvalid = std::numeric_limits<T>::max();
/// Constructor for invalid index
constexpr TableIndex() : value(kInvalid) {}
/// Constructor
/// @param index the index value
constexpr explicit TableIndex(T index) : value(index) {}
/// @returns true if this index is not invalid
bool IsValid() const { return value != kInvalid; }
/// Equality operator
/// @param other the index to compare against
/// @returns true if this index is equal to @p other
bool operator==(const TableIndex& other) { return value == other.value; }
/// Inequality operator
/// @param other the index to compare against
/// @returns true if this index is equal to @p other
bool operator!=(const TableIndex& other) { return value != other.value; }
/// @param offset the offset to apply to this index
/// @returns a new index offset by @p offset
template <typename U>
auto operator+(U offset) const {
static_assert(std::is_integral_v<U> && std::is_unsigned_v<U>,
"T must be an unsigned integer type");
using C = std::conditional_t<(sizeof(U) > sizeof(T)), U, T>;
C new_value = static_cast<C>(value) + static_cast<C>(offset);
return TableIndex<N, decltype(new_value)>(new_value);
}
/// @param arr a C-style array
/// @returns true if the integer type `T` has enough bits to index all the
/// elements in the array @p arr.
template <typename U, size_t COUNT>
static constexpr bool CanIndex(U (&arr)[COUNT]) {
(void)arr; // The array isn't actually used
/// kInvalid is the largest value representable by `T`. It is not a valid index.
return COUNT < kInvalid;
}
/// The index value
const T value = kInvalid;
};
/// Index type used to index TableData::template_types
using TemplateIndex = TableIndex<TableIndexNamespace::kTemplate, uint8_t>;
/// Index type used to index TableData::type_matchers or TableData::number_matchers
using MatcherIndex = TableIndex<TableIndexNamespace::kMatcher, uint8_t>;
/// Index type used to index TableData::matcher_indices
using MatcherIndicesIndex = TableIndex<TableIndexNamespace::kMatcherIndices, uint16_t>;
/// Index type used to index TableData::type_matchers
using TypeMatcherIndex = TableIndex<TableIndexNamespace::kTypeMatcher, uint8_t>;
/// Index type used to index TableData::number_matchers
using NumberMatcherIndex = TableIndex<TableIndexNamespace::kNumberMatcher, uint8_t>;
/// Index type used to index TableData::parameters
using ParameterIndex = TableIndex<TableIndexNamespace::kParameter, uint16_t>;
/// Index type used to index TableData::overloads
using OverloadIndex = TableIndex<TableIndexNamespace::kOverload, uint16_t>;
/// Index type used to index TableData::const_eval_functions
using ConstEvalFunctionIndex = TableIndex<TableIndexNamespace::kConstEvalFunction, uint8_t>;
/// Unique flag bits for overloads
enum class OverloadFlag : uint8_t {
kIsBuiltin, // The overload is a builtin ('fn')
kIsOperator, // The overload is an operator ('op')
kIsConstructor, // The overload is a value constructor ('ctor')
kIsConverter, // The overload is a value converter ('conv')
kSupportsVertexPipeline, // The overload can be used in vertex shaders
kSupportsFragmentPipeline, // The overload can be used in fragment shaders
kSupportsComputePipeline, // The overload can be used in compute shaders
kMustUse, // The overload cannot be called as a statement
kMemberFunction, // The overload is a member function
kIsDeprecated, // The overload is deprecated
};
/// An enum set of OverloadFlag, used by OperatorInfo
using OverloadFlags = tint::EnumSet<OverloadFlag>;
/// ParameterInfo describes a parameter
struct ParameterInfo {
/// The parameter usage (parameter name in definition file)
const ParameterUsage usage;
/// Index of the matcher indices that are used to match the parameter types.
/// These indices are consumed by the matchers themselves.
const MatcherIndicesIndex matcher_indices;
};
/// TemplateInfo describes an template
struct TemplateInfo {
/// An enumerator of template kind
enum class Kind : uint8_t { kType, kNumber };
/// Name of the template type (e.g. 'T')
const char* name;
/// Index of the type matcher indices that are used to match the template types.
/// These indices are consumed by the matchers themselves.
const MatcherIndicesIndex matcher_indices;
/// The template kind
const Kind kind;
};
/// OverloadInfo describes a single function overload
struct OverloadInfo {
/// The flags for the overload
const OverloadFlags flags;
/// Total number of parameters for the overload
const uint8_t num_parameters;
/// Total number of explicit templates for the overload
const uint8_t num_explicit_templates;
/// Total number of implicit and explicit templates for the overload
const uint8_t num_templates;
/// Index of the first template in TableData::templates
/// This is a list of explicit template types followed by the implicit template types.
const TemplateIndex templates;
/// Index of the first parameter in TableData::parameters
const ParameterIndex parameters;
/// Index of a list of type matcher indices that are used to build the return type.
const MatcherIndicesIndex return_matcher_indices;
/// The function used to evaluate the overload at shader-creation time.
const ConstEvalFunctionIndex const_eval_fn;
};
/// IntrinsicInfo describes a builtin function or operator overload
struct IntrinsicInfo {
/// Number of overloads of the intrinsic
const uint8_t num_overloads;
/// Index of the first overload for the function
const OverloadIndex overloads;
};
/// A IntrinsicInfo with no overloads
static constexpr IntrinsicInfo kNoOverloads{0, OverloadIndex(OverloadIndex::kInvalid)};
/// Number is an 32 bit unsigned integer, which can be in one of three states:
/// * Invalid - Number has not been assigned a value
/// * Valid - a fixed integer value
/// * Any - matches any other non-invalid number
class Number {
enum State : uint8_t {
kInvalid,
kValid,
kAny,
};
constexpr explicit Number(State state) : state_(state) {}
public:
/// A special number representing any number
static const Number any;
/// An invalid number
static const Number invalid;
/// Constructed as a valid number with the value v
/// @param v the value for the number
explicit constexpr Number(uint32_t v) : value_(v), state_(kValid) {}
/// @returns the value of the number
inline uint32_t Value() const { return value_; }
/// @returns the true if the number is valid
inline bool IsValid() const { return state_ == kValid; }
/// @returns the true if the number is any
inline bool IsAny() const { return state_ == kAny; }
/// Assignment operator.
/// The number becomes valid, with the value n
/// @param n the new value for the number
/// @returns this so calls can be chained
inline Number& operator=(uint32_t n) {
value_ = n;
state_ = kValid;
return *this;
}
private:
uint32_t value_ = 0;
State state_ = kInvalid;
};
/// A special type that matches all TypeMatchers
class Any final : public Castable<Any, core::type::Type> {
public:
Any();
~Any() override;
/// @copydoc core::type::UniqueNode::Equals
bool Equals(const core::type::UniqueNode& other) const override;
/// @copydoc core::type::Type::FriendlyName
std::string FriendlyName() const override;
/// @copydoc core::type::Type::Clone
core::type::Type* Clone(core::type::CloneContext& ctx) const override;
};
/// TemplateState holds the state of the template numbers and types.
/// Used by the MatchState.
class TemplateState {
public:
/// If the template type with index @p idx is undefined, then it is defined with the @p ty
/// and Type() returns @p ty. If the template type is defined, and @p ty can be converted to
/// the template type then the template type is returned. If the template type is defined,
/// and the template type can be converted to @p ty, then the template type is replaced with
/// @p ty, and @p ty is returned. If none of the above applies, then @p ty is a type
/// mismatch for the template type, and nullptr is returned.
/// @param idx the index of the template type
/// @param ty the type
/// @returns true on match or newly defined
const core::type::Type* Type(size_t idx, const core::type::Type* ty) {
if (idx >= types_.Length()) {
types_.Resize(idx + 1);
}
auto& t = types_[idx];
if (t == nullptr) {
t = ty;
return ty;
}
ty = core::type::Type::Common(Vector{t, ty});
if (ty) {
t = ty;
}
return ty;
}
/// If the number with index @p idx is undefined, then it is defined with the number
/// `number` and Num() returns true. If the number is defined, then `Num()` returns true iff
/// it is equal to @p ty.
/// @param idx the index of the template number
/// @param number the number
/// @returns true on match or newly defined
bool Num(size_t idx, Number number) {
if (idx >= numbers_.Length()) {
numbers_.Resize(idx + 1, Number::any);
}
auto& n = numbers_[idx];
if (n.IsAny()) {
n = number.Value();
return true;
}
return n.Value() == number.Value();
}
/// @param idx the index of the template type
/// @returns the template type with index @p idx, or nullptr if the type was not
/// defined.
const core::type::Type* Type(size_t idx) const {
if (idx >= types_.Length()) {
return nullptr;
}
return types_[idx];
}
/// SetType replaces the template type with index @p idx with type @p ty.
/// @param idx the index of the template type
/// @param ty the new type for the template
void SetType(size_t idx, const core::type::Type* ty) {
if (idx >= types_.Length()) {
types_.Resize(idx + 1);
}
types_[idx] = ty;
}
/// @returns the number type with index @p idx.
/// @param idx the index of the template number
Number Num(size_t idx) const {
if (idx >= numbers_.Length()) {
return Number::invalid;
}
return numbers_[idx];
}
/// SetNum replaces the template number with index @p idx with number @p num.
/// @param idx the index of the template number
/// @param num the new number for the template
void SetNum(size_t idx, Number num) {
if (idx >= numbers_.Length()) {
numbers_.Resize(idx + 1, Number::any);
}
numbers_[idx] = num;
}
/// @return the total number of type and number templates
size_t Count() const { return types_.Length() + numbers_.Length(); }
private:
Vector<const core::type::Type*, 4> types_;
Vector<Number, 2> numbers_;
};
/// The current overload match state
/// MatchState holds the state used to match an overload.
class MatchState {
public:
/// Constructor
/// @param ty_mgr the type manager
/// @param syms the symbol table
/// @param t the template state
/// @param d the table data
/// @param o the current overload
/// @param matcher_indices the remaining matcher indices
/// @param s the required evaluation stage of the overload
MatchState(core::type::Manager& ty_mgr,
SymbolTable& syms,
TemplateState& t,
const TableData& d,
const OverloadInfo& o,
const MatcherIndex* matcher_indices,
EvaluationStage s)
: types(ty_mgr),
symbols(syms),
templates(t),
data(d),
overload(o),
earliest_eval_stage(s),
matcher_indices_(matcher_indices) {}
/// The type manager
core::type::Manager& types;
/// The symbol manager
SymbolTable& symbols;
/// The template types and numbers
TemplateState& templates;
/// The table data
const TableData& data;
/// The current overload being evaluated
const OverloadInfo& overload;
/// The earliest evaluation stage of the builtin call
EvaluationStage earliest_eval_stage;
/// Type uses the next TypeMatcher from the matcher indices to match the type @p ty.
/// @param ty the type to try matching
/// @returns the canonical expected type if the type matches, otherwise nullptr.
/// @note: The matcher indices are progressed on calling.
inline const core::type::Type* Type(const core::type::Type* ty);
/// Num uses the next NumMatcher from the matcher indices to match @p number.
/// @param number the number to try matching
/// @returns the canonical expected number if the number matches, otherwise an invalid
/// number.
/// @note: The matcher indices are progressed on calling.
inline Number Num(Number number);
/// Prints the type matcher representation to @p out
/// @note: The matcher indices are progressed on calling.
inline void PrintType(StyledText& out);
/// Prints the number matcher representation to @p out
/// @note: The matcher indices are progressed on calling.
inline void PrintNum(StyledText& out);
private:
const MatcherIndex* matcher_indices_ = nullptr;
};
/// A TypeMatcher is the interface used to match an type used as part of an
/// overload's parameter or return type.
struct TypeMatcher {
/// Checks whether the given type matches the matcher rules, and returns the
/// expected, canonicalized type on success.
/// Match may define and refine the template types and numbers in state.
/// The parameter `type` is the type to match
/// Returns the canonicalized type on match, otherwise nullptr
using MatchFn = const core::type::Type*(MatchState& state, const core::type::Type* type);
/// @see #MatchFn
MatchFn* const match;
/// Prints the representation of the matcher.
/// Used for printing error messages when no overload is found.
using PrintFn = void(MatchState* state, StyledText& out);
/// @see #PrintFn
PrintFn* const print;
};
/// A NumberMatcher is the interface used to match a number or enumerator used
/// as part of an overload's parameter or return type.
struct NumberMatcher {
/// Checks whether the given number matches the matcher rules.
/// Match may define template numbers in state.
/// The parameter `number` is the number to match
/// Returns true if the argument type is as expected.
using MatchFn = Number(MatchState& state, Number number);
/// @see #MatchFn
MatchFn* const match;
/// Prints the representation of the matcher.
/// Used for printing error messages when no overload is found.
using PrintFn = void(MatchState* state, StyledText& out);
/// @see #PrintFn
PrintFn* const print;
};
/// TableData holds the immutable data that holds the intrinsic data for a language.
struct TableData {
/// @param idx the index of the TemplateInfo in the table data
/// @returns the TemplateInfo with the given index
template <typename T>
const TemplateInfo& operator[](TableIndex<TableIndexNamespace::kTemplate, T> idx) const {
return templates[idx.value];
}
/// @param idx the index of the MatcherIndices in the table data
/// @returns the MatcherIndices with the given index
template <typename T>
const MatcherIndex* operator[](TableIndex<TableIndexNamespace::kMatcherIndices, T> idx) const {
if (idx.IsValid()) {
return &matcher_indices[idx.value];
}
return nullptr;
}
/// @param idx the index of the TypeMatcher in the table data
/// @returns the TypeMatcher with the given index
template <typename T>
const TypeMatcher& operator[](TableIndex<TableIndexNamespace::kTypeMatcher, T> idx) const {
return type_matchers[idx.value];
}
/// @param idx the index of the NumberMatcher in the table data
/// @returns the NumberMatcher with the given index
template <typename T>
const NumberMatcher& operator[](TableIndex<TableIndexNamespace::kNumberMatcher, T> idx) const {
return number_matchers[idx.value];
}
/// @param idx the index of the ParameterInfo in the table data
/// @returns the ParameterInfo with the given index
template <typename T>
const ParameterInfo& operator[](TableIndex<TableIndexNamespace::kParameter, T> idx) const {
return parameters[idx.value];
}
/// @param idx the index of the OverloadInfo in the table data
/// @returns the OverloadInfo with the given index
template <typename T>
const OverloadInfo& operator[](TableIndex<TableIndexNamespace::kOverload, T> idx) const {
return overloads[idx.value];
}
/// @param idx the index of the constant::Eval::Function in the table data
/// @returns the constant::Eval::Function with the given index
template <typename T>
constant::Eval::Function operator[](
TableIndex<TableIndexNamespace::kConstEvalFunction, T> idx) const {
return idx.IsValid() ? const_eval_functions[idx.value] : nullptr;
}
/// The list of templates used by the intrinsic overloads
const Slice<const TemplateInfo> templates;
/// The list of type matcher indices
const Slice<const MatcherIndex> matcher_indices;
/// The list of type matchers used by the intrinsic overloads
const Slice<const TypeMatcher> type_matchers;
/// The list of number matchers used by the intrinsic overloads
const Slice<const NumberMatcher> number_matchers;
/// The list of parameters used by the intrinsic overloads
const Slice<const ParameterInfo> parameters;
/// The list of overloads used by the intrinsics
const Slice<const OverloadInfo> overloads;
/// The list of constant evaluation functions used by the intrinsics
const Slice<const constant::Eval::Function> const_eval_functions;
/// The type constructor and convertor intrinsics
const Slice<const IntrinsicInfo> ctor_conv;
/// The builtin function intrinsic
const Slice<const IntrinsicInfo> builtins;
/// The IntrinsicInfo for the binary operator 'plus'
const IntrinsicInfo& binary_plus;
/// The IntrinsicInfo for the binary operator 'minus'
const IntrinsicInfo& binary_minus;
/// The IntrinsicInfo for the binary operator 'star'
const IntrinsicInfo& binary_star;
/// The IntrinsicInfo for the binary operator 'divide'
const IntrinsicInfo& binary_divide;
/// The IntrinsicInfo for the binary operator 'modulo'
const IntrinsicInfo& binary_modulo;
/// The IntrinsicInfo for the binary operator 'xor'
const IntrinsicInfo& binary_xor;
/// The IntrinsicInfo for the binary operator 'and'
const IntrinsicInfo& binary_and;
/// The IntrinsicInfo for the binary operator 'or'
const IntrinsicInfo& binary_or;
/// The IntrinsicInfo for the binary operator 'logical_and'
const IntrinsicInfo& binary_logical_and;
/// The IntrinsicInfo for the binary operator 'logical_or'
const IntrinsicInfo& binary_logical_or;
/// The IntrinsicInfo for the binary operator 'equal'
const IntrinsicInfo& binary_equal;
/// The IntrinsicInfo for the binary operator 'not_equal'
const IntrinsicInfo& binary_not_equal;
/// The IntrinsicInfo for the binary operator 'less_than'
const IntrinsicInfo& binary_less_than;
/// The IntrinsicInfo for the binary operator 'greater_than'
const IntrinsicInfo& binary_greater_than;
/// The IntrinsicInfo for the binary operator 'less_than_equal'
const IntrinsicInfo& binary_less_than_equal;
/// The IntrinsicInfo for the binary operator 'greater_than_equal'
const IntrinsicInfo& binary_greater_than_equal;
/// The IntrinsicInfo for the binary operator 'shift_left'
const IntrinsicInfo& binary_shift_left;
/// The IntrinsicInfo for the binary operator 'shift_right'
const IntrinsicInfo& binary_shift_right;
/// The IntrinsicInfo for the unary operator 'not'
const IntrinsicInfo& unary_not;
/// The IntrinsicInfo for the unary operator 'complement'
const IntrinsicInfo& unary_complement;
/// The IntrinsicInfo for the unary operator 'minus'
const IntrinsicInfo& unary_minus;
/// The IntrinsicInfo for the unary operator 'star'
const IntrinsicInfo& unary_star;
/// The IntrinsicInfo for the unary operator 'and'
const IntrinsicInfo& unary_and;
};
const core::type::Type* MatchState::Type(const core::type::Type* ty) {
TypeMatcherIndex matcher_index{(*matcher_indices_++).value};
auto& matcher = data[matcher_index];
return matcher.match(*this, ty);
}
Number MatchState::Num(Number number) {
NumberMatcherIndex matcher_index{(*matcher_indices_++).value};
auto& matcher = data[matcher_index];
return matcher.match(*this, number);
}
void MatchState::PrintType(StyledText& out) {
TypeMatcherIndex matcher_index{(*matcher_indices_++).value};
auto& matcher = data[matcher_index];
matcher.print(this, out);
}
void MatchState::PrintNum(StyledText& out) {
NumberMatcherIndex matcher_index{(*matcher_indices_++).value};
auto& matcher = data[matcher_index];
matcher.print(this, out);
}
/// TemplateTypeMatcher is a Matcher for a template type.
/// The TemplateTypeMatcher will initially match against any type, and then will only be further
/// constrained based on the conversion rules defined at
/// https://www.w3.org/TR/WGSL/#conversion-rank
template <size_t INDEX>
struct TemplateTypeMatcher {
/// The TypeMatcher for the template type with the index `INDEX`
static constexpr TypeMatcher matcher{
/* match */
[](MatchState& state, const core::type::Type* type) -> const core::type::Type* {
if (type->Is<Any>()) {
return state.templates.Type(INDEX);
}
if (auto* templates = state.templates.Type(INDEX, type)) {
return templates;
}
return nullptr;
},
/* print */
[](MatchState* state, StyledText& out) {
out << style::Type(state->data[state->overload.templates + INDEX].name);
},
};
};
/// TemplateNumberMatcher is a Matcher for a template number.
/// The TemplateNumberMatcher will match against any number (so long as it is
/// consistent for all uses in the overload)
template <size_t INDEX>
struct TemplateNumberMatcher {
/// The NumberMatcher for the template number with the index `INDEX`
static constexpr NumberMatcher matcher{
/* match */
[](MatchState& state, Number number) -> Number {
if (number.IsAny()) {
return state.templates.Num(INDEX);
}
return state.templates.Num(INDEX, number) ? number : Number::invalid;
},
/* print */
[](MatchState* state, StyledText& out) {
out << style::Variable(state->data[state->overload.templates + INDEX].name);
},
};
};
} // namespace tint::core::intrinsic
#endif // SRC_TINT_LANG_CORE_INTRINSIC_TABLE_DATA_H_