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",
"reader/reader.cc",
"reader/reader.h",
"resolver/ctor_conv_intrinsic.cc",
"resolver/ctor_conv_intrinsic.h",
"resolver/dependency_graph.cc",
"resolver/dependency_graph.h",
"resolver/intrinsic_table.cc",

View File

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

View File

@ -72,6 +72,15 @@ type u32
type vec2<T>
type vec3<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("mat{N}x{M}<{T}>")]] type mat<N: num, M: num, T>
type ptr<S: storage_class, T, A: access>
@ -112,6 +121,10 @@ match fiu32: f32 | i32 | u32
match fi32: f32 | i32
match iu32: i32 | u32
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 //
@ -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 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 //
// //

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/sampled_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/map.h"
#include "src/tint/utils/math.h"
@ -120,17 +122,10 @@ class ClosedState {
return res.second || res.first->second == number.Value();
}
/// Type returns the closed type with index `idx`.
/// An ICE is raised if the type is not closed.
/// Type returns the closed type with index `idx`, or nullptr if the type was not closed.
const sem::Type* Type(uint32_t idx) const {
auto it = types_.find(idx);
if (it == types_.end()) {
TINT_ICE(Resolver, builder.Diagnostics())
<< "type with index " << idx << " is not closed";
return nullptr;
}
TINT_ASSERT(Resolver, it != types_.end());
return it->second;
return (it != types_.end()) ? it->second : nullptr;
}
/// Type returns the number type with index `idx`.
@ -295,6 +290,8 @@ using PipelineStage = ast::PipelineStage;
enum class OverloadFlag {
kIsBuiltin, // The overload is a builtin ('fn')
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
kSupportsFragmentPipeline, // The overload can be used in fragment 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;
}
const sem::Vector* build_vec(MatchState& state, Number N, const sem::Type* el) {
return state.builder.create<sem::Vector>(el, N.Value());
}
template <int N>
template <uint32_t N>
bool match_vec(const sem::Type* ty, const sem::Type*& T) {
if (ty->Is<Any>()) {
T = ty;
@ -371,29 +364,22 @@ bool match_vec(const sem::Type* ty, const sem::Type*& T) {
return false;
}
bool match_vec2(const sem::Type* ty, const sem::Type*& T) {
return match_vec<2>(ty, T);
const sem::Vector* build_vec(MatchState& state, Number N, const sem::Type* el) {
return state.builder.create<sem::Vector>(el, N.Value());
}
const sem::Vector* build_vec2(MatchState& state, const sem::Type* T) {
return build_vec(state, Number(2), T);
template <uint32_t N>
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) {
return match_vec<3>(ty, T);
}
constexpr auto match_vec2 = match_vec<2>;
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) {
return build_vec(state, Number(3), T);
}
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);
}
constexpr auto build_vec2 = build_vec<2>;
constexpr auto build_vec3 = build_vec<3>;
constexpr auto build_vec4 = build_vec<4>;
bool match_mat(const sem::Type* ty, Number& M, Number& N, const sem::Type*& T) {
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;
}
const sem::Matrix* build_mat(MatchState& state, Number N, Number M, const sem::Type* T) {
auto* column_type = state.builder.create<sem::Vector>(T, M.Value());
return state.builder.create<sem::Matrix>(column_type, N.Value());
template <uint32_t C, uint32_t R>
bool match_mat(const sem::Type* ty, const sem::Type*& T) {
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) {
if (ty->Is<Any>()) {
T = ty;
@ -843,37 +870,108 @@ class Impl : public IntrinsicTable {
const Source& source,
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:
// Candidate holds information about a mismatched overload that could be what the user intended
// to call.
/// Candidate holds information about an overload evaluated for resolution.
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;
};
const IntrinsicPrototype Match(const char* intrinsic_name,
const OverloadInfo& overload,
const std::vector<const sem::Type*>& args,
int& match_score);
/// A list of candidates
using Candidates = std::vector<Candidate>;
/// 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,
const OverloadInfo& overload,
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;
Matchers matchers;
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
/// types.
std::string CallSignature(ProgramBuilder& builder,
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;
ss << intrinsic_name << "(";
ss << intrinsic_name;
if (template_arg) {
ss << "<" << template_arg->FriendlyName(builder.Symbols()) << ">";
}
ss << "(";
{
bool first = true;
for (auto* arg : args) {
@ -902,25 +1000,36 @@ Impl::Impl(ProgramBuilder& b) : builder(b) {}
const sem::Builtin* Impl::Lookup(sem::BuiltinType builtin_type,
const std::vector<const sem::Type*>& args,
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);
auto& builtin = kBuiltins[intrinsic_index];
for (uint32_t o = 0; o < builtin.num_overloads; o++) {
int match_score = 1000;
auto& overload = builtin.overloads[o];
auto match = Match(intrinsic_name, overload, args, match_score);
if (match.return_type) {
// Generates an error when no overloads match the provided arguments
auto on_no_match = [&](Candidates candidates) {
std::stringstream ss;
ss << "no matching call to " << CallSignature(builder, intrinsic_name, args) << std::endl;
if (!candidates.empty()) {
ss << std::endl
<< candidates.size() << " candidate function" << (candidates.size() > 1 ? "s:" : ":")
<< std::endl;
PrintCandidates(ss, candidates, intrinsic_name);
}
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 {};
}
// De-duplicate builtins that are identical.
return utils::GetOrCreate(builtins, match, [&] {
std::vector<sem::Parameter*> 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));
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)) {
@ -936,39 +1045,11 @@ const sem::Builtin* Impl::Lookup(sem::BuiltinType builtin_type,
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});
}
}
// Sort the candidates with the most promising first
std::stable_sort(candidates.begin(), candidates.end(),
[](const Candidate& a, const Candidate& b) { return a.score > b.score; });
// Generate an error message
std::stringstream ss;
ss << "no matching call to " << CallSignature(builder, intrinsic_name, args) << std::endl;
if (!candidates.empty()) {
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;
}
}
builder.Diagnostics().add_error(diag::System::Resolver, ss.str(), source);
return nullptr;
}
IntrinsicTable::UnaryOperator Impl::Lookup(ast::UnaryOp op,
const sem::Type* arg,
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*> {
switch (op) {
case ast::UnaryOp::kComplement:
@ -982,38 +1063,27 @@ IntrinsicTable::UnaryOperator Impl::Lookup(ast::UnaryOp op,
}
}();
auto& builtin = kUnaryOperators[intrinsic_index];
for (uint32_t o = 0; o < builtin.num_overloads; o++) {
int match_score = 1000;
auto& overload = builtin.overloads[o];
auto match = Match(intrinsic_name, overload, {arg}, match_score);
if (match.return_type) {
return UnaryOperator{match.return_type, match.parameters[0].type};
}
if (match_score > 0) {
candidates.emplace_back(Candidate{&overload, match_score});
}
}
// Sort the candidates with the most promising first
std::stable_sort(candidates.begin(), candidates.end(),
[](const Candidate& a, const Candidate& b) { return a.score > b.score; });
// Generate an error message
// Generates an error when no overloads match the provided arguments
auto on_no_match = [&, name = intrinsic_name](Candidates candidates) {
std::stringstream ss;
ss << "no matching overload for " << CallSignature(builder, intrinsic_name, {arg}) << std::endl;
ss << "no matching overload for " << CallSignature(builder, name, {arg}) << std::endl;
if (!candidates.empty()) {
ss << std::endl;
ss << candidates.size() << " candidate operator" << (candidates.size() > 1 ? "s:" : ":")
ss << std::endl
<< candidates.size() << " candidate operator" << (candidates.size() > 1 ? "s:" : ":")
<< std::endl;
for (auto& candidate : candidates) {
ss << " ";
PrintOverload(ss, *candidate.overload, intrinsic_name);
ss << std::endl;
}
PrintCandidates(ss, candidates, name);
}
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 {};
}
return UnaryOperator{match.return_type, match.parameters[0].type};
}
IntrinsicTable::BinaryOperator Impl::Lookup(ast::BinaryOp op,
@ -1021,9 +1091,6 @@ IntrinsicTable::BinaryOperator Impl::Lookup(ast::BinaryOp op,
const sem::Type* rhs,
const Source& source,
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*> {
switch (op) {
case ast::BinaryOp::kAnd:
@ -1067,47 +1134,175 @@ IntrinsicTable::BinaryOperator Impl::Lookup(ast::BinaryOp op,
}
}();
auto& builtin = kBinaryOperators[intrinsic_index];
for (uint32_t o = 0; o < builtin.num_overloads; o++) {
int match_score = 1000;
auto& overload = builtin.overloads[o];
auto match = Match(intrinsic_name, overload, {lhs, rhs}, match_score);
if (match.return_type) {
return BinaryOperator{match.return_type, match.parameters[0].type,
match.parameters[1].type};
// Generates an error when no overloads match the provided arguments
auto on_no_match = [&, name = intrinsic_name](Candidates candidates) {
std::stringstream ss;
ss << "no matching overload for " << CallSignature(builder, name, {lhs, rhs}) << std::endl;
if (!candidates.empty()) {
ss << std::endl
<< candidates.size() << " candidate operator" << (candidates.size() > 1 ? "s:" : ":")
<< std::endl;
PrintCandidates(ss, candidates, name);
}
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(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
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++) {
candidate_indices[i] = i;
}
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);
}
// Generate an error message
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;
}
}
builder.Diagnostics().add_error(diag::System::Resolver, ss.str(), source);
// 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,
const OverloadInfo& overload,
Impl::Candidate Impl::ScoreOverload(const OverloadInfo& overload,
const std::vector<const sem::Type*>& args,
int& match_score) {
// Score wait for argument <-> parameter count matches / mismatches
ClosedState closed) const {
// Score weight 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 kScorePerMatchedParam = 2;
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());
bool overload_matched = true;
int overload_score = 0;
if (static_cast<uint64_t>(args.size()) >
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) {
match_score += kScorePerParamArgMismatch * (std::max(num_parameters, num_arguments) -
overload_score += kScorePerParamArgMismatch * (std::max(num_parameters, num_arguments) -
std::min(num_parameters, num_arguments));
overload_matched = false;
}
ClosedState closed(builder);
std::vector<IntrinsicPrototype::Parameter> parameters;
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());
if (type) {
parameters.emplace_back(IntrinsicPrototype::Parameter{type, parameter.usage});
match_score += kScorePerMatchedParam;
overload_score += kScorePerMatchedParam;
} else {
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++) {
auto& open_type = overload.open_types[ot];
if (open_type.matcher_index != kNoMatcher) {
auto* index = &open_type.matcher_index;
if (Match(closed, overload, index).Type(closed.Type(ot))) {
match_score += kScorePerMatchedOpenType;
auto* closed_type = closed.Type(ot);
auto* matcher_index = &open_type.matcher_index;
if (closed_type && Match(closed, overload, matcher_index).Type(closed_type)) {
overload_score += kScorePerMatchedOpenType;
} else {
overload_matched = false;
}
@ -1168,7 +1363,7 @@ const IntrinsicPrototype Impl::Match(const char* intrinsic_name,
if (open_number.matcher_index != kNoMatcher) {
auto* index = &open_number.matcher_index;
if (Match(closed, overload, index).Num(closed.Num(on)).IsValid()) {
match_score += kScorePerMatchedOpenNumber;
overload_score += kScorePerMatchedOpenNumber;
} else {
overload_matched = false;
}
@ -1176,31 +1371,7 @@ const IntrinsicPrototype Impl::Match(const char* intrinsic_name,
}
}
if (!overload_matched) {
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;
return Candidate{overload, closed, parameters, overload_matched, overload_score};
}
MatchState Impl::Match(ClosedState& closed,
@ -1209,10 +1380,12 @@ MatchState Impl::Match(ClosedState& closed,
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);
ss << name << "(";
ss << intrinsic_name << "(";
for (uint32_t p = 0; p < overload.num_parameters; p++) {
auto& parameter = overload.parameters[p];
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) {
MatcherIndex matcher_index = *matcher_indices_++;
auto* matcher = matchers.type[matcher_index];
@ -1280,6 +1463,41 @@ std::string MatchState::NumName() {
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
std::unique_ptr<IntrinsicTable> IntrinsicTable::Create(ProgramBuilder& builder) {

View File

@ -19,6 +19,7 @@
#include <string>
#include <vector>
#include "src/tint/resolver/ctor_conv_intrinsic.h"
#include "src/tint/sem/builtin.h"
// Forward declarations
@ -89,6 +90,18 @@ class IntrinsicTable {
const sem::Type* rhs,
const Source& source,
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

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}};
{{- 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
{{ end -}}

View File

@ -26,6 +26,8 @@
#include "src/tint/sem/reference.h"
#include "src/tint/sem/sampled_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 {
@ -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
auto* f32 = create<sem::F32>();
std::vector<const sem::Type*> arg_tys(257, f32);

View File

@ -1148,133 +1148,157 @@ sem::Expression* Resolver::Bitcast(const ast::BitcastExpression* 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::Type*> arg_tys(args.size());
std::vector<const sem::Type*> arg_tys(expr->args.size());
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++) {
auto* arg = sem_.Get(expr->args[i]);
if (!arg) {
return nullptr;
}
args[i] = arg;
arg_tys[i] = args[i]->Type();
arg_tys[i] = arg->Type();
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);
auto type_ctor_or_conv = [&](const sem::Type* ty) -> sem::Call* {
// The call has resolved to a type constructor or cast.
if (args.size() == 1) {
auto* target = ty;
auto* source = args[0]->Type()->UnwrapRef();
if ((source != target) && //
((source->is_scalar() && target->is_scalar()) ||
(source->Is<sem::Vector>() && target->Is<sem::Vector>()) ||
(source->Is<sem::Matrix>() && target->Is<sem::Matrix>()))) {
// 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 TypeConversion(expr, ty, args[0], arg_tys[0]);
}
}
return TypeConstructor(expr, ty, std::move(args), std::move(arg_tys));
// Did any arguments have side effects?
bool has_side_effects =
std::any_of(args.begin(), args.end(), [](auto* e) { return e->HasSideEffects(); });
// array_or_struct_ctor is a helper for building a sem::TypeConstructor call for an array or
// structure type. These types have constructors that are always explicitly typed (no
// inference), and do not support type conversion. As such, they do not use the IntrinsicTable.
auto array_or_struct_ctor = [&](const sem::Type* ty) -> sem::Call* {
auto* call_target = utils::GetOrCreate(
type_ctors_, TypeConstructorSig{ty, arg_tys}, [&]() -> sem::TypeConstructor* {
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
}));
});
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
// function call, cast or type constructor expression.
// ct_ctor_or_conv is a helper for building either a sem::TypeConstructor or sem::TypeConversion
// call for a CtorConvIntrinsic with an optional template argument type.
auto ct_ctor_or_conv = [&](CtorConvIntrinsic ty, const sem::Type* template_arg) -> sem::Call* {
auto* call_target = intrinsic_table_->Lookup(ty, template_arg, arg_tys, expr->source);
if (!call_target) {
return nullptr;
}
auto value = EvaluateConstantValue(expr, call_target->ReturnType());
return builder_->create<sem::Call>(expr, call_target, std::move(args), current_statement_,
value, has_side_effects);
};
// ct_ctor_or_conv is a helper for building either a sem::TypeConstructor or sem::TypeConversion
// call for the given semantic type.
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;
});
};
// ast::CallExpression has a target which is either an ast::Type or an ast::IdentifierExpression
sem::Call* call = nullptr;
if (expr->target.type) {
const sem::Type* ty = nullptr;
auto err_cannot_infer_el_ty = [&](std::string name) {
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;
}
}
}
if (ty == nullptr) {
ty = Type(expr->target.type);
if (!ty) {
// 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;
}
}
return type_ctor_or_conv(ty);
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);
return Switch(
call = Switch<sem::Call*>(
resolved, //
[&](sem::Type* type) { return type_ctor_or_conv(type); },
[&](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);
},
@ -1292,11 +1316,18 @@ sem::Call* Resolver::Call(const ast::CallExpression* expr) {
}
TINT_ICE(Resolver, diagnostics_)
<< expr->source << " unresolved CallExpression target:\n"
<< expr->source << " unhandled CallExpression target:\n"
<< "resolved: " << (resolved ? resolved->TypeInfo().name : "<null>") << "\n"
<< "name: " << builder_->Symbols().NameFor(ident->symbol);
return nullptr;
});
}
if (!call) {
return nullptr;
}
return validator_.Call(call, current_statement_) ? call : nullptr;
}
sem::Call* Resolver::BuiltinCall(const ast::CallExpression* expr,
@ -1414,133 +1445,6 @@ sem::Call* Resolver::FunctionCall(const ast::CallExpression* expr,
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) {
auto* ty = Switch(
literal,
@ -2464,22 +2368,6 @@ bool Resolver::IsBuiltin(Symbol symbol) const {
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
////////////////////////////////////////////////////////////////////////////////

View File

@ -193,14 +193,6 @@ class Resolver {
const std::vector<const sem::Type*> arg_tys);
sem::Expression* Literal(const ast::LiteralExpression*);
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*);
// Statement resolving methods
@ -226,7 +218,6 @@ class Resolver {
sem::Statement* VariableDeclStatement(const ast::VariableDeclStatement*);
bool Statements(const ast::StatementList&);
/// Resolves the WorkgroupSize for the given function, assigning it to
/// current_function_
bool WorkgroupSize(const ast::Function*);
@ -337,23 +328,6 @@ class Resolver {
/// @returns true if the symbol is the name of a builtin function.
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 {
const sem::Type* type;
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_set<const ast::Node*> marked_;
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>
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;
}
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,
sem::Statement* current_statement) const {
if (auto* continuing = ClosestContinuing(/*stop_at_loop*/ false, current_statement)) {
@ -1649,7 +1678,7 @@ bool Validator::FunctionCall(const sem::Call* call, sem::Statement* current_stat
return true;
}
bool Validator::StructureConstructorOrCast(const ast::CallExpression* ctor,
bool Validator::StructureConstructor(const ast::CallExpression* ctor,
const sem::Struct* struct_type) const {
if (!struct_type->IsConstructible()) {
AddError("struct constructor has non-constructible type", ctor->source);
@ -1682,7 +1711,7 @@ bool Validator::StructureConstructorOrCast(const ast::CallExpression* ctor,
return true;
}
bool Validator::ArrayConstructorOrCast(const ast::CallExpression* ctor,
bool Validator::ArrayConstructor(const ast::CallExpression* ctor,
const sem::Array* array_type) const {
auto& values = ctor->args;
auto* elem_ty = array_type->ElemType();
@ -1721,66 +1750,6 @@ bool Validator::ArrayConstructorOrCast(const ast::CallExpression* ctor,
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 {
if (!ty->type()->is_scalar()) {
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;
}
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 {
auto check_workgroup_storage = [&](const sem::Function* func,
const sem::Function* entry_point) {

View File

@ -183,6 +183,12 @@ class Validator {
/// @returns true on success, false otherwise
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
/// @param stmt the statement to validate
/// @param current_statement the current statement being resolved
@ -308,11 +314,11 @@ class Validator {
/// @returns true on success, false otherwise.
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 struct_type the type of the structure
/// @returns true on success, false otherwise
bool StructureConstructorOrCast(const ast::CallExpression* ctor,
bool StructureConstructor(const ast::CallExpression* ctor,
const sem::Struct* struct_type) const;
/// Validates a switch statement
@ -342,31 +348,11 @@ class Validator {
/// @returns true on success, false otherwise
bool Vector(const sem::Vector* ty, const Source& source) const;
/// Validates a vector constructor or cast
/// @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
/// Validates an array constructor
/// @param ctor the call expresion to validate
/// @param arr_type the type of the array
/// @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
/// @param call the builtin call to validate

View File

@ -56,6 +56,24 @@ const char* str(ParameterUsage usage) {
return "texture";
case ParameterUsage::kValue:
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>";
}

View File

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