[ir] Add polyfill for clamping insertBits args
Use it in the SPIR-V writer.
Bug: tint:1718, tint:1906
Change-Id: I3265f53024dae1f157e83eb78773fde80448188c
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/152821
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: Ben Clayton <bclayton@google.com>
diff --git a/src/tint/lang/core/ir/transform/builtin_polyfill.cc b/src/tint/lang/core/ir/transform/builtin_polyfill.cc
index b058d8c..48e5832 100644
--- a/src/tint/lang/core/ir/transform/builtin_polyfill.cc
+++ b/src/tint/lang/core/ir/transform/builtin_polyfill.cc
@@ -80,6 +80,11 @@
worklist.Push(builtin);
}
break;
+ case core::BuiltinFn::kInsertBits:
+ if (config.insert_bits != BuiltinPolyfillLevel::kNone) {
+ worklist.Push(builtin);
+ }
+ break;
case core::BuiltinFn::kSaturate:
if (config.saturate) {
worklist.Push(builtin);
@@ -120,6 +125,9 @@
case core::BuiltinFn::kFirstTrailingBit:
replacement = FirstTrailingBit(builtin);
break;
+ case core::BuiltinFn::kInsertBits:
+ replacement = InsertBits(builtin);
+ break;
case core::BuiltinFn::kSaturate:
replacement = Saturate(builtin);
break;
@@ -446,6 +454,36 @@
return result;
}
+ /// Polyfill an `insertBits()` builtin call.
+ /// @param call the builtin call instruction
+ /// @returns the replacement value
+ ir::Value* InsertBits(ir::CoreBuiltinCall* call) {
+ auto* offset = call->Args()[2];
+ auto* count = call->Args()[3];
+
+ switch (config.insert_bits) {
+ case BuiltinPolyfillLevel::kClampOrRangeCheck: {
+ b.InsertBefore(call, [&] {
+ // Replace:
+ // insertBits(e, offset, count)
+ // With:
+ // let o = min(offset, 32);
+ // let c = min(count, w - o);
+ // insertBits(e, o, c);
+ auto* o = b.Call(ty.u32(), core::BuiltinFn::kMin, offset, 32_u);
+ auto* c = b.Call(ty.u32(), core::BuiltinFn::kMin, count,
+ b.Subtract(ty.u32(), 32_u, o));
+ call->SetOperand(ir::CoreBuiltinCall::kArgsOperandOffset + 2, o->Result());
+ call->SetOperand(ir::CoreBuiltinCall::kArgsOperandOffset + 3, c->Result());
+ });
+ return call->Result();
+ }
+ default:
+ TINT_UNIMPLEMENTED() << "insertBits polyfill level";
+ }
+ return nullptr;
+ }
+
/// Polyfill a `saturate()` builtin call.
/// @param call the builtin call instruction
/// @returns the replacement value
diff --git a/src/tint/lang/core/ir/transform/builtin_polyfill.h b/src/tint/lang/core/ir/transform/builtin_polyfill.h
index 5d440b3..1e6a534 100644
--- a/src/tint/lang/core/ir/transform/builtin_polyfill.h
+++ b/src/tint/lang/core/ir/transform/builtin_polyfill.h
@@ -48,6 +48,8 @@
bool first_leading_bit = false;
/// Should `firstTrailingBit()` be polyfilled?
bool first_trailing_bit = false;
+ /// How should `insertBits()` be polyfilled?
+ BuiltinPolyfillLevel insert_bits = BuiltinPolyfillLevel::kNone;
/// Should `saturate()` be polyfilled?
bool saturate = false;
/// Should `textureSampleBaseClampToEdge()` be polyfilled for texture_2d<f32> textures?
diff --git a/src/tint/lang/core/ir/transform/builtin_polyfill_test.cc b/src/tint/lang/core/ir/transform/builtin_polyfill_test.cc
index e4b9f4b..4e141ab 100644
--- a/src/tint/lang/core/ir/transform/builtin_polyfill_test.cc
+++ b/src/tint/lang/core/ir/transform/builtin_polyfill_test.cc
@@ -1200,6 +1200,148 @@
EXPECT_EQ(expect, str());
}
+TEST_F(IR_BuiltinPolyfillTest, InsertBits_NoPolyfill) {
+ Build(core::BuiltinFn::kInsertBits, ty.u32(), Vector{ty.u32(), ty.u32(), ty.u32(), ty.u32()});
+ auto* src = R"(
+%foo = func(%arg:u32, %arg_1:u32, %arg_2:u32, %arg_3:u32):u32 -> %b1 { # %arg_1: 'arg', %arg_2: 'arg', %arg_3: 'arg'
+ %b1 = block {
+ %result:u32 = insertBits %arg, %arg_1, %arg_2, %arg_3
+ ret %result
+ }
+}
+)";
+ auto* expect = src;
+
+ EXPECT_EQ(src, str());
+
+ BuiltinPolyfillConfig config;
+ config.insert_bits = BuiltinPolyfillLevel::kNone;
+ Run(BuiltinPolyfill, config);
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_BuiltinPolyfillTest, InsertBits_ClampArgs_U32) {
+ Build(core::BuiltinFn::kInsertBits, ty.u32(), Vector{ty.u32(), ty.u32(), ty.u32(), ty.u32()});
+ auto* src = R"(
+%foo = func(%arg:u32, %arg_1:u32, %arg_2:u32, %arg_3:u32):u32 -> %b1 { # %arg_1: 'arg', %arg_2: 'arg', %arg_3: 'arg'
+ %b1 = block {
+ %result:u32 = insertBits %arg, %arg_1, %arg_2, %arg_3
+ ret %result
+ }
+}
+)";
+ auto* expect = R"(
+%foo = func(%arg:u32, %arg_1:u32, %arg_2:u32, %arg_3:u32):u32 -> %b1 { # %arg_1: 'arg', %arg_2: 'arg', %arg_3: 'arg'
+ %b1 = block {
+ %6:u32 = min %arg_2, 32u
+ %7:u32 = sub 32u, %6
+ %8:u32 = min %arg_3, %7
+ %result:u32 = insertBits %arg, %arg_1, %6, %8
+ ret %result
+ }
+}
+)";
+
+ EXPECT_EQ(src, str());
+
+ BuiltinPolyfillConfig config;
+ config.insert_bits = BuiltinPolyfillLevel::kClampOrRangeCheck;
+ Run(BuiltinPolyfill, config);
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_BuiltinPolyfillTest, InsertBits_ClampArgs_I32) {
+ Build(core::BuiltinFn::kInsertBits, ty.i32(), Vector{ty.i32(), ty.i32(), ty.u32(), ty.u32()});
+ auto* src = R"(
+%foo = func(%arg:i32, %arg_1:i32, %arg_2:u32, %arg_3:u32):i32 -> %b1 { # %arg_1: 'arg', %arg_2: 'arg', %arg_3: 'arg'
+ %b1 = block {
+ %result:i32 = insertBits %arg, %arg_1, %arg_2, %arg_3
+ ret %result
+ }
+}
+)";
+ auto* expect = R"(
+%foo = func(%arg:i32, %arg_1:i32, %arg_2:u32, %arg_3:u32):i32 -> %b1 { # %arg_1: 'arg', %arg_2: 'arg', %arg_3: 'arg'
+ %b1 = block {
+ %6:u32 = min %arg_2, 32u
+ %7:u32 = sub 32u, %6
+ %8:u32 = min %arg_3, %7
+ %result:i32 = insertBits %arg, %arg_1, %6, %8
+ ret %result
+ }
+}
+)";
+
+ EXPECT_EQ(src, str());
+
+ BuiltinPolyfillConfig config;
+ config.insert_bits = BuiltinPolyfillLevel::kClampOrRangeCheck;
+ Run(BuiltinPolyfill, config);
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_BuiltinPolyfillTest, InsertBits_ClampArgs_Vec2U32) {
+ Build(core::BuiltinFn::kInsertBits, ty.vec2<u32>(),
+ Vector{ty.vec2<u32>(), ty.vec2<u32>(), ty.u32(), ty.u32()});
+ auto* src = R"(
+%foo = func(%arg:vec2<u32>, %arg_1:vec2<u32>, %arg_2:u32, %arg_3:u32):vec2<u32> -> %b1 { # %arg_1: 'arg', %arg_2: 'arg', %arg_3: 'arg'
+ %b1 = block {
+ %result:vec2<u32> = insertBits %arg, %arg_1, %arg_2, %arg_3
+ ret %result
+ }
+}
+)";
+ auto* expect = R"(
+%foo = func(%arg:vec2<u32>, %arg_1:vec2<u32>, %arg_2:u32, %arg_3:u32):vec2<u32> -> %b1 { # %arg_1: 'arg', %arg_2: 'arg', %arg_3: 'arg'
+ %b1 = block {
+ %6:u32 = min %arg_2, 32u
+ %7:u32 = sub 32u, %6
+ %8:u32 = min %arg_3, %7
+ %result:vec2<u32> = insertBits %arg, %arg_1, %6, %8
+ ret %result
+ }
+}
+)";
+
+ EXPECT_EQ(src, str());
+
+ BuiltinPolyfillConfig config;
+ config.insert_bits = BuiltinPolyfillLevel::kClampOrRangeCheck;
+ Run(BuiltinPolyfill, config);
+ EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_BuiltinPolyfillTest, InsertBits_ClampArgs_Vec4I32) {
+ Build(core::BuiltinFn::kInsertBits, ty.vec4<i32>(),
+ Vector{ty.vec4<i32>(), ty.vec4<i32>(), ty.u32(), ty.u32()});
+ auto* src = R"(
+%foo = func(%arg:vec4<i32>, %arg_1:vec4<i32>, %arg_2:u32, %arg_3:u32):vec4<i32> -> %b1 { # %arg_1: 'arg', %arg_2: 'arg', %arg_3: 'arg'
+ %b1 = block {
+ %result:vec4<i32> = insertBits %arg, %arg_1, %arg_2, %arg_3
+ ret %result
+ }
+}
+)";
+ auto* expect = R"(
+%foo = func(%arg:vec4<i32>, %arg_1:vec4<i32>, %arg_2:u32, %arg_3:u32):vec4<i32> -> %b1 { # %arg_1: 'arg', %arg_2: 'arg', %arg_3: 'arg'
+ %b1 = block {
+ %6:u32 = min %arg_2, 32u
+ %7:u32 = sub 32u, %6
+ %8:u32 = min %arg_3, %7
+ %result:vec4<i32> = insertBits %arg, %arg_1, %6, %8
+ ret %result
+ }
+}
+)";
+
+ EXPECT_EQ(src, str());
+
+ BuiltinPolyfillConfig config;
+ config.insert_bits = BuiltinPolyfillLevel::kClampOrRangeCheck;
+ Run(BuiltinPolyfill, config);
+ EXPECT_EQ(expect, str());
+}
+
TEST_F(IR_BuiltinPolyfillTest, TextureSampleBaseClampToEdge_2d_f32_NoPolyfill) {
auto* texture_ty =
ty.Get<core::type::SampledTexture>(core::type::TextureDimension::k2d, ty.f32());
diff --git a/src/tint/lang/spirv/writer/builtin_test.cc b/src/tint/lang/spirv/writer/builtin_test.cc
index e17d3c6..9daaa1e 100644
--- a/src/tint/lang/spirv/writer/builtin_test.cc
+++ b/src/tint/lang/spirv/writer/builtin_test.cc
@@ -1548,7 +1548,12 @@
});
ASSERT_TRUE(Generate()) << Error() << output_;
- EXPECT_INST("%result = OpBitFieldInsert %int %arg %newbits %offset %count");
+ EXPECT_INST(R"(
+ %10 = OpExtInst %uint %11 UMin %offset %uint_32
+ %13 = OpISub %uint %uint_32 %10
+ %14 = OpExtInst %uint %11 UMin %count %13
+ %result = OpBitFieldInsert %int %arg %newbits %10 %14
+)");
}
TEST_F(SpirvWriterTest, Builtin_InsertBits_Scalar_U32) {
@@ -1566,7 +1571,12 @@
});
ASSERT_TRUE(Generate()) << Error() << output_;
- EXPECT_INST("%result = OpBitFieldInsert %uint %arg %newbits %offset %count");
+ EXPECT_INST(R"(
+ %9 = OpExtInst %uint %10 UMin %offset %uint_32
+ %12 = OpISub %uint %uint_32 %9
+ %13 = OpExtInst %uint %10 UMin %count %12
+ %result = OpBitFieldInsert %uint %arg %newbits %9 %13
+)");
}
TEST_F(SpirvWriterTest, Builtin_InsertBits_Vector_I32) {
@@ -1585,7 +1595,12 @@
});
ASSERT_TRUE(Generate()) << Error() << output_;
- EXPECT_INST("%result = OpBitFieldInsert %v4int %arg %newbits %offset %count");
+ EXPECT_INST(R"(
+ %11 = OpExtInst %uint %12 UMin %offset %uint_32
+ %14 = OpISub %uint %uint_32 %11
+ %15 = OpExtInst %uint %12 UMin %count %14
+ %result = OpBitFieldInsert %v4int %arg %newbits %11 %15
+)");
}
TEST_F(SpirvWriterTest, Builtin_InsertBits_Vector_U32) {
@@ -1604,7 +1619,12 @@
});
ASSERT_TRUE(Generate()) << Error() << output_;
- EXPECT_INST("%result = OpBitFieldInsert %v2uint %arg %newbits %offset %count");
+ EXPECT_INST(R"(
+ %10 = OpExtInst %uint %11 UMin %offset %uint_32
+ %13 = OpISub %uint %uint_32 %10
+ %14 = OpExtInst %uint %11 UMin %count %13
+ %result = OpBitFieldInsert %v2uint %arg %newbits %10 %14
+)");
}
TEST_F(SpirvWriterTest, Builtin_FaceForward_F32) {
diff --git a/src/tint/lang/spirv/writer/raise/raise.cc b/src/tint/lang/spirv/writer/raise/raise.cc
index e417125..0c287ba 100644
--- a/src/tint/lang/spirv/writer/raise/raise.cc
+++ b/src/tint/lang/spirv/writer/raise/raise.cc
@@ -55,6 +55,7 @@
core_polyfills.extract_bits = core::ir::transform::BuiltinPolyfillLevel::kClampOrRangeCheck;
core_polyfills.first_leading_bit = true;
core_polyfills.first_trailing_bit = true;
+ core_polyfills.insert_bits = core::ir::transform::BuiltinPolyfillLevel::kClampOrRangeCheck;
core_polyfills.saturate = true;
core_polyfills.texture_sample_base_clamp_to_edge_2d_f32 = true;
RUN_TRANSFORM(core::ir::transform::BuiltinPolyfill, module, core_polyfills);