src: Reimplement IntrinsicTable from intrisics.def

Add a template file to generate intrinsic_table.inl and
include this from intrinsic_table.cc.

Speeds up execution of the unittests by 20 - 30%, and
reduces the executable size by a couple of percent.

Bug: tint:832
Change-Id: Ifa7f3c42202c92e97b46ed1716ece48660dd29dd
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/52504
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: David Neto <dneto@google.com>
Reviewed-by: Antonio Maiorano <amaiorano@google.com>
Commit-Queue: David Neto <dneto@google.com>
This commit is contained in:
Ben Clayton 2021-06-01 19:06:31 +00:00 committed by Tint LUCI CQ
parent ae5437cc87
commit b29a59de6e
13 changed files with 9411 additions and 1636 deletions

View File

@ -436,6 +436,7 @@ libtint_source_set("libtint_core_all_src") {
"inspector/scalar.h", "inspector/scalar.h",
"intrinsic_table.cc", "intrinsic_table.cc",
"intrinsic_table.h", "intrinsic_table.h",
"intrinsic_table.inl",
"program.cc", "program.cc",
"program.h", "program.h",
"program_builder.cc", "program_builder.cc",

View File

@ -221,6 +221,7 @@ set(TINT_LIB_SRCS
inspector/scalar.h inspector/scalar.h
intrinsic_table.cc intrinsic_table.cc
intrinsic_table.h intrinsic_table.h
intrinsic_table.inl
program_builder.cc program_builder.cc
program_builder.h program_builder.h
program_id.cc program_id.cc

File diff suppressed because it is too large Load Diff

View File

@ -29,30 +29,23 @@ class ProgramBuilder;
/// IntrinsicTable is a lookup table of all the WGSL intrinsic functions /// IntrinsicTable is a lookup table of all the WGSL intrinsic functions
class IntrinsicTable { class IntrinsicTable {
public: public:
/// @param builder the program builder
/// @return a pointer to a newly created IntrinsicTable /// @return a pointer to a newly created IntrinsicTable
static std::unique_ptr<IntrinsicTable> Create(); static std::unique_ptr<IntrinsicTable> Create(ProgramBuilder& builder);
/// Destructor /// Destructor
virtual ~IntrinsicTable(); virtual ~IntrinsicTable();
/// Result is returned by Lookup /// Lookup looks for the intrinsic overload with the given signature, raising
struct Result { /// an error diagnostic if the intrinsic was not found.
/// The intrinsic, if the lookup succeeded, otherwise nullptr
sem::Intrinsic* intrinsic;
/// Diagnostic messages
diag::List diagnostics;
};
/// Lookup looks for the intrinsic overload with the given signature.
/// @param builder the program builder
/// @param type the intrinsic type /// @param type the intrinsic type
/// @param args the argument types passed to the intrinsic function /// @param args the argument types passed to the intrinsic function
/// @param source the source of the intrinsic call /// @param source the source of the intrinsic call
/// @return the semantic intrinsic if found, otherwise nullptr /// @return the semantic intrinsic if found, otherwise nullptr
virtual Result Lookup(ProgramBuilder& builder, virtual const sem::Intrinsic* Lookup(
sem::IntrinsicType type, sem::IntrinsicType type,
const std::vector<const sem::Type*>& args, const std::vector<const sem::Type*>& args,
const Source& source) const = 0; const Source& source) const = 0;
}; };
} // namespace tint } // namespace tint

7551
src/intrinsic_table.inl Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,447 @@
{{- /*
--------------------------------------------------------------------------------
Template file for use with tools/intrinsic-gen to generate intrinsic_table.inl
Used by IntrinsicTable.cc for intrinsic overload resolution.
See:
* tools/cmd/intrinsic-gen/gen for structures used by this template
* https://golang.org/pkg/text/template/ for documentation on the template syntax
--------------------------------------------------------------------------------
*/ -}}
// clang-format off
/// ParameterInfo describes a parameter
struct ParameterInfo {
/// The parameter usage (parameter name in definition file)
ParameterUsage const usage;
/// Pointer to a list of indices that are used to match the parameter type.
/// The matcher indices index on Matchers::type and / or Matchers::number.
/// These indices are consumed by the matchers themselves.
/// The first index is always a TypeMatcher.
MatcherIndex const* const matcher_indices;
};
/// OpenTypeInfo describes an open type
struct OpenTypeInfo {
/// Name of the open type (e.g. 'T')
const char* name;
/// Optional type matcher constraint.
/// Either an index in Matchers::type, or kNoMatcher
MatcherIndex const matcher_index;
};
/// OpenNumberInfo describes an open number
struct OpenNumberInfo {
/// Name of the open number (e.g. 'N')
const char* name;
/// Optional number matcher constraint.
/// Either an index in Matchers::number, or kNoMatcher
MatcherIndex const matcher_index;
};
/// OverloadInfo describes a single function overload
struct OverloadInfo {
/// Total number of parameters for the overload
uint8_t const num_parameters;
/// Total number of open types for the overload
uint8_t const num_open_types;
/// Total number of open numbers for the overload
uint8_t const num_open_numbers;
/// Pointer to the first open type
OpenTypeInfo const* const open_types;
/// Pointer to the first open number
OpenNumberInfo const* const open_numbers;
/// Pointer to the first parameter
ParameterInfo const* const parameters;
/// Pointer to a list of matcher indices that index on Matchers::type and
/// Matchers::number, used to build the return type. If the function has no
/// return type then this is null.
MatcherIndex const* const return_matcher_indices;
};
/// IntrinsicInfo describes an intrinsic function
struct IntrinsicInfo {
/// Number of overloads of the intrinsic function
uint8_t const num_overloads;
/// Pointer to the start of the overloads for the function
OverloadInfo const* const overloads;
};
{{ with .Sem -}}
{{ range .Types -}}
{{ template "Type" . }}
{{ end -}}
{{ range .TypeMatchers -}}
{{ template "TypeMatcher" . }}
{{ end -}}
{{ range .EnumMatchers -}}
{{ template "EnumMatcher" . }}
{{ end -}}
{{- end -}}
{{- with IntrinsicTable -}}
{{- template "Matchers" . }}
constexpr MatcherIndex kMatcherIndices[] = {
{{- range $i, $idx := .MatcherIndices }}
/* [{{$i}}] */ {{$idx}},
{{- end }}
};
// Assert that the MatcherIndex is big enough to index all the matchers, plus
// kNoMatcher.
static_assert(static_cast<int>(sizeof(kMatcherIndices) / sizeof(kMatcherIndices[0])) <
static_cast<int>(std::numeric_limits<MatcherIndex>::max() - 1),
"MatcherIndex is not large enough to index kMatcherIndices");
constexpr ParameterInfo kParameters[] = {
{{- range $i, $p := .Parameters }}
{
/* [{{$i}}] */
/* usage */ ParameterUsage::
{{- if $p.Usage }}k{{PascalCase $p.Usage}}
{{- else }}kNone
{{- end }},
/* matcher indices */ &kMatcherIndices[{{$p.MatcherIndicesOffset}}],
},
{{- end }}
};
constexpr OpenTypeInfo kOpenTypes[] = {
{{- range $i, $o := .OpenTypes }}
{
/* [{{$i}}] */
/* name */ "{{$o.Name}}",
/* matcher index */
{{- if ge $o.MatcherIndex 0 }} {{$o.MatcherIndex}}
{{- else }} kNoMatcher
{{- end }},
},
{{- end }}
};
constexpr OpenNumberInfo kOpenNumbers[] = {
{{- range $i, $o := .OpenNumbers }}
{
/* [{{$i}}] */
/* name */ "{{$o.Name}}",
/* matcher index */
{{- if ge $o.MatcherIndex 0 }} {{$o.MatcherIndex}}
{{- else }} kNoMatcher
{{- end }},
},
{{- end }}
};
constexpr OverloadInfo kOverloads[] = {
{{- range $i, $o := .Overloads }}
{
/* [{{$i}}] */
/* num parameters */ {{$o.NumParameters}},
/* num open types */ {{$o.NumOpenTypes}},
/* num open numbers */ {{$o.NumOpenNumbers}},
/* open types */
{{- if $o.OpenTypesOffset }} &kOpenTypes[{{$o.OpenTypesOffset}}],
{{- else }} nullptr,
{{- end }}
/* open numbers */
{{- if $o.OpenNumbersOffset }} &kOpenNumbers[{{$o.OpenNumbersOffset}}]
{{- else }} nullptr
{{- end }},
/* parameters */ &kParameters[{{$o.ParametersOffset}}],
/* return matcher indices */
{{- if $o.ReturnMatcherIndicesOffset }} &kMatcherIndices[{{$o.ReturnMatcherIndicesOffset}}]
{{- else }} nullptr
{{- end }},
},
{{- end }}
};
constexpr IntrinsicInfo kIntrinsics[] = {
{{- range $i, $f := .Functions }}
{
/* [{{$i}}] */
{{- range $f.OverloadDescriptions }}
/* {{.}} */
{{- end }}
/* num overloads */ {{$f.NumOverloads}},
/* overloads */ &kOverloads[{{$f.OverloadsOffset}}],
},
{{- end }}
};
// clang-format on
{{ end -}}
{{- /* ------------------------------------------------------------------ */ -}}
{{- define "Type" -}}
{{- /* ------------------------------------------------------------------ */ -}}
{{- $class := PascalCase .Name -}}
/// TypeMatcher for 'type {{.Name}}'
{{- if .Decl.Source.S.Filepath }}
/// @see {{.Decl.Source}}
{{- end }}
class {{$class}} : public TypeMatcher {
public:
/// Checks whether the given type matches the matcher rules.
/// Match may close open types and numbers in state.
/// @param state the MatchState
/// @param type the type to match
/// @returns the canonicalized type on match, otherwise nullptr
const sem::Type* Match(MatchState& state,
const sem::Type* type) const override;
/// @param state the MatchState
/// @return a string representation of the matcher.
std::string String(MatchState& state) const override;
};
const sem::Type* {{$class}}::Match(MatchState& state, const sem::Type* ty) const {
{{- range .TemplateParams }}
{{- template "DeclareLocalTemplateParam" . }}
{{- end }}
if (!match_{{.Name}}(ty{{range .TemplateParams}}, {{.GetName}}{{end}})) {
return nullptr;
}
{{- range .TemplateParams }}
{{.Name}} = {{ template "MatchTemplateParam" .}}({{.Name}});
if ({{ template "IsTemplateParamInvalid" .}}) {
return nullptr;
}
{{- end }}
return build_{{.Name}}(state{{range .TemplateParams}}, {{.GetName}}{{end}});
}
std::string {{$class}}::String(MatchState&{{if .TemplateParams}} state{{end}}) const {
{{- range .TemplateParams }}
{{- template "DeclareLocalTemplateParamName" . }}
{{- end }}
{{- if .DisplayName }}
std::stringstream ss;
ss{{range SplitDisplayName .DisplayName}} << {{.}}{{end}};
return ss.str();
{{- else if .TemplateParams }}
return "{{.Name}}<"{{template "AppendTemplateParamNames" .TemplateParams}} + ">";
{{- else }}
return "{{.Name}}";
{{- end }}
}
{{ end -}}
{{- /* ------------------------------------------------------------------ */ -}}
{{- define "TypeMatcher" -}}
{{- /* ------------------------------------------------------------------ */ -}}
{{- $class := PascalCase .Name -}}
/// TypeMatcher for 'match {{.Name}}'
{{- if .Decl.Source.S.Filepath }}
/// @see {{.Decl.Source}}
{{- end }}
class {{$class}} : public TypeMatcher {
public:
/// Checks whether the given type matches the matcher rules, and returns the
/// expected, canonicalized type on success.
/// Match may close open types and numbers in state.
/// @param state the MatchState
/// @param type the type to match
/// @returns the canonicalized type on match, otherwise nullptr
const sem::Type* Match(MatchState& state,
const sem::Type* type) const override;
/// @param state the MatchState
/// @return a string representation of the matcher.
std::string String(MatchState& state) const override;
};
const sem::Type* {{$class}}::Match(MatchState& state, const sem::Type* ty) const {
{{- range .Types }}
if (match_{{.Name}}(ty)) {
return build_{{.Name}}(state);
}
{{- end }}
return nullptr;
}
std::string {{$class}}::String(MatchState&) const {
return "
{{- range .Types -}}
{{- if IsFirstIn . $.Types }}{{.Name}}
{{- else if IsLastIn . $.Types }} or {{.Name}}
{{- else }}, {{.Name}}
{{- end -}}
{{- end -}}
";
}
{{ end -}}
{{- /* ------------------------------------------------------------------ */ -}}
{{- define "EnumMatcher" -}}
{{- /* ------------------------------------------------------------------ */ -}}
{{- $class := PascalCase .Name -}}
{{- $enum := PascalCase .Enum.Name -}}
/// EnumMatcher for 'match {{.Name}}'
{{- if .Decl.Source.S.Filepath }}
/// @see {{.Decl.Source}}
{{- end }}
class {{$class}} : public NumberMatcher {
public:
/// Checks whether the given number matches the enum matcher rules.
/// Match may close open types and numbers in state.
/// @param state the MatchState
/// @param number the enum value as a Number
/// @return true if the enum value matches the set
Number Match(MatchState& state, Number number) const override;
/// @param state the MatchState
/// @return a string representation of the matcher.
std::string String(MatchState& state) const override;
};
{{ if eq 1 (len .Options) -}}
{{- $option := index .Options 0 }}
{{- $entry := printf "k%v" (PascalCase $option.Name) -}}
Number {{$class}}::Match(MatchState&, Number number) const {
if (number.IsAny() || number.Value() == static_cast<uint32_t>({{$enum}}::{{$entry}})) {
return Number({{$enum}}::{{$entry}});
}
return Number::invalid;
}
{{- else -}}
Number {{$class}}::Match(MatchState&, Number number) const {
switch (static_cast<{{$enum}}>(number.Value())) {
{{- range .Options }}
case {{$enum}}::k{{PascalCase .Name}}:
{{- end }}
return number;
default:
return Number::invalid;
}
}
{{- end }}
std::string {{$class}}::String(MatchState&) const {
return "{{.Name}}";
}
{{ end -}}
{{- /* ------------------------------------------------------------------ */ -}}
{{- define "Matchers" -}}
{{- /* ------------------------------------------------------------------ */ -}}
/// Matchers holds type and number matchers
class Matchers {
private:
{{- $t_names := Map -}}
{{- $n_names := Map -}}
{{- range Iterate .Sem.MaxOpenTypes -}}
{{- $name := printf "open_type_%v" . -}}
{{- $t_names.Put . $name }}
OpenTypeMatcher {{$name}}_{ {{- . -}} };
{{- end }}
{{- range Iterate .Sem.MaxOpenNumbers -}}
{{- $name := printf "open_number_%v" . -}}
{{- $n_names.Put . $name }}
OpenNumberMatcher {{$name}}_{ {{- . -}} };
{{- end }}
{{- range .Sem.Types -}}
{{- $name := PascalCase .Name -}}
{{- $t_names.Put . $name }}
{{$name}} {{$name}}_;
{{- end }}
{{- range .Sem.TypeMatchers -}}
{{- $name := PascalCase .Name -}}
{{- $t_names.Put . $name }}
{{$name}} {{$name}}_;
{{- end }}
{{- range .Sem.EnumMatchers -}}
{{- $name := PascalCase .Name -}}
{{- $n_names.Put . $name }}
{{$name}} {{$name}}_;
{{- end }}
public:
/// Constructor
Matchers();
/// Destructor
~Matchers();
/// The open-types, types, and type matchers
TypeMatcher const* const type[{{len .TMatchers}}] = {
{{- range $i, $m := .TMatchers }}
/* [{{$i}}] */
{{- if $m }} &{{$t_names.Get $m}}_,
{{- else }} &{{$t_names.Get $i}}_,
{{- end }}
{{- end }}
};
/// The open-numbers, and number matchers
NumberMatcher const* const number[{{len .NMatchers}}] = {
{{- range $i, $m := .NMatchers }}
/* [{{$i}}] */
{{- if $m }} &{{$n_names.Get $m}}_,
{{- else }} &{{$n_names.Get $i}}_,
{{- end }}
{{- end }}
};
};
Matchers::Matchers() = default;
Matchers::~Matchers() = default;
{{- end -}}
{{- /* ------------------------------------------------------------------ */ -}}
{{- define "DeclareLocalTemplateParam" -}}
{{- /* ------------------------------------------------------------------ */ -}}
{{- if IsTemplateTypeParam . }}
const sem::Type* {{.Name}} = nullptr;
{{- else if IsTemplateNumberParam . }}
Number {{.Name}} = Number::invalid;
{{- else if IsTemplateEnumParam . }}
Number {{.Name}} = Number::invalid;
{{- end -}}
{{- end -}}
{{- /* ------------------------------------------------------------------ */ -}}
{{- define "DeclareLocalTemplateParamName" -}}
{{- /* ------------------------------------------------------------------ */ -}}
{{- if IsTemplateTypeParam . }}
const std::string {{.Name}} = state.TypeName();
{{- else if IsTemplateNumberParam . }}
const std::string {{.Name}} = state.NumName();
{{- else if IsTemplateEnumParam . }}
const std::string {{.Name}} = state.NumName();
{{- end -}}
{{- end -}}
{{- /* ------------------------------------------------------------------ */ -}}
{{- define "MatchTemplateParam" -}}
{{- /* ------------------------------------------------------------------ */ -}}
{{- if IsTemplateTypeParam . -}}
state.Type
{{- else if IsTemplateNumberParam . -}}
state.Num
{{- else if IsTemplateEnumParam . -}}
state.Num
{{- end -}}
{{- end -}}
{{- /* ------------------------------------------------------------------ */ -}}
{{- define "IsTemplateParamInvalid" -}}
{{- /* ------------------------------------------------------------------ */ -}}
{{- if IsTemplateTypeParam . -}}
{{.Name}} == nullptr
{{- else if IsTemplateNumberParam . -}}
!{{.Name}}.IsValid()
{{- else if IsTemplateEnumParam . -}}
!{{.Name}}.IsValid()
{{- end -}}
{{- end -}}
{{- /* ------------------------------------------------------------------ */ -}}
{{- define "AppendTemplateParamNames" -}}
{{- /* ------------------------------------------------------------------ */ -}}
{{- range $i, $ := . -}}
{{- if $i }} + ", " + {{.Name}}
{{- else }} + {{.Name}}
{{- end -}}
{{- end -}}
{{- end -}}

View File

@ -35,45 +35,45 @@ using ParameterUsage = sem::ParameterUsage;
class IntrinsicTableTest : public testing::Test, public ProgramBuilder { class IntrinsicTableTest : public testing::Test, public ProgramBuilder {
public: public:
std::unique_ptr<IntrinsicTable> table = IntrinsicTable::Create(); std::unique_ptr<IntrinsicTable> table = IntrinsicTable::Create(*this);
}; };
TEST_F(IntrinsicTableTest, MatchF32) { TEST_F(IntrinsicTableTest, MatchF32) {
auto* f32 = create<sem::F32>(); auto* f32 = create<sem::F32>();
auto result = table->Lookup(*this, IntrinsicType::kCos, {f32}, Source{}); auto* result = table->Lookup(IntrinsicType::kCos, {f32}, Source{});
ASSERT_NE(result.intrinsic, nullptr); ASSERT_NE(result, nullptr) << Diagnostics().str();
ASSERT_EQ(result.diagnostics.str(), ""); ASSERT_EQ(Diagnostics().str(), "");
EXPECT_THAT(result.intrinsic->Type(), IntrinsicType::kCos); EXPECT_THAT(result->Type(), IntrinsicType::kCos);
EXPECT_THAT(result.intrinsic->ReturnType(), f32); EXPECT_THAT(result->ReturnType(), f32);
EXPECT_THAT(result.intrinsic->Parameters(), ElementsAre(Parameter{f32})); EXPECT_THAT(result->Parameters(), ElementsAre(Parameter{f32}));
} }
TEST_F(IntrinsicTableTest, MismatchF32) { TEST_F(IntrinsicTableTest, MismatchF32) {
auto* i32 = create<sem::I32>(); auto* i32 = create<sem::I32>();
auto result = table->Lookup(*this, IntrinsicType::kCos, {i32}, Source{}); auto* result = table->Lookup(IntrinsicType::kCos, {i32}, Source{});
ASSERT_EQ(result.intrinsic, nullptr); ASSERT_EQ(result, nullptr);
ASSERT_THAT(result.diagnostics.str(), HasSubstr("no matching call")); ASSERT_THAT(Diagnostics().str(), HasSubstr("no matching call"));
} }
TEST_F(IntrinsicTableTest, MatchU32) { TEST_F(IntrinsicTableTest, MatchU32) {
auto* f32 = create<sem::F32>(); auto* f32 = create<sem::F32>();
auto* u32 = create<sem::U32>(); auto* u32 = create<sem::U32>();
auto* vec2_f32 = create<sem::Vector>(f32, 2); auto* vec2_f32 = create<sem::Vector>(f32, 2);
auto result = auto* result =
table->Lookup(*this, IntrinsicType::kUnpack2x16float, {u32}, Source{}); table->Lookup(IntrinsicType::kUnpack2x16float, {u32}, Source{});
ASSERT_NE(result.intrinsic, nullptr); ASSERT_NE(result, nullptr) << Diagnostics().str();
ASSERT_EQ(result.diagnostics.str(), ""); ASSERT_EQ(Diagnostics().str(), "");
EXPECT_THAT(result.intrinsic->Type(), IntrinsicType::kUnpack2x16float); EXPECT_THAT(result->Type(), IntrinsicType::kUnpack2x16float);
EXPECT_THAT(result.intrinsic->ReturnType(), vec2_f32); EXPECT_THAT(result->ReturnType(), vec2_f32);
EXPECT_THAT(result.intrinsic->Parameters(), ElementsAre(Parameter{u32})); EXPECT_THAT(result->Parameters(), ElementsAre(Parameter{u32}));
} }
TEST_F(IntrinsicTableTest, MismatchU32) { TEST_F(IntrinsicTableTest, MismatchU32) {
auto* f32 = create<sem::F32>(); auto* f32 = create<sem::F32>();
auto result = auto* result =
table->Lookup(*this, IntrinsicType::kUnpack2x16float, {f32}, Source{}); table->Lookup(IntrinsicType::kUnpack2x16float, {f32}, Source{});
ASSERT_EQ(result.intrinsic, nullptr); ASSERT_EQ(result, nullptr);
ASSERT_THAT(result.diagnostics.str(), HasSubstr("no matching call")); ASSERT_THAT(Diagnostics().str(), HasSubstr("no matching call"));
} }
TEST_F(IntrinsicTableTest, MatchI32) { TEST_F(IntrinsicTableTest, MatchI32) {
@ -81,13 +81,13 @@ TEST_F(IntrinsicTableTest, MatchI32) {
auto* i32 = create<sem::I32>(); auto* i32 = create<sem::I32>();
auto* vec4_f32 = create<sem::Vector>(f32, 4); auto* vec4_f32 = create<sem::Vector>(f32, 4);
auto* tex = create<sem::SampledTexture>(ast::TextureDimension::k1d, f32); auto* tex = create<sem::SampledTexture>(ast::TextureDimension::k1d, f32);
auto result = table->Lookup(*this, IntrinsicType::kTextureLoad, auto* result =
{tex, i32, i32}, Source{}); table->Lookup(IntrinsicType::kTextureLoad, {tex, i32, i32}, Source{});
ASSERT_NE(result.intrinsic, nullptr); ASSERT_NE(result, nullptr) << Diagnostics().str();
ASSERT_EQ(result.diagnostics.str(), ""); ASSERT_EQ(Diagnostics().str(), "");
EXPECT_THAT(result.intrinsic->Type(), IntrinsicType::kTextureLoad); EXPECT_THAT(result->Type(), IntrinsicType::kTextureLoad);
EXPECT_THAT(result.intrinsic->ReturnType(), vec4_f32); EXPECT_THAT(result->ReturnType(), vec4_f32);
EXPECT_THAT(result.intrinsic->Parameters(), EXPECT_THAT(result->Parameters(),
ElementsAre(Parameter{tex, ParameterUsage::kTexture}, ElementsAre(Parameter{tex, ParameterUsage::kTexture},
Parameter{i32, ParameterUsage::kCoords}, Parameter{i32, ParameterUsage::kCoords},
Parameter{i32, ParameterUsage::kLevel})); Parameter{i32, ParameterUsage::kLevel}));
@ -96,146 +96,139 @@ TEST_F(IntrinsicTableTest, MatchI32) {
TEST_F(IntrinsicTableTest, MismatchI32) { TEST_F(IntrinsicTableTest, MismatchI32) {
auto* f32 = create<sem::F32>(); auto* f32 = create<sem::F32>();
auto* tex = create<sem::SampledTexture>(ast::TextureDimension::k1d, f32); auto* tex = create<sem::SampledTexture>(ast::TextureDimension::k1d, f32);
auto result = auto* result =
table->Lookup(*this, IntrinsicType::kTextureLoad, {tex, f32}, Source{}); table->Lookup(IntrinsicType::kTextureLoad, {tex, f32}, Source{});
ASSERT_EQ(result.intrinsic, nullptr); ASSERT_EQ(result, nullptr);
ASSERT_THAT(result.diagnostics.str(), HasSubstr("no matching call")); ASSERT_THAT(Diagnostics().str(), HasSubstr("no matching call"));
} }
TEST_F(IntrinsicTableTest, MatchIU32AsI32) { TEST_F(IntrinsicTableTest, MatchIU32AsI32) {
auto* i32 = create<sem::I32>(); auto* i32 = create<sem::I32>();
auto result = auto* result = table->Lookup(IntrinsicType::kCountOneBits, {i32}, Source{});
table->Lookup(*this, IntrinsicType::kCountOneBits, {i32}, Source{}); ASSERT_NE(result, nullptr) << Diagnostics().str();
ASSERT_NE(result.intrinsic, nullptr); ASSERT_EQ(Diagnostics().str(), "");
ASSERT_EQ(result.diagnostics.str(), ""); EXPECT_THAT(result->Type(), IntrinsicType::kCountOneBits);
EXPECT_THAT(result.intrinsic->Type(), IntrinsicType::kCountOneBits); EXPECT_THAT(result->ReturnType(), i32);
EXPECT_THAT(result.intrinsic->ReturnType(), i32); EXPECT_THAT(result->Parameters(), ElementsAre(Parameter{i32}));
EXPECT_THAT(result.intrinsic->Parameters(), ElementsAre(Parameter{i32}));
} }
TEST_F(IntrinsicTableTest, MatchIU32AsU32) { TEST_F(IntrinsicTableTest, MatchIU32AsU32) {
auto* u32 = create<sem::U32>(); auto* u32 = create<sem::U32>();
auto result = auto* result = table->Lookup(IntrinsicType::kCountOneBits, {u32}, Source{});
table->Lookup(*this, IntrinsicType::kCountOneBits, {u32}, Source{}); ASSERT_NE(result, nullptr) << Diagnostics().str();
ASSERT_NE(result.intrinsic, nullptr); ASSERT_EQ(Diagnostics().str(), "");
ASSERT_EQ(result.diagnostics.str(), ""); EXPECT_THAT(result->Type(), IntrinsicType::kCountOneBits);
EXPECT_THAT(result.intrinsic->Type(), IntrinsicType::kCountOneBits); EXPECT_THAT(result->ReturnType(), u32);
EXPECT_THAT(result.intrinsic->ReturnType(), u32); EXPECT_THAT(result->Parameters(), ElementsAre(Parameter{u32}));
EXPECT_THAT(result.intrinsic->Parameters(), ElementsAre(Parameter{u32}));
} }
TEST_F(IntrinsicTableTest, MismatchIU32) { TEST_F(IntrinsicTableTest, MismatchIU32) {
auto* f32 = create<sem::F32>(); auto* f32 = create<sem::F32>();
auto result = auto* result = table->Lookup(IntrinsicType::kCountOneBits, {f32}, Source{});
table->Lookup(*this, IntrinsicType::kCountOneBits, {f32}, Source{}); ASSERT_EQ(result, nullptr);
ASSERT_EQ(result.intrinsic, nullptr); ASSERT_THAT(Diagnostics().str(), HasSubstr("no matching call"));
ASSERT_THAT(result.diagnostics.str(), HasSubstr("no matching call"));
} }
TEST_F(IntrinsicTableTest, MatchFIU32AsI32) { TEST_F(IntrinsicTableTest, MatchFIU32AsI32) {
auto* i32 = create<sem::I32>(); auto* i32 = create<sem::I32>();
auto result = auto* result =
table->Lookup(*this, IntrinsicType::kClamp, {i32, i32, i32}, Source{}); table->Lookup(IntrinsicType::kClamp, {i32, i32, i32}, Source{});
ASSERT_NE(result.intrinsic, nullptr); ASSERT_NE(result, nullptr) << Diagnostics().str();
ASSERT_EQ(result.diagnostics.str(), ""); ASSERT_EQ(Diagnostics().str(), "");
EXPECT_THAT(result.intrinsic->Type(), IntrinsicType::kClamp); EXPECT_THAT(result->Type(), IntrinsicType::kClamp);
EXPECT_THAT(result.intrinsic->ReturnType(), i32); EXPECT_THAT(result->ReturnType(), i32);
EXPECT_THAT(result.intrinsic->Parameters(), EXPECT_THAT(result->Parameters(),
ElementsAre(Parameter{i32}, Parameter{i32}, Parameter{i32})); ElementsAre(Parameter{i32}, Parameter{i32}, Parameter{i32}));
} }
TEST_F(IntrinsicTableTest, MatchFIU32AsU32) { TEST_F(IntrinsicTableTest, MatchFIU32AsU32) {
auto* u32 = create<sem::U32>(); auto* u32 = create<sem::U32>();
auto result = auto* result =
table->Lookup(*this, IntrinsicType::kClamp, {u32, u32, u32}, Source{}); table->Lookup(IntrinsicType::kClamp, {u32, u32, u32}, Source{});
ASSERT_NE(result.intrinsic, nullptr); ASSERT_NE(result, nullptr) << Diagnostics().str();
ASSERT_EQ(result.diagnostics.str(), ""); ASSERT_EQ(Diagnostics().str(), "");
EXPECT_THAT(result.intrinsic->Type(), IntrinsicType::kClamp); EXPECT_THAT(result->Type(), IntrinsicType::kClamp);
EXPECT_THAT(result.intrinsic->ReturnType(), u32); EXPECT_THAT(result->ReturnType(), u32);
EXPECT_THAT(result.intrinsic->Parameters(), EXPECT_THAT(result->Parameters(),
ElementsAre(Parameter{u32}, Parameter{u32}, Parameter{u32})); ElementsAre(Parameter{u32}, Parameter{u32}, Parameter{u32}));
} }
TEST_F(IntrinsicTableTest, MatchFIU32AsF32) { TEST_F(IntrinsicTableTest, MatchFIU32AsF32) {
auto* f32 = create<sem::F32>(); auto* f32 = create<sem::F32>();
auto result = auto* result =
table->Lookup(*this, IntrinsicType::kClamp, {f32, f32, f32}, Source{}); table->Lookup(IntrinsicType::kClamp, {f32, f32, f32}, Source{});
ASSERT_NE(result.intrinsic, nullptr); ASSERT_NE(result, nullptr) << Diagnostics().str();
ASSERT_EQ(result.diagnostics.str(), ""); ASSERT_EQ(Diagnostics().str(), "");
EXPECT_THAT(result.intrinsic->Type(), IntrinsicType::kClamp); EXPECT_THAT(result->Type(), IntrinsicType::kClamp);
EXPECT_THAT(result.intrinsic->ReturnType(), f32); EXPECT_THAT(result->ReturnType(), f32);
EXPECT_THAT(result.intrinsic->Parameters(), EXPECT_THAT(result->Parameters(),
ElementsAre(Parameter{f32}, Parameter{f32}, Parameter{f32})); ElementsAre(Parameter{f32}, Parameter{f32}, Parameter{f32}));
} }
TEST_F(IntrinsicTableTest, MismatchFIU32) { TEST_F(IntrinsicTableTest, MismatchFIU32) {
auto* bool_ = create<sem::Bool>(); auto* bool_ = create<sem::Bool>();
auto result = table->Lookup(*this, IntrinsicType::kClamp, auto* result =
{bool_, bool_, bool_}, Source{}); table->Lookup(IntrinsicType::kClamp, {bool_, bool_, bool_}, Source{});
ASSERT_EQ(result.intrinsic, nullptr); ASSERT_EQ(result, nullptr);
ASSERT_THAT(result.diagnostics.str(), HasSubstr("no matching call")); ASSERT_THAT(Diagnostics().str(), HasSubstr("no matching call"));
} }
TEST_F(IntrinsicTableTest, MatchBool) { TEST_F(IntrinsicTableTest, MatchBool) {
auto* f32 = create<sem::F32>(); auto* f32 = create<sem::F32>();
auto* bool_ = create<sem::Bool>(); auto* bool_ = create<sem::Bool>();
auto result = auto* result =
table->Lookup(*this, IntrinsicType::kSelect, {f32, f32, bool_}, Source{}); table->Lookup(IntrinsicType::kSelect, {f32, f32, bool_}, Source{});
ASSERT_NE(result.intrinsic, nullptr); ASSERT_NE(result, nullptr) << Diagnostics().str();
ASSERT_EQ(result.diagnostics.str(), ""); ASSERT_EQ(Diagnostics().str(), "");
EXPECT_THAT(result.intrinsic->Type(), IntrinsicType::kSelect); EXPECT_THAT(result->Type(), IntrinsicType::kSelect);
EXPECT_THAT(result.intrinsic->ReturnType(), f32); EXPECT_THAT(result->ReturnType(), f32);
EXPECT_THAT(result.intrinsic->Parameters(), EXPECT_THAT(result->Parameters(),
ElementsAre(Parameter{f32}, Parameter{f32}, Parameter{bool_})); ElementsAre(Parameter{f32}, Parameter{f32}, Parameter{bool_}));
} }
TEST_F(IntrinsicTableTest, MismatchBool) { TEST_F(IntrinsicTableTest, MismatchBool) {
auto* f32 = create<sem::F32>(); auto* f32 = create<sem::F32>();
auto result = auto* result =
table->Lookup(*this, IntrinsicType::kSelect, {f32, f32, f32}, Source{}); table->Lookup(IntrinsicType::kSelect, {f32, f32, f32}, Source{});
ASSERT_EQ(result.intrinsic, nullptr); ASSERT_EQ(result, nullptr);
ASSERT_THAT(result.diagnostics.str(), HasSubstr("no matching call")); ASSERT_THAT(Diagnostics().str(), HasSubstr("no matching call"));
} }
TEST_F(IntrinsicTableTest, MatchPointer) { TEST_F(IntrinsicTableTest, MatchPointer) {
auto* f32 = create<sem::F32>(); auto* f32 = create<sem::F32>();
auto* ptr = create<sem::Pointer>(f32, ast::StorageClass::kNone); auto* ptr = create<sem::Pointer>(f32, ast::StorageClass::kNone);
auto result = auto* result = table->Lookup(IntrinsicType::kModf, {f32, ptr}, Source{});
table->Lookup(*this, IntrinsicType::kModf, {f32, ptr}, Source{}); ASSERT_NE(result, nullptr) << Diagnostics().str();
ASSERT_NE(result.intrinsic, nullptr); ASSERT_EQ(Diagnostics().str(), "");
ASSERT_EQ(result.diagnostics.str(), ""); EXPECT_THAT(result->Type(), IntrinsicType::kModf);
EXPECT_THAT(result.intrinsic->Type(), IntrinsicType::kModf); EXPECT_THAT(result->ReturnType(), f32);
EXPECT_THAT(result.intrinsic->ReturnType(), f32); EXPECT_THAT(result->Parameters(),
EXPECT_THAT(result.intrinsic->Parameters(),
ElementsAre(Parameter{f32}, Parameter{ptr})); ElementsAre(Parameter{f32}, Parameter{ptr}));
} }
TEST_F(IntrinsicTableTest, MismatchPointer) { TEST_F(IntrinsicTableTest, MismatchPointer) {
auto* f32 = create<sem::F32>(); auto* f32 = create<sem::F32>();
auto result = auto* result = table->Lookup(IntrinsicType::kModf, {f32, f32}, Source{});
table->Lookup(*this, IntrinsicType::kModf, {f32, f32}, Source{}); ASSERT_EQ(result, nullptr);
ASSERT_EQ(result.intrinsic, nullptr); ASSERT_THAT(Diagnostics().str(), HasSubstr("no matching call"));
ASSERT_THAT(result.diagnostics.str(), HasSubstr("no matching call"));
} }
TEST_F(IntrinsicTableTest, MatchArray) { TEST_F(IntrinsicTableTest, MatchArray) {
auto* arr = create<sem::Array>(create<sem::U32>(), 0, 4, 4, 4, true); auto* arr = create<sem::Array>(create<sem::U32>(), 0, 4, 4, 4, true);
auto result = auto* result = table->Lookup(IntrinsicType::kArrayLength, {arr}, Source{});
table->Lookup(*this, IntrinsicType::kArrayLength, {arr}, Source{}); ASSERT_NE(result, nullptr) << Diagnostics().str();
ASSERT_NE(result.intrinsic, nullptr); ASSERT_EQ(Diagnostics().str(), "");
ASSERT_EQ(result.diagnostics.str(), ""); EXPECT_THAT(result->Type(), IntrinsicType::kArrayLength);
EXPECT_THAT(result.intrinsic->Type(), IntrinsicType::kArrayLength); EXPECT_TRUE(result->ReturnType()->Is<sem::U32>());
EXPECT_TRUE(result.intrinsic->ReturnType()->Is<sem::U32>()); ASSERT_EQ(result->Parameters().size(), 1u);
ASSERT_EQ(result.intrinsic->Parameters().size(), 1u); EXPECT_TRUE(result->Parameters()[0].type->Is<sem::Array>());
EXPECT_TRUE(result.intrinsic->Parameters()[0].type->Is<sem::Array>());
} }
TEST_F(IntrinsicTableTest, MismatchArray) { TEST_F(IntrinsicTableTest, MismatchArray) {
auto* f32 = create<sem::F32>(); auto* f32 = create<sem::F32>();
auto result = auto* result = table->Lookup(IntrinsicType::kArrayLength, {f32}, Source{});
table->Lookup(*this, IntrinsicType::kArrayLength, {f32}, Source{}); ASSERT_EQ(result, nullptr);
ASSERT_EQ(result.intrinsic, nullptr); ASSERT_THAT(Diagnostics().str(), HasSubstr("no matching call"));
ASSERT_THAT(result.diagnostics.str(), HasSubstr("no matching call"));
} }
TEST_F(IntrinsicTableTest, MatchSampler) { TEST_F(IntrinsicTableTest, MatchSampler) {
@ -244,13 +237,13 @@ TEST_F(IntrinsicTableTest, MatchSampler) {
auto* vec4_f32 = create<sem::Vector>(f32, 4); auto* vec4_f32 = create<sem::Vector>(f32, 4);
auto* tex = create<sem::SampledTexture>(ast::TextureDimension::k2d, f32); auto* tex = create<sem::SampledTexture>(ast::TextureDimension::k2d, f32);
auto* sampler = create<sem::Sampler>(ast::SamplerKind::kSampler); auto* sampler = create<sem::Sampler>(ast::SamplerKind::kSampler);
auto result = table->Lookup(*this, IntrinsicType::kTextureSample, auto* result = table->Lookup(IntrinsicType::kTextureSample,
{tex, sampler, vec2_f32}, Source{}); {tex, sampler, vec2_f32}, Source{});
ASSERT_NE(result.intrinsic, nullptr); ASSERT_NE(result, nullptr) << Diagnostics().str();
ASSERT_EQ(result.diagnostics.str(), ""); ASSERT_EQ(Diagnostics().str(), "");
EXPECT_THAT(result.intrinsic->Type(), IntrinsicType::kTextureSample); EXPECT_THAT(result->Type(), IntrinsicType::kTextureSample);
EXPECT_THAT(result.intrinsic->ReturnType(), vec4_f32); EXPECT_THAT(result->ReturnType(), vec4_f32);
EXPECT_THAT(result.intrinsic->Parameters(), EXPECT_THAT(result->Parameters(),
ElementsAre(Parameter{tex, ParameterUsage::kTexture}, ElementsAre(Parameter{tex, ParameterUsage::kTexture},
Parameter{sampler, ParameterUsage::kSampler}, Parameter{sampler, ParameterUsage::kSampler},
Parameter{vec2_f32, ParameterUsage::kCoords})); Parameter{vec2_f32, ParameterUsage::kCoords}));
@ -260,10 +253,10 @@ TEST_F(IntrinsicTableTest, MismatchSampler) {
auto* f32 = create<sem::F32>(); auto* f32 = create<sem::F32>();
auto* vec2_f32 = create<sem::Vector>(f32, 2); auto* vec2_f32 = create<sem::Vector>(f32, 2);
auto* tex = create<sem::SampledTexture>(ast::TextureDimension::k2d, f32); auto* tex = create<sem::SampledTexture>(ast::TextureDimension::k2d, f32);
auto result = table->Lookup(*this, IntrinsicType::kTextureSample, auto* result = table->Lookup(IntrinsicType::kTextureSample,
{tex, f32, vec2_f32}, Source{}); {tex, f32, vec2_f32}, Source{});
ASSERT_EQ(result.intrinsic, nullptr); ASSERT_EQ(result, nullptr);
ASSERT_THAT(result.diagnostics.str(), HasSubstr("no matching call")); ASSERT_THAT(Diagnostics().str(), HasSubstr("no matching call"));
} }
TEST_F(IntrinsicTableTest, MatchSampledTexture) { TEST_F(IntrinsicTableTest, MatchSampledTexture) {
@ -272,13 +265,13 @@ TEST_F(IntrinsicTableTest, MatchSampledTexture) {
auto* vec2_i32 = create<sem::Vector>(i32, 2); auto* vec2_i32 = create<sem::Vector>(i32, 2);
auto* vec4_f32 = create<sem::Vector>(f32, 4); auto* vec4_f32 = create<sem::Vector>(f32, 4);
auto* tex = create<sem::SampledTexture>(ast::TextureDimension::k2d, f32); auto* tex = create<sem::SampledTexture>(ast::TextureDimension::k2d, f32);
auto result = table->Lookup(*this, IntrinsicType::kTextureLoad, auto* result = table->Lookup(IntrinsicType::kTextureLoad,
{tex, vec2_i32, i32}, Source{}); {tex, vec2_i32, i32}, Source{});
ASSERT_NE(result.intrinsic, nullptr); ASSERT_NE(result, nullptr) << Diagnostics().str();
ASSERT_EQ(result.diagnostics.str(), ""); ASSERT_EQ(Diagnostics().str(), "");
EXPECT_THAT(result.intrinsic->Type(), IntrinsicType::kTextureLoad); EXPECT_THAT(result->Type(), IntrinsicType::kTextureLoad);
EXPECT_THAT(result.intrinsic->ReturnType(), vec4_f32); EXPECT_THAT(result->ReturnType(), vec4_f32);
EXPECT_THAT(result.intrinsic->Parameters(), EXPECT_THAT(result->Parameters(),
ElementsAre(Parameter{tex, ParameterUsage::kTexture}, ElementsAre(Parameter{tex, ParameterUsage::kTexture},
Parameter{vec2_i32, ParameterUsage::kCoords}, Parameter{vec2_i32, ParameterUsage::kCoords},
Parameter{i32, ParameterUsage::kLevel})); Parameter{i32, ParameterUsage::kLevel}));
@ -290,13 +283,13 @@ TEST_F(IntrinsicTableTest, MatchMultisampledTexture) {
auto* vec2_i32 = create<sem::Vector>(i32, 2); auto* vec2_i32 = create<sem::Vector>(i32, 2);
auto* vec4_f32 = create<sem::Vector>(f32, 4); auto* vec4_f32 = create<sem::Vector>(f32, 4);
auto* tex = create<sem::MultisampledTexture>(ast::TextureDimension::k2d, f32); auto* tex = create<sem::MultisampledTexture>(ast::TextureDimension::k2d, f32);
auto result = table->Lookup(*this, IntrinsicType::kTextureLoad, auto* result = table->Lookup(IntrinsicType::kTextureLoad,
{tex, vec2_i32, i32}, Source{}); {tex, vec2_i32, i32}, Source{});
ASSERT_NE(result.intrinsic, nullptr); ASSERT_NE(result, nullptr) << Diagnostics().str();
ASSERT_EQ(result.diagnostics.str(), ""); ASSERT_EQ(Diagnostics().str(), "");
EXPECT_THAT(result.intrinsic->Type(), IntrinsicType::kTextureLoad); EXPECT_THAT(result->Type(), IntrinsicType::kTextureLoad);
EXPECT_THAT(result.intrinsic->ReturnType(), vec4_f32); EXPECT_THAT(result->ReturnType(), vec4_f32);
EXPECT_THAT(result.intrinsic->Parameters(), EXPECT_THAT(result->Parameters(),
ElementsAre(Parameter{tex, ParameterUsage::kTexture}, ElementsAre(Parameter{tex, ParameterUsage::kTexture},
Parameter{vec2_i32, ParameterUsage::kCoords}, Parameter{vec2_i32, ParameterUsage::kCoords},
Parameter{i32, ParameterUsage::kSampleIndex})); Parameter{i32, ParameterUsage::kSampleIndex}));
@ -307,13 +300,13 @@ TEST_F(IntrinsicTableTest, MatchDepthTexture) {
auto* i32 = create<sem::I32>(); auto* i32 = create<sem::I32>();
auto* vec2_i32 = create<sem::Vector>(i32, 2); auto* vec2_i32 = create<sem::Vector>(i32, 2);
auto* tex = create<sem::DepthTexture>(ast::TextureDimension::k2d); auto* tex = create<sem::DepthTexture>(ast::TextureDimension::k2d);
auto result = table->Lookup(*this, IntrinsicType::kTextureLoad, auto* result = table->Lookup(IntrinsicType::kTextureLoad,
{tex, vec2_i32, i32}, Source{}); {tex, vec2_i32, i32}, Source{});
ASSERT_NE(result.intrinsic, nullptr); ASSERT_NE(result, nullptr) << Diagnostics().str();
ASSERT_EQ(result.diagnostics.str(), ""); ASSERT_EQ(Diagnostics().str(), "");
EXPECT_THAT(result.intrinsic->Type(), IntrinsicType::kTextureLoad); EXPECT_THAT(result->Type(), IntrinsicType::kTextureLoad);
EXPECT_THAT(result.intrinsic->ReturnType(), f32); EXPECT_THAT(result->ReturnType(), f32);
EXPECT_THAT(result.intrinsic->Parameters(), EXPECT_THAT(result->Parameters(),
ElementsAre(Parameter{tex, ParameterUsage::kTexture}, ElementsAre(Parameter{tex, ParameterUsage::kTexture},
Parameter{vec2_i32, ParameterUsage::kCoords}, Parameter{vec2_i32, ParameterUsage::kCoords},
Parameter{i32, ParameterUsage::kLevel})); Parameter{i32, ParameterUsage::kLevel}));
@ -325,13 +318,13 @@ TEST_F(IntrinsicTableTest, MatchExternalTexture) {
auto* vec2_i32 = create<sem::Vector>(i32, 2); auto* vec2_i32 = create<sem::Vector>(i32, 2);
auto* vec4_f32 = create<sem::Vector>(f32, 4); auto* vec4_f32 = create<sem::Vector>(f32, 4);
auto* tex = create<sem::ExternalTexture>(); auto* tex = create<sem::ExternalTexture>();
auto result = table->Lookup(*this, IntrinsicType::kTextureLoad, auto* result =
{tex, vec2_i32}, Source{}); table->Lookup(IntrinsicType::kTextureLoad, {tex, vec2_i32}, Source{});
ASSERT_NE(result.intrinsic, nullptr); ASSERT_NE(result, nullptr) << Diagnostics().str();
ASSERT_EQ(result.diagnostics.str(), ""); ASSERT_EQ(Diagnostics().str(), "");
EXPECT_THAT(result.intrinsic->Type(), IntrinsicType::kTextureLoad); EXPECT_THAT(result->Type(), IntrinsicType::kTextureLoad);
EXPECT_THAT(result.intrinsic->ReturnType(), vec4_f32); EXPECT_THAT(result->ReturnType(), vec4_f32);
EXPECT_THAT(result.intrinsic->Parameters(), EXPECT_THAT(result->Parameters(),
ElementsAre(Parameter{tex, ParameterUsage::kTexture}, ElementsAre(Parameter{tex, ParameterUsage::kTexture},
Parameter{vec2_i32, ParameterUsage::kCoords})); Parameter{vec2_i32, ParameterUsage::kCoords}));
} }
@ -342,18 +335,18 @@ TEST_F(IntrinsicTableTest, MatchROStorageTexture) {
auto* vec2_i32 = create<sem::Vector>(i32, 2); auto* vec2_i32 = create<sem::Vector>(i32, 2);
auto* vec4_f32 = create<sem::Vector>(f32, 4); auto* vec4_f32 = create<sem::Vector>(f32, 4);
auto* subtype = auto* subtype =
sem::StorageTexture::SubtypeFor(ast::ImageFormat::kR16Float, Types()); sem::StorageTexture::SubtypeFor(ast::ImageFormat::kR32Float, Types());
auto* tex = create<sem::StorageTexture>(ast::TextureDimension::k2d, auto* tex = create<sem::StorageTexture>(ast::TextureDimension::k2d,
ast::ImageFormat::kR16Float, ast::ImageFormat::kR32Float,
ast::AccessControl::kRead, subtype); ast::AccessControl::kRead, subtype);
auto result = table->Lookup(*this, IntrinsicType::kTextureLoad, auto* result =
{tex, vec2_i32}, Source{}); table->Lookup(IntrinsicType::kTextureLoad, {tex, vec2_i32}, Source{});
ASSERT_NE(result.intrinsic, nullptr); ASSERT_NE(result, nullptr) << Diagnostics().str();
ASSERT_EQ(result.diagnostics.str(), ""); ASSERT_EQ(Diagnostics().str(), "");
EXPECT_THAT(result.intrinsic->Type(), IntrinsicType::kTextureLoad); EXPECT_THAT(result->Type(), IntrinsicType::kTextureLoad);
EXPECT_THAT(result.intrinsic->ReturnType(), vec4_f32); EXPECT_THAT(result->ReturnType(), vec4_f32);
EXPECT_THAT(result.intrinsic->Parameters(), EXPECT_THAT(result->Parameters(),
ElementsAre(Parameter{tex, ParameterUsage::kTexture}, ElementsAre(Parameter{tex, ParameterUsage::kTexture},
Parameter{vec2_i32, ParameterUsage::kCoords})); Parameter{vec2_i32, ParameterUsage::kCoords}));
} }
@ -364,18 +357,18 @@ TEST_F(IntrinsicTableTest, MatchWOStorageTexture) {
auto* vec2_i32 = create<sem::Vector>(i32, 2); auto* vec2_i32 = create<sem::Vector>(i32, 2);
auto* vec4_f32 = create<sem::Vector>(f32, 4); auto* vec4_f32 = create<sem::Vector>(f32, 4);
auto* subtype = auto* subtype =
sem::StorageTexture::SubtypeFor(ast::ImageFormat::kR16Float, Types()); sem::StorageTexture::SubtypeFor(ast::ImageFormat::kR32Float, Types());
auto* tex = create<sem::StorageTexture>(ast::TextureDimension::k2d, auto* tex = create<sem::StorageTexture>(ast::TextureDimension::k2d,
ast::ImageFormat::kR16Float, ast::ImageFormat::kR32Float,
ast::AccessControl::kWrite, subtype); ast::AccessControl::kWrite, subtype);
auto result = table->Lookup(*this, IntrinsicType::kTextureStore, auto* result = table->Lookup(IntrinsicType::kTextureStore,
{tex, vec2_i32, vec4_f32}, Source{}); {tex, vec2_i32, vec4_f32}, Source{});
ASSERT_NE(result.intrinsic, nullptr); ASSERT_NE(result, nullptr) << Diagnostics().str();
ASSERT_EQ(result.diagnostics.str(), ""); ASSERT_EQ(Diagnostics().str(), "");
EXPECT_THAT(result.intrinsic->Type(), IntrinsicType::kTextureStore); EXPECT_THAT(result->Type(), IntrinsicType::kTextureStore);
EXPECT_TRUE(result.intrinsic->ReturnType()->Is<sem::Void>()); EXPECT_TRUE(result->ReturnType()->Is<sem::Void>());
EXPECT_THAT(result.intrinsic->Parameters(), EXPECT_THAT(result->Parameters(),
ElementsAre(Parameter{tex, ParameterUsage::kTexture}, ElementsAre(Parameter{tex, ParameterUsage::kTexture},
Parameter{vec2_i32, ParameterUsage::kCoords}, Parameter{vec2_i32, ParameterUsage::kCoords},
Parameter{vec4_f32, ParameterUsage::kValue})); Parameter{vec4_f32, ParameterUsage::kValue}));
@ -385,55 +378,55 @@ TEST_F(IntrinsicTableTest, MismatchTexture) {
auto* f32 = create<sem::F32>(); auto* f32 = create<sem::F32>();
auto* i32 = create<sem::I32>(); auto* i32 = create<sem::I32>();
auto* vec2_i32 = create<sem::Vector>(i32, 2); auto* vec2_i32 = create<sem::Vector>(i32, 2);
auto result = table->Lookup(*this, IntrinsicType::kTextureLoad, auto* result =
{f32, vec2_i32}, Source{}); table->Lookup(IntrinsicType::kTextureLoad, {f32, vec2_i32}, Source{});
ASSERT_EQ(result.intrinsic, nullptr); ASSERT_EQ(result, nullptr);
ASSERT_THAT(result.diagnostics.str(), HasSubstr("no matching call")); ASSERT_THAT(Diagnostics().str(), HasSubstr("no matching call"));
} }
TEST_F(IntrinsicTableTest, ImplicitLoadOnReference) { TEST_F(IntrinsicTableTest, ImplicitLoadOnReference) {
auto* f32 = create<sem::F32>(); auto* f32 = create<sem::F32>();
auto result = table->Lookup( auto* result = table->Lookup(
*this, IntrinsicType::kCos, IntrinsicType::kCos,
{create<sem::Reference>(f32, ast::StorageClass::kNone)}, Source{}); {create<sem::Reference>(f32, ast::StorageClass::kNone)}, Source{});
ASSERT_NE(result.intrinsic, nullptr); ASSERT_NE(result, nullptr) << Diagnostics().str();
ASSERT_EQ(result.diagnostics.str(), ""); ASSERT_EQ(Diagnostics().str(), "");
EXPECT_THAT(result.intrinsic->Type(), IntrinsicType::kCos); EXPECT_THAT(result->Type(), IntrinsicType::kCos);
EXPECT_THAT(result.intrinsic->ReturnType(), f32); EXPECT_THAT(result->ReturnType(), f32);
EXPECT_THAT(result.intrinsic->Parameters(), ElementsAre(Parameter{f32})); EXPECT_THAT(result->Parameters(), ElementsAre(Parameter{f32}));
} }
TEST_F(IntrinsicTableTest, MatchOpenType) { TEST_F(IntrinsicTableTest, MatchOpenType) {
auto* f32 = create<sem::F32>(); auto* f32 = create<sem::F32>();
auto result = auto* result =
table->Lookup(*this, IntrinsicType::kClamp, {f32, f32, f32}, Source{}); table->Lookup(IntrinsicType::kClamp, {f32, f32, f32}, Source{});
ASSERT_NE(result.intrinsic, nullptr); ASSERT_NE(result, nullptr) << Diagnostics().str();
ASSERT_EQ(result.diagnostics.str(), ""); ASSERT_EQ(Diagnostics().str(), "");
EXPECT_THAT(result.intrinsic->Type(), IntrinsicType::kClamp); EXPECT_THAT(result->Type(), IntrinsicType::kClamp);
EXPECT_THAT(result.intrinsic->ReturnType(), f32); EXPECT_THAT(result->ReturnType(), f32);
EXPECT_THAT(result.intrinsic->Parameters(), EXPECT_THAT(result->Parameters(),
ElementsAre(Parameter{f32}, Parameter{f32}, Parameter{f32})); ElementsAre(Parameter{f32}, Parameter{f32}, Parameter{f32}));
} }
TEST_F(IntrinsicTableTest, MismatchOpenType) { TEST_F(IntrinsicTableTest, MismatchOpenType) {
auto* f32 = create<sem::F32>(); auto* f32 = create<sem::F32>();
auto* u32 = create<sem::U32>(); auto* u32 = create<sem::U32>();
auto result = auto* result =
table->Lookup(*this, IntrinsicType::kClamp, {f32, u32, f32}, Source{}); table->Lookup(IntrinsicType::kClamp, {f32, u32, f32}, Source{});
ASSERT_EQ(result.intrinsic, nullptr); ASSERT_EQ(result, nullptr);
ASSERT_THAT(result.diagnostics.str(), HasSubstr("no matching call")); ASSERT_THAT(Diagnostics().str(), HasSubstr("no matching call"));
} }
TEST_F(IntrinsicTableTest, MatchOpenSizeVector) { TEST_F(IntrinsicTableTest, MatchOpenSizeVector) {
auto* f32 = create<sem::F32>(); auto* f32 = create<sem::F32>();
auto* vec2_f32 = create<sem::Vector>(f32, 2); auto* vec2_f32 = create<sem::Vector>(f32, 2);
auto result = table->Lookup(*this, IntrinsicType::kClamp, auto* result = table->Lookup(IntrinsicType::kClamp,
{vec2_f32, vec2_f32, vec2_f32}, Source{}); {vec2_f32, vec2_f32, vec2_f32}, Source{});
ASSERT_NE(result.intrinsic, nullptr); ASSERT_NE(result, nullptr) << Diagnostics().str();
ASSERT_EQ(result.diagnostics.str(), ""); ASSERT_EQ(Diagnostics().str(), "");
EXPECT_THAT(result.intrinsic->Type(), IntrinsicType::kClamp); EXPECT_THAT(result->Type(), IntrinsicType::kClamp);
EXPECT_THAT(result.intrinsic->ReturnType(), vec2_f32); EXPECT_THAT(result->ReturnType(), vec2_f32);
EXPECT_THAT(result.intrinsic->Parameters(), EXPECT_THAT(result->Parameters(),
ElementsAre(Parameter{vec2_f32}, Parameter{vec2_f32}, ElementsAre(Parameter{vec2_f32}, Parameter{vec2_f32},
Parameter{vec2_f32})); Parameter{vec2_f32}));
} }
@ -442,110 +435,106 @@ TEST_F(IntrinsicTableTest, MismatchOpenSizeVector) {
auto* f32 = create<sem::F32>(); auto* f32 = create<sem::F32>();
auto* u32 = create<sem::U32>(); auto* u32 = create<sem::U32>();
auto* vec2_f32 = create<sem::Vector>(f32, 2); auto* vec2_f32 = create<sem::Vector>(f32, 2);
auto result = table->Lookup(*this, IntrinsicType::kClamp, auto* result =
{vec2_f32, u32, vec2_f32}, Source{}); table->Lookup(IntrinsicType::kClamp, {vec2_f32, u32, vec2_f32}, Source{});
ASSERT_EQ(result.intrinsic, nullptr); ASSERT_EQ(result, nullptr);
ASSERT_THAT(result.diagnostics.str(), HasSubstr("no matching call")); ASSERT_THAT(Diagnostics().str(), HasSubstr("no matching call"));
} }
TEST_F(IntrinsicTableTest, MatchOpenSizeMatrix) { TEST_F(IntrinsicTableTest, MatchOpenSizeMatrix) {
auto* f32 = create<sem::F32>(); auto* f32 = create<sem::F32>();
auto* vec3_f32 = create<sem::Vector>(f32, 3); auto* vec3_f32 = create<sem::Vector>(f32, 3);
auto* mat3_f32 = create<sem::Matrix>(vec3_f32, 3); auto* mat3_f32 = create<sem::Matrix>(vec3_f32, 3);
auto result = auto* result =
table->Lookup(*this, IntrinsicType::kDeterminant, {mat3_f32}, Source{}); table->Lookup(IntrinsicType::kDeterminant, {mat3_f32}, Source{});
ASSERT_NE(result.intrinsic, nullptr); ASSERT_NE(result, nullptr) << Diagnostics().str();
ASSERT_EQ(result.diagnostics.str(), ""); ASSERT_EQ(Diagnostics().str(), "");
EXPECT_THAT(result.intrinsic->Type(), IntrinsicType::kDeterminant); EXPECT_THAT(result->Type(), IntrinsicType::kDeterminant);
EXPECT_THAT(result.intrinsic->ReturnType(), f32); EXPECT_THAT(result->ReturnType(), f32);
EXPECT_THAT(result.intrinsic->Parameters(), ElementsAre(Parameter{mat3_f32})); EXPECT_THAT(result->Parameters(), ElementsAre(Parameter{mat3_f32}));
} }
TEST_F(IntrinsicTableTest, MismatchOpenSizeMatrix) { TEST_F(IntrinsicTableTest, MismatchOpenSizeMatrix) {
auto* f32 = create<sem::F32>(); auto* f32 = create<sem::F32>();
auto* vec2_f32 = create<sem::Vector>(f32, 2); auto* vec2_f32 = create<sem::Vector>(f32, 2);
auto* mat3x2_f32 = create<sem::Matrix>(vec2_f32, 3); auto* mat3x2_f32 = create<sem::Matrix>(vec2_f32, 3);
auto result = auto* result =
table->Lookup(*this, IntrinsicType::kDeterminant, {mat3x2_f32}, Source{}); table->Lookup(IntrinsicType::kDeterminant, {mat3x2_f32}, Source{});
ASSERT_EQ(result.intrinsic, nullptr); ASSERT_EQ(result, nullptr);
ASSERT_THAT(result.diagnostics.str(), HasSubstr("no matching call")); ASSERT_THAT(Diagnostics().str(), HasSubstr("no matching call"));
} }
TEST_F(IntrinsicTableTest, OverloadOrderByNumberOfParameters) { TEST_F(IntrinsicTableTest, OverloadOrderByNumberOfParameters) {
// None of the arguments match, so expect the overloads with 2 parameters to // None of the arguments match, so expect the overloads with 2 parameters to
// come first // come first
auto* bool_ = create<sem::Bool>(); auto* bool_ = create<sem::Bool>();
auto result = table->Lookup(*this, IntrinsicType::kTextureDimensions, table->Lookup(IntrinsicType::kTextureDimensions, {bool_, bool_}, Source{});
{bool_, bool_}, Source{}); ASSERT_EQ(Diagnostics().str(),
ASSERT_EQ(result.diagnostics.str(),
R"(error: no matching call to textureDimensions(bool, bool) R"(error: no matching call to textureDimensions(bool, bool)
26 candidate functions: 25 candidate functions:
textureDimensions(texture : texture_2d<T>, level : i32) -> vec2<i32> textureDimensions(texture: texture_2d<T>, level: i32) -> vec2<i32>
textureDimensions(texture : texture_2d_array<T>, level : i32) -> vec2<i32> textureDimensions(texture: texture_2d_array<T>, level: i32) -> vec2<i32>
textureDimensions(texture : texture_3d<T>, level : i32) -> vec3<i32> textureDimensions(texture: texture_3d<T>, level: i32) -> vec3<i32>
textureDimensions(texture : texture_cube<T>, level : i32) -> vec3<i32> textureDimensions(texture: texture_cube<T>, level: i32) -> vec3<i32>
textureDimensions(texture : texture_cube_array<T>, level : i32) -> vec3<i32> textureDimensions(texture: texture_cube_array<T>, level: i32) -> vec3<i32>
textureDimensions(texture : texture_depth_2d, level : i32) -> vec2<i32> textureDimensions(texture: texture_depth_2d, level: i32) -> vec2<i32>
textureDimensions(texture : texture_depth_2d_array, level : i32) -> vec2<i32> textureDimensions(texture: texture_depth_2d_array, level: i32) -> vec2<i32>
textureDimensions(texture : texture_depth_cube, level : i32) -> vec3<i32> textureDimensions(texture: texture_depth_cube, level: i32) -> vec3<i32>
textureDimensions(texture : texture_depth_cube_array, level : i32) -> vec3<i32> textureDimensions(texture: texture_depth_cube_array, level: i32) -> vec3<i32>
textureDimensions(texture : texture_1d<T>) -> i32 textureDimensions(texture: texture_1d<T>) -> i32
textureDimensions(texture : texture_2d<T>) -> vec2<i32> textureDimensions(texture: texture_2d<T>) -> vec2<i32>
textureDimensions(texture : texture_2d_array<T>) -> vec2<i32> textureDimensions(texture: texture_2d_array<T>) -> vec2<i32>
textureDimensions(texture : texture_3d<T>) -> vec3<i32> textureDimensions(texture: texture_3d<T>) -> vec3<i32>
textureDimensions(texture : texture_cube<T>) -> vec3<i32> textureDimensions(texture: texture_cube<T>) -> vec3<i32>
textureDimensions(texture : texture_cube_array<T>) -> vec3<i32> textureDimensions(texture: texture_cube_array<T>) -> vec3<i32>
textureDimensions(texture : texture_multisampled_2d<T>) -> vec2<i32> textureDimensions(texture: texture_multisampled_2d<T>) -> vec2<i32>
textureDimensions(texture : texture_multisampled_2d_array<T>) -> vec2<i32> textureDimensions(texture: texture_depth_2d) -> vec2<i32>
textureDimensions(texture : texture_depth_2d) -> vec2<i32> textureDimensions(texture: texture_depth_2d_array) -> vec2<i32>
textureDimensions(texture : texture_depth_2d_array) -> vec2<i32> textureDimensions(texture: texture_depth_cube) -> vec3<i32>
textureDimensions(texture : texture_depth_cube) -> vec3<i32> textureDimensions(texture: texture_depth_cube_array) -> vec3<i32>
textureDimensions(texture : texture_depth_cube_array) -> vec3<i32> textureDimensions(texture: texture_storage_1d<F, A>) -> i32
textureDimensions(texture : texture_storage_1d<F, A>) -> i32 textureDimensions(texture: texture_storage_2d<F, A>) -> vec2<i32>
textureDimensions(texture : texture_storage_2d<F, A>) -> vec2<i32> textureDimensions(texture: texture_storage_2d_array<F, A>) -> vec2<i32>
textureDimensions(texture : texture_storage_2d_array<F, A>) -> vec2<i32> textureDimensions(texture: texture_storage_3d<F, A>) -> vec3<i32>
textureDimensions(texture : texture_storage_3d<F, A>) -> vec3<i32> textureDimensions(texture: texture_external) -> vec2<i32>
textureDimensions(texture : texture_external) -> vec2<i32>
)"); )");
} }
TEST_F(IntrinsicTableTest, OverloadOrderByMatchingParameter) { TEST_F(IntrinsicTableTest, OverloadOrderByMatchingParameter) {
auto* tex = create<sem::DepthTexture>(ast::TextureDimension::k2d); auto* tex = create<sem::DepthTexture>(ast::TextureDimension::k2d);
auto* bool_ = create<sem::Bool>(); auto* bool_ = create<sem::Bool>();
auto result = table->Lookup(*this, IntrinsicType::kTextureDimensions, table->Lookup(IntrinsicType::kTextureDimensions, {tex, bool_}, Source{});
{tex, bool_}, Source{});
ASSERT_EQ( ASSERT_EQ(
result.diagnostics.str(), Diagnostics().str(),
R"(error: no matching call to textureDimensions(texture_depth_2d, bool) R"(error: no matching call to textureDimensions(texture_depth_2d, bool)
26 candidate functions: 25 candidate functions:
textureDimensions(texture : texture_depth_2d, level : i32) -> vec2<i32> textureDimensions(texture: texture_depth_2d, level: i32) -> vec2<i32>
textureDimensions(texture : texture_depth_2d) -> vec2<i32> textureDimensions(texture: texture_depth_2d) -> vec2<i32>
textureDimensions(texture : texture_2d<T>, level : i32) -> vec2<i32> textureDimensions(texture: texture_2d<T>, level: i32) -> vec2<i32>
textureDimensions(texture : texture_2d_array<T>, level : i32) -> vec2<i32> textureDimensions(texture: texture_2d_array<T>, level: i32) -> vec2<i32>
textureDimensions(texture : texture_3d<T>, level : i32) -> vec3<i32> textureDimensions(texture: texture_3d<T>, level: i32) -> vec3<i32>
textureDimensions(texture : texture_cube<T>, level : i32) -> vec3<i32> textureDimensions(texture: texture_cube<T>, level: i32) -> vec3<i32>
textureDimensions(texture : texture_cube_array<T>, level : i32) -> vec3<i32> textureDimensions(texture: texture_cube_array<T>, level: i32) -> vec3<i32>
textureDimensions(texture : texture_depth_2d_array, level : i32) -> vec2<i32> textureDimensions(texture: texture_depth_2d_array, level: i32) -> vec2<i32>
textureDimensions(texture : texture_depth_cube, level : i32) -> vec3<i32> textureDimensions(texture: texture_depth_cube, level: i32) -> vec3<i32>
textureDimensions(texture : texture_depth_cube_array, level : i32) -> vec3<i32> textureDimensions(texture: texture_depth_cube_array, level: i32) -> vec3<i32>
textureDimensions(texture : texture_1d<T>) -> i32 textureDimensions(texture: texture_1d<T>) -> i32
textureDimensions(texture : texture_2d<T>) -> vec2<i32> textureDimensions(texture: texture_2d<T>) -> vec2<i32>
textureDimensions(texture : texture_2d_array<T>) -> vec2<i32> textureDimensions(texture: texture_2d_array<T>) -> vec2<i32>
textureDimensions(texture : texture_3d<T>) -> vec3<i32> textureDimensions(texture: texture_3d<T>) -> vec3<i32>
textureDimensions(texture : texture_cube<T>) -> vec3<i32> textureDimensions(texture: texture_cube<T>) -> vec3<i32>
textureDimensions(texture : texture_cube_array<T>) -> vec3<i32> textureDimensions(texture: texture_cube_array<T>) -> vec3<i32>
textureDimensions(texture : texture_multisampled_2d<T>) -> vec2<i32> textureDimensions(texture: texture_multisampled_2d<T>) -> vec2<i32>
textureDimensions(texture : texture_multisampled_2d_array<T>) -> vec2<i32> textureDimensions(texture: texture_depth_2d_array) -> vec2<i32>
textureDimensions(texture : texture_depth_2d_array) -> vec2<i32> textureDimensions(texture: texture_depth_cube) -> vec3<i32>
textureDimensions(texture : texture_depth_cube) -> vec3<i32> textureDimensions(texture: texture_depth_cube_array) -> vec3<i32>
textureDimensions(texture : texture_depth_cube_array) -> vec3<i32> textureDimensions(texture: texture_storage_1d<F, A>) -> i32
textureDimensions(texture : texture_storage_1d<F, A>) -> i32 textureDimensions(texture: texture_storage_2d<F, A>) -> vec2<i32>
textureDimensions(texture : texture_storage_2d<F, A>) -> vec2<i32> textureDimensions(texture: texture_storage_2d_array<F, A>) -> vec2<i32>
textureDimensions(texture : texture_storage_2d_array<F, A>) -> vec2<i32> textureDimensions(texture: texture_storage_3d<F, A>) -> vec3<i32>
textureDimensions(texture : texture_storage_3d<F, A>) -> vec3<i32> textureDimensions(texture: texture_external) -> vec2<i32>
textureDimensions(texture : texture_external) -> vec2<i32>
)"); )");
} }

View File

@ -468,8 +468,8 @@ TEST_F(ResolverIntrinsicTest, Select_Error_NoParams) {
R"(error: no matching call to select() R"(error: no matching call to select()
2 candidate functions: 2 candidate functions:
select(T, T, bool) -> T where: T is scalar select(T, T, bool) -> T where: T is f32, i32, u32 or bool
select(vecN<T>, vecN<T>, vecN<bool>) -> vecN<T> where: T is scalar select(vecN<T>, vecN<T>, vecN<bool>) -> vecN<T> where: T is f32, i32, u32 or bool
)"); )");
} }
@ -483,8 +483,8 @@ TEST_F(ResolverIntrinsicTest, Select_Error_SelectorInt) {
R"(error: no matching call to select(i32, i32, i32) R"(error: no matching call to select(i32, i32, i32)
2 candidate functions: 2 candidate functions:
select(T, T, bool) -> T where: T is scalar select(T, T, bool) -> T where: T is f32, i32, u32 or bool
select(vecN<T>, vecN<T>, vecN<bool>) -> vecN<T> where: T is scalar select(vecN<T>, vecN<T>, vecN<bool>) -> vecN<T> where: T is f32, i32, u32 or bool
)"); )");
} }
@ -500,8 +500,8 @@ TEST_F(ResolverIntrinsicTest, Select_Error_Matrix) {
R"(error: no matching call to select(mat2x2<f32>, mat2x2<f32>, bool) R"(error: no matching call to select(mat2x2<f32>, mat2x2<f32>, bool)
2 candidate functions: 2 candidate functions:
select(T, T, bool) -> T where: T is scalar select(T, T, bool) -> T where: T is f32, i32, u32 or bool
select(vecN<T>, vecN<T>, vecN<bool>) -> vecN<T> where: T is scalar select(vecN<T>, vecN<T>, vecN<bool>) -> vecN<T> where: T is f32, i32, u32 or bool
)"); )");
} }
@ -515,8 +515,8 @@ TEST_F(ResolverIntrinsicTest, Select_Error_MismatchTypes) {
R"(error: no matching call to select(f32, vec2<f32>, bool) R"(error: no matching call to select(f32, vec2<f32>, bool)
2 candidate functions: 2 candidate functions:
select(T, T, bool) -> T where: T is scalar select(T, T, bool) -> T where: T is f32, i32, u32 or bool
select(vecN<T>, vecN<T>, vecN<bool>) -> vecN<T> where: T is scalar select(vecN<T>, vecN<T>, vecN<bool>) -> vecN<T> where: T is f32, i32, u32 or bool
)"); )");
} }
@ -531,8 +531,8 @@ TEST_F(ResolverIntrinsicTest, Select_Error_MismatchVectorSize) {
R"(error: no matching call to select(vec2<f32>, vec3<f32>, bool) R"(error: no matching call to select(vec2<f32>, vec3<f32>, bool)
2 candidate functions: 2 candidate functions:
select(T, T, bool) -> T where: T is scalar select(T, T, bool) -> T where: T is f32, i32, u32 or bool
select(vecN<T>, vecN<T>, vecN<bool>) -> vecN<T> where: T is scalar select(vecN<T>, vecN<T>, vecN<bool>) -> vecN<T> where: T is f32, i32, u32 or bool
)"); )");
} }

View File

@ -126,7 +126,7 @@ bool IsValidationDisabled(const ast::DecorationList& decorations,
Resolver::Resolver(ProgramBuilder* builder) Resolver::Resolver(ProgramBuilder* builder)
: builder_(builder), : builder_(builder),
diagnostics_(builder->Diagnostics()), diagnostics_(builder->Diagnostics()),
intrinsic_table_(IntrinsicTable::Create()) {} intrinsic_table_(IntrinsicTable::Create(*builder)) {}
Resolver::~Resolver() = default; Resolver::~Resolver() = default;
@ -1694,17 +1694,15 @@ bool Resolver::IntrinsicCall(ast::CallExpression* call,
arg_tys.emplace_back(TypeOf(expr)); arg_tys.emplace_back(TypeOf(expr));
} }
auto result = intrinsic_table_->Lookup(*builder_, intrinsic_type, arg_tys, auto* result =
call->source()); intrinsic_table_->Lookup(intrinsic_type, arg_tys, call->source());
if (!result.intrinsic) { if (!result) {
// Intrinsic lookup failed.
diagnostics_.add(result.diagnostics);
return false; return false;
} }
builder_->Sem().Add(call, builder_->create<sem::Call>(call, result.intrinsic, builder_->Sem().Add(
current_statement_)); call, builder_->create<sem::Call>(call, result, current_statement_));
SetType(call, result.intrinsic->ReturnType()); SetType(call, result->ReturnType());
return true; return true;
} }

View File

@ -127,7 +127,7 @@ type Parameter struct {
// Format implements the fmt.Formatter interface // Format implements the fmt.Formatter interface
func (p Parameter) Format(w fmt.State, verb rune) { func (p Parameter) Format(w fmt.State, verb rune) {
if p.Name != "" { if p.Name != "" {
fmt.Fprintf(w, "%v ", p.Name) fmt.Fprintf(w, "%v: ", p.Name)
} }
p.Type.Format(w, verb) p.Type.Format(w, verb)
} }

View File

@ -26,7 +26,10 @@ import (
) )
type generator struct { type generator struct {
s *sem.Sem s *sem.Sem
cached struct {
intrinsicTable *IntrinsicTable // lazily built by intrinsicTable()
}
} }
// Generate executes the template tmpl using the provided semantic // Generate executes the template tmpl using the provided semantic
@ -52,6 +55,7 @@ func (g *generator) generate(tmpl string, w io.Writer) error {
"IsTemplateEnumParam": is(&sem.TemplateEnumParam{}), "IsTemplateEnumParam": is(&sem.TemplateEnumParam{}),
"IsFirstIn": isFirstIn, "IsFirstIn": isFirstIn,
"IsLastIn": isLastIn, "IsLastIn": isLastIn,
"IntrinsicTable": g.intrinsicTable,
}). }).
Option("missingkey=error"). Option("missingkey=error").
Parse(tmpl) Parse(tmpl)
@ -63,6 +67,19 @@ func (g *generator) generate(tmpl string, w io.Writer) error {
}) })
} }
// intrinsicTable lazily calls and returns the result of buildIntrinsicTable(),
// caching the result for repeated calls.
func (g *generator) intrinsicTable() (*IntrinsicTable, error) {
if g.cached.intrinsicTable == nil {
var err error
g.cached.intrinsicTable, err = buildIntrinsicTable(g.s)
if err != nil {
return nil, err
}
}
return g.cached.intrinsicTable, nil
}
// Map is a simple generic key-value map, which can be used in the template // Map is a simple generic key-value map, which can be used in the template
type Map map[interface{}]interface{} type Map map[interface{}]interface{}

View File

@ -0,0 +1,381 @@
// 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.
package gen
import (
"fmt"
"dawn.googlesource.com/tint/tools/src/cmd/intrinsic-gen/sem"
"dawn.googlesource.com/tint/tools/src/list"
"dawn.googlesource.com/tint/tools/src/lut"
)
// IntrinsicTable holds data specific to the intrinsic_table.inl.tmpl template
type IntrinsicTable struct {
// The semantic info
Sem *sem.Sem
// TMatchers are all the sem.OpenType, sem.Type and sem.TypeMatchers.
// These are all implemented by classes deriving from tint::TypeMatcher
TMatchers []sem.Named
TMatcherIndex map[sem.Named]int // [object -> index] in TMatcher
// NMatchers are all the sem.OpenNumber and sem.EnumMatchers.
// These are all implemented by classes deriving from tint::NumberMatcher
NMatchers []sem.Named
NMatcherIndex map[sem.Named]int // [object -> index] in NMatchers
MatcherIndices []int // kMatcherIndices table content
OpenTypes []OpenType // kOpenTypes table content
OpenNumbers []OpenNumber // kOpenNumbers table content
Parameters []Parameter // kParameters table content
Overloads []Overload // kOverloads table content
Functions []Function // kIntrinsics table content
}
// OpenType is used to create the C++ OpenTypeInfo structure
type OpenType struct {
// Name of the open type (e.g. 'T')
Name string
// Optional type matcher constraint.
// Either an index in Matchers::type, or -1
MatcherIndex int
}
// OpenNumber is used to create the C++ OpenNumberInfo structure
type OpenNumber struct {
// Name of the open number (e.g. 'N')
Name string
// Optional type matcher constraint.
// Either an index in Matchers::type, or -1
MatcherIndex int
}
// Parameter is used to create the C++ ParameterInfo structure
type Parameter struct {
// The parameter usage (parameter name)
Usage string
// Index into IntrinsicTable.MatcherIndices, beginning the list of matchers
// required to match the parameter type. The matcher indices index
// into IntrinsicTable::TMatchers and / or IntrinsicTable::NMatchers.
// These indices are consumed by the matchers themselves.
// The first index is always a TypeMatcher.
MatcherIndicesOffset *int
}
// Overload is used to create the C++ OverloadInfo structure
type Overload struct {
// Total number of parameters for the overload
NumParameters int
// Total number of open types for the overload
NumOpenTypes int
// Total number of open numbers for the overload
NumOpenNumbers int
// Index to the first open type in IntrinsicTable.OpenTypes
OpenTypesOffset *int
// Index to the first open number in IntrinsicTable.OpenNumbers
OpenNumbersOffset *int
// Index to the first parameter in IntrinsicTable.Parameters
ParametersOffset *int
// Index into IntrinsicTable.MatcherIndices, beginning the list of matchers
// required to match the return type. The matcher indices index
// into IntrinsicTable::TMatchers and / or IntrinsicTable::NMatchers.
// These indices are consumed by the matchers themselves.
// The first index is always a TypeMatcher.
ReturnMatcherIndicesOffset *int
}
// Function is used to create the C++ IntrinsicInfo structure
type Function struct {
OverloadDescriptions []string
NumOverloads int
OverloadsOffset *int
}
// Helper for building the IntrinsicTable
type intrinsicTableBuilder struct {
// The output of the builder
IntrinsicTable
// Lookup tables.
// These are packed (compressed) once all the entries have been added.
lut struct {
matcherIndices lut.LUT
openTypes lut.LUT
openNumbers lut.LUT
parameters lut.LUT
overloads lut.LUT
}
}
// Helper for building a single overload
type overloadBuilder struct {
*intrinsicTableBuilder
// Maps TemplateParam to index in openTypes
openTypeIndex map[sem.TemplateParam]int
// Maps TemplateParam to index in openNumbers
openNumberIndex map[sem.TemplateParam]int
// Open types used by the overload
openTypes []OpenType
// Open numbers used by the overload
openNumbers []OpenNumber
// All parameters declared by the overload
parameters []Parameter
// Index into IntrinsicTable.MatcherIndices, beginning the list of matchers
// required to match the return type. The matcher indices index
// into IntrinsicTable::TMatchers and / or IntrinsicTable::NMatchers.
// These indices are consumed by the matchers themselves.
// The first index is always a TypeMatcher.
returnTypeMatcherIndicesOffset *int
}
// layoutMatchers assigns each of the TMatchers and NMatchers a unique index
// in the C++ Matchers::type and Matchers::number arrays, respectively.
func (b *intrinsicTableBuilder) layoutMatchers(s *sem.Sem) {
// First MaxOpenTypes of TMatchers are open types
b.TMatchers = make([]sem.Named, s.MaxOpenTypes)
for _, m := range s.Types {
b.TMatcherIndex[m] = len(b.TMatchers)
b.TMatchers = append(b.TMatchers, m)
}
for _, m := range s.TypeMatchers {
b.TMatcherIndex[m] = len(b.TMatchers)
b.TMatchers = append(b.TMatchers, m)
}
// First MaxOpenNumbers of NMatchers are open numbers
b.NMatchers = make([]sem.Named, s.MaxOpenNumbers)
for _, m := range s.EnumMatchers {
b.NMatcherIndex[m] = len(b.NMatchers)
b.NMatchers = append(b.NMatchers, m)
}
}
// buildOverload constructs an Overload for a sem.Overload
func (b *intrinsicTableBuilder) buildOverload(o *sem.Overload) (Overload, error) {
ob := overloadBuilder{
intrinsicTableBuilder: b,
openTypeIndex: map[sem.TemplateParam]int{},
openNumberIndex: map[sem.TemplateParam]int{},
}
if err := ob.buildOpenTypes(o); err != nil {
return Overload{}, err
}
if err := ob.buildOpenNumbers(o); err != nil {
return Overload{}, err
}
if err := ob.buildParameters(o); err != nil {
return Overload{}, err
}
if err := ob.buildReturnType(o); err != nil {
return Overload{}, err
}
return Overload{
NumParameters: len(ob.parameters),
NumOpenTypes: len(ob.openTypes),
NumOpenNumbers: len(ob.openNumbers),
OpenTypesOffset: b.lut.openTypes.Add(ob.openTypes),
OpenNumbersOffset: b.lut.openNumbers.Add(ob.openNumbers),
ParametersOffset: b.lut.parameters.Add(ob.parameters),
ReturnMatcherIndicesOffset: ob.returnTypeMatcherIndicesOffset,
}, nil
}
// buildOpenTypes constructs the OpenTypes used by the overload, populating
// b.openTypes
func (b *overloadBuilder) buildOpenTypes(o *sem.Overload) error {
b.openTypes = make([]OpenType, len(o.OpenTypes))
for i, t := range o.OpenTypes {
b.openTypeIndex[t] = i
matcherIndex := -1
if t.Type != nil {
var err error
matcherIndex, err = b.matcherIndex(t.Type)
if err != nil {
return err
}
}
b.openTypes[i] = OpenType{
Name: t.Name,
MatcherIndex: matcherIndex,
}
}
return nil
}
// buildOpenNumbers constructs the OpenNumbers used by the overload, populating
// b.openNumbers
func (b *overloadBuilder) buildOpenNumbers(o *sem.Overload) error {
b.openNumbers = make([]OpenNumber, len(o.OpenNumbers))
for i, t := range o.OpenNumbers {
b.openNumberIndex[t] = i
matcherIndex := -1
if e, ok := t.(*sem.TemplateEnumParam); ok && e.Matcher != nil {
var err error
matcherIndex, err = b.matcherIndex(e.Matcher)
if err != nil {
return err
}
}
b.openNumbers[i] = OpenNumber{
Name: t.GetName(),
MatcherIndex: matcherIndex,
}
}
return nil
}
// buildParameters constructs the Parameters used by the overload, populating
// b.parameters
func (b *overloadBuilder) buildParameters(o *sem.Overload) error {
b.parameters = make([]Parameter, len(o.Parameters))
for i, p := range o.Parameters {
indices, err := b.collectMatcherIndices(p.Type)
if err != nil {
return err
}
b.parameters[i] = Parameter{
Usage: p.Name,
MatcherIndicesOffset: b.lut.matcherIndices.Add(indices),
}
}
return nil
}
// buildParameters calculates the matcher indices required to match the
// overload's return type (if the overload has a return value), possibly
// populating b.returnTypeMatcherIndicesOffset
func (b *overloadBuilder) buildReturnType(o *sem.Overload) error {
if o.ReturnType != nil {
indices, err := b.collectMatcherIndices(*o.ReturnType)
if err != nil {
return err
}
b.returnTypeMatcherIndicesOffset = b.lut.matcherIndices.Add(indices)
}
return nil
}
// matcherIndex returns the index of TMatcher or NMatcher in
// IntrinsicTable.TMatcher or IntrinsicTable.NMatcher, respectively.
func (b *overloadBuilder) matcherIndex(n sem.Named) (int, error) {
switch n := n.(type) {
case *sem.Type, *sem.TypeMatcher:
if i, ok := b.TMatcherIndex[n]; ok {
return i, nil
}
return 0, fmt.Errorf("matcherIndex missing entry for %v %T", n.GetName(), n)
case *sem.TemplateTypeParam:
if i, ok := b.openTypeIndex[n]; ok {
return i, nil
}
return 0, fmt.Errorf("openTypeIndex missing entry for %v %T", n.Name, n)
case *sem.EnumMatcher:
if i, ok := b.NMatcherIndex[n]; ok {
return i, nil
}
return 0, fmt.Errorf("matcherIndex missing entry for %v %T", n.GetName(), n)
case *sem.TemplateEnumParam:
if i, ok := b.openNumberIndex[n]; ok {
return i, nil
}
return 0, fmt.Errorf("openNumberIndex missing entry for %v %T", n, n)
case *sem.TemplateNumberParam:
if i, ok := b.openNumberIndex[n]; ok {
return i, nil
}
return 0, fmt.Errorf("openNumberIndex missing entry for %v %T", n, n)
default:
return 0, fmt.Errorf("overload.matcherIndex() does not handle %v %T", n, n)
}
}
// collectMatcherIndices returns the full list of matcher indices required to
// match the fully-qualified-name. For names that have do not have templated
// arguments, collectMatcherIndices() will return a single TMatcher index.
// For names that do have templated arguments, collectMatcherIndices() returns
// a list of type matcher indices, starting with the target of the fully
// qualified name, then followed by each of the template arguments from left to
// right. Note that template arguments may themselves have template arguments,
// and so collectMatcherIndices() may call itself.
// The order of returned matcher indices is always the order of the fully
// qualified name as read from left to right.
// For example, calling collectMatcherIndices() for the fully qualified name:
// A<B<C, D>, E<F, G<H>, I>
// Would return the matcher indices:
// A, B, C, D, E, F, G, H, I
func (b *overloadBuilder) collectMatcherIndices(fqn sem.FullyQualifiedName) ([]int, error) {
idx, err := b.matcherIndex(fqn.Target)
if err != nil {
return nil, err
}
out := []int{idx}
for _, arg := range fqn.TemplateArguments {
indices, err := b.collectMatcherIndices(arg)
if err != nil {
return nil, err
}
out = append(out, indices...)
}
return out, nil
}
// buildIntrinsicTable builds the IntrinsicTable from the semantic info
func buildIntrinsicTable(s *sem.Sem) (*IntrinsicTable, error) {
b := intrinsicTableBuilder{
IntrinsicTable: IntrinsicTable{
Sem: s,
TMatcherIndex: map[sem.Named]int{},
NMatcherIndex: map[sem.Named]int{},
},
}
b.lut.matcherIndices = lut.New(list.Wrap(&b.MatcherIndices))
b.lut.openTypes = lut.New(list.Wrap(&b.OpenTypes))
b.lut.openNumbers = lut.New(list.Wrap(&b.OpenNumbers))
b.lut.parameters = lut.New(list.Wrap(&b.Parameters))
b.lut.overloads = lut.New(list.Wrap(&b.Overloads))
b.layoutMatchers(s)
for _, f := range s.Functions {
overloads := make([]Overload, len(f.Overloads))
overloadDescriptions := make([]string, len(f.Overloads))
for i, o := range f.Overloads {
overloadDescriptions[i] = fmt.Sprint(o.Decl)
var err error
if overloads[i], err = b.buildOverload(o); err != nil {
return nil, err
}
}
b.Functions = append(b.Functions, Function{
OverloadDescriptions: overloadDescriptions,
NumOverloads: len(overloads),
OverloadsOffset: b.lut.overloads.Add(overloads),
})
}
b.lut.matcherIndices.Compact()
b.lut.openTypes.Compact()
b.lut.openNumbers.Compact()
b.lut.parameters.Compact()
b.lut.overloads.Compact()
return &b.IntrinsicTable, nil
}

View File

@ -32,6 +32,8 @@ import (
"dawn.googlesource.com/tint/tools/src/glob" "dawn.googlesource.com/tint/tools/src/glob"
) )
const defProjectRelPath = "src/intrinsics.def"
func main() { func main() {
if err := run(); err != nil { if err := run(); err != nil {
fmt.Println(err) fmt.Println(err)
@ -58,7 +60,7 @@ optional flags:`)
func run() error { func run() error {
// Load the intrinsics definition file // Load the intrinsics definition file
projectRoot := fileutils.ProjectRoot() projectRoot := fileutils.ProjectRoot()
defPath := filepath.Join(projectRoot, "src/intrinsics.def") defPath := filepath.Join(projectRoot, defProjectRelPath)
defSource, err := ioutil.ReadFile(defPath) defSource, err := ioutil.ReadFile(defPath)
if err != nil { if err != nil {
@ -66,7 +68,7 @@ func run() error {
} }
// Parse the definition file to produce an AST // Parse the definition file to produce an AST
ast, err := parser.Parse(string(defSource), defPath) ast, err := parser.Parse(string(defSource), defProjectRelPath)
if err != nil { if err != nil {
return err return err
} }