[spirv-reader][ir] Convert OpBitwiseAnd

Add conversion for OpBitwiseAnd, converting the arguments such that they
match the type of the first parameter.

Bug: 391487132
Change-Id: I036d29907103282d939396d52971f42b7e3f17ac
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/226854
Commit-Queue: dan sinclair <dsinclair@chromium.org>
Reviewed-by: James Price <jrprice@google.com>
diff --git a/src/tint/lang/spirv/builtin_fn.cc b/src/tint/lang/spirv/builtin_fn.cc
index 60cb6b9..ebb1b3b 100644
--- a/src/tint/lang/spirv/builtin_fn.cc
+++ b/src/tint/lang/spirv/builtin_fn.cc
@@ -170,6 +170,8 @@
             return "convertSToF";
         case BuiltinFn::kConvertUToF:
             return "convertUToF";
+        case BuiltinFn::kBitwiseAnd:
+            return "bitwiseAnd";
         case BuiltinFn::kSdot:
             return "sdot";
         case BuiltinFn::kUdot:
@@ -263,6 +265,7 @@
         case BuiltinFn::kConvertFToS:
         case BuiltinFn::kConvertSToF:
         case BuiltinFn::kConvertUToF:
+        case BuiltinFn::kBitwiseAnd:
             break;
     }
     return core::ir::Instruction::Accesses{};
diff --git a/src/tint/lang/spirv/builtin_fn.cc.tmpl b/src/tint/lang/spirv/builtin_fn.cc.tmpl
index 8203aa9..87210f2 100644
--- a/src/tint/lang/spirv/builtin_fn.cc.tmpl
+++ b/src/tint/lang/spirv/builtin_fn.cc.tmpl
@@ -106,6 +106,7 @@
         case BuiltinFn::kConvertFToS:
         case BuiltinFn::kConvertSToF:
         case BuiltinFn::kConvertUToF:
+        case BuiltinFn::kBitwiseAnd:
             break;
     }
     return core::ir::Instruction::Accesses{};
diff --git a/src/tint/lang/spirv/builtin_fn.h b/src/tint/lang/spirv/builtin_fn.h
index 687eaa6..43e52cc 100644
--- a/src/tint/lang/spirv/builtin_fn.h
+++ b/src/tint/lang/spirv/builtin_fn.h
@@ -112,6 +112,7 @@
     kConvertFToS,
     kConvertSToF,
     kConvertUToF,
+    kBitwiseAnd,
     kSdot,
     kUdot,
     kCooperativeMatrixLoad,
diff --git a/src/tint/lang/spirv/intrinsic/data.cc b/src/tint/lang/spirv/intrinsic/data.cc
index 15f7fee..ac8af96 100644
--- a/src/tint/lang/spirv/intrinsic/data.cc
+++ b/src/tint/lang/spirv/intrinsic/data.cc
@@ -6718,30 +6718,37 @@
   },
   {
     /* [64] */
+    /* fn bitwiseAnd<R : iu32>[A : iu32, B : iu32](A, B) -> R */
+    /* fn bitwiseAnd<R : iu32>[A : iu32, B : iu32, N : num](vec<N, A>, vec<N, B>) -> vec<N, R> */
+    /* num overloads */ 2,
+    /* overloads */ OverloadIndex(176),
+  },
+  {
+    /* [65] */
     /* fn sdot(u32, u32, u32) -> i32 */
     /* num overloads */ 1,
     /* overloads */ OverloadIndex(193),
   },
   {
-    /* [65] */
+    /* [66] */
     /* fn udot(u32, u32, u32) -> u32 */
     /* num overloads */ 1,
     /* overloads */ OverloadIndex(194),
   },
   {
-    /* [66] */
+    /* [67] */
     /* fn cooperative_matrix_load<T : subgroup_matrix<K, S, C, R>>[K : subgroup_matrix_kind, S : fiu32_f16, C : num, R : num](ptr<workgroup_or_storage, S, readable>, u32, u32, u32) -> T */
     /* num overloads */ 1,
     /* overloads */ OverloadIndex(195),
   },
   {
-    /* [67] */
+    /* [68] */
     /* fn cooperative_matrix_store[K : subgroup_matrix_kind, S : fiu32_f16, C : num, R : num](ptr<workgroup_or_storage, S, writable>, subgroup_matrix<K, S, C, R>, u32, u32, u32) */
     /* num overloads */ 1,
     /* overloads */ OverloadIndex(196),
   },
   {
-    /* [68] */
+    /* [69] */
     /* fn cooperative_matrix_mul_add[T : subgroup_matrix_elements, TR : subgroup_matrix_elements, C : num, R : num, K : num](subgroup_matrix<subgroup_matrix_kind_left, T, K, R>, subgroup_matrix<subgroup_matrix_kind_right, T, C, K>, subgroup_matrix<subgroup_matrix_kind_result, TR, C, R>, u32) -> subgroup_matrix<subgroup_matrix_kind_result, TR, C, R> */
     /* num overloads */ 1,
     /* overloads */ OverloadIndex(197),
diff --git a/src/tint/lang/spirv/reader/lower/builtins.cc b/src/tint/lang/spirv/reader/lower/builtins.cc
index fc2b6cc..e7c7b88 100644
--- a/src/tint/lang/spirv/reader/lower/builtins.cc
+++ b/src/tint/lang/spirv/reader/lower/builtins.cc
@@ -155,6 +155,9 @@
                 case spirv::BuiltinFn::kConvertUToF:
                     ConvertUToF(builtin);
                     break;
+                case spirv::BuiltinFn::kBitwiseAnd:
+                    BitwiseAnd(builtin);
+                    break;
                 default:
                     TINT_UNREACHABLE() << "unknown spirv builtin: " << builtin->Func();
             }
@@ -225,6 +228,10 @@
         call->Destroy();
     }
 
+    void BitwiseAnd(spirv::ir::BuiltinCall* call) {
+        EmitBinaryWrappedAsFirstArg(call, core::BinaryOp::kAnd);
+    }
+
     void Add(spirv::ir::BuiltinCall* call) {
         EmitBinaryWrappedAsFirstArg(call, core::BinaryOp::kAdd);
     }
diff --git a/src/tint/lang/spirv/reader/lower/builtins_test.cc b/src/tint/lang/spirv/reader/lower/builtins_test.cc
index 71ba45e..34f4beb 100644
--- a/src/tint/lang/spirv/reader/lower/builtins_test.cc
+++ b/src/tint/lang/spirv/reader/lower/builtins_test.cc
@@ -5467,5 +5467,525 @@
     EXPECT_EQ(expect, str());
 }
 
+TEST_F(SpirvParser_BuiltinsTest, BitwiseAnd_Scalar_SignedSigned_Signed) {
+    auto* ep = b.ComputeFunction("foo");
+
+    b.Append(ep->Block(), [&] {  //
+        b.CallExplicit<spirv::ir::BuiltinCall>(ty.i32(), spirv::BuiltinFn::kBitwiseAnd,
+                                               Vector{ty.i32()}, 1_i, 2_i);
+        b.Return(ep);
+    });
+
+    auto* src = R"(
+%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
+  $B1: {
+    %2:i32 = spirv.bitwiseAnd<i32> 1i, 2i
+    ret
+  }
+}
+)";
+    EXPECT_EQ(src, str());
+    Run(Builtins);
+
+    auto* expect = R"(
+%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
+  $B1: {
+    %2:i32 = and 1i, 2i
+    ret
+  }
+}
+)";
+    EXPECT_EQ(expect, str());
+}
+
+TEST_F(SpirvParser_BuiltinsTest, BitwiseAnd_Scalar_SignedUnsigned_Signed) {
+    auto* ep = b.ComputeFunction("foo");
+
+    b.Append(ep->Block(), [&] {  //
+        b.CallExplicit<spirv::ir::BuiltinCall>(ty.i32(), spirv::BuiltinFn::kBitwiseAnd,
+                                               Vector{ty.i32()}, 1_i, 8_u);
+        b.Return(ep);
+    });
+
+    auto* src = R"(
+%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
+  $B1: {
+    %2:i32 = spirv.bitwiseAnd<i32> 1i, 8u
+    ret
+  }
+}
+)";
+    EXPECT_EQ(src, str());
+    Run(Builtins);
+
+    auto* expect = R"(
+%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
+  $B1: {
+    %2:i32 = bitcast 8u
+    %3:i32 = and 1i, %2
+    ret
+  }
+}
+)";
+    EXPECT_EQ(expect, str());
+}
+
+TEST_F(SpirvParser_BuiltinsTest, BitwiseAnd_Scalar_UnsignedSigned_Signed) {
+    auto* ep = b.ComputeFunction("foo");
+
+    b.Append(ep->Block(), [&] {  //
+        b.CallExplicit<spirv::ir::BuiltinCall>(ty.i32(), spirv::BuiltinFn::kBitwiseAnd,
+                                               Vector{ty.i32()}, 8_u, 1_i);
+        b.Return(ep);
+    });
+
+    auto* src = R"(
+%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
+  $B1: {
+    %2:i32 = spirv.bitwiseAnd<i32> 8u, 1i
+    ret
+  }
+}
+)";
+    EXPECT_EQ(src, str());
+    Run(Builtins);
+
+    auto* expect = R"(
+%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
+  $B1: {
+    %2:u32 = bitcast 1i
+    %3:u32 = and 8u, %2
+    %4:i32 = bitcast %3
+    ret
+  }
+}
+)";
+    EXPECT_EQ(expect, str());
+}
+
+TEST_F(SpirvParser_BuiltinsTest, BitwiseAnd_Scalar_UnsignedUnsigned_Signed) {
+    auto* ep = b.ComputeFunction("foo");
+
+    b.Append(ep->Block(), [&] {  //
+        b.CallExplicit<spirv::ir::BuiltinCall>(ty.i32(), spirv::BuiltinFn::kBitwiseAnd,
+                                               Vector{ty.i32()}, 8_u, 9_u);
+        b.Return(ep);
+    });
+
+    auto* src = R"(
+%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
+  $B1: {
+    %2:i32 = spirv.bitwiseAnd<i32> 8u, 9u
+    ret
+  }
+}
+)";
+    EXPECT_EQ(src, str());
+    Run(Builtins);
+
+    auto* expect = R"(
+%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
+  $B1: {
+    %2:u32 = and 8u, 9u
+    %3:i32 = bitcast %2
+    ret
+  }
+}
+)";
+    EXPECT_EQ(expect, str());
+}
+
+TEST_F(SpirvParser_BuiltinsTest, BitwiseAnd_Scalar_UnsignedUnsigned_Unsigned) {
+    auto* ep = b.ComputeFunction("foo");
+
+    b.Append(ep->Block(), [&] {  //
+        b.CallExplicit<spirv::ir::BuiltinCall>(ty.u32(), spirv::BuiltinFn::kBitwiseAnd,
+                                               Vector{ty.u32()}, 8_u, 9_u);
+        b.Return(ep);
+    });
+
+    auto* src = R"(
+%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
+  $B1: {
+    %2:u32 = spirv.bitwiseAnd<u32> 8u, 9u
+    ret
+  }
+}
+)";
+    EXPECT_EQ(src, str());
+    Run(Builtins);
+
+    auto* expect = R"(
+%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
+  $B1: {
+    %2:u32 = and 8u, 9u
+    ret
+  }
+}
+)";
+    EXPECT_EQ(expect, str());
+}
+
+TEST_F(SpirvParser_BuiltinsTest, BitwiseAnd_Scalar_UnsignedSigned_Unsigned) {
+    auto* ep = b.ComputeFunction("foo");
+
+    b.Append(ep->Block(), [&] {  //
+        b.CallExplicit<spirv::ir::BuiltinCall>(ty.u32(), spirv::BuiltinFn::kBitwiseAnd,
+                                               Vector{ty.u32()}, 8_u, 1_i);
+        b.Return(ep);
+    });
+
+    auto* src = R"(
+%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
+  $B1: {
+    %2:u32 = spirv.bitwiseAnd<u32> 8u, 1i
+    ret
+  }
+}
+)";
+    EXPECT_EQ(src, str());
+    Run(Builtins);
+
+    auto* expect = R"(
+%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
+  $B1: {
+    %2:u32 = bitcast 1i
+    %3:u32 = and 8u, %2
+    ret
+  }
+}
+)";
+    EXPECT_EQ(expect, str());
+}
+
+TEST_F(SpirvParser_BuiltinsTest, BitwiseAnd_Scalar_SignedUnsigned_Unsigned) {
+    auto* ep = b.ComputeFunction("foo");
+
+    b.Append(ep->Block(), [&] {  //
+        b.CallExplicit<spirv::ir::BuiltinCall>(ty.u32(), spirv::BuiltinFn::kBitwiseAnd,
+                                               Vector{ty.u32()}, 1_i, 8_u);
+        b.Return(ep);
+    });
+
+    auto* src = R"(
+%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
+  $B1: {
+    %2:u32 = spirv.bitwiseAnd<u32> 1i, 8u
+    ret
+  }
+}
+)";
+    EXPECT_EQ(src, str());
+    Run(Builtins);
+
+    auto* expect = R"(
+%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
+  $B1: {
+    %2:i32 = bitcast 8u
+    %3:i32 = and 1i, %2
+    %4:u32 = bitcast %3
+    ret
+  }
+}
+)";
+    EXPECT_EQ(expect, str());
+}
+
+TEST_F(SpirvParser_BuiltinsTest, BitwiseAnd_Scalar_SignedSigned_Unsigned) {
+    auto* ep = b.ComputeFunction("foo");
+
+    b.Append(ep->Block(), [&] {  //
+        b.CallExplicit<spirv::ir::BuiltinCall>(ty.u32(), spirv::BuiltinFn::kBitwiseAnd,
+                                               Vector{ty.u32()}, 1_i, 2_i);
+        b.Return(ep);
+    });
+
+    auto* src = R"(
+%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
+  $B1: {
+    %2:u32 = spirv.bitwiseAnd<u32> 1i, 2i
+    ret
+  }
+}
+)";
+    EXPECT_EQ(src, str());
+    Run(Builtins);
+
+    auto* expect = R"(
+%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
+  $B1: {
+    %2:i32 = and 1i, 2i
+    %3:u32 = bitcast %2
+    ret
+  }
+}
+)";
+    EXPECT_EQ(expect, str());
+}
+
+TEST_F(SpirvParser_BuiltinsTest, BitwiseAnd_Vector_SignedSigned_Signed) {
+    auto* ep = b.ComputeFunction("foo");
+
+    b.Append(ep->Block(), [&] {  //
+        b.CallExplicit<spirv::ir::BuiltinCall>(ty.vec2<i32>(), spirv::BuiltinFn::kBitwiseAnd,
+                                               Vector{ty.i32()}, b.Splat<vec2<i32>>(1_i),
+                                               b.Splat<vec2<i32>>(2_i));
+        b.Return(ep);
+    });
+
+    auto* src = R"(
+%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
+  $B1: {
+    %2:vec2<i32> = spirv.bitwiseAnd<i32> vec2<i32>(1i), vec2<i32>(2i)
+    ret
+  }
+}
+)";
+    EXPECT_EQ(src, str());
+    Run(Builtins);
+
+    auto* expect = R"(
+%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
+  $B1: {
+    %2:vec2<i32> = and vec2<i32>(1i), vec2<i32>(2i)
+    ret
+  }
+}
+)";
+    EXPECT_EQ(expect, str());
+}
+
+TEST_F(SpirvParser_BuiltinsTest, BitwiseAnd_Vector_SignedUnsigned_Signed) {
+    auto* ep = b.ComputeFunction("foo");
+
+    b.Append(ep->Block(), [&] {  //
+        b.CallExplicit<spirv::ir::BuiltinCall>(ty.vec2<i32>(), spirv::BuiltinFn::kBitwiseAnd,
+                                               Vector{ty.i32()}, b.Splat<vec2<i32>>(1_i),
+                                               b.Splat<vec2<u32>>(8_u));
+        b.Return(ep);
+    });
+
+    auto* src = R"(
+%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
+  $B1: {
+    %2:vec2<i32> = spirv.bitwiseAnd<i32> vec2<i32>(1i), vec2<u32>(8u)
+    ret
+  }
+}
+)";
+    EXPECT_EQ(src, str());
+    Run(Builtins);
+
+    auto* expect = R"(
+%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
+  $B1: {
+    %2:vec2<i32> = bitcast vec2<u32>(8u)
+    %3:vec2<i32> = and vec2<i32>(1i), %2
+    ret
+  }
+}
+)";
+    EXPECT_EQ(expect, str());
+}
+
+TEST_F(SpirvParser_BuiltinsTest, BitwiseAnd_Vector_UnsignedSigned_Signed) {
+    auto* ep = b.ComputeFunction("foo");
+
+    b.Append(ep->Block(), [&] {  //
+        b.CallExplicit<spirv::ir::BuiltinCall>(ty.vec2<i32>(), spirv::BuiltinFn::kBitwiseAnd,
+                                               Vector{ty.i32()}, b.Splat<vec2<u32>>(8_u),
+                                               b.Splat<vec2<i32>>(1_i));
+        b.Return(ep);
+    });
+
+    auto* src = R"(
+%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
+  $B1: {
+    %2:vec2<i32> = spirv.bitwiseAnd<i32> vec2<u32>(8u), vec2<i32>(1i)
+    ret
+  }
+}
+)";
+    EXPECT_EQ(src, str());
+    Run(Builtins);
+
+    auto* expect = R"(
+%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
+  $B1: {
+    %2:vec2<u32> = bitcast vec2<i32>(1i)
+    %3:vec2<u32> = and vec2<u32>(8u), %2
+    %4:vec2<i32> = bitcast %3
+    ret
+  }
+}
+)";
+    EXPECT_EQ(expect, str());
+}
+
+TEST_F(SpirvParser_BuiltinsTest, BitwiseAnd_Vector_UnsignedUnsigned_Signed) {
+    auto* ep = b.ComputeFunction("foo");
+
+    b.Append(ep->Block(), [&] {  //
+        b.CallExplicit<spirv::ir::BuiltinCall>(ty.vec2<i32>(), spirv::BuiltinFn::kBitwiseAnd,
+                                               Vector{ty.i32()}, b.Splat<vec2<u32>>(8_u),
+                                               b.Splat<vec2<u32>>(9_u));
+        b.Return(ep);
+    });
+
+    auto* src = R"(
+%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
+  $B1: {
+    %2:vec2<i32> = spirv.bitwiseAnd<i32> vec2<u32>(8u), vec2<u32>(9u)
+    ret
+  }
+}
+)";
+    EXPECT_EQ(src, str());
+    Run(Builtins);
+
+    auto* expect = R"(
+%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
+  $B1: {
+    %2:vec2<u32> = and vec2<u32>(8u), vec2<u32>(9u)
+    %3:vec2<i32> = bitcast %2
+    ret
+  }
+}
+)";
+    EXPECT_EQ(expect, str());
+}
+
+TEST_F(SpirvParser_BuiltinsTest, BitwiseAnd_Vector_UnsignedUnsigned_Unsigned) {
+    auto* ep = b.ComputeFunction("foo");
+
+    b.Append(ep->Block(), [&] {  //
+        b.CallExplicit<spirv::ir::BuiltinCall>(ty.vec2<u32>(), spirv::BuiltinFn::kBitwiseAnd,
+                                               Vector{ty.u32()}, b.Splat<vec2<u32>>(8_u),
+                                               b.Splat<vec2<u32>>(9_u));
+        b.Return(ep);
+    });
+
+    auto* src = R"(
+%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
+  $B1: {
+    %2:vec2<u32> = spirv.bitwiseAnd<u32> vec2<u32>(8u), vec2<u32>(9u)
+    ret
+  }
+}
+)";
+    EXPECT_EQ(src, str());
+    Run(Builtins);
+
+    auto* expect = R"(
+%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
+  $B1: {
+    %2:vec2<u32> = and vec2<u32>(8u), vec2<u32>(9u)
+    ret
+  }
+}
+)";
+    EXPECT_EQ(expect, str());
+}
+
+TEST_F(SpirvParser_BuiltinsTest, BitwiseAnd_Vector_UnsignedSigned_Unsigned) {
+    auto* ep = b.ComputeFunction("foo");
+
+    b.Append(ep->Block(), [&] {  //
+        b.CallExplicit<spirv::ir::BuiltinCall>(ty.vec2<u32>(), spirv::BuiltinFn::kBitwiseAnd,
+                                               Vector{ty.u32()}, b.Splat<vec2<u32>>(8_u),
+                                               b.Splat<vec2<i32>>(1_i));
+        b.Return(ep);
+    });
+
+    auto* src = R"(
+%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
+  $B1: {
+    %2:vec2<u32> = spirv.bitwiseAnd<u32> vec2<u32>(8u), vec2<i32>(1i)
+    ret
+  }
+}
+)";
+    EXPECT_EQ(src, str());
+    Run(Builtins);
+
+    auto* expect = R"(
+%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
+  $B1: {
+    %2:vec2<u32> = bitcast vec2<i32>(1i)
+    %3:vec2<u32> = and vec2<u32>(8u), %2
+    ret
+  }
+}
+)";
+    EXPECT_EQ(expect, str());
+}
+
+TEST_F(SpirvParser_BuiltinsTest, BitwiseAnd_Vector_SignedUnsigned_Unsigned) {
+    auto* ep = b.ComputeFunction("foo");
+
+    b.Append(ep->Block(), [&] {  //
+        b.CallExplicit<spirv::ir::BuiltinCall>(ty.vec2<u32>(), spirv::BuiltinFn::kBitwiseAnd,
+                                               Vector{ty.u32()}, b.Splat<vec2<i32>>(1_i),
+                                               b.Splat<vec2<u32>>(8_u));
+        b.Return(ep);
+    });
+
+    auto* src = R"(
+%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
+  $B1: {
+    %2:vec2<u32> = spirv.bitwiseAnd<u32> vec2<i32>(1i), vec2<u32>(8u)
+    ret
+  }
+}
+)";
+    EXPECT_EQ(src, str());
+    Run(Builtins);
+
+    auto* expect = R"(
+%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
+  $B1: {
+    %2:vec2<i32> = bitcast vec2<u32>(8u)
+    %3:vec2<i32> = and vec2<i32>(1i), %2
+    %4:vec2<u32> = bitcast %3
+    ret
+  }
+}
+)";
+    EXPECT_EQ(expect, str());
+}
+
+TEST_F(SpirvParser_BuiltinsTest, BitwiseAnd_Vector_SignedSigned_Unsigned) {
+    auto* ep = b.ComputeFunction("foo");
+
+    b.Append(ep->Block(), [&] {  //
+        b.CallExplicit<spirv::ir::BuiltinCall>(ty.vec2<u32>(), spirv::BuiltinFn::kBitwiseAnd,
+                                               Vector{ty.u32()}, b.Splat<vec2<i32>>(1_i),
+                                               b.Splat<vec2<i32>>(2_i));
+        b.Return(ep);
+    });
+
+    auto* src = R"(
+%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
+  $B1: {
+    %2:vec2<u32> = spirv.bitwiseAnd<u32> vec2<i32>(1i), vec2<i32>(2i)
+    ret
+  }
+}
+)";
+    EXPECT_EQ(src, str());
+    Run(Builtins);
+
+    auto* expect = R"(
+%foo = @compute @workgroup_size(1u, 1u, 1u) func():void {
+  $B1: {
+    %2:vec2<i32> = and vec2<i32>(1i), vec2<i32>(2i)
+    %3:vec2<u32> = bitcast %2
+    ret
+  }
+}
+)";
+    EXPECT_EQ(expect, str());
+}
+
 }  // namespace
 }  // namespace tint::spirv::reader::lower
diff --git a/src/tint/lang/spirv/reader/parser/BUILD.bazel b/src/tint/lang/spirv/reader/parser/BUILD.bazel
index fffb95a..c426687 100644
--- a/src/tint/lang/spirv/reader/parser/BUILD.bazel
+++ b/src/tint/lang/spirv/reader/parser/BUILD.bazel
@@ -82,6 +82,7 @@
   alwayslink = True,
   srcs = [
     "binary_test.cc",
+    "bit_test.cc",
     "branch_test.cc",
     "builtin_test.cc",
     "composite_test.cc",
diff --git a/src/tint/lang/spirv/reader/parser/BUILD.cmake b/src/tint/lang/spirv/reader/parser/BUILD.cmake
index 51c33bd..330e5c1 100644
--- a/src/tint/lang/spirv/reader/parser/BUILD.cmake
+++ b/src/tint/lang/spirv/reader/parser/BUILD.cmake
@@ -91,6 +91,7 @@
 ################################################################################
 tint_add_target(tint_lang_spirv_reader_parser_test test
   lang/spirv/reader/parser/binary_test.cc
+  lang/spirv/reader/parser/bit_test.cc
   lang/spirv/reader/parser/branch_test.cc
   lang/spirv/reader/parser/builtin_test.cc
   lang/spirv/reader/parser/composite_test.cc
diff --git a/src/tint/lang/spirv/reader/parser/BUILD.gn b/src/tint/lang/spirv/reader/parser/BUILD.gn
index 382792d..13fa322 100644
--- a/src/tint/lang/spirv/reader/parser/BUILD.gn
+++ b/src/tint/lang/spirv/reader/parser/BUILD.gn
@@ -90,6 +90,7 @@
     tint_unittests_source_set("unittests") {
       sources = [
         "binary_test.cc",
+        "bit_test.cc",
         "branch_test.cc",
         "builtin_test.cc",
         "composite_test.cc",
diff --git a/src/tint/lang/spirv/reader/parser/bit_test.cc b/src/tint/lang/spirv/reader/parser/bit_test.cc
new file mode 100644
index 0000000..2e6de9b
--- /dev/null
+++ b/src/tint/lang/spirv/reader/parser/bit_test.cc
@@ -0,0 +1,575 @@
+// Copyright 2025 The Dawn & Tint Authors
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+//    list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+//    contributors may be used to endorse or promote products derived from
+//    this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "src/tint/lang/spirv/reader/parser/helper_test.h"
+
+namespace tint::spirv::reader {
+namespace {
+
+TEST_F(SpirvParserTest, BitwiseAnd_Scalar_SignedSigned_Signed) {
+    EXPECT_IR(R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main"
+               OpExecutionMode %main LocalSize 1 1 1
+       %void = OpTypeVoid
+        %int = OpTypeInt 32 1
+        %one = OpConstant %int 1
+        %two = OpConstant %int 2
+    %void_fn = OpTypeFunction %void
+
+       %main = OpFunction %void None %void_fn
+ %main_start = OpLabel
+               %1 = OpBitwiseAnd %int %one %two
+               OpReturn
+               OpFunctionEnd
+)",
+              R"(
+%main = @compute @workgroup_size(1u, 1u, 1u) func():void {
+  $B1: {
+    %2:i32 = spirv.bitwiseAnd<i32> 1i, 2i
+    ret
+  }
+}
+)");
+}
+
+TEST_F(SpirvParserTest, BitwiseAnd_Scalar_SignedUnsigned_Signed) {
+    EXPECT_IR(R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main"
+               OpExecutionMode %main LocalSize 1 1 1
+       %void = OpTypeVoid
+       %uint = OpTypeInt 32 0
+        %int = OpTypeInt 32 1
+        %one = OpConstant %int 1
+        %two = OpConstant %int 2
+      %eight = OpConstant %uint 8
+       %nine = OpConstant %uint 9
+    %void_fn = OpTypeFunction %void
+
+       %main = OpFunction %void None %void_fn
+ %main_start = OpLabel
+               %1 = OpBitwiseAnd %int %one %eight
+               OpReturn
+               OpFunctionEnd
+)",
+              R"(
+%main = @compute @workgroup_size(1u, 1u, 1u) func():void {
+  $B1: {
+    %2:i32 = spirv.bitwiseAnd<i32> 1i, 8u
+    ret
+  }
+}
+)");
+}
+
+TEST_F(SpirvParserTest, BitwiseAnd_Scalar_UnsignedSigned_Signed) {
+    EXPECT_IR(R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main"
+               OpExecutionMode %main LocalSize 1 1 1
+       %void = OpTypeVoid
+       %uint = OpTypeInt 32 0
+        %int = OpTypeInt 32 1
+        %one = OpConstant %int 1
+        %two = OpConstant %int 2
+      %eight = OpConstant %uint 8
+       %nine = OpConstant %uint 9
+    %void_fn = OpTypeFunction %void
+
+       %main = OpFunction %void None %void_fn
+ %main_start = OpLabel
+               %1 = OpBitwiseAnd %int %eight %one
+               OpReturn
+               OpFunctionEnd
+)",
+              R"(
+%main = @compute @workgroup_size(1u, 1u, 1u) func():void {
+  $B1: {
+    %2:i32 = spirv.bitwiseAnd<i32> 8u, 1i
+    ret
+  }
+}
+)");
+}
+
+TEST_F(SpirvParserTest, BitwiseAnd_Scalar_UnsignedUnsigned_Signed) {
+    EXPECT_IR(R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main"
+               OpExecutionMode %main LocalSize 1 1 1
+       %void = OpTypeVoid
+       %uint = OpTypeInt 32 0
+        %int = OpTypeInt 32 1
+        %one = OpConstant %int 1
+        %two = OpConstant %int 2
+      %eight = OpConstant %uint 8
+       %nine = OpConstant %uint 9
+    %void_fn = OpTypeFunction %void
+
+       %main = OpFunction %void None %void_fn
+ %main_start = OpLabel
+               %1 = OpBitwiseAnd %int %eight %nine
+               OpReturn
+               OpFunctionEnd
+)",
+              R"(
+%main = @compute @workgroup_size(1u, 1u, 1u) func():void {
+  $B1: {
+    %2:i32 = spirv.bitwiseAnd<i32> 8u, 9u
+    ret
+  }
+}
+)");
+}
+
+TEST_F(SpirvParserTest, BitwiseAnd_Scalar_UnsignedUnsigned_Unsigned) {
+    EXPECT_IR(R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main"
+               OpExecutionMode %main LocalSize 1 1 1
+       %void = OpTypeVoid
+        %int = OpTypeInt 32 1
+       %uint = OpTypeInt 32 0
+        %one = OpConstant %int 1
+        %two = OpConstant %int 2
+      %eight = OpConstant %uint 8
+       %nine = OpConstant %uint 9
+    %void_fn = OpTypeFunction %void
+
+       %main = OpFunction %void None %void_fn
+ %main_start = OpLabel
+               %1 = OpBitwiseAnd %uint %eight %nine
+               OpReturn
+               OpFunctionEnd
+)",
+              R"(
+%main = @compute @workgroup_size(1u, 1u, 1u) func():void {
+  $B1: {
+    %2:u32 = spirv.bitwiseAnd<u32> 8u, 9u
+    ret
+  }
+}
+)");
+}
+
+TEST_F(SpirvParserTest, BitwiseAnd_Scalar_UnsignedSigned_Unsigned) {
+    EXPECT_IR(R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main"
+               OpExecutionMode %main LocalSize 1 1 1
+       %void = OpTypeVoid
+       %uint = OpTypeInt 32 0
+        %int = OpTypeInt 32 1
+        %one = OpConstant %int 1
+        %two = OpConstant %int 2
+      %eight = OpConstant %uint 8
+       %nine = OpConstant %uint 9
+    %void_fn = OpTypeFunction %void
+
+       %main = OpFunction %void None %void_fn
+ %main_start = OpLabel
+               %1 = OpBitwiseAnd %uint %eight %one
+               OpReturn
+               OpFunctionEnd
+)",
+              R"(
+%main = @compute @workgroup_size(1u, 1u, 1u) func():void {
+  $B1: {
+    %2:u32 = spirv.bitwiseAnd<u32> 8u, 1i
+    ret
+  }
+}
+)");
+}
+
+TEST_F(SpirvParserTest, BitwiseAnd_Scalar_SignedUnsigned_Unsigned) {
+    EXPECT_IR(R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main"
+               OpExecutionMode %main LocalSize 1 1 1
+       %void = OpTypeVoid
+       %uint = OpTypeInt 32 0
+        %int = OpTypeInt 32 1
+        %one = OpConstant %int 1
+        %two = OpConstant %int 2
+      %eight = OpConstant %uint 8
+       %nine = OpConstant %uint 9
+    %void_fn = OpTypeFunction %void
+
+       %main = OpFunction %void None %void_fn
+ %main_start = OpLabel
+               %1 = OpBitwiseAnd %uint %one %eight
+               OpReturn
+               OpFunctionEnd
+)",
+              R"(
+%main = @compute @workgroup_size(1u, 1u, 1u) func():void {
+  $B1: {
+    %2:u32 = spirv.bitwiseAnd<u32> 1i, 8u
+    ret
+  }
+}
+)");
+}
+
+TEST_F(SpirvParserTest, BitwiseAnd_Scalar_SignedSigned_Unsigned) {
+    EXPECT_IR(R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main"
+               OpExecutionMode %main LocalSize 1 1 1
+       %void = OpTypeVoid
+       %uint = OpTypeInt 32 0
+        %int = OpTypeInt 32 1
+        %one = OpConstant %int 1
+        %two = OpConstant %int 2
+      %eight = OpConstant %uint 8
+       %nine = OpConstant %uint 9
+    %void_fn = OpTypeFunction %void
+
+       %main = OpFunction %void None %void_fn
+ %main_start = OpLabel
+               %1 = OpBitwiseAnd %uint %one %two
+               OpReturn
+               OpFunctionEnd
+)",
+              R"(
+%main = @compute @workgroup_size(1u, 1u, 1u) func():void {
+  $B1: {
+    %2:u32 = spirv.bitwiseAnd<u32> 1i, 2i
+    ret
+  }
+}
+)");
+}
+
+TEST_F(SpirvParserTest, BitwiseAnd_Vector_SignedSigned_Signed) {
+    EXPECT_IR(R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main"
+               OpExecutionMode %main LocalSize 1 1 1
+       %void = OpTypeVoid
+       %uint = OpTypeInt 32 0
+        %int = OpTypeInt 32 1
+     %v2uint = OpTypeVector %uint 2
+      %v2int = OpTypeVector %int 2
+        %one = OpConstant %int 1
+        %two = OpConstant %int 2
+      %eight = OpConstant %uint 8
+       %nine = OpConstant %uint 9
+      %v2one = OpConstantComposite %v2int %one %one
+      %v2two = OpConstantComposite %v2int %two %two
+    %v2eight = OpConstantComposite %v2uint %eight %eight
+     %v2nine = OpConstantComposite %v2uint %nine %nine
+    %void_fn = OpTypeFunction %void
+
+       %main = OpFunction %void None %void_fn
+ %main_start = OpLabel
+               %1 = OpBitwiseAnd %v2int %v2one %v2two
+               OpReturn
+               OpFunctionEnd
+)",
+              R"(
+%main = @compute @workgroup_size(1u, 1u, 1u) func():void {
+  $B1: {
+    %2:vec2<i32> = spirv.bitwiseAnd<i32> vec2<i32>(1i), vec2<i32>(2i)
+    ret
+  }
+}
+)");
+}
+
+TEST_F(SpirvParserTest, BitwiseAnd_Vector_SignedUnsigned_Signed) {
+    EXPECT_IR(R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main"
+               OpExecutionMode %main LocalSize 1 1 1
+       %void = OpTypeVoid
+       %uint = OpTypeInt 32 0
+        %int = OpTypeInt 32 1
+     %v2uint = OpTypeVector %uint 2
+      %v2int = OpTypeVector %int 2
+        %one = OpConstant %int 1
+        %two = OpConstant %int 2
+      %eight = OpConstant %uint 8
+       %nine = OpConstant %uint 9
+      %v2one = OpConstantComposite %v2int %one %one
+      %v2two = OpConstantComposite %v2int %two %two
+    %v2eight = OpConstantComposite %v2uint %eight %eight
+     %v2nine = OpConstantComposite %v2uint %nine %nine
+    %void_fn = OpTypeFunction %void
+
+       %main = OpFunction %void None %void_fn
+ %main_start = OpLabel
+               %1 = OpBitwiseAnd %v2int %v2one %v2eight
+               OpReturn
+               OpFunctionEnd
+)",
+              R"(
+%main = @compute @workgroup_size(1u, 1u, 1u) func():void {
+  $B1: {
+    %2:vec2<i32> = spirv.bitwiseAnd<i32> vec2<i32>(1i), vec2<u32>(8u)
+    ret
+  }
+}
+)");
+}
+
+TEST_F(SpirvParserTest, BitwiseAnd_Vector_UnsignedSigned_Signed) {
+    EXPECT_IR(R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main"
+               OpExecutionMode %main LocalSize 1 1 1
+       %void = OpTypeVoid
+       %uint = OpTypeInt 32 0
+        %int = OpTypeInt 32 1
+     %v2uint = OpTypeVector %uint 2
+      %v2int = OpTypeVector %int 2
+        %one = OpConstant %int 1
+        %two = OpConstant %int 2
+      %eight = OpConstant %uint 8
+       %nine = OpConstant %uint 9
+      %v2one = OpConstantComposite %v2int %one %one
+      %v2two = OpConstantComposite %v2int %two %two
+    %v2eight = OpConstantComposite %v2uint %eight %eight
+     %v2nine = OpConstantComposite %v2uint %nine %nine
+    %void_fn = OpTypeFunction %void
+
+       %main = OpFunction %void None %void_fn
+ %main_start = OpLabel
+               %1 = OpBitwiseAnd %v2int %v2eight %v2one
+               OpReturn
+               OpFunctionEnd
+)",
+              R"(
+%main = @compute @workgroup_size(1u, 1u, 1u) func():void {
+  $B1: {
+    %2:vec2<i32> = spirv.bitwiseAnd<i32> vec2<u32>(8u), vec2<i32>(1i)
+    ret
+  }
+}
+)");
+}
+
+TEST_F(SpirvParserTest, BitwiseAnd_Vector_UnsignedUnsigned_Signed) {
+    EXPECT_IR(R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main"
+               OpExecutionMode %main LocalSize 1 1 1
+       %void = OpTypeVoid
+       %uint = OpTypeInt 32 0
+        %int = OpTypeInt 32 1
+     %v2uint = OpTypeVector %uint 2
+      %v2int = OpTypeVector %int 2
+        %one = OpConstant %int 1
+        %two = OpConstant %int 2
+      %eight = OpConstant %uint 8
+       %nine = OpConstant %uint 9
+      %v2one = OpConstantComposite %v2int %one %one
+      %v2two = OpConstantComposite %v2int %two %two
+    %v2eight = OpConstantComposite %v2uint %eight %eight
+     %v2nine = OpConstantComposite %v2uint %nine %nine
+    %void_fn = OpTypeFunction %void
+
+       %main = OpFunction %void None %void_fn
+ %main_start = OpLabel
+               %1 = OpBitwiseAnd %v2int %v2eight %v2nine
+               OpReturn
+               OpFunctionEnd
+)",
+              R"(
+%main = @compute @workgroup_size(1u, 1u, 1u) func():void {
+  $B1: {
+    %2:vec2<i32> = spirv.bitwiseAnd<i32> vec2<u32>(8u), vec2<u32>(9u)
+    ret
+  }
+}
+)");
+}
+
+TEST_F(SpirvParserTest, BitwiseAnd_Vector_UnsignedUnsigned_Unsigned) {
+    EXPECT_IR(R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main"
+               OpExecutionMode %main LocalSize 1 1 1
+       %void = OpTypeVoid
+       %uint = OpTypeInt 32 0
+        %int = OpTypeInt 32 1
+     %v2uint = OpTypeVector %uint 2
+      %v2int = OpTypeVector %int 2
+        %one = OpConstant %int 1
+        %two = OpConstant %int 2
+      %eight = OpConstant %uint 8
+       %nine = OpConstant %uint 9
+      %v2one = OpConstantComposite %v2int %one %one
+      %v2two = OpConstantComposite %v2int %two %two
+    %v2eight = OpConstantComposite %v2uint %eight %eight
+     %v2nine = OpConstantComposite %v2uint %nine %nine
+    %void_fn = OpTypeFunction %void
+
+       %main = OpFunction %void None %void_fn
+ %main_start = OpLabel
+               %1 = OpBitwiseAnd %v2uint %v2eight %v2nine
+               OpReturn
+               OpFunctionEnd
+)",
+              R"(
+%main = @compute @workgroup_size(1u, 1u, 1u) func():void {
+  $B1: {
+    %2:vec2<u32> = spirv.bitwiseAnd<u32> vec2<u32>(8u), vec2<u32>(9u)
+    ret
+  }
+}
+)");
+}
+
+TEST_F(SpirvParserTest, BitwiseAnd_Vector_UnsignedSigned_Unsigned) {
+    EXPECT_IR(R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main"
+               OpExecutionMode %main LocalSize 1 1 1
+       %void = OpTypeVoid
+       %uint = OpTypeInt 32 0
+        %int = OpTypeInt 32 1
+     %v2uint = OpTypeVector %uint 2
+      %v2int = OpTypeVector %int 2
+        %one = OpConstant %int 1
+        %two = OpConstant %int 2
+      %eight = OpConstant %uint 8
+       %nine = OpConstant %uint 9
+      %v2one = OpConstantComposite %v2int %one %one
+      %v2two = OpConstantComposite %v2int %two %two
+    %v2eight = OpConstantComposite %v2uint %eight %eight
+     %v2nine = OpConstantComposite %v2uint %nine %nine
+    %void_fn = OpTypeFunction %void
+
+       %main = OpFunction %void None %void_fn
+ %main_start = OpLabel
+               %1 = OpBitwiseAnd %v2uint %v2eight %v2one
+               OpReturn
+               OpFunctionEnd
+)",
+              R"(
+%main = @compute @workgroup_size(1u, 1u, 1u) func():void {
+  $B1: {
+    %2:vec2<u32> = spirv.bitwiseAnd<u32> vec2<u32>(8u), vec2<i32>(1i)
+    ret
+  }
+}
+)");
+}
+
+TEST_F(SpirvParserTest, BitwiseAnd_Vector_SignedUnsigned_Unsigned) {
+    EXPECT_IR(R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main"
+               OpExecutionMode %main LocalSize 1 1 1
+       %void = OpTypeVoid
+       %uint = OpTypeInt 32 0
+        %int = OpTypeInt 32 1
+     %v2uint = OpTypeVector %uint 2
+      %v2int = OpTypeVector %int 2
+        %one = OpConstant %int 1
+        %two = OpConstant %int 2
+      %eight = OpConstant %uint 8
+       %nine = OpConstant %uint 9
+      %v2one = OpConstantComposite %v2int %one %one
+      %v2two = OpConstantComposite %v2int %two %two
+    %v2eight = OpConstantComposite %v2uint %eight %eight
+     %v2nine = OpConstantComposite %v2uint %nine %nine
+    %void_fn = OpTypeFunction %void
+
+       %main = OpFunction %void None %void_fn
+ %main_start = OpLabel
+               %1 = OpBitwiseAnd %v2uint %v2one %v2eight
+               OpReturn
+               OpFunctionEnd
+)",
+              R"(
+%main = @compute @workgroup_size(1u, 1u, 1u) func():void {
+  $B1: {
+    %2:vec2<u32> = spirv.bitwiseAnd<u32> vec2<i32>(1i), vec2<u32>(8u)
+    ret
+  }
+}
+)");
+}
+
+TEST_F(SpirvParserTest, BitwiseAnd_Vector_SignedSigned_Unsigned) {
+    EXPECT_IR(R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main"
+               OpExecutionMode %main LocalSize 1 1 1
+       %void = OpTypeVoid
+       %uint = OpTypeInt 32 0
+        %int = OpTypeInt 32 1
+     %v2uint = OpTypeVector %uint 2
+      %v2int = OpTypeVector %int 2
+        %one = OpConstant %int 1
+        %two = OpConstant %int 2
+      %eight = OpConstant %uint 8
+       %nine = OpConstant %uint 9
+      %v2one = OpConstantComposite %v2int %one %one
+      %v2two = OpConstantComposite %v2int %two %two
+    %v2eight = OpConstantComposite %v2uint %eight %eight
+     %v2nine = OpConstantComposite %v2uint %nine %nine
+    %void_fn = OpTypeFunction %void
+
+       %main = OpFunction %void None %void_fn
+ %main_start = OpLabel
+               %1 = OpBitwiseAnd %v2uint %v2one %v2two
+               OpReturn
+               OpFunctionEnd
+)",
+              R"(
+%main = @compute @workgroup_size(1u, 1u, 1u) func():void {
+  $B1: {
+    %2:vec2<u32> = spirv.bitwiseAnd<u32> vec2<i32>(1i), vec2<i32>(2i)
+    ret
+  }
+}
+)");
+}
+
+}  // namespace
+}  // namespace tint::spirv::reader
diff --git a/src/tint/lang/spirv/reader/parser/parser.cc b/src/tint/lang/spirv/reader/parser/parser.cc
index 736ca0e..80d054f 100644
--- a/src/tint/lang/spirv/reader/parser/parser.cc
+++ b/src/tint/lang/spirv/reader/parser/parser.cc
@@ -668,6 +668,9 @@
                 case spv::Op::OpConvertUToF:
                     EmitSpirvExplicitBuiltinCall(inst, spirv::BuiltinFn::kConvertUToF);
                     break;
+                case spv::Op::OpBitwiseAnd:
+                    EmitSpirvExplicitBuiltinCall(inst, spirv::BuiltinFn::kBitwiseAnd);
+                    break;
                 case spv::Op::OpAccessChain:
                 case spv::Op::OpInBoundsAccessChain:
                     EmitAccess(inst);
diff --git a/src/tint/lang/spirv/spirv.def b/src/tint/lang/spirv/spirv.def
index 3cf4ad5..c8c879b 100644
--- a/src/tint/lang/spirv/spirv.def
+++ b/src/tint/lang/spirv/spirv.def
@@ -421,6 +421,9 @@
 implicit(T: iu32) fn convertUToF<R: f32_f16>(T) -> R
 implicit(T: iu32, N: num) fn convertUToF<R: f32_f16>(vec<N, T>) -> vec<N, R>
 
+implicit(A: iu32, B: iu32) fn bitwiseAnd<R: iu32>(A, B) -> R
+implicit(A: iu32, B: iu32, N: num) fn bitwiseAnd<R: iu32>(vec<N, A>, vec<N, B>) -> vec<N, R>
+
 ////////////////////////////////////////////////////////////////////////////////
 // SPV_KHR_integer_dot_product instructions
 ////////////////////////////////////////////////////////////////////////////////
diff --git a/src/tint/lang/spirv/writer/printer/printer.cc b/src/tint/lang/spirv/writer/printer/printer.cc
index eaed98a..e5b4f59 100644
--- a/src/tint/lang/spirv/writer/printer/printer.cc
+++ b/src/tint/lang/spirv/writer/printer/printer.cc
@@ -1533,6 +1533,9 @@
             case BuiltinFn::kConvertUToF:
                 op = spv::Op::OpConvertUToF;
                 break;
+            case BuiltinFn::kBitwiseAnd:
+                op = spv::Op::OpBitwiseAnd;
+                break;
             case spirv::BuiltinFn::kNone:
                 TINT_ICE() << "undefined spirv ir function";
         }