[flang-commits] [flang] [flang] Use saturated intrinsic for floating point conversions (PR #130686)

via flang-commits flang-commits at lists.llvm.org
Mon Mar 10 16:46:46 PDT 2025


llvmbot wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-flang-codegen

Author: Asher Mancinelli (ashermancinelli)

<details>
<summary>Changes</summary>

The saturated floating point conversion intrinsics more closely match the semantics in the standard.

Case 2 of 16.9.100 is

> INT (A [, KIND])
> If A is of type real, there are two cases: if |A| < 1, INT (A) has the value 0; if |A| ≥ 1, INT (A) is the integer whose magnitude is the largest integer that does not exceed the magnitude of A and whose sign is the same as the sign of A.

Currently, converting a floating point value into an integer type too small to hold the constant will be converted to poison in opt, leaving us with garbage:

```
> cat t.f90
program main
  real(kind=16)   :: f
  integer(kind=4) :: i
  f=huge(f)
  i=f
  print *, i
end program main

# current upstream
> for i in `seq 10`; do; ./a.out; done
 -862156992
 -1497393344
 -739096768
 -1649494208
 1761228608
 -1959270592
 -746244288
 -1629194432
 -231217344
 382322496
```

With the saturated fptoui/fptosi intrinsics, we get the appropriate values

```
# mine
> flang -O2 ./t.f90 && ./a.out
 2147483647

> perl -e 'printf "%d\n", (2 ** 31) - 1'
2147483647
```

One notable difference: NaNs being converted to ints will become zero, unlike current flang (and other compilers).

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


3 Files Affected:

- (modified) flang/lib/Optimizer/CodeGen/CodeGen.cpp (+14-4) 
- (modified) flang/test/Fir/convert-to-llvm.fir (+13-5) 
- (added) flang/test/Integration/fp-convert.f90 (+236) 


``````````diff
diff --git a/flang/lib/Optimizer/CodeGen/CodeGen.cpp b/flang/lib/Optimizer/CodeGen/CodeGen.cpp
index a2743edd7844a..2302c08fae508 100644
--- a/flang/lib/Optimizer/CodeGen/CodeGen.cpp
+++ b/flang/lib/Optimizer/CodeGen/CodeGen.cpp
@@ -835,10 +835,20 @@ struct ConvertOpConversion : public fir::FIROpConversion<fir::ConvertOp> {
         return mlir::success();
       }
       if (mlir::isa<mlir::IntegerType>(toTy)) {
-        if (toTy.isUnsignedInteger())
-          rewriter.replaceOpWithNewOp<mlir::LLVM::FPToUIOp>(convert, toTy, op0);
-        else
-          rewriter.replaceOpWithNewOp<mlir::LLVM::FPToSIOp>(convert, toTy, op0);
+        // NOTE: We are checking the fir type here because toTy is an LLVM type
+        // which is signless, and we need to use the intrinsic that matches the
+        // sign of the output in fir.
+        if (toFirTy.isUnsignedInteger()) {
+          auto intrinsicName =
+              mlir::StringAttr::get(convert.getContext(), "llvm.fptoui.sat");
+          rewriter.replaceOpWithNewOp<mlir::LLVM::CallIntrinsicOp>(
+              convert, toTy, intrinsicName, op0);
+        } else {
+          auto intrinsicName =
+              mlir::StringAttr::get(convert.getContext(), "llvm.fptosi.sat");
+          rewriter.replaceOpWithNewOp<mlir::LLVM::CallIntrinsicOp>(
+              convert, toTy, intrinsicName, op0);
+        }
         return mlir::success();
       }
     } else if (mlir::isa<mlir::IntegerType>(fromTy)) {
diff --git a/flang/test/Fir/convert-to-llvm.fir b/flang/test/Fir/convert-to-llvm.fir
index c7037019ee701..2960528fb6c24 100644
--- a/flang/test/Fir/convert-to-llvm.fir
+++ b/flang/test/Fir/convert-to-llvm.fir
@@ -701,6 +701,10 @@ func.func @convert_from_float(%arg0 : f32) {
   %7 = fir.convert %arg0 : (f32) -> i16
   %8 = fir.convert %arg0 : (f32) -> i32
   %9 = fir.convert %arg0 : (f32) -> i64
+  %10 = fir.convert %arg0 : (f32) -> ui8
+  %11 = fir.convert %arg0 : (f32) -> ui16
+  %12 = fir.convert %arg0 : (f32) -> ui32
+  %13 = fir.convert %arg0 : (f32) -> ui64
   return
 }
 
@@ -711,11 +715,15 @@ func.func @convert_from_float(%arg0 : f32) {
 // CHECK:         %{{.*}} = llvm.fpext %[[ARG0]] : f32 to f64
 // CHECK:         %{{.*}} = llvm.fpext %[[ARG0]] : f32 to f80
 // CHECK:         %{{.*}} = llvm.fpext %[[ARG0]] : f32 to f128
-// CHECK:         %{{.*}} = llvm.fptosi %[[ARG0]] : f32 to i1
-// CHECK:         %{{.*}} = llvm.fptosi %[[ARG0]] : f32 to i8
-// CHECK:         %{{.*}} = llvm.fptosi %[[ARG0]] : f32 to i16
-// CHECK:         %{{.*}} = llvm.fptosi %[[ARG0]] : f32 to i32
-// CHECK:         %{{.*}} = llvm.fptosi %[[ARG0]] : f32 to i64
+// CHECK:         %{{.*}} = llvm.call_intrinsic "llvm.fptosi.sat"(%[[ARG0]]) : (f32) -> i1
+// CHECK:         %{{.*}} = llvm.call_intrinsic "llvm.fptosi.sat"(%[[ARG0]]) : (f32) -> i8
+// CHECK:         %{{.*}} = llvm.call_intrinsic "llvm.fptosi.sat"(%[[ARG0]]) : (f32) -> i16
+// CHECK:         %{{.*}} = llvm.call_intrinsic "llvm.fptosi.sat"(%[[ARG0]]) : (f32) -> i32
+// CHECK:         %{{.*}} = llvm.call_intrinsic "llvm.fptosi.sat"(%[[ARG0]]) : (f32) -> i64
+// CHECK:         %{{.*}} = llvm.call_intrinsic "llvm.fptoui.sat"(%[[ARG0]]) : (f32) -> i8
+// CHECK:         %{{.*}} = llvm.call_intrinsic "llvm.fptoui.sat"(%[[ARG0]]) : (f32) -> i16
+// CHECK:         %{{.*}} = llvm.call_intrinsic "llvm.fptoui.sat"(%[[ARG0]]) : (f32) -> i32
+// CHECK:         %{{.*}} = llvm.call_intrinsic "llvm.fptoui.sat"(%[[ARG0]]) : (f32) -> i64
 
 // -----
 
diff --git a/flang/test/Integration/fp-convert.f90 b/flang/test/Integration/fp-convert.f90
new file mode 100644
index 0000000000000..a042b28827b9a
--- /dev/null
+++ b/flang/test/Integration/fp-convert.f90
@@ -0,0 +1,236 @@
+! RUN: %flang -funsigned %s -o %t && %t | FileCheck %s
+! RUN: %flang -funsigned -emit-llvm -S -o - %s | FileCheck %s --check-prefix=LLVMIR
+
+module fp_convert_m
+  implicit none
+  interface set_and_print
+    module procedure set_and_print_r16
+    module procedure set_and_print_r8
+  end interface
+contains
+  subroutine set_and_print_r16(value)
+    real(kind=16), intent(in) :: value
+    integer(kind=1) :: i8
+    integer(kind=2) :: i16
+    integer(kind=4) :: i32
+    integer(kind=8) :: i64
+    integer(kind=16) :: i128
+    unsigned(kind=1) :: u8
+    unsigned(kind=2) :: u16
+    unsigned(kind=4) :: u32
+    unsigned(kind=8) :: u64
+    unsigned(kind=16) :: u128
+    print *, "Original real(16) value:", value
+    i8 = int(value, kind=1)
+    i16 = int(value, kind=2)
+    i32 = int(value, kind=4)
+    i64 = int(value, kind=8)
+    i128 = int(value, kind=16)
+    u8 = uint(value, kind=1)
+    u16 = uint(value, kind=2)
+    u32 = uint(value, kind=4)
+    u64 = uint(value, kind=8)
+    u128 = uint(value, kind=16)
+    print *, "Converted to 8-bit integer:", i8
+    print *, "Converted to 16-bit integer:", i16
+    print *, "Converted to 32-bit integer:", i32
+    print *, "Converted to 64-bit integer:", i64
+    print *, "Converted to 128-bit integer:", i128
+    print *, "Converted to 8-bit unsigned integer:", u8
+    print *, "Converted to 16-bit unsigned integer:", u16
+    print *, "Converted to 32-bit unsigned integer:", u32
+    print *, "Converted to 64-bit unsigned integer:", u64
+    print *, "Converted to 128-bit unsigned integer:", u128
+  end subroutine
+
+  subroutine set_and_print_r8(value)
+    real(kind=8), intent(in) :: value
+    integer(kind=1) :: i8
+    integer(kind=2) :: i16
+    integer(kind=4) :: i32
+    integer(kind=8) :: i64
+    integer(kind=16) :: i128
+    unsigned(kind=1) :: u8
+    unsigned(kind=2) :: u16
+    unsigned(kind=4) :: u32
+    unsigned(kind=8) :: u64
+    unsigned(kind=16) :: u128
+    print *, "Original real(8) value:", value
+    i8 = int(value, kind=1)
+    i16 = int(value, kind=2)
+    i32 = int(value, kind=4)
+    i64 = int(value, kind=8)
+    i128 = int(value, kind=16)
+    u8 = uint(value, kind=1)
+    u16 = uint(value, kind=2)
+    u32 = uint(value, kind=4)
+    u64 = uint(value, kind=8)
+    u128 = uint(value, kind=16)
+    print *, "Converted to 8-bit integer:", i8
+    print *, "Converted to 16-bit integer:", i16
+    print *, "Converted to 32-bit integer:", i32
+    print *, "Converted to 64-bit integer:", i64
+    print *, "Converted to 128-bit integer:", i128
+    print *, "Converted to 8-bit unsigned integer:", u8
+    print *, "Converted to 16-bit unsigned integer:", u16
+    print *, "Converted to 32-bit unsigned integer:", u32
+    print *, "Converted to 64-bit unsigned integer:", u64
+    print *, "Converted to 128-bit unsigned integer:", u128
+  end subroutine
+end module fp_convert_m
+
+program fp_convert
+  use ieee_arithmetic, only: ieee_value, ieee_quiet_nan, ieee_positive_inf, ieee_negative_inf
+  use fp_convert_m, only: set_and_print
+  implicit none
+
+  real(kind=8) :: nan, inf, ninf
+  nan = ieee_value(nan, ieee_quiet_nan)
+  inf = ieee_value(inf, ieee_positive_inf)
+  ninf = ieee_value(ninf, ieee_negative_inf)
+
+  call set_and_print(huge(0.0_8))
+  call set_and_print(-huge(0.0_8))
+  call set_and_print(huge(0.0_16))
+  call set_and_print(-huge(0.0_16))
+  call set_and_print(tiny(0.0_8))
+  call set_and_print(-tiny(0.0_8))
+  call set_and_print(tiny(0.0_16))
+  call set_and_print(-tiny(0.0_16))
+  call set_and_print(nan)
+  call set_and_print(inf)
+  call set_and_print(ninf)
+
+end program fp_convert
+
+! LLVMIR: call i8 @llvm.fptosi.sat.i8.f128(fp128 %{{.+}})
+! LLVMIR: call i16 @llvm.fptosi.sat.i16.f128(fp128 %{{.+}})
+! LLVMIR: call i32 @llvm.fptosi.sat.i32.f128(fp128 %{{.+}})
+! LLVMIR: call i64 @llvm.fptosi.sat.i64.f128(fp128 %{{.+}})
+! LLVMIR: call i128 @llvm.fptosi.sat.i128.f128(fp128 %{{.+}})
+! LLVMIR: call i8 @llvm.fptoui.sat.i8.f128(fp128 %{{.+}})
+! LLVMIR: call i16 @llvm.fptoui.sat.i16.f128(fp128 %{{.+}})
+! LLVMIR: call i32 @llvm.fptoui.sat.i32.f128(fp128 %{{.+}})
+! LLVMIR: call i64 @llvm.fptoui.sat.i64.f128(fp128 %{{.+}})
+! LLVMIR: call i128 @llvm.fptoui.sat.i128.f128(fp128 %{{.+}})
+! LLVMIR: call i8 @llvm.fptosi.sat.i8.f64(double %{{.+}})
+! LLVMIR: call i16 @llvm.fptosi.sat.i16.f64(double %{{.+}})
+! LLVMIR: call i32 @llvm.fptosi.sat.i32.f64(double %{{.+}})
+! LLVMIR: call i64 @llvm.fptosi.sat.i64.f64(double %{{.+}})
+! LLVMIR: call i128 @llvm.fptosi.sat.i128.f64(double %{{.+}})
+! LLVMIR: call i8 @llvm.fptoui.sat.i8.f64(double %{{.+}})
+! LLVMIR: call i16 @llvm.fptoui.sat.i16.f64(double %{{.+}})
+! LLVMIR: call i32 @llvm.fptoui.sat.i32.f64(double %{{.+}})
+! LLVMIR: call i64 @llvm.fptoui.sat.i64.f64(double %{{.+}})
+! LLVMIR: call i128 @llvm.fptoui.sat.i128.f64(double %{{.+}})
+
+! CHECK: Converted to 8-bit integer: 127
+! CHECK: Converted to 16-bit integer: 32767
+! CHECK: Converted to 32-bit integer: 2147483647
+! CHECK: Converted to 64-bit integer: 9223372036854775807
+! CHECK: Converted to 128-bit integer: 170141183460469231731687303715884105727
+! CHECK: Converted to 8-bit unsigned integer: 255
+! CHECK: Converted to 16-bit unsigned integer: 65535
+! CHECK: Converted to 32-bit unsigned integer: 4294967295
+! CHECK: Converted to 64-bit unsigned integer: 18446744073709551615
+! CHECK: Converted to 128-bit unsigned integer: 340282366920938463463374607431768211455
+! CHECK: Converted to 8-bit integer: -128
+! CHECK: Converted to 16-bit integer: -32768
+! CHECK: Converted to 32-bit integer: -2147483648
+! CHECK: Converted to 64-bit integer: -9223372036854775808
+! CHECK: Converted to 128-bit integer: -170141183460469231731687303715884105728
+! CHECK: Converted to 8-bit unsigned integer: 0
+! CHECK: Converted to 16-bit unsigned integer: 0
+! CHECK: Converted to 32-bit unsigned integer: 0
+! CHECK: Converted to 64-bit unsigned integer: 0
+! CHECK: Converted to 128-bit unsigned integer: 0
+! CHECK: Converted to 8-bit integer: 127
+! CHECK: Converted to 16-bit integer: 32767
+! CHECK: Converted to 32-bit integer: 2147483647
+! CHECK: Converted to 64-bit integer: 9223372036854775807
+! CHECK: Converted to 128-bit integer: 170141183460469231731687303715884105727
+! CHECK: Converted to 8-bit unsigned integer: 255
+! CHECK: Converted to 16-bit unsigned integer: 65535
+! CHECK: Converted to 32-bit unsigned integer: 4294967295
+! CHECK: Converted to 64-bit unsigned integer: 18446744073709551615
+! CHECK: Converted to 128-bit unsigned integer: 340282366920938463463374607431768211455
+! CHECK: Converted to 8-bit integer: -128
+! CHECK: Converted to 16-bit integer: -32768
+! CHECK: Converted to 32-bit integer: -2147483648
+! CHECK: Converted to 64-bit integer: -9223372036854775808
+! CHECK: Converted to 128-bit integer: -170141183460469231731687303715884105728
+! CHECK: Converted to 8-bit unsigned integer: 0
+! CHECK: Converted to 16-bit unsigned integer: 0
+! CHECK: Converted to 32-bit unsigned integer: 0
+! CHECK: Converted to 64-bit unsigned integer: 0
+! CHECK: Converted to 128-bit unsigned integer: 0
+! CHECK: Converted to 8-bit integer: 0
+! CHECK: Converted to 16-bit integer: 0
+! CHECK: Converted to 32-bit integer: 0
+! CHECK: Converted to 64-bit integer: 0
+! CHECK: Converted to 128-bit integer: 0
+! CHECK: Converted to 8-bit unsigned integer: 0
+! CHECK: Converted to 16-bit unsigned integer: 0
+! CHECK: Converted to 32-bit unsigned integer: 0
+! CHECK: Converted to 64-bit unsigned integer: 0
+! CHECK: Converted to 128-bit unsigned integer: 0
+! CHECK: Converted to 8-bit integer: 0
+! CHECK: Converted to 16-bit integer: 0
+! CHECK: Converted to 32-bit integer: 0
+! CHECK: Converted to 64-bit integer: 0
+! CHECK: Converted to 128-bit integer: 0
+! CHECK: Converted to 8-bit unsigned integer: 0
+! CHECK: Converted to 16-bit unsigned integer: 0
+! CHECK: Converted to 32-bit unsigned integer: 0
+! CHECK: Converted to 64-bit unsigned integer: 0
+! CHECK: Converted to 128-bit unsigned integer: 0
+! CHECK: Converted to 8-bit integer: 0
+! CHECK: Converted to 16-bit integer: 0
+! CHECK: Converted to 32-bit integer: 0
+! CHECK: Converted to 64-bit integer: 0
+! CHECK: Converted to 128-bit integer: 0
+! CHECK: Converted to 8-bit unsigned integer: 0
+! CHECK: Converted to 16-bit unsigned integer: 0
+! CHECK: Converted to 32-bit unsigned integer: 0
+! CHECK: Converted to 64-bit unsigned integer: 0
+! CHECK: Converted to 128-bit unsigned integer: 0
+! CHECK: Converted to 8-bit integer: 0
+! CHECK: Converted to 16-bit integer: 0
+! CHECK: Converted to 32-bit integer: 0
+! CHECK: Converted to 64-bit integer: 0
+! CHECK: Converted to 128-bit integer: 0
+! CHECK: Converted to 8-bit unsigned integer: 0
+! CHECK: Converted to 16-bit unsigned integer: 0
+! CHECK: Converted to 32-bit unsigned integer: 0
+! CHECK: Converted to 64-bit unsigned integer: 0
+! CHECK: Converted to 128-bit unsigned integer: 0
+! CHECK: Converted to 8-bit integer: 0
+! CHECK: Converted to 16-bit integer: 0
+! CHECK: Converted to 32-bit integer: 0
+! CHECK: Converted to 64-bit integer: 0
+! CHECK: Converted to 128-bit integer: 0
+! CHECK: Converted to 8-bit unsigned integer: 0
+! CHECK: Converted to 16-bit unsigned integer: 0
+! CHECK: Converted to 32-bit unsigned integer: 0
+! CHECK: Converted to 64-bit unsigned integer: 0
+! CHECK: Converted to 128-bit unsigned integer: 0
+! CHECK: Converted to 8-bit integer: 127
+! CHECK: Converted to 16-bit integer: 32767
+! CHECK: Converted to 32-bit integer: 2147483647
+! CHECK: Converted to 64-bit integer: 9223372036854775807
+! CHECK: Converted to 128-bit integer: 170141183460469231731687303715884105727
+! CHECK: Converted to 8-bit unsigned integer: 255
+! CHECK: Converted to 16-bit unsigned integer: 65535
+! CHECK: Converted to 32-bit unsigned integer: 4294967295
+! CHECK: Converted to 64-bit unsigned integer: 18446744073709551615
+! CHECK: Converted to 128-bit unsigned integer: 340282366920938463463374607431768211455
+! CHECK: Converted to 8-bit integer: -128
+! CHECK: Converted to 16-bit integer: -32768
+! CHECK: Converted to 32-bit integer: -2147483648
+! CHECK: Converted to 64-bit integer: -9223372036854775808
+! CHECK: Converted to 128-bit integer: -170141183460469231731687303715884105728
+! CHECK: Converted to 8-bit unsigned integer: 0
+! CHECK: Converted to 16-bit unsigned integer: 0
+! CHECK: Converted to 32-bit unsigned integer: 0
+! CHECK: Converted to 64-bit unsigned integer: 0
+! CHECK: Converted to 128-bit unsigned integer: 0

``````````

</details>


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


More information about the flang-commits mailing list