[flang-commits] [flang] 252eb2a - [flang][FIR] add a new fir.bitcast operation (#187793)
via flang-commits
flang-commits at lists.llvm.org
Mon Mar 23 01:55:31 PDT 2026
Author: jeanPerier
Date: 2026-03-23T09:55:25+01:00
New Revision: 252eb2a743f56f1f34048d8c311bad6037052232
URL: https://github.com/llvm/llvm-project/commit/252eb2a743f56f1f34048d8c311bad6037052232
DIFF: https://github.com/llvm/llvm-project/commit/252eb2a743f56f1f34048d8c311bad6037052232.diff
LOG: [flang][FIR] add a new fir.bitcast operation (#187793)
This patch introduces a new bitcast operation for integer, float,
character, and logical.
The main rational for it is that it is currently not possible to express
such bitcast in FIR without going trough memory and there is a need to
have some bitcast support when interfacing with the memref dialect where
one cannot use fir.char<> and fir.logical and must use the underlying
storage type. Using fir.convert is not a good idea because it is a
semantic cast and it will for instance normalize integers when
converting from/to logical.
This could also be used to simplify the implementation of TRANSFER for
the cases of simple scalars of those types.
Assisted by: Claude
Added:
flang/test/Fir/bitcast-fold.fir
Modified:
flang/include/flang/Optimizer/Dialect/FIROps.td
flang/lib/Optimizer/CodeGen/CodeGen.cpp
flang/lib/Optimizer/Dialect/FIROps.cpp
flang/test/Fir/convert-to-llvm.fir
flang/test/Fir/fir-ops.fir
flang/test/Fir/invalid.fir
Removed:
################################################################################
diff --git a/flang/include/flang/Optimizer/Dialect/FIROps.td b/flang/include/flang/Optimizer/Dialect/FIROps.td
index 776914bb9bbe8..f499be9fb5dd2 100644
--- a/flang/include/flang/Optimizer/Dialect/FIROps.td
+++ b/flang/include/flang/Optimizer/Dialect/FIROps.td
@@ -2975,6 +2975,34 @@ def fir_ConvertOp
let hasCanonicalizer = 1;
}
+def fir_BitcastOp : fir_SimpleOneResultOp<"bitcast", [Pure]> {
+ let summary = "bitcast between Fortran scalar types of the same bit size";
+
+ let description = [{
+ Reinterpret the bits of a value from one type to another. Both types must
+ have the same bit size. This is a pure bitwise reinterpretation: no value
+ normalization or conversion takes place, unlike `fir.convert`.
+
+ The supported types are integer, floating-point, `fir.logical`, and
+ singleton `fir.char` (i.e. `fir.char<K>` with length 1 for any kind K).
+ Pointer types are not allowed.
+
+ ```
+ %0 = fir.bitcast %v : (i32) -> !fir.logical<4>
+ ```
+ }];
+
+ let arguments = (ins AnyType:$value);
+ let results = (outs AnyType:$res);
+
+ let assemblyFormat = [{
+ $value attr-dict `:` functional-type($value, results)
+ }];
+
+ let hasFolder = 1;
+ let hasVerifier = 1;
+}
+
def FortranTypeAttr : Attr<And<[CPred<"mlir::isa<mlir::TypeAttr>($_self)">,
Or<[CPred<"mlir::isa<fir::CharacterType, fir::IntegerType, "
"fir::UnsignedType, fir::LogicalType, mlir::FloatType, "
diff --git a/flang/lib/Optimizer/CodeGen/CodeGen.cpp b/flang/lib/Optimizer/CodeGen/CodeGen.cpp
index b4950746164b6..25eb6194efa99 100644
--- a/flang/lib/Optimizer/CodeGen/CodeGen.cpp
+++ b/flang/lib/Optimizer/CodeGen/CodeGen.cpp
@@ -844,6 +844,47 @@ struct IsAssumedSizeExtentOpConversion
}
};
+/// Bitcast between types of the same bit size.
+struct BitcastOpConversion : public fir::FIROpConversion<fir::BitcastOp> {
+ using FIROpConversion::FIROpConversion;
+
+ llvm::LogicalResult
+ matchAndRewrite(fir::BitcastOp bitcast, OpAdaptor adaptor,
+ mlir::ConversionPatternRewriter &rewriter) const override {
+ auto fromTy = convertType(bitcast.getValue().getType());
+ auto toTy = convertType(bitcast.getRes().getType());
+ mlir::Value op0 = adaptor.getOperands()[0];
+ if (fromTy == toTy) {
+ rewriter.replaceOp(bitcast, op0);
+ return mlir::success();
+ }
+ mlir::Location loc = bitcast.getLoc();
+ bool fromChar = mlir::isa<fir::CharacterType>(bitcast.getValue().getType());
+ bool toChar = mlir::isa<fir::CharacterType>(bitcast.getRes().getType());
+ mlir::Value cast = op0;
+ mlir::Type scalarFromTy = fromTy;
+ if (fromChar) {
+ cast = mlir::LLVM::ExtractValueOp::create(rewriter, loc, cast, {0});
+ scalarFromTy = cast.getType();
+ }
+ mlir::Type scalarToTy = toTy;
+ if (toChar)
+ scalarToTy = mlir::cast<mlir::LLVM::LLVMArrayType>(toTy).getElementType();
+
+ if (scalarFromTy != scalarToTy)
+ cast = mlir::LLVM::BitcastOp::create(rewriter, loc, scalarToTy, cast);
+
+ if (toChar) {
+ mlir::Value undef = mlir::LLVM::UndefOp::create(rewriter, loc, toTy);
+ llvm::SmallVector<int64_t> position{0};
+ cast = mlir::LLVM::InsertValueOp::create(rewriter, loc, undef, cast,
+ position);
+ }
+ rewriter.replaceOp(bitcast, cast);
+ return mlir::success();
+ }
+};
+
/// convert value of from-type to value of to-type
struct ConvertOpConversion : public fir::FIROpConversion<fir::ConvertOp> {
using FIROpConversion::FIROpConversion;
@@ -4578,15 +4619,16 @@ void fir::populateFIRToLLVMConversionPatterns(
fir::FIRToLLVMPassOptions &options) {
patterns.insert<
AbsentOpConversion, AddcOpConversion, AddrOfOpConversion,
- AllocaOpConversion, AllocMemOpConversion, BoxAddrOpConversion,
- BoxCharLenOpConversion, BoxDimsOpConversion, BoxEleSizeOpConversion,
- BoxIsAllocOpConversion, BoxIsArrayOpConversion, BoxIsPtrOpConversion,
- AssumedSizeExtentOpConversion, IsAssumedSizeExtentOpConversion,
- BoxOffsetOpConversion, BoxProcHostOpConversion, BoxRankOpConversion,
- BoxTypeCodeOpConversion, BoxTypeDescOpConversion, CallOpConversion,
- CmpcOpConversion, VolatileCastOpConversion, ConvertOpConversion,
- CoordinateOpConversion, CopyOpConversion, DTEntryOpConversion,
- DeclareOpConversion, DeclareValueOpConversion,
+ AllocaOpConversion, AllocMemOpConversion, BitcastOpConversion,
+ BoxAddrOpConversion, BoxCharLenOpConversion, BoxDimsOpConversion,
+ BoxEleSizeOpConversion, BoxIsAllocOpConversion, BoxIsArrayOpConversion,
+ BoxIsPtrOpConversion, AssumedSizeExtentOpConversion,
+ IsAssumedSizeExtentOpConversion, BoxOffsetOpConversion,
+ BoxProcHostOpConversion, BoxRankOpConversion, BoxTypeCodeOpConversion,
+ BoxTypeDescOpConversion, CallOpConversion, CmpcOpConversion,
+ VolatileCastOpConversion, ConvertOpConversion, CoordinateOpConversion,
+ CopyOpConversion, DTEntryOpConversion, DeclareOpConversion,
+ DeclareValueOpConversion,
DoConcurrentSpecifierOpConversion<fir::LocalitySpecifierOp>,
DoConcurrentSpecifierOpConversion<fir::DeclareReductionOp>,
DivcOpConversion, EmboxOpConversion, EmboxCharOpConversion,
diff --git a/flang/lib/Optimizer/Dialect/FIROps.cpp b/flang/lib/Optimizer/Dialect/FIROps.cpp
index 978120963c514..93a141da111a2 100644
--- a/flang/lib/Optimizer/Dialect/FIROps.cpp
+++ b/flang/lib/Optimizer/Dialect/FIROps.cpp
@@ -1481,6 +1481,54 @@ static mlir::ParseResult parseCmpOp(mlir::OpAsmParser &parser,
return mlir::success();
}
+//===----------------------------------------------------------------------===//
+// BitcastOp
+//===----------------------------------------------------------------------===//
+
+static bool isBitcastCompatibleType(mlir::Type ty) {
+ return mlir::isa<mlir::IntegerType, mlir::FloatType, fir::LogicalType>(ty) ||
+ (mlir::isa<fir::CharacterType>(ty) &&
+ mlir::cast<fir::CharacterType>(ty).getLen() ==
+ fir::CharacterType::singleton());
+}
+
+static std::optional<unsigned> getBitcastBitSize(mlir::Type ty) {
+ if (auto intTy = mlir::dyn_cast<mlir::IntegerType>(ty))
+ return intTy.getWidth();
+ if (auto floatTy = mlir::dyn_cast<mlir::FloatType>(ty))
+ return floatTy.getWidth();
+ // Bit size of fir.logical and fir.char depends on the kind map which is not
+ // available in the verifier without an expensive lookup.
+ return std::nullopt;
+}
+
+llvm::LogicalResult fir::BitcastOp::verify() {
+ mlir::Type inType = getValue().getType();
+ mlir::Type outType = getType();
+ if (!isBitcastCompatibleType(inType))
+ return emitOpError("input type is not bitcast compatible: ") << inType;
+ if (!isBitcastCompatibleType(outType))
+ return emitOpError("output type is not bitcast compatible: ") << outType;
+ auto inBits = getBitcastBitSize(inType);
+ auto outBits = getBitcastBitSize(outType);
+ if (inBits && outBits && *inBits != *outBits)
+ return emitOpError("bit size mismatch: input has ")
+ << *inBits << " bits but output has " << *outBits << " bits";
+ return mlir::success();
+}
+
+mlir::OpFoldResult fir::BitcastOp::fold(FoldAdaptor adaptor) {
+ if (getValue().getType() == getType())
+ return getValue();
+ if (auto inner = getValue().getDefiningOp<fir::BitcastOp>()) {
+ if (inner.getValue().getType() == getType())
+ return inner.getValue();
+ getValueMutable().assign(inner.getValue());
+ return getResult();
+ }
+ return {};
+}
+
//===----------------------------------------------------------------------===//
// CmpcOp
//===----------------------------------------------------------------------===//
diff --git a/flang/test/Fir/bitcast-fold.fir b/flang/test/Fir/bitcast-fold.fir
new file mode 100644
index 0000000000000..18b7a5e2e3daa
--- /dev/null
+++ b/flang/test/Fir/bitcast-fold.fir
@@ -0,0 +1,26 @@
+// RUN: fir-opt --canonicalize %s | FileCheck %s
+
+// CHECK-LABEL: @bitcast_roundtrip_i32_logical
+func.func @bitcast_roundtrip_i32_logical(%x : i32) -> i32 {
+ %0 = fir.bitcast %x : (i32) -> !fir.logical<4>
+ %1 = fir.bitcast %0 : (!fir.logical<4>) -> i32
+ // CHECK-NEXT: return %{{.*}} : i32
+ return %1 : i32
+}
+
+// CHECK-LABEL: @bitcast_roundtrip_f32_i32
+func.func @bitcast_roundtrip_f32_i32(%x : f32) -> f32 {
+ %0 = fir.bitcast %x : (f32) -> i32
+ %1 = fir.bitcast %0 : (i32) -> f32
+ // CHECK-NEXT: return %{{.*}} : f32
+ return %1 : f32
+}
+
+// CHECK-LABEL: @bitcast_chain_collapse
+func.func @bitcast_chain_collapse(%x : i32) -> !fir.logical<4> {
+ %0 = fir.bitcast %x : (i32) -> f32
+ %1 = fir.bitcast %0 : (f32) -> !fir.logical<4>
+ // CHECK-NEXT: %[[RES:.*]] = fir.bitcast %{{.*}} : (i32) -> !fir.logical<4>
+ // CHECK-NEXT: return %[[RES]]
+ return %1 : !fir.logical<4>
+}
diff --git a/flang/test/Fir/convert-to-llvm.fir b/flang/test/Fir/convert-to-llvm.fir
index a52043c1bdc63..0866b1daef07a 100644
--- a/flang/test/Fir/convert-to-llvm.fir
+++ b/flang/test/Fir/convert-to-llvm.fir
@@ -2986,3 +2986,126 @@ func.func @test_load_box_alignment(%addr : !fir.ref<!fir.box<!fir.array<10xf32>>
}
// CHECK-LABEL: llvm.func @test_load_box_alignment(
// CHECK: "llvm.intr.memcpy"(%{{.*}}, %[[arg0]], %{{.*}}) <{arg_attrs = [{llvm.align = 8 : i64}, {llvm.align = 8 : i64}, {}], isVolatile = false}>
+
+// -----
+
+// Test `fir.bitcast` operation conversion.
+
+func.func @bitcast_i32_to_f32(%arg0 : i32) -> f32 {
+ %0 = fir.bitcast %arg0 : (i32) -> f32
+ return %0 : f32
+}
+
+// CHECK-LABEL: llvm.func @bitcast_i32_to_f32(
+// CHECK-SAME: %[[ARG0:.*]]: i32) -> f32
+// CHECK: %[[RES:.*]] = llvm.bitcast %[[ARG0]] : i32 to f32
+// CHECK: llvm.return %[[RES]] : f32
+
+// -----
+
+func.func @bitcast_f32_to_i32(%arg0 : f32) -> i32 {
+ %0 = fir.bitcast %arg0 : (f32) -> i32
+ return %0 : i32
+}
+
+// CHECK-LABEL: llvm.func @bitcast_f32_to_i32(
+// CHECK-SAME: %[[ARG0:.*]]: f32) -> i32
+// CHECK: %[[RES:.*]] = llvm.bitcast %[[ARG0]] : f32 to i32
+// CHECK: llvm.return %[[RES]] : i32
+
+// -----
+
+func.func @bitcast_i32_to_logical(%arg0 : i32) -> !fir.logical<4> {
+ %0 = fir.bitcast %arg0 : (i32) -> !fir.logical<4>
+ return %0 : !fir.logical<4>
+}
+
+// CHECK-LABEL: llvm.func @bitcast_i32_to_logical(
+// CHECK-SAME: %[[ARG0:.*]]: i32) -> i32
+// CHECK: llvm.return %[[ARG0]] : i32
+
+// -----
+
+func.func @bitcast_logical_to_i32(%arg0 : !fir.logical<4>) -> i32 {
+ %0 = fir.bitcast %arg0 : (!fir.logical<4>) -> i32
+ return %0 : i32
+}
+
+// CHECK-LABEL: llvm.func @bitcast_logical_to_i32(
+// CHECK-SAME: %[[ARG0:.*]]: i32) -> i32
+// CHECK: llvm.return %[[ARG0]] : i32
+
+// -----
+
+func.func @bitcast_logical_to_f32(%arg0 : !fir.logical<4>) -> f32 {
+ %0 = fir.bitcast %arg0 : (!fir.logical<4>) -> f32
+ return %0 : f32
+}
+
+// CHECK-LABEL: llvm.func @bitcast_logical_to_f32(
+// CHECK-SAME: %[[ARG0:.*]]: i32) -> f32
+// CHECK: %[[RES:.*]] = llvm.bitcast %[[ARG0]] : i32 to f32
+// CHECK: llvm.return %[[RES]] : f32
+
+// -----
+
+func.func @bitcast_f32_to_logical(%arg0 : f32) -> !fir.logical<4> {
+ %0 = fir.bitcast %arg0 : (f32) -> !fir.logical<4>
+ return %0 : !fir.logical<4>
+}
+
+// CHECK-LABEL: llvm.func @bitcast_f32_to_logical(
+// CHECK-SAME: %[[ARG0:.*]]: f32) -> i32
+// CHECK: %[[RES:.*]] = llvm.bitcast %[[ARG0]] : f32 to i32
+// CHECK: llvm.return %[[RES]] : i32
+
+// -----
+
+func.func @bitcast_char1_to_i8(%arg0 : !fir.char<1>) -> i8 {
+ %0 = fir.bitcast %arg0 : (!fir.char<1>) -> i8
+ return %0 : i8
+}
+
+// CHECK-LABEL: llvm.func @bitcast_char1_to_i8(
+// CHECK-SAME: %[[ARG0:.*]]: !llvm.array<1 x i8>) -> i8
+// CHECK: %[[EXT:.*]] = llvm.extractvalue %[[ARG0]][0] : !llvm.array<1 x i8>
+// CHECK: llvm.return %[[EXT]] : i8
+
+// -----
+
+func.func @bitcast_i8_to_char1(%arg0 : i8) -> !fir.char<1> {
+ %0 = fir.bitcast %arg0 : (i8) -> !fir.char<1>
+ return %0 : !fir.char<1>
+}
+
+// CHECK-LABEL: llvm.func @bitcast_i8_to_char1(
+// CHECK-SAME: %[[ARG0:.*]]: i8) -> !llvm.array<1 x i8>
+// CHECK: %[[UNDEF:.*]] = llvm.mlir.undef : !llvm.array<1 x i8>
+// CHECK: %[[INS:.*]] = llvm.insertvalue %[[ARG0]], %[[UNDEF]][0] : !llvm.array<1 x i8>
+// CHECK: llvm.return %[[INS]] : !llvm.array<1 x i8>
+
+// -----
+
+func.func @bitcast_f32_to_char4(%arg0 : f32) -> !fir.char<4> {
+ %0 = fir.bitcast %arg0 : (f32) -> !fir.char<4>
+ return %0 : !fir.char<4>
+}
+
+// CHECK-LABEL: llvm.func @bitcast_f32_to_char4(
+// CHECK-SAME: %[[ARG0:.*]]: f32) -> !llvm.array<1 x i32>
+// CHECK: %[[BC:.*]] = llvm.bitcast %[[ARG0]] : f32 to i32
+// CHECK: %[[UNDEF:.*]] = llvm.mlir.undef : !llvm.array<1 x i32>
+// CHECK: %[[INS:.*]] = llvm.insertvalue %[[BC]], %[[UNDEF]][0] : !llvm.array<1 x i32>
+// CHECK: llvm.return %[[INS]] : !llvm.array<1 x i32>
+
+// -----
+
+func.func @bitcast_f64_to_i64(%arg0 : f64) -> i64 {
+ %0 = fir.bitcast %arg0 : (f64) -> i64
+ return %0 : i64
+}
+
+// CHECK-LABEL: llvm.func @bitcast_f64_to_i64(
+// CHECK-SAME: %[[ARG0:.*]]: f64) -> i64
+// CHECK: %[[RES:.*]] = llvm.bitcast %[[ARG0]] : f64 to i64
+// CHECK: llvm.return %[[RES]] : i64
diff --git a/flang/test/Fir/fir-ops.fir b/flang/test/Fir/fir-ops.fir
index 51c064290de35..c9ba08a5a47c8 100644
--- a/flang/test/Fir/fir-ops.fir
+++ b/flang/test/Fir/fir-ops.fir
@@ -1060,3 +1060,27 @@ func.func @test_declare_value(%arg0: i32, %scope: !fir.dscope) {
fir.declare_value %arg0 dummy_scope %scope arg 1 {uniq_name = "test"} : i32
return
}
+
+// CHECK-LABEL: func @test_bitcast(
+// CHECK-SAME: %[[I32:.*]]: i32, %[[F32:.*]]: f32, %[[LOG:.*]]: !fir.logical<4>, %[[CH1:.*]]: !fir.char<1>, %[[CH2:.*]]: !fir.char<2>) {
+func.func @test_bitcast(%arg0: i32, %arg1: f32, %arg2: !fir.logical<4>, %arg3: !fir.char<1>, %arg4: !fir.char<2>) {
+// CHECK: %{{.*}} = fir.bitcast %[[I32]] : (i32) -> f32
+ %0 = fir.bitcast %arg0 : (i32) -> f32
+// CHECK: %{{.*}} = fir.bitcast %[[F32]] : (f32) -> i32
+ %1 = fir.bitcast %arg1 : (f32) -> i32
+// CHECK: %{{.*}} = fir.bitcast %[[I32]] : (i32) -> !fir.logical<4>
+ %2 = fir.bitcast %arg0 : (i32) -> !fir.logical<4>
+// CHECK: %{{.*}} = fir.bitcast %[[LOG]] : (!fir.logical<4>) -> i32
+ %3 = fir.bitcast %arg2 : (!fir.logical<4>) -> i32
+// CHECK: %{{.*}} = fir.bitcast %[[LOG]] : (!fir.logical<4>) -> f32
+ %4 = fir.bitcast %arg2 : (!fir.logical<4>) -> f32
+// CHECK: %{{.*}} = fir.bitcast %[[F32]] : (f32) -> !fir.logical<4>
+ %5 = fir.bitcast %arg1 : (f32) -> !fir.logical<4>
+// CHECK: %{{.*}} = fir.bitcast %[[CH1]] : (!fir.char<1>) -> i8
+ %6 = fir.bitcast %arg3 : (!fir.char<1>) -> i8
+// CHECK: %{{.*}} = fir.bitcast %[[CH2]] : (!fir.char<2>) -> i16
+ %7 = fir.bitcast %arg4 : (!fir.char<2>) -> i16
+// CHECK: %{{.*}} = fir.bitcast %[[I32]] : (i32) -> !fir.logical<4>
+ %8 = fir.bitcast %arg0 : (i32) -> !fir.logical<4>
+ return
+}
diff --git a/flang/test/Fir/invalid.fir b/flang/test/Fir/invalid.fir
index e37e8ec77fcb7..9ab5e4f8553d1 100644
--- a/flang/test/Fir/invalid.fir
+++ b/flang/test/Fir/invalid.fir
@@ -1491,3 +1491,51 @@ func.func @bad_declare_value(%arg0: !fir.ref<i32>) {
fir.declare_value %arg0 {uniq_name = "test"} : !fir.ref<i32>
return
}
+
+// -----
+
+func.func @bitcast_pointer_input(%arg0: !fir.ref<i32>) -> i32 {
+ // expected-error at +1 {{'fir.bitcast' op input type is not bitcast compatible}}
+ %0 = fir.bitcast %arg0 : (!fir.ref<i32>) -> i32
+ return %0 : i32
+}
+
+// -----
+
+func.func @bitcast_pointer_output(%arg0: i32) -> !fir.ref<i32> {
+ // expected-error at +1 {{'fir.bitcast' op output type is not bitcast compatible}}
+ %0 = fir.bitcast %arg0 : (i32) -> !fir.ref<i32>
+ return %0 : !fir.ref<i32>
+}
+
+// -----
+
+func.func @bitcast_complex(%arg0: complex<f32>) -> i64 {
+ // expected-error at +1 {{'fir.bitcast' op input type is not bitcast compatible}}
+ %0 = fir.bitcast %arg0 : (complex<f32>) -> i64
+ return %0 : i64
+}
+
+// -----
+
+func.func @bitcast_char_not_singleton(%arg0: !fir.char<1,2>) -> i16 {
+ // expected-error at +1 {{'fir.bitcast' op input type is not bitcast compatible}}
+ %0 = fir.bitcast %arg0 : (!fir.char<1,2>) -> i16
+ return %0 : i16
+}
+
+// -----
+
+func.func @bitcast_box(%arg0: !fir.box<!fir.type<t{i:i32}>>) -> i64 {
+ // expected-error at +1 {{'fir.bitcast' op input type is not bitcast compatible}}
+ %0 = fir.bitcast %arg0 : (!fir.box<!fir.type<t{i:i32}>>) -> i64
+ return %0 : i64
+}
+
+// -----
+
+func.func @bitcast_size_mismatch(%arg0: i32) -> f64 {
+ // expected-error at +1 {{'fir.bitcast' op bit size mismatch: input has 32 bits but output has 64 bits}}
+ %0 = fir.bitcast %arg0 : (i32) -> f64
+ return %0 : f64
+}
More information about the flang-commits
mailing list