tint: Implement const eval of unary minus

Bug: tint:1581
Change-Id: I228e8d083229fabfe8a4c0876160d673502e10a3
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/96422
Kokoro: Kokoro <noreply+kokoro@google.com>
Commit-Queue: Antonio Maiorano <amaiorano@google.com>
Reviewed-by: Ben Clayton <bclayton@google.com>
Reviewed-by: dan sinclair <dsinclair@google.com>
Reviewed-by: Dan Sinclair <dsinclair@chromium.org>
This commit is contained in:
Antonio Maiorano 2022-07-20 18:00:35 +00:00 committed by Dawn LUCI CQ
parent f0a52d8f20
commit 3b8b9699d6
10 changed files with 282 additions and 75 deletions

View File

@ -18,6 +18,8 @@
#include <vector>
#include "src/tint/program_builder.h"
#include "src/tint/sem/abstract_float.h"
#include "src/tint/sem/abstract_int.h"
namespace tint::fuzzers::ast_fuzzer {
@ -98,7 +100,8 @@ std::vector<ast::UnaryOp> MutationWrapUnaryOperator::GetValidUnaryWrapper(
return {ast::UnaryOp::kNot};
}
if (expr_type->is_signed_scalar_or_vector()) {
if (expr_type->is_signed_scalar_or_vector() ||
expr_type->is_abstract_integer_scalar_or_vector()) {
return {ast::UnaryOp::kNegation, ast::UnaryOp::kComplement};
}
@ -106,7 +109,7 @@ std::vector<ast::UnaryOp> MutationWrapUnaryOperator::GetValidUnaryWrapper(
return {ast::UnaryOp::kComplement};
}
if (expr_type->is_float_scalar_or_vector()) {
if (expr_type->is_float_scalar_or_vector() || expr_type->is_abstract_float_scalar_or_vector()) {
return {ast::UnaryOp::kNegation};
}

View File

@ -134,6 +134,7 @@ match fi32: f32 | i32
match fi32f16: f32 | f16 | i32
match iu32: i32 | u32
match aiu32: ai | i32 | u32
match afi32f16: ai | af | f32 | i32 | f16
match scalar: f32 | f16 | i32 | u32 | bool
match abstract_or_scalar: ai | af | f32 | f16 | i32 | u32 | bool
match af_f32: af | f32
@ -822,8 +823,8 @@ op ! <N: num> (vec<N, bool>) -> vec<N, bool>
@const op ~ <T: aiu32>(T) -> T
@const op ~ <T: aiu32, N: num> (vec<N, T>) -> vec<N, T>
op - <T: fi32f16>(T) -> T
op - <T: fi32f16, N: num> (vec<N, T>) -> vec<N, T>
@const op - <T: afi32f16>(T) -> T
@const op - <T: afi32f16, N: num> (vec<N, T>) -> vec<N, T>
////////////////////////////////////////////////////////////////////////////////
// Binary Operators //

View File

@ -18,6 +18,7 @@
#include <limits>
#include <optional>
#include <string>
#include <type_traits>
#include <unordered_map>
#include <utility>
@ -45,14 +46,38 @@ namespace tint::resolver {
namespace {
/// TypeDispatch is a helper for calling the function `f`, passing a single zero-value argument of
/// the C++ type that corresponds to the sem::Type `type`. For example, calling `TypeDispatch()`
/// with a type of `sem::I32*` will call the function f with a single argument of `i32(0)`.
/// Helper that calls 'f' passing in `c`'s value
template <typename F>
auto aiu32Dispatch(const sem::Constant* c, F&& f) {
return Switch(
c->Type(), [&](const sem::AbstractInt*) { return f(c->As<AInt>()); },
[&](const sem::I32*) { return f(c->As<i32>()); },
[&](const sem::U32*) { return f(c->As<u32>()); });
}
/// Helper that calls 'f' passing in `c`'s value
template <typename F>
auto afi32f16Dispatch(const sem::Constant* c, F&& f) {
return Switch(
c->Type(), [&](const sem::AbstractInt*) { return f(c->As<AInt>()); },
[&](const sem::AbstractFloat*) { return f(c->As<AFloat>()); },
[&](const sem::F32*) { return f(c->As<f32>()); },
[&](const sem::I32*) { return f(c->As<i32>()); },
[&](const sem::F16*) {
// TODO(crbug.com/tint/1502): Support const eval for f16
return nullptr;
});
}
/// ZeroTypeDispatch is a helper for calling the function `f`, passing a single zero-value argument
/// of the C++ type that corresponds to the sem::Type `type`. For example, calling
/// `ZeroTypeDispatch()` with a type of `sem::I32*` will call the function f with a single argument
/// of `i32(0)`.
/// @returns the value returned by calling `f`.
/// @note `type` must be a scalar or abstract numeric type. Other types will not call `f`, and will
/// return the zero-initialized value of the return type for `f`.
template <typename F>
auto TypeDispatch(const sem::Type* type, F&& f) {
auto ZeroTypeDispatch(const sem::Type* type, F&& f) {
return Switch(
type, //
[&](const sem::AbstractInt*) { return f(AInt(0)); }, //
@ -64,20 +89,6 @@ auto TypeDispatch(const sem::Type* type, F&& f) {
[&](const sem::Bool*) { return f(static_cast<bool>(0)); });
}
/// IntegerDispatch is a helper for calling the function `f`, passing the integer value of the
/// constant c.
/// @returns the value returned by calling `f`.
/// @note `c` must be of an integer type. Other types will not call `f`, and will return the
/// zero-initialized value of the return type for `f`
template <typename F>
auto IntegerDispatch(const sem::Constant* c, F&& f) {
return Switch(
c->Type(), //
[&](const sem::AbstractInt*) { return f(c->As<AInt>()); }, //
[&](const sem::I32*) { return f(c->As<i32>()); }, //
[&](const sem::U32*) { return f(c->As<u32>()); });
}
/// @returns `value` if `T` is not a Number, otherwise ValueOf returns the inner value of the
/// Number.
template <typename T>
@ -142,7 +153,7 @@ struct Element : Constant {
return this;
}
bool failed = false;
auto* res = TypeDispatch(target_ty, [&](auto zero_to) -> const Constant* {
auto* res = ZeroTypeDispatch(target_ty, [&](auto zero_to) -> const Constant* {
// `T` is the source type, `value` is the source value.
// `TO` is the target type.
using TO = std::decay_t<decltype(zero_to)>;
@ -333,7 +344,7 @@ const Constant* ZeroValue(ProgramBuilder& builder, const sem::Type* type) {
return CreateComposite(builder, s, std::move(zeros));
},
[&](Default) -> const Constant* {
return TypeDispatch(type, [&](auto zero) -> const Constant* {
return ZeroTypeDispatch(type, [&](auto zero) -> const Constant* {
return CreateElement(builder, type, zero);
});
});
@ -643,12 +654,34 @@ const sem::Constant* ConstEval::OpComplement(const sem::Type*,
sem::Expression const* const* args,
size_t) {
return TransformElements(builder, args[0]->ConstantValue(), [&](const sem::Constant* c) {
return IntegerDispatch(c, [&](auto i) { //
return aiu32Dispatch(c, [&](auto i) { //
return CreateElement(builder, c->Type(), decltype(i)(~i.value));
});
});
}
const sem::Constant* ConstEval::OpMinus(const sem::Type*,
sem::Expression const* const* args,
size_t) {
return TransformElements(builder, args[0]->ConstantValue(), [&](const sem::Constant* c) {
return afi32f16Dispatch(c, [&](auto i) { //
// For signed integrals, avoid C++ UB by not negating the smallest negative number. In
// WGSL, this operation is well defined to return the same value, see:
// https://gpuweb.github.io/gpuweb/wgsl/#arithmetic-expr.
using T = UnwrapNumber<decltype(i)>;
if constexpr (std::is_integral_v<T>) {
auto v = i.value;
if (v != std::numeric_limits<T>::min()) {
v = -v;
}
return CreateElement(builder, c->Type(), decltype(i)(v));
} else {
return CreateElement(builder, c->Type(), decltype(i)(-i.value));
}
});
});
}
utils::Result<const sem::Constant*> ConstEval::Convert(const sem::Type* target_ty,
const sem::Constant* value,
const Source& source) {

View File

@ -187,6 +187,15 @@ class ConstEval {
sem::Expression const* const* args,
size_t num_args);
/// Minus operator '-'
/// @param ty the expression type
/// @param args the input arguments
/// @param num_args the number of input arguments (must be 1)
/// @return the result value, or null if the value cannot be calculated
const sem::Constant* OpMinus(const sem::Type* ty,
sem::Expression const* const* args,
size_t num_args);
private:
/// Adds the given error message to the diagnostics
void AddError(const std::string& msg, const Source& source) const;

View File

@ -13,6 +13,7 @@
// limitations under the License.
#include <cmath>
#include <type_traits>
#include "gtest/gtest.h"
#include "src/tint/resolver/resolver_test_helper.h"
@ -2929,6 +2930,29 @@ TEST_F(ResolverConstEvalTest, MemberAccess) {
////////////////////////////////////////////////////////////////////////////////////////////////////
namespace unary_op {
template <typename T>
auto Highest() {
return T(T::kHighest);
}
template <typename T>
auto Lowest() {
return T(T::kLowest);
}
template <typename T>
constexpr auto Negate(const Number<T>& v) {
// For signed integrals, avoid C++ UB by not negating the smallest negative number. In
// WGSL, this operation is well defined to return the same value, see:
// https://gpuweb.github.io/gpuweb/wgsl/#arithmetic-expr.
if constexpr (std::is_integral_v<T> && std::is_signed_v<T>) {
if (v == std::numeric_limits<T>::min()) {
return v;
}
}
return -v;
}
template <typename T>
struct Values {
T input;
@ -2936,7 +2960,7 @@ struct Values {
};
struct Case {
std::variant<Values<AInt>, Values<u32>, Values<i32>> values;
std::variant<Values<AInt>, Values<AFloat>, Values<u32>, Values<i32>, Values<f32>> values;
};
static std::ostream& operator<<(std::ostream& o, const Case& c) {
@ -2952,6 +2976,8 @@ Case C(T input, T expect) {
using ResolverConstEvalUnaryOpTest = ResolverTestWithParam<std::tuple<ast::UnaryOp, Case>>;
TEST_P(ResolverConstEvalUnaryOpTest, Test) {
Enable(ast::Extension::kF16);
auto op = std::get<0>(GetParam());
auto c = std::get<1>(GetParam());
std::visit(
@ -3000,6 +3026,60 @@ INSTANTIATE_TEST_SUITE_P(Complement,
C(2_i, -3_i),
C(-3_i, 2_i),
})));
INSTANTIATE_TEST_SUITE_P(Negation,
ResolverConstEvalUnaryOpTest,
testing::Combine(testing::Values(ast::UnaryOp::kNegation),
testing::ValuesIn({
// AInt
C(0_a, -0_a),
C(-0_a, 0_a),
C(1_a, -1_a),
C(-1_a, 1_a),
C(Highest<AInt>(), -Highest<AInt>()),
C(-Highest<AInt>(), Highest<AInt>()),
C(Lowest<AInt>(), Negate(Lowest<AInt>())),
C(Negate(Lowest<AInt>()), Lowest<AInt>()),
// i32
C(0_i, -0_i),
C(-0_i, 0_i),
C(1_i, -1_i),
C(-1_i, 1_i),
C(Highest<i32>(), -Highest<i32>()),
C(-Highest<i32>(), Highest<i32>()),
C(Lowest<i32>(), Negate(Lowest<i32>())),
C(Negate(Lowest<i32>()), Lowest<i32>()),
// AFloat
C(0.0_a, -0.0_a),
C(-0.0_a, 0.0_a),
C(1.0_a, -1.0_a),
C(-1.0_a, 1.0_a),
C(Highest<AFloat>(), -Highest<AFloat>()),
C(-Highest<AFloat>(), Highest<AFloat>()),
C(Lowest<AFloat>(), Negate(Lowest<AFloat>())),
C(Negate(Lowest<AFloat>()), Lowest<AFloat>()),
// f32
C(0.0_f, -0.0_f),
C(-0.0_f, 0.0_f),
C(1.0_f, -1.0_f),
C(-1.0_f, 1.0_f),
C(Highest<f32>(), -Highest<f32>()),
C(-Highest<f32>(), Highest<f32>()),
C(Lowest<f32>(), Negate(Lowest<f32>())),
C(Negate(Lowest<f32>()), Lowest<f32>()),
})));
// Make sure UBSan doesn't trip on C++'s undefined behaviour of negating the smallest negative
// number.
TEST_F(ResolverConstEvalTest, UnaryNegateLowestAbstract) {
// const break_me = -(-9223372036854775808);
auto* c = GlobalConst("break_me", nullptr, Negation(Negation(Expr(9223372036854775808_a))));
(void)c;
EXPECT_TRUE(r()->Resolve()) << r()->error();
auto* sem = Sem().Get(c);
EXPECT_EQ(sem->ConstantValue()->As<AInt>(), 9223372036854775808_a);
}
} // namespace unary_op
} // namespace

View File

@ -1832,8 +1832,52 @@ std::string Aiu32::String(MatchState*) const {
return ss.str();
}
/// TypeMatcher for 'match scalar'
/// TypeMatcher for 'match afi32f16'
/// @see src/tint/intrinsics.def:137:7
class Afi32F16 : public TypeMatcher {
public:
/// Checks whether the given type matches the matcher rules, and returns the
/// expected, canonicalized type on success.
/// Match may define and refine the template types and numbers in state.
/// @param state the MatchState
/// @param type the type to match
/// @returns the canonicalized type on match, otherwise nullptr
const sem::Type* Match(MatchState& state,
const sem::Type* type) const override;
/// @param state the MatchState
/// @return a string representation of the matcher.
std::string String(MatchState* state) const override;
};
const sem::Type* Afi32F16::Match(MatchState& state, const sem::Type* ty) const {
if (match_af(ty)) {
return build_af(state);
}
if (match_ai(ty)) {
return build_ai(state);
}
if (match_i32(ty)) {
return build_i32(state);
}
if (match_f32(ty)) {
return build_f32(state);
}
if (match_f16(ty)) {
return build_f16(state);
}
return nullptr;
}
std::string Afi32F16::String(MatchState*) const {
std::stringstream ss;
// Note: We pass nullptr to the TypeMatcher::String() functions, as 'matcher's do not support
// template arguments, nor can they match sub-types. As such, they have no use for the MatchState.
ss << Ai().String(nullptr) << ", " << Af().String(nullptr) << ", " << F32().String(nullptr) << ", " << I32().String(nullptr) << " or " << F16().String(nullptr);
return ss.str();
}
/// TypeMatcher for 'match scalar'
/// @see src/tint/intrinsics.def:138:7
class Scalar : public TypeMatcher {
public:
/// Checks whether the given type matches the matcher rules, and returns the
@ -1877,7 +1921,7 @@ std::string Scalar::String(MatchState*) const {
}
/// TypeMatcher for 'match abstract_or_scalar'
/// @see src/tint/intrinsics.def:138:7
/// @see src/tint/intrinsics.def:139:7
class AbstractOrScalar : public TypeMatcher {
public:
/// Checks whether the given type matches the matcher rules, and returns the
@ -1927,7 +1971,7 @@ std::string AbstractOrScalar::String(MatchState*) const {
}
/// TypeMatcher for 'match af_f32'
/// @see src/tint/intrinsics.def:139:7
/// @see src/tint/intrinsics.def:140:7
class AfF32 : public TypeMatcher {
public:
/// Checks whether the given type matches the matcher rules, and returns the
@ -1962,7 +2006,7 @@ std::string AfF32::String(MatchState*) const {
}
/// TypeMatcher for 'match af_f32f16'
/// @see src/tint/intrinsics.def:140:7
/// @see src/tint/intrinsics.def:141:7
class AfF32F16 : public TypeMatcher {
public:
/// Checks whether the given type matches the matcher rules, and returns the
@ -2000,7 +2044,7 @@ std::string AfF32F16::String(MatchState*) const {
}
/// TypeMatcher for 'match scalar_no_f32'
/// @see src/tint/intrinsics.def:141:7
/// @see src/tint/intrinsics.def:142:7
class ScalarNoF32 : public TypeMatcher {
public:
/// Checks whether the given type matches the matcher rules, and returns the
@ -2041,7 +2085,7 @@ std::string ScalarNoF32::String(MatchState*) const {
}
/// TypeMatcher for 'match scalar_no_f16'
/// @see src/tint/intrinsics.def:142:7
/// @see src/tint/intrinsics.def:143:7
class ScalarNoF16 : public TypeMatcher {
public:
/// Checks whether the given type matches the matcher rules, and returns the
@ -2082,7 +2126,7 @@ std::string ScalarNoF16::String(MatchState*) const {
}
/// TypeMatcher for 'match scalar_no_i32'
/// @see src/tint/intrinsics.def:143:7
/// @see src/tint/intrinsics.def:144:7
class ScalarNoI32 : public TypeMatcher {
public:
/// Checks whether the given type matches the matcher rules, and returns the
@ -2123,7 +2167,7 @@ std::string ScalarNoI32::String(MatchState*) const {
}
/// TypeMatcher for 'match scalar_no_u32'
/// @see src/tint/intrinsics.def:144:7
/// @see src/tint/intrinsics.def:145:7
class ScalarNoU32 : public TypeMatcher {
public:
/// Checks whether the given type matches the matcher rules, and returns the
@ -2164,7 +2208,7 @@ std::string ScalarNoU32::String(MatchState*) const {
}
/// TypeMatcher for 'match scalar_no_bool'
/// @see src/tint/intrinsics.def:145:7
/// @see src/tint/intrinsics.def:146:7
class ScalarNoBool : public TypeMatcher {
public:
/// Checks whether the given type matches the matcher rules, and returns the
@ -2205,7 +2249,7 @@ std::string ScalarNoBool::String(MatchState*) const {
}
/// EnumMatcher for 'match f32_texel_format'
/// @see src/tint/intrinsics.def:156:7
/// @see src/tint/intrinsics.def:157:7
class F32TexelFormat : public NumberMatcher {
public:
/// Checks whether the given number matches the enum matcher rules.
@ -2238,7 +2282,7 @@ std::string F32TexelFormat::String(MatchState*) const {
}
/// EnumMatcher for 'match i32_texel_format'
/// @see src/tint/intrinsics.def:158:7
/// @see src/tint/intrinsics.def:159:7
class I32TexelFormat : public NumberMatcher {
public:
/// Checks whether the given number matches the enum matcher rules.
@ -2270,7 +2314,7 @@ std::string I32TexelFormat::String(MatchState*) const {
}
/// EnumMatcher for 'match u32_texel_format'
/// @see src/tint/intrinsics.def:160:7
/// @see src/tint/intrinsics.def:161:7
class U32TexelFormat : public NumberMatcher {
public:
/// Checks whether the given number matches the enum matcher rules.
@ -2302,7 +2346,7 @@ std::string U32TexelFormat::String(MatchState*) const {
}
/// EnumMatcher for 'match write_only'
/// @see src/tint/intrinsics.def:163:7
/// @see src/tint/intrinsics.def:164:7
class WriteOnly : public NumberMatcher {
public:
/// Checks whether the given number matches the enum matcher rules.
@ -2328,7 +2372,7 @@ std::string WriteOnly::String(MatchState*) const {
}
/// EnumMatcher for 'match function_private_workgroup'
/// @see src/tint/intrinsics.def:165:7
/// @see src/tint/intrinsics.def:166:7
class FunctionPrivateWorkgroup : public NumberMatcher {
public:
/// Checks whether the given number matches the enum matcher rules.
@ -2358,7 +2402,7 @@ std::string FunctionPrivateWorkgroup::String(MatchState*) const {
}
/// EnumMatcher for 'match workgroup_or_storage'
/// @see src/tint/intrinsics.def:166:7
/// @see src/tint/intrinsics.def:167:7
class WorkgroupOrStorage : public NumberMatcher {
public:
/// Checks whether the given number matches the enum matcher rules.
@ -2524,6 +2568,7 @@ class Matchers {
Fi32F16 Fi32F16_;
Iu32 Iu32_;
Aiu32 Aiu32_;
Afi32F16 Afi32F16_;
Scalar Scalar_;
AbstractOrScalar AbstractOrScalar_;
AfF32 AfF32_;
@ -2550,7 +2595,7 @@ class Matchers {
~Matchers();
/// The template types, types, and type matchers
TypeMatcher const* const type[66] = {
TypeMatcher const* const type[67] = {
/* [0] */ &template_type_0_,
/* [1] */ &template_type_1_,
/* [2] */ &Bool_,
@ -2608,15 +2653,16 @@ class Matchers {
/* [54] */ &Fi32F16_,
/* [55] */ &Iu32_,
/* [56] */ &Aiu32_,
/* [57] */ &Scalar_,
/* [58] */ &AbstractOrScalar_,
/* [59] */ &AfF32_,
/* [60] */ &AfF32F16_,
/* [61] */ &ScalarNoF32_,
/* [62] */ &ScalarNoF16_,
/* [63] */ &ScalarNoI32_,
/* [64] */ &ScalarNoU32_,
/* [65] */ &ScalarNoBool_,
/* [57] */ &Afi32F16_,
/* [58] */ &Scalar_,
/* [59] */ &AbstractOrScalar_,
/* [60] */ &AfF32_,
/* [61] */ &AfF32F16_,
/* [62] */ &ScalarNoF32_,
/* [63] */ &ScalarNoF16_,
/* [64] */ &ScalarNoI32_,
/* [65] */ &ScalarNoU32_,
/* [66] */ &ScalarNoBool_,
};
/// The template numbers, and number matchers
@ -7914,7 +7960,7 @@ constexpr TemplateTypeInfo kTemplateTypes[] = {
{
/* [1] */
/* name */ "U",
/* matcher index */ 65,
/* matcher index */ 66,
},
{
/* [2] */
@ -7924,7 +7970,7 @@ constexpr TemplateTypeInfo kTemplateTypes[] = {
{
/* [3] */
/* name */ "U",
/* matcher index */ 61,
/* matcher index */ 62,
},
{
/* [4] */
@ -7934,7 +7980,7 @@ constexpr TemplateTypeInfo kTemplateTypes[] = {
{
/* [5] */
/* name */ "U",
/* matcher index */ 62,
/* matcher index */ 63,
},
{
/* [6] */
@ -7944,7 +7990,7 @@ constexpr TemplateTypeInfo kTemplateTypes[] = {
{
/* [7] */
/* name */ "U",
/* matcher index */ 63,
/* matcher index */ 64,
},
{
/* [8] */
@ -7954,7 +8000,7 @@ constexpr TemplateTypeInfo kTemplateTypes[] = {
{
/* [9] */
/* name */ "U",
/* matcher index */ 64,
/* matcher index */ 65,
},
{
/* [10] */
@ -7964,7 +8010,7 @@ constexpr TemplateTypeInfo kTemplateTypes[] = {
{
/* [11] */
/* name */ "T",
/* matcher index */ 60,
/* matcher index */ 61,
},
{
/* [12] */
@ -7979,12 +8025,12 @@ constexpr TemplateTypeInfo kTemplateTypes[] = {
{
/* [14] */
/* name */ "T",
/* matcher index */ 58,
/* matcher index */ 59,
},
{
/* [15] */
/* name */ "T",
/* matcher index */ 57,
/* matcher index */ 58,
},
{
/* [16] */
@ -7994,27 +8040,27 @@ constexpr TemplateTypeInfo kTemplateTypes[] = {
{
/* [17] */
/* name */ "T",
/* matcher index */ 65,
/* matcher index */ 66,
},
{
/* [18] */
/* name */ "T",
/* matcher index */ 62,
/* matcher index */ 63,
},
{
/* [19] */
/* name */ "T",
/* matcher index */ 61,
/* matcher index */ 62,
},
{
/* [20] */
/* name */ "T",
/* matcher index */ 64,
/* matcher index */ 65,
},
{
/* [21] */
/* name */ "T",
/* matcher index */ 63,
/* matcher index */ 64,
},
{
/* [22] */
@ -8024,7 +8070,7 @@ constexpr TemplateTypeInfo kTemplateTypes[] = {
{
/* [23] */
/* name */ "T",
/* matcher index */ 54,
/* matcher index */ 57,
},
{
/* [24] */
@ -13161,7 +13207,7 @@ constexpr OverloadInfo kOverloads[] = {
/* parameters */ &kParameters[862],
/* return matcher indices */ &kMatcherIndices[1],
/* flags */ OverloadFlags(OverloadFlag::kIsOperator, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
/* const eval */ nullptr,
/* const eval */ &ConstEval::OpMinus,
},
{
/* [423] */
@ -13173,7 +13219,7 @@ constexpr OverloadInfo kOverloads[] = {
/* parameters */ &kParameters[863],
/* return matcher indices */ &kMatcherIndices[39],
/* flags */ OverloadFlags(OverloadFlag::kIsOperator, OverloadFlag::kSupportsVertexPipeline, OverloadFlag::kSupportsFragmentPipeline, OverloadFlag::kSupportsComputePipeline),
/* const eval */ nullptr,
/* const eval */ &ConstEval::OpMinus,
},
{
/* [424] */
@ -14521,8 +14567,8 @@ constexpr IntrinsicInfo kUnaryOperators[] = {
},
{
/* [2] */
/* op -<T : fi32f16>(T) -> T */
/* op -<T : fi32f16, N : num>(vec<N, T>) -> vec<N, T> */
/* op -<T : afi32f16>(T) -> T */
/* op -<T : afi32f16, N : num>(vec<N, T>) -> vec<N, T> */
/* num overloads */ 2,
/* overloads */ &kOverloads[422],
},

View File

@ -604,8 +604,8 @@ TEST_F(IntrinsicTableTest, MismatchUnaryOp) {
EXPECT_EQ(Diagnostics().str(), R"(12:34 error: no matching overload for operator - (bool)
2 candidate operators:
operator - (T) -> T where: T is f32, f16 or i32
operator - (vecN<T>) -> vecN<T> where: T is f32, f16 or i32
operator - (T) -> T where: T is abstract-int, abstract-float, f32, i32 or f16
operator - (vecN<T>) -> vecN<T> where: T is abstract-int, abstract-float, f32, i32 or f16
)");
}

View File

@ -136,6 +136,30 @@ bool Type::is_integer_scalar_or_vector() const {
return is_unsigned_scalar_or_vector() || is_signed_scalar_or_vector();
}
bool Type::is_abstract_scalar_vector() const {
return Is([](const Vector* v) { return v->type()->Is<sem::AbstractNumeric>(); });
}
bool Type::is_abstract_integer_vector() const {
return Is([](const Vector* v) { return v->type()->Is<sem::AbstractInt>(); });
}
bool Type::is_abstract_float_vector() const {
return Is([](const Vector* v) { return v->type()->Is<sem::AbstractFloat>(); });
}
bool Type::is_abstract_scalar_or_vector() const {
return Is<sem::AbstractNumeric>() || is_abstract_scalar_vector();
}
bool Type::is_abstract_integer_scalar_or_vector() const {
return Is<sem::AbstractInt>() || is_abstract_integer_vector();
}
bool Type::is_abstract_float_scalar_or_vector() const {
return Is<sem::AbstractFloat>() || is_abstract_float_vector();
}
bool Type::is_bool_vector() const {
return Is([](const Vector* v) { return v->type()->Is<Bool>(); });
}

View File

@ -103,6 +103,18 @@ class Type : public Castable<Type, Node> {
bool is_signed_scalar_or_vector() const;
/// @returns true if this type is an integer scalar or vector
bool is_integer_scalar_or_vector() const;
/// @returns true if this type is an abstract scalar vector
bool is_abstract_scalar_vector() const;
/// @returns true if this type is an abstract integer vector
bool is_abstract_integer_vector() const;
/// @returns true if this type is an abstract float vector
bool is_abstract_float_vector() const;
/// @returns true if this type is an abstract scalar or vector
bool is_abstract_scalar_or_vector() const;
/// @returns true if this type is an abstract integer scalar or vector
bool is_abstract_integer_scalar_or_vector() const;
/// @returns true if this type is an abstract float scalar or vector
bool is_abstract_float_scalar_or_vector() const;
/// @returns true if this type is a boolean vector
bool is_bool_vector() const;
/// @returns true if this type is boolean scalar or vector

View File

@ -81,16 +81,15 @@ TEST_F(MslUnaryOpTest, Negation) {
EXPECT_EQ(out.str(), "tint_unary_minus(expr)");
}
TEST_F(MslUnaryOpTest, NegationOfIntMin) {
auto* op = create<ast::UnaryOpExpression>(ast::UnaryOp::kNegation,
Expr(i32(std::numeric_limits<int32_t>::min())));
TEST_F(MslUnaryOpTest, IntMin) {
auto* op = Expr(i32(std::numeric_limits<int32_t>::min()));
WrapInFunction(op);
GeneratorImpl& gen = Build();
std::stringstream out;
ASSERT_TRUE(gen.EmitExpression(out, op)) << gen.error();
EXPECT_EQ(out.str(), "tint_unary_minus((-2147483647 - 1))");
EXPECT_EQ(out.str(), "(-2147483647 - 1)");
}
} // namespace