[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