git: 255538cd9060 - main - powerpc64le: switch long double to IEEE binary128

From: Piotr Kubaj <pkubaj_at_FreeBSD.org>
Date: Fri, 19 Jun 2026 22:00:18 UTC
The branch main has been updated by pkubaj:

URL: https://cgit.FreeBSD.org/src/commit/?id=255538cd906045095d0c2113ae6c4731ce36c0cf

commit 255538cd906045095d0c2113ae6c4731ce36c0cf
Author:     Piotr Kubaj <pkubaj@FreeBSD.org>
AuthorDate: 2026-06-15 15:37:26 +0000
Commit:     Piotr Kubaj <pkubaj@FreeBSD.org>
CommitDate: 2026-06-19 21:25:39 +0000

    powerpc64le: switch long double to IEEE binary128
    
    Change powerpc64le's long double from 64-bit double to IEEE 754
    binary128 (quad, 113-bit mantissa), matching aarch64 and riscv64.
    
    Gated on FreeBSD 16 and powerpc64le only.
    
    Differential Revision: https://reviews.freebsd.org/D57388
    Reviewed by:    adrian
    Relnotes:       yes
---
 UPDATING                                           | 13 +++++
 contrib/llvm-project/clang/lib/Basic/Targets/PPC.h |  7 ++-
 .../llvm-project/clang/lib/CodeGen/CGBuiltin.cpp   |  6 ++-
 .../llvm-project/clang/lib/Driver/ToolChain.cpp    |  4 ++
 lib/clang/freebsd_cc_version.h                     |  2 +-
 lib/libc/powerpc64/Makefile.inc                    |  6 +++
 lib/libc/powerpc64/_fpmath.h                       | 29 +++++++++--
 lib/libc/powerpc64/gd_qnan.h                       |  6 +--
 lib/libc/tests/gen/Makefile                        |  1 +
 lib/libc/tests/stdlib/Makefile                     |  1 +
 lib/libcompiler_rt/Makefile.inc                    | 59 ++++++++++++++++++++++
 lib/libcompiler_rt/comparekf2.c                    | 43 ++++++++++++++++
 lib/libcxxrt/Makefile                              | 12 ++++-
 lib/msun/powerpc/Makefile.inc                      |  7 +++
 lib/msun/powerpc/Symbol_f128.map                   | 36 +++++++++++++
 lib/msun/powerpc/ld128_compat.c                    | 43 ++++++++++++++++
 lib/msun/src/s_nearbyint.c                         | 11 ++--
 lib/msun/tests/Makefile                            |  1 +
 share/man/man7/arch.7                              |  6 +--
 sys/powerpc/include/float.h                        | 17 +++++++
 sys/sys/param.h                                    |  2 +-
 21 files changed, 292 insertions(+), 20 deletions(-)

diff --git a/UPDATING b/UPDATING
index a83925a811a4..f164208e9553 100644
--- a/UPDATING
+++ b/UPDATING
@@ -27,9 +27,22 @@ NOTE TO PEOPLE WHO THINK THAT FreeBSD 16.x IS SLOW:
 	world, or to merely disable the most expensive debugging functionality
 	at runtime, run "ln -s 'abort:false,junk:false' /etc/malloc.conf".)
 
+20260620:
+	On powerpc64le, long double is now IEEE 754 binary128 (quad
+	precision, 113-bit mantissa) instead of 64-bit double.  This is an
+	ABI-incompatible change with no symbol versioning: all C and C++
+	code that passes, returns, or stores long double (or long double
+	complex) -- including printf/scanf "%Lf", strtold, and the libm
+	*l() routines -- must be rebuilt against the new world.  Mixing old
+	and new objects that exchange long double values will silently
+	corrupt data.  Rebuild all installed ports and any out-of-tree
+	software after upgrading.  Big-endian powerpc and powerpc64 are
+	unaffected and keep 64-bit long double.
+
 20260521:
 	Audio devices are now created with GID 43 / audio. You will need to add
 	users who need access to audio devices to this group.
+
 20260512:
 	"bsdinstall script" will now do a pkgbase installation by default.  To
 	revert to the legacy distset installation, set "DISTRIBUTIONS" in
diff --git a/contrib/llvm-project/clang/lib/Basic/Targets/PPC.h b/contrib/llvm-project/clang/lib/Basic/Targets/PPC.h
index 9f3a4cd2da71..5c47e0dca666 100644
--- a/contrib/llvm-project/clang/lib/Basic/Targets/PPC.h
+++ b/contrib/llvm-project/clang/lib/Basic/Targets/PPC.h
@@ -468,7 +468,12 @@ public:
       DataLayout += "-i64:64-i128:128-n32:64";
     }
 
-    if (Triple.isOSFreeBSD() || Triple.isOSOpenBSD() || Triple.isMusl()) {
+    if (Triple.isOSFreeBSD() && Triple.getArch() == llvm::Triple::ppc64le &&
+        Triple.getOSMajorVersion() >= 16) {
+      LongDoubleWidth = LongDoubleAlign = 128;
+      LongDoubleFormat = &llvm::APFloat::IEEEquad();
+    } else if (Triple.isOSFreeBSD() || Triple.isOSOpenBSD() ||
+               Triple.isMusl()) {
       LongDoubleWidth = LongDoubleAlign = 64;
       LongDoubleFormat = &llvm::APFloat::IEEEdouble();
     }
diff --git a/contrib/llvm-project/clang/lib/CodeGen/CGBuiltin.cpp b/contrib/llvm-project/clang/lib/CodeGen/CGBuiltin.cpp
index 5f2eb76e7bac..7cd155191861 100644
--- a/contrib/llvm-project/clang/lib/CodeGen/CGBuiltin.cpp
+++ b/contrib/llvm-project/clang/lib/CodeGen/CGBuiltin.cpp
@@ -228,7 +228,10 @@ llvm::Constant *CodeGenModule::getBuiltinLibFunction(const FunctionDecl *FD,
   else {
     // TODO: This mutation should also be applied to other targets other than
     // PPC, after backend supports IEEE 128-bit style libcalls.
-    if (getTriple().isPPC64() &&
+    // FreeBSD's powerpc64le has a single IEEE-128 long double format and uses
+    // the unsuffixed libm names, so skip the IEEE-128 libcall redirection
+    // there; other PPC64 IEEE-128 targets keep it.
+    if (getTriple().isPPC64() && !getTriple().isOSFreeBSD() &&
         &getTarget().getLongDoubleFormat() == &llvm::APFloat::IEEEquad() &&
         F128Builtins.contains(BuiltinID))
       Name = F128Builtins[BuiltinID];
@@ -2601,6 +2604,7 @@ RValue CodeGenFunction::EmitBuiltinExpr(const GlobalDecl GD, unsigned BuiltinID,
   // TODO: This mutation should also be applied to other targets other than PPC,
   // after backend supports IEEE 128-bit style libcalls.
   if (getTarget().getTriple().isPPC64() &&
+      !getTarget().getTriple().isOSFreeBSD() &&
       &getTarget().getLongDoubleFormat() == &llvm::APFloat::IEEEquad())
     BuiltinID = mutateLongDoubleBuiltin(BuiltinID);
 
diff --git a/contrib/llvm-project/clang/lib/Driver/ToolChain.cpp b/contrib/llvm-project/clang/lib/Driver/ToolChain.cpp
index 07a3ae925f96..1171b2abb590 100644
--- a/contrib/llvm-project/clang/lib/Driver/ToolChain.cpp
+++ b/contrib/llvm-project/clang/lib/Driver/ToolChain.cpp
@@ -193,6 +193,10 @@ bool ToolChain::useRelaxRelocations() const {
 }
 
 bool ToolChain::defaultToIEEELongDouble() const {
+  if (getTriple().isOSFreeBSD() &&
+      getTriple().getArch() == llvm::Triple::ppc64le &&
+      getTriple().getOSMajorVersion() >= 16)
+    return true;
   return PPC_LINUX_DEFAULT_IEEELONGDOUBLE && getTriple().isOSLinux();
 }
 
diff --git a/lib/clang/freebsd_cc_version.h b/lib/clang/freebsd_cc_version.h
index cf525916fe29..584e3adfb3fd 100644
--- a/lib/clang/freebsd_cc_version.h
+++ b/lib/clang/freebsd_cc_version.h
@@ -1 +1 @@
-#define	FREEBSD_CC_VERSION 1600001
+#define	FREEBSD_CC_VERSION 1600002
diff --git a/lib/libc/powerpc64/Makefile.inc b/lib/libc/powerpc64/Makefile.inc
index 734afe95f243..1289a48baa06 100644
--- a/lib/libc/powerpc64/Makefile.inc
+++ b/lib/libc/powerpc64/Makefile.inc
@@ -1,3 +1,9 @@
+.if ${MACHINE_ARCH} == "powerpc64le"
+# Long double is IEEE binary128
+GDTOASRCS+=strtorQ.c
+SRCS+=machdep_ldisQ.c
+.else
 # Long double is 64-bits
 SRCS+=machdep_ldisd.c
+.endif
 SYM_MAPS+=${LIBC_SRCTOP}/powerpc64/Symbol.map
diff --git a/lib/libc/powerpc64/_fpmath.h b/lib/libc/powerpc64/_fpmath.h
index 9bc7450aacaf..ad640cb3ad41 100644
--- a/lib/libc/powerpc64/_fpmath.h
+++ b/lib/libc/powerpc64/_fpmath.h
@@ -28,25 +28,43 @@
 
 union IEEEl2bits {
 	long double	e;
-	struct {
 #if _BYTE_ORDER == _LITTLE_ENDIAN
-		unsigned int	manl	:32;
-		unsigned int	manh	:20;
-		unsigned int	exp	:11;
+	struct {
+		unsigned long	manl	:64;
+		unsigned long	manh	:48;
+		unsigned int	exp	:15;
 		unsigned int	sign	:1;
+	} bits;
+	struct {
+		unsigned long	manl	:64;
+		unsigned long	manh	:48;
+		unsigned int	expsign	:16;
+	} xbits;
 #else	/* _BYTE_ORDER == _LITTLE_ENDIAN */
+	struct {
 		unsigned int		sign	:1;
 		unsigned int		exp	:11;
 		unsigned int		manh	:20;
 		unsigned int		manl	:32;
-#endif
 	} bits;
+#endif
 };
 
 #define	mask_nbit_l(u)	((void)0)
 #define	LDBL_IMPLICIT_NBIT
 #define	LDBL_NBIT	0
 
+#if _BYTE_ORDER == _LITTLE_ENDIAN
+#define	LDBL_MANH_SIZE	48
+#define	LDBL_MANL_SIZE	64
+
+#define	LDBL_TO_ARRAY32(u, a) do {			\
+	(a)[0] = (uint32_t)(u).bits.manl;		\
+	(a)[1] = (uint32_t)((u).bits.manl >> 32);	\
+	(a)[2] = (uint32_t)(u).bits.manh;		\
+	(a)[3] = (uint32_t)((u).bits.manh >> 32);	\
+} while(0)
+#else	/* _BYTE_ORDER == _LITTLE_ENDIAN */
 #define	LDBL_MANH_SIZE	20
 #define	LDBL_MANL_SIZE	32
 
@@ -54,3 +72,4 @@ union IEEEl2bits {
 	(a)[0] = (uint32_t)(u).bits.manl;		\
 	(a)[1] = (uint32_t)(u).bits.manh;		\
 } while(0)
+#endif	/* _BYTE_ORDER == _LITTLE_ENDIAN */
diff --git a/lib/libc/powerpc64/gd_qnan.h b/lib/libc/powerpc64/gd_qnan.h
index 3e78a83adc71..37867d2015e5 100644
--- a/lib/libc/powerpc64/gd_qnan.h
+++ b/lib/libc/powerpc64/gd_qnan.h
@@ -10,13 +10,13 @@
 #define d_QNAN0 0x0
 #define d_QNAN1 0x7ff80000
 #define ld_QNAN0 0x0
-#define ld_QNAN1 0x7ff80000
+#define ld_QNAN1 0x0
 #define ld_QNAN2 0x0
-#define ld_QNAN3 0x0
+#define ld_QNAN3 0x7fff8000
 #define ldus_QNAN0 0x0
 #define ldus_QNAN1 0x0
 #define ldus_QNAN2 0x0
-#define ldus_QNAN3 0x7ff8
+#define ldus_QNAN3 0x0
 #define ldus_QNAN4 0x0
 #else
 #define d_QNAN0 0x7ff80000
diff --git a/lib/libc/tests/gen/Makefile b/lib/libc/tests/gen/Makefile
index 395171a86bac..f5d57275cb44 100644
--- a/lib/libc/tests/gen/Makefile
+++ b/lib/libc/tests/gen/Makefile
@@ -60,6 +60,7 @@ CFLAGS+=	-DTEST_LONG_DOUBLE
 .if ${MACHINE_CPUARCH} == "aarch64" || \
     ${MACHINE_CPUARCH} == "amd64" || \
     ${MACHINE_CPUARCH} == "i386" || \
+    ${MACHINE_ARCH} == "powerpc64le" || \
     ${MACHINE_CPUARCH} == "riscv"
 CFLAGS+=	-D__HAVE_LONG_DOUBLE
 .endif
diff --git a/lib/libc/tests/stdlib/Makefile b/lib/libc/tests/stdlib/Makefile
index a714a8cab774..0dbe059e5264 100644
--- a/lib/libc/tests/stdlib/Makefile
+++ b/lib/libc/tests/stdlib/Makefile
@@ -30,6 +30,7 @@ CFLAGS+=	-D__HAVE_FENV
 .if ${MACHINE_CPUARCH} == "aarch64" || \
     ${MACHINE_CPUARCH} == "amd64" || \
     ${MACHINE_CPUARCH} == "i386" || \
+    ${MACHINE_ARCH} == "powerpc64le" || \
     ${MACHINE_CPUARCH} == "riscv"
 CFLAGS+=	-D__HAVE_LONG_DOUBLE
 .endif
diff --git a/lib/libcompiler_rt/Makefile.inc b/lib/libcompiler_rt/Makefile.inc
index 57cfdee95541..187b1ebee76d 100644
--- a/lib/libcompiler_rt/Makefile.inc
+++ b/lib/libcompiler_rt/Makefile.inc
@@ -4,6 +4,12 @@ CRTARCH=	${MACHINE_CPUARCH:C/amd64/x86_64/:C/powerpc/ppc/}
 
 CRTSRC=		${SRCTOP}/contrib/llvm-project/compiler-rt/lib/builtins
 
+.if ${MACHINE_ARCH} == "powerpc64le"
+# Long double is IEEE binary128.  Search the generic builtins first so the
+# IEEE-128 sources win over the IBM double-double versions of the same file
+# names in the ppc/ directory.
+.PATH:		${CRTSRC}
+.endif
 .PATH:		${CRTSRC}/${CRTARCH}
 .PATH:		${CRTSRC}
 
@@ -207,6 +213,59 @@ SRCF+=		trunctfhf2
 SRCF+=		trunctfsf2
 .endif
 
+#
+# IEEE binary128 long double support for powerpc64le.  PowerPC names the
+# IEEE-128 soft-float builtins with a "kf" infix (KFmode) instead of the
+# generic "tf" (see clang's PPCSystemLibrary in RuntimeLibcalls.td), so build
+# the generic IEEE-128 sources but rename each exported symbol to its kf name.
+# The .PATH reordering above makes these resolve to the generic implementations
+# rather than the IBM double-double versions in ppc/.
+#
+.if ${MACHINE_ARCH} == "powerpc64le"
+.for _f _old _new in \
+	addtf3		__addtf3	__addkf3 \
+	subtf3		__subtf3	__subkf3 \
+	multf3		__multf3	__mulkf3 \
+	divtf3		__divtf3	__divkf3 \
+	powitf2		__powitf2	__powikf2 \
+	extendsftf2	__extendsftf2	__extendsfkf2 \
+	extenddftf2	__extenddftf2	__extenddfkf2 \
+	trunctfsf2	__trunctfsf2	__trunckfsf2 \
+	trunctfdf2	__trunctfdf2	__trunckfdf2 \
+	fixtfsi		__fixtfsi	__fixkfsi \
+	fixtfdi		__fixtfdi	__fixkfdi \
+	fixtfti		__fixtfti	__fixkfti \
+	fixunstfsi	__fixunstfsi	__fixunskfsi \
+	fixunstfdi	__fixunstfdi	__fixunskfdi \
+	fixunstfti	__fixunstfti	__fixunskfti \
+	floatsitf	__floatsitf	__floatsikf \
+	floatditf	__floatditf	__floatdikf \
+	floattitf	__floattitf	__floattikf \
+	floatunsitf	__floatunsitf	__floatunsikf \
+	floatunditf	__floatunditf	__floatundikf \
+	floatuntitf	__floatuntitf	__floatuntikf
+SRCF+=			${_f}
+CFLAGS.${_f}.c+=	-D${_old}=${_new}
+.endfor
+
+# subtf3.c implements __subtf3 as __addtf3(a, -b), so its hardcoded call to
+# __addtf3 must also be renamed to resolve to the kf-named add.
+CFLAGS.subtf3.c+=	-D__addtf3=__addkf3
+
+# Complex IEEE-128 multiply/divide keep the generic tf names: PowerPC does not
+# override MUL_C128/DIV_C128, so clang emits __multc3/__divtc3 for IEEE-128.
+SRCF+=		divtc3
+SRCF+=		multc3
+
+# The kf comparisons cannot use a -D rename of comparetf2.c because its internal
+# COMPILER_RT_ALIAS stringizes the (unrenamed) target name; provide them here.
+# comparekf2.c is FreeBSD-local, so put its directory on .PATH: this fragment is
+# also .include'd by libgcc_s/libgcc_eh, whose .CURDIR differs from ours.
+.PATH:		${SRCTOP}/lib/libcompiler_rt
+SRCS+=		comparekf2.c
+CFLAGS.comparekf2.c+=	-I${CRTSRC}
+.endif
+
 # These are already shipped by libc.a on some architectures.
 .if ${MACHINE_CPUARCH} != "riscv"
 SRCF+=		adddf3
diff --git a/lib/libcompiler_rt/comparekf2.c b/lib/libcompiler_rt/comparekf2.c
new file mode 100644
index 000000000000..bee74911c5db
--- /dev/null
+++ b/lib/libcompiler_rt/comparekf2.c
@@ -0,0 +1,43 @@
+//===-- comparekf2.c - IEEE-128 comparisons for powerpc64le -----*- C -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// PowerPC names the IEEE binary128 comparison soft-float routines with a "kf"
+// infix instead of the generic "tf".  The semantics are identical to the
+// generic comparetf2.c routines; only the exported names differ.  We provide
+// them here because comparetf2.c cannot be reused via a -D rename: its libgcc
+// compatibility aliases stringize the (unrenamed) target symbol name.
+//
+//   __lekf2(a,b) returns -1 if a < b, 0 if a == b, 1 if a > b, 1 if NaN
+//   __gekf2(a,b) returns -1 if a < b, 0 if a == b, 1 if a > b, -1 if NaN
+//   __unordkf2(a,b) returns 1 if either operand is NaN, 0 otherwise
+//
+//===----------------------------------------------------------------------===//
+
+#define QUAD_PRECISION
+#include "fp_lib.h"
+
+#if defined(CRT_HAS_TF_MODE)
+#include "fp_compare_impl.inc"
+
+COMPILER_RT_ABI CMP_RESULT __eqkf2(fp_t a, fp_t b) { return __leXf2__(a, b); }
+
+COMPILER_RT_ABI CMP_RESULT __nekf2(fp_t a, fp_t b) { return __leXf2__(a, b); }
+
+COMPILER_RT_ABI CMP_RESULT __ltkf2(fp_t a, fp_t b) { return __leXf2__(a, b); }
+
+COMPILER_RT_ABI CMP_RESULT __lekf2(fp_t a, fp_t b) { return __leXf2__(a, b); }
+
+COMPILER_RT_ABI CMP_RESULT __gtkf2(fp_t a, fp_t b) { return __geXf2__(a, b); }
+
+COMPILER_RT_ABI CMP_RESULT __gekf2(fp_t a, fp_t b) { return __geXf2__(a, b); }
+
+COMPILER_RT_ABI CMP_RESULT __unordkf2(fp_t a, fp_t b) {
+  return __unordXf2__(a, b);
+}
+
+#endif
diff --git a/lib/libcxxrt/Makefile b/lib/libcxxrt/Makefile
index 0f203b3a7cb1..4a61e84be79f 100644
--- a/lib/libcxxrt/Makefile
+++ b/lib/libcxxrt/Makefile
@@ -33,11 +33,19 @@ VERSION_MAP=	Version-32.map
 VERSION_MAP=	Version-64.map
 .endif
 
+# On powerpc64le long double is IEEE binary128, which clang mangles as
+# u9__ieee128 ("__ieee128" demangled) instead of the generic "long double" (e).
+# Rewrite the typeinfo entries so the version script matches the emitted symbols.
+LDBL_SED=
+.if ${MACHINE_ARCH} == "powerpc64le"
+LDBL_SED=	-e 's/long double/__ieee128/g'
+.endif
+
 Version-32.map: Version.map
-	sed 's/%%NEW_DELETE_TYPE%%/int/' ${.ALLSRC} > ${.TARGET}
+	sed -e 's/%%NEW_DELETE_TYPE%%/int/' ${LDBL_SED} ${.ALLSRC} > ${.TARGET}
 
 Version-64.map: Version.map
-	sed 's/%%NEW_DELETE_TYPE%%/long/' ${.ALLSRC} > ${.TARGET}
+	sed -e 's/%%NEW_DELETE_TYPE%%/long/' ${LDBL_SED} ${.ALLSRC} > ${.TARGET}
 .endif
 
 .include <bsd.lib.mk>
diff --git a/lib/msun/powerpc/Makefile.inc b/lib/msun/powerpc/Makefile.inc
index 2ae9bacdaab0..d0e6078e358c 100644
--- a/lib/msun/powerpc/Makefile.inc
+++ b/lib/msun/powerpc/Makefile.inc
@@ -1 +1,8 @@
+.if ${MACHINE_ARCH} == "powerpc64le"
+LDBL_PREC = 113
+ARCH_SRCS = ld128_compat.c
+CFLAGS.ld128_compat.c+= -fno-builtin
+SYM_MAPS+= ${.CURDIR}/${ARCH_SUBDIR}/Symbol_f128.map
+.else
 LDBL_PREC = 53
+.endif
diff --git a/lib/msun/powerpc/Symbol_f128.map b/lib/msun/powerpc/Symbol_f128.map
new file mode 100644
index 000000000000..d520bfaa0fab
--- /dev/null
+++ b/lib/msun/powerpc/Symbol_f128.map
@@ -0,0 +1,36 @@
+/*
+ */
+FBSD_1.9 {
+	acosf128;
+	asinf128;
+	atanf128;
+	atan2f128;
+	ceilf128;
+	cosf128;
+	coshf128;
+	expf128;
+	exp2f128;
+	floorf128;
+	fmaf128;
+	fmaxf128;
+	fminf128;
+	fmodf128;
+	ldexpf128;
+	llroundf128;
+	logf128;
+	log10f128;
+	log2f128;
+	lroundf128;
+	modff128;
+	nearbyintf128;
+	powf128;
+	rintf128;
+	roundf128;
+	sincosf128;
+	sinf128;
+	sinhf128;
+	sqrtf128;
+	tanf128;
+	tanhf128;
+	truncf128;
+};
diff --git a/lib/msun/powerpc/ld128_compat.c b/lib/msun/powerpc/ld128_compat.c
new file mode 100644
index 000000000000..94d6bdc2f09b
--- /dev/null
+++ b/lib/msun/powerpc/ld128_compat.c
@@ -0,0 +1,43 @@
+/*
+ * On powerpc64le, long double is IEEE binary128, which the compiler treats as
+ * _Float128.  clang lowers long double libm calls to the f128-suffixed entry
+ * points (sinf128, powf128, ...) rather than the *l names.  Provide those entry
+ * points as thin forwarders to the existing long double implementations.
+ *
+ * This file is compiled with -fno-builtin so the calls below are not themselves
+ * rewritten back to the f128 names, which would recurse infinitely.
+ */
+#include <math.h>
+
+long double acosf128(long double x) { return acosl(x); }
+long double asinf128(long double x) { return asinl(x); }
+long double atanf128(long double x) { return atanl(x); }
+long double atan2f128(long double y, long double x) { return atan2l(y, x); }
+long double ceilf128(long double x) { return ceill(x); }
+long double cosf128(long double x) { return cosl(x); }
+long double coshf128(long double x) { return coshl(x); }
+long double expf128(long double x) { return expl(x); }
+long double exp2f128(long double x) { return exp2l(x); }
+long double floorf128(long double x) { return floorl(x); }
+long double fmaf128(long double x, long double y, long double z) { return fmal(x, y, z); }
+long double fmaxf128(long double x, long double y) { return fmaxl(x, y); }
+long double fminf128(long double x, long double y) { return fminl(x, y); }
+long double fmodf128(long double x, long double y) { return fmodl(x, y); }
+long double ldexpf128(long double x, int n) { return ldexpl(x, n); }
+long long llroundf128(long double x) { return llroundl(x); }
+long double logf128(long double x) { return logl(x); }
+long double log10f128(long double x) { return log10l(x); }
+long double log2f128(long double x) { return log2l(x); }
+long lroundf128(long double x) { return lroundl(x); }
+long double modff128(long double x, long double *iptr) { return modfl(x, iptr); }
+long double nearbyintf128(long double x) { return nearbyintl(x); }
+long double powf128(long double x, long double y) { return powl(x, y); }
+long double rintf128(long double x) { return rintl(x); }
+long double roundf128(long double x) { return roundl(x); }
+void sincosf128(long double x, long double *s, long double *c) { sincosl(x, s, c); }
+long double sinf128(long double x) { return sinl(x); }
+long double sinhf128(long double x) { return sinhl(x); }
+long double sqrtf128(long double x) { return sqrtl(x); }
+long double tanf128(long double x) { return tanl(x); }
+long double tanhf128(long double x) { return tanhl(x); }
+long double truncf128(long double x) { return truncl(x); }
diff --git a/lib/msun/src/s_nearbyint.c b/lib/msun/src/s_nearbyint.c
index 3dcaf98b369a..6558ab0358b4 100644
--- a/lib/msun/src/s_nearbyint.c
+++ b/lib/msun/src/s_nearbyint.c
@@ -29,6 +29,8 @@
 #include <fenv.h>
 #include <math.h>
 
+#pragma STDC FENV_ACCESS ON
+
 /*
  * We save and restore the floating-point environment to avoid raising
  * an inexact exception.  We can get away with using fesetenv()
@@ -36,9 +38,12 @@
  * because the only exception defined for rint() is overflow, and
  * rounding can't overflow as long as emax >= p.
  *
- * The volatile keyword is needed below because clang incorrectly assumes
- * that rint won't raise any floating-point exceptions. Declaring ret volatile
- * is sufficient to trick the compiler into doing the right thing.
+ * FENV_ACCESS ON is required so the compiler does not move the rounding
+ * (which it may inline as a hardware round-to-integer instruction) across
+ * the fegetenv()/fesetenv() calls.  Without it, clang hoists the rounding
+ * before fegetenv() on the binary128 path, so the saved environment already
+ * carries the inexact flag and fesetenv() restores it, leaking the exception.
+ * The volatile keyword on ret is kept as a belt-and-suspenders guard.
  */
 #define	DECL(type, fn, rint)	\
 type				\
diff --git a/lib/msun/tests/Makefile b/lib/msun/tests/Makefile
index d723e0aaf656..5d8537c74a0a 100644
--- a/lib/msun/tests/Makefile
+++ b/lib/msun/tests/Makefile
@@ -13,6 +13,7 @@ CFLAGS+=	-I${TESTSRC:H}/libc/gen
 .if ${MACHINE_CPUARCH} == "aarch64" || \
     ${MACHINE_CPUARCH} == "amd64" || \
     ${MACHINE_CPUARCH} == "i386" || \
+    ${MACHINE_ARCH} == "powerpc64le" || \
     ${MACHINE_CPUARCH} == "riscv"
 CFLAGS+=	-D__HAVE_LONG_DOUBLE
 .endif
diff --git a/share/man/man7/arch.7 b/share/man/man7/arch.7
index 05f657d14ee4..09aee577b340 100644
--- a/share/man/man7/arch.7
+++ b/share/man/man7/arch.7
@@ -24,7 +24,7 @@
 .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 .\" SUCH DAMAGE.
 .\"
-.Dd June 5, 2026
+.Dd June 20, 2026
 .Dt ARCH 7
 .Os
 .Sh NAME
@@ -235,7 +235,7 @@ Machine-dependent type sizes:
 .It i386        Ta 4 Ta  4 Ta 12 Ta 4
 .It powerpc     Ta 4 Ta  4 Ta  8 Ta 8
 .It powerpc64   Ta 8 Ta  8 Ta  8 Ta 8
-.It powerpc64le Ta 8 Ta  8 Ta  8 Ta 8
+.It powerpc64le Ta 8 Ta  8 Ta 16 Ta 8
 .It riscv64     Ta 8 Ta  8 Ta 16 Ta 8
 .It riscv64c    Ta 8 Ta 16 Ta 16 Ta 8
 .El
@@ -336,7 +336,7 @@ currently supports Sv39 and Sv48 and defaults to using Sv39.
 .It i386        Ta hard Ta hard, 80 bit
 .It powerpc     Ta hard Ta hard, double precision
 .It powerpc64   Ta hard Ta hard, double precision
-.It powerpc64le Ta hard Ta hard, double precision
+.It powerpc64le Ta hard Ta soft, quad precision
 .It riscv64     Ta hard Ta hard, quad precision
 .It riscv64c    Ta hard Ta hard, quad precision
 .El
diff --git a/sys/powerpc/include/float.h b/sys/powerpc/include/float.h
index 58ecbcfed74e..737d283ecf38 100644
--- a/sys/powerpc/include/float.h
+++ b/sys/powerpc/include/float.h
@@ -80,6 +80,22 @@ __END_DECLS
 #define	DBL_HAS_SUBNORM	1
 #endif /* __ISO_C_VISIBLE >= 2011 */
 
+#if __LDBL_MANT_DIG__ == 113
+#define LDBL_MANT_DIG	113
+#define LDBL_EPSILON	1.925929944387235853055977942584927319E-34L
+#define LDBL_DIG	33
+#define LDBL_MIN_EXP	(-16381)
+#define LDBL_MIN	3.362103143112093506262677817321752603E-4932L
+#define LDBL_MIN_10_EXP	(-4931)
+#define LDBL_MAX_EXP	(+16384)
+#define LDBL_MAX	1.189731495357231765085759326628007016E+4932L
+#define LDBL_MAX_10_EXP	(+4932)
+#if __ISO_C_VISIBLE >= 2011
+#define	LDBL_TRUE_MIN	6.475175119438025110924438958227646552E-4966L
+#define	LDBL_DECIMAL_DIG 36
+#define	LDBL_HAS_SUBNORM 1
+#endif /* __ISO_C_VISIBLE >= 2011 */
+#else
 #define LDBL_MANT_DIG	DBL_MANT_DIG
 #define LDBL_EPSILON	((long double)DBL_EPSILON)
 #define LDBL_DIG	DBL_DIG
@@ -94,5 +110,6 @@ __END_DECLS
 #define	LDBL_DECIMAL_DIG DBL_DECIMAL_DIG
 #define	LDBL_HAS_SUBNORM DBL_HAS_SUBNORM
 #endif /* __ISO_C_VISIBLE >= 2011 */
+#endif /* __LDBL_MANT_DIG__ == 113 */
 
 #endif /* _MACHINE_FLOAT_H_ */
diff --git a/sys/sys/param.h b/sys/sys/param.h
index 7c3d02f842f9..a708baf43727 100644
--- a/sys/sys/param.h
+++ b/sys/sys/param.h
@@ -74,7 +74,7 @@
  * cannot include sys/param.h and should only be updated here.
  */
 #undef __FreeBSD_version
-#define __FreeBSD_version 1600018
+#define __FreeBSD_version 1600019
 
 /*
  * __FreeBSD_kernel__ indicates that this system uses the kernel of FreeBSD,