[Mlir-commits] [mlir] [mlir][arith] Add support for `fptosi`, `fptoui` to `ArithToAPFloat` (PR #169277)
Matthias Springer
llvmlistbot at llvm.org
Mon Nov 24 17:50:11 PST 2025
https://github.com/matthias-springer updated https://github.com/llvm/llvm-project/pull/169277
>From d9db8f9c4196c4547edad57d2b535d819cea8e95 Mon Sep 17 00:00:00 2001
From: Matthias Springer <me at m-sp.org>
Date: Mon, 24 Nov 2025 04:19:59 +0000
Subject: [PATCH 1/2] [mlir][arith] Add support for `fptosi`, `fptoui` to
`ArithToAPFloat`
---
.../ArithToAPFloat/ArithToAPFloat.cpp | 58 +++++++++++++++++++
mlir/lib/ExecutionEngine/APFloatWrappers.cpp | 14 +++++
.../ArithToApfloat/arith-to-apfloat.mlir | 26 +++++++++
.../Arith/CPU/test-apfloat-emulation.mlir | 10 ++++
4 files changed, 108 insertions(+)
diff --git a/mlir/lib/Conversion/ArithToAPFloat/ArithToAPFloat.cpp b/mlir/lib/Conversion/ArithToAPFloat/ArithToAPFloat.cpp
index 9aee85bc7e9ea..a41fec8bc73d5 100644
--- a/mlir/lib/Conversion/ArithToAPFloat/ArithToAPFloat.cpp
+++ b/mlir/lib/Conversion/ArithToAPFloat/ArithToAPFloat.cpp
@@ -185,6 +185,60 @@ struct FpToFpConversion final : OpRewritePattern<OpTy> {
SymbolOpInterface symTable;
};
+template <typename OpTy>
+struct FpToIntConversion final : OpRewritePattern<OpTy> {
+ FpToIntConversion(MLIRContext *context, SymbolOpInterface symTable,
+ bool isUnsigned, PatternBenefit benefit = 1)
+ : OpRewritePattern<OpTy>(context, benefit), symTable(symTable),
+ isUnsigned(isUnsigned){};
+
+ LogicalResult matchAndRewrite(OpTy op,
+ PatternRewriter &rewriter) const override {
+ // Get APFloat function from runtime library.
+ auto i1Type = IntegerType::get(symTable->getContext(), 1);
+ auto i32Type = IntegerType::get(symTable->getContext(), 32);
+ auto i64Type = IntegerType::get(symTable->getContext(), 64);
+ FailureOr<FuncOp> fn =
+ lookupOrCreateApFloatFn(rewriter, symTable, "convert_to_int",
+ {i32Type, i32Type, i1Type, i64Type});
+ if (failed(fn))
+ return fn;
+
+ rewriter.setInsertionPoint(op);
+ // Cast operands to 64-bit integers.
+ Location loc = op.getLoc();
+ auto inFloatTy = cast<FloatType>(op.getOperand().getType());
+ auto inIntWType = rewriter.getIntegerType(inFloatTy.getWidth());
+ auto int64Type = rewriter.getI64Type();
+ Value operandBits = arith::ExtUIOp::create(
+ rewriter, loc, int64Type,
+ arith::BitcastOp::create(rewriter, loc, inIntWType, op.getOperand()));
+
+ // Call APFloat function.
+ Value inSemValue = getSemanticsValue(rewriter, loc, inFloatTy);
+ auto outIntTy = cast<IntegerType>(op.getType());
+ Value outWidthValue = arith::ConstantOp::create(
+ rewriter, loc, i32Type,
+ rewriter.getIntegerAttr(i32Type, outIntTy.getWidth()));
+ Value isUnsignedValue = arith::ConstantOp::create(
+ rewriter, loc, i1Type, rewriter.getIntegerAttr(i1Type, isUnsigned));
+ SmallVector<Value> params = {inSemValue, outWidthValue, isUnsignedValue,
+ operandBits};
+ auto resultOp =
+ func::CallOp::create(rewriter, loc, TypeRange(rewriter.getI64Type()),
+ SymbolRefAttr::get(*fn), params);
+
+ // Truncate result to the original width.
+ Value truncatedBits = arith::TruncIOp::create(rewriter, loc, outIntTy,
+ resultOp->getResult(0));
+ rewriter.replaceOp(op, truncatedBits);
+ return success();
+ }
+
+ SymbolOpInterface symTable;
+ bool isUnsigned;
+};
+
namespace {
struct ArithToAPFloatConversionPass final
: impl::ArithToAPFloatConversionPassBase<ArithToAPFloatConversionPass> {
@@ -209,6 +263,10 @@ void ArithToAPFloatConversionPass::runOnOperation() {
patterns
.add<FpToFpConversion<arith::ExtFOp>, FpToFpConversion<arith::TruncFOp>>(
context, getOperation());
+ patterns.add<FpToIntConversion<arith::FPToSIOp>>(context, getOperation(),
+ /*isUnsigned=*/false);
+ patterns.add<FpToIntConversion<arith::FPToUIOp>>(context, getOperation(),
+ /*isUnsigned=*/true);
LogicalResult result = success();
ScopedDiagnosticHandler scopedHandler(context, [&result](Diagnostic &diag) {
if (diag.getSeverity() == DiagnosticSeverity::Error) {
diff --git a/mlir/lib/ExecutionEngine/APFloatWrappers.cpp b/mlir/lib/ExecutionEngine/APFloatWrappers.cpp
index 511b05ea380f0..632fe9cf2269d 100644
--- a/mlir/lib/ExecutionEngine/APFloatWrappers.cpp
+++ b/mlir/lib/ExecutionEngine/APFloatWrappers.cpp
@@ -20,6 +20,7 @@
// APFloatBase::Semantics enum value.
//
#include "llvm/ADT/APFloat.h"
+#include "llvm/ADT/APSInt.h"
#ifdef _WIN32
#ifndef MLIR_APFLOAT_WRAPPERS_EXPORT
@@ -101,4 +102,17 @@ _mlir_apfloat_convert(int32_t inSemantics, int32_t outSemantics, uint64_t a) {
llvm::APInt result = val.bitcastToAPInt();
return result.getZExtValue();
}
+
+MLIR_APFLOAT_WRAPPERS_EXPORT uint64_t _mlir_apfloat_convert_to_int(
+ int32_t semantics, int32_t resultWidth, bool isUnsigned, uint64_t a) {
+ const llvm::fltSemantics &sem = llvm::APFloatBase::EnumToSemantics(
+ static_cast<llvm::APFloatBase::Semantics>(semantics));
+ unsigned inputWidth = llvm::APFloatBase::semanticsSizeInBits(sem);
+ llvm::APFloat val(sem, llvm::APInt(inputWidth, a));
+ llvm::APSInt result(resultWidth, isUnsigned);
+ bool isExact;
+ // TODO: Custom rounding modes are not supported yet.
+ val.convertToInteger(result, llvm::RoundingMode::NearestTiesToEven, &isExact);
+ return result.getZExtValue();
+}
}
diff --git a/mlir/test/Conversion/ArithToApfloat/arith-to-apfloat.mlir b/mlir/test/Conversion/ArithToApfloat/arith-to-apfloat.mlir
index 038acbfc965a2..f1acfd5e5618a 100644
--- a/mlir/test/Conversion/ArithToApfloat/arith-to-apfloat.mlir
+++ b/mlir/test/Conversion/ArithToApfloat/arith-to-apfloat.mlir
@@ -148,3 +148,29 @@ func.func @truncf(%arg0: bf16) {
%0 = arith.truncf %arg0 : bf16 to f4E2M1FN
return
}
+
+// -----
+
+// CHECK: func.func private @_mlir_apfloat_convert_to_int(i32, i32, i1, i64) -> i64
+// CHECK: %[[sem_in:.*]] = arith.constant 0 : i32
+// CHECK: %[[out_width:.*]] = arith.constant 4 : i32
+// CHECK: %[[is_unsigned:.*]] = arith.constant false
+// CHECK: %[[res:.*]] = call @_mlir_apfloat_convert_to_int(%[[sem_in]], %[[out_width]], %[[is_unsigned]], %{{.*}}) : (i32, i32, i1, i64) -> i64
+// CHECK: arith.trunci %[[res]] : i64 to i4
+func.func @fptosi(%arg0: f16) {
+ %0 = arith.fptosi %arg0 : f16 to i4
+ return
+}
+
+// -----
+
+// CHECK: func.func private @_mlir_apfloat_convert_to_int(i32, i32, i1, i64) -> i64
+// CHECK: %[[sem_in:.*]] = arith.constant 0 : i32
+// CHECK: %[[out_width:.*]] = arith.constant 4 : i32
+// CHECK: %[[is_unsigned:.*]] = arith.constant true
+// CHECK: %[[res:.*]] = call @_mlir_apfloat_convert_to_int(%[[sem_in]], %[[out_width]], %[[is_unsigned]], %{{.*}}) : (i32, i32, i1, i64) -> i64
+// CHECK: arith.trunci %[[res]] : i64 to i4
+func.func @fptoui(%arg0: f16) {
+ %0 = arith.fptoui %arg0 : f16 to i4
+ return
+}
diff --git a/mlir/test/Integration/Dialect/Arith/CPU/test-apfloat-emulation.mlir b/mlir/test/Integration/Dialect/Arith/CPU/test-apfloat-emulation.mlir
index 51976434d2be2..5e93945c3eb60 100644
--- a/mlir/test/Integration/Dialect/Arith/CPU/test-apfloat-emulation.mlir
+++ b/mlir/test/Integration/Dialect/Arith/CPU/test-apfloat-emulation.mlir
@@ -43,5 +43,15 @@ func.func @entry() {
%cvt = arith.truncf %b2 : f32 to f8E4M3FN
vector.print %cvt : f8E4M3FN
+ // CHECK-NEXT: 1
+ // Bit pattern: 01, interpreted as signed integer: 1
+ %cvt_int_signed = arith.fptosi %cvt : f8E4M3FN to i2
+ vector.print %cvt_int_signed : i2
+
+ // CHECK-NEXT: -2
+ // Bit pattern: 10, interpreted as signed integer: -2
+ %cvt_int_unsigned = arith.fptoui %cvt : f8E4M3FN to i2
+ vector.print %cvt_int_unsigned : i2
+
return
}
>From 790e4bc38ed60f8d6a34a4f07c390e8f1d353cc5 Mon Sep 17 00:00:00 2001
From: Matthias Springer <me at m-sp.org>
Date: Tue, 25 Nov 2025 01:23:27 +0000
Subject: [PATCH 2/2] address comments
---
.../lib/Conversion/ArithToAPFloat/ArithToAPFloat.cpp | 12 +++++++-----
mlir/lib/ExecutionEngine/APFloatWrappers.cpp | 4 ++++
2 files changed, 11 insertions(+), 5 deletions(-)
diff --git a/mlir/lib/Conversion/ArithToAPFloat/ArithToAPFloat.cpp b/mlir/lib/Conversion/ArithToAPFloat/ArithToAPFloat.cpp
index a41fec8bc73d5..1de67d0fd184c 100644
--- a/mlir/lib/Conversion/ArithToAPFloat/ArithToAPFloat.cpp
+++ b/mlir/lib/Conversion/ArithToAPFloat/ArithToAPFloat.cpp
@@ -159,9 +159,8 @@ struct FpToFpConversion final : OpRewritePattern<OpTy> {
Location loc = op.getLoc();
auto inFloatTy = cast<FloatType>(op.getOperand().getType());
auto inIntWType = rewriter.getIntegerType(inFloatTy.getWidth());
- auto int64Type = rewriter.getI64Type();
Value operandBits = arith::ExtUIOp::create(
- rewriter, loc, int64Type,
+ rewriter, loc, i64Type,
arith::BitcastOp::create(rewriter, loc, inIntWType, op.getOperand()));
// Call APFloat function.
@@ -190,10 +189,14 @@ struct FpToIntConversion final : OpRewritePattern<OpTy> {
FpToIntConversion(MLIRContext *context, SymbolOpInterface symTable,
bool isUnsigned, PatternBenefit benefit = 1)
: OpRewritePattern<OpTy>(context, benefit), symTable(symTable),
- isUnsigned(isUnsigned){};
+ isUnsigned(isUnsigned) {}
LogicalResult matchAndRewrite(OpTy op,
PatternRewriter &rewriter) const override {
+ if (op.getType().getIntOrFloatBitWidth() > 64)
+ return rewriter.notifyMatchFailure(
+ op, "result type > 64 bits is not supported");
+
// Get APFloat function from runtime library.
auto i1Type = IntegerType::get(symTable->getContext(), 1);
auto i32Type = IntegerType::get(symTable->getContext(), 32);
@@ -209,9 +212,8 @@ struct FpToIntConversion final : OpRewritePattern<OpTy> {
Location loc = op.getLoc();
auto inFloatTy = cast<FloatType>(op.getOperand().getType());
auto inIntWType = rewriter.getIntegerType(inFloatTy.getWidth());
- auto int64Type = rewriter.getI64Type();
Value operandBits = arith::ExtUIOp::create(
- rewriter, loc, int64Type,
+ rewriter, loc, i64Type,
arith::BitcastOp::create(rewriter, loc, inIntWType, op.getOperand()));
// Call APFloat function.
diff --git a/mlir/lib/ExecutionEngine/APFloatWrappers.cpp b/mlir/lib/ExecutionEngine/APFloatWrappers.cpp
index 632fe9cf2269d..8b89c43446765 100644
--- a/mlir/lib/ExecutionEngine/APFloatWrappers.cpp
+++ b/mlir/lib/ExecutionEngine/APFloatWrappers.cpp
@@ -113,6 +113,10 @@ MLIR_APFLOAT_WRAPPERS_EXPORT uint64_t _mlir_apfloat_convert_to_int(
bool isExact;
// TODO: Custom rounding modes are not supported yet.
val.convertToInteger(result, llvm::RoundingMode::NearestTiesToEven, &isExact);
+ // This function always returns uint64_t, regardless of the desired result
+ // width. It does not matter whether we zero-extend or sign-extend the APSInt
+ // to 64 bits because the generated IR in arith-to-apfloat will truncate the
+ // result to the desired result width.
return result.getZExtValue();
}
}
More information about the Mlir-commits
mailing list