[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);