[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