tint: Add constructors and conversions to the intrinsic table

For all types except for arrays and structures, which are explicitly
typed and have trivial overloads.

This will simplify maintenance of type functions, unifies diagnostic
messages and will greatly simplify the [AbstractInt -> i32|u32]
[AbstractFloat -> f32|f16] logic.

Bug: tint:1504
Change-Id: I2b17ed530d1cece22adcbfc6de0bec4fbda4c7bd
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/90248
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: David Neto <dneto@google.com>
Commit-Queue: Ben Clayton <bclayton@google.com>
This commit is contained in:
Ben Clayton 2022-05-16 20:54:42 +00:00 committed by Dawn LUCI CQ
parent 8520f3bb93
commit 6ae608cb03
19 changed files with 8195 additions and 5448 deletions

View File

@ -367,6 +367,8 @@ libtint_source_set("libtint_core_all_src") {
"program_id.h", "program_id.h",
"reader/reader.cc", "reader/reader.cc",
"reader/reader.h", "reader/reader.h",
"resolver/ctor_conv_intrinsic.cc",
"resolver/ctor_conv_intrinsic.h",
"resolver/dependency_graph.cc", "resolver/dependency_graph.cc",
"resolver/dependency_graph.h", "resolver/dependency_graph.h",
"resolver/intrinsic_table.cc", "resolver/intrinsic_table.cc",

View File

@ -248,6 +248,8 @@ set(TINT_LIB_SRCS
program.h program.h
reader/reader.cc reader/reader.cc
reader/reader.h reader/reader.h
resolver/ctor_conv_intrinsic.cc
resolver/ctor_conv_intrinsic.h
resolver/dependency_graph.cc resolver/dependency_graph.cc
resolver/dependency_graph.h resolver/dependency_graph.h
resolver/intrinsic_table.cc resolver/intrinsic_table.cc

View File

@ -72,6 +72,15 @@ type u32
type vec2<T> type vec2<T>
type vec3<T> type vec3<T>
type vec4<T> type vec4<T>
type mat2x2<T>
type mat2x3<T>
type mat2x4<T>
type mat3x2<T>
type mat3x3<T>
type mat3x4<T>
type mat4x2<T>
type mat4x3<T>
type mat4x4<T>
[[display("vec{N}<{T}>")]] type vec<N: num, T> [[display("vec{N}<{T}>")]] type vec<N: num, T>
[[display("mat{N}x{M}<{T}>")]] type mat<N: num, M: num, T> [[display("mat{N}x{M}<{T}>")]] type mat<N: num, M: num, T>
type ptr<S: storage_class, T, A: access> type ptr<S: storage_class, T, A: access>
@ -112,6 +121,10 @@ match fiu32: f32 | i32 | u32
match fi32: f32 | i32 match fi32: f32 | i32
match iu32: i32 | u32 match iu32: i32 | u32
match scalar: f32 | i32 | u32 | bool match scalar: f32 | i32 | u32 | bool
match scalar_no_f32: i32 | u32 | bool
match scalar_no_i32: f32 | u32 | bool
match scalar_no_u32: f32 | i32 | bool
match scalar_no_bool: f32 | i32 | u32
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// Enum matchers // // Enum matchers //
@ -562,6 +575,122 @@ fn textureLoad(texture: texture_external, coords: vec2<i32>) -> vec4<f32>
[[stage("fragment", "compute")]] fn atomicExchange<T: iu32, S: workgroup_or_storage>(ptr<S, atomic<T>, read_write>, T) -> T [[stage("fragment", "compute")]] fn atomicExchange<T: iu32, S: workgroup_or_storage>(ptr<S, atomic<T>, read_write>, T) -> T
[[stage("fragment", "compute")]] fn atomicCompareExchangeWeak<T: iu32, S: workgroup_or_storage>(ptr<S, atomic<T>, read_write>, T, T) -> vec2<T> [[stage("fragment", "compute")]] fn atomicCompareExchangeWeak<T: iu32, S: workgroup_or_storage>(ptr<S, atomic<T>, read_write>, T, T) -> vec2<T>
////////////////////////////////////////////////////////////////////////////////
// Type constructors //
////////////////////////////////////////////////////////////////////////////////
// Zero value constructors
ctor i32() -> i32
ctor u32() -> u32
ctor f32() -> f32
ctor bool() -> bool
ctor vec2<T: scalar>() -> vec2<T>
ctor vec3<T: scalar>() -> vec3<T>
ctor vec4<T: scalar>() -> vec4<T>
ctor mat2x2() -> mat2x2<f32>
ctor mat2x3() -> mat2x3<f32>
ctor mat2x4() -> mat2x4<f32>
ctor mat3x2() -> mat3x2<f32>
ctor mat3x3() -> mat3x3<f32>
ctor mat3x4() -> mat3x4<f32>
ctor mat4x2() -> mat4x2<f32>
ctor mat4x3() -> mat4x3<f32>
ctor mat4x4() -> mat4x4<f32>
// Identity constructors
ctor i32(i32) -> i32
ctor u32(u32) -> u32
ctor f32(f32) -> f32
ctor bool(bool) -> bool
ctor vec2<T: scalar>(vec2<T>) -> vec2<T>
ctor vec3<T: scalar>(vec3<T>) -> vec3<T>
ctor vec4<T: scalar>(vec4<T>) -> vec4<T>
// Vector constructors
ctor vec2<T: scalar>(T) -> vec2<T>
ctor vec2<T: scalar>(x: T, y: T) -> vec2<T>
ctor vec3<T: scalar>(T) -> vec3<T>
ctor vec3<T: scalar>(x: T, y: T, z: T) -> vec3<T>
ctor vec3<T: scalar>(xy: vec2<T>, z: T) -> vec3<T>
ctor vec3<T: scalar>(x: T, yz: vec2<T>) -> vec3<T>
ctor vec4<T: scalar>(T) -> vec4<T>
ctor vec4<T: scalar>(x: T, y: T, z: T, w: T) -> vec4<T>
ctor vec4<T: scalar>(xy: vec2<T>, z: T, w: T) -> vec4<T>
ctor vec4<T: scalar>(x: T, yz: vec2<T>, w: T) -> vec4<T>
ctor vec4<T: scalar>(x: T, y: T, zw: vec2<T>) -> vec4<T>
ctor vec4<T: scalar>(xy: vec2<T>, zw: vec2<T>) -> vec4<T>
ctor vec4<T: scalar>(xyz: vec3<T>, w: T) -> vec4<T>
ctor vec4<T: scalar>(x: T, zyw: vec3<T>) -> vec4<T>
// Matrix constructors
ctor mat2x2(f32, f32,
f32, f32) -> mat2x2<f32>
ctor mat2x2(vec2<f32>, vec2<f32>) -> mat2x2<f32>
ctor mat2x3(f32, f32, f32,
f32, f32, f32) -> mat2x3<f32>
ctor mat2x3(vec3<f32>, vec3<f32>) -> mat2x3<f32>
ctor mat2x4(f32, f32, f32, f32,
f32, f32, f32, f32) -> mat2x4<f32>
ctor mat2x4(vec4<f32>, vec4<f32>) -> mat2x4<f32>
ctor mat3x2(f32, f32,
f32, f32,
f32, f32) -> mat3x2<f32>
ctor mat3x2(vec2<f32>, vec2<f32>, vec2<f32>) -> mat3x2<f32>
ctor mat3x3(f32, f32, f32,
f32, f32, f32,
f32, f32, f32) -> mat3x3<f32>
ctor mat3x3(vec3<f32>, vec3<f32>, vec3<f32>) -> mat3x3<f32>
ctor mat3x4(f32, f32, f32, f32,
f32, f32, f32, f32,
f32, f32, f32, f32) -> mat3x4<f32>
ctor mat3x4(vec4<f32>, vec4<f32>, vec4<f32>) -> mat3x4<f32>
ctor mat4x2(f32, f32,
f32, f32,
f32, f32,
f32, f32) -> mat4x2<f32>
ctor mat4x2(vec2<f32>, vec2<f32>, vec2<f32>, vec2<f32>) -> mat4x2<f32>
ctor mat4x3(f32, f32, f32,
f32, f32, f32,
f32, f32, f32,
f32, f32, f32) -> mat4x3<f32>
ctor mat4x3(vec3<f32>, vec3<f32>, vec3<f32>, vec3<f32>) -> mat4x3<f32>
ctor mat4x4(f32, f32, f32, f32,
f32, f32, f32, f32,
f32, f32, f32, f32,
f32, f32, f32, f32) -> mat4x4<f32>
ctor mat4x4(vec4<f32>, vec4<f32>, vec4<f32>, vec4<f32>) -> mat4x4<f32>
////////////////////////////////////////////////////////////////////////////////
// Type conversions //
////////////////////////////////////////////////////////////////////////////////
conv f32<T: scalar_no_f32>(T) -> f32
conv i32<T: scalar_no_i32>(T) -> i32
conv u32<T: scalar_no_u32>(T) -> u32
conv bool<T: scalar_no_bool>(T) -> bool
conv vec2<T: f32, U: scalar_no_f32>(vec2<U>) -> vec2<f32>
conv vec2<T: i32, U: scalar_no_i32>(vec2<U>) -> vec2<i32>
conv vec2<T: u32, U: scalar_no_u32>(vec2<U>) -> vec2<u32>
conv vec2<T: bool, U: scalar_no_bool>(vec2<U>) -> vec2<bool>
conv vec3<T: f32, U: scalar_no_f32>(vec3<U>) -> vec3<f32>
conv vec3<T: i32, U: scalar_no_i32>(vec3<U>) -> vec3<i32>
conv vec3<T: u32, U: scalar_no_u32>(vec3<U>) -> vec3<u32>
conv vec3<T: bool, U: scalar_no_bool>(vec3<U>) -> vec3<bool>
conv vec4<T: f32, U: scalar_no_f32>(vec4<U>) -> vec4<f32>
conv vec4<T: i32, U: scalar_no_i32>(vec4<U>) -> vec4<i32>
conv vec4<T: u32, U: scalar_no_u32>(vec4<U>) -> vec4<u32>
conv vec4<T: bool, U: scalar_no_bool>(vec4<U>) -> vec4<bool>
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// Operators // // Operators //
// // // //

View File

@ -0,0 +1,70 @@
// 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.
////////////////////////////////////////////////////////////////////////////////
// File generated by tools/intrinsic-gen
// using the template:
// src/tint/resolver/ctor_conv_intrinsic.cc.tmpl
// and the intrinsic defintion file:
// src/tint/intrinsics.def
//
// Do not modify this file directly
////////////////////////////////////////////////////////////////////////////////
#include "src/tint/resolver/ctor_conv_intrinsic.h"
namespace tint::resolver {
const char* str(CtorConvIntrinsic i) {
switch (i) {
case CtorConvIntrinsic::kNone:
return "<none>";
case CtorConvIntrinsic::kI32:
return "i32";
case CtorConvIntrinsic::kU32:
return "u32";
case CtorConvIntrinsic::kF32:
return "f32";
case CtorConvIntrinsic::kBool:
return "bool";
case CtorConvIntrinsic::kVec2:
return "vec2";
case CtorConvIntrinsic::kVec3:
return "vec3";
case CtorConvIntrinsic::kVec4:
return "vec4";
case CtorConvIntrinsic::kMat2x2:
return "mat2x2";
case CtorConvIntrinsic::kMat2x3:
return "mat2x3";
case CtorConvIntrinsic::kMat2x4:
return "mat2x4";
case CtorConvIntrinsic::kMat3x2:
return "mat3x2";
case CtorConvIntrinsic::kMat3x3:
return "mat3x3";
case CtorConvIntrinsic::kMat3x4:
return "mat3x4";
case CtorConvIntrinsic::kMat4x2:
return "mat4x2";
case CtorConvIntrinsic::kMat4x3:
return "mat4x3";
case CtorConvIntrinsic::kMat4x4:
return "mat4x4";
}
return "<unknown>";
}
} // namespace tint::resolver

View File

@ -0,0 +1,28 @@
{{- /*
--------------------------------------------------------------------------------
Template file for use with tools/builtin-gen to generate ctor_conv_intrinsic.cc
See:
* tools/cmd/intrinsic-gen/gen for structures used by this template
* https://golang.org/pkg/text/template/ for documentation on the template syntax
--------------------------------------------------------------------------------
*/ -}}
#include "src/tint/resolver/ctor_conv_intrinsic.h"
namespace tint::resolver {
const char* str(CtorConvIntrinsic i) {
switch (i) {
case CtorConvIntrinsic::kNone:
return "<none>";
{{- range .Sem.ConstructorsAndConverters }}
case CtorConvIntrinsic::k{{Title .Name}}:
return "{{.Name}}";
{{- end }}
}
return "<unknown>";
}
} // namespace tint::resolver

View File

@ -0,0 +1,100 @@
// 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.
////////////////////////////////////////////////////////////////////////////////
// File generated by tools/intrinsic-gen
// using the template:
// src/tint/resolver/ctor_conv_intrinsic.h.tmpl
// and the intrinsic defintion file:
// src/tint/intrinsics.def
//
// Do not modify this file directly
////////////////////////////////////////////////////////////////////////////////
#ifndef SRC_TINT_RESOLVER_CTOR_CONV_INTRINSIC_H_
#define SRC_TINT_RESOLVER_CTOR_CONV_INTRINSIC_H_
#include <cstdint>
namespace tint::resolver {
/// CtorConvIntrinsic is an enumerator of types that have a constructor or converter overload
/// declared in the intrinsic table.
enum class CtorConvIntrinsic {
kNone = -1,
kI32,
kU32,
kF32,
kBool,
kVec2,
kVec3,
kVec4,
kMat2x2,
kMat2x3,
kMat2x4,
kMat3x2,
kMat3x3,
kMat3x4,
kMat4x2,
kMat4x3,
kMat4x4,
};
/// @returns the name of the type.
const char* str(CtorConvIntrinsic i);
/// @param n the width of the vector
/// @return the CtorConvIntrinsic for a vector of width `n`
inline CtorConvIntrinsic VectorCtorConvIntrinsic(uint32_t n) {
switch (n) {
case 2:
return CtorConvIntrinsic::kVec2;
case 3:
return CtorConvIntrinsic::kVec3;
case 4:
return CtorConvIntrinsic::kVec4;
}
return CtorConvIntrinsic::kNone;
}
/// @param c the number of columns in the matrix
/// @param r the number of rows in the matrix
/// @return the CtorConvIntrinsic for a matrix with `c` columns and `r` rows
inline CtorConvIntrinsic MatrixCtorConvIntrinsic(uint32_t c, uint32_t r) {
switch ((c - 2) * 3 + (r - 2)) {
case 0:
return CtorConvIntrinsic::kMat2x2;
case 1:
return CtorConvIntrinsic::kMat2x3;
case 2:
return CtorConvIntrinsic::kMat2x4;
case 3:
return CtorConvIntrinsic::kMat3x2;
case 4:
return CtorConvIntrinsic::kMat3x3;
case 5:
return CtorConvIntrinsic::kMat3x4;
case 6:
return CtorConvIntrinsic::kMat4x2;
case 7:
return CtorConvIntrinsic::kMat4x3;
case 8:
return CtorConvIntrinsic::kMat4x4;
}
return CtorConvIntrinsic::kNone;
}
} // namespace tint::resolver
#endif // SRC_TINT_RESOLVER_CTOR_CONV_INTRINSIC_H_

View File

@ -0,0 +1,73 @@
{{- /*
--------------------------------------------------------------------------------
Template file for use with tools/builtin-gen to generate ctor_conv_intrinsic.h
See:
* tools/cmd/intrinsic-gen/gen for structures used by this template
* https://golang.org/pkg/text/template/ for documentation on the template syntax
--------------------------------------------------------------------------------
*/ -}}
#ifndef SRC_TINT_RESOLVER_CTOR_CONV_INTRINSIC_H_
#define SRC_TINT_RESOLVER_CTOR_CONV_INTRINSIC_H_
#include <cstdint>
namespace tint::resolver {
/// CtorConvIntrinsic is an enumerator of types that have a constructor or converter overload
/// declared in the intrinsic table.
enum class CtorConvIntrinsic {
kNone = -1,
{{- range .Sem.ConstructorsAndConverters }}
k{{Title .Name}},
{{- end }}
};
/// @returns the name of the type.
const char* str(CtorConvIntrinsic i);
/// @param n the width of the vector
/// @return the CtorConvIntrinsic for a vector of width `n`
inline CtorConvIntrinsic VectorCtorConvIntrinsic(uint32_t n) {
switch (n) {
case 2:
return CtorConvIntrinsic::kVec2;
case 3:
return CtorConvIntrinsic::kVec3;
case 4:
return CtorConvIntrinsic::kVec4;
}
return CtorConvIntrinsic::kNone;
}
/// @param c the number of columns in the matrix
/// @param r the number of rows in the matrix
/// @return the CtorConvIntrinsic for a matrix with `c` columns and `r` rows
inline CtorConvIntrinsic MatrixCtorConvIntrinsic(uint32_t c, uint32_t r) {
switch ((c - 2) * 3 + (r - 2)) {
case 0:
return CtorConvIntrinsic::kMat2x2;
case 1:
return CtorConvIntrinsic::kMat2x3;
case 2:
return CtorConvIntrinsic::kMat2x4;
case 3:
return CtorConvIntrinsic::kMat3x2;
case 4:
return CtorConvIntrinsic::kMat3x3;
case 5:
return CtorConvIntrinsic::kMat3x4;
case 6:
return CtorConvIntrinsic::kMat4x2;
case 7:
return CtorConvIntrinsic::kMat4x3;
case 8:
return CtorConvIntrinsic::kMat4x4;
}
return CtorConvIntrinsic::kNone;
}
} // namespace tint::resolver
#endif // SRC_TINT_RESOLVER_CTOR_CONV_INTRINSIC_H_

View File

@ -28,6 +28,8 @@
#include "src/tint/sem/pipeline_stage_set.h" #include "src/tint/sem/pipeline_stage_set.h"
#include "src/tint/sem/sampled_texture.h" #include "src/tint/sem/sampled_texture.h"
#include "src/tint/sem/storage_texture.h" #include "src/tint/sem/storage_texture.h"
#include "src/tint/sem/type_constructor.h"
#include "src/tint/sem/type_conversion.h"
#include "src/tint/utils/hash.h" #include "src/tint/utils/hash.h"
#include "src/tint/utils/map.h" #include "src/tint/utils/map.h"
#include "src/tint/utils/math.h" #include "src/tint/utils/math.h"
@ -120,17 +122,10 @@ class ClosedState {
return res.second || res.first->second == number.Value(); return res.second || res.first->second == number.Value();
} }
/// Type returns the closed type with index `idx`. /// Type returns the closed type with index `idx`, or nullptr if the type was not closed.
/// An ICE is raised if the type is not closed.
const sem::Type* Type(uint32_t idx) const { const sem::Type* Type(uint32_t idx) const {
auto it = types_.find(idx); auto it = types_.find(idx);
if (it == types_.end()) { return (it != types_.end()) ? it->second : nullptr;
TINT_ICE(Resolver, builder.Diagnostics())
<< "type with index " << idx << " is not closed";
return nullptr;
}
TINT_ASSERT(Resolver, it != types_.end());
return it->second;
} }
/// Type returns the number type with index `idx`. /// Type returns the number type with index `idx`.
@ -295,6 +290,8 @@ using PipelineStage = ast::PipelineStage;
enum class OverloadFlag { enum class OverloadFlag {
kIsBuiltin, // The overload is a builtin ('fn') kIsBuiltin, // The overload is a builtin ('fn')
kIsOperator, // The overload is an operator ('op') kIsOperator, // The overload is an operator ('op')
kIsConstructor, // The overload is a type constructor ('ctor')
kIsConverter, // The overload is a type converter ('conv')
kSupportsVertexPipeline, // The overload can be used in vertex shaders kSupportsVertexPipeline, // The overload can be used in vertex shaders
kSupportsFragmentPipeline, // The overload can be used in fragment shaders kSupportsFragmentPipeline, // The overload can be used in fragment shaders
kSupportsComputePipeline, // The overload can be used in compute shaders kSupportsComputePipeline, // The overload can be used in compute shaders
@ -351,11 +348,7 @@ bool match_vec(const sem::Type* ty, Number& N, const sem::Type*& T) {
return false; return false;
} }
const sem::Vector* build_vec(MatchState& state, Number N, const sem::Type* el) { template <uint32_t N>
return state.builder.create<sem::Vector>(el, N.Value());
}
template <int N>
bool match_vec(const sem::Type* ty, const sem::Type*& T) { bool match_vec(const sem::Type* ty, const sem::Type*& T) {
if (ty->Is<Any>()) { if (ty->Is<Any>()) {
T = ty; T = ty;
@ -371,29 +364,22 @@ bool match_vec(const sem::Type* ty, const sem::Type*& T) {
return false; return false;
} }
bool match_vec2(const sem::Type* ty, const sem::Type*& T) { const sem::Vector* build_vec(MatchState& state, Number N, const sem::Type* el) {
return match_vec<2>(ty, T); return state.builder.create<sem::Vector>(el, N.Value());
} }
const sem::Vector* build_vec2(MatchState& state, const sem::Type* T) { template <uint32_t N>
return build_vec(state, Number(2), T); const sem::Vector* build_vec(MatchState& state, const sem::Type* el) {
return state.builder.create<sem::Vector>(el, N);
} }
bool match_vec3(const sem::Type* ty, const sem::Type*& T) { constexpr auto match_vec2 = match_vec<2>;
return match_vec<3>(ty, T); constexpr auto match_vec3 = match_vec<3>;
} constexpr auto match_vec4 = match_vec<4>;
const sem::Vector* build_vec3(MatchState& state, const sem::Type* T) { constexpr auto build_vec2 = build_vec<2>;
return build_vec(state, Number(3), T); constexpr auto build_vec3 = build_vec<3>;
} constexpr auto build_vec4 = build_vec<4>;
bool match_vec4(const sem::Type* ty, const sem::Type*& T) {
return match_vec<4>(ty, T);
}
const sem::Vector* build_vec4(MatchState& state, const sem::Type* T) {
return build_vec(state, Number(4), T);
}
bool match_mat(const sem::Type* ty, Number& M, Number& N, const sem::Type*& T) { bool match_mat(const sem::Type* ty, Number& M, Number& N, const sem::Type*& T) {
if (ty->Is<Any>()) { if (ty->Is<Any>()) {
@ -411,11 +397,52 @@ bool match_mat(const sem::Type* ty, Number& M, Number& N, const sem::Type*& T) {
return false; return false;
} }
const sem::Matrix* build_mat(MatchState& state, Number N, Number M, const sem::Type* T) { template <uint32_t C, uint32_t R>
auto* column_type = state.builder.create<sem::Vector>(T, M.Value()); bool match_mat(const sem::Type* ty, const sem::Type*& T) {
return state.builder.create<sem::Matrix>(column_type, N.Value()); if (ty->Is<Any>()) {
T = ty;
return true;
}
if (auto* m = ty->As<sem::Matrix>()) {
if (m->columns() == C && m->rows() == R) {
T = m->type();
return true;
}
}
return false;
} }
const sem::Matrix* build_mat(MatchState& state, Number C, Number R, const sem::Type* T) {
auto* column_type = state.builder.create<sem::Vector>(T, R.Value());
return state.builder.create<sem::Matrix>(column_type, C.Value());
}
template <uint32_t C, uint32_t R>
const sem::Matrix* build_mat(MatchState& state, const sem::Type* T) {
auto* column_type = state.builder.create<sem::Vector>(T, R);
return state.builder.create<sem::Matrix>(column_type, C);
}
constexpr auto build_mat2x2 = build_mat<2, 2>;
constexpr auto build_mat2x3 = build_mat<2, 3>;
constexpr auto build_mat2x4 = build_mat<2, 4>;
constexpr auto build_mat3x2 = build_mat<3, 2>;
constexpr auto build_mat3x3 = build_mat<3, 3>;
constexpr auto build_mat3x4 = build_mat<3, 4>;
constexpr auto build_mat4x2 = build_mat<4, 2>;
constexpr auto build_mat4x3 = build_mat<4, 3>;
constexpr auto build_mat4x4 = build_mat<4, 4>;
constexpr auto match_mat2x2 = match_mat<2, 2>;
constexpr auto match_mat2x3 = match_mat<2, 3>;
constexpr auto match_mat2x4 = match_mat<2, 4>;
constexpr auto match_mat3x2 = match_mat<3, 2>;
constexpr auto match_mat3x3 = match_mat<3, 3>;
constexpr auto match_mat3x4 = match_mat<3, 4>;
constexpr auto match_mat4x2 = match_mat<4, 2>;
constexpr auto match_mat4x3 = match_mat<4, 3>;
constexpr auto match_mat4x4 = match_mat<4, 4>;
bool match_array(const sem::Type* ty, const sem::Type*& T) { bool match_array(const sem::Type* ty, const sem::Type*& T) {
if (ty->Is<Any>()) { if (ty->Is<Any>()) {
T = ty; T = ty;
@ -843,37 +870,108 @@ class Impl : public IntrinsicTable {
const Source& source, const Source& source,
bool is_compound) override; bool is_compound) override;
const sem::CallTarget* Lookup(CtorConvIntrinsic type,
const sem::Type* template_arg,
const std::vector<const sem::Type*>& args,
const Source& source) override;
private: private:
// Candidate holds information about a mismatched overload that could be what the user intended /// Candidate holds information about an overload evaluated for resolution.
// to call.
struct Candidate { struct Candidate {
const OverloadInfo* overload; /// The candidate overload
const OverloadInfo& overload;
/// The closed types and numbers
ClosedState closed;
/// The parameter types for the candidate overload
std::vector<IntrinsicPrototype::Parameter> parameters;
/// True if the candidate is a viable match for the call
bool matched;
/// The match-score of the candidate overload. Used for diagnostics when no overload
/// matches. Higher scores are displayed first (top-most).
int score; int score;
}; };
const IntrinsicPrototype Match(const char* intrinsic_name, /// A list of candidates
const OverloadInfo& overload, using Candidates = std::vector<Candidate>;
const std::vector<const sem::Type*>& args,
int& match_score);
/// Callback function when no overloads match.
using OnNoMatch = std::function<void(Candidates)>;
/// Attempts to find a single intrinsic overload that matches the provided argument types.
/// @param intrinsic the intrinsic being called
/// @param intrinsic_name the name of the intrinsic
/// @param args the argument types
/// @param closed initial closed state. This may contain explicitly specified template
/// arguments. For example `vec3<f32>()` would have the first template-type closed
/// as `f32`.
/// @param on_no_match an error callback when no intrinsic overloads matched the provided
/// arguments.
/// @returns the matched intrinsic. If no intrinsic could be matched then IntrinsicPrototype
/// will hold nullptrs for IntrinsicPrototype::overload and
/// IntrinsicPrototype::return_type.
IntrinsicPrototype MatchIntrinsic(const IntrinsicInfo& intrinsic,
const char* intrinsic_name,
const std::vector<const sem::Type*>& args,
ClosedState closed,
OnNoMatch on_no_match) const;
/// Evaluates the overload for the provided argument types.
/// @param overload the overload being considered
/// @param args the argument types
/// @param closed initial closed state. This may contain explicitly specified template
/// arguments. For example `vec3<f32>()` would have the first template-type closed
/// as `f32`.
/// @returns the evaluated Candidate information.
Candidate ScoreOverload(const OverloadInfo& overload,
const std::vector<const sem::Type*>& args,
ClosedState closed) const;
/// Match constructs a new MatchState
/// @param closed the open / closed numbers and types used for matcher evaluation
/// @param overload the overload being evaluated
/// @param matcher_indices pointer to a list of matcher indices
MatchState Match(ClosedState& closed, MatchState Match(ClosedState& closed,
const OverloadInfo& overload, const OverloadInfo& overload,
MatcherIndex const* matcher_indices) const; MatcherIndex const* matcher_indices) const;
void PrintOverload(std::ostream& ss, const OverloadInfo& overload, const char* name) const; // Prints the overload for emitting diagnostics
void PrintOverload(std::ostream& ss,
const OverloadInfo& overload,
const char* intrinsic_name) const;
// Prints the list of candidates for emitting diagnostics
void PrintCandidates(std::ostream& ss,
const Candidates& candidates,
const char* intrinsic_name) const;
/// Raises an ICE when multiple overload candidates match, as this should never happen.
void ErrMultipleOverloadsMatched(uint32_t num_matched,
const char* intrinsic_name,
const std::vector<const sem::Type*>& args,
ClosedState closed,
Candidates candidates) const;
ProgramBuilder& builder; ProgramBuilder& builder;
Matchers matchers; Matchers matchers;
std::unordered_map<IntrinsicPrototype, sem::Builtin*, IntrinsicPrototype::Hasher> builtins; std::unordered_map<IntrinsicPrototype, sem::Builtin*, IntrinsicPrototype::Hasher> builtins;
std::unordered_map<IntrinsicPrototype, sem::TypeConstructor*, IntrinsicPrototype::Hasher>
constructors;
std::unordered_map<IntrinsicPrototype, sem::TypeConversion*, IntrinsicPrototype::Hasher>
converters;
}; };
/// @return a string representing a call to a builtin with the given argument /// @return a string representing a call to a builtin with the given argument
/// types. /// types.
std::string CallSignature(ProgramBuilder& builder, std::string CallSignature(ProgramBuilder& builder,
const char* intrinsic_name, const char* intrinsic_name,
const std::vector<const sem::Type*>& args) { const std::vector<const sem::Type*>& args,
const sem::Type* template_arg = nullptr) {
std::stringstream ss; std::stringstream ss;
ss << intrinsic_name << "("; ss << intrinsic_name;
if (template_arg) {
ss << "<" << template_arg->FriendlyName(builder.Symbols()) << ">";
}
ss << "(";
{ {
bool first = true; bool first = true;
for (auto* arg : args) { for (auto* arg : args) {
@ -902,73 +1000,56 @@ Impl::Impl(ProgramBuilder& b) : builder(b) {}
const sem::Builtin* Impl::Lookup(sem::BuiltinType builtin_type, const sem::Builtin* Impl::Lookup(sem::BuiltinType builtin_type,
const std::vector<const sem::Type*>& args, const std::vector<const sem::Type*>& args,
const Source& source) { const Source& source) {
// The list of failed matches that had promise.
std::vector<Candidate> candidates;
uint32_t intrinsic_index = static_cast<uint32_t>(builtin_type);
const char* intrinsic_name = sem::str(builtin_type); const char* intrinsic_name = sem::str(builtin_type);
auto& builtin = kBuiltins[intrinsic_index];
for (uint32_t o = 0; o < builtin.num_overloads; o++) { // Generates an error when no overloads match the provided arguments
int match_score = 1000; auto on_no_match = [&](Candidates candidates) {
auto& overload = builtin.overloads[o]; std::stringstream ss;
auto match = Match(intrinsic_name, overload, args, match_score); ss << "no matching call to " << CallSignature(builder, intrinsic_name, args) << std::endl;
if (match.return_type) { if (!candidates.empty()) {
// De-duplicate builtins that are identical. ss << std::endl
return utils::GetOrCreate(builtins, match, [&] { << candidates.size() << " candidate function" << (candidates.size() > 1 ? "s:" : ":")
std::vector<sem::Parameter*> params; << std::endl;
params.reserve(match.parameters.size()); PrintCandidates(ss, candidates, intrinsic_name);
for (auto& p : match.parameters) {
params.emplace_back(builder.create<sem::Parameter>(
nullptr, static_cast<uint32_t>(params.size()), p.type,
ast::StorageClass::kNone, ast::Access::kUndefined, p.usage));
}
sem::PipelineStageSet supported_stages;
if (match.overload->flags.Contains(OverloadFlag::kSupportsVertexPipeline)) {
supported_stages.Add(ast::PipelineStage::kVertex);
}
if (match.overload->flags.Contains(OverloadFlag::kSupportsFragmentPipeline)) {
supported_stages.Add(ast::PipelineStage::kFragment);
}
if (match.overload->flags.Contains(OverloadFlag::kSupportsComputePipeline)) {
supported_stages.Add(ast::PipelineStage::kCompute);
}
return builder.create<sem::Builtin>(
builtin_type, match.return_type, std::move(params), supported_stages,
match.overload->flags.Contains(OverloadFlag::kIsDeprecated));
});
}
if (match_score > 0) {
candidates.emplace_back(Candidate{&overload, match_score});
} }
builder.Diagnostics().add_error(diag::System::Resolver, ss.str(), source);
};
// Resolve the intrinsic overload
auto match = MatchIntrinsic(kBuiltins[static_cast<uint32_t>(builtin_type)], intrinsic_name,
args, ClosedState(builder), on_no_match);
if (!match.overload) {
return {};
} }
// Sort the candidates with the most promising first // De-duplicate builtins that are identical.
std::stable_sort(candidates.begin(), candidates.end(), return utils::GetOrCreate(builtins, match, [&] {
[](const Candidate& a, const Candidate& b) { return a.score > b.score; }); std::vector<sem::Parameter*> params;
params.reserve(match.parameters.size());
// Generate an error message for (auto& p : match.parameters) {
std::stringstream ss; params.emplace_back(builder.create<sem::Parameter>(
ss << "no matching call to " << CallSignature(builder, intrinsic_name, args) << std::endl; nullptr, static_cast<uint32_t>(params.size()), p.type, ast::StorageClass::kNone,
if (!candidates.empty()) { ast::Access::kUndefined, p.usage));
ss << std::endl;
ss << candidates.size() << " candidate function" << (candidates.size() > 1 ? "s:" : ":")
<< std::endl;
for (auto& candidate : candidates) {
ss << " ";
PrintOverload(ss, *candidate.overload, intrinsic_name);
ss << std::endl;
} }
} sem::PipelineStageSet supported_stages;
builder.Diagnostics().add_error(diag::System::Resolver, ss.str(), source); if (match.overload->flags.Contains(OverloadFlag::kSupportsVertexPipeline)) {
return nullptr; supported_stages.Add(ast::PipelineStage::kVertex);
}
if (match.overload->flags.Contains(OverloadFlag::kSupportsFragmentPipeline)) {
supported_stages.Add(ast::PipelineStage::kFragment);
}
if (match.overload->flags.Contains(OverloadFlag::kSupportsComputePipeline)) {
supported_stages.Add(ast::PipelineStage::kCompute);
}
return builder.create<sem::Builtin>(
builtin_type, match.return_type, std::move(params), supported_stages,
match.overload->flags.Contains(OverloadFlag::kIsDeprecated));
});
} }
IntrinsicTable::UnaryOperator Impl::Lookup(ast::UnaryOp op, IntrinsicTable::UnaryOperator Impl::Lookup(ast::UnaryOp op,
const sem::Type* arg, const sem::Type* arg,
const Source& source) { const Source& source) {
// The list of failed matches that had promise.
std::vector<Candidate> candidates;
auto [intrinsic_index, intrinsic_name] = [&]() -> std::pair<uint32_t, const char*> { auto [intrinsic_index, intrinsic_name] = [&]() -> std::pair<uint32_t, const char*> {
switch (op) { switch (op) {
case ast::UnaryOp::kComplement: case ast::UnaryOp::kComplement:
@ -982,38 +1063,27 @@ IntrinsicTable::UnaryOperator Impl::Lookup(ast::UnaryOp op,
} }
}(); }();
auto& builtin = kUnaryOperators[intrinsic_index]; // Generates an error when no overloads match the provided arguments
for (uint32_t o = 0; o < builtin.num_overloads; o++) { auto on_no_match = [&, name = intrinsic_name](Candidates candidates) {
int match_score = 1000; std::stringstream ss;
auto& overload = builtin.overloads[o]; ss << "no matching overload for " << CallSignature(builder, name, {arg}) << std::endl;
auto match = Match(intrinsic_name, overload, {arg}, match_score); if (!candidates.empty()) {
if (match.return_type) { ss << std::endl
return UnaryOperator{match.return_type, match.parameters[0].type}; << candidates.size() << " candidate operator" << (candidates.size() > 1 ? "s:" : ":")
} << std::endl;
if (match_score > 0) { PrintCandidates(ss, candidates, name);
candidates.emplace_back(Candidate{&overload, match_score});
} }
builder.Diagnostics().add_error(diag::System::Resolver, ss.str(), source);
};
// Resolve the intrinsic overload
auto match = MatchIntrinsic(kUnaryOperators[intrinsic_index], intrinsic_name, {arg},
ClosedState(builder), on_no_match);
if (!match.overload) {
return {};
} }
// Sort the candidates with the most promising first return UnaryOperator{match.return_type, match.parameters[0].type};
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 overload for " << CallSignature(builder, intrinsic_name, {arg}) << std::endl;
if (!candidates.empty()) {
ss << std::endl;
ss << candidates.size() << " candidate operator" << (candidates.size() > 1 ? "s:" : ":")
<< std::endl;
for (auto& candidate : candidates) {
ss << " ";
PrintOverload(ss, *candidate.overload, intrinsic_name);
ss << std::endl;
}
}
builder.Diagnostics().add_error(diag::System::Resolver, ss.str(), source);
return {};
} }
IntrinsicTable::BinaryOperator Impl::Lookup(ast::BinaryOp op, IntrinsicTable::BinaryOperator Impl::Lookup(ast::BinaryOp op,
@ -1021,9 +1091,6 @@ IntrinsicTable::BinaryOperator Impl::Lookup(ast::BinaryOp op,
const sem::Type* rhs, const sem::Type* rhs,
const Source& source, const Source& source,
bool is_compound) { bool is_compound) {
// The list of failed matches that had promise.
std::vector<Candidate> candidates;
auto [intrinsic_index, intrinsic_name] = [&]() -> std::pair<uint32_t, const char*> { auto [intrinsic_index, intrinsic_name] = [&]() -> std::pair<uint32_t, const char*> {
switch (op) { switch (op) {
case ast::BinaryOp::kAnd: case ast::BinaryOp::kAnd:
@ -1067,47 +1134,175 @@ IntrinsicTable::BinaryOperator Impl::Lookup(ast::BinaryOp op,
} }
}(); }();
auto& builtin = kBinaryOperators[intrinsic_index]; // Generates an error when no overloads match the provided arguments
for (uint32_t o = 0; o < builtin.num_overloads; o++) { auto on_no_match = [&, name = intrinsic_name](Candidates candidates) {
int match_score = 1000; std::stringstream ss;
auto& overload = builtin.overloads[o]; ss << "no matching overload for " << CallSignature(builder, name, {lhs, rhs}) << std::endl;
auto match = Match(intrinsic_name, overload, {lhs, rhs}, match_score); if (!candidates.empty()) {
if (match.return_type) { ss << std::endl
return BinaryOperator{match.return_type, match.parameters[0].type, << candidates.size() << " candidate operator" << (candidates.size() > 1 ? "s:" : ":")
match.parameters[1].type}; << std::endl;
PrintCandidates(ss, candidates, name);
} }
if (match_score > 0) { builder.Diagnostics().add_error(diag::System::Resolver, ss.str(), source);
candidates.emplace_back(Candidate{&overload, match_score}); };
// Resolve the intrinsic overload
auto match = MatchIntrinsic(kBinaryOperators[intrinsic_index], intrinsic_name, {lhs, rhs},
ClosedState(builder), on_no_match);
if (!match.overload) {
return {};
}
return BinaryOperator{match.return_type, match.parameters[0].type, match.parameters[1].type};
}
const sem::CallTarget* Impl::Lookup(CtorConvIntrinsic type,
const sem::Type* template_arg,
const std::vector<const sem::Type*>& args,
const Source& source) {
auto name = str(type);
// Generates an error when no overloads match the provided arguments
auto on_no_match = [&](Candidates candidates) {
std::stringstream ss;
ss << "no matching constructor for " << CallSignature(builder, name, args, template_arg)
<< std::endl;
Candidates ctor, conv;
for (auto candidate : candidates) {
if (candidate.overload.flags.Contains(OverloadFlag::kIsConstructor)) {
ctor.emplace_back(candidate);
} else {
conv.emplace_back(candidate);
}
} }
if (!ctor.empty()) {
ss << std::endl
<< ctor.size() << " candidate constructor" << (ctor.size() > 1 ? "s:" : ":")
<< std::endl;
PrintCandidates(ss, ctor, name);
}
if (!conv.empty()) {
ss << std::endl
<< conv.size() << " candidate conversion" << (conv.size() > 1 ? "s:" : ":")
<< std::endl;
PrintCandidates(ss, conv, name);
}
builder.Diagnostics().add_error(diag::System::Resolver, ss.str(), source);
};
// If a template type was provided, then close the 0'th type with this.
ClosedState closed(builder);
if (template_arg) {
closed.Type(0, template_arg);
}
// Resolve the intrinsic overload
auto match = MatchIntrinsic(kConstructorsAndConverters[static_cast<uint32_t>(type)], name, args,
closed, on_no_match);
if (!match.overload) {
return {};
}
// Was this overload a constructor or conversion?
if (match.overload->flags.Contains(OverloadFlag::kIsConstructor)) {
sem::ParameterList params;
params.reserve(match.parameters.size());
for (auto& p : match.parameters) {
params.emplace_back(builder.create<sem::Parameter>(
nullptr, static_cast<uint32_t>(params.size()), p.type, ast::StorageClass::kNone,
ast::Access::kUndefined, p.usage));
}
return utils::GetOrCreate(constructors, match, [&]() {
return builder.create<sem::TypeConstructor>(match.return_type, std::move(params));
});
}
// Conversion.
return utils::GetOrCreate(converters, match, [&]() {
auto param = builder.create<sem::Parameter>(
nullptr, 0, match.parameters[0].type, ast::StorageClass::kNone, ast::Access::kUndefined,
match.parameters[0].usage);
return builder.create<sem::TypeConversion>(match.return_type, param);
});
}
IntrinsicPrototype Impl::MatchIntrinsic(const IntrinsicInfo& intrinsic,
const char* intrinsic_name,
const std::vector<const sem::Type*>& args,
ClosedState closed,
OnNoMatch on_no_match) const {
uint32_t num_matched = 0;
Candidates candidates;
candidates.reserve(intrinsic.num_overloads);
for (uint8_t overload_idx = 0; overload_idx < intrinsic.num_overloads; overload_idx++) {
auto candidate = ScoreOverload(intrinsic.overloads[overload_idx], args, closed);
if (candidate.matched) {
num_matched++;
}
candidates.emplace_back(std::move(candidate));
} }
// Sort the candidates with the most promising first // 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; }); std::vector<size_t> candidate_indices(candidates.size());
for (size_t i = 0; i < candidate_indices.size(); i++) {
// Generate an error message candidate_indices[i] = i;
std::stringstream ss;
ss << "no matching overload for " << CallSignature(builder, intrinsic_name, {lhs, rhs})
<< std::endl;
if (!candidates.empty()) {
ss << std::endl;
ss << candidates.size() << " candidate operator" << (candidates.size() > 1 ? "s:" : ":")
<< std::endl;
for (auto& candidate : candidates) {
ss << " ";
PrintOverload(ss, *candidate.overload, intrinsic_name);
ss << std::endl;
} }
std::stable_sort(candidate_indices.begin(), candidate_indices.end(),
[&](size_t a, size_t b) {
if (candidates[a].matched && !candidates[b].matched) {
return true;
}
if (candidates[b].matched && !candidates[a].matched) {
return false;
}
return candidates[a].score > candidates[b].score;
});
Candidates candidates_sorted;
candidates_sorted.reserve(candidate_indices.size());
for (size_t idx : candidate_indices) {
candidates_sorted.emplace_back(std::move(candidates[idx]));
}
std::swap(candidates, candidates_sorted);
} }
builder.Diagnostics().add_error(diag::System::Resolver, ss.str(), source);
return {}; // How many candidates matched?
switch (num_matched) {
case 0:
on_no_match(std::move(candidates));
return {};
case 1:
break;
default:
ErrMultipleOverloadsMatched(num_matched, intrinsic_name, args, closed, candidates);
}
auto match = candidates[0];
// Build the return type
const sem::Type* return_type = nullptr;
if (auto* indices = match.overload.return_matcher_indices) {
Any any;
return_type = Match(match.closed, match.overload, indices).Type(&any);
if (!return_type) {
TINT_ICE(Resolver, builder.Diagnostics()) << "MatchState.Match() returned null";
return {};
}
} else {
return_type = builder.create<sem::Void>();
}
return IntrinsicPrototype{&match.overload, return_type, std::move(match.parameters)};
} }
const IntrinsicPrototype Impl::Match(const char* intrinsic_name, Impl::Candidate Impl::ScoreOverload(const OverloadInfo& overload,
const OverloadInfo& overload, const std::vector<const sem::Type*>& args,
const std::vector<const sem::Type*>& args, ClosedState closed) const {
int& match_score) { // Score weight for argument <-> parameter count matches / mismatches
// Score wait for argument <-> parameter count matches / mismatches // This scoring is used to order the suggested overloads in diagnostic on overload mismatch, and
// has no impact for a correct program.
// The overloads with the highest score will be displayed first (top-most).
constexpr int kScorePerParamArgMismatch = -1; constexpr int kScorePerParamArgMismatch = -1;
constexpr int kScorePerMatchedParam = 2; constexpr int kScorePerMatchedParam = 2;
constexpr int kScorePerMatchedOpenType = 1; constexpr int kScorePerMatchedOpenType = 1;
@ -1117,6 +1312,7 @@ const IntrinsicPrototype Impl::Match(const char* intrinsic_name,
uint32_t num_arguments = static_cast<uint32_t>(args.size()); uint32_t num_arguments = static_cast<uint32_t>(args.size());
bool overload_matched = true; bool overload_matched = true;
int overload_score = 0;
if (static_cast<uint64_t>(args.size()) > if (static_cast<uint64_t>(args.size()) >
static_cast<uint64_t>(std::numeric_limits<uint32_t>::max())) { static_cast<uint64_t>(std::numeric_limits<uint32_t>::max())) {
@ -1124,13 +1320,11 @@ const IntrinsicPrototype Impl::Match(const char* intrinsic_name,
} }
if (num_parameters != num_arguments) { if (num_parameters != num_arguments) {
match_score += kScorePerParamArgMismatch * (std::max(num_parameters, num_arguments) - overload_score += kScorePerParamArgMismatch * (std::max(num_parameters, num_arguments) -
std::min(num_parameters, num_arguments)); std::min(num_parameters, num_arguments));
overload_matched = false; overload_matched = false;
} }
ClosedState closed(builder);
std::vector<IntrinsicPrototype::Parameter> parameters; std::vector<IntrinsicPrototype::Parameter> parameters;
auto num_params = std::min(num_parameters, num_arguments); auto num_params = std::min(num_parameters, num_arguments);
@ -1140,7 +1334,7 @@ const IntrinsicPrototype Impl::Match(const char* intrinsic_name,
auto* type = Match(closed, overload, indices).Type(args[p]->UnwrapRef()); auto* type = Match(closed, overload, indices).Type(args[p]->UnwrapRef());
if (type) { if (type) {
parameters.emplace_back(IntrinsicPrototype::Parameter{type, parameter.usage}); parameters.emplace_back(IntrinsicPrototype::Parameter{type, parameter.usage});
match_score += kScorePerMatchedParam; overload_score += kScorePerMatchedParam;
} else { } else {
overload_matched = false; overload_matched = false;
} }
@ -1151,9 +1345,10 @@ const IntrinsicPrototype Impl::Match(const char* intrinsic_name,
for (uint32_t ot = 0; ot < overload.num_open_types; ot++) { for (uint32_t ot = 0; ot < overload.num_open_types; ot++) {
auto& open_type = overload.open_types[ot]; auto& open_type = overload.open_types[ot];
if (open_type.matcher_index != kNoMatcher) { if (open_type.matcher_index != kNoMatcher) {
auto* index = &open_type.matcher_index; auto* closed_type = closed.Type(ot);
if (Match(closed, overload, index).Type(closed.Type(ot))) { auto* matcher_index = &open_type.matcher_index;
match_score += kScorePerMatchedOpenType; if (closed_type && Match(closed, overload, matcher_index).Type(closed_type)) {
overload_score += kScorePerMatchedOpenType;
} else { } else {
overload_matched = false; overload_matched = false;
} }
@ -1168,7 +1363,7 @@ const IntrinsicPrototype Impl::Match(const char* intrinsic_name,
if (open_number.matcher_index != kNoMatcher) { if (open_number.matcher_index != kNoMatcher) {
auto* index = &open_number.matcher_index; auto* index = &open_number.matcher_index;
if (Match(closed, overload, index).Num(closed.Num(on)).IsValid()) { if (Match(closed, overload, index).Num(closed.Num(on)).IsValid()) {
match_score += kScorePerMatchedOpenNumber; overload_score += kScorePerMatchedOpenNumber;
} else { } else {
overload_matched = false; overload_matched = false;
} }
@ -1176,31 +1371,7 @@ const IntrinsicPrototype Impl::Match(const char* intrinsic_name,
} }
} }
if (!overload_matched) { return Candidate{overload, closed, parameters, overload_matched, overload_score};
return {};
}
// Build the return type
const sem::Type* return_type = nullptr;
if (auto* indices = overload.return_matcher_indices) {
Any any;
return_type = Match(closed, overload, indices).Type(&any);
if (!return_type) {
std::stringstream ss;
PrintOverload(ss, overload, intrinsic_name);
TINT_ICE(Resolver, builder.Diagnostics())
<< "MatchState.Match() returned null for " << ss.str();
return {};
}
} else {
return_type = builder.create<sem::Void>();
}
IntrinsicPrototype builtin;
builtin.overload = &overload;
builtin.return_type = return_type;
builtin.parameters = std::move(parameters);
return builtin;
} }
MatchState Impl::Match(ClosedState& closed, MatchState Impl::Match(ClosedState& closed,
@ -1209,10 +1380,12 @@ MatchState Impl::Match(ClosedState& closed,
return MatchState(builder, closed, matchers, overload, matcher_indices); return MatchState(builder, closed, matchers, overload, matcher_indices);
} }
void Impl::PrintOverload(std::ostream& ss, const OverloadInfo& overload, const char* name) const { void Impl::PrintOverload(std::ostream& ss,
const OverloadInfo& overload,
const char* intrinsic_name) const {
ClosedState closed(builder); ClosedState closed(builder);
ss << name << "("; ss << intrinsic_name << "(";
for (uint32_t p = 0; p < overload.num_parameters; p++) { for (uint32_t p = 0; p < overload.num_parameters; p++) {
auto& parameter = overload.parameters[p]; auto& parameter = overload.parameters[p];
if (p > 0) { if (p > 0) {
@ -1256,6 +1429,16 @@ void Impl::PrintOverload(std::ostream& ss, const OverloadInfo& overload, const c
} }
} }
void Impl::PrintCandidates(std::ostream& ss,
const Candidates& candidates,
const char* intrinsic_name) const {
for (auto& candidate : candidates) {
ss << " ";
PrintOverload(ss, candidate.overload, intrinsic_name);
ss << std::endl;
}
}
const sem::Type* MatchState::Type(const sem::Type* ty) { const sem::Type* MatchState::Type(const sem::Type* ty) {
MatcherIndex matcher_index = *matcher_indices_++; MatcherIndex matcher_index = *matcher_indices_++;
auto* matcher = matchers.type[matcher_index]; auto* matcher = matchers.type[matcher_index];
@ -1280,6 +1463,41 @@ std::string MatchState::NumName() {
return matcher->String(*this); return matcher->String(*this);
} }
void Impl::ErrMultipleOverloadsMatched(uint32_t num_matched,
const char* intrinsic_name,
const std::vector<const sem::Type*>& args,
ClosedState closed,
Candidates candidates) const {
std::stringstream ss;
ss << num_matched << " overloads matched " << intrinsic_name;
for (uint32_t i = 0; i < 0xffffffffu; i++) {
if (auto* ty = closed.Type(i)) {
ss << ((i == 0) ? "<" : ", ") << ty->FriendlyName(builder.Symbols());
} else if (i > 0) {
ss << ">";
break;
}
}
ss << "(";
bool first = true;
for (auto* arg : args) {
if (!first) {
ss << ", ";
}
first = false;
ss << arg->FriendlyName(builder.Symbols());
}
ss << "):\n";
for (auto& candidate : candidates) {
if (candidate.matched) {
ss << " ";
PrintOverload(ss, candidate.overload, intrinsic_name);
ss << std::endl;
}
}
TINT_ICE(Resolver, builder.Diagnostics()) << ss.str();
}
} // namespace } // namespace
std::unique_ptr<IntrinsicTable> IntrinsicTable::Create(ProgramBuilder& builder) { std::unique_ptr<IntrinsicTable> IntrinsicTable::Create(ProgramBuilder& builder) {

View File

@ -19,6 +19,7 @@
#include <string> #include <string>
#include <vector> #include <vector>
#include "src/tint/resolver/ctor_conv_intrinsic.h"
#include "src/tint/sem/builtin.h" #include "src/tint/sem/builtin.h"
// Forward declarations // Forward declarations
@ -89,6 +90,18 @@ class IntrinsicTable {
const sem::Type* rhs, const sem::Type* rhs,
const Source& source, const Source& source,
bool is_compound) = 0; bool is_compound) = 0;
/// Lookup looks for the type constructor or conversion overload for the given
/// CtorConvIntrinsic.
/// @param type the type being constructed or converted
/// @param template_arg the optional template argument
/// @param args the argument types passed to the constructor / conversion call
/// @param source the source of the call
/// @return a sem::TypeConstructor, sem::TypeConversion or nullptr if nothing matched
virtual const sem::CallTarget* Lookup(CtorConvIntrinsic type,
const sem::Type* template_arg,
const std::vector<const sem::Type*>& args,
const Source& source) = 0;
}; };
} // namespace tint::resolver } // namespace tint::resolver

File diff suppressed because it is too large Load Diff

View File

@ -153,6 +153,19 @@ constexpr IntrinsicInfo kBinaryOperators[] = {
constexpr uint8_t kBinaryOperator{{template "OperatorName" $o.Name}} = {{$i}}; constexpr uint8_t kBinaryOperator{{template "OperatorName" $o.Name}} = {{$i}};
{{- end }} {{- end }}
constexpr IntrinsicInfo kConstructorsAndConverters[] = {
{{- range $i, $o := .ConstructorsAndConverters }}
{
/* [{{$i}}] */
{{- range $o.OverloadDescriptions }}
/* {{.}} */
{{- end }}
/* num overloads */ {{$o.NumOverloads}},
/* overloads */ &kOverloads[{{$o.OverloadsOffset}}],
},
{{- end }}
};
// clang-format on // clang-format on
{{ end -}} {{ end -}}

View File

@ -26,6 +26,8 @@
#include "src/tint/sem/reference.h" #include "src/tint/sem/reference.h"
#include "src/tint/sem/sampled_texture.h" #include "src/tint/sem/sampled_texture.h"
#include "src/tint/sem/storage_texture.h" #include "src/tint/sem/storage_texture.h"
#include "src/tint/sem/type_constructor.h"
#include "src/tint/sem/type_conversion.h"
namespace tint::resolver { namespace tint::resolver {
namespace { namespace {
@ -663,6 +665,118 @@ TEST_F(IntrinsicTableTest, MismatchCompoundOp) {
)"); )");
} }
TEST_F(IntrinsicTableTest, MatchTypeConstructorImplicit) {
auto* i32 = create<sem::I32>();
auto* vec3_i32 = create<sem::Vector>(i32, 3u);
auto* result =
table->Lookup(CtorConvIntrinsic::kVec3, nullptr, {i32, i32, i32}, Source{{12, 34}});
ASSERT_NE(result, nullptr);
EXPECT_EQ(result->ReturnType(), vec3_i32);
EXPECT_TRUE(result->Is<sem::TypeConstructor>());
ASSERT_EQ(result->Parameters().size(), 3u);
EXPECT_EQ(result->Parameters()[0]->Type(), i32);
EXPECT_EQ(result->Parameters()[1]->Type(), i32);
EXPECT_EQ(result->Parameters()[2]->Type(), i32);
}
TEST_F(IntrinsicTableTest, MatchTypeConstructorExplicit) {
auto* i32 = create<sem::I32>();
auto* vec3_i32 = create<sem::Vector>(i32, 3u);
auto* result = table->Lookup(CtorConvIntrinsic::kVec3, i32, {i32, i32, i32}, Source{{12, 34}});
ASSERT_NE(result, nullptr);
EXPECT_EQ(result->ReturnType(), vec3_i32);
EXPECT_TRUE(result->Is<sem::TypeConstructor>());
ASSERT_EQ(result->Parameters().size(), 3u);
EXPECT_EQ(result->Parameters()[0]->Type(), i32);
EXPECT_EQ(result->Parameters()[1]->Type(), i32);
EXPECT_EQ(result->Parameters()[2]->Type(), i32);
}
TEST_F(IntrinsicTableTest, MismatchTypeConstructorImplicit) {
auto* i32 = create<sem::I32>();
auto* f32 = create<sem::F32>();
auto* result =
table->Lookup(CtorConvIntrinsic::kVec3, nullptr, {i32, f32, i32}, Source{{12, 34}});
ASSERT_EQ(result, nullptr);
EXPECT_EQ(Diagnostics().str(), R"(12:34 error: no matching constructor for vec3(i32, f32, i32)
6 candidate constructors:
vec3(x: T, y: T, z: T) -> vec3<T> where: T is f32, i32, u32 or bool
vec3(xy: vec2<T>, z: T) -> vec3<T> where: T is f32, i32, u32 or bool
vec3(x: T, yz: vec2<T>) -> vec3<T> where: T is f32, i32, u32 or bool
vec3(T) -> vec3<T> where: T is f32, i32, u32 or bool
vec3(vec3<T>) -> vec3<T> where: T is f32, i32, u32 or bool
vec3() -> vec3<T> where: T is f32, i32, u32 or bool
4 candidate conversions:
vec3(vec3<U>) -> vec3<f32> where: T is f32, U is i32, u32 or bool
vec3(vec3<U>) -> vec3<i32> where: T is i32, U is f32, u32 or bool
vec3(vec3<U>) -> vec3<u32> where: T is u32, U is f32, i32 or bool
vec3(vec3<U>) -> vec3<bool> where: T is bool, U is f32, i32 or u32
)");
}
TEST_F(IntrinsicTableTest, MismatchTypeConstructorExplicit) {
auto* i32 = create<sem::I32>();
auto* f32 = create<sem::F32>();
auto* result = table->Lookup(CtorConvIntrinsic::kVec3, i32, {i32, f32, i32}, Source{{12, 34}});
ASSERT_EQ(result, nullptr);
EXPECT_EQ(Diagnostics().str(),
R"(12:34 error: no matching constructor for vec3<i32>(i32, f32, i32)
6 candidate constructors:
vec3(x: T, y: T, z: T) -> vec3<T> where: T is f32, i32, u32 or bool
vec3(x: T, yz: vec2<T>) -> vec3<T> where: T is f32, i32, u32 or bool
vec3(T) -> vec3<T> where: T is f32, i32, u32 or bool
vec3(xy: vec2<T>, z: T) -> vec3<T> where: T is f32, i32, u32 or bool
vec3(vec3<T>) -> vec3<T> where: T is f32, i32, u32 or bool
vec3() -> vec3<T> where: T is f32, i32, u32 or bool
4 candidate conversions:
vec3(vec3<U>) -> vec3<f32> where: T is f32, U is i32, u32 or bool
vec3(vec3<U>) -> vec3<i32> where: T is i32, U is f32, u32 or bool
vec3(vec3<U>) -> vec3<u32> where: T is u32, U is f32, i32 or bool
vec3(vec3<U>) -> vec3<bool> where: T is bool, U is f32, i32 or u32
)");
}
TEST_F(IntrinsicTableTest, MatchTypeConversion) {
auto* i32 = create<sem::I32>();
auto* vec3_i32 = create<sem::Vector>(i32, 3u);
auto* f32 = create<sem::F32>();
auto* vec3_f32 = create<sem::Vector>(f32, 3u);
auto* result = table->Lookup(CtorConvIntrinsic::kVec3, i32, {vec3_f32}, Source{{12, 34}});
ASSERT_NE(result, nullptr);
EXPECT_EQ(result->ReturnType(), vec3_i32);
EXPECT_TRUE(result->Is<sem::TypeConversion>());
ASSERT_EQ(result->Parameters().size(), 1u);
EXPECT_EQ(result->Parameters()[0]->Type(), vec3_f32);
}
TEST_F(IntrinsicTableTest, MismatchTypeConversion) {
auto* arr = create<sem::Array>(create<sem::U32>(), 0u, 4u, 4u, 4u, 4u);
auto* f32 = create<sem::F32>();
auto* result = table->Lookup(CtorConvIntrinsic::kVec3, f32, {arr}, Source{{12, 34}});
ASSERT_EQ(result, nullptr);
EXPECT_EQ(Diagnostics().str(),
R"(12:34 error: no matching constructor for vec3<f32>(array<u32>)
6 candidate constructors:
vec3(vec3<T>) -> vec3<T> where: T is f32, i32, u32 or bool
vec3(T) -> vec3<T> where: T is f32, i32, u32 or bool
vec3() -> vec3<T> where: T is f32, i32, u32 or bool
vec3(xy: vec2<T>, z: T) -> vec3<T> where: T is f32, i32, u32 or bool
vec3(x: T, yz: vec2<T>) -> vec3<T> where: T is f32, i32, u32 or bool
vec3(x: T, y: T, z: T) -> vec3<T> where: T is f32, i32, u32 or bool
4 candidate conversions:
vec3(vec3<U>) -> vec3<f32> where: T is f32, U is i32, u32 or bool
vec3(vec3<U>) -> vec3<i32> where: T is i32, U is f32, u32 or bool
vec3(vec3<U>) -> vec3<u32> where: T is u32, U is f32, i32 or bool
vec3(vec3<U>) -> vec3<bool> where: T is bool, U is f32, i32 or u32
)");
}
TEST_F(IntrinsicTableTest, Err257Arguments) { // crbug.com/1323605 TEST_F(IntrinsicTableTest, Err257Arguments) { // crbug.com/1323605
auto* f32 = create<sem::F32>(); auto* f32 = create<sem::F32>();
std::vector<const sem::Type*> arg_tys(257, f32); std::vector<const sem::Type*> arg_tys(257, f32);

View File

@ -1148,155 +1148,186 @@ sem::Expression* Resolver::Bitcast(const ast::BitcastExpression* expr) {
} }
sem::Call* Resolver::Call(const ast::CallExpression* expr) { sem::Call* Resolver::Call(const ast::CallExpression* expr) {
// A CallExpression can resolve to one of:
// * A function call.
// * A builtin call.
// * A type constructor.
// * A type conversion.
// Resolve all of the arguments, their types and the set of behaviors.
std::vector<const sem::Expression*> args(expr->args.size()); std::vector<const sem::Expression*> args(expr->args.size());
std::vector<const sem::Type*> arg_tys(args.size()); std::vector<const sem::Type*> arg_tys(expr->args.size());
sem::Behaviors arg_behaviors; sem::Behaviors arg_behaviors;
// The element type of all the arguments. Nullptr if argument types are
// different.
const sem::Type* arg_el_ty = nullptr;
for (size_t i = 0; i < expr->args.size(); i++) { for (size_t i = 0; i < expr->args.size(); i++) {
auto* arg = sem_.Get(expr->args[i]); auto* arg = sem_.Get(expr->args[i]);
if (!arg) { if (!arg) {
return nullptr; return nullptr;
} }
args[i] = arg; args[i] = arg;
arg_tys[i] = args[i]->Type(); arg_tys[i] = arg->Type();
arg_behaviors.Add(arg->Behaviors()); arg_behaviors.Add(arg->Behaviors());
// Determine the common argument element type
auto* el_ty = arg_tys[i]->UnwrapRef();
if (auto* vec = el_ty->As<sem::Vector>()) {
el_ty = vec->type();
} else if (auto* mat = el_ty->As<sem::Matrix>()) {
el_ty = mat->type();
}
if (i == 0) {
arg_el_ty = el_ty;
} else if (arg_el_ty != el_ty) {
arg_el_ty = nullptr;
}
} }
arg_behaviors.Remove(sem::Behavior::kNext); arg_behaviors.Remove(sem::Behavior::kNext);
auto type_ctor_or_conv = [&](const sem::Type* ty) -> sem::Call* { // Did any arguments have side effects?
// The call has resolved to a type constructor or cast. bool has_side_effects =
if (args.size() == 1) { std::any_of(args.begin(), args.end(), [](auto* e) { return e->HasSideEffects(); });
auto* target = ty;
auto* source = args[0]->Type()->UnwrapRef(); // array_or_struct_ctor is a helper for building a sem::TypeConstructor call for an array or
if ((source != target) && // // structure type. These types have constructors that are always explicitly typed (no
((source->is_scalar() && target->is_scalar()) || // inference), and do not support type conversion. As such, they do not use the IntrinsicTable.
(source->Is<sem::Vector>() && target->Is<sem::Vector>()) || auto array_or_struct_ctor = [&](const sem::Type* ty) -> sem::Call* {
(source->Is<sem::Matrix>() && target->Is<sem::Matrix>()))) { auto* call_target = utils::GetOrCreate(
// Note: Matrix types currently cannot be converted (the element type type_ctors_, TypeConstructorSig{ty, arg_tys}, [&]() -> sem::TypeConstructor* {
// must only be f32). We implement this for the day we support other return builder_->create<sem::TypeConstructor>(
// matrix element types. ty, utils::Transform(
return TypeConversion(expr, ty, args[0], arg_tys[0]); arg_tys, [&](const sem::Type* t, size_t i) -> const sem::Parameter* {
} return builder_->create<sem::Parameter>(
} nullptr, // declaration
return TypeConstructor(expr, ty, std::move(args), std::move(arg_tys)); static_cast<uint32_t>(i), // index
t->UnwrapRef(), // type
ast::StorageClass::kNone, // storage_class
ast::Access::kUndefined); // access
}));
});
auto value = EvaluateConstantValue(expr, ty);
return builder_->create<sem::Call>(expr, call_target, std::move(args), current_statement_,
value, has_side_effects);
}; };
// Resolve the target of the CallExpression to determine whether this is a // ct_ctor_or_conv is a helper for building either a sem::TypeConstructor or sem::TypeConversion
// function call, cast or type constructor expression. // call for a CtorConvIntrinsic with an optional template argument type.
if (expr->target.type) { auto ct_ctor_or_conv = [&](CtorConvIntrinsic ty, const sem::Type* template_arg) -> sem::Call* {
const sem::Type* ty = nullptr; auto* call_target = intrinsic_table_->Lookup(ty, template_arg, arg_tys, expr->source);
if (!call_target) {
auto err_cannot_infer_el_ty = [&](std::string name) { return nullptr;
AddError("cannot infer " + name +
" element type, as constructor arguments have different types",
expr->source);
for (size_t i = 0; i < args.size(); i++) {
auto* arg = args[i];
AddNote("argument " + std::to_string(i) + " has type " +
arg->Type()->FriendlyName(builder_->Symbols()),
arg->Declaration()->source);
}
};
if (!expr->args.empty()) {
// vecN() without explicit element type?
// Try to infer element type from args
if (auto* vec = expr->target.type->As<ast::Vector>()) {
if (!vec->type) {
if (!arg_el_ty) {
err_cannot_infer_el_ty("vector");
return nullptr;
}
Mark(vec);
auto* v =
builder_->create<sem::Vector>(arg_el_ty, static_cast<uint32_t>(vec->width));
if (!validator_.Vector(v, vec->source)) {
return nullptr;
}
builder_->Sem().Add(vec, v);
ty = v;
}
}
// matNxM() without explicit element type?
// Try to infer element type from args
if (auto* mat = expr->target.type->As<ast::Matrix>()) {
if (!mat->type) {
if (!arg_el_ty) {
err_cannot_infer_el_ty("matrix");
return nullptr;
}
Mark(mat);
auto* column_type = builder_->create<sem::Vector>(arg_el_ty, mat->rows);
auto* m = builder_->create<sem::Matrix>(column_type, mat->columns);
if (!validator_.Matrix(m, mat->source)) {
return nullptr;
}
builder_->Sem().Add(mat, m);
ty = m;
}
}
} }
auto value = EvaluateConstantValue(expr, call_target->ReturnType());
return builder_->create<sem::Call>(expr, call_target, std::move(args), current_statement_,
value, has_side_effects);
};
if (ty == nullptr) { // ct_ctor_or_conv is a helper for building either a sem::TypeConstructor or sem::TypeConversion
ty = Type(expr->target.type); // call for the given semantic type.
if (!ty) { auto ty_ctor_or_conv = [&](const sem::Type* ty) {
return Switch(
ty, //
[&](const sem::Vector* v) {
return ct_ctor_or_conv(VectorCtorConvIntrinsic(v->Width()), v->type());
},
[&](const sem::Matrix* m) {
return ct_ctor_or_conv(MatrixCtorConvIntrinsic(m->columns(), m->rows()), m->type());
},
[&](const sem::I32*) { return ct_ctor_or_conv(CtorConvIntrinsic::kI32, nullptr); },
[&](const sem::U32*) { return ct_ctor_or_conv(CtorConvIntrinsic::kU32, nullptr); },
[&](const sem::F32*) { return ct_ctor_or_conv(CtorConvIntrinsic::kF32, nullptr); },
[&](const sem::Bool*) { return ct_ctor_or_conv(CtorConvIntrinsic::kBool, nullptr); },
[&](const sem::Array*) { return array_or_struct_ctor(ty); },
[&](const sem::Struct*) { return array_or_struct_ctor(ty); },
[&](Default) {
AddError("type is not constructible", expr->source);
return nullptr; return nullptr;
} });
} };
return type_ctor_or_conv(ty); // ast::CallExpression has a target which is either an ast::Type or an ast::IdentifierExpression
sem::Call* call = nullptr;
if (expr->target.type) {
// ast::CallExpression has an ast::Type as the target.
// This call is either a type constructor or type conversion.
call = Switch(
expr->target.type,
[&](const ast::Vector* v) -> sem::Call* {
Mark(v);
// vector element type must be inferred if it was not specified.
sem::Type* template_arg = nullptr;
if (v->type) {
template_arg = Type(v->type);
if (!template_arg) {
return nullptr;
}
}
if (auto* c = ct_ctor_or_conv(VectorCtorConvIntrinsic(v->width), template_arg)) {
builder_->Sem().Add(expr->target.type, c->Target()->ReturnType());
return c;
}
return nullptr;
},
[&](const ast::Matrix* m) -> sem::Call* {
Mark(m);
// matrix element type must be inferred if it was not specified.
sem::Type* template_arg = nullptr;
if (m->type) {
template_arg = Type(m->type);
if (!template_arg) {
return nullptr;
}
}
if (auto* c = ct_ctor_or_conv(MatrixCtorConvIntrinsic(m->columns, m->rows),
template_arg)) {
builder_->Sem().Add(expr->target.type, c->Target()->ReturnType());
return c;
}
return nullptr;
},
[&](const ast::Type* ast) -> sem::Call* {
// Handler for AST types that do not have an optional element type.
if (auto* ty = Type(ast)) {
return ty_ctor_or_conv(ty);
}
return nullptr;
},
[&](Default) {
TINT_ICE(Resolver, diagnostics_)
<< expr->source << " unhandled CallExpression target:\n"
<< "type: "
<< (expr->target.type ? expr->target.type->TypeInfo().name : "<null>");
return nullptr;
});
} else {
// ast::CallExpression has an ast::IdentifierExpression as the target.
// This call is either a function call, builtin call, type constructor or type conversion.
auto* ident = expr->target.name;
Mark(ident);
auto* resolved = sem_.ResolvedSymbol(ident);
call = Switch<sem::Call*>(
resolved, //
[&](sem::Type* ty) {
// A type constructor or conversions.
// Note: Unlike the codepath where we're resolving the call target from an
// ast::Type, all types must already have the element type explicitly specified, so
// there's no need to infer element types.
return ty_ctor_or_conv(ty);
},
[&](sem::Function* func) {
return FunctionCall(expr, func, std::move(args), arg_behaviors);
},
[&](sem::Variable* var) {
auto name = builder_->Symbols().NameFor(var->Declaration()->symbol);
AddError("cannot call variable '" + name + "'", ident->source);
AddNote("'" + name + "' declared here", var->Declaration()->source);
return nullptr;
},
[&](Default) -> sem::Call* {
auto name = builder_->Symbols().NameFor(ident->symbol);
auto builtin_type = sem::ParseBuiltinType(name);
if (builtin_type != sem::BuiltinType::kNone) {
return BuiltinCall(expr, builtin_type, std::move(args), std::move(arg_tys));
}
TINT_ICE(Resolver, diagnostics_)
<< expr->source << " unhandled CallExpression target:\n"
<< "resolved: " << (resolved ? resolved->TypeInfo().name : "<null>") << "\n"
<< "name: " << builder_->Symbols().NameFor(ident->symbol);
return nullptr;
});
} }
auto* ident = expr->target.name; if (!call) {
Mark(ident); return nullptr;
}
auto* resolved = sem_.ResolvedSymbol(ident); return validator_.Call(call, current_statement_) ? call : nullptr;
return Switch(
resolved, //
[&](sem::Type* type) { return type_ctor_or_conv(type); },
[&](sem::Function* func) {
return FunctionCall(expr, func, std::move(args), arg_behaviors);
},
[&](sem::Variable* var) {
auto name = builder_->Symbols().NameFor(var->Declaration()->symbol);
AddError("cannot call variable '" + name + "'", ident->source);
AddNote("'" + name + "' declared here", var->Declaration()->source);
return nullptr;
},
[&](Default) -> sem::Call* {
auto name = builder_->Symbols().NameFor(ident->symbol);
auto builtin_type = sem::ParseBuiltinType(name);
if (builtin_type != sem::BuiltinType::kNone) {
return BuiltinCall(expr, builtin_type, std::move(args), std::move(arg_tys));
}
TINT_ICE(Resolver, diagnostics_)
<< expr->source << " unresolved CallExpression target:\n"
<< "resolved: " << (resolved ? resolved->TypeInfo().name : "<null>") << "\n"
<< "name: " << builder_->Symbols().NameFor(ident->symbol);
return nullptr;
});
} }
sem::Call* Resolver::BuiltinCall(const ast::CallExpression* expr, sem::Call* Resolver::BuiltinCall(const ast::CallExpression* expr,
@ -1414,133 +1445,6 @@ sem::Call* Resolver::FunctionCall(const ast::CallExpression* expr,
return call; return call;
} }
sem::Call* Resolver::TypeConversion(const ast::CallExpression* expr,
const sem::Type* target,
const sem::Expression* arg,
const sem::Type* source) {
// It is not valid to have a type-cast call expression inside a call
// statement.
if (IsCallStatement(expr)) {
AddError("type cast evaluated but not used", expr->source);
return nullptr;
}
auto* call_target = utils::GetOrCreate(
type_conversions_, TypeConversionSig{target, source}, [&]() -> sem::TypeConversion* {
// Now that the argument types have been determined, make sure that
// they obey the conversion rules laid out in
// https://gpuweb.github.io/gpuweb/wgsl/#conversion-expr.
bool ok = Switch(
target,
[&](const sem::Vector* vec_type) {
return validator_.VectorConstructorOrCast(expr, vec_type);
},
[&](const sem::Matrix* mat_type) {
// Note: Matrix types currently cannot be converted (the element
// type must only be f32). We implement this for the day we
// support other matrix element types.
return validator_.MatrixConstructorOrCast(expr, mat_type);
},
[&](const sem::Array* arr_type) {
return validator_.ArrayConstructorOrCast(expr, arr_type);
},
[&](const sem::Struct* struct_type) {
return validator_.StructureConstructorOrCast(expr, struct_type);
},
[&](Default) {
if (target->is_scalar()) {
return validator_.ScalarConstructorOrCast(expr, target);
}
AddError("type is not constructible", expr->source);
return false;
});
if (!ok) {
return nullptr;
}
auto* param =
builder_->create<sem::Parameter>(nullptr, // declaration
0, // index
source->UnwrapRef(), // type
ast::StorageClass::kNone, // storage_class
ast::Access::kUndefined); // access
return builder_->create<sem::TypeConversion>(target, param);
});
if (!call_target) {
return nullptr;
}
auto val = EvaluateConstantValue(expr, target);
bool has_side_effects = arg->HasSideEffects();
return builder_->create<sem::Call>(expr, call_target, std::vector<const sem::Expression*>{arg},
current_statement_, val, has_side_effects);
}
sem::Call* Resolver::TypeConstructor(const ast::CallExpression* expr,
const sem::Type* ty,
const std::vector<const sem::Expression*> args,
const std::vector<const sem::Type*> arg_tys) {
// It is not valid to have a type-constructor call expression as a call
// statement.
if (IsCallStatement(expr)) {
AddError("type constructor evaluated but not used", expr->source);
return nullptr;
}
auto* call_target = utils::GetOrCreate(
type_ctors_, TypeConstructorSig{ty, arg_tys}, [&]() -> sem::TypeConstructor* {
// Now that the argument types have been determined, make sure that
// they obey the constructor type rules laid out in
// https://gpuweb.github.io/gpuweb/wgsl/#type-constructor-expr.
bool ok = Switch(
ty,
[&](const sem::Vector* vec_type) {
return validator_.VectorConstructorOrCast(expr, vec_type);
},
[&](const sem::Matrix* mat_type) {
return validator_.MatrixConstructorOrCast(expr, mat_type);
},
[&](const sem::Array* arr_type) {
return validator_.ArrayConstructorOrCast(expr, arr_type);
},
[&](const sem::Struct* struct_type) {
return validator_.StructureConstructorOrCast(expr, struct_type);
},
[&](Default) {
if (ty->is_scalar()) {
return validator_.ScalarConstructorOrCast(expr, ty);
}
AddError("type is not constructible", expr->source);
return false;
});
if (!ok) {
return nullptr;
}
return builder_->create<sem::TypeConstructor>(
ty, utils::Transform(arg_tys,
[&](const sem::Type* t, size_t i) -> const sem::Parameter* {
return builder_->create<sem::Parameter>(
nullptr, // declaration
static_cast<uint32_t>(i), // index
t->UnwrapRef(), // type
ast::StorageClass::kNone, // storage_class
ast::Access::kUndefined); // access
}));
});
if (!call_target) {
return nullptr;
}
auto val = EvaluateConstantValue(expr, ty);
bool has_side_effects =
std::any_of(args.begin(), args.end(), [](auto* e) { return e->HasSideEffects(); });
return builder_->create<sem::Call>(expr, call_target, std::move(args), current_statement_, val,
has_side_effects);
}
sem::Expression* Resolver::Literal(const ast::LiteralExpression* literal) { sem::Expression* Resolver::Literal(const ast::LiteralExpression* literal) {
auto* ty = Switch( auto* ty = Switch(
literal, literal,
@ -2464,22 +2368,6 @@ bool Resolver::IsBuiltin(Symbol symbol) const {
return sem::ParseBuiltinType(name) != sem::BuiltinType::kNone; return sem::ParseBuiltinType(name) != sem::BuiltinType::kNone;
} }
bool Resolver::IsCallStatement(const ast::Expression* expr) const {
return current_statement_ &&
Is<ast::CallStatement>(current_statement_->Declaration(),
[&](auto* stmt) { return stmt->expr == expr; });
}
////////////////////////////////////////////////////////////////////////////////
// Resolver::TypeConversionSig
////////////////////////////////////////////////////////////////////////////////
bool Resolver::TypeConversionSig::operator==(const TypeConversionSig& rhs) const {
return target == rhs.target && source == rhs.source;
}
std::size_t Resolver::TypeConversionSig::Hasher::operator()(const TypeConversionSig& sig) const {
return utils::Hash(sig.target, sig.source);
}
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
// Resolver::TypeConstructorSig // Resolver::TypeConstructorSig
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////

View File

@ -193,14 +193,6 @@ class Resolver {
const std::vector<const sem::Type*> arg_tys); const std::vector<const sem::Type*> arg_tys);
sem::Expression* Literal(const ast::LiteralExpression*); sem::Expression* Literal(const ast::LiteralExpression*);
sem::Expression* MemberAccessor(const ast::MemberAccessorExpression*); sem::Expression* MemberAccessor(const ast::MemberAccessorExpression*);
sem::Call* TypeConversion(const ast::CallExpression* expr,
const sem::Type* ty,
const sem::Expression* arg,
const sem::Type* arg_ty);
sem::Call* TypeConstructor(const ast::CallExpression* expr,
const sem::Type* ty,
const std::vector<const sem::Expression*> args,
const std::vector<const sem::Type*> arg_tys);
sem::Expression* UnaryOp(const ast::UnaryOpExpression*); sem::Expression* UnaryOp(const ast::UnaryOpExpression*);
// Statement resolving methods // Statement resolving methods
@ -226,7 +218,6 @@ class Resolver {
sem::Statement* VariableDeclStatement(const ast::VariableDeclStatement*); sem::Statement* VariableDeclStatement(const ast::VariableDeclStatement*);
bool Statements(const ast::StatementList&); bool Statements(const ast::StatementList&);
/// Resolves the WorkgroupSize for the given function, assigning it to /// Resolves the WorkgroupSize for the given function, assigning it to
/// current_function_ /// current_function_
bool WorkgroupSize(const ast::Function*); bool WorkgroupSize(const ast::Function*);
@ -337,23 +328,6 @@ class Resolver {
/// @returns true if the symbol is the name of a builtin function. /// @returns true if the symbol is the name of a builtin function.
bool IsBuiltin(Symbol) const; bool IsBuiltin(Symbol) const;
/// @returns true if `expr` is the current CallStatement's CallExpression
bool IsCallStatement(const ast::Expression* expr) const;
struct TypeConversionSig {
const sem::Type* target;
const sem::Type* source;
bool operator==(const TypeConversionSig&) const;
/// Hasher provides a hash function for the TypeConversionSig
struct Hasher {
/// @param sig the TypeConversionSig to create a hash for
/// @return the hash value
std::size_t operator()(const TypeConversionSig& sig) const;
};
};
struct TypeConstructorSig { struct TypeConstructorSig {
const sem::Type* type; const sem::Type* type;
const std::vector<const sem::Type*> parameters; const std::vector<const sem::Type*> parameters;
@ -381,8 +355,6 @@ class Resolver {
std::unordered_map<const sem::Type*, const Source&> atomic_composite_info_; std::unordered_map<const sem::Type*, const Source&> atomic_composite_info_;
std::unordered_set<const ast::Node*> marked_; std::unordered_set<const ast::Node*> marked_;
std::unordered_map<uint32_t, const sem::Variable*> constant_ids_; std::unordered_map<uint32_t, const sem::Variable*> constant_ids_;
std::unordered_map<TypeConversionSig, sem::CallTarget*, TypeConversionSig::Hasher>
type_conversions_;
std::unordered_map<TypeConstructorSig, sem::CallTarget*, TypeConstructorSig::Hasher> std::unordered_map<TypeConstructorSig, sem::CallTarget*, TypeConstructorSig::Hasher>
type_ctors_; type_ctors_;

File diff suppressed because it is too large Load Diff

View File

@ -1356,6 +1356,35 @@ bool Validator::ContinueStatement(const sem::Statement* stmt,
return true; return true;
} }
bool Validator::Call(const sem::Call* call, sem::Statement* current_statement) const {
auto* expr = call->Declaration();
bool is_call_stmt =
current_statement && Is<ast::CallStatement>(current_statement->Declaration(),
[&](auto* stmt) { return stmt->expr == expr; });
return Switch(
call->Target(), //
[&](const sem::TypeConversion*) {
if (is_call_stmt) {
AddError("type conversion evaluated but not used", call->Declaration()->source);
return false;
}
return true;
},
[&](const sem::TypeConstructor* ctor) {
if (is_call_stmt) {
AddError("type constructor evaluated but not used", call->Declaration()->source);
return false;
}
return Switch(
ctor->ReturnType(), //
[&](const sem::Array* arr) { return ArrayConstructor(expr, arr); },
[&](const sem::Struct* str) { return StructureConstructor(expr, str); },
[&](Default) { return true; });
},
[&](Default) { return true; });
}
bool Validator::DiscardStatement(const sem::Statement* stmt, bool Validator::DiscardStatement(const sem::Statement* stmt,
sem::Statement* current_statement) const { sem::Statement* current_statement) const {
if (auto* continuing = ClosestContinuing(/*stop_at_loop*/ false, current_statement)) { if (auto* continuing = ClosestContinuing(/*stop_at_loop*/ false, current_statement)) {
@ -1649,8 +1678,8 @@ bool Validator::FunctionCall(const sem::Call* call, sem::Statement* current_stat
return true; return true;
} }
bool Validator::StructureConstructorOrCast(const ast::CallExpression* ctor, bool Validator::StructureConstructor(const ast::CallExpression* ctor,
const sem::Struct* struct_type) const { const sem::Struct* struct_type) const {
if (!struct_type->IsConstructible()) { if (!struct_type->IsConstructible()) {
AddError("struct constructor has non-constructible type", ctor->source); AddError("struct constructor has non-constructible type", ctor->source);
return false; return false;
@ -1682,8 +1711,8 @@ bool Validator::StructureConstructorOrCast(const ast::CallExpression* ctor,
return true; return true;
} }
bool Validator::ArrayConstructorOrCast(const ast::CallExpression* ctor, bool Validator::ArrayConstructor(const ast::CallExpression* ctor,
const sem::Array* array_type) const { const sem::Array* array_type) const {
auto& values = ctor->args; auto& values = ctor->args;
auto* elem_ty = array_type->ElemType(); auto* elem_ty = array_type->ElemType();
for (auto* value : values) { for (auto* value : values) {
@ -1721,66 +1750,6 @@ bool Validator::ArrayConstructorOrCast(const ast::CallExpression* ctor,
return true; return true;
} }
bool Validator::VectorConstructorOrCast(const ast::CallExpression* ctor,
const sem::Vector* vec_type) const {
auto& values = ctor->args;
auto* elem_ty = vec_type->type();
size_t value_cardinality_sum = 0;
for (auto* value : values) {
auto* value_ty = sem_.TypeOf(value)->UnwrapRef();
if (value_ty->is_scalar()) {
if (elem_ty != value_ty) {
AddError(
"type in vector constructor does not match vector type: "
"expected '" +
sem_.TypeNameOf(elem_ty) + "', found '" + sem_.TypeNameOf(value_ty) + "'",
value->source);
return false;
}
value_cardinality_sum++;
} else if (auto* value_vec = value_ty->As<sem::Vector>()) {
auto* value_elem_ty = value_vec->type();
// A mismatch of vector type parameter T is only an error if multiple
// arguments are present. A single argument constructor constitutes a
// type conversion expression.
if (elem_ty != value_elem_ty && values.size() > 1u) {
AddError(
"type in vector constructor does not match vector type: "
"expected '" +
sem_.TypeNameOf(elem_ty) + "', found '" + sem_.TypeNameOf(value_elem_ty) +
"'",
value->source);
return false;
}
value_cardinality_sum += value_vec->Width();
} else {
// A vector constructor can only accept vectors and scalars.
AddError("expected vector or scalar type in vector constructor; found: " +
sem_.TypeNameOf(value_ty),
value->source);
return false;
}
}
// A correct vector constructor must either be a zero-value expression,
// a single-value initializer (splat) expression, or the number of components
// of all constructor arguments must add up to the vector cardinality.
if (value_cardinality_sum > 1 && value_cardinality_sum != vec_type->Width()) {
if (values.empty()) {
TINT_ICE(Resolver, diagnostics_) << "constructor arguments expected to be non-empty!";
}
const Source& values_start = values[0]->source;
const Source& values_end = values[values.size() - 1]->source;
AddError("attempted to construct '" + sem_.TypeNameOf(vec_type) + "' with " +
std::to_string(value_cardinality_sum) + " component(s)",
Source::Combine(values_start, values_end));
return false;
}
return true;
}
bool Validator::Vector(const sem::Vector* ty, const Source& source) const { bool Validator::Vector(const sem::Vector* ty, const Source& source) const {
if (!ty->type()->is_scalar()) { if (!ty->type()->is_scalar()) {
AddError("vector element type must be 'bool', 'f32', 'i32' or 'u32'", source); AddError("vector element type must be 'bool', 'f32', 'i32' or 'u32'", source);
@ -1797,117 +1766,6 @@ bool Validator::Matrix(const sem::Matrix* ty, const Source& source) const {
return true; return true;
} }
bool Validator::MatrixConstructorOrCast(const ast::CallExpression* ctor,
const sem::Matrix* matrix_ty) const {
auto& values = ctor->args;
// Zero Value expression
if (values.empty()) {
return true;
}
if (!Matrix(matrix_ty, ctor->source)) {
return false;
}
std::vector<const sem::Type*> arg_tys;
arg_tys.reserve(values.size());
for (auto* value : values) {
arg_tys.emplace_back(sem_.TypeOf(value)->UnwrapRef());
}
auto* elem_type = matrix_ty->type();
auto num_elements = matrix_ty->columns() * matrix_ty->rows();
// Print a generic error for an invalid matrix constructor, showing the
// available overloads.
auto print_error = [&]() {
const Source& values_start = values[0]->source;
const Source& values_end = values[values.size() - 1]->source;
auto type_name = sem_.TypeNameOf(matrix_ty);
auto elem_type_name = sem_.TypeNameOf(elem_type);
std::stringstream ss;
ss << "no matching constructor " + type_name << "(";
for (size_t i = 0; i < values.size(); i++) {
if (i > 0) {
ss << ", ";
}
ss << arg_tys[i]->FriendlyName(symbols_);
}
ss << ")" << std::endl << std::endl;
ss << "3 candidates available:" << std::endl;
ss << " " << type_name << "()" << std::endl;
ss << " " << type_name << "(" << elem_type_name << ",...," << elem_type_name << ")"
<< " // " << std::to_string(num_elements) << " arguments" << std::endl;
ss << " " << type_name << "(";
for (uint32_t c = 0; c < matrix_ty->columns(); c++) {
if (c > 0) {
ss << ", ";
}
ss << VectorPretty(matrix_ty->rows(), elem_type);
}
ss << ")" << std::endl;
AddError(ss.str(), Source::Combine(values_start, values_end));
};
const sem::Type* expected_arg_type = nullptr;
if (num_elements == values.size()) {
// Column-major construction from scalar elements.
expected_arg_type = matrix_ty->type();
} else if (matrix_ty->columns() == values.size()) {
// Column-by-column construction from vectors.
expected_arg_type = matrix_ty->ColumnType();
} else {
print_error();
return false;
}
for (auto* arg_ty : arg_tys) {
if (arg_ty != expected_arg_type) {
print_error();
return false;
}
}
return true;
}
bool Validator::ScalarConstructorOrCast(const ast::CallExpression* ctor,
const sem::Type* ty) const {
if (ctor->args.size() == 0) {
return true;
}
if (ctor->args.size() > 1) {
AddError(
"expected zero or one value in constructor, got " + std::to_string(ctor->args.size()),
ctor->source);
return false;
}
// Validate constructor
auto* value = ctor->args[0];
auto* value_ty = sem_.TypeOf(value)->UnwrapRef();
using Bool = sem::Bool;
using I32 = sem::I32;
using U32 = sem::U32;
using F16 = sem::F16;
using F32 = sem::F32;
const bool is_valid =
(ty->Is<Bool>() && value_ty->is_scalar()) || (ty->Is<I32>() && value_ty->is_scalar()) ||
(ty->Is<U32>() && value_ty->is_scalar()) || (ty->Is<F16>() && value_ty->is_scalar()) ||
(ty->Is<F32>() && value_ty->is_scalar());
if (!is_valid) {
AddError("cannot construct '" + sem_.TypeNameOf(ty) + "' with a value of type '" +
sem_.TypeNameOf(value_ty) + "'",
ctor->source);
return false;
}
return true;
}
bool Validator::PipelineStages(const std::vector<sem::Function*>& entry_points) const { bool Validator::PipelineStages(const std::vector<sem::Function*>& entry_points) const {
auto check_workgroup_storage = [&](const sem::Function* func, auto check_workgroup_storage = [&](const sem::Function* func,
const sem::Function* entry_point) { const sem::Function* entry_point) {

View File

@ -183,6 +183,12 @@ class Validator {
/// @returns true on success, false otherwise /// @returns true on success, false otherwise
bool ContinueStatement(const sem::Statement* stmt, sem::Statement* current_statement) const; bool ContinueStatement(const sem::Statement* stmt, sem::Statement* current_statement) const;
/// Validates a call
/// @param call the call
/// @param current_statement the current statement being resolved
/// @returns true on success, false otherwise
bool Call(const sem::Call* call, sem::Statement* current_statement) const;
/// Validates a discard statement /// Validates a discard statement
/// @param stmt the statement to validate /// @param stmt the statement to validate
/// @param current_statement the current statement being resolved /// @param current_statement the current statement being resolved
@ -308,12 +314,12 @@ class Validator {
/// @returns true on success, false otherwise. /// @returns true on success, false otherwise.
bool Structure(const sem::Struct* str, ast::PipelineStage stage) const; bool Structure(const sem::Struct* str, ast::PipelineStage stage) const;
/// Validates a structure constructor or cast /// Validates a structure constructor
/// @param ctor the call expression to validate /// @param ctor the call expression to validate
/// @param struct_type the type of the structure /// @param struct_type the type of the structure
/// @returns true on success, false otherwise /// @returns true on success, false otherwise
bool StructureConstructorOrCast(const ast::CallExpression* ctor, bool StructureConstructor(const ast::CallExpression* ctor,
const sem::Struct* struct_type) const; const sem::Struct* struct_type) const;
/// Validates a switch statement /// Validates a switch statement
/// @param s the switch to validate /// @param s the switch to validate
@ -342,31 +348,11 @@ class Validator {
/// @returns true on success, false otherwise /// @returns true on success, false otherwise
bool Vector(const sem::Vector* ty, const Source& source) const; bool Vector(const sem::Vector* ty, const Source& source) const;
/// Validates a vector constructor or cast /// Validates an array constructor
/// @param ctor the call expression to validate
/// @param vec_type the vector type
/// @returns true on success, false otherwise
bool VectorConstructorOrCast(const ast::CallExpression* ctor,
const sem::Vector* vec_type) const;
/// Validates a matrix constructor or cast
/// @param ctor the call expression to validate
/// @param matrix_type the type of the matrix
/// @returns true on success, false otherwise
bool MatrixConstructorOrCast(const ast::CallExpression* ctor,
const sem::Matrix* matrix_type) const;
/// Validates a scalar constructor or cast
/// @param ctor the call expression to validate
/// @param type the type of the scalar
/// @returns true on success, false otherwise.
bool ScalarConstructorOrCast(const ast::CallExpression* ctor, const sem::Type* type) const;
/// Validates an array constructor or cast
/// @param ctor the call expresion to validate /// @param ctor the call expresion to validate
/// @param arr_type the type of the array /// @param arr_type the type of the array
/// @returns true on success, false otherwise /// @returns true on success, false otherwise
bool ArrayConstructorOrCast(const ast::CallExpression* ctor, const sem::Array* arr_type) const; bool ArrayConstructor(const ast::CallExpression* ctor, const sem::Array* arr_type) const;
/// Validates a texture builtin function /// Validates a texture builtin function
/// @param call the builtin call to validate /// @param call the builtin call to validate

View File

@ -56,6 +56,24 @@ const char* str(ParameterUsage usage) {
return "texture"; return "texture";
case ParameterUsage::kValue: case ParameterUsage::kValue:
return "value"; return "value";
case ParameterUsage::kW:
return "w";
case ParameterUsage::kX:
return "x";
case ParameterUsage::kXy:
return "xy";
case ParameterUsage::kXyz:
return "xyz";
case ParameterUsage::kY:
return "y";
case ParameterUsage::kYz:
return "yz";
case ParameterUsage::kZ:
return "z";
case ParameterUsage::kZw:
return "zw";
case ParameterUsage::kZyw:
return "zyw";
} }
return "<unknown>"; return "<unknown>";
} }

View File

@ -44,6 +44,15 @@ enum class ParameterUsage {
kSampler, kSampler,
kTexture, kTexture,
kValue, kValue,
kW,
kX,
kXy,
kXyz,
kY,
kYz,
kZ,
kZw,
kZyw,
}; };
/// @returns a string representation of the given parameter usage. /// @returns a string representation of the given parameter usage.