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:
Ben Clayton 2021-02-08 22:42:54 +00:00 committed by Commit Bot service account
parent 316f9f6b6d
commit 59d24735d0
7 changed files with 1211 additions and 190 deletions

View File

@ -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",
]

View File

@ -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
)

994
src/intrinsic_table.cc Normal file
View File

@ -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

58
src/intrinsic_table.h Normal file
View File

@ -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_

View File

@ -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;
}

View File

@ -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_;

View File

@ -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,