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

llvmlistbot at llvm.org llvmlistbot at llvm.org
Fri Oct 16 20:05:54 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-mlir-llvm

Author: Nick Riasanovsky (njriasan)

<details>
<summary>Changes</summary>

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.

---
Full diff: https://github.com/llvm/llvm-project/pull/231630.diff


4 Files Affected:

- (modified) mlir/include/mlir/Dialect/LLVMIR/LLVMTypes.td (+12) 
- (modified) mlir/lib/Dialect/LLVMIR/IR/LLVMDialect.cpp (+76-10) 
- (modified) mlir/test/Dialect/LLVMIR/invalid.mlir (+71) 
- (modified) mlir/test/Dialect/LLVMIR/roundtrip.mlir (+28-6) 


``````````diff
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 be5ea6df0caba..f635a58b8bedd 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,36 +4041,101 @@ OpFoldResult LLVM::BitcastOp::fold(FoldAdaptor adaptor) {
   return foldChainableCast(*this, adaptor);
 }
 
-LogicalResult LLVM::BitcastOp::verify() {
-  Type srcElemType = extractVectorElementType(getArg().getType());
-  Type dstElemType = extractVectorElementType(getResult().getType());
+static FailureOr<llvm::TypeSize> getBitcastSizeInBits(Type type) {
+  if (type.isIntOrFloat())
+    return llvm::TypeSize::getFixed(type.getIntOrFloatBitWidth());
+  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();
+  FailureOr<llvm::TypeSize> elemSize =
+      getBitcastSizeInBits(vecType.getElementType());
+  if (failed(elemSize) || !elemSize->isFixed())
+    return failure();
+  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()) {
+    if (static_cast<uint64_t>(dim) != 0 &&
+        numElements > kMaxBits / static_cast<uint64_t>(dim))
+      return failure();
+    numElements *= static_cast<uint64_t>(dim);
+  }
+  if (elemBits != 0 && numElements > kMaxBits / elemBits)
+    return failure();
+  return llvm::TypeSize::get(numElements * elemBits, vecType.isScalable());
+}
 
-  // TODO: 'bitcast' requires result and operand type to be identical in size.
-  // Byte types may be cast from/to any type pointer constraints.
-  if (isa<LLVMByteType>(srcElemType) || isa<LLVMByteType>(dstElemType))
-    return success();
+LogicalResult LLVM::BitcastOp::verify() {
+  Type srcType = getArg().getType();
+  Type dstType = getType();
+  Type srcElemType = extractVectorElementType(srcType);
+  Type dstElemType = extractVectorElementType(dstType);
 
   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'.
+  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))
     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);
+    // 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");
+    if (*srcSize != *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 ")
+             << sizeToString(*srcSize) << " bits and " << sizeToString(*dstSize)
+             << " 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()))
+  // 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");
 
-  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)) {
+    // 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())
+      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..03cd6537af320 100644
--- a/mlir/test/Dialect/LLVMIR/invalid.mlir
+++ b/mlir/test/Dialect/LLVMIR/invalid.mlir
@@ -1693,6 +1693,77 @@ 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_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 8f7e3c8707e31..dea35885df0cd 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>
@@ -1356,6 +1350,34 @@ 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: @bitcast_byte
+llvm.func @bitcast_byte(%ptr: !llvm.ptr, %i64: i64) {
+  // 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
+  // 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)

``````````

</details>


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


More information about the Mlir-commits mailing list