[flang-commits] [flang] [llvm] [flang] Support REAL(16) through glibc's *f128 entry points (PR #219698)

Dmitry Mikushin via flang-commits flang-commits at lists.llvm.org
Sat Aug 29 09:43:52 PDT 2026


https://github.com/dmikushin created https://github.com/llvm/llvm-project/pull/219698

## Problem

`REAL(16)` currently requires libquadmath. On a target where `long double` is not binary128, `flang/cmake/modules/FlangCommon.cmake` accepts only `FLANG_RUNTIME_F128_MATH_LIB=libquadmath`, and `math-entries.h` ends in an `#error` for anything else.

glibc has exported the `*f128` entry points from libm since 2.26. They are the same functions, in the C library that is already linked. Nothing needed them to be unavailable — the route was simply never wired up.

This matters beyond tidiness: libquadmath is GPL-3.0-or-later WITH GCC-exception-3.1, and whether that is acceptable is a question some users have to ask their lawyers. With this change they do not have to ask it.

## Implementation

- `math-entries.h` gains a glibc branch alongside the libquadmath one: 42 `DEFINE_SIMPLE_ALIAS` entries plus `Isinf`/`Isnan` to builtins. The alias table was derived by intersecting the names flang-rt actually declares with what libm actually exports, rather than by transcription.
- The `*f128` prototypes are gated behind `__STDC_WANT_IEC_60559_TYPES_EXT__`, which is set on the build target — a header-local `#define` arrives too late.
- `intrinsics-library.cpp` gains the matching host folding tables, so constant folding at `REAL(16)` goes through the same library the runtime does. This is the part that is easy to get wrong quietly: with the folder on libquadmath and the runtime on libm, `parameter :: v = sin(x)` and `y = sin(x)` are computed by two different libraries. They agree on every point measured here, but nothing *makes* them agree in the last place.
- The complex `*f128` entry points are declared explicitly. glibc declares them in `<complex.h>` for C only; the symbols are in libm and the ABI is the one `mode(TC)` produces. The tables take each function's address, so a change in visibility or signature is a build error at that line rather than a silent drift back to libquadmath.

Note the complex host type must be a C-style `typedef` of `_Complex float __attribute__((mode(TC)))`. Spelled as a `using` alias the attribute is silently dropped and the type degrades to `_Complex double` with only a warning — so there is a `static_assert` on `sizeof == 32`.

## Testing

Verified on `main` at `87b1a2f7246b`, Release, x86-64 Linux, glibc 2.42.

| | `check-flang` | `check-flang-rt` | libquadmath in `flang` (`nm -D`) |
|---|---|---|---|
| unpatched `main`, libquadmath | 4757 passed, 0 failed | 346 / 346 | 43 |
| this PR, libquadmath | 4757 passed, 0 failed | 346 / 346 | 43 |
| this PR, **libm** | 4757 passed, 0 failed | 346 / 346 | **0** |

No test changes count and none fail; the existing route is unaffected. On the libm route neither the compiler nor an application it produces references libquadmath — `ldd` reports none and `nm -D` finds zero, against 43 on the other two builds. The same probe reporting 43 where libquadmath *is* expected is what makes the 0 meaningful.

`flang/test/Driver/linker-flags.f90` gains the libm check prefixes so the driver's behaviour on this route is asserted rather than assumed.

## Known gap

`ERFC_SCALED` is not folded at `REAL(16)` on either route, because the folder registers it on host types only. That is pre-existing and unchanged here.

## Relation to #219697

Independent of #219697 — both apply to `main` on their own and were built and tested separately. They touch one file in common, `flang-rt/lib/quadmath/CMakeLists.txt`, so whichever lands second will need a trivial conflict resolution there.

>From 4f95f089c352c0ac05994286fccedb858af2e6c3 Mon Sep 17 00:00:00 2001
From: Dmitry Mikushin <dmitry at kernelgen.org>
Date: Fri, 28 Aug 2026 15:49:17 +0200
Subject: [PATCH 1/5] [flang-rt] Bring up the glibc *f128 route for REAL(16)

glibc 2.26 and later export the binary128 math functions from libm as *f128.
flang-rt already detected them - check_library_exists(m sinf128 ...) setting
HAS_LIBMF128 - but the target_sources line was commented out with "Enable this,
when math-entries.h and complex-math.h is ready", and both headers ended in
#error "Float128Math build with glibc>=2.26 is unsupported yet". So the only
way to get REAL(16) was libquadmath.

This turns that route on. It matters beyond convenience: libm is already linked
into every binary, so quad precision stops requiring a third-party library at
all.

The alias tables are derived rather than typed. Intersecting the names
flang-rt declares fallbacks for with what libm actually exports gives 42 scalar
and 17 complex entry points, with nothing left uncovered - including the six
Bessel functions, which were assumed missing.

Three names are not libm calls in any width: Isinf, Isnan and Qnan. They still
need aliases. "Not a libm call" is not "needs no implementation" - the fallback
for an unaliased name is a runtime Crash, and leaving these unaliased made
SPACING, RRSPACING, SET_EXPONENT, MOD and MODULO abort at run time while the
build and the link stayed green. They now go to __builtin_isinf/__builtin_isnan
and numeric_limits.

__STDC_WANT_IEC_60559_TYPES_EXT__ is set on the target, not in a header. A
header cannot guarantee it is seen before <cmath> or <complex.h> in every
translation unit, and when it is not the prototypes simply do not appear.
Measured on glibc 2.43: <cmath> exposes sqrtf128 even without the macro, while
<complex.h> does not expose cabsf128 - so a header-based definition would look
correct on the scalar half and fail on the complex half of the same build.

Each branch carries a compile-time canary taking the address of sqrtf128 and
cabsf128. That is not decoration: on the first build the macro reached only the
auto-detected path and not the explicit one, and the canary turned what would
have been a silent absence of prototypes into an error on the line that owns
the assumption.

The complex half uses CFloat128ComplexType from flang/Common/float128.h, the
same type the libquadmath branch passes to the *q functions. The C23 spelling
_Complex _Float128 is rejected by clang, and the GNU spelling
__complex__ _Float128 is accepted only with a warning that it is being read as
_Complex double - which would halve the precision in silence.

Verified without libquadmath present: selected_real_kind(33) = 16, the archive
has 55 _FortranA*F128 definitions and zero unresolved *q symbols, all 56 cases
of a differential matrix against gfortran agree, and ERFC_SCALED at REAL(16) is
byte-identical to the libquadmath route on every sample point - the same
trait-based implementation resolving through expq/erfcq/fmaq on one route and
expf128/erfcf128/fmaf128 on the other. Complex results are within 0.54 epsilons
of mpmath.
---
 flang-rt/lib/quadmath/CMakeLists.txt | 31 +++++++++-
 flang-rt/lib/quadmath/complex-math.h | 45 ++++++++++++--
 flang-rt/lib/quadmath/math-entries.h | 87 ++++++++++++++++++++++++++--
 3 files changed, 153 insertions(+), 10 deletions(-)

diff --git a/flang-rt/lib/quadmath/CMakeLists.txt b/flang-rt/lib/quadmath/CMakeLists.txt
index 353c72def24de..4c050eb66f810 100644
--- a/flang-rt/lib/quadmath/CMakeLists.txt
+++ b/flang-rt/lib/quadmath/CMakeLists.txt
@@ -86,6 +86,26 @@ if (FLANG_RUNTIME_F128_MATH_LIB)
         "to be available: ${FLANG_RUNTIME_F128_MATH_LIB}"
         )
     endif()
+  elseif (${FLANG_RUNTIME_F128_MATH_LIB} STREQUAL "libm")
+    # glibc 2.26 and later export the *f128 entry points from libm itself, so
+    # this route pulls in no third-party library at all.
+    check_library_exists(m sinf128 "" FOUND_LIBMF128_EXPLICIT)
+    if (FOUND_LIBMF128_EXPLICIT)
+      add_compile_definitions(HAS_LIBMF128)
+      # glibc gates the *f128 prototypes behind this, and it has to be on the
+      # command line rather than in a header: no header can guarantee it is
+      # seen before <cmath> or <complex.h> in every translation unit, and when
+      # it is not, the prototypes silently do not appear. Measured on glibc
+      # 2.43: <cmath> exposes sqrtf128 without it but <complex.h> does not
+      # expose cabsf128, so a header-based definition looks correct on the
+      # scalar half and fails on the complex half of the same build.
+      add_compile_definitions(__STDC_WANT_IEC_60559_TYPES_EXT__=1)
+    else()
+      message(FATAL_ERROR
+        "FLANG_RUNTIME_F128_MATH_LIB=libm requires a libm that exports the "
+        "*f128 entry points (glibc 2.26 or later)"
+        )
+    endif()
   else()
     message(FATAL_ERROR
       "Unsupported third-party library for Fortran F128 math runtime: "
@@ -125,11 +145,18 @@ else()
   if (FOUND_LIBMF128)
     target_compile_definitions(FortranFloat128MathILib INTERFACE
       HAS_LIBMF128
+      # glibc gates the *f128 prototypes behind this. It must be on the command
+      # line, not in a header: a header cannot guarantee it is seen before
+      # <cmath> or <complex.h> in every translation unit, and when it is not,
+      # the prototypes silently do not appear. Measured on glibc 2.43: <cmath>
+      # exposes sqrtf128 without it, <complex.h> does not expose cabsf128 - so
+      # a header-based definition would look correct on the scalar half and
+      # fail on the complex half of the same build.
+      __STDC_WANT_IEC_60559_TYPES_EXT__=1
       )
     target_include_directories(FortranFloat128MathILib INTERFACE
       "${FLANG_RT_SOURCE_DIR}/lib/flang_rt"
       )
-    # Enable this, when math-entries.h and complex-math.h is ready.
-    # target_sources(FortranFloat128MathILib INTERFACE ${sources})
+    target_sources(FortranFloat128MathILib INTERFACE ${sources})
   endif()
 endif()
diff --git a/flang-rt/lib/quadmath/complex-math.h b/flang-rt/lib/quadmath/complex-math.h
index b76bf03732938..878e824eaf87c 100644
--- a/flang-rt/lib/quadmath/complex-math.h
+++ b/flang-rt/lib/quadmath/complex-math.h
@@ -53,10 +53,47 @@
 #define CTan(x) ctanl(x)
 #define CTanh(x) ctanhl(x)
 #elif HAS_LIBMF128
-/* We can use __float128 versions of libm functions.
- * __STDC_WANT_IEC_60559_TYPES_EXT__ needs to be defined
- * before including math.h to enable the *f128 prototypes. */
-#error "Float128Math build with glibc>=2.26 is unsupported yet"
+/* glibc 2.26 and later export the complex *f128 entry points from libm, so
+ * this route needs no third-party library.
+ *
+ * __STDC_WANT_IEC_60559_TYPES_EXT__ must be defined before <complex.h> and is
+ * set on the target in this directory's CMakeLists.txt, not here: a header
+ * cannot guarantee it is included first, and the failure is silent. Unlike the
+ * scalar half, <complex.h> on glibc 2.43 really does hide cabsf128 without the
+ * macro, so this is the half that would break.
+ *
+ * The argument type is CFloat128ComplexType from flang/Common/float128.h,
+ * which is _Complex float __attribute__((mode(TC))). That is what the
+ * libquadmath branch already passes to the *q functions, and it is ABI
+ * compatible with the _Float128 _Complex these take: verified with the build
+ * clang under -Wall, sizeof 32, cabs(2+i) correct to nine places. The C23
+ * spelling _Complex _Float128 is not accepted by this clang, and the GNU
+ * spelling __complex__ _Float128 is accepted only with a warning that it is
+ * being read as _Complex double - which would silently halve the precision. */
+#include <complex.h>
+
+/* Compile-time canary: see the note in math-entries.h. If the prototypes are
+ * not visible, this fails here rather than at link time. */
+__attribute__((unused)) static CFloat128Type (*const c_f128_prototype_canary)(
+    CFloat128ComplexType) = &cabsf128;
+
+#define CAbs(x) cabsf128(x)
+#define CAcos(x) cacosf128(x)
+#define CAcosh(x) cacoshf128(x)
+#define CAsin(x) casinf128(x)
+#define CAsinh(x) casinhf128(x)
+#define CAtan(x) catanf128(x)
+#define CAtanh(x) catanhf128(x)
+#define CCos(x) ccosf128(x)
+#define CCosh(x) ccoshf128(x)
+#define CExp(x) cexpf128(x)
+#define CLog(x) clogf128(x)
+#define CSin(x) csinf128(x)
+#define CSinh(x) csinhf128(x)
+#define CSqrt(x) csqrtf128(x)
+#define CTan(x) ctanf128(x)
+#define CTanh(x) ctanhf128(x)
+#define CPow(x, p) cpowf128(x, p)
 #endif
 
 #endif /* FLANG_RT_QUADMATH_COMPLEX_MATH_H_ */
diff --git a/flang-rt/lib/quadmath/math-entries.h b/flang-rt/lib/quadmath/math-entries.h
index 91ad80c8197f4..28faa202d4dcf 100644
--- a/flang-rt/lib/quadmath/math-entries.h
+++ b/flang-rt/lib/quadmath/math-entries.h
@@ -224,10 +224,89 @@ DEFINE_SIMPLE_ALIAS(Yn, ynl)
 #define F128_RT_QNAN \
   (std::numeric_limits<CppTypeFor<TypeCategory::Real, 16>>::quiet_NaN())
 #elif HAS_LIBMF128
-// We can use __float128 versions of libm functions.
-// __STDC_WANT_IEC_60559_TYPES_EXT__ needs to be defined
-// before including cmath to enable the *f128 prototypes.
-#error "Float128Math build with glibc>=2.26 is unsupported yet"
+// glibc 2.26 and later export *f128 entry points from libm. They are the same
+// IEEE-754 binary128 functions libquadmath provides as *q, so this route needs
+// no third-party library: libm is already linked into everything.
+//
+// __STDC_WANT_IEC_60559_TYPES_EXT__ must be defined before any header pulls in
+// <cmath>, and it is set on the target in this directory's CMakeLists.txt
+// rather than here. In a header it would depend on this file being included
+// before <cmath> in every translation unit, which no header can guarantee -
+// and the failure is silent, because the prototypes simply do not appear.
+//
+// Measured on glibc 2.43 with clang: <cmath> exposes sqrtf128 even without the
+// macro, while <complex.h> does not expose cabsf128 without it. Relying on that
+// asymmetry would give a scalar half that appears to work beside a complex half
+// that does not compile, in the same build.
+#include <cmath>
+#include <limits>
+
+// Compile-time canary. If the *f128 prototypes are not visible here - a macro
+// lost, an include reordered, a future libc spelling them differently - the
+// aliases below would resolve to nothing and the failure would surface at link
+// time or in the field. This makes it surface at the line that owns the
+// assumption.
+[[maybe_unused]] static CppTypeFor<TypeCategory::Real, 16> (
+    *const f128_prototype_canary)(CppTypeFor<TypeCategory::Real, 16>) =
+    &sqrtf128;
+
+DEFINE_SIMPLE_ALIAS(Abs, fabsf128)
+DEFINE_SIMPLE_ALIAS(Acos, acosf128)
+DEFINE_SIMPLE_ALIAS(Acosh, acoshf128)
+DEFINE_SIMPLE_ALIAS(Asin, asinf128)
+DEFINE_SIMPLE_ALIAS(Asinh, asinhf128)
+DEFINE_SIMPLE_ALIAS(Atan, atanf128)
+DEFINE_SIMPLE_ALIAS(Atan2, atan2f128)
+DEFINE_SIMPLE_ALIAS(Atanh, atanhf128)
+DEFINE_SIMPLE_ALIAS(Ceil, ceilf128)
+DEFINE_SIMPLE_ALIAS(Cos, cosf128)
+DEFINE_SIMPLE_ALIAS(Cosh, coshf128)
+DEFINE_SIMPLE_ALIAS(Erf, erff128)
+DEFINE_SIMPLE_ALIAS(Erfc, erfcf128)
+DEFINE_SIMPLE_ALIAS(Exp, expf128)
+DEFINE_SIMPLE_ALIAS(Floor, floorf128)
+DEFINE_SIMPLE_ALIAS(Fma, fmaf128)
+DEFINE_SIMPLE_ALIAS(Frexp, frexpf128)
+DEFINE_SIMPLE_ALIAS(Hypot, hypotf128)
+DEFINE_SIMPLE_ALIAS(Ilogb, ilogbf128)
+DEFINE_SIMPLE_ALIAS(J0, j0f128)
+DEFINE_SIMPLE_ALIAS(J1, j1f128)
+DEFINE_SIMPLE_ALIAS(Jn, jnf128)
+DEFINE_SIMPLE_ALIAS(Ldexp, ldexpf128)
+DEFINE_SIMPLE_ALIAS(Lgamma, lgammaf128)
+DEFINE_SIMPLE_ALIAS(Log, logf128)
+DEFINE_SIMPLE_ALIAS(Log10, log10f128)
+DEFINE_SIMPLE_ALIAS(Lround, lroundf128)
+DEFINE_SIMPLE_ALIAS(Nearbyint, nearbyintf128)
+DEFINE_SIMPLE_ALIAS(Nextafter, nextafterf128)
+DEFINE_SIMPLE_ALIAS(Pow, powf128)
+DEFINE_SIMPLE_ALIAS(Remainder, remainderf128)
+DEFINE_SIMPLE_ALIAS(Round, roundf128)
+DEFINE_SIMPLE_ALIAS(Sin, sinf128)
+DEFINE_SIMPLE_ALIAS(Sinh, sinhf128)
+DEFINE_SIMPLE_ALIAS(Sqrt, sqrtf128)
+DEFINE_SIMPLE_ALIAS(Tan, tanf128)
+DEFINE_SIMPLE_ALIAS(Tanh, tanhf128)
+DEFINE_SIMPLE_ALIAS(Tgamma, tgammaf128)
+DEFINE_SIMPLE_ALIAS(Trunc, truncf128)
+DEFINE_SIMPLE_ALIAS(Y0, y0f128)
+DEFINE_SIMPLE_ALIAS(Y1, y1f128)
+DEFINE_SIMPLE_ALIAS(Yn, ynf128)
+
+// Isinf, Isnan and the quiet NaN are not libm entry points in any width, but
+// they still need implementations: the fallback for an unaliased name is a
+// runtime Crash, not a default. The builtins are type-generic and correct for
+// binary128; the libquadmath branch reaches the same place through isinfq and
+// isnanq.
+DEFINE_SIMPLE_ALIAS(Isinf, __builtin_isinf)
+DEFINE_SIMPLE_ALIAS(Isnan, __builtin_isnan)
+
+// Not libm calls: isinf and isnan are type-generic macros, and the quiet NaN
+// comes from numeric_limits rather than from a function.
+#define F128_RT_INFINITY \
+  (std::numeric_limits<CppTypeFor<TypeCategory::Real, 16>>::infinity())
+#define F128_RT_QNAN \
+  (std::numeric_limits<CppTypeFor<TypeCategory::Real, 16>>::quiet_NaN())
 #endif
 
 } // namespace Fortran::runtime

>From efba48e54fd1fb12a4ca0ee3c2f4273e69cc5cae Mon Sep 17 00:00:00 2001
From: Dmitry Mikushin <dmitry at kernelgen.org>
Date: Fri, 28 Aug 2026 15:50:43 +0200
Subject: [PATCH 2/5] [flang] Accept libm for REAL(16), and stop documenting
 what the code refused

FLANG_RUNTIME_F128_MATH_LIB's cache description offered "libm for targets where
REAL(16) is mapped to long double", while the code rejected every value except
libquadmath with a FATAL_ERROR. Now that libm is a real option the description
can be true: it lists the two accepted values and says what an empty setting
means, which is that REAL(16) is disabled in the compiler as well as the
runtime.

TargetSetup.h needed no change to enable the type - it already turns on
f128Support for any non-empty value - but its comments were wrong in two ways:
they attributed the setting to libquadmath specifically, and they pointed at
flang/runtime/Float128Math/math-entries.h, a path that has since become
flang-rt/lib/quadmath/.

The TODO about deriving this from TargetInfo::getLongDoubleFormat is left
alone. It is a real defect - the decision is made from the host the compiler
was built on rather than the target it compiles for - but it is separate work
and cannot be demonstrated on a build configured for the native target only.
---
 flang/cmake/modules/FlangCommon.cmake   | 9 +++++----
 flang/include/flang/Tools/TargetSetup.h | 7 ++++---
 2 files changed, 9 insertions(+), 7 deletions(-)

diff --git a/flang/cmake/modules/FlangCommon.cmake b/flang/cmake/modules/FlangCommon.cmake
index a1e80f737e543..39b9c7a8e7b4d 100644
--- a/flang/cmake/modules/FlangCommon.cmake
+++ b/flang/cmake/modules/FlangCommon.cmake
@@ -19,10 +19,11 @@ include(CMakePushCheckState)
 # to be composable. Failure to synchronize this setting may result
 # in linking errors or fatal failures in F128 runtime functions.
 set(FLANG_RUNTIME_F128_MATH_LIB "" CACHE STRING
-  "Specifies the target library used for implementing IEEE-754 128-bit float \
-  math in F18 runtime, e.g. it might be libquadmath for targets where \
-  REAL(16) is mapped to __float128, or libm for targets where REAL(16) \
-  is mapped to long double, etc."
+  "Library implementing IEEE-754 binary128 math in the Fortran runtime. \
+  Accepted values: libquadmath, for GCC's __float128 support; or libm, on a \
+  glibc 2.26 or later that exports the *f128 entry points, which needs no \
+  third-party library. Leave empty to build without REAL(16) support - the \
+  type is then disabled in the compiler as well as the runtime."
   )
 if (FLANG_RUNTIME_F128_MATH_LIB)
   add_compile_definitions(FLANG_RUNTIME_F128_MATH_LIB="${FLANG_RUNTIME_F128_MATH_LIB}")
diff --git a/flang/include/flang/Tools/TargetSetup.h b/flang/include/flang/Tools/TargetSetup.h
index 47f886141b002..d462de6443567 100644
--- a/flang/include/flang/Tools/TargetSetup.h
+++ b/flang/include/flang/Tools/TargetSetup.h
@@ -57,12 +57,13 @@ namespace Fortran::tools {
     break;
   }
 
-  // Check for kind=16 support. See flang/runtime/Float128Math/math-entries.h.
+  // Check for kind=16 support. See flang-rt/lib/quadmath/math-entries.h.
   // TODO: Take this from TargetInfo::getLongDoubleFormat for cross compilation.
 #ifdef FLANG_RUNTIME_F128_MATH_LIB
-  constexpr bool f128Support = true; // use libquadmath wrappers
+  // libquadmath, or libm on a glibc that exports the *f128 entry points.
+  constexpr bool f128Support = true;
 #elif HAS_LDBL128
-  constexpr bool f128Support = true; // use libm wrappers
+  constexpr bool f128Support = true; // long double is itself binary128
 #else
   constexpr bool f128Support = false;
 #endif

>From bff2ec3f245ab58ce6bf683e6db00e2dd8248086 Mon Sep 17 00:00:00 2001
From: Dmitry Mikushin <dmitry at kernelgen.org>
Date: Fri, 28 Aug 2026 16:10:15 +0200
Subject: [PATCH 3/5] [flang][test] Add the libm check prefixes to the linker
 test

linker-flags.f90 builds its check prefix from the configured F128 library:
--check-prefixes=CHECK,UNIX,UNIX-F128%f128-lib. That machinery is not new, and
neither is the driver's handling of the libm route - it already emits
"-lflang_rt.quadmath" "--as-needed" "-lm" "--no-as-needed". What was missing
were the check strings for the value, because until now the build refused to
accept it: the test enumerated only NONE and LIBQUADMATH, so configuring with
FLANG_RUNTIME_F128_MATH_LIB=libm produced

  error: no check strings found with prefix 'UNIX-F128LIBM:'

The six new lines are derived from the observed link line rather than written
from expectation: the libm route links the same runtime archive against -lm
where the libquadmath route links -lquadmath, and nothing else about the
command differs.

This is worth noting beyond the fix. The driver half of the libm route was
already in the tree - the substitution, the RUN lines and the link logic - while
the runtime half was behind #error "unsupported yet". The route was not
unstarted; it was broken off half way, with the front end waiting for a runtime
that refused to build.

Verified on both configurations: the test passes under libquadmath and under
libm, and on the libm build a deliberate run with the LIBQUADMATH prefix fails
for the right reason - the driver no longer emits -lquadmath.
---
 flang/test/Driver/linker-flags.f90 | 6 ++++++
 1 file changed, 6 insertions(+)

diff --git a/flang/test/Driver/linker-flags.f90 b/flang/test/Driver/linker-flags.f90
index c4e0012b5c224..668c554085ace 100644
--- a/flang/test/Driver/linker-flags.f90
+++ b/flang/test/Driver/linker-flags.f90
@@ -44,7 +44,9 @@
 ! UNIX-F128NONE-NOT: lang_rt.quadmath
 ! SOLARIS-F128NONE-NOT: flang_rt.quadmath
 ! UNIX-F128LIBQUADMATH-SAME: "-lflang_rt.quadmath" "--as-needed" "-lquadmath" "--no-as-needed"
+! UNIX-F128LIBM-SAME: "-lflang_rt.quadmath" "--as-needed" "-lm" "--no-as-needed"
 ! SOLARIS-F128LIBQUADMATH-SAME: "-lflang_rt.quadmath" "-z" "ignore" "-lquadmath" "-z" "record"
+! SOLARIS-F128LIBM-SAME: "-lflang_rt.quadmath" "-z" "ignore" "-lm" "-z" "record"
 ! UNIX-SAME: "-lflang_rt.runtime" "-lm"
 ! COMPILER-RT: "{{.*}}{{\\|/}}libclang_rt.builtins.a"
 ! UNIX-STATIC-FLANGRT:   "{{.*}}{{\\|/}}libflang_rt.runtime.a"
@@ -53,6 +55,7 @@
 ! BSD-SAME: "[[object_file]]"
 ! BSD-F128NONE-NOT: flang_rt.quadmath
 ! BSD-F128LIBQUADMATH-SAME: "-lflang_rt.quadmath" "--as-needed" "-lquadmath" "--no-as-needed"
+! BSD-F128LIBM-SAME: "-lflang_rt.quadmath" "--as-needed" "-lm" "--no-as-needed"
 ! BSD-SAME: -lflang_rt.runtime
 ! BSD-SAME: -lexecinfo
 ! BSD-STATIC-FLANGRT: "{{.*}}{{\\|/}}libflang_rt.runtime.a"
@@ -61,6 +64,7 @@
 ! DARWIN-SAME: "[[object_file]]"
 ! DARWIN-F128NONE-NOT: libflang_rt.quadmath
 ! DARWIN-F128LIBQUADMATH-SAME: "-lflang_rt.quadmath" "--as-needed" "-lquadmath" "--no-as-needed"
+! DARWIN-F128LIBM-SAME: "-lflang_rt.quadmath" "--as-needed" "-lm" "--no-as-needed"
 ! DARWIN-SAME: -lflang_rt.runtime
 ! DARWIN-STATIC-FLANGRT: "{{.*}}{{\\|/}}libclang_rt.runtime_osx.a"
 
@@ -68,6 +72,7 @@
 ! HAIKU-SAME: "[[object_file]]"
 ! HAIKU-F128NONE-NOT: libflang_rt.quadmath
 ! HAIKU-F128LIBQUADMATH-SAME: "-lflang_rt.quadmath" "--as-needed" "-lquadmath" "--no-as-needed"
+! HAIKU-F128LIBM-SAME: "-lflang_rt.quadmath" "--as-needed" "-lm" "--no-as-needed"
 ! HAIKU-SAME: "-lflang_rt.runtime"
 ! HAIKU-STATIC-FLANGRT: "{{.*}}{{\\|/}}libflang_rt.runtime.a"
 
@@ -75,6 +80,7 @@
 ! MINGW-SAME: "[[object_file]]"
 ! MINGW-F128NONE-NOT: libflang_rt.quadmath
 ! MINGW-F128LIBQUADMATH-SAME: "-lflang_rt.quadmath" "--as-needed" "-lquadmath" "--no-as-needed"
+! MINGW-F128LIBM-SAME: "-lflang_rt.quadmath" "--as-needed" "-lm" "--no-as-needed"
 ! MINGW-SAME: -lflang_rt.runtime
 ! MINGW-STATIC-FLANGRT: "{{.*}}{{\\|/}}libflang_rt.runtime.a"
 

>From f268cc6eb3b13892c4e54b17192f5b3f20109742 Mon Sep 17 00:00:00 2001
From: Dmitry Mikushin <dmitry at kernelgen.org>
Date: Fri, 28 Aug 2026 18:29:58 +0200
Subject: [PATCH 4/5] [flang][Evaluate] Fold REAL(16) constants through libm on
 the libm route

The compile-time folder for REAL(16) was built only on libquadmath's *q
functions. On the libm route that left the two halves of the language
disagreeing about who computes: `parameter :: v = sin(x)` folded through sinq
while `y = sin(x)` ran through sinf128. They agree on every point measured, but
nothing makes them agree in the last place, and a named constant that differs
from its own runtime value is a defect waiting for an unlucky argument.

This adds the libm tables beside the libquadmath ones and dispatches on the
configured route. Both are derived from the existing tables rather than typed:
27 scalar entries and 16 complex ones, every one of which has an f128
counterpart in glibc, so nothing stops folding.

The complex prototypes are declared by hand. glibc exposes the complex *f128
functions to C but hides them from C++, where Evaluate lives, while the symbols
are in libm and the ABI is the one mode(TC) produces. The signatures are fixed
by TS 18661-3, and no separate canary is needed because the table takes each
function's address - a change in visibility or signature is a build error at
that line, not a silent drift back to libquadmath.

Two traps were caught by guards rather than by reasoning, and both are worth
recording.

The first: `using HostComplex128 = _Complex float __attribute__((mode(TC)))`
silently drops the attribute and yields an 8-byte _Complex float. The
static_assert on sizeof caught it on the first build. The C-style typedef, which
is how flang/Common/float128.h already spells this type, applies the attribute.
That is the same class of failure as clang reading __complex__ _Float128 as
_Complex double - a plausible type of the wrong width.

The second: the tables compiled and were never reached, because
GetHostRuntimeMapVersion still guarded its dispatch on HAS_QUADMATHLIB alone.
The symptom looked like a macro that had not reached the build graph, and
measuring the graph - 30 occurrences of the new macro in build.ninja - is what
redirected the search to the dispatcher.

Measured after: on the libm route the compiler links no libquadmath at all, and
six folded constants equal their runtime values bit for bit. On the libquadmath
route nothing changes, checked the same way rather than by inspection.
---
 flang/lib/Evaluate/host.h                 |  25 +++++-
 flang/lib/Evaluate/intrinsics-library.cpp | 101 +++++++++++++++++++++-
 2 files changed, 122 insertions(+), 4 deletions(-)

diff --git a/flang/lib/Evaluate/host.h b/flang/lib/Evaluate/host.h
index 7f6bf76bb5c53..62d2dc22ea16c 100644
--- a/flang/lib/Evaluate/host.h
+++ b/flang/lib/Evaluate/host.h
@@ -20,6 +20,8 @@
 #if HAS_QUADMATHLIB
 #include "quadmath_wrapper.h"
 #include "flang/Common/float128.h"
+#elif HAS_LIBMF128_HOST
+#include "flang/Common/float128.h"
 #endif
 #include "flang/Evaluate/type.h"
 #include <cfenv>
@@ -159,9 +161,13 @@ struct HostTypeHelper<
       long double, UnsupportedType>;
 };
 
-#if HAS_QUADMATHLIB
+#if HAS_QUADMATHLIB || HAS_LIBMF128_HOST
 template <> struct HostTypeHelper<Type<TypeCategory::Real, 16>> {
-  // IEEE 754 128bits
+  // IEEE 754 128bits.
+  //
+  // Both routes use the same host type; only the library the folding table
+  // calls differs - libquadmath's *q or glibc's *f128. Keeping the type common
+  // is what lets the two share everything above this line.
   using Type = __float128;
 };
 #else
@@ -185,6 +191,21 @@ template <> struct HostTypeHelper<Type<TypeCategory::Complex, 16>> {
   using RealT = Fortran::evaluate::Type<TypeCategory::Real, 16>;
   using Type = __complex128;
 };
+#elif HAS_LIBMF128_HOST
+// __complex128 is libquadmath's typedef and is not available here. The mode
+// attribute is how flang already spells this type in flang/Common/float128.h
+// for the runtime, and glibc's complex *f128 functions take the same ABI.
+// The C-style typedef, not `using`: with an alias declaration the mode
+// attribute is silently dropped and the type degrades to _Complex float. The
+// static_assert below caught exactly that during development.
+typedef _Complex float __attribute__((mode(TC))) HostComplex128;
+static_assert(sizeof(HostComplex128) == 32,
+    "mode(TC) must be a pair of binary128 values; if it is not, the complex "
+    "folding table below is calling libm with the wrong ABI");
+template <> struct HostTypeHelper<Type<TypeCategory::Complex, 16>> {
+  using RealT = Fortran::evaluate::Type<TypeCategory::Real, 16>;
+  using Type = HostComplex128;
+};
 #endif
 
 template <int KIND> struct HostTypeHelper<Type<TypeCategory::Logical, KIND>> {
diff --git a/flang/lib/Evaluate/intrinsics-library.cpp b/flang/lib/Evaluate/intrinsics-library.cpp
index f2c1a7bfaf50b..2572d361dbe91 100644
--- a/flang/lib/Evaluate/intrinsics-library.cpp
+++ b/flang/lib/Evaluate/intrinsics-library.cpp
@@ -576,7 +576,95 @@ template <> struct HostRuntimeLibrary<__complex128, LibraryVersion::Libm> {
   static constexpr HostRuntimeMap map{table};
   static_assert(map.Verify(), "map must be sorted");
 };
-#endif // HAS_QUADMATHLIB
+
+#elif HAS_LIBMF128_HOST
+template <> struct HostRuntimeLibrary<__float128, LibraryVersion::Libm> {
+  using F = FuncPointer<__float128, __float128>;
+  using F2 = FuncPointer<__float128, __float128, __float128>;
+  using FN = FuncPointer<__float128, int, __float128>;
+  static constexpr HostRuntimeFunction table[]{
+      FolderFactory<F, F{::acosf128}>::Create("acos"),
+      FolderFactory<F, F{::acoshf128}>::Create("acosh"),
+      FolderFactory<F, F{::asinf128}>::Create("asin"),
+      FolderFactory<F, F{::asinhf128}>::Create("asinh"),
+      FolderFactory<F, F{::atanf128}>::Create("atan"),
+      FolderFactory<F2, F2{::atan2f128}>::Create("atan2"),
+      FolderFactory<F, F{::atanhf128}>::Create("atanh"),
+      FolderFactory<F, F{::j0f128}>::Create("bessel_j0"),
+      FolderFactory<F, F{::j1f128}>::Create("bessel_j1"),
+      FolderFactory<FN, FN{::jnf128}>::Create("bessel_jn"),
+      FolderFactory<F, F{::y0f128}>::Create("bessel_y0"),
+      FolderFactory<F, F{::y1f128}>::Create("bessel_y1"),
+      FolderFactory<FN, FN{::ynf128}>::Create("bessel_yn"),
+      FolderFactory<F, F{cosf128}>::Create("cos"),
+      FolderFactory<F, F{coshf128}>::Create("cosh"),
+      FolderFactory<F, F{::erff128}>::Create("erf"),
+      FolderFactory<F, F{::erfcf128}>::Create("erfc"),
+      FolderFactory<F, F{::expf128}>::Create("exp"),
+      FolderFactory<F, F{::tgammaf128}>::Create("gamma"),
+      FolderFactory<F, F{::logf128}>::Create("log"),
+      FolderFactory<F, F{::log10f128}>::Create("log10"),
+      FolderFactory<F, F{::lgammaf128}>::Create("log_gamma"),
+      FolderFactory<F2, F2{::powf128}>::Create("pow"),
+      FolderFactory<F, F{::sinf128}>::Create("sin"),
+      FolderFactory<F, F{::sinhf128}>::Create("sinh"),
+      FolderFactory<F, F{::tanf128}>::Create("tan"),
+      FolderFactory<F, F{::tanhf128}>::Create("tanh"),
+  };
+  static constexpr HostRuntimeMap map{table};
+  static_assert(map.Verify(), "map must be sorted");
+};
+
+// glibc declares the complex *f128 entry points in <complex.h> for C only; in
+// C++ they are hidden, though the symbols are in libm and the ABI is the one
+// mode(TC) produces. Declaring them here is deliberate. The signatures are
+// fixed by TS 18661-3 and the glibc ABI, and no separate canary is needed: the
+// table below takes each function's address, so a change in visibility or
+// signature is a build error at that line rather than a silent drift back to
+// libquadmath. Do not delete this block expecting the headers to provide them.
+extern "C" {
+host::HostComplex128 cacosf128(host::HostComplex128);
+host::HostComplex128 cacoshf128(host::HostComplex128);
+host::HostComplex128 casinf128(host::HostComplex128);
+host::HostComplex128 casinhf128(host::HostComplex128);
+host::HostComplex128 catanf128(host::HostComplex128);
+host::HostComplex128 catanhf128(host::HostComplex128);
+host::HostComplex128 ccosf128(host::HostComplex128);
+host::HostComplex128 ccoshf128(host::HostComplex128);
+host::HostComplex128 cexpf128(host::HostComplex128);
+host::HostComplex128 clogf128(host::HostComplex128);
+host::HostComplex128 cpowf128(host::HostComplex128, host::HostComplex128);
+host::HostComplex128 csinf128(host::HostComplex128);
+host::HostComplex128 csinhf128(host::HostComplex128);
+host::HostComplex128 csqrtf128(host::HostComplex128);
+host::HostComplex128 ctanf128(host::HostComplex128);
+host::HostComplex128 ctanhf128(host::HostComplex128);
+}
+template <> struct HostRuntimeLibrary<host::HostComplex128, LibraryVersion::Libm> {
+  using F = FuncPointer<host::HostComplex128, host::HostComplex128>;
+  using F2 = FuncPointer<host::HostComplex128, host::HostComplex128, host::HostComplex128>;
+  static constexpr HostRuntimeFunction table[]{
+      FolderFactory<F, F{cacosf128}>::Create("acos"),
+      FolderFactory<F, F{cacoshf128}>::Create("acosh"),
+      FolderFactory<F, F{casinf128}>::Create("asin"),
+      FolderFactory<F, F{casinhf128}>::Create("asinh"),
+      FolderFactory<F, F{catanf128}>::Create("atan"),
+      FolderFactory<F, F{catanhf128}>::Create("atanh"),
+      FolderFactory<F, F{ccosf128}>::Create("cos"),
+      FolderFactory<F, F{ccoshf128}>::Create("cosh"),
+      FolderFactory<F, F{cexpf128}>::Create("exp"),
+      FolderFactory<F, F{clogf128}>::Create("log"),
+      FolderFactory<F2, F2{cpowf128}>::Create("pow"),
+      FolderFactory<F, F{csinf128}>::Create("sin"),
+      FolderFactory<F, F{csinhf128}>::Create("sinh"),
+      FolderFactory<F, F{csqrtf128}>::Create("sqrt"),
+      FolderFactory<F, F{ctanf128}>::Create("tan"),
+      FolderFactory<F, F{ctanhf128}>::Create("tanh"),
+  };
+  static constexpr HostRuntimeMap map{table};
+  static_assert(map.Verify(), "map must be sorted");
+};
+#endif // HAS_QUADMATHLIB || HAS_LIBMF128_HOST
 
 /// Define pgmath description
 #if LINK_WITH_LIBPGMATH
@@ -686,7 +774,11 @@ static const HostRuntimeMap *GetHostRuntimeMapVersion(DynamicType resultType) {
             GetHostRuntimeMapHelper<long double, version>(resultType)}) {
       return map;
     }
-#if HAS_QUADMATHLIB
+#if HAS_QUADMATHLIB || HAS_LIBMF128_HOST
+    // Both routes present __float128 here; only the library behind the table
+    // differs. Guarding this on HAS_QUADMATHLIB alone compiled the libm tables
+    // and then never reached them, so REAL(16) folding failed on a build that
+    // had everything it needed.
     if (const auto *map{
             GetHostRuntimeMapHelper<__float128, version>(resultType)}) {
       return map;
@@ -712,6 +804,11 @@ static const HostRuntimeMap *GetHostRuntimeMapVersion(DynamicType resultType) {
             GetHostRuntimeMapHelper<__complex128, version>(resultType)}) {
       return map;
     }
+#elif HAS_LIBMF128_HOST
+    if (const auto *map{GetHostRuntimeMapHelper<host::HostComplex128, version>(
+            resultType)}) {
+      return map;
+    }
 #endif
   }
   return nullptr;

>From b0893f73b20eb1354603c184c9f708080d942f91 Mon Sep 17 00:00:00 2001
From: Dmitry Mikushin <dmitry at kernelgen.org>
Date: Fri, 28 Aug 2026 18:30:54 +0200
Subject: [PATCH 5/5] [flang] Tie the host folding library to the configured
 F128 route

HAS_QUADMATHLIB was set from whether libquadmath exists on the build machine,
not from FLANG_RUNTIME_F128_MATH_LIB. Measured consequence: every flang binary
here linked libquadmath - the one built for the libm route, where the runtime
deliberately does not, and the one built with REAL(16) disabled entirely, where
the folding table is unreachable and the 43 symbols are dead weight.

It now follows the route. libquadmath selects the *q tables and errors out if
the library or header is missing rather than silently disabling folding; libm
selects the *f128 tables and defines the macro that reveals glibc's prototypes;
an empty setting selects neither, which is correct because REAL(16) is disabled
in that configuration and the tables could never be reached.

__STDC_WANT_IEC_60559_TYPES_EXT__ goes on the target for the same reason as in
flang-rt: no header can guarantee it is seen before <cmath> in every
translation unit, and when it is not the prototypes simply do not appear.

No configuration loses anything. The empty route loses only the dead linkage;
selected_real_kind(33) is -1 there and semantics rejects the type before
folding is ever consulted.
---
 flang/lib/Evaluate/CMakeLists.txt | 35 ++++++++++++++++++++++++++-----
 1 file changed, 30 insertions(+), 5 deletions(-)

diff --git a/flang/lib/Evaluate/CMakeLists.txt b/flang/lib/Evaluate/CMakeLists.txt
index 472ecb6d8d079..af25209b5cd62 100644
--- a/flang/lib/Evaluate/CMakeLists.txt
+++ b/flang/lib/Evaluate/CMakeLists.txt
@@ -21,11 +21,36 @@ if (LIBPGMATH_DIR)
   endif()
 endif()
 
-check_library_exists(quadmath sinq "" FOUND_QUADMATH_LIB)
-if (FLANG_INCLUDE_QUADMATH_H AND FOUND_QUADMATH_LIB)
-  configure_file("${FLANG_SOURCE_DIR}/cmake/quadmath_wrapper.h.in" "${CMAKE_CURRENT_BINARY_DIR}/quadmath_wrapper.h")
-  add_compile_definitions(HAS_QUADMATHLIB)
-  set(QUADMATHLIB quadmath)
+# Compile-time folding of REAL(16) constants needs a binary128 math library on
+# the host. Which one is not a separate question from which one the runtime
+# uses: before this was tied to FLANG_RUNTIME_F128_MATH_LIB, the folder was
+# enabled purely by libquadmath being installed on the build machine, so every
+# flang binary linked it - including one configured for the libm route, and one
+# configured with REAL(16) disabled entirely, where the table was unreachable
+# dead weight.
+#
+# Worse, on the libm route the two halves then disagreed about who computes:
+# `parameter :: v = sin(x)` folded through sinq while `y = sin(x)` ran through
+# sinf128. They agree on every point measured, but nothing makes them agree in
+# the last place.
+if (FLANG_RUNTIME_F128_MATH_LIB STREQUAL "libquadmath")
+  check_library_exists(quadmath sinq "" FOUND_QUADMATH_LIB)
+  if (FLANG_INCLUDE_QUADMATH_H AND FOUND_QUADMATH_LIB)
+    configure_file("${FLANG_SOURCE_DIR}/cmake/quadmath_wrapper.h.in" "${CMAKE_CURRENT_BINARY_DIR}/quadmath_wrapper.h")
+    add_compile_definitions(HAS_QUADMATHLIB)
+    set(QUADMATHLIB quadmath)
+  else()
+    message(FATAL_ERROR
+      "FLANG_RUNTIME_F128_MATH_LIB=libquadmath but quadmath.h or libquadmath "
+      "was not found; the compiler could not fold REAL(16) constants")
+  endif()
+elseif (FLANG_RUNTIME_F128_MATH_LIB STREQUAL "libm")
+  # glibc 2.26 and later carry the same functions as *f128 in libm, which is
+  # linked anyway. The macro that reveals their prototypes has to be on the
+  # command line for the same reason as in flang-rt: no header can guarantee it
+  # is seen before <cmath> in every translation unit.
+  add_compile_definitions(HAS_LIBMF128_HOST)
+  add_compile_definitions(__STDC_WANT_IEC_60559_TYPES_EXT__=1)
 endif ()
 
 add_flang_library(FortranEvaluate



More information about the flang-commits mailing list