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:
parent
8520f3bb93
commit
6ae608cb03
|
@ -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",
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 //
|
||||
// //
|
||||
|
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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_
|
|
@ -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_
|
|
@ -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,73 +1000,56 @@ 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) {
|
||||
// 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));
|
||||
}
|
||||
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});
|
||||
|
||||
// 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 {};
|
||||
}
|
||||
|
||||
// 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;
|
||||
// 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));
|
||||
}
|
||||
}
|
||||
builder.Diagnostics().add_error(diag::System::Resolver, ss.str(), source);
|
||||
return nullptr;
|
||||
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));
|
||||
});
|
||||
}
|
||||
|
||||
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});
|
||||
// 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, {arg}) << std::endl;
|
||||
if (!candidates.empty()) {
|
||||
ss << std::endl
|
||||
<< candidates.size() << " candidate operator" << (candidates.size() > 1 ? "s:" : ":")
|
||||
<< 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 {};
|
||||
}
|
||||
|
||||
// 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 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 {};
|
||||
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; });
|
||||
|
||||
// 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;
|
||||
{
|
||||
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);
|
||||
}
|
||||
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,
|
||||
const OverloadInfo& overload,
|
||||
const std::vector<const sem::Type*>& args,
|
||||
int& match_score) {
|
||||
// Score wait for argument <-> parameter count matches / mismatches
|
||||
Impl::Candidate Impl::ScoreOverload(const OverloadInfo& overload,
|
||||
const std::vector<const sem::Type*>& args,
|
||||
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) -
|
||||
std::min(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) {
|
||||
|
|
|
@ -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
|
@ -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 -}}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -1148,155 +1148,186 @@ 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.
|
||||
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;
|
||||
}
|
||||
}
|
||||
// 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);
|
||||
};
|
||||
|
||||
if (ty == nullptr) {
|
||||
ty = Type(expr->target.type);
|
||||
if (!ty) {
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
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;
|
||||
Mark(ident);
|
||||
if (!call) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto* resolved = sem_.ResolvedSymbol(ident);
|
||||
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;
|
||||
});
|
||||
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
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
|
|
@ -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
|
@ -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,8 +1678,8 @@ bool Validator::FunctionCall(const sem::Call* call, sem::Statement* current_stat
|
|||
return true;
|
||||
}
|
||||
|
||||
bool Validator::StructureConstructorOrCast(const ast::CallExpression* ctor,
|
||||
const sem::Struct* struct_type) const {
|
||||
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);
|
||||
return false;
|
||||
|
@ -1682,8 +1711,8 @@ bool Validator::StructureConstructorOrCast(const ast::CallExpression* ctor,
|
|||
return true;
|
||||
}
|
||||
|
||||
bool Validator::ArrayConstructorOrCast(const ast::CallExpression* ctor,
|
||||
const sem::Array* array_type) const {
|
||||
bool Validator::ArrayConstructor(const ast::CallExpression* ctor,
|
||||
const sem::Array* array_type) const {
|
||||
auto& values = ctor->args;
|
||||
auto* elem_ty = array_type->ElemType();
|
||||
for (auto* value : values) {
|
||||
|
@ -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) {
|
||||
|
|
|
@ -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,12 +314,12 @@ 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,
|
||||
const sem::Struct* struct_type) const;
|
||||
bool StructureConstructor(const ast::CallExpression* ctor,
|
||||
const sem::Struct* struct_type) const;
|
||||
|
||||
/// Validates a switch statement
|
||||
/// @param s the switch to validate
|
||||
|
@ -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
|
||||
|
|
|
@ -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>";
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
Loading…
Reference in New Issue