tint/transform: Implement div / mod polyfill
Prevents UB for divide-by-zero and integer overflow when dividing
Fixed: tint:1349
Change-Id: Ieef66d27d7aec3011628ced076b2bccc7770a8af
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/108925
Commit-Queue: Ben Clayton <bclayton@google.com>
Reviewed-by: Dan Sinclair <dsinclair@chromium.org>
Kokoro: Kokoro <noreply+kokoro@google.com>
diff --git a/src/tint/number.h b/src/tint/number.h
index 0558c8f..c116ddf 100644
--- a/src/tint/number.h
+++ b/src/tint/number.h
@@ -124,6 +124,9 @@
/// type is the underlying type of the Number
using type = T;
+ /// Number of bits in the number.
+ static constexpr size_t kNumBits = sizeof(T) * 8;
+
/// Highest finite representable value of this type.
static constexpr type kHighestValue = std::numeric_limits<type>::max();
@@ -187,6 +190,9 @@
/// C++ does not have a native float16 type, so we use a 32-bit float instead.
using type = float;
+ /// Number of bits in the number.
+ static constexpr size_t kNumBits = 16;
+
/// Highest finite representable value of this type.
static constexpr type kHighestValue = 65504.0f; // 2¹⁵ × (1 + 1023/1024)
diff --git a/src/tint/program_builder.h b/src/tint/program_builder.h
index 2f5b5e0..3d1f40e 100644
--- a/src/tint/program_builder.h
+++ b/src/tint/program_builder.h
@@ -3267,6 +3267,14 @@
//! @cond Doxygen_Suppress
// Various template specializations for ProgramBuilder::TypesBuilder::CToAST.
template <>
+struct ProgramBuilder::TypesBuilder::CToAST<AInt> {
+ static const ast::Type* get(const ProgramBuilder::TypesBuilder*) { return nullptr; }
+};
+template <>
+struct ProgramBuilder::TypesBuilder::CToAST<AFloat> {
+ static const ast::Type* get(const ProgramBuilder::TypesBuilder*) { return nullptr; }
+};
+template <>
struct ProgramBuilder::TypesBuilder::CToAST<i32> {
static const ast::Type* get(const ProgramBuilder::TypesBuilder* t) { return t->i32(); }
};
diff --git a/src/tint/transform/builtin_polyfill.cc b/src/tint/transform/builtin_polyfill.cc
index 17fcc20..4dfc149 100644
--- a/src/tint/transform/builtin_polyfill.cc
+++ b/src/tint/transform/builtin_polyfill.cc
@@ -14,6 +14,8 @@
#include "src/tint/transform/builtin_polyfill.h"
+#include <algorithm>
+#include <tuple>
#include <unordered_map>
#include <utility>
@@ -29,6 +31,9 @@
namespace tint::transform {
+/// BinaryOpSignature is tuple of a binary op, LHS type and RHS type
+using BinaryOpSignature = std::tuple<ast::BinaryOp, const sem::Type*, const sem::Type*>;
+
/// PIMPL state for the transform
struct BuiltinPolyfill::State {
/// Constructor
@@ -36,15 +41,6 @@
/// @param p the builtins to polyfill
State(CloneContext& c, Builtins p) : ctx(c), polyfill(p) {}
- /// The clone context
- CloneContext& ctx;
- /// The builtins to polyfill
- Builtins polyfill;
- /// The destination program builder
- ProgramBuilder& b = *ctx.dst;
- /// The source clone context
- const sem::Info& sem = ctx.src->Sem();
-
/// Builds the polyfill function for the `acosh` builtin
/// @param ty the parameter and return type for the function
/// @return the polyfill function name
@@ -563,6 +559,63 @@
return name;
}
+ /// Builds the polyfill function for a divide or modulo operator with integer scalar or vector
+ /// operands.
+ /// @param sig the signature of the binary operator
+ /// @return the polyfill function name
+ Symbol int_div_mod(const BinaryOpSignature& sig) {
+ const auto op = std::get<0>(sig);
+ const auto* lhs_ty = std::get<1>(sig);
+ const auto* rhs_ty = std::get<2>(sig);
+ const bool is_div = op == ast::BinaryOp::kDivide;
+
+ uint32_t lhs_width = 1;
+ uint32_t rhs_width = 1;
+ const auto* lhs_el_ty = sem::Type::ElementOf(lhs_ty, &lhs_width);
+ const auto* rhs_el_ty = sem::Type::ElementOf(rhs_ty, &rhs_width);
+
+ const uint32_t width = std::max(lhs_width, rhs_width);
+
+ const char* lhs = "lhs";
+ const char* rhs = "rhs";
+
+ utils::Vector<const ast::Statement*, 4> body;
+
+ if (lhs_width < width) {
+ // lhs is scalar, rhs is vector. Convert lhs to vector.
+ body.Push(b.Decl(b.Let("l", b.vec(T(lhs_el_ty), width, b.Expr(lhs)))));
+ lhs = "l";
+ }
+ if (rhs_width < width) {
+ // lhs is vector, rhs is scalar. Convert rhs to vector.
+ body.Push(b.Decl(b.Let("r", b.vec(T(rhs_el_ty), width, b.Expr(rhs)))));
+ rhs = "r";
+ }
+
+ auto name = b.Symbols().New(is_div ? "tint_div" : "tint_mod");
+ auto* use_one = b.Equal(rhs, ScalarOrVector(width, 0_a));
+ if (lhs_ty->is_signed_scalar_or_vector()) {
+ const auto bits = lhs_el_ty->Size() * 8;
+ auto min_int = AInt(AInt::kLowestValue >> (AInt::kNumBits - bits));
+ const ast::Expression* lhs_is_min = b.Equal(lhs, ScalarOrVector(width, min_int));
+ const ast::Expression* rhs_is_minus_one = b.Equal(rhs, ScalarOrVector(width, -1_a));
+ // use_one = use_one | ((lhs == MIN_INT) & (rhs == -1))
+ use_one = b.Or(use_one, b.And(lhs_is_min, rhs_is_minus_one));
+ }
+ auto* select = b.Call("select", rhs, ScalarOrVector(width, 1_a), use_one);
+
+ body.Push(b.Return(is_div ? b.Div(lhs, select) : b.Mod(lhs, select)));
+ b.Func(name,
+ utils::Vector{
+ b.Param("lhs", T(lhs_ty)),
+ b.Param("rhs", T(rhs_ty)),
+ },
+ width == 1 ? T(lhs_ty) : b.ty.vec(T(lhs_el_ty), width), // return type
+ std::move(body));
+
+ return name;
+ }
+
/// Builds the polyfill function for the `saturate` builtin
/// @param ty the parameter and return type for the function
/// @return the polyfill function name
@@ -625,6 +678,15 @@
}
private:
+ /// The clone context
+ CloneContext& ctx;
+ /// The builtins to polyfill
+ Builtins polyfill;
+ /// The destination program builder
+ ProgramBuilder& b = *ctx.dst;
+ /// The source clone context
+ const sem::Info& sem = ctx.src->Sem();
+
/// @returns the AST type for the given sem type
const ast::Type* T(const sem::Type* ty) const { return CreateASTTypeFor(ctx, ty); }
@@ -659,13 +721,14 @@
return SkipTransform;
}
- auto& builtins = cfg->builtins;
+ auto& polyfill = cfg->builtins;
utils::Hashmap<const sem::Builtin*, Symbol, 8> builtin_polyfills;
+ utils::Hashmap<BinaryOpSignature, Symbol, 8> binary_op_polyfills;
ProgramBuilder b;
CloneContext ctx{&b, src, /* auto_clone_symbols */ true};
- State s{ctx, builtins};
+ State s{ctx, polyfill};
bool made_changes = false;
for (auto* node : src->ASTNodes().Objects()) {
@@ -679,84 +742,84 @@
if (!builtin) {
continue;
}
- Symbol polyfill;
+ Symbol fn;
switch (builtin->Type()) {
case sem::BuiltinType::kAcosh:
- if (builtins.acosh != Level::kNone) {
- polyfill = builtin_polyfills.GetOrCreate(
+ if (polyfill.acosh != Level::kNone) {
+ fn = builtin_polyfills.GetOrCreate(
builtin, [&] { return s.acosh(builtin->ReturnType()); });
}
break;
case sem::BuiltinType::kAsinh:
- if (builtins.asinh) {
- polyfill = builtin_polyfills.GetOrCreate(
+ if (polyfill.asinh) {
+ fn = builtin_polyfills.GetOrCreate(
builtin, [&] { return s.asinh(builtin->ReturnType()); });
}
break;
case sem::BuiltinType::kAtanh:
- if (builtins.atanh != Level::kNone) {
- polyfill = builtin_polyfills.GetOrCreate(
+ if (polyfill.atanh != Level::kNone) {
+ fn = builtin_polyfills.GetOrCreate(
builtin, [&] { return s.atanh(builtin->ReturnType()); });
}
break;
case sem::BuiltinType::kClamp:
- if (builtins.clamp_int) {
+ if (polyfill.clamp_int) {
auto& sig = builtin->Signature();
if (sig.parameters[0]->Type()->is_integer_scalar_or_vector()) {
- polyfill = builtin_polyfills.GetOrCreate(
+ fn = builtin_polyfills.GetOrCreate(
builtin, [&] { return s.clampInteger(builtin->ReturnType()); });
}
}
break;
case sem::BuiltinType::kCountLeadingZeros:
- if (builtins.count_leading_zeros) {
- polyfill = builtin_polyfills.GetOrCreate(
+ if (polyfill.count_leading_zeros) {
+ fn = builtin_polyfills.GetOrCreate(
builtin, [&] { return s.countLeadingZeros(builtin->ReturnType()); });
}
break;
case sem::BuiltinType::kCountTrailingZeros:
- if (builtins.count_trailing_zeros) {
- polyfill = builtin_polyfills.GetOrCreate(
+ if (polyfill.count_trailing_zeros) {
+ fn = builtin_polyfills.GetOrCreate(
builtin, [&] { return s.countTrailingZeros(builtin->ReturnType()); });
}
break;
case sem::BuiltinType::kExtractBits:
- if (builtins.extract_bits != Level::kNone) {
- polyfill = builtin_polyfills.GetOrCreate(
+ if (polyfill.extract_bits != Level::kNone) {
+ fn = builtin_polyfills.GetOrCreate(
builtin, [&] { return s.extractBits(builtin->ReturnType()); });
}
break;
case sem::BuiltinType::kFirstLeadingBit:
- if (builtins.first_leading_bit) {
- polyfill = builtin_polyfills.GetOrCreate(
+ if (polyfill.first_leading_bit) {
+ fn = builtin_polyfills.GetOrCreate(
builtin, [&] { return s.firstLeadingBit(builtin->ReturnType()); });
}
break;
case sem::BuiltinType::kFirstTrailingBit:
- if (builtins.first_trailing_bit) {
- polyfill = builtin_polyfills.GetOrCreate(
+ if (polyfill.first_trailing_bit) {
+ fn = builtin_polyfills.GetOrCreate(
builtin, [&] { return s.firstTrailingBit(builtin->ReturnType()); });
}
break;
case sem::BuiltinType::kInsertBits:
- if (builtins.insert_bits != Level::kNone) {
- polyfill = builtin_polyfills.GetOrCreate(
+ if (polyfill.insert_bits != Level::kNone) {
+ fn = builtin_polyfills.GetOrCreate(
builtin, [&] { return s.insertBits(builtin->ReturnType()); });
}
break;
case sem::BuiltinType::kSaturate:
- if (builtins.saturate) {
- polyfill = builtin_polyfills.GetOrCreate(
+ if (polyfill.saturate) {
+ fn = builtin_polyfills.GetOrCreate(
builtin, [&] { return s.saturate(builtin->ReturnType()); });
}
break;
case sem::BuiltinType::kTextureSampleBaseClampToEdge:
- if (builtins.texture_sample_base_clamp_to_edge_2d_f32) {
+ if (polyfill.texture_sample_base_clamp_to_edge_2d_f32) {
auto& sig = builtin->Signature();
auto* tex = sig.Parameter(sem::ParameterUsage::kTexture);
if (auto* stex = tex->Type()->As<sem::SampledTexture>()) {
if (stex->type()->Is<sem::F32>()) {
- polyfill = builtin_polyfills.GetOrCreate(builtin, [&] {
+ fn = builtin_polyfills.GetOrCreate(builtin, [&] {
return s.textureSampleBaseClampToEdge_2d_f32();
});
}
@@ -764,9 +827,9 @@
}
break;
case sem::BuiltinType::kQuantizeToF16:
- if (builtins.quantize_to_vec_f16) {
+ if (polyfill.quantize_to_vec_f16) {
if (auto* vec = builtin->ReturnType()->As<sem::Vector>()) {
- polyfill = builtin_polyfills.GetOrCreate(
+ fn = builtin_polyfills.GetOrCreate(
builtin, [&] { return s.quantizeToF16(vec); });
}
}
@@ -776,16 +839,16 @@
break;
}
- if (polyfill.IsValid()) {
- auto* replacement = s.b.Call(polyfill, ctx.Clone(call->Declaration()->args));
+ if (fn.IsValid()) {
+ auto* replacement = b.Call(fn, ctx.Clone(call->Declaration()->args));
ctx.Replace(call->Declaration(), replacement);
made_changes = true;
}
} else if (auto* bin_op = node->As<ast::BinaryExpression>()) {
switch (bin_op->op) {
case ast::BinaryOp::kShiftLeft:
- case ast::BinaryOp::kShiftRight:
- if (builtins.bitshift_modulo) {
+ case ast::BinaryOp::kShiftRight: {
+ if (polyfill.bitshift_modulo) {
auto* lhs_ty = src->TypeOf(bin_op->lhs)->UnwrapRef();
auto* rhs_ty = src->TypeOf(bin_op->rhs)->UnwrapRef();
auto* lhs_el_ty = sem::Type::DeepestElementOf(lhs_ty);
@@ -798,6 +861,24 @@
made_changes = true;
}
break;
+ }
+ case ast::BinaryOp::kDivide:
+ case ast::BinaryOp::kModulo: {
+ if (polyfill.int_div_mod) {
+ auto* lhs_ty = src->TypeOf(bin_op->lhs)->UnwrapRef();
+ if (lhs_ty->is_integer_scalar_or_vector()) {
+ auto* rhs_ty = src->TypeOf(bin_op->rhs)->UnwrapRef();
+ BinaryOpSignature sig{bin_op->op, lhs_ty, rhs_ty};
+ auto fn = binary_op_polyfills.GetOrCreate(
+ sig, [&] { return s.int_div_mod(sig); });
+ auto* lhs = ctx.Clone(bin_op->lhs);
+ auto* rhs = ctx.Clone(bin_op->rhs);
+ ctx.Replace(bin_op, b.Call(fn, lhs, rhs));
+ made_changes = true;
+ }
+ }
+ break;
+ }
default:
break;
}
diff --git a/src/tint/transform/builtin_polyfill.h b/src/tint/transform/builtin_polyfill.h
index 7083aa7..f9eb029 100644
--- a/src/tint/transform/builtin_polyfill.h
+++ b/src/tint/transform/builtin_polyfill.h
@@ -63,6 +63,9 @@
bool first_trailing_bit = false;
/// Should `insertBits()` be polyfilled?
Level insert_bits = Level::kNone;
+ /// Should integer scalar / vector divides and modulos be polyfilled to avoid DBZ and
+ /// integer overflows?
+ bool int_div_mod = false;
/// Should `saturate()` be polyfilled?
bool saturate = false;
/// Should `textureSampleBaseClampToEdge()` be polyfilled for texture_2d<f32> textures?
diff --git a/src/tint/transform/builtin_polyfill_test.cc b/src/tint/transform/builtin_polyfill_test.cc
index 1e380d4..b767167 100644
--- a/src/tint/transform/builtin_polyfill_test.cc
+++ b/src/tint/transform/builtin_polyfill_test.cc
@@ -1921,6 +1921,775 @@
}
////////////////////////////////////////////////////////////////////////////////
+// int_div_mod
+////////////////////////////////////////////////////////////////////////////////
+DataMap polyfillIntDivMod() {
+ BuiltinPolyfill::Builtins builtins;
+ builtins.int_div_mod = true;
+ DataMap data;
+ data.Add<BuiltinPolyfill::Config>(builtins);
+ return data;
+}
+
+TEST_F(BuiltinPolyfillTest, ShouldRunIntDiv) {
+ auto* src = R"(
+fn f() {
+ let v = 10i;
+ let x = 20i / v;
+}
+)";
+
+ EXPECT_FALSE(ShouldRun<BuiltinPolyfill>(src));
+ EXPECT_TRUE(ShouldRun<BuiltinPolyfill>(src, polyfillIntDivMod()));
+}
+
+TEST_F(BuiltinPolyfillTest, ShouldRunIntMod) {
+ auto* src = R"(
+fn f() {
+ let v = 10i;
+ let x = 20i % v;
+}
+)";
+
+ EXPECT_FALSE(ShouldRun<BuiltinPolyfill>(src));
+ EXPECT_TRUE(ShouldRun<BuiltinPolyfill>(src, polyfillIntDivMod()));
+}
+
+TEST_F(BuiltinPolyfillTest, IntDiv_ai_i32) {
+ auto* src = R"(
+fn f() {
+ let v = 10i;
+ let x = 20 / v;
+}
+)";
+
+ auto* expect = R"(
+fn tint_div(lhs : i32, rhs : i32) -> i32 {
+ return (lhs / select(rhs, 1, ((rhs == 0) | ((lhs == -2147483648) & (rhs == -1)))));
+}
+
+fn f() {
+ let v = 10i;
+ let x = tint_div(20, v);
+}
+)";
+
+ auto got = Run<BuiltinPolyfill>(src, polyfillIntDivMod());
+
+ EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(BuiltinPolyfillTest, IntMod_ai_i32) {
+ auto* src = R"(
+fn f() {
+ let v = 10i;
+ let x = 20 % v;
+}
+)";
+
+ auto* expect = R"(
+fn tint_mod(lhs : i32, rhs : i32) -> i32 {
+ return (lhs % select(rhs, 1, ((rhs == 0) | ((lhs == -2147483648) & (rhs == -1)))));
+}
+
+fn f() {
+ let v = 10i;
+ let x = tint_mod(20, v);
+}
+)";
+
+ auto got = Run<BuiltinPolyfill>(src, polyfillIntDivMod());
+
+ EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(BuiltinPolyfillTest, IntDiv_i32_ai) {
+ auto* src = R"(
+fn f() {
+ let v = 10i;
+ let x = v / 20;
+}
+)";
+
+ auto* expect = R"(
+fn tint_div(lhs : i32, rhs : i32) -> i32 {
+ return (lhs / select(rhs, 1, ((rhs == 0) | ((lhs == -2147483648) & (rhs == -1)))));
+}
+
+fn f() {
+ let v = 10i;
+ let x = tint_div(v, 20);
+}
+)";
+
+ auto got = Run<BuiltinPolyfill>(src, polyfillIntDivMod());
+
+ EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(BuiltinPolyfillTest, IntMod_i32_ai) {
+ auto* src = R"(
+fn f() {
+ let v = 10i;
+ let x = v % 20;
+}
+)";
+
+ auto* expect = R"(
+fn tint_mod(lhs : i32, rhs : i32) -> i32 {
+ return (lhs % select(rhs, 1, ((rhs == 0) | ((lhs == -2147483648) & (rhs == -1)))));
+}
+
+fn f() {
+ let v = 10i;
+ let x = tint_mod(v, 20);
+}
+)";
+
+ auto got = Run<BuiltinPolyfill>(src, polyfillIntDivMod());
+
+ EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(BuiltinPolyfillTest, IntDiv_i32_i32) {
+ auto* src = R"(
+fn f() {
+ let v = 10i;
+ let x = 20i / v;
+}
+)";
+
+ auto* expect = R"(
+fn tint_div(lhs : i32, rhs : i32) -> i32 {
+ return (lhs / select(rhs, 1, ((rhs == 0) | ((lhs == -2147483648) & (rhs == -1)))));
+}
+
+fn f() {
+ let v = 10i;
+ let x = tint_div(20i, v);
+}
+)";
+
+ auto got = Run<BuiltinPolyfill>(src, polyfillIntDivMod());
+
+ EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(BuiltinPolyfillTest, IntMod_i32_i32) {
+ auto* src = R"(
+fn f() {
+ let v = 10i;
+ let x = 20i % v;
+}
+)";
+
+ auto* expect = R"(
+fn tint_mod(lhs : i32, rhs : i32) -> i32 {
+ return (lhs % select(rhs, 1, ((rhs == 0) | ((lhs == -2147483648) & (rhs == -1)))));
+}
+
+fn f() {
+ let v = 10i;
+ let x = tint_mod(20i, v);
+}
+)";
+
+ auto got = Run<BuiltinPolyfill>(src, polyfillIntDivMod());
+
+ EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(BuiltinPolyfillTest, IntDiv_ai_u32) {
+ auto* src = R"(
+fn f() {
+ let v = 10u;
+ let x = 20 / v;
+}
+)";
+
+ auto* expect = R"(
+fn tint_div(lhs : u32, rhs : u32) -> u32 {
+ return (lhs / select(rhs, 1, (rhs == 0)));
+}
+
+fn f() {
+ let v = 10u;
+ let x = tint_div(20, v);
+}
+)";
+
+ auto got = Run<BuiltinPolyfill>(src, polyfillIntDivMod());
+
+ EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(BuiltinPolyfillTest, IntMod_ai_u32) {
+ auto* src = R"(
+fn f() {
+ let v = 10u;
+ let x = 20 % v;
+}
+)";
+
+ auto* expect = R"(
+fn tint_mod(lhs : u32, rhs : u32) -> u32 {
+ return (lhs % select(rhs, 1, (rhs == 0)));
+}
+
+fn f() {
+ let v = 10u;
+ let x = tint_mod(20, v);
+}
+)";
+
+ auto got = Run<BuiltinPolyfill>(src, polyfillIntDivMod());
+
+ EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(BuiltinPolyfillTest, IntDiv_u32_ai) {
+ auto* src = R"(
+fn f() {
+ let v = 10u;
+ let x = v / 20;
+}
+)";
+
+ auto* expect = R"(
+fn tint_div(lhs : u32, rhs : u32) -> u32 {
+ return (lhs / select(rhs, 1, (rhs == 0)));
+}
+
+fn f() {
+ let v = 10u;
+ let x = tint_div(v, 20);
+}
+)";
+
+ auto got = Run<BuiltinPolyfill>(src, polyfillIntDivMod());
+
+ EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(BuiltinPolyfillTest, IntMod_u32_ai) {
+ auto* src = R"(
+fn f() {
+ let v = 10u;
+ let x = v % 20;
+}
+)";
+
+ auto* expect = R"(
+fn tint_mod(lhs : u32, rhs : u32) -> u32 {
+ return (lhs % select(rhs, 1, (rhs == 0)));
+}
+
+fn f() {
+ let v = 10u;
+ let x = tint_mod(v, 20);
+}
+)";
+
+ auto got = Run<BuiltinPolyfill>(src, polyfillIntDivMod());
+
+ EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(BuiltinPolyfillTest, IntDiv_u32_u32) {
+ auto* src = R"(
+fn f() {
+ let v = 10u;
+ let x = 20u / v;
+}
+)";
+
+ auto* expect = R"(
+fn tint_div(lhs : u32, rhs : u32) -> u32 {
+ return (lhs / select(rhs, 1, (rhs == 0)));
+}
+
+fn f() {
+ let v = 10u;
+ let x = tint_div(20u, v);
+}
+)";
+
+ auto got = Run<BuiltinPolyfill>(src, polyfillIntDivMod());
+
+ EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(BuiltinPolyfillTest, IntMod_u32_u32) {
+ auto* src = R"(
+fn f() {
+ let v = 10u;
+ let x = 20u % v;
+}
+)";
+
+ auto* expect = R"(
+fn tint_mod(lhs : u32, rhs : u32) -> u32 {
+ return (lhs % select(rhs, 1, (rhs == 0)));
+}
+
+fn f() {
+ let v = 10u;
+ let x = tint_mod(20u, v);
+}
+)";
+
+ auto got = Run<BuiltinPolyfill>(src, polyfillIntDivMod());
+
+ EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(BuiltinPolyfillTest, IntDiv_vec3_ai_i32) {
+ auto* src = R"(
+fn f() {
+ let v = 10i;
+ let x = vec3(20) / v;
+}
+)";
+
+ auto* expect = R"(
+fn tint_div(lhs : vec3<i32>, rhs : i32) -> vec3<i32> {
+ let r = vec3<i32>(rhs);
+ return (lhs / select(r, vec3(1), ((r == vec3(0)) | ((lhs == vec3(-2147483648)) & (r == vec3(-1))))));
+}
+
+fn f() {
+ let v = 10i;
+ let x = tint_div(vec3(20), v);
+}
+)";
+
+ auto got = Run<BuiltinPolyfill>(src, polyfillIntDivMod());
+
+ EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(BuiltinPolyfillTest, IntMod_vec3_ai_i32) {
+ auto* src = R"(
+fn f() {
+ let v = 10i;
+ let x = vec3(20) % v;
+}
+)";
+
+ auto* expect = R"(
+fn tint_mod(lhs : vec3<i32>, rhs : i32) -> vec3<i32> {
+ let r = vec3<i32>(rhs);
+ return (lhs % select(r, vec3(1), ((r == vec3(0)) | ((lhs == vec3(-2147483648)) & (r == vec3(-1))))));
+}
+
+fn f() {
+ let v = 10i;
+ let x = tint_mod(vec3(20), v);
+}
+)";
+
+ auto got = Run<BuiltinPolyfill>(src, polyfillIntDivMod());
+
+ EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(BuiltinPolyfillTest, IntDiv_vec3_i32_ai) {
+ auto* src = R"(
+fn f() {
+ let v = 10i;
+ let x = vec3(v) / 20;
+}
+)";
+
+ auto* expect = R"(
+fn tint_div(lhs : vec3<i32>, rhs : i32) -> vec3<i32> {
+ let r = vec3<i32>(rhs);
+ return (lhs / select(r, vec3(1), ((r == vec3(0)) | ((lhs == vec3(-2147483648)) & (r == vec3(-1))))));
+}
+
+fn f() {
+ let v = 10i;
+ let x = tint_div(vec3(v), 20);
+}
+)";
+
+ auto got = Run<BuiltinPolyfill>(src, polyfillIntDivMod());
+
+ EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(BuiltinPolyfillTest, IntMod_vec3_i32_ai) {
+ auto* src = R"(
+fn f() {
+ let v = 10i;
+ let x = vec3(v) % 20;
+}
+)";
+
+ auto* expect = R"(
+fn tint_mod(lhs : vec3<i32>, rhs : i32) -> vec3<i32> {
+ let r = vec3<i32>(rhs);
+ return (lhs % select(r, vec3(1), ((r == vec3(0)) | ((lhs == vec3(-2147483648)) & (r == vec3(-1))))));
+}
+
+fn f() {
+ let v = 10i;
+ let x = tint_mod(vec3(v), 20);
+}
+)";
+
+ auto got = Run<BuiltinPolyfill>(src, polyfillIntDivMod());
+
+ EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(BuiltinPolyfillTest, IntDiv_vec3_i32_i32) {
+ auto* src = R"(
+fn f() {
+ let v = 10i;
+ let x = vec3<i32>(20i) / v;
+}
+)";
+
+ auto* expect = R"(
+fn tint_div(lhs : vec3<i32>, rhs : i32) -> vec3<i32> {
+ let r = vec3<i32>(rhs);
+ return (lhs / select(r, vec3(1), ((r == vec3(0)) | ((lhs == vec3(-2147483648)) & (r == vec3(-1))))));
+}
+
+fn f() {
+ let v = 10i;
+ let x = tint_div(vec3<i32>(20i), v);
+}
+)";
+
+ auto got = Run<BuiltinPolyfill>(src, polyfillIntDivMod());
+
+ EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(BuiltinPolyfillTest, IntMod_vec3_i32_i32) {
+ auto* src = R"(
+fn f() {
+ let v = 10i;
+ let x = vec3<i32>(20i) % v;
+}
+)";
+
+ auto* expect = R"(
+fn tint_mod(lhs : vec3<i32>, rhs : i32) -> vec3<i32> {
+ let r = vec3<i32>(rhs);
+ return (lhs % select(r, vec3(1), ((r == vec3(0)) | ((lhs == vec3(-2147483648)) & (r == vec3(-1))))));
+}
+
+fn f() {
+ let v = 10i;
+ let x = tint_mod(vec3<i32>(20i), v);
+}
+)";
+
+ auto got = Run<BuiltinPolyfill>(src, polyfillIntDivMod());
+
+ EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(BuiltinPolyfillTest, IntDiv_vec3_u32_u32) {
+ auto* src = R"(
+fn f() {
+ let v = 10u;
+ let x = vec3<u32>(20u) / v;
+}
+)";
+
+ auto* expect = R"(
+fn tint_div(lhs : vec3<u32>, rhs : u32) -> vec3<u32> {
+ let r = vec3<u32>(rhs);
+ return (lhs / select(r, vec3(1), (r == vec3(0))));
+}
+
+fn f() {
+ let v = 10u;
+ let x = tint_div(vec3<u32>(20u), v);
+}
+)";
+
+ auto got = Run<BuiltinPolyfill>(src, polyfillIntDivMod());
+
+ EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(BuiltinPolyfillTest, IntMod_vec3_u32_u32) {
+ auto* src = R"(
+fn f() {
+ let v = 10u;
+ let x = vec3<u32>(20u) % v;
+}
+)";
+
+ auto* expect = R"(
+fn tint_mod(lhs : vec3<u32>, rhs : u32) -> vec3<u32> {
+ let r = vec3<u32>(rhs);
+ return (lhs % select(r, vec3(1), (r == vec3(0))));
+}
+
+fn f() {
+ let v = 10u;
+ let x = tint_mod(vec3<u32>(20u), v);
+}
+)";
+
+ auto got = Run<BuiltinPolyfill>(src, polyfillIntDivMod());
+
+ EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(BuiltinPolyfillTest, IntDiv_ai_vec3_i32) {
+ auto* src = R"(
+fn f() {
+ let v = 10i;
+ let x = 20 / vec3(v);
+}
+)";
+
+ auto* expect = R"(
+fn tint_div(lhs : i32, rhs : vec3<i32>) -> vec3<i32> {
+ let l = vec3<i32>(lhs);
+ return (l / select(rhs, vec3(1), ((rhs == vec3(0)) | ((l == vec3(-2147483648)) & (rhs == vec3(-1))))));
+}
+
+fn f() {
+ let v = 10i;
+ let x = tint_div(20, vec3(v));
+}
+)";
+
+ auto got = Run<BuiltinPolyfill>(src, polyfillIntDivMod());
+
+ EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(BuiltinPolyfillTest, IntMod_ai_vec3_i32) {
+ auto* src = R"(
+fn f() {
+ let v = 10i;
+ let x = 20 % vec3(v);
+}
+)";
+
+ auto* expect = R"(
+fn tint_mod(lhs : i32, rhs : vec3<i32>) -> vec3<i32> {
+ let l = vec3<i32>(lhs);
+ return (l % select(rhs, vec3(1), ((rhs == vec3(0)) | ((l == vec3(-2147483648)) & (rhs == vec3(-1))))));
+}
+
+fn f() {
+ let v = 10i;
+ let x = tint_mod(20, vec3(v));
+}
+)";
+
+ auto got = Run<BuiltinPolyfill>(src, polyfillIntDivMod());
+
+ EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(BuiltinPolyfillTest, IntDiv_i32_vec3_i32) {
+ auto* src = R"(
+fn f() {
+ let v = 10i;
+ let x = 20i / vec3<i32>(v);
+}
+)";
+
+ auto* expect = R"(
+fn tint_div(lhs : i32, rhs : vec3<i32>) -> vec3<i32> {
+ let l = vec3<i32>(lhs);
+ return (l / select(rhs, vec3(1), ((rhs == vec3(0)) | ((l == vec3(-2147483648)) & (rhs == vec3(-1))))));
+}
+
+fn f() {
+ let v = 10i;
+ let x = tint_div(20i, vec3<i32>(v));
+}
+)";
+
+ auto got = Run<BuiltinPolyfill>(src, polyfillIntDivMod());
+
+ EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(BuiltinPolyfillTest, IntMod_i32_vec3_i32) {
+ auto* src = R"(
+fn f() {
+ let v = 10i;
+ let x = 20i % vec3<i32>(v);
+}
+)";
+
+ auto* expect = R"(
+fn tint_mod(lhs : i32, rhs : vec3<i32>) -> vec3<i32> {
+ let l = vec3<i32>(lhs);
+ return (l % select(rhs, vec3(1), ((rhs == vec3(0)) | ((l == vec3(-2147483648)) & (rhs == vec3(-1))))));
+}
+
+fn f() {
+ let v = 10i;
+ let x = tint_mod(20i, vec3<i32>(v));
+}
+)";
+
+ auto got = Run<BuiltinPolyfill>(src, polyfillIntDivMod());
+
+ EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(BuiltinPolyfillTest, IntDiv_u32_vec3_u32) {
+ auto* src = R"(
+fn f() {
+ let v = 10u;
+ let x = 20u / vec3<u32>(v);
+}
+)";
+
+ auto* expect = R"(
+fn tint_div(lhs : u32, rhs : vec3<u32>) -> vec3<u32> {
+ let l = vec3<u32>(lhs);
+ return (l / select(rhs, vec3(1), (rhs == vec3(0))));
+}
+
+fn f() {
+ let v = 10u;
+ let x = tint_div(20u, vec3<u32>(v));
+}
+)";
+
+ auto got = Run<BuiltinPolyfill>(src, polyfillIntDivMod());
+
+ EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(BuiltinPolyfillTest, IntMod_u32_vec3_u32) {
+ auto* src = R"(
+fn f() {
+ let v = 10u;
+ let x = 20u % vec3<u32>(v);
+}
+)";
+
+ auto* expect = R"(
+fn tint_mod(lhs : u32, rhs : vec3<u32>) -> vec3<u32> {
+ let l = vec3<u32>(lhs);
+ return (l % select(rhs, vec3(1), (rhs == vec3(0))));
+}
+
+fn f() {
+ let v = 10u;
+ let x = tint_mod(20u, vec3<u32>(v));
+}
+)";
+
+ auto got = Run<BuiltinPolyfill>(src, polyfillIntDivMod());
+
+ EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(BuiltinPolyfillTest, IntDiv_vec3_i32_vec3_i32) {
+ auto* src = R"(
+fn f() {
+ let v = 10i;
+ let x = vec3<i32>(20i) / vec3<i32>(v);
+}
+)";
+
+ auto* expect = R"(
+fn tint_div(lhs : vec3<i32>, rhs : vec3<i32>) -> vec3<i32> {
+ return (lhs / select(rhs, vec3(1), ((rhs == vec3(0)) | ((lhs == vec3(-2147483648)) & (rhs == vec3(-1))))));
+}
+
+fn f() {
+ let v = 10i;
+ let x = tint_div(vec3<i32>(20i), vec3<i32>(v));
+}
+)";
+
+ auto got = Run<BuiltinPolyfill>(src, polyfillIntDivMod());
+
+ EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(BuiltinPolyfillTest, IntMod_vec3_i32_vec3_i32) {
+ auto* src = R"(
+fn f() {
+ let v = 10i;
+ let x = vec3<i32>(20i) % vec3<i32>(v);
+}
+)";
+
+ auto* expect = R"(
+fn tint_mod(lhs : vec3<i32>, rhs : vec3<i32>) -> vec3<i32> {
+ return (lhs % select(rhs, vec3(1), ((rhs == vec3(0)) | ((lhs == vec3(-2147483648)) & (rhs == vec3(-1))))));
+}
+
+fn f() {
+ let v = 10i;
+ let x = tint_mod(vec3<i32>(20i), vec3<i32>(v));
+}
+)";
+
+ auto got = Run<BuiltinPolyfill>(src, polyfillIntDivMod());
+
+ EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(BuiltinPolyfillTest, IntDiv_vec3_u32_vec3_u32) {
+ auto* src = R"(
+fn f() {
+ let v = 10u;
+ let x = vec3<u32>(20u) / vec3<u32>(v);
+}
+)";
+
+ auto* expect = R"(
+fn tint_div(lhs : vec3<u32>, rhs : vec3<u32>) -> vec3<u32> {
+ return (lhs / select(rhs, vec3(1), (rhs == vec3(0))));
+}
+
+fn f() {
+ let v = 10u;
+ let x = tint_div(vec3<u32>(20u), vec3<u32>(v));
+}
+)";
+
+ auto got = Run<BuiltinPolyfill>(src, polyfillIntDivMod());
+
+ EXPECT_EQ(expect, str(got));
+}
+
+TEST_F(BuiltinPolyfillTest, IntMod_vec3_u32_vec3_u32) {
+ auto* src = R"(
+fn f() {
+ let v = 10u;
+ let x = vec3<u32>(20u) % vec3<u32>(v);
+}
+)";
+
+ auto* expect = R"(
+fn tint_mod(lhs : vec3<u32>, rhs : vec3<u32>) -> vec3<u32> {
+ return (lhs % select(rhs, vec3(1), (rhs == vec3(0))));
+}
+
+fn f() {
+ let v = 10u;
+ let x = tint_mod(vec3<u32>(20u), vec3<u32>(v));
+}
+)";
+
+ auto got = Run<BuiltinPolyfill>(src, polyfillIntDivMod());
+
+ EXPECT_EQ(expect, str(got));
+}
+
+////////////////////////////////////////////////////////////////////////////////
// saturate
////////////////////////////////////////////////////////////////////////////////
DataMap polyfillSaturate() {
diff --git a/src/tint/writer/glsl/generator_impl.cc b/src/tint/writer/glsl/generator_impl.cc
index 29136cb..b8140b9 100644
--- a/src/tint/writer/glsl/generator_impl.cc
+++ b/src/tint/writer/glsl/generator_impl.cc
@@ -182,6 +182,9 @@
manager.Add<transform::DisableUniformityAnalysis>();
+ // ExpandCompoundAssignment must come before BuiltinPolyfill
+ manager.Add<transform::ExpandCompoundAssignment>();
+
{ // Builtin polyfills
transform::BuiltinPolyfill::Builtins polyfills;
polyfills.acosh = transform::BuiltinPolyfill::Level::kRangeCheck;
@@ -193,6 +196,7 @@
polyfills.first_leading_bit = true;
polyfills.first_trailing_bit = true;
polyfills.insert_bits = transform::BuiltinPolyfill::Level::kClampParameters;
+ polyfills.int_div_mod = true;
polyfills.saturate = true;
polyfills.texture_sample_base_clamp_to_edge_2d_f32 = true;
data.Add<transform::BuiltinPolyfill::Config>(polyfills);
@@ -214,7 +218,6 @@
manager.Add<transform::ZeroInitWorkgroupMemory>();
}
manager.Add<transform::CanonicalizeEntryPointIO>();
- manager.Add<transform::ExpandCompoundAssignment>();
manager.Add<transform::PromoteSideEffectsToDecl>();
manager.Add<transform::PadStructs>();
diff --git a/src/tint/writer/hlsl/generator_impl.cc b/src/tint/writer/hlsl/generator_impl.cc
index a7c3f00..3eb3358 100644
--- a/src/tint/writer/hlsl/generator_impl.cc
+++ b/src/tint/writer/hlsl/generator_impl.cc
@@ -157,6 +157,9 @@
manager.Add<transform::DisableUniformityAnalysis>();
+ // ExpandCompoundAssignment must come before BuiltinPolyfill
+ manager.Add<transform::ExpandCompoundAssignment>();
+
{ // Builtin polyfills
transform::BuiltinPolyfill::Builtins polyfills;
polyfills.acosh = transform::BuiltinPolyfill::Level::kFull;
@@ -172,6 +175,7 @@
polyfills.first_leading_bit = true;
polyfills.first_trailing_bit = true;
polyfills.insert_bits = transform::BuiltinPolyfill::Level::kFull;
+ polyfills.int_div_mod = true;
polyfills.texture_sample_base_clamp_to_edge_2d_f32 = true;
data.Add<transform::BuiltinPolyfill::Config>(polyfills);
manager.Add<transform::BuiltinPolyfill>();
@@ -211,7 +215,6 @@
// assumes that num_workgroups builtins only appear as struct members and are
// only accessed directly via member accessors.
manager.Add<transform::NumWorkgroupsFromUniform>();
- manager.Add<transform::ExpandCompoundAssignment>();
manager.Add<transform::PromoteSideEffectsToDecl>();
manager.Add<transform::VectorizeScalarMatrixInitializers>();
manager.Add<transform::SimplifyPointers>();
@@ -661,117 +664,6 @@
return true;
}
-bool GeneratorImpl::EmitExpressionOrOneIfZero(std::ostream& out, const ast::Expression* expr) {
- // For constants, replace literal 0 with 1.
- if (const auto* val = builder_.Sem().Get(expr)->ConstantValue()) {
- if (!val->AnyZero()) {
- return EmitExpression(out, expr);
- }
-
- auto* ty = val->Type();
-
- if (ty->IsAnyOf<sem::I32, sem::U32>()) {
- return EmitValue(out, ty, 1);
- }
-
- if (auto* vec = ty->As<sem::Vector>()) {
- auto* elem_ty = vec->type();
-
- if (!EmitType(out, ty, ast::AddressSpace::kNone, ast::Access::kUndefined, "")) {
- return false;
- }
-
- out << "(";
- for (size_t i = 0; i < vec->Width(); ++i) {
- if (i != 0) {
- out << ", ";
- }
- auto s = val->Index(i)->As<AInt>();
- if (!EmitValue(out, elem_ty, (s == 0) ? 1 : static_cast<int>(s))) {
- return false;
- }
- }
- out << ")";
- return true;
- }
-
- TINT_ICE(Writer, diagnostics_)
- << "EmitExpressionOrOneIfZero expects integer scalar or vector";
- return false;
- }
-
- auto* ty = TypeOf(expr)->UnwrapRef();
-
- // For non-constants, we need to emit runtime code to check if the value is 0,
- // and return 1 in that case.
- std::string zero;
- {
- std::ostringstream ss;
- EmitValue(ss, ty, 0);
- zero = ss.str();
- }
- std::string one;
- {
- std::ostringstream ss;
- EmitValue(ss, ty, 1);
- one = ss.str();
- }
-
- // For identifiers, no need for a function call as it's fine to evaluate
- // `expr` more than once.
- if (expr->Is<ast::IdentifierExpression>()) {
- out << "(";
- if (!EmitExpression(out, expr)) {
- return false;
- }
- out << " == " << zero << " ? " << one << " : ";
- if (!EmitExpression(out, expr)) {
- return false;
- }
- out << ")";
- return true;
- }
-
- // For non-identifier expressions, call a function to make sure `expr` is only
- // evaluated once.
- auto name = utils::GetOrCreate(value_or_one_if_zero_, ty, [&]() -> std::string {
- // Example:
- // int4 tint_value_or_one_if_zero_int4(int4 value) {
- // return value == 0 ? 0 : value;
- // }
- std::string ty_name;
- {
- std::ostringstream ss;
- if (!EmitType(ss, ty, tint::ast::AddressSpace::kUndefined, ast::Access::kUndefined,
- "")) {
- return "";
- }
- ty_name = ss.str();
- }
-
- std::string fn = UniqueIdentifier("value_or_one_if_zero_" + ty_name);
- line(&helpers_) << ty_name << " " << fn << "(" << ty_name << " value) {";
- {
- ScopedIndent si(&helpers_);
- line(&helpers_) << "return value == " << zero << " ? " << one << " : value;";
- }
- line(&helpers_) << "}";
- line(&helpers_);
- return fn;
- });
-
- if (name.empty()) {
- return false;
- }
-
- out << name << "(";
- if (!EmitExpression(out, expr)) {
- return false;
- }
- out << ")";
- return true;
-}
-
bool GeneratorImpl::EmitBinary(std::ostream& out, const ast::BinaryExpression* expr) {
if (expr->op == ast::BinaryOp::kLogicalAnd || expr->op == ast::BinaryOp::kLogicalOr) {
auto name = UniqueIdentifier(kTempNamePrefix);
@@ -892,21 +784,9 @@
break;
case ast::BinaryOp::kDivide:
out << "/";
- // BUG(crbug.com/tint/1083): Integer divide/modulo by zero is a FXC
- // compile error, and undefined behavior in WGSL.
- if (TypeOf(expr->rhs)->UnwrapRef()->is_integer_scalar_or_vector()) {
- out << " ";
- return EmitExpressionOrOneIfZero(out, expr->rhs);
- }
break;
case ast::BinaryOp::kModulo:
out << "%";
- // BUG(crbug.com/tint/1083): Integer divide/modulo by zero is a FXC
- // compile error, and undefined behavior in WGSL.
- if (TypeOf(expr->rhs)->UnwrapRef()->is_integer_scalar_or_vector()) {
- out << " ";
- return EmitExpressionOrOneIfZero(out, expr->rhs);
- }
break;
case ast::BinaryOp::kNone:
diagnostics_.add_error(diag::System::Writer, "missing binary operation type");
diff --git a/src/tint/writer/hlsl/generator_impl.h b/src/tint/writer/hlsl/generator_impl.h
index eca7734..16b824b 100644
--- a/src/tint/writer/hlsl/generator_impl.h
+++ b/src/tint/writer/hlsl/generator_impl.h
@@ -93,12 +93,6 @@
/// @param stmt the statement to emit
/// @returns true if the statement was emitted successfully
bool EmitAssign(const ast::AssignmentStatement* stmt);
- /// Emits code such that if `expr` is zero, it emits one, else `expr`.
- /// Used to avoid divide-by-zeros by substituting constant zeros with ones.
- /// @param out the output of the expression stream
- /// @param expr the expression
- /// @returns true if the expression was emitted, false otherwise
- bool EmitExpressionOrOneIfZero(std::ostream& out, const ast::Expression* expr);
/// Handles generating a binary expression
/// @param out the output of the expression stream
/// @param expr the binary expression
diff --git a/src/tint/writer/hlsl/generator_impl_binary_test.cc b/src/tint/writer/hlsl/generator_impl_binary_test.cc
index 1163078..6e2b3c7 100644
--- a/src/tint/writer/hlsl/generator_impl_binary_test.cc
+++ b/src/tint/writer/hlsl/generator_impl_binary_test.cc
@@ -653,295 +653,5 @@
)");
}
-namespace HlslGeneratorDivMod {
-
-struct Params {
- enum class Type { Div, Mod };
- Type type;
-};
-
-struct HlslGeneratorDivModTest : TestParamHelper<Params> {
- std::string Token() { return GetParam().type == Params::Type::Div ? "/" : "%"; }
-
- template <typename... Args>
- auto Op(Args... args) {
- return GetParam().type == Params::Type::Div ? Div(std::forward<Args>(args)...)
- : Mod(std::forward<Args>(args)...);
- }
-};
-
-INSTANTIATE_TEST_SUITE_P(HlslGeneratorImplTest,
- HlslGeneratorDivModTest,
- testing::Values(Params{Params::Type::Div}, Params{Params::Type::Mod}));
-
-TEST_P(HlslGeneratorDivModTest, DivOrModByLiteralZero_i32) {
- Func("fn", utils::Empty, ty.void_(),
- utils::Vector{
- Decl(Var("a", ty.i32())),
- Decl(Let("r", Op("a", 0_i))),
- });
-
- GeneratorImpl& gen = Build();
-
- ASSERT_TRUE(gen.Generate());
- EXPECT_EQ(gen.result(), R"(void fn() {
- int a = 0;
- const int r = (a )" + Token() +
- R"( 1);
-}
-)");
-}
-
-TEST_P(HlslGeneratorDivModTest, DivOrModByLiteralZero_u32) {
- Func("fn", utils::Empty, ty.void_(),
- utils::Vector{
- Decl(Var("a", ty.u32())),
- Decl(Let("r", Op("a", 0_u))),
- });
-
- GeneratorImpl& gen = Build();
-
- ASSERT_TRUE(gen.Generate());
- EXPECT_EQ(gen.result(), R"(void fn() {
- uint a = 0u;
- const uint r = (a )" + Token() +
- R"( 1u);
-}
-)");
-}
-
-TEST_P(HlslGeneratorDivModTest, DivOrModByLiteralZero_vec_by_vec_i32) {
- Func("fn", utils::Empty, ty.void_(),
- utils::Vector{
- Decl(Var("a", vec4<i32>(100_i, 100_i, 100_i, 100_i))),
- Decl(Let("r", Op("a", vec4<i32>(50_i, 0_i, 25_i, 0_i)))),
- });
-
- GeneratorImpl& gen = Build();
-
- ASSERT_TRUE(gen.Generate());
- EXPECT_EQ(gen.result(), R"(void fn() {
- int4 a = (100).xxxx;
- const int4 r = (a )" + Token() +
- R"( int4(50, 1, 25, 1));
-}
-)");
-}
-
-TEST_P(HlslGeneratorDivModTest, DivOrModByLiteralZero_vec_by_scalar_i32) {
- Func("fn", utils::Empty, ty.void_(),
- utils::Vector{
- Decl(Var("a", vec4<i32>(100_i, 100_i, 100_i, 100_i))),
- Decl(Let("r", Op("a", 0_i))),
- });
-
- GeneratorImpl& gen = Build();
-
- ASSERT_TRUE(gen.Generate());
- EXPECT_EQ(gen.result(), R"(void fn() {
- int4 a = (100).xxxx;
- const int4 r = (a )" + Token() +
- R"( 1);
-}
-)");
-}
-
-TEST_P(HlslGeneratorDivModTest, DivOrModByIdentifier_i32) {
- Func("fn", utils::Vector{Param("b", ty.i32())}, ty.void_(),
- utils::Vector{
- Decl(Var("a", ty.i32())),
- Decl(Let("r", Op("a", "b"))),
- });
-
- GeneratorImpl& gen = Build();
-
- ASSERT_TRUE(gen.Generate());
- EXPECT_EQ(gen.result(), R"(void fn(int b) {
- int a = 0;
- const int r = (a )" + Token() +
- R"( (b == 0 ? 1 : b));
-}
-)");
-}
-
-TEST_P(HlslGeneratorDivModTest, DivOrModByIdentifier_u32) {
- Func("fn", utils::Vector{Param("b", ty.u32())}, ty.void_(),
- utils::Vector{
- Decl(Var("a", ty.u32())),
- Decl(Let("r", Op("a", "b"))),
- });
-
- GeneratorImpl& gen = Build();
-
- ASSERT_TRUE(gen.Generate());
- EXPECT_EQ(gen.result(), R"(void fn(uint b) {
- uint a = 0u;
- const uint r = (a )" + Token() +
- R"( (b == 0u ? 1u : b));
-}
-)");
-}
-
-TEST_P(HlslGeneratorDivModTest, DivOrModByIdentifier_vec_by_vec_i32) {
- Func("fn", utils::Vector{Param("b", ty.vec3<i32>())}, ty.void_(),
- utils::Vector{
- Decl(Var("a", ty.vec3<i32>())),
- Decl(Let("r", Op("a", "b"))),
- });
-
- GeneratorImpl& gen = Build();
-
- ASSERT_TRUE(gen.Generate());
- EXPECT_EQ(gen.result(), R"(void fn(int3 b) {
- int3 a = int3(0, 0, 0);
- const int3 r = (a )" + Token() +
- R"( (b == int3(0, 0, 0) ? int3(1, 1, 1) : b));
-}
-)");
-}
-
-TEST_P(HlslGeneratorDivModTest, DivOrModByIdentifier_vec_by_scalar_i32) {
- Func("fn", utils::Vector{Param("b", ty.i32())}, ty.void_(),
- utils::Vector{
- Decl(Var("a", ty.vec3<i32>())),
- Decl(Let("r", Op("a", "b"))),
- });
-
- GeneratorImpl& gen = Build();
-
- ASSERT_TRUE(gen.Generate());
- EXPECT_EQ(gen.result(), R"(void fn(int b) {
- int3 a = int3(0, 0, 0);
- const int3 r = (a )" + Token() +
- R"( (b == 0 ? 1 : b));
-}
-)");
-}
-
-TEST_P(HlslGeneratorDivModTest, DivOrModByExpression_i32) {
- Func("zero", utils::Empty, ty.i32(),
- utils::Vector{
- Return(Expr(0_i)),
- });
-
- Func("fn", utils::Empty, ty.void_(),
- utils::Vector{
- Decl(Var("a", ty.i32())),
- Decl(Let("r", Op("a", Call("zero")))),
- });
-
- GeneratorImpl& gen = Build();
-
- ASSERT_TRUE(gen.Generate());
- EXPECT_EQ(gen.result(), R"(int value_or_one_if_zero_int(int value) {
- return value == 0 ? 1 : value;
-}
-
-int zero() {
- return 0;
-}
-
-void fn() {
- int a = 0;
- const int r = (a )" + Token() +
- R"( value_or_one_if_zero_int(zero()));
-}
-)");
-}
-
-TEST_P(HlslGeneratorDivModTest, DivOrModByExpression_u32) {
- Func("zero", utils::Empty, ty.u32(),
- utils::Vector{
- Return(Expr(0_u)),
- });
-
- Func("fn", utils::Empty, ty.void_(),
- utils::Vector{
- Decl(Var("a", ty.u32())),
- Decl(Let("r", Op("a", Call("zero")))),
- });
-
- GeneratorImpl& gen = Build();
-
- ASSERT_TRUE(gen.Generate());
- EXPECT_EQ(gen.result(), R"(uint value_or_one_if_zero_uint(uint value) {
- return value == 0u ? 1u : value;
-}
-
-uint zero() {
- return 0u;
-}
-
-void fn() {
- uint a = 0u;
- const uint r = (a )" + Token() +
- R"( value_or_one_if_zero_uint(zero()));
-}
-)");
-}
-
-TEST_P(HlslGeneratorDivModTest, DivOrModByExpression_vec_by_vec_i32) {
- Func("zero", utils::Empty, ty.vec3<i32>(),
- utils::Vector{
- Return(vec3<i32>(0_i, 0_i, 0_i)),
- });
-
- Func("fn", utils::Empty, ty.void_(),
- utils::Vector{
- Decl(Var("a", ty.vec3<i32>())),
- Decl(Let("r", Op("a", Call("zero")))),
- });
-
- GeneratorImpl& gen = Build();
-
- ASSERT_TRUE(gen.Generate());
- EXPECT_EQ(gen.result(), R"(int3 value_or_one_if_zero_int3(int3 value) {
- return value == int3(0, 0, 0) ? int3(1, 1, 1) : value;
-}
-
-int3 zero() {
- return (0).xxx;
-}
-
-void fn() {
- int3 a = int3(0, 0, 0);
- const int3 r = (a )" + Token() +
- R"( value_or_one_if_zero_int3(zero()));
-}
-)");
-}
-
-TEST_P(HlslGeneratorDivModTest, DivOrModByExpression_vec_by_scalar_i32) {
- Func("zero", utils::Empty, ty.i32(),
- utils::Vector{
- Return(0_i),
- });
-
- Func("fn", utils::Empty, ty.void_(),
- utils::Vector{
- Decl(Var("a", ty.vec3<i32>())),
- Decl(Let("r", Op("a", Call("zero")))),
- });
-
- GeneratorImpl& gen = Build();
-
- ASSERT_TRUE(gen.Generate());
- EXPECT_EQ(gen.result(), R"(int value_or_one_if_zero_int(int value) {
- return value == 0 ? 1 : value;
-}
-
-int zero() {
- return 0;
-}
-
-void fn() {
- int3 a = int3(0, 0, 0);
- const int3 r = (a )" + Token() +
- R"( value_or_one_if_zero_int(zero()));
-}
-)");
-}
-} // namespace HlslGeneratorDivMod
-
} // namespace
} // namespace tint::writer::hlsl
diff --git a/src/tint/writer/msl/generator_impl.cc b/src/tint/writer/msl/generator_impl.cc
index 88578df..351a6d4 100644
--- a/src/tint/writer/msl/generator_impl.cc
+++ b/src/tint/writer/msl/generator_impl.cc
@@ -167,6 +167,9 @@
manager.Add<transform::DisableUniformityAnalysis>();
+ // ExpandCompoundAssignment must come before BuiltinPolyfill
+ manager.Add<transform::ExpandCompoundAssignment>();
+
{ // Builtin polyfills
transform::BuiltinPolyfill::Builtins polyfills;
polyfills.acosh = transform::BuiltinPolyfill::Level::kRangeCheck;
@@ -177,6 +180,7 @@
polyfills.first_leading_bit = true;
polyfills.first_trailing_bit = true;
polyfills.insert_bits = transform::BuiltinPolyfill::Level::kClampParameters;
+ polyfills.int_div_mod = true;
polyfills.texture_sample_base_clamp_to_edge_2d_f32 = true;
data.Add<transform::BuiltinPolyfill::Config>(polyfills);
manager.Add<transform::BuiltinPolyfill>();
@@ -224,7 +228,6 @@
manager.Add<transform::ZeroInitWorkgroupMemory>();
}
manager.Add<transform::CanonicalizeEntryPointIO>();
- manager.Add<transform::ExpandCompoundAssignment>();
manager.Add<transform::PromoteSideEffectsToDecl>();
manager.Add<transform::PromoteInitializersToLet>();
diff --git a/src/tint/writer/spirv/generator_impl.cc b/src/tint/writer/spirv/generator_impl.cc
index 30dd186..18c7511 100644
--- a/src/tint/writer/spirv/generator_impl.cc
+++ b/src/tint/writer/spirv/generator_impl.cc
@@ -48,6 +48,9 @@
manager.Add<transform::DisableUniformityAnalysis>();
+ // ExpandCompoundAssignment must come before BuiltinPolyfill
+ manager.Add<transform::ExpandCompoundAssignment>();
+
{ // Builtin polyfills
transform::BuiltinPolyfill::Builtins polyfills;
polyfills.acosh = transform::BuiltinPolyfill::Level::kRangeCheck;
@@ -60,6 +63,7 @@
polyfills.first_leading_bit = true;
polyfills.first_trailing_bit = true;
polyfills.insert_bits = transform::BuiltinPolyfill::Level::kClampParameters;
+ polyfills.int_div_mod = true;
polyfills.saturate = true;
polyfills.texture_sample_base_clamp_to_edge_2d_f32 = true;
polyfills.quantize_to_vec_f16 = true; // crbug.com/tint/1741
@@ -80,7 +84,6 @@
manager.Add<transform::ZeroInitWorkgroupMemory>();
}
manager.Add<transform::RemoveUnreachableStatements>();
- manager.Add<transform::ExpandCompoundAssignment>();
manager.Add<transform::PromoteSideEffectsToDecl>();
manager.Add<transform::SimplifyPointers>(); // Required for arrayLength()
manager.Add<transform::RemovePhonies>();