Add IntrinsicTable
Provides a centeralized table for all intrinsic overloads. IntrinsicTable::Lookup() takes the intrinsic type and list of arguments, returning either the matched overload, or a sensible error message. The validator has expectations that the TypeDeterminer resolves the return type of an intrinsic call, even when the signature doesn't match. To handle this, create semantic::Intrinsic nodes even when the overload fails to match. A significant portion of the Validator's logic for handling intrinsics can be removed (future change). There are a number of benefits to migrating the TypeDeterminer and Validator over to the IntrinsicTable: * There's far less intrininsic-bespoke code to maintain (no more duplicate `kIntrinsicData` tables in TypeDeterminer and Validator). * Adding or adjusting an intrinsic overload involves adding or adjusting a single Register() line. * Error messages give helpful suggestions for related overloads when given incorrect arguments. * Error messages are consistent for all intrinsics. * Error messages are far more understandable than those produced by the TypeDeterminer. * Further improvements on the error messages produced by the IntrinsicTable will benefit _all_ the intrinsics and their overloads. * The IntrinsicTable generates correct parameter information, including whether parameters are pointers or not. * The IntrinsicTable will help with implementing autocomplete for a language server Change-Id: I4bfa88533396b0b372aef41a62fe47b738531aed Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/40504 Commit-Queue: Ben Clayton <bclayton@google.com> Reviewed-by: dan sinclair <dsinclair@chromium.org>
This commit is contained in:
parent
316f9f6b6d
commit
59d24735d0
6
BUILD.gn
6
BUILD.gn
|
@ -355,6 +355,8 @@ source_set("libtint_core_src") {
|
|||
"src/clone_context.h",
|
||||
"src/demangler.cc",
|
||||
"src/demangler.h",
|
||||
"src/intrinsic_table.cc",
|
||||
"src/intrinsic_table.h",
|
||||
"src/diagnostic/diagnostic.cc",
|
||||
"src/diagnostic/diagnostic.h",
|
||||
"src/diagnostic/formatter.cc",
|
||||
|
@ -456,8 +458,6 @@ source_set("libtint_core_src") {
|
|||
"src/validator/validator.h",
|
||||
"src/validator/validator_impl.cc",
|
||||
"src/validator/validator_impl.h",
|
||||
"src/validator/validator_test_helper.cc",
|
||||
"src/validator/validator_test_helper.h",
|
||||
"src/writer/append_vector.cc",
|
||||
"src/writer/append_vector.h",
|
||||
"src/writer/float_to_string.cc",
|
||||
|
@ -864,6 +864,8 @@ source_set("tint_unittests_core_src") {
|
|||
"src/validator/validator_control_block_test.cc",
|
||||
"src/validator/validator_function_test.cc",
|
||||
"src/validator/validator_test.cc",
|
||||
"src/validator/validator_test_helper.cc",
|
||||
"src/validator/validator_test_helper.h",
|
||||
"src/writer/float_to_string_test.cc",
|
||||
]
|
||||
|
||||
|
|
|
@ -169,6 +169,8 @@ set(TINT_LIB_SRCS
|
|||
clone_context.h
|
||||
demangler.cc
|
||||
demangler.h;
|
||||
intrinsic_table.cc
|
||||
intrinsic_table.h
|
||||
diagnostic/diagnostic.cc
|
||||
diagnostic/diagnostic.h
|
||||
diagnostic/formatter.cc
|
||||
|
@ -270,8 +272,6 @@ set(TINT_LIB_SRCS
|
|||
validator/validator.h
|
||||
validator/validator_impl.cc
|
||||
validator/validator_impl.h
|
||||
validator/validator_test_helper.cc
|
||||
validator/validator_test_helper.h
|
||||
writer/append_vector.cc
|
||||
writer/append_vector.h
|
||||
writer/float_to_string.cc
|
||||
|
@ -489,6 +489,8 @@ if(${TINT_BUILD_TESTS})
|
|||
validator/validator_function_test.cc
|
||||
validator/validator_test.cc
|
||||
validator/validator_type_test.cc
|
||||
validator/validator_test_helper.cc
|
||||
validator/validator_test_helper.h
|
||||
writer/float_to_string_test.cc
|
||||
)
|
||||
|
||||
|
|
|
@ -0,0 +1,994 @@
|
|||
// Copyright 2021 The Tint Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "src/intrinsic_table.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
|
||||
#include "src/block_allocator.h"
|
||||
#include "src/program_builder.h"
|
||||
#include "src/semantic/intrinsic.h"
|
||||
#include "src/type/f32_type.h"
|
||||
|
||||
namespace tint {
|
||||
namespace {
|
||||
|
||||
/// OpenTypes are the symbols used for templated types in overload signatures
|
||||
enum class OpenType {
|
||||
T,
|
||||
Count, // Number of entries in the enum. Not a usable symbol.
|
||||
};
|
||||
|
||||
/// OpenNumber are the symbols used for templated integers in overload
|
||||
/// signatures
|
||||
enum class OpenNumber {
|
||||
N, // Typically used for vecN
|
||||
M, // Typically used for matNxM
|
||||
F, // Typically used for texture_storage_2d<F>
|
||||
};
|
||||
|
||||
/// @return a string of the OpenType symbol `ty`
|
||||
const char* str(OpenType ty) {
|
||||
switch (ty) {
|
||||
case OpenType::T:
|
||||
return "T";
|
||||
|
||||
case OpenType::Count:
|
||||
break;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
/// @return a string of the OpenNumber symbol `num`
|
||||
const char* str(OpenNumber num) {
|
||||
switch (num) {
|
||||
case OpenNumber::N:
|
||||
return "N";
|
||||
case OpenNumber::M:
|
||||
return "M";
|
||||
case OpenNumber::F:
|
||||
return "F";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
/// A Matcher is an interface of a class used to match an overload parameter,
|
||||
/// return type, or open type.
|
||||
class Matcher {
|
||||
public:
|
||||
/// Current state passed to Match()
|
||||
struct MatchState {
|
||||
/// The map of open types. A new entry is assigned the first time an
|
||||
/// OpenType is encountered. If the OpenType is encountered again, a
|
||||
/// comparison is made to see if the type is consistent.
|
||||
std::unordered_map<OpenType, type::Type*> open_types;
|
||||
/// The map of open numbers. A new entry is assigned the first time an
|
||||
/// OpenNumber is encountered. If the OpenNumber is encountered again, a
|
||||
/// comparison is made to see if the number is consistent.
|
||||
std::unordered_map<OpenNumber, uint32_t> open_numbers;
|
||||
};
|
||||
|
||||
/// Destructor
|
||||
virtual ~Matcher() = default;
|
||||
|
||||
/// Checks whether the given argument type matches.
|
||||
/// Match may add to, or compare against the open types and numbers in state.
|
||||
/// @returns true if the argument type is as expected.
|
||||
virtual bool Match(MatchState& state, type::Type* argument_type) const = 0;
|
||||
|
||||
/// @return a string representation of the matcher. Used for printing error
|
||||
/// messages when no overload is found.
|
||||
virtual std::string str() const = 0;
|
||||
|
||||
protected:
|
||||
/// Checks `state.open_type` to see if the OpenType `t` is equal to the type
|
||||
/// `ty`. If `state.open_type` does not contain an entry for `t`, then `ty`
|
||||
/// is added and returns true.
|
||||
bool MatchOpenType(MatchState& state, OpenType t, type::Type* ty) const {
|
||||
auto it = state.open_types.find(t);
|
||||
if (it != state.open_types.end()) {
|
||||
return it->second == ty;
|
||||
}
|
||||
state.open_types[t] = ty;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Checks `state.open_numbers` to see if the OpenNumber `n` is equal to
|
||||
/// `val`. If `state.open_numbers` does not contain an entry for `n`, then
|
||||
/// `val` is added and returns true.
|
||||
bool MatchOpenNumber(MatchState& state, OpenNumber n, uint32_t val) const {
|
||||
auto it = state.open_numbers.find(n);
|
||||
if (it != state.open_numbers.end()) {
|
||||
return it->second == val;
|
||||
}
|
||||
state.open_numbers[n] = val;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
/// Builder is an extension of the Matcher interface that can also build the
|
||||
/// expected type. Builders are used to generate the parameter and return types
|
||||
/// on successful overload match.
|
||||
class Builder : public Matcher {
|
||||
public:
|
||||
/// Final matched state passed to Build()
|
||||
struct BuildState {
|
||||
/// The type manager used to construct new types
|
||||
type::Manager& ty_mgr;
|
||||
/// The final resolved list of open types
|
||||
std::unordered_map<OpenType, type::Type*> const open_types;
|
||||
/// The final resolved list of open numbers
|
||||
std::unordered_map<OpenNumber, uint32_t> const open_numbers;
|
||||
};
|
||||
|
||||
/// Destructor
|
||||
~Builder() override = default;
|
||||
|
||||
/// Constructs and returns the expected type
|
||||
virtual type::Type* Build(BuildState& state) const = 0;
|
||||
};
|
||||
|
||||
/// OpenTypeBuilder is a Matcher / Builder for an open type (T etc).
|
||||
/// The OpenTypeBuilder will match against any type (so long as it is consistent
|
||||
/// for the overload), and Build() will build the type it matched against.
|
||||
class OpenTypeBuilder : public Builder {
|
||||
public:
|
||||
explicit OpenTypeBuilder(OpenType open_type) : open_type_(open_type) {}
|
||||
|
||||
bool Match(MatchState& state, type::Type* ty) const override {
|
||||
return MatchOpenType(state, open_type_, ty);
|
||||
}
|
||||
|
||||
type::Type* Build(BuildState& state) const override {
|
||||
return state.open_types.at(open_type_);
|
||||
}
|
||||
|
||||
std::string str() const override { return tint::str(open_type_); }
|
||||
|
||||
private:
|
||||
OpenType open_type_;
|
||||
};
|
||||
|
||||
/// BoolBuilder is a Matcher / Builder for boolean types.
|
||||
class BoolBuilder : public Builder {
|
||||
public:
|
||||
bool Match(MatchState&, type::Type* ty) const override {
|
||||
return ty->Is<type::Bool>();
|
||||
}
|
||||
type::Type* Build(BuildState& state) const override {
|
||||
return state.ty_mgr.Get<type::Bool>();
|
||||
}
|
||||
std::string str() const override { return "bool"; }
|
||||
};
|
||||
|
||||
/// F32Builder is a Matcher / Builder for f32 types.
|
||||
class F32Builder : public Builder {
|
||||
public:
|
||||
bool Match(MatchState&, type::Type* ty) const override {
|
||||
return ty->Is<type::F32>();
|
||||
}
|
||||
type::Type* Build(BuildState& state) const override {
|
||||
return state.ty_mgr.Get<type::F32>();
|
||||
}
|
||||
std::string str() const override { return "f32"; }
|
||||
};
|
||||
|
||||
/// U32Builder is a Matcher / Builder for u32 types.
|
||||
class U32Builder : public Builder {
|
||||
public:
|
||||
bool Match(MatchState&, type::Type* ty) const override {
|
||||
return ty->Is<type::U32>();
|
||||
}
|
||||
type::Type* Build(BuildState& state) const override {
|
||||
return state.ty_mgr.Get<type::U32>();
|
||||
}
|
||||
std::string str() const override { return "u32"; }
|
||||
};
|
||||
|
||||
/// I32Builder is a Matcher / Builder for i32 types.
|
||||
class I32Builder : public Builder {
|
||||
public:
|
||||
bool Match(MatchState&, type::Type* ty) const override {
|
||||
return ty->Is<type::I32>();
|
||||
}
|
||||
type::Type* Build(BuildState& state) const override {
|
||||
return state.ty_mgr.Get<type::I32>();
|
||||
}
|
||||
std::string str() const override { return "i32"; }
|
||||
};
|
||||
|
||||
/// IU32Matcher is a Matcher for i32 or u32 types.
|
||||
class IU32Matcher : public Matcher {
|
||||
public:
|
||||
bool Match(MatchState&, type::Type* ty) const override {
|
||||
return ty->Is<type::I32>() || ty->Is<type::U32>();
|
||||
}
|
||||
std::string str() const override { return "i32 or u32"; }
|
||||
};
|
||||
|
||||
/// FIU32Matcher is a Matcher for f32, i32 or u32 types.
|
||||
class FIU32Matcher : public Matcher {
|
||||
public:
|
||||
bool Match(MatchState&, type::Type* ty) const override {
|
||||
return ty->Is<type::F32>() || ty->Is<type::I32>() || ty->Is<type::U32>();
|
||||
}
|
||||
std::string str() const override { return "f32, i32 or u32"; }
|
||||
};
|
||||
|
||||
/// ScalarMatcher is a Matcher for f32, i32, u32 or boolean types.
|
||||
class ScalarMatcher : public Matcher {
|
||||
public:
|
||||
bool Match(MatchState&, type::Type* ty) const override {
|
||||
return ty->is_scalar();
|
||||
}
|
||||
std::string str() const override { return "scalar"; }
|
||||
};
|
||||
|
||||
/// OpenSizeVecBuilder is a Matcher / Builder for vector types of an open number
|
||||
/// size.
|
||||
class OpenSizeVecBuilder : public Builder {
|
||||
public:
|
||||
OpenSizeVecBuilder(OpenNumber size, Builder* element_builder)
|
||||
: size_(size), element_builder_(element_builder) {}
|
||||
|
||||
bool Match(MatchState& state, type::Type* ty) const override {
|
||||
if (auto* vec = ty->UnwrapAll()->As<type::Vector>()) {
|
||||
if (!MatchOpenNumber(state, size_, vec->size())) {
|
||||
return false;
|
||||
}
|
||||
return element_builder_->Match(state, vec->type());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
type::Type* Build(BuildState& state) const override {
|
||||
auto* el = element_builder_->Build(state);
|
||||
auto n = state.open_numbers.at(size_);
|
||||
return state.ty_mgr.Get<type::Vector>(el, n);
|
||||
}
|
||||
|
||||
std::string str() const override {
|
||||
return "vec" + std::string(tint::str(size_)) + "<" +
|
||||
element_builder_->str() + ">";
|
||||
}
|
||||
|
||||
protected:
|
||||
OpenNumber const size_;
|
||||
Builder* const element_builder_;
|
||||
};
|
||||
|
||||
/// VecBuilder is a Matcher / Builder for vector types of a fixed size.
|
||||
class VecBuilder : public Builder {
|
||||
public:
|
||||
VecBuilder(uint32_t size, Builder* element_builder)
|
||||
: size_(size), element_builder_(element_builder) {}
|
||||
|
||||
bool Match(MatchState& state, type::Type* ty) const override {
|
||||
if (auto* vec = ty->UnwrapAll()->As<type::Vector>()) {
|
||||
if (vec->size() == size_) {
|
||||
return element_builder_->Match(state, vec->type());
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
type::Type* Build(BuildState& state) const override {
|
||||
auto* el = element_builder_->Build(state);
|
||||
return state.ty_mgr.Get<type::Vector>(el, size_);
|
||||
}
|
||||
|
||||
std::string str() const override {
|
||||
return "vec" + std::to_string(size_) + "<" + element_builder_->str() + ">";
|
||||
}
|
||||
|
||||
protected:
|
||||
const uint32_t size_;
|
||||
Builder* element_builder_;
|
||||
};
|
||||
|
||||
/// OpenSizeVecBuilder is a Matcher / Builder for matrix types of an open number
|
||||
/// column and row size.
|
||||
class OpenSizeMatBuilder : public Builder {
|
||||
public:
|
||||
OpenSizeMatBuilder(OpenNumber columns,
|
||||
OpenNumber rows,
|
||||
Builder* element_builder)
|
||||
: columns_(columns), rows_(rows), element_builder_(element_builder) {}
|
||||
|
||||
bool Match(MatchState& state, type::Type* ty) const override {
|
||||
if (auto* mat = ty->UnwrapAll()->As<type::Matrix>()) {
|
||||
if (!MatchOpenNumber(state, columns_, mat->columns())) {
|
||||
return false;
|
||||
}
|
||||
if (!MatchOpenNumber(state, rows_, mat->rows())) {
|
||||
return false;
|
||||
}
|
||||
return element_builder_->Match(state, mat->type());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
type::Type* Build(BuildState& state) const override {
|
||||
auto* el = element_builder_->Build(state);
|
||||
auto columns = state.open_numbers.at(columns_);
|
||||
auto rows = state.open_numbers.at(rows_);
|
||||
return state.ty_mgr.Get<type::Matrix>(el, rows, columns);
|
||||
}
|
||||
|
||||
std::string str() const override {
|
||||
return "max" + std::string(tint::str(columns_)) + "x" +
|
||||
std::string(tint::str(rows_)) + "<" + element_builder_->str() + ">";
|
||||
}
|
||||
|
||||
protected:
|
||||
OpenNumber const columns_;
|
||||
OpenNumber const rows_;
|
||||
Builder* const element_builder_;
|
||||
};
|
||||
|
||||
/// PtrBuilder is a Matcher / Builder for pointer types.
|
||||
class PtrBuilder : public Builder {
|
||||
public:
|
||||
explicit PtrBuilder(Builder* element_builder)
|
||||
: element_builder_(element_builder) {}
|
||||
|
||||
bool Match(MatchState& state, type::Type* ty) const override {
|
||||
if (auto* ptr = ty->As<type::Pointer>()) {
|
||||
return element_builder_->Match(state, ptr->type());
|
||||
}
|
||||
// TODO(bclayton): https://crbug.com/tint/486
|
||||
// TypeDeterminer currently folds away the pointers on expressions.
|
||||
// We'll need to fix this to ensure that pointer parameters are not fed
|
||||
// non-pointer arguments, but for now just accept them.
|
||||
return element_builder_->Match(state, ty);
|
||||
}
|
||||
|
||||
type::Type* Build(BuildState& state) const override {
|
||||
auto* el = element_builder_->Build(state);
|
||||
return state.ty_mgr.Get<type::Pointer>(el, ast::StorageClass::kNone);
|
||||
}
|
||||
|
||||
std::string str() const override {
|
||||
return "ptr<" + element_builder_->str() + ">";
|
||||
}
|
||||
|
||||
private:
|
||||
Builder* const element_builder_;
|
||||
};
|
||||
|
||||
/// ArrayBuilder is a Matcher / Builder for runtime sized array types.
|
||||
class ArrayBuilder : public Builder {
|
||||
public:
|
||||
explicit ArrayBuilder(Builder* element_builder)
|
||||
: element_builder_(element_builder) {}
|
||||
|
||||
bool Match(MatchState& state, type::Type* ty) const override {
|
||||
if (auto* arr = ty->As<type::Array>()) {
|
||||
if (arr->size() == 0) {
|
||||
return element_builder_->Match(state, arr->type());
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
type::Type* Build(BuildState& state) const override {
|
||||
auto* el = element_builder_->Build(state);
|
||||
return state.ty_mgr.Get<type::Array>(el, 0, ast::ArrayDecorationList{});
|
||||
}
|
||||
|
||||
std::string str() const override {
|
||||
return "array<" + element_builder_->str() + ">";
|
||||
}
|
||||
|
||||
private:
|
||||
Builder* const element_builder_;
|
||||
};
|
||||
|
||||
/// Impl is the private implementation of the IntrinsicTable interface.
|
||||
class Impl : public IntrinsicTable {
|
||||
public:
|
||||
Impl();
|
||||
|
||||
IntrinsicTable::Result Lookup(
|
||||
ProgramBuilder& builder,
|
||||
semantic::IntrinsicType type,
|
||||
const std::vector<type::Type*>& args) const override;
|
||||
|
||||
/// A single overload definition.
|
||||
struct Overload {
|
||||
/// @returns a human readable string representation of the overload
|
||||
std::string str() const;
|
||||
|
||||
/// Attempts to match this overload given the IntrinsicType and argument
|
||||
/// types. If a match is made, the build intrinsic is returned, otherwise
|
||||
/// `match_score` is assigned a score of how closely the overload matched
|
||||
/// (positive representing a greater match), and nullptr is returned.
|
||||
semantic::Intrinsic* Match(ProgramBuilder& builder,
|
||||
semantic::IntrinsicType type,
|
||||
const std::vector<type::Type*>& arg_types,
|
||||
int& match_score) const;
|
||||
|
||||
semantic::IntrinsicType type;
|
||||
Builder* return_type;
|
||||
std::vector<Builder*> parameters;
|
||||
std::unordered_map<OpenType, Matcher*> open_type_matchers;
|
||||
};
|
||||
|
||||
private:
|
||||
/// Allocator for the built Matcher / Builders
|
||||
BlockAllocator<Matcher> matcher_allocator_;
|
||||
|
||||
/// Commonly used Matcher / Builders
|
||||
struct {
|
||||
BoolBuilder bool_;
|
||||
F32Builder f32;
|
||||
I32Builder i32;
|
||||
IU32Matcher iu32;
|
||||
FIU32Matcher fiu32;
|
||||
ScalarMatcher scalar;
|
||||
U32Builder u32;
|
||||
OpenTypeBuilder T{OpenType::T};
|
||||
} matchers_;
|
||||
|
||||
// TODO(bclayton): Sort by type, or array these by IntrinsicType
|
||||
std::vector<Overload> overloads_;
|
||||
|
||||
/// @returns a Matcher / Builder that matches a pointer with the given element
|
||||
/// type
|
||||
PtrBuilder* ptr(Builder* element_builder) {
|
||||
return matcher_allocator_.Create<PtrBuilder>(element_builder);
|
||||
}
|
||||
|
||||
/// @returns a Matcher / Builder that matches a vector of size OpenNumber::N
|
||||
/// with the given element type
|
||||
OpenSizeVecBuilder* vecN(Builder* element_builder) {
|
||||
return matcher_allocator_.Create<OpenSizeVecBuilder>(OpenNumber::N,
|
||||
element_builder);
|
||||
}
|
||||
|
||||
/// @returns a Matcher / Builder that matches a vector of the given size and
|
||||
/// element type
|
||||
VecBuilder* vec(uint32_t size, Builder* element_builder) {
|
||||
return matcher_allocator_.Create<VecBuilder>(size, element_builder);
|
||||
}
|
||||
|
||||
/// @returns a Matcher / Builder that matches a runtime sized array with the
|
||||
/// given element type
|
||||
ArrayBuilder* array(Builder* element_builder) {
|
||||
return matcher_allocator_.Create<ArrayBuilder>(element_builder);
|
||||
}
|
||||
|
||||
/// @returns a Matcher / Builder that matches a matrix with the given size and
|
||||
/// element type
|
||||
OpenSizeMatBuilder* mat(OpenNumber columns,
|
||||
OpenNumber rows,
|
||||
Builder* element_builder) {
|
||||
return matcher_allocator_.Create<OpenSizeMatBuilder>(columns, rows,
|
||||
element_builder);
|
||||
}
|
||||
|
||||
/// @returns a Matcher / Builder that matches a square matrix with the column
|
||||
/// / row count of OpenNumber::N
|
||||
template <typename T>
|
||||
auto matNxN(T&& in) {
|
||||
return mat(OpenNumber::N, OpenNumber::N, std::forward<T>(in));
|
||||
}
|
||||
|
||||
/// Registers an overload with the given intrinsic type, return type Matcher /
|
||||
/// Builder, and parameter Matcher / Builders.
|
||||
/// This overload of Register does not constrain any OpenTypes.
|
||||
void Register(semantic::IntrinsicType type,
|
||||
Builder* return_type,
|
||||
std::vector<Builder*> parameters) {
|
||||
Overload overload{type, return_type, std::move(parameters), {}};
|
||||
overloads_.emplace_back(overload);
|
||||
}
|
||||
|
||||
/// Registers an overload with the given intrinsic type, return type Matcher /
|
||||
/// Builder, and parameter Matcher / Builders.
|
||||
/// A single OpenType is contained with the given Matcher in
|
||||
/// open_type_matcher.
|
||||
void Register(semantic::IntrinsicType type,
|
||||
Builder* return_type,
|
||||
std::vector<Builder*> parameters,
|
||||
std::pair<OpenType, Matcher*> open_type_matcher) {
|
||||
Overload overload{
|
||||
type, return_type, std::move(parameters), {open_type_matcher}};
|
||||
overloads_.emplace_back(overload);
|
||||
}
|
||||
};
|
||||
|
||||
Impl::Impl() {
|
||||
using I = semantic::IntrinsicType;
|
||||
|
||||
auto* bool_ = &matchers_.bool_; // bool
|
||||
auto* f32 = &matchers_.f32; // f32
|
||||
auto* u32 = &matchers_.u32; // u32
|
||||
auto* iu32 = &matchers_.iu32; // i32 or u32
|
||||
auto* fiu32 = &matchers_.fiu32; // f32, i32 or u32
|
||||
auto* scalar = &matchers_.scalar; // f32, i32, u32 or bool
|
||||
auto* T = &matchers_.T; // Any T type
|
||||
auto* array_T = array(T); // array<T>
|
||||
auto* vec2_f32 = vec(2, f32); // vec2<f32>
|
||||
auto* vec3_f32 = vec(3, f32); // vec3<f32>
|
||||
auto* vec4_f32 = vec(4, f32); // vec4<f32>
|
||||
auto* vecN_f32 = vecN(f32); // vecN<f32>
|
||||
auto* vecN_T = vecN(T); // vecN<T>
|
||||
auto* vecN_bool = vecN(bool_); // vecN<bool>
|
||||
auto* matNxN_f32 = matNxN(f32); // matNxN<f32>
|
||||
auto* ptr_T = ptr(T); // ptr<T>
|
||||
auto* ptr_f32 = ptr(f32); // ptr<f32>
|
||||
auto* ptr_vecN_T = ptr(vecN_T); // ptr<vecN<T>>
|
||||
auto* ptr_vecN_f32 = ptr(vecN_f32); // ptr<vecN<f32>>
|
||||
|
||||
// Intrinsic overloads are registered with a call to the Register().
|
||||
//
|
||||
// The best way to explain Register() and the lookup process is by example.
|
||||
//
|
||||
// Let's begin with a simple overload declaration:
|
||||
//
|
||||
// Register(I::kIsInf, bool_, {f32});
|
||||
//
|
||||
// I - is an alias to semantic::IntrinsicType.
|
||||
// I::kIsInf is shorthand for semantic::IntrinsicType::kIsInf.
|
||||
// bool_ - is a pointer to a pre-constructed BoolBuilder which matches and
|
||||
// builds type::Bool types.
|
||||
// {f32} - is the list of parameter Builders for the overload.
|
||||
// Builders are a type of Matcher that can also build the the type.
|
||||
// All Builders are Matchers, not all Matchers are Builders.
|
||||
// f32 is a pointer to a pre-constructed F32Builder which matches and
|
||||
// builds type::F32 types.
|
||||
//
|
||||
// This call registers the overload for the `isInf(f32) -> bool` intrinsic.
|
||||
//
|
||||
// Let's now see the process of Overload::Match() when passed a single f32
|
||||
// argument:
|
||||
//
|
||||
// (1) Overload::Match() begins by attempting to match the argument types
|
||||
// from left to right.
|
||||
// F32Builder::Match() is called with the type::F32 argument type.
|
||||
// F32Builder (only) matches the type::F32 type, so F32Builder::Match()
|
||||
// returns true.
|
||||
// (2) All the parameters have had their Matcher::Match() methods return
|
||||
// true, there are no open-types (more about these later), so the
|
||||
// overload has matched.
|
||||
// (3) The semantic::Intrinsic now needs to be built, so we begin by
|
||||
// building the overload's parameter types (these may not exactly match
|
||||
// the argument types). Build() is called for each parameter Builder,
|
||||
// returning the parameter type.
|
||||
// (4) Finally, Builder::Build() is called for the return_type, and the
|
||||
// semantic::Intrinsic is constructed and returned.
|
||||
// Job done.
|
||||
//
|
||||
// Overload resolution also supports basic pattern matching through the use of
|
||||
// open-types and open-numbers.
|
||||
//
|
||||
// OpenTypeBuilder is a Matcher that matches a single open-type.
|
||||
//
|
||||
// An 'open-type' can be thought as a template type that is determined by the
|
||||
// arguments to the intrinsic.
|
||||
//
|
||||
// At the beginning of Overload::Match(), all open-types are undefined.
|
||||
// Open-types are closed (pinned to a fixed type) on the first attempt to
|
||||
// match against that open-type (e.g. via OpenTypeBuilder::Match()).
|
||||
// Once open-types are closed, they remain that type, and
|
||||
// OpenTypeBuilder::Match() will only ever return true if the queried type
|
||||
// matches the closed type.
|
||||
//
|
||||
// To better understand, let's consider the following hypothetical overload
|
||||
// declaration:
|
||||
//
|
||||
// Register(I::kFoo, T, {T, T}, {OpenType::T, scalar});
|
||||
//
|
||||
// T - is the matcher for the open-type OpenType::T.
|
||||
// scalar - is a pointer to a pre-constructed ScalarMatcher
|
||||
// which matches scalar types (f32, i32, u32, bool).
|
||||
// {OpenType::T, scalar} - is a constraint on the open-type OpenType::T that
|
||||
// it needs to resolve to a scalar.
|
||||
//
|
||||
// This call to Register() declares the foo intrinsic which accepts the
|
||||
// identical scalar type for both arguments, and returns that scalar type.
|
||||
//
|
||||
// The process for resolving this overload is as follows:
|
||||
//
|
||||
// (1) Overload::Match() begins by attempting to match the argument types
|
||||
// from left to right.
|
||||
// OpenTypeBuilder::Match() is called for the first parameter, being
|
||||
// passed the type of the first argument.
|
||||
// The OpenType::T has not been closed yet, so the OpenType::T is closed
|
||||
// as the type of the first argument.
|
||||
// There's no verification that the T type is a scalar at this stage.
|
||||
// (2) OpenTypeBuilder::Match() is called again for the second parameter
|
||||
// with the type of the second argument.
|
||||
// As the OpenType::T is now closed, the argument type is compared
|
||||
// against the value of the closed-type of OpenType::T.
|
||||
// OpenTypeBuilder::Match() returns true if these type match, otherwise
|
||||
// false and the overload match fails.
|
||||
// (3) If all the parameters have had their Matcher::Match() methods return
|
||||
// true, then the open-type constraints need to be checked next.
|
||||
// The Matcher::Match() is called for each closed type. If any return
|
||||
// false then the overload match fails.
|
||||
// (4) Overload::Match() now needs to build and return the output
|
||||
// semantic::Intrinsic holding the matched overload signature.
|
||||
// (5) The parameter types are built by calling OpenTypeBuilder::Build().
|
||||
// This simply returns the closed type.
|
||||
// (6) OpenTypeBuilder::Build() is called again for the return_type, and the
|
||||
// semantic::Intrinsic is constructed and returned.
|
||||
// Job done.
|
||||
//
|
||||
// Open-numbers are very similar to open-types, except they match against
|
||||
// integers instead of types. The rules for open-numbers are almost identical
|
||||
// to open-types, except open-numbers do not support constraints.
|
||||
//
|
||||
// vecN(f32) is an example of a Matcher that uses open-numbers.
|
||||
// vecN() constructs a OpenSizeVecBuilder that will match a vector of size
|
||||
// OpenNumber::N and of element type f32. As vecN() always uses the
|
||||
// OpenNumber::N, using vecN() multiple times in the same overload signature
|
||||
// will ensure that the vector size is identical for all vector types.
|
||||
//
|
||||
// Some Matcher implementations accept other Matchers for matching sub-types.
|
||||
// Consider:
|
||||
//
|
||||
// Register(I::kClamp, vecN(T), {vecN(T), vecN(T), vecN(T)},
|
||||
// {OpenType::T, fiu32});
|
||||
//
|
||||
// vecN(T) is a OpenSizeVecBuilder that matches a vector of size OpenNumber::N
|
||||
// and of element type OpenType::T, where T must be either a f32, i32, or u32.
|
||||
|
||||
// clang-format off
|
||||
|
||||
// name return type parameter types open type constraints // NOLINT
|
||||
Register(I::kAbs, T, {T}, {OpenType::T, fiu32} ); // NOLINT
|
||||
Register(I::kAbs, vecN_T, {vecN_T}, {OpenType::T, fiu32} ); // NOLINT
|
||||
Register(I::kAcos, f32, {f32} ); // NOLINT
|
||||
Register(I::kAcos, vecN_f32, {vecN_f32} ); // NOLINT
|
||||
Register(I::kAll, bool_, {vecN_bool} ); // NOLINT
|
||||
Register(I::kAny, bool_, {vecN_bool} ); // NOLINT
|
||||
Register(I::kArrayLength, u32, {array_T} ); // NOLINT
|
||||
Register(I::kAsin, f32, {f32} ); // NOLINT
|
||||
Register(I::kAsin, vecN_f32, {vecN_f32} ); // NOLINT
|
||||
Register(I::kAtan, f32, {f32} ); // NOLINT
|
||||
Register(I::kAtan, vecN_f32, {vecN_f32} ); // NOLINT
|
||||
Register(I::kAtan2, f32, {f32, f32} ); // NOLINT
|
||||
Register(I::kAtan2, vecN_f32, {vecN_f32, vecN_f32} ); // NOLINT
|
||||
Register(I::kCeil, f32, {f32} ); // NOLINT
|
||||
Register(I::kCeil, vecN_f32, {vecN_f32} ); // NOLINT
|
||||
Register(I::kClamp, T, {T, T, T}, {OpenType::T, fiu32} ); // NOLINT
|
||||
Register(I::kClamp, vecN_T, {vecN_T, vecN_T, vecN_T}, {OpenType::T, fiu32} ); // NOLINT
|
||||
Register(I::kCos, f32, {f32} ); // NOLINT
|
||||
Register(I::kCos, vecN_f32, {vecN_f32} ); // NOLINT
|
||||
Register(I::kCosh, f32, {f32} ); // NOLINT
|
||||
Register(I::kCosh, vecN_f32, {vecN_f32} ); // NOLINT
|
||||
Register(I::kCountOneBits, T, {T}, {OpenType::T, iu32} ); // NOLINT
|
||||
Register(I::kCountOneBits, vecN_T, {vecN_T}, {OpenType::T, iu32} ); // NOLINT
|
||||
Register(I::kCross, vec3_f32, {vec3_f32, vec3_f32} ); // NOLINT
|
||||
Register(I::kDeterminant, f32, {matNxN_f32} ); // NOLINT
|
||||
Register(I::kDistance, f32, {f32, f32} ); // NOLINT
|
||||
Register(I::kDistance, f32, {vecN_f32, vecN_f32} ); // NOLINT
|
||||
Register(I::kDot, f32, {vecN_f32, vecN_f32} ); // NOLINT
|
||||
Register(I::kDpdx, f32, {f32} ); // NOLINT
|
||||
Register(I::kDpdx, vecN_f32, {vecN_f32} ); // NOLINT
|
||||
Register(I::kDpdxCoarse, f32, {f32} ); // NOLINT
|
||||
Register(I::kDpdxCoarse, vecN_f32, {vecN_f32} ); // NOLINT
|
||||
Register(I::kDpdxFine, f32, {f32} ); // NOLINT
|
||||
Register(I::kDpdxFine, vecN_f32, {vecN_f32} ); // NOLINT
|
||||
Register(I::kDpdy, f32, {f32} ); // NOLINT
|
||||
Register(I::kDpdy, vecN_f32, {vecN_f32} ); // NOLINT
|
||||
Register(I::kDpdyCoarse, f32, {f32} ); // NOLINT
|
||||
Register(I::kDpdyCoarse, vecN_f32, {vecN_f32} ); // NOLINT
|
||||
Register(I::kDpdyFine, f32, {f32} ); // NOLINT
|
||||
Register(I::kDpdyFine, vecN_f32, {vecN_f32} ); // NOLINT
|
||||
Register(I::kExp, f32, {f32} ); // NOLINT
|
||||
Register(I::kExp, vecN_f32, {vecN_f32} ); // NOLINT
|
||||
Register(I::kExp2, f32, {f32} ); // NOLINT
|
||||
Register(I::kExp2, vecN_f32, {vecN_f32} ); // NOLINT
|
||||
Register(I::kFaceForward, f32, {f32, f32, f32} ); // NOLINT
|
||||
Register(I::kFaceForward, vecN_f32, {vecN_f32, vecN_f32, vecN_f32} ); // NOLINT
|
||||
Register(I::kFloor, f32, {f32} ); // NOLINT
|
||||
Register(I::kFloor, vecN_f32, {vecN_f32} ); // NOLINT
|
||||
Register(I::kFma, f32, {f32, f32, f32} ); // NOLINT
|
||||
Register(I::kFma, vecN_f32, {vecN_f32, vecN_f32, vecN_f32} ); // NOLINT
|
||||
Register(I::kFract, f32, {f32} ); // NOLINT
|
||||
Register(I::kFract, vecN_f32, {vecN_f32} ); // NOLINT
|
||||
Register(I::kFrexp, f32, {f32, ptr_T}, {OpenType::T, iu32} ); // NOLINT
|
||||
Register(I::kFrexp, vecN_f32, {vecN_f32, ptr_vecN_T}, {OpenType::T, iu32} ); // NOLINT
|
||||
Register(I::kFwidth, f32, {f32} ); // NOLINT
|
||||
Register(I::kFwidth, vecN_f32, {vecN_f32} ); // NOLINT
|
||||
Register(I::kFwidthCoarse, f32, {f32} ); // NOLINT
|
||||
Register(I::kFwidthCoarse, vecN_f32, {vecN_f32} ); // NOLINT
|
||||
Register(I::kFwidthFine, f32, {f32} ); // NOLINT
|
||||
Register(I::kFwidthFine, vecN_f32, {vecN_f32} ); // NOLINT
|
||||
Register(I::kInverseSqrt, f32, {f32} ); // NOLINT
|
||||
Register(I::kInverseSqrt, vecN_f32, {vecN_f32} ); // NOLINT
|
||||
Register(I::kIsFinite, bool_, {f32} ); // NOLINT
|
||||
Register(I::kIsFinite, vecN_bool, {vecN_f32} ); // NOLINT
|
||||
Register(I::kIsInf, bool_, {f32} ); // NOLINT
|
||||
Register(I::kIsInf, vecN_bool, {vecN_f32} ); // NOLINT
|
||||
Register(I::kIsNan, bool_, {f32} ); // NOLINT
|
||||
Register(I::kIsNan, vecN_bool, {vecN_f32} ); // NOLINT
|
||||
Register(I::kIsNormal, bool_, {f32} ); // NOLINT
|
||||
Register(I::kIsNormal, vecN_bool, {vecN_f32} ); // NOLINT
|
||||
Register(I::kLdexp, f32, {f32, T}, {OpenType::T, iu32} ); // NOLINT
|
||||
Register(I::kLdexp, vecN_f32, {vecN_f32, vecN_T}, {OpenType::T, iu32} ); // NOLINT
|
||||
Register(I::kLength, f32, {f32} ); // NOLINT
|
||||
Register(I::kLength, f32, {vecN_f32} ); // NOLINT
|
||||
Register(I::kLog, f32, {f32} ); // NOLINT
|
||||
Register(I::kLog, vecN_f32, {vecN_f32} ); // NOLINT
|
||||
Register(I::kLog2, f32, {f32} ); // NOLINT
|
||||
Register(I::kLog2, vecN_f32, {vecN_f32} ); // NOLINT
|
||||
Register(I::kMax, T, {T, T}, {OpenType::T, fiu32} ); // NOLINT
|
||||
Register(I::kMax, vecN_T, {vecN_T, vecN_T}, {OpenType::T, fiu32} ); // NOLINT
|
||||
Register(I::kMin, T, {T, T}, {OpenType::T, fiu32} ); // NOLINT
|
||||
Register(I::kMin, vecN_T, {vecN_T, vecN_T}, {OpenType::T, fiu32} ); // NOLINT
|
||||
Register(I::kMix, f32, {f32, f32, f32} ); // NOLINT
|
||||
Register(I::kMix, vecN_f32, {vecN_f32, vecN_f32, vecN_f32} ); // NOLINT
|
||||
Register(I::kModf, f32, {f32, ptr_f32} ); // NOLINT
|
||||
Register(I::kModf, vecN_f32, {vecN_f32, ptr_vecN_f32} ); // NOLINT
|
||||
Register(I::kNormalize, vecN_f32, {vecN_f32} ); // NOLINT
|
||||
Register(I::kPack2x16Float, u32, {vec2_f32} ); // NOLINT
|
||||
Register(I::kPack2x16Snorm, u32, {vec2_f32} ); // NOLINT
|
||||
Register(I::kPack2x16Unorm, u32, {vec2_f32} ); // NOLINT
|
||||
Register(I::kPack4x8Snorm, u32, {vec4_f32} ); // NOLINT
|
||||
Register(I::kPack4x8Unorm, u32, {vec4_f32} ); // NOLINT
|
||||
Register(I::kPow, f32, {f32, f32} ); // NOLINT
|
||||
Register(I::kPow, vecN_f32, {vecN_f32, vecN_f32} ); // NOLINT
|
||||
Register(I::kReflect, f32, {f32, f32} ); // NOLINT
|
||||
Register(I::kReflect, vecN_f32, {vecN_f32, vecN_f32} ); // NOLINT
|
||||
Register(I::kReverseBits, T, {T}, {OpenType::T, iu32} ); // NOLINT
|
||||
Register(I::kReverseBits, vecN_T, {vecN_T}, {OpenType::T, iu32} ); // NOLINT
|
||||
Register(I::kRound, f32, {f32} ); // NOLINT
|
||||
Register(I::kRound, vecN_f32, {vecN_f32} ); // NOLINT
|
||||
Register(I::kSelect, T, {T, T, bool_}, {OpenType::T, scalar} ); // NOLINT
|
||||
Register(I::kSelect, vecN_T, {vecN_T, vecN_T, vecN_bool}, {OpenType::T, scalar} ); // NOLINT
|
||||
Register(I::kSign, f32, {f32} ); // NOLINT
|
||||
Register(I::kSign, vecN_f32, {vecN_f32} ); // NOLINT
|
||||
Register(I::kSin, f32, {f32} ); // NOLINT
|
||||
Register(I::kSin, vecN_f32, {vecN_f32} ); // NOLINT
|
||||
Register(I::kSinh, f32, {f32} ); // NOLINT
|
||||
Register(I::kSinh, vecN_f32, {vecN_f32} ); // NOLINT
|
||||
Register(I::kSmoothStep, f32, {f32, f32, f32} ); // NOLINT
|
||||
Register(I::kSmoothStep, vecN_f32, {vecN_f32, vecN_f32, vecN_f32} ); // NOLINT
|
||||
Register(I::kSqrt, f32, {f32} ); // NOLINT
|
||||
Register(I::kSqrt, vecN_f32, {vecN_f32} ); // NOLINT
|
||||
Register(I::kStep, f32, {f32, f32} ); // NOLINT
|
||||
Register(I::kStep, vecN_f32, {vecN_f32, vecN_f32} ); // NOLINT
|
||||
Register(I::kTan, f32, {f32} ); // NOLINT
|
||||
Register(I::kTan, vecN_f32, {vecN_f32} ); // NOLINT
|
||||
Register(I::kTanh, f32, {f32} ); // NOLINT
|
||||
Register(I::kTanh, vecN_f32, {vecN_f32} ); // NOLINT
|
||||
Register(I::kTrunc, f32, {f32} ); // NOLINT
|
||||
Register(I::kTrunc, vecN_f32, {vecN_f32} ); // NOLINT
|
||||
|
||||
// clang-format on
|
||||
}
|
||||
|
||||
std::string Impl::Overload::str() const {
|
||||
std::stringstream ss;
|
||||
ss << type << "(";
|
||||
{
|
||||
bool first = true;
|
||||
for (auto* param : parameters) {
|
||||
if (!first) {
|
||||
ss << ", ";
|
||||
}
|
||||
first = false;
|
||||
ss << param->str();
|
||||
}
|
||||
}
|
||||
ss << ") -> ";
|
||||
ss << return_type->str();
|
||||
|
||||
if (!open_type_matchers.empty()) {
|
||||
ss << " where: ";
|
||||
|
||||
for (uint32_t i = 0; i < static_cast<uint32_t>(OpenType::Count); i++) {
|
||||
auto open_type = static_cast<OpenType>(i);
|
||||
auto it = open_type_matchers.find(open_type);
|
||||
if (it != open_type_matchers.end()) {
|
||||
if (i > 0) {
|
||||
ss << ", ";
|
||||
}
|
||||
ss << tint::str(open_type) << " is " << it->second->str();
|
||||
}
|
||||
}
|
||||
}
|
||||
return ss.str();
|
||||
}
|
||||
/// TODO(bclayton): This really does not belong here. It would be nice if
|
||||
/// type::Type::type_name() returned these strings.
|
||||
/// @returns a human readable string for the type `ty`.
|
||||
std::string TypeName(type::Type* ty) {
|
||||
ty = ty->UnwrapAll();
|
||||
if (ty->Is<type::F32>()) {
|
||||
return "f32";
|
||||
}
|
||||
if (ty->Is<type::U32>()) {
|
||||
return "u32";
|
||||
}
|
||||
if (ty->Is<type::I32>()) {
|
||||
return "i32";
|
||||
}
|
||||
if (ty->Is<type::Bool>()) {
|
||||
return "bool";
|
||||
}
|
||||
if (ty->Is<type::Void>()) {
|
||||
return "void";
|
||||
}
|
||||
if (auto* ptr = ty->As<type::Pointer>()) {
|
||||
return "ptr<" + TypeName(ptr->type()) + ">";
|
||||
}
|
||||
if (auto* vec = ty->As<type::Vector>()) {
|
||||
return "vec" + std::to_string(vec->size()) + "<" + TypeName(vec->type()) +
|
||||
">";
|
||||
}
|
||||
if (auto* mat = ty->As<type::Matrix>()) {
|
||||
return "mat" + std::to_string(mat->columns()) + "x" +
|
||||
std::to_string(mat->rows()) + "<" + TypeName(mat->type()) + ">";
|
||||
}
|
||||
return ty->type_name();
|
||||
}
|
||||
|
||||
IntrinsicTable::Result Impl::Lookup(
|
||||
ProgramBuilder& builder,
|
||||
semantic::IntrinsicType type,
|
||||
const std::vector<type::Type*>& args) const {
|
||||
// Candidate holds information about a mismatched overload that could be what
|
||||
// the user intended to call.
|
||||
struct Candidate {
|
||||
const Overload* overload;
|
||||
int score;
|
||||
};
|
||||
|
||||
// The list of failed matches that had promise.
|
||||
std::vector<Candidate> candidates;
|
||||
|
||||
// TODO(bclayton) Sort overloads_, or place them into a map keyed by intrinsic
|
||||
// type. This is horribly inefficient.
|
||||
for (auto& overload : overloads_) {
|
||||
int match_score = 0;
|
||||
if (auto* intrinsic = overload.Match(builder, type, args, match_score)) {
|
||||
return Result{intrinsic, ""}; // Match found
|
||||
}
|
||||
if (match_score > 0) {
|
||||
candidates.emplace_back(Candidate{&overload, match_score});
|
||||
}
|
||||
}
|
||||
|
||||
// Sort the candidates with the most promising first
|
||||
std::stable_sort(
|
||||
candidates.begin(), candidates.end(),
|
||||
[](const Candidate& a, const Candidate& b) { return a.score > b.score; });
|
||||
|
||||
// Generate an error message
|
||||
std::stringstream ss;
|
||||
ss << "no matching call to " << semantic::str(type) << "(";
|
||||
{
|
||||
bool first = true;
|
||||
for (auto* arg : args) {
|
||||
if (!first) {
|
||||
ss << ", ";
|
||||
}
|
||||
first = false;
|
||||
ss << TypeName(arg);
|
||||
}
|
||||
}
|
||||
ss << ")" << std::endl;
|
||||
|
||||
if (!candidates.empty()) {
|
||||
ss << std::endl;
|
||||
ss << candidates.size() << " candidate function"
|
||||
<< (candidates.size() > 1 ? "s:" : ":") << std::endl;
|
||||
for (auto& candidate : candidates) {
|
||||
ss << " " << candidate.overload->str() << std::endl;
|
||||
}
|
||||
}
|
||||
|
||||
return Result{nullptr, ss.str()};
|
||||
}
|
||||
|
||||
semantic::Intrinsic* Impl::Overload::Match(ProgramBuilder& builder,
|
||||
semantic::IntrinsicType intrinsic,
|
||||
const std::vector<type::Type*>& args,
|
||||
int& match_score) const {
|
||||
if (type != intrinsic) {
|
||||
match_score = std::numeric_limits<int>::min();
|
||||
return nullptr; // Incorrect function
|
||||
}
|
||||
|
||||
// Penalize argument <-> parameter count mismatches
|
||||
match_score = 1000;
|
||||
match_score -= std::max(parameters.size(), args.size()) -
|
||||
std::min(parameters.size(), args.size());
|
||||
|
||||
bool matched = parameters.size() == args.size();
|
||||
|
||||
Matcher::MatchState matcher_state;
|
||||
|
||||
// Check that each of the parameters match.
|
||||
// This stage also populates the open_types and open_numbers.
|
||||
auto count = std::min(parameters.size(), args.size());
|
||||
for (size_t i = 0; i < count; i++) {
|
||||
assert(args[i]);
|
||||
auto* arg_ty = args[i]->UnwrapAll();
|
||||
if (!parameters[i]->Match(matcher_state, arg_ty)) {
|
||||
matched = false;
|
||||
continue;
|
||||
}
|
||||
// Weight correct parameter matches more than exact number of arguments
|
||||
match_score += 2;
|
||||
}
|
||||
if (!matched) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// If any of the open-types are constrained, check that they match.
|
||||
for (auto matcher_it : open_type_matchers) {
|
||||
OpenType open_type = matcher_it.first;
|
||||
auto* matcher = matcher_it.second;
|
||||
auto type_it = matcher_state.open_types.find(open_type);
|
||||
if (type_it == matcher_state.open_types.end()) {
|
||||
// We have an overload that claims to have matched, but didn't actually
|
||||
// resolve the open type. This is a bug that needs fixing.
|
||||
assert(false);
|
||||
return nullptr;
|
||||
}
|
||||
auto* resolved_type = type_it->second;
|
||||
if (resolved_type == nullptr) {
|
||||
// We have an overload that claims to have matched, but has a nullptr
|
||||
// resolved open type. This is a bug that needs fixing.
|
||||
assert(false);
|
||||
return nullptr;
|
||||
}
|
||||
if (!matcher->Match(matcher_state, resolved_type)) {
|
||||
matched = false;
|
||||
continue;
|
||||
}
|
||||
match_score++;
|
||||
}
|
||||
if (!matched) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Overload matched!
|
||||
|
||||
// Build the return type
|
||||
Builder::BuildState builder_state{builder.Types(), matcher_state.open_types,
|
||||
matcher_state.open_numbers};
|
||||
auto* ret = return_type->Build(builder_state);
|
||||
assert(ret); // Build() must return a type
|
||||
|
||||
// Build the semantic parameters
|
||||
semantic::Parameters params;
|
||||
params.reserve(parameters.size());
|
||||
for (size_t i = 0; i < args.size(); i++) {
|
||||
auto* ty = parameters[i]->Build(builder_state);
|
||||
params.emplace_back(semantic::Parameter{ty});
|
||||
}
|
||||
|
||||
return builder.create<semantic::Intrinsic>(intrinsic, ret, params);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::unique_ptr<IntrinsicTable> IntrinsicTable::Create() {
|
||||
return std::make_unique<Impl>();
|
||||
}
|
||||
|
||||
IntrinsicTable::~IntrinsicTable() = default;
|
||||
|
||||
} // namespace tint
|
|
@ -0,0 +1,58 @@
|
|||
// Copyright 2021 The Tint Authors.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#ifndef SRC_INTRINSIC_TABLE_H_
|
||||
#define SRC_INTRINSIC_TABLE_H_
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "src/semantic/intrinsic.h"
|
||||
|
||||
namespace tint {
|
||||
|
||||
// Forward declarations
|
||||
class ProgramBuilder;
|
||||
|
||||
/// IntrinsicTable is a lookup table of all the WGSL intrinsic functions
|
||||
class IntrinsicTable {
|
||||
public:
|
||||
/// @return a pointer to a newly created IntrinsicTable
|
||||
static std::unique_ptr<IntrinsicTable> Create();
|
||||
|
||||
/// Destructor
|
||||
virtual ~IntrinsicTable();
|
||||
|
||||
/// Result is returned by Lookup
|
||||
struct Result {
|
||||
/// The intrinsic, if the lookup succeeded, otherwise nullptr
|
||||
semantic::Intrinsic* intrinsic;
|
||||
/// The error message, if the lookup failed, otherwise empty
|
||||
std::string error;
|
||||
};
|
||||
|
||||
/// Lookup looks for the intrinsic overload with the given signature.
|
||||
/// @param builder the program builder
|
||||
/// @param type the intrinsic type
|
||||
/// @param args the argument types passed to the intrinsic function
|
||||
/// @return the semantic intrinsic if found, otherwise nullptr
|
||||
virtual Result Lookup(ProgramBuilder& builder,
|
||||
semantic::IntrinsicType type,
|
||||
const std::vector<type::Type*>& args) const = 0;
|
||||
};
|
||||
|
||||
} // namespace tint
|
||||
|
||||
#endif // SRC_INTRINSIC_TABLE_H_
|
|
@ -71,7 +71,8 @@ using IntrinsicType = tint::semantic::IntrinsicType;
|
|||
|
||||
} // namespace
|
||||
|
||||
TypeDeterminer::TypeDeterminer(ProgramBuilder* builder) : builder_(builder) {}
|
||||
TypeDeterminer::TypeDeterminer(ProgramBuilder* builder)
|
||||
: builder_(builder), intrinsic_table_(IntrinsicTable::Create()) {}
|
||||
|
||||
TypeDeterminer::~TypeDeterminer() = default;
|
||||
|
||||
|
@ -456,97 +457,9 @@ bool TypeDeterminer::DetermineCall(ast::CallExpression* call) {
|
|||
return true;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
enum class IntrinsicDataType {
|
||||
kDependent,
|
||||
kSignedInteger,
|
||||
kUnsignedInteger,
|
||||
kFloat,
|
||||
kBool,
|
||||
};
|
||||
|
||||
struct IntrinsicData {
|
||||
IntrinsicType intrinsic;
|
||||
IntrinsicDataType result_type;
|
||||
uint8_t result_vector_width;
|
||||
uint8_t param_for_result_type;
|
||||
};
|
||||
|
||||
// Note, this isn't all the intrinsics. Some are handled specially before
|
||||
// we get to the generic code. See the DetermineIntrinsic code below.
|
||||
constexpr const IntrinsicData kIntrinsicData[] = {
|
||||
{IntrinsicType::kAbs, IntrinsicDataType::kDependent, 0, 0},
|
||||
{IntrinsicType::kAcos, IntrinsicDataType::kDependent, 0, 0},
|
||||
{IntrinsicType::kAll, IntrinsicDataType::kBool, 1, 0},
|
||||
{IntrinsicType::kAny, IntrinsicDataType::kBool, 1, 0},
|
||||
{IntrinsicType::kArrayLength, IntrinsicDataType::kUnsignedInteger, 1, 0},
|
||||
{IntrinsicType::kAsin, IntrinsicDataType::kDependent, 0, 0},
|
||||
{IntrinsicType::kAtan, IntrinsicDataType::kDependent, 0, 0},
|
||||
{IntrinsicType::kAtan2, IntrinsicDataType::kDependent, 0, 0},
|
||||
{IntrinsicType::kCeil, IntrinsicDataType::kDependent, 0, 0},
|
||||
{IntrinsicType::kClamp, IntrinsicDataType::kDependent, 0, 0},
|
||||
{IntrinsicType::kCos, IntrinsicDataType::kDependent, 0, 0},
|
||||
{IntrinsicType::kCosh, IntrinsicDataType::kDependent, 0, 0},
|
||||
{IntrinsicType::kCountOneBits, IntrinsicDataType::kDependent, 0, 0},
|
||||
{IntrinsicType::kCross, IntrinsicDataType::kFloat, 3, 0},
|
||||
{IntrinsicType::kDeterminant, IntrinsicDataType::kFloat, 1, 0},
|
||||
{IntrinsicType::kDistance, IntrinsicDataType::kFloat, 1, 0},
|
||||
{IntrinsicType::kDpdx, IntrinsicDataType::kDependent, 0, 0},
|
||||
{IntrinsicType::kDpdxCoarse, IntrinsicDataType::kDependent, 0, 0},
|
||||
{IntrinsicType::kDpdxFine, IntrinsicDataType::kDependent, 0, 0},
|
||||
{IntrinsicType::kDpdy, IntrinsicDataType::kDependent, 0, 0},
|
||||
{IntrinsicType::kDpdyCoarse, IntrinsicDataType::kDependent, 0, 0},
|
||||
{IntrinsicType::kDpdyFine, IntrinsicDataType::kDependent, 0, 0},
|
||||
{IntrinsicType::kDot, IntrinsicDataType::kFloat, 1, 0},
|
||||
{IntrinsicType::kExp, IntrinsicDataType::kDependent, 0, 0},
|
||||
{IntrinsicType::kExp2, IntrinsicDataType::kDependent, 0, 0},
|
||||
{IntrinsicType::kFaceForward, IntrinsicDataType::kDependent, 0, 0},
|
||||
{IntrinsicType::kFloor, IntrinsicDataType::kDependent, 0, 0},
|
||||
{IntrinsicType::kFwidth, IntrinsicDataType::kDependent, 0, 0},
|
||||
{IntrinsicType::kFwidthCoarse, IntrinsicDataType::kDependent, 0, 0},
|
||||
{IntrinsicType::kFwidthFine, IntrinsicDataType::kDependent, 0, 0},
|
||||
{IntrinsicType::kFma, IntrinsicDataType::kDependent, 0, 0},
|
||||
{IntrinsicType::kFract, IntrinsicDataType::kDependent, 0, 0},
|
||||
{IntrinsicType::kFrexp, IntrinsicDataType::kDependent, 0, 0},
|
||||
{IntrinsicType::kInverseSqrt, IntrinsicDataType::kDependent, 0, 0},
|
||||
{IntrinsicType::kLdexp, IntrinsicDataType::kDependent, 0, 0},
|
||||
{IntrinsicType::kLength, IntrinsicDataType::kFloat, 1, 0},
|
||||
{IntrinsicType::kLog, IntrinsicDataType::kDependent, 0, 0},
|
||||
{IntrinsicType::kLog2, IntrinsicDataType::kDependent, 0, 0},
|
||||
{IntrinsicType::kMax, IntrinsicDataType::kDependent, 0, 0},
|
||||
{IntrinsicType::kMin, IntrinsicDataType::kDependent, 0, 0},
|
||||
{IntrinsicType::kMix, IntrinsicDataType::kDependent, 0, 0},
|
||||
{IntrinsicType::kModf, IntrinsicDataType::kDependent, 0, 0},
|
||||
{IntrinsicType::kNormalize, IntrinsicDataType::kDependent, 0, 0},
|
||||
{IntrinsicType::kPack4x8Snorm, IntrinsicDataType::kUnsignedInteger, 1, 0},
|
||||
{IntrinsicType::kPack4x8Unorm, IntrinsicDataType::kUnsignedInteger, 1, 0},
|
||||
{IntrinsicType::kPack2x16Snorm, IntrinsicDataType::kUnsignedInteger, 1, 0},
|
||||
{IntrinsicType::kPack2x16Unorm, IntrinsicDataType::kUnsignedInteger, 1, 0},
|
||||
{IntrinsicType::kPack2x16Float, IntrinsicDataType::kUnsignedInteger, 1, 0},
|
||||
{IntrinsicType::kPow, IntrinsicDataType::kDependent, 0, 0},
|
||||
{IntrinsicType::kReflect, IntrinsicDataType::kDependent, 0, 0},
|
||||
{IntrinsicType::kReverseBits, IntrinsicDataType::kDependent, 0, 0},
|
||||
{IntrinsicType::kRound, IntrinsicDataType::kDependent, 0, 0},
|
||||
{IntrinsicType::kSelect, IntrinsicDataType::kDependent, 0, 0},
|
||||
{IntrinsicType::kSign, IntrinsicDataType::kDependent, 0, 0},
|
||||
{IntrinsicType::kSin, IntrinsicDataType::kDependent, 0, 0},
|
||||
{IntrinsicType::kSinh, IntrinsicDataType::kDependent, 0, 0},
|
||||
{IntrinsicType::kSmoothStep, IntrinsicDataType::kDependent, 0, 0},
|
||||
{IntrinsicType::kSqrt, IntrinsicDataType::kDependent, 0, 0},
|
||||
{IntrinsicType::kStep, IntrinsicDataType::kDependent, 0, 0},
|
||||
{IntrinsicType::kTan, IntrinsicDataType::kDependent, 0, 0},
|
||||
{IntrinsicType::kTanh, IntrinsicDataType::kDependent, 0, 0},
|
||||
{IntrinsicType::kTrunc, IntrinsicDataType::kDependent, 0, 0},
|
||||
};
|
||||
|
||||
constexpr const uint32_t kIntrinsicDataCount =
|
||||
sizeof(kIntrinsicData) / sizeof(IntrinsicData);
|
||||
|
||||
} // namespace
|
||||
|
||||
bool TypeDeterminer::DetermineIntrinsicCall(ast::CallExpression* call,
|
||||
IntrinsicType intrinsic_type) {
|
||||
bool TypeDeterminer::DetermineIntrinsicCall(
|
||||
ast::CallExpression* call,
|
||||
semantic::IntrinsicType intrinsic_type) {
|
||||
using Parameter = semantic::Parameter;
|
||||
using Parameters = semantic::Parameters;
|
||||
using Usage = Parameter::Usage;
|
||||
|
@ -557,30 +470,9 @@ bool TypeDeterminer::DetermineIntrinsicCall(ast::CallExpression* call,
|
|||
arg_tys.emplace_back(TypeOf(expr));
|
||||
}
|
||||
|
||||
auto create_sem = [&](type::Type* return_type) {
|
||||
semantic::Parameters params; // TODO(bclayton): Populate this
|
||||
auto* intrinsic = builder_->create<semantic::Intrinsic>(
|
||||
intrinsic_type, return_type, params);
|
||||
builder_->Sem().Add(call, builder_->create<semantic::Call>(intrinsic));
|
||||
};
|
||||
|
||||
std::string name = semantic::str(intrinsic_type);
|
||||
if (semantic::IsFloatClassificationIntrinsic(intrinsic_type)) {
|
||||
if (call->params().size() != 1) {
|
||||
set_error(call->source(), "incorrect number of parameters for " + name);
|
||||
return false;
|
||||
}
|
||||
|
||||
auto* bool_type = builder_->create<type::Bool>();
|
||||
|
||||
auto* param_type = TypeOf(call->params()[0])->UnwrapPtrIfNeeded();
|
||||
if (auto* vec = param_type->As<type::Vector>()) {
|
||||
create_sem(builder_->create<type::Vector>(bool_type, vec->size()));
|
||||
} else {
|
||||
create_sem(bool_type);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
// TODO(bclayton): Add these to the IntrinsicTable
|
||||
if (semantic::IsTextureIntrinsic(intrinsic_type)) {
|
||||
Parameters params;
|
||||
|
||||
|
@ -768,54 +660,45 @@ bool TypeDeterminer::DetermineIntrinsicCall(ast::CallExpression* call,
|
|||
return true;
|
||||
}
|
||||
|
||||
const IntrinsicData* data = nullptr;
|
||||
for (uint32_t i = 0; i < kIntrinsicDataCount; ++i) {
|
||||
if (intrinsic_type == kIntrinsicData[i].intrinsic) {
|
||||
data = &kIntrinsicData[i];
|
||||
break;
|
||||
auto result = intrinsic_table_->Lookup(*builder_, intrinsic_type, arg_tys);
|
||||
if (!result.intrinsic) {
|
||||
// Intrinsic lookup failed.
|
||||
set_error(call->source(), result.error);
|
||||
|
||||
// TODO(bclayton): https://crbug.com/tint/487
|
||||
// The Validator expects intrinsic signature mismatches to still produce
|
||||
// type information. The rules for what the Validator expects are rather
|
||||
// bespoke. Try to match what the Validator expects. As the Validator's
|
||||
// checks on intrinsics is now almost entirely covered by the
|
||||
// IntrinsicTable, we should remove the Validator checks on intrinsic
|
||||
// signatures and remove these hacks.
|
||||
semantic::Parameters parameters;
|
||||
parameters.reserve(arg_tys.size());
|
||||
for (auto* arg : arg_tys) {
|
||||
parameters.emplace_back(semantic::Parameter{arg});
|
||||
}
|
||||
}
|
||||
if (data == nullptr) {
|
||||
error_ = "unable to find intrinsic " + name;
|
||||
type::Type* ret_ty = nullptr;
|
||||
switch (intrinsic_type) {
|
||||
case IntrinsicType::kCross:
|
||||
ret_ty = builder_->ty.vec3<ProgramBuilder::f32>();
|
||||
break;
|
||||
case IntrinsicType::kDeterminant:
|
||||
ret_ty = builder_->create<type::F32>();
|
||||
break;
|
||||
case IntrinsicType::kArrayLength:
|
||||
ret_ty = builder_->create<type::U32>();
|
||||
break;
|
||||
default:
|
||||
ret_ty = arg_tys.empty() ? builder_->ty.void_() : arg_tys[0];
|
||||
break;
|
||||
}
|
||||
auto* intrinsic = builder_->create<semantic::Intrinsic>(intrinsic_type,
|
||||
ret_ty, parameters);
|
||||
builder_->Sem().Add(call, builder_->create<semantic::Call>(intrinsic));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (data->result_type == IntrinsicDataType::kDependent) {
|
||||
const auto param_idx = data->param_for_result_type;
|
||||
if (call->params().size() <= param_idx) {
|
||||
set_error(call->source(),
|
||||
"missing parameter " + std::to_string(param_idx) +
|
||||
" required for type determination in builtin " + name);
|
||||
return false;
|
||||
}
|
||||
create_sem(TypeOf(call->params()[param_idx])->UnwrapPtrIfNeeded());
|
||||
} else {
|
||||
// The result type is not dependent on the parameter types.
|
||||
type::Type* type = nullptr;
|
||||
switch (data->result_type) {
|
||||
case IntrinsicDataType::kSignedInteger:
|
||||
type = builder_->create<type::I32>();
|
||||
break;
|
||||
case IntrinsicDataType::kUnsignedInteger:
|
||||
type = builder_->create<type::U32>();
|
||||
break;
|
||||
case IntrinsicDataType::kFloat:
|
||||
type = builder_->create<type::F32>();
|
||||
break;
|
||||
case IntrinsicDataType::kBool:
|
||||
type = builder_->create<type::Bool>();
|
||||
break;
|
||||
default:
|
||||
error_ = "unhandled intrinsic data type for " + name;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (data->result_vector_width > 1) {
|
||||
type = builder_->create<type::Vector>(type, data->result_vector_width);
|
||||
}
|
||||
create_sem(type);
|
||||
}
|
||||
|
||||
builder_->Sem().Add(call, builder_->create<semantic::Call>(result.intrinsic));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
#ifndef SRC_TYPE_DETERMINER_H_
|
||||
#define SRC_TYPE_DETERMINER_H_
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
|
@ -22,6 +23,7 @@
|
|||
|
||||
#include "src/ast/module.h"
|
||||
#include "src/diagnostic/diagnostic.h"
|
||||
#include "src/intrinsic_table.h"
|
||||
#include "src/program_builder.h"
|
||||
#include "src/scope_stack.h"
|
||||
#include "src/semantic/intrinsic.h"
|
||||
|
@ -197,6 +199,7 @@ class TypeDeterminer {
|
|||
void SetType(ast::Expression* expr, type::Type* type) const;
|
||||
|
||||
ProgramBuilder* const builder_;
|
||||
std::unique_ptr<IntrinsicTable> const intrinsic_table_;
|
||||
std::string error_;
|
||||
ScopeStack<VariableInfo*> variable_stack_;
|
||||
std::unordered_map<Symbol, FunctionInfo*> symbol_to_function_;
|
||||
|
|
|
@ -1256,9 +1256,11 @@ TEST_P(IntrinsicDerivativeTest, MissingParam) {
|
|||
|
||||
EXPECT_FALSE(td()->Determine());
|
||||
|
||||
EXPECT_EQ(td()->error(),
|
||||
"missing parameter 0 required for type determination in builtin " +
|
||||
std::string(name));
|
||||
EXPECT_EQ(td()->error(), "no matching call to " + name +
|
||||
"()\n\n"
|
||||
"2 candidate functions:\n " +
|
||||
name + "(f32) -> f32\n " + name +
|
||||
"(vecN<f32>) -> vecN<f32>\n");
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(TypeDeterminerTest,
|
||||
|
@ -1332,7 +1334,11 @@ TEST_P(Intrinsic_FloatMethod, MissingParam) {
|
|||
|
||||
EXPECT_FALSE(td()->Determine());
|
||||
|
||||
EXPECT_EQ(td()->error(), "incorrect number of parameters for " + name);
|
||||
EXPECT_EQ(td()->error(), "no matching call to " + name +
|
||||
"()\n\n"
|
||||
"2 candidate functions:\n " +
|
||||
name + "(f32) -> bool\n " + name +
|
||||
"(vecN<f32>) -> vecN<bool>\n");
|
||||
}
|
||||
|
||||
TEST_P(Intrinsic_FloatMethod, TooManyParams) {
|
||||
|
@ -1345,7 +1351,11 @@ TEST_P(Intrinsic_FloatMethod, TooManyParams) {
|
|||
|
||||
EXPECT_FALSE(td()->Determine());
|
||||
|
||||
EXPECT_EQ(td()->error(), "incorrect number of parameters for " + name);
|
||||
EXPECT_EQ(td()->error(), "no matching call to " + name +
|
||||
"(f32, f32)\n\n"
|
||||
"2 candidate functions:\n " +
|
||||
name + "(f32) -> bool\n " + name +
|
||||
"(vecN<f32>) -> vecN<bool>\n");
|
||||
}
|
||||
INSTANTIATE_TEST_SUITE_P(
|
||||
TypeDeterminerTest,
|
||||
|
@ -1564,9 +1574,13 @@ TEST_F(TypeDeterminerTest, Intrinsic_Select_NoParams) {
|
|||
|
||||
EXPECT_FALSE(td()->Determine());
|
||||
|
||||
EXPECT_EQ(
|
||||
td()->error(),
|
||||
"missing parameter 0 required for type determination in builtin select");
|
||||
EXPECT_EQ(td()->error(),
|
||||
R"(no matching call to select()
|
||||
|
||||
2 candidate functions:
|
||||
select(T, T, bool) -> T where: T is scalar
|
||||
select(vecN<T>, vecN<T>, vecN<bool>) -> vecN<T> where: T is scalar
|
||||
)");
|
||||
}
|
||||
|
||||
using UnaryOpExpressionTest = TypeDeterminerTestWithParam<ast::UnaryOp>;
|
||||
|
@ -1781,9 +1795,12 @@ TEST_P(ImportData_SingleParamTest, Error_NoParams) {
|
|||
|
||||
EXPECT_FALSE(td()->Determine());
|
||||
|
||||
EXPECT_EQ(td()->error(),
|
||||
"missing parameter 0 required for type determination in builtin " +
|
||||
std::string(param.name));
|
||||
EXPECT_EQ(td()->error(), "no matching call to " + std::string(param.name) +
|
||||
"()\n\n"
|
||||
"2 candidate functions:\n " +
|
||||
std::string(param.name) + "(f32) -> f32\n " +
|
||||
std::string(param.name) +
|
||||
"(vecN<f32>) -> vecN<f32>\n");
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(
|
||||
|
@ -1829,8 +1846,9 @@ TEST_F(IntrinsicDataTest, Normalize_Error_NoParams) {
|
|||
EXPECT_FALSE(td()->Determine());
|
||||
|
||||
EXPECT_EQ(td()->error(),
|
||||
"missing parameter 0 required for type determination in builtin "
|
||||
"normalize");
|
||||
"no matching call to normalize()\n\n"
|
||||
"1 candidate function:\n "
|
||||
"normalize(vecN<f32>) -> vecN<f32>\n");
|
||||
}
|
||||
|
||||
using ImportData_SingleParam_FloatOrInt_Test =
|
||||
|
@ -1930,8 +1948,13 @@ TEST_P(ImportData_SingleParam_FloatOrInt_Test, Error_NoParams) {
|
|||
EXPECT_FALSE(td()->Determine());
|
||||
|
||||
EXPECT_EQ(td()->error(),
|
||||
"missing parameter 0 required for type determination in builtin " +
|
||||
std::string(param.name));
|
||||
"no matching call to " + std::string(param.name) +
|
||||
"()\n\n"
|
||||
"2 candidate functions:\n " +
|
||||
std::string(param.name) +
|
||||
"(T) -> T where: T is f32, i32 or u32\n " +
|
||||
std::string(param.name) +
|
||||
"(vecN<T>) -> vecN<T> where: T is f32, i32 or u32\n");
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(TypeDeterminerTest,
|
||||
|
@ -1996,9 +2019,13 @@ TEST_P(ImportData_TwoParamTest, Error_NoParams) {
|
|||
|
||||
EXPECT_FALSE(td()->Determine());
|
||||
|
||||
EXPECT_EQ(td()->error(),
|
||||
"missing parameter 0 required for type determination in builtin " +
|
||||
std::string(param.name));
|
||||
EXPECT_EQ(td()->error(), "no matching call to " + std::string(param.name) +
|
||||
"()\n\n"
|
||||
"2 candidate functions:\n " +
|
||||
std::string(param.name) +
|
||||
"(f32, f32) -> f32\n " +
|
||||
std::string(param.name) +
|
||||
"(vecN<f32>, vecN<f32>) -> vecN<f32>\n");
|
||||
}
|
||||
INSTANTIATE_TEST_SUITE_P(
|
||||
TypeDeterminerTest,
|
||||
|
@ -2041,10 +2068,23 @@ TEST_F(TypeDeterminerTest, ImportData_Cross) {
|
|||
EXPECT_EQ(TypeOf(call)->As<type::Vector>()->size(), 3u);
|
||||
}
|
||||
|
||||
TEST_F(TypeDeterminerTest, ImportData_Cross_AutoType) {
|
||||
TEST_F(TypeDeterminerTest, ImportData_Cross_NoArgs) {
|
||||
auto* call = Call("cross");
|
||||
WrapInFunction(call);
|
||||
|
||||
EXPECT_FALSE(td()->Determine());
|
||||
|
||||
EXPECT_EQ(td()->error(), R"(no matching call to cross()
|
||||
|
||||
1 candidate function:
|
||||
cross(vec3<f32>, vec3<f32>) -> vec3<f32>
|
||||
)");
|
||||
}
|
||||
|
||||
TEST_F(TypeDeterminerTest, ImportData_Normalize) {
|
||||
auto* call = Call("normalize", vec3<f32>(1.0f, 1.0f, 3.0f));
|
||||
WrapInFunction(call);
|
||||
|
||||
EXPECT_TRUE(td()->Determine()) << td()->error();
|
||||
|
||||
ASSERT_NE(TypeOf(call), nullptr);
|
||||
|
@ -2052,6 +2092,19 @@ TEST_F(TypeDeterminerTest, ImportData_Cross_AutoType) {
|
|||
EXPECT_EQ(TypeOf(call)->As<type::Vector>()->size(), 3u);
|
||||
}
|
||||
|
||||
TEST_F(TypeDeterminerTest, ImportData_Normalize_NoArgs) {
|
||||
auto* call = Call("normalize");
|
||||
WrapInFunction(call);
|
||||
|
||||
EXPECT_FALSE(td()->Determine());
|
||||
|
||||
EXPECT_EQ(td()->error(), R"(no matching call to normalize()
|
||||
|
||||
1 candidate function:
|
||||
normalize(vecN<f32>) -> vecN<f32>
|
||||
)");
|
||||
}
|
||||
|
||||
using ImportData_ThreeParamTest = TypeDeterminerTestWithParam<IntrinsicData>;
|
||||
TEST_P(ImportData_ThreeParamTest, Scalar) {
|
||||
auto param = GetParam();
|
||||
|
@ -2087,8 +2140,12 @@ TEST_P(ImportData_ThreeParamTest, Error_NoParams) {
|
|||
EXPECT_FALSE(td()->Determine());
|
||||
|
||||
EXPECT_EQ(td()->error(),
|
||||
"missing parameter 0 required for type determination in builtin " +
|
||||
std::string(param.name));
|
||||
"no matching call to " + std::string(param.name) +
|
||||
"()\n\n"
|
||||
"2 candidate functions:\n " +
|
||||
std::string(param.name) + "(f32, f32, f32) -> f32\n " +
|
||||
std::string(param.name) +
|
||||
"(vecN<f32>, vecN<f32>, vecN<f32>) -> vecN<f32>\n");
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(
|
||||
|
@ -2188,8 +2245,14 @@ TEST_P(ImportData_ThreeParam_FloatOrInt_Test, Error_NoParams) {
|
|||
EXPECT_FALSE(td()->Determine());
|
||||
|
||||
EXPECT_EQ(td()->error(),
|
||||
"missing parameter 0 required for type determination in builtin " +
|
||||
std::string(param.name));
|
||||
"no matching call to " + std::string(param.name) +
|
||||
"()\n\n"
|
||||
"2 candidate functions:\n " +
|
||||
std::string(param.name) +
|
||||
"(T, T, T) -> T where: T is f32, i32 or u32\n " +
|
||||
std::string(param.name) +
|
||||
"(vecN<T>, vecN<T>, vecN<T>) -> vecN<T> where: T is f32, i32 "
|
||||
"or u32\n");
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(TypeDeterminerTest,
|
||||
|
@ -2233,8 +2296,13 @@ TEST_P(ImportData_Int_SingleParamTest, Error_NoParams) {
|
|||
EXPECT_FALSE(td()->Determine());
|
||||
|
||||
EXPECT_EQ(td()->error(),
|
||||
"missing parameter 0 required for type determination in builtin " +
|
||||
std::string(param.name));
|
||||
"no matching call to " + std::string(param.name) +
|
||||
"()\n\n"
|
||||
"2 candidate functions:\n " +
|
||||
std::string(param.name) +
|
||||
"(T) -> T where: T is i32 or u32\n " +
|
||||
std::string(param.name) +
|
||||
"(vecN<T>) -> vecN<T> where: T is i32 or u32\n");
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(
|
||||
|
@ -2330,8 +2398,13 @@ TEST_P(ImportData_FloatOrInt_TwoParamTest, Error_NoParams) {
|
|||
EXPECT_FALSE(td()->Determine());
|
||||
|
||||
EXPECT_EQ(td()->error(),
|
||||
"missing parameter 0 required for type determination in builtin " +
|
||||
std::string(param.name));
|
||||
"no matching call to " + std::string(param.name) +
|
||||
"()\n\n"
|
||||
"2 candidate functions:\n " +
|
||||
std::string(param.name) +
|
||||
"(T, T) -> T where: T is f32, i32 or u32\n " +
|
||||
std::string(param.name) +
|
||||
"(vecN<T>, vecN<T>) -> vecN<T> where: T is f32, i32 or u32\n");
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(
|
||||
|
@ -2361,9 +2434,15 @@ TEST_P(ImportData_Matrix_OneParam_Test, NoParams) {
|
|||
auto* call = Call(param.name);
|
||||
WrapInFunction(call);
|
||||
|
||||
EXPECT_TRUE(td()->Determine()) << td()->error();
|
||||
EXPECT_FALSE(td()->Determine());
|
||||
|
||||
EXPECT_TRUE(TypeOf(call)->Is<type::F32>());
|
||||
EXPECT_EQ(td()->error(),
|
||||
"no matching call to " + std::string(param.name) + R"(()
|
||||
|
||||
1 candidate function:
|
||||
determinant(maxNxN<f32>) -> f32
|
||||
)");
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(TypeDeterminerTest,
|
||||
|
|
Loading…
Reference in New Issue