[Mlir-commits] [mlir] [mlir][LLVM] Verify llvm.bitcast operand/result bit widths (PR #231630)

Nick Riasanovsky llvmlistbot at llvm.org
Fri Oct 16 20:05:06 PDT 2026


https://github.com/njriasan updated https://github.com/llvm/llvm-project/pull/231630

>From 9e835cca5b0ccf19db6d76f5204a3471e97370f9 Mon Sep 17 00:00:00 2001
From: Nick Riasanovsky <njriasan at meta.com>
Date: Tue, 13 Oct 2026 10:59:44 -0700
Subject: [PATCH 1/4] [mlir][LLVM] Verify llvm.bitcast operand/result bit
 widths

Implement the size check in LLVM::BitcastOp::verify that was left as a
TODO: 'bitcast requires result and operand type to be identical in size.
Without it, width-mismatched bitcasts (e.g. i128 to vector<4xbf16>)
pass verification and crash LLVM IR translation in
CastInst::Create (castIsValid assertion).

Sizes are computed structurally following llvm::Type::getPrimitiveSizeInBits
rather than via DataLayout, whose vector sizes round up to power-of-two
element counts and would diverge from LLVM's exact rule. Pointers of the
same address space need no size query; vectors of pointers additionally
require equal element counts, matching castIsValid.

Byte-involved bitcasts stay exempt per existing semantics, and types
without a primitive bit width skip the check; both are marked with TODOs,
as is the saturated handling of dynamic vector dimensions.
---
 mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp | 75 ++++++++++++++++++++--
 mlir/test/Dialect/LLVMIR/invalid.mlir      | 35 ++++++++++
 mlir/test/Dialect/LLVMIR/roundtrip.mlir    | 15 +++++
 3 files changed, 119 insertions(+), 6 deletions(-)

diff --git a/mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp b/mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp
index be5ea6df0caba..b823d4456a03f 100644
--- a/mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp
+++ b/mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp
@@ -35,6 +35,7 @@
 
 #include "LLVMDialectBytecode.h"
 
+#include <limits>
 #include <numeric>
 #include <optional>
 
@@ -4040,12 +4041,50 @@ OpFoldResult LLVM::BitcastOp::fold(FoldAdaptor adaptor) {
   return foldChainableCast(*this, adaptor);
 }
 
+/// Computes the primitive bit size of an LLVM-compatible scalar or vector
+/// type, following the rules of `llvm::Type::getPrimitiveSizeInBits`.
+/// Returns failure for types without a primitive bit size.
+static FailureOr<llvm::TypeSize> getBitcastSizeInBits(Type type) {
+  if (type.isIntOrFloat())
+    return llvm::TypeSize::getFixed(type.getIntOrFloatBitWidth());
+  // Not builtin float types, but have fixed primitive sizes.
+  if (isa<LLVMPPCFP128Type>(type))
+    return llvm::TypeSize::getFixed(128);
+  if (isa<LLVMX86AMXType>(type))
+    return llvm::TypeSize::getFixed(8192);
+  auto vecType = dyn_cast<VectorType>(type);
+  if (!vecType)
+    return failure();
+  FailureOr<llvm::TypeSize> elemSize =
+      getBitcastSizeInBits(vecType.getElementType());
+  if (failed(elemSize) || !elemSize->isFixed())
+    return failure();
+  constexpr uint64_t max = std::numeric_limits<uint64_t>::max();
+  uint64_t elemBits = elemSize->getFixedValue();
+  uint64_t numElements = 1;
+  for (int64_t dim : vecType.getShape()) {
+    // TODO: Diagnose dynamic dimensions explicitly instead of saturating.
+    if (dim < 0)
+      return llvm::TypeSize::get(max, vecType.isScalable());
+    if (static_cast<uint64_t>(dim) != 0 &&
+        numElements > max / static_cast<uint64_t>(dim))
+      return llvm::TypeSize::get(max, vecType.isScalable());
+    numElements *= static_cast<uint64_t>(dim);
+  }
+  if (elemBits != 0 && numElements > max / elemBits)
+    return llvm::TypeSize::get(max, vecType.isScalable());
+  return llvm::TypeSize::get(numElements * elemBits, vecType.isScalable());
+}
+
 LogicalResult LLVM::BitcastOp::verify() {
-  Type srcElemType = extractVectorElementType(getArg().getType());
-  Type dstElemType = extractVectorElementType(getResult().getType());
+  Type srcType = getArg().getType();
+  Type dstType = getType();
+  Type srcElemType = extractVectorElementType(srcType);
+  Type dstElemType = extractVectorElementType(dstType);
 
-  // TODO: 'bitcast' requires result and operand type to be identical in size.
   // Byte types may be cast from/to any type pointer constraints.
+  // TODO: Tighten to byte <-> pointer only; mismatched byte bitcasts such
+  // as i64 to byte<8> are accepted here but crash LLVM IR translation.
   if (isa<LLVMByteType>(srcElemType) || isa<LLVMByteType>(dstElemType))
     return success();
 
@@ -4057,19 +4096,43 @@ LogicalResult LLVM::BitcastOp::verify() {
   if (static_cast<bool>(resultType) != static_cast<bool>(sourceType))
     return emitOpError("can only cast pointers from and to pointers");
 
-  if (!resultType)
+  if (!resultType) {
+    FailureOr<llvm::TypeSize> srcSize = getBitcastSizeInBits(srcType);
+    FailureOr<llvm::TypeSize> dstSize = getBitcastSizeInBits(dstType);
+    // TODO: Reject bitcasts of types without a primitive bit width (void,
+    // labels, metadata, functions, tokens, target extensions) instead of
+    // skipping the check; they crash LLVM IR translation today.
+    if (failed(srcSize) || failed(dstSize))
+      return success();
+    if (*srcSize != *dstSize) {
+      std::string srcStr, dstStr;
+      llvm::raw_string_ostream(srcStr) << *srcSize;
+      llvm::raw_string_ostream(dstStr) << *dstSize;
+      return emitOpError("operand and result must have the same bit width, "
+                         "got ")
+             << srcStr << " bits and " << dstStr << " bits";
+    }
     return success();
+  }
 
   auto isVector = llvm::IsaPred<VectorType>;
 
   // Due to bitcast requiring both operands to be of the same size, it is not
   // possible for only one of the two to be a pointer of vectors.
-  if (isVector(getResult().getType()) && !isVector(getArg().getType()))
+  if (isVector(dstType) && !isVector(srcType))
     return emitOpError("cannot cast pointer to vector of pointers");
 
-  if (!isVector(getResult().getType()) && isVector(getArg().getType()))
+  if (!isVector(dstType) && isVector(srcType))
     return emitOpError("cannot cast vector of pointers to pointer");
 
+  if (auto dstVecType = dyn_cast<VectorType>(dstType)) {
+    auto srcVecType = cast<VectorType>(srcType);
+    if (dstVecType.getShape() != srcVecType.getShape() ||
+        dstVecType.isScalable() != srcVecType.isScalable())
+      return emitOpError(
+          "cannot cast between vectors of pointers with different sizes");
+  }
+
   // Bitcast cannot cast between pointers of different address spaces.
   // 'llvm.addrspacecast' must be used for this purpose instead.
   if (resultType.getAddressSpace() != sourceType.getAddressSpace())
diff --git a/mlir/test/Dialect/LLVMIR/invalid.mlir b/mlir/test/Dialect/LLVMIR/invalid.mlir
index fc2ecd9d11b64..9627a5386158a 100644
--- a/mlir/test/Dialect/LLVMIR/invalid.mlir
+++ b/mlir/test/Dialect/LLVMIR/invalid.mlir
@@ -1693,6 +1693,41 @@ func.func @invalid_bitcast_addr_cast_vec(%arg : vector<4x!llvm.ptr<1>>) {
 
 // -----
 
+func.func @invalid_bitcast_size_mismatch(%arg0: i128) {
+  // expected-error at +1 {{operand and result must have the same bit width, got 128 bits and 64 bits}}
+  %0 = llvm.bitcast %arg0 : i128 to vector<4xbf16>
+}
+
+// -----
+
+func.func @invalid_bitcast_scalar_size_mismatch(%arg0: i32) {
+  // expected-error at +1 {{operand and result must have the same bit width, got 32 bits and 64 bits}}
+  %0 = llvm.bitcast %arg0 : i32 to i64
+}
+
+// -----
+
+func.func @invalid_bitcast_vector_size_mismatch(%arg0: vector<4xi32>) {
+  // expected-error at +1 {{operand and result must have the same bit width, got 128 bits and 256 bits}}
+  %0 = llvm.bitcast %arg0 : vector<4xi32> to vector<4xi64>
+}
+
+// -----
+
+func.func @invalid_bitcast_scalable_size_mismatch(%arg0: vector<[4]xi32>) {
+  // expected-error at +1 {{operand and result must have the same bit width, got vscale x 128 bits and 128 bits}}
+  %0 = llvm.bitcast %arg0 : vector<[4]xi32> to vector<16xi8>
+}
+
+// -----
+
+func.func @invalid_bitcast_ptr_vector_size_mismatch(%arg0: vector<2x!llvm.ptr>) {
+  // expected-error at +1 {{cannot cast between vectors of pointers with different sizes}}
+  %0 = llvm.bitcast %arg0 : vector<2x!llvm.ptr> to vector<4x!llvm.ptr>
+}
+
+// -----
+
 func.func @invalid_target_ext_alloca() {
   %0 = llvm.mlir.constant(1 : i64) : i64
   // expected-error at +1 {{this target extension type cannot be used in alloca}}
diff --git a/mlir/test/Dialect/LLVMIR/roundtrip.mlir b/mlir/test/Dialect/LLVMIR/roundtrip.mlir
index 8f7e3c8707e31..73a245164041c 100644
--- a/mlir/test/Dialect/LLVMIR/roundtrip.mlir
+++ b/mlir/test/Dialect/LLVMIR/roundtrip.mlir
@@ -1356,6 +1356,21 @@ llvm.func @masked_nontemporal_no_alignment(%ptr: !llvm.ptr, %mask: vector<7xi1>)
   llvm.return
 }
 
+// CHECK-LABEL: @bitcast_same_size
+llvm.func @bitcast_same_size(%arg0: !llvm.ppc_fp128, %arg1: vector<[4]xi32>,
+                              %arg2: !llvm.x86_amx) {
+  // CHECK: llvm.bitcast {{.*}} : !llvm.ppc_fp128 to i128
+  %0 = llvm.bitcast %arg0 : !llvm.ppc_fp128 to i128
+  // CHECK: llvm.bitcast {{.*}} : vector<[4]xi32> to vector<[8]xi16>
+  %1 = llvm.bitcast %arg1 : vector<[4]xi32> to vector<[8]xi16>
+  // CHECK: llvm.bitcast {{.*}} : !llvm.x86_amx to vector<256xi32>
+  %2 = llvm.bitcast %arg2 : !llvm.x86_amx to vector<256xi32>
+  // CHECK: llvm.bitcast {{.*}} : i64 to vector<4xbf16>
+  %c = llvm.mlir.constant(0 : i64) : i64
+  %3 = llvm.bitcast %c : i64 to vector<4xbf16>
+  llvm.return
+}
+
 // CHECK-LABEL: @switch_result_number
 llvm.func @switch_result_number(%arg0: i32) -> i32 {
   %0:2 = "test.op_with_two_results"() : () -> (i32, i32)

>From 6db6f67706b5f30c985474ecd5a26f4719be929c Mon Sep 17 00:00:00 2001
From: Nick Riasanovsky <njriasan at meta.com>
Date: Wed, 14 Oct 2026 16:37:20 -0700
Subject: [PATCH 2/4] [mlir][LLVM] Clean up bitcast verifier per self-review

Move primitive bit widths to the LLVM type definitions, tighten byte
handling to byte<->pointer only, reject sizeless types instead of
skipping the check, and extend invalid/roundtrip test coverage.
---
 mlir/include/mlir/Dialect/LLVMIR/LLVMTypes.td | 12 +++
 mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp    | 73 +++++++++++--------
 mlir/test/Dialect/LLVMIR/invalid.mlir         | 36 +++++++++
 mlir/test/Dialect/LLVMIR/roundtrip.mlir       | 21 ++++--
 4 files changed, 107 insertions(+), 35 deletions(-)

diff --git a/mlir/include/mlir/Dialect/LLVMIR/LLVMTypes.td b/mlir/include/mlir/Dialect/LLVMIR/LLVMTypes.td
index 910256aa4617b..d3016f677885e 100644
--- a/mlir/include/mlir/Dialect/LLVMIR/LLVMTypes.td
+++ b/mlir/include/mlir/Dialect/LLVMIR/LLVMTypes.td
@@ -361,6 +361,12 @@ def LLVMX86AMXType : LLVMType<"LLVMX86AMX", "x86_amx"> {
     The x86_amx type represents a value held in an AMX tile register on an x86
     machine. Can only be used in AMX intrinsics calls.
   }];
+
+  let extraClassDeclaration = [{
+    /// Returns the primitive bit width of this type, matching
+    /// \c llvm::Type::getPrimitiveSizeInBits.
+    unsigned getPrimitiveBitWidth() const { return 8192; }
+  }];
 }
 
 //===----------------------------------------------------------------------===//
@@ -374,6 +380,12 @@ def LLVMPPCFP128Type : LLVMType<"LLVMPPCFP128", "ppc_fp128",
     A 128 bit floating-point type with IBM double-double semantics.
     See S_PPCDoubleDouble in APFloat.h for details.
   }];
+
+  let extraClassDeclaration = [{
+    /// Returns the primitive bit width of this type, matching
+    /// \c llvm::Type::getPrimitiveSizeInBits.
+    unsigned getPrimitiveBitWidth() const { return 128; }
+  }];
 }
 
 #endif // LLVMTYPES_TD
diff --git a/mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp b/mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp
index b823d4456a03f..da45e28ee5f01 100644
--- a/mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp
+++ b/mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp
@@ -4042,16 +4042,26 @@ OpFoldResult LLVM::BitcastOp::fold(FoldAdaptor adaptor) {
 }
 
 /// Computes the primitive bit size of an LLVM-compatible scalar or vector
-/// type, following the rules of `llvm::Type::getPrimitiveSizeInBits`.
-/// Returns failure for types without a primitive bit size.
+/// type, following the rules of \c llvm::Type::getPrimitiveSizeInBits.
+/// Returns failure for types without a primitive bit size (pointers, void,
+/// labels, metadata, functions, tokens, target extensions and aggregates)
+/// and for vector sizes overflowing \c uint64_t. Vector dimensions are always
+/// positive (dynamic dimensions are not representable in \c VectorType).
+///
+/// This intentionally does not reuse \c getPrimitiveTypeSizeInBits, whose
+/// assert-based contract and conflation of sizeless types with size 0 make it
+/// unsuitable for use in a verifier.
 static FailureOr<llvm::TypeSize> getBitcastSizeInBits(Type type) {
   if (type.isIntOrFloat())
     return llvm::TypeSize::getFixed(type.getIntOrFloatBitWidth());
-  // Not builtin float types, but have fixed primitive sizes.
-  if (isa<LLVMPPCFP128Type>(type))
-    return llvm::TypeSize::getFixed(128);
-  if (isa<LLVMX86AMXType>(type))
-    return llvm::TypeSize::getFixed(8192);
+  if (auto byteTy = dyn_cast<LLVMByteType>(type))
+    return llvm::TypeSize::getFixed(byteTy.getBitWidth());
+  // Not builtin float types, but have fixed primitive sizes reported by the
+  // type definitions.
+  if (auto ppcTy = dyn_cast<LLVMPPCFP128Type>(type))
+    return llvm::TypeSize::getFixed(ppcTy.getPrimitiveBitWidth());
+  if (auto amxTy = dyn_cast<LLVMX86AMXType>(type))
+    return llvm::TypeSize::getFixed(amxTy.getPrimitiveBitWidth());
   auto vecType = dyn_cast<VectorType>(type);
   if (!vecType)
     return failure();
@@ -4059,20 +4069,17 @@ static FailureOr<llvm::TypeSize> getBitcastSizeInBits(Type type) {
       getBitcastSizeInBits(vecType.getElementType());
   if (failed(elemSize) || !elemSize->isFixed())
     return failure();
-  constexpr uint64_t max = std::numeric_limits<uint64_t>::max();
+  constexpr uint64_t kMaxBits = std::numeric_limits<uint64_t>::max();
   uint64_t elemBits = elemSize->getFixedValue();
   uint64_t numElements = 1;
   for (int64_t dim : vecType.getShape()) {
-    // TODO: Diagnose dynamic dimensions explicitly instead of saturating.
-    if (dim < 0)
-      return llvm::TypeSize::get(max, vecType.isScalable());
     if (static_cast<uint64_t>(dim) != 0 &&
-        numElements > max / static_cast<uint64_t>(dim))
-      return llvm::TypeSize::get(max, vecType.isScalable());
+        numElements > kMaxBits / static_cast<uint64_t>(dim))
+      return failure();
     numElements *= static_cast<uint64_t>(dim);
   }
-  if (elemBits != 0 && numElements > max / elemBits)
-    return llvm::TypeSize::get(max, vecType.isScalable());
+  if (elemBits != 0 && numElements > kMaxBits / elemBits)
+    return failure();
   return llvm::TypeSize::get(numElements * elemBits, vecType.isScalable());
 }
 
@@ -4082,15 +4089,16 @@ LogicalResult LLVM::BitcastOp::verify() {
   Type srcElemType = extractVectorElementType(srcType);
   Type dstElemType = extractVectorElementType(dstType);
 
-  // Byte types may be cast from/to any type pointer constraints.
-  // TODO: Tighten to byte <-> pointer only; mismatched byte bitcasts such
-  // as i64 to byte<8> are accepted here but crash LLVM IR translation.
-  if (isa<LLVMByteType>(srcElemType) || isa<LLVMByteType>(dstElemType))
-    return success();
-
   auto resultType = llvm::dyn_cast<LLVMPointerType>(dstElemType);
   auto sourceType = llvm::dyn_cast<LLVMPointerType>(srcElemType);
 
+  // Bitcasts between bytes and pointers are always valid, matching
+  // 'CastInst::castIsValid'. Byte casts against anything else are
+  // size-checked below like any other type.
+  if ((isa<LLVMByteType>(srcElemType) && resultType) ||
+      (sourceType && isa<LLVMByteType>(dstElemType)))
+    return success();
+
   // If one of the types is a pointer (or vector of pointers), then
   // both source and result type have to be pointers.
   if (static_cast<bool>(resultType) != static_cast<bool>(sourceType))
@@ -4099,18 +4107,21 @@ LogicalResult LLVM::BitcastOp::verify() {
   if (!resultType) {
     FailureOr<llvm::TypeSize> srcSize = getBitcastSizeInBits(srcType);
     FailureOr<llvm::TypeSize> dstSize = getBitcastSizeInBits(dstType);
-    // TODO: Reject bitcasts of types without a primitive bit width (void,
-    // labels, metadata, functions, tokens, target extensions) instead of
-    // skipping the check; they crash LLVM IR translation today.
+    // Types without a primitive bit width (void, labels, metadata,
+    // functions, tokens, target extensions) cannot be bitcast, matching
+    // 'CastInst::castIsValid'.
     if (failed(srcSize) || failed(dstSize))
-      return success();
+      return emitOpError("operand and result must have a primitive bit width");
     if (*srcSize != *dstSize) {
-      std::string srcStr, dstStr;
-      llvm::raw_string_ostream(srcStr) << *srcSize;
-      llvm::raw_string_ostream(dstStr) << *dstSize;
+      auto sizeToString = [](llvm::TypeSize size) {
+        std::string str;
+        llvm::raw_string_ostream(str) << size;
+        return str;
+      };
       return emitOpError("operand and result must have the same bit width, "
                          "got ")
-             << srcStr << " bits and " << dstStr << " bits";
+             << sizeToString(*srcSize) << " bits and " << sizeToString(*dstSize)
+             << " bits";
     }
     return success();
   }
@@ -4119,6 +4130,8 @@ LogicalResult LLVM::BitcastOp::verify() {
 
   // Due to bitcast requiring both operands to be of the same size, it is not
   // possible for only one of the two to be a pointer of vectors.
+  // Note: this is stricter than 'CastInst::castIsValid', which permits casts
+  // between scalar pointers and 1-element pointer vectors.
   if (isVector(dstType) && !isVector(srcType))
     return emitOpError("cannot cast pointer to vector of pointers");
 
@@ -4126,6 +4139,8 @@ LogicalResult LLVM::BitcastOp::verify() {
     return emitOpError("cannot cast vector of pointers to pointer");
 
   if (auto dstVecType = dyn_cast<VectorType>(dstType)) {
+    // Safe: the checks above guarantee that if one side is a vector of
+    // pointers, so is the other.
     auto srcVecType = cast<VectorType>(srcType);
     if (dstVecType.getShape() != srcVecType.getShape() ||
         dstVecType.isScalable() != srcVecType.isScalable())
diff --git a/mlir/test/Dialect/LLVMIR/invalid.mlir b/mlir/test/Dialect/LLVMIR/invalid.mlir
index 9627a5386158a..03cd6537af320 100644
--- a/mlir/test/Dialect/LLVMIR/invalid.mlir
+++ b/mlir/test/Dialect/LLVMIR/invalid.mlir
@@ -1728,6 +1728,42 @@ func.func @invalid_bitcast_ptr_vector_size_mismatch(%arg0: vector<2x!llvm.ptr>)
 
 // -----
 
+func.func @invalid_bitcast_amx_size_mismatch(%arg0: !llvm.x86_amx) {
+  // expected-error at +1 {{operand and result must have the same bit width, got 8192 bits and 128 bits}}
+  %0 = llvm.bitcast %arg0 : !llvm.x86_amx to i128
+}
+
+// -----
+
+func.func @invalid_bitcast_byte_size_mismatch(%arg0: i64) {
+  // expected-error at +1 {{operand and result must have the same bit width, got 64 bits and 8 bits}}
+  %0 = llvm.bitcast %arg0 : i64 to !llvm.byte<8>
+}
+
+// -----
+
+func.func @invalid_bitcast_byte_to_int_size_mismatch(%arg0: !llvm.byte<64>) {
+  // expected-error at +1 {{operand and result must have the same bit width, got 64 bits and 32 bits}}
+  %0 = llvm.bitcast %arg0 : !llvm.byte<64> to i32
+}
+
+// -----
+
+func.func @invalid_bitcast_byte_to_byte_size_mismatch(%arg0: !llvm.byte<64>) {
+  // expected-error at +1 {{operand and result must have the same bit width, got 64 bits and 8 bits}}
+  %0 = llvm.bitcast %arg0 : !llvm.byte<64> to !llvm.byte<8>
+}
+
+// -----
+
+func.func @invalid_bitcast_no_primitive_size() {
+  %0 = llvm.mlir.undef : !llvm.target<"spirv.Event">
+  // expected-error at +1 {{operand and result must have a primitive bit width}}
+  %1 = llvm.bitcast %0 : !llvm.target<"spirv.Event"> to i32
+}
+
+// -----
+
 func.func @invalid_target_ext_alloca() {
   %0 = llvm.mlir.constant(1 : i64) : i64
   // expected-error at +1 {{this target extension type cannot be used in alloca}}
diff --git a/mlir/test/Dialect/LLVMIR/roundtrip.mlir b/mlir/test/Dialect/LLVMIR/roundtrip.mlir
index 73a245164041c..00e11d03253e8 100644
--- a/mlir/test/Dialect/LLVMIR/roundtrip.mlir
+++ b/mlir/test/Dialect/LLVMIR/roundtrip.mlir
@@ -422,12 +422,6 @@ func.func @casts(%arg0: i32, %arg1: i64, %arg2: vector<4xi32>,
   %12 = llvm.bitcast %arg1 : i64 to !llvm.byte<64>
 // CHECK:  = llvm.bitcast %12 : !llvm.byte<64> to i64
   %13 = llvm.bitcast %12 : !llvm.byte<64> to i64
-// CHECK:  = llvm.bitcast %[[I64]] : i64 to !llvm.byte<8>
-  %14 = llvm.bitcast %arg1 : i64 to !llvm.byte<8>
-// CHECK:  = llvm.bitcast %12 : !llvm.byte<64> to i32
-  %15 = llvm.bitcast %12 : !llvm.byte<64> to i32
-// CHECK:  = llvm.bitcast %12 : !llvm.byte<64> to !llvm.byte<8>
-  %16 = llvm.bitcast %12 : !llvm.byte<64> to !llvm.byte<8>
 // CHECK:  = llvm.bitcast %12 : !llvm.byte<64> to !llvm.ptr
   %17 = llvm.bitcast %12 : !llvm.byte<64> to !llvm.ptr
 // CHECK:  = llvm.bitcast %[[PTR]] : !llvm.ptr to !llvm.byte<8>
@@ -1371,6 +1365,21 @@ llvm.func @bitcast_same_size(%arg0: !llvm.ppc_fp128, %arg1: vector<[4]xi32>,
   llvm.return
 }
 
+// CHECK-LABEL: @bitcast_byte
+llvm.func @bitcast_byte(%ptr: !llvm.ptr, %i64: i64) {
+  // Byte casts against non-pointers are size-checked.
+  // CHECK: llvm.bitcast {{.*}} : i64 to !llvm.byte<64>
+  %0 = llvm.bitcast %i64 : i64 to !llvm.byte<64>
+  // CHECK: llvm.bitcast {{.*}} : !llvm.byte<64> to i64
+  %1 = llvm.bitcast %0 : !llvm.byte<64> to i64
+  // Byte casts to and from pointers are always valid.
+  // CHECK: llvm.bitcast {{.*}} : !llvm.ptr to !llvm.byte<64>
+  %2 = llvm.bitcast %ptr : !llvm.ptr to !llvm.byte<64>
+  // CHECK: llvm.bitcast {{.*}} : !llvm.byte<64> to !llvm.ptr
+  %3 = llvm.bitcast %2 : !llvm.byte<64> to !llvm.ptr
+  llvm.return
+}
+
 // CHECK-LABEL: @switch_result_number
 llvm.func @switch_result_number(%arg0: i32) -> i32 {
   %0:2 = "test.op_with_two_results"() : () -> (i32, i32)

>From 8434af643456eafb18dde4014b428c3e964dbe7c Mon Sep 17 00:00:00 2001
From: Nick Riasanovsky <njriasan at meta.com>
Date: Wed, 14 Oct 2026 21:59:57 -0700
Subject: [PATCH 3/4] [mlir][LLVM] Simplify and prune bitcast verifier comments

---
 mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp | 17 ++++-------------
 mlir/test/Dialect/LLVMIR/roundtrip.mlir    |  2 --
 2 files changed, 4 insertions(+), 15 deletions(-)

diff --git a/mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp b/mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp
index da45e28ee5f01..6e18424f90329 100644
--- a/mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp
+++ b/mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp
@@ -4042,15 +4042,8 @@ OpFoldResult LLVM::BitcastOp::fold(FoldAdaptor adaptor) {
 }
 
 /// Computes the primitive bit size of an LLVM-compatible scalar or vector
-/// type, following the rules of \c llvm::Type::getPrimitiveSizeInBits.
-/// Returns failure for types without a primitive bit size (pointers, void,
-/// labels, metadata, functions, tokens, target extensions and aggregates)
-/// and for vector sizes overflowing \c uint64_t. Vector dimensions are always
-/// positive (dynamic dimensions are not representable in \c VectorType).
-///
-/// This intentionally does not reuse \c getPrimitiveTypeSizeInBits, whose
-/// assert-based contract and conflation of sizeless types with size 0 make it
-/// unsuitable for use in a verifier.
+/// type, following \c llvm::Type::getPrimitiveSizeInBits. Returns failure if
+/// the type has no primitive bit size or the size overflows.
 static FailureOr<llvm::TypeSize> getBitcastSizeInBits(Type type) {
   if (type.isIntOrFloat())
     return llvm::TypeSize::getFixed(type.getIntOrFloatBitWidth());
@@ -4093,8 +4086,7 @@ LogicalResult LLVM::BitcastOp::verify() {
   auto sourceType = llvm::dyn_cast<LLVMPointerType>(srcElemType);
 
   // Bitcasts between bytes and pointers are always valid, matching
-  // 'CastInst::castIsValid'. Byte casts against anything else are
-  // size-checked below like any other type.
+  // 'CastInst::castIsValid'.
   if ((isa<LLVMByteType>(srcElemType) && resultType) ||
       (sourceType && isa<LLVMByteType>(dstElemType)))
     return success();
@@ -4107,8 +4099,7 @@ LogicalResult LLVM::BitcastOp::verify() {
   if (!resultType) {
     FailureOr<llvm::TypeSize> srcSize = getBitcastSizeInBits(srcType);
     FailureOr<llvm::TypeSize> dstSize = getBitcastSizeInBits(dstType);
-    // Types without a primitive bit width (void, labels, metadata,
-    // functions, tokens, target extensions) cannot be bitcast, matching
+    // Types without a primitive bit width cannot be bitcast, matching
     // 'CastInst::castIsValid'.
     if (failed(srcSize) || failed(dstSize))
       return emitOpError("operand and result must have a primitive bit width");
diff --git a/mlir/test/Dialect/LLVMIR/roundtrip.mlir b/mlir/test/Dialect/LLVMIR/roundtrip.mlir
index 00e11d03253e8..dea35885df0cd 100644
--- a/mlir/test/Dialect/LLVMIR/roundtrip.mlir
+++ b/mlir/test/Dialect/LLVMIR/roundtrip.mlir
@@ -1367,12 +1367,10 @@ llvm.func @bitcast_same_size(%arg0: !llvm.ppc_fp128, %arg1: vector<[4]xi32>,
 
 // CHECK-LABEL: @bitcast_byte
 llvm.func @bitcast_byte(%ptr: !llvm.ptr, %i64: i64) {
-  // Byte casts against non-pointers are size-checked.
   // CHECK: llvm.bitcast {{.*}} : i64 to !llvm.byte<64>
   %0 = llvm.bitcast %i64 : i64 to !llvm.byte<64>
   // CHECK: llvm.bitcast {{.*}} : !llvm.byte<64> to i64
   %1 = llvm.bitcast %0 : !llvm.byte<64> to i64
-  // Byte casts to and from pointers are always valid.
   // CHECK: llvm.bitcast {{.*}} : !llvm.ptr to !llvm.byte<64>
   %2 = llvm.bitcast %ptr : !llvm.ptr to !llvm.byte<64>
   // CHECK: llvm.bitcast {{.*}} : !llvm.byte<64> to !llvm.ptr

>From 2934d2c43d329b42d10b37e60c9c06dda26ecfe9 Mon Sep 17 00:00:00 2001
From: Nick Riasanovsky <njriasan at meta.com>
Date: Fri, 16 Oct 2026 19:56:16 -0700
Subject: [PATCH 4/4] [mlir][LLVM] Remove getBitcastSizeInBits doc comment

---
 mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp | 3 ---
 1 file changed, 3 deletions(-)

diff --git a/mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp b/mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp
index 6e18424f90329..f635a58b8bedd 100644
--- a/mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp
+++ b/mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp
@@ -4041,9 +4041,6 @@ OpFoldResult LLVM::BitcastOp::fold(FoldAdaptor adaptor) {
   return foldChainableCast(*this, adaptor);
 }
 
-/// Computes the primitive bit size of an LLVM-compatible scalar or vector
-/// type, following \c llvm::Type::getPrimitiveSizeInBits. Returns failure if
-/// the type has no primitive bit size or the size overflows.
 static FailureOr<llvm::TypeSize> getBitcastSizeInBits(Type type) {
   if (type.isIntOrFloat())
     return llvm::TypeSize::getFixed(type.getIntOrFloatBitWidth());



More information about the Mlir-commits mailing list