Skip to content

build: f128 without libquadmath - #48

Open
ondrik wants to merge 2 commits into
fix/portability-gmp-ltofrom
feat/f128-without-quadmath
Open

ondrik wants to merge 2 commits into
fix/portability-gmp-ltofrom
feat/f128-without-quadmath

Conversation

@ondrik

@ondrik ondrik commented Oct 14, 2026 •

Copy link
Copy Markdown
Collaborator

Stacked on #47. f128 no longer needs GCC's libquadmath, so it builds under clang and on ARM Linux.

Why this is possible

No CPU does IEEE binary128 arithmetic in hardware, x86 included. Every __float128 add or multiply is already a software routine that the compiler emits. What f128 needed was a binary128 type plus four functions (round, sqrt, pow, print), not libquadmath.

What changes

leaf_primitive_double.h picks the type the toolchain has:

where type functions
aarch64, riscv64, s390x Linux long double (already binary128 there) roundl, sqrtl, powl, %Lg
x86_64, ppc64le __float128 glibc's roundf128, sqrtf128, powf128, strfromf128
arm64 macOS neither stops with a clear message (see below)

Checked on x86_64 (GCC 13)

  • Identical output. glibc's *f128 functions and libquadmath share their implementation. Over 613 benchmark runs (all of benchmarks/no-measure, plus --symbolic for LP-*, capped at 20 s each), every res.dot has exactly the same labels as the libquadmath build.
  • No libquadmath dependency: ldd MEDUSA_buddy_doubles_f128 no longer lists it.
  • Tests: make test passes, and so does make test-grover-f128 USE_CXX=1 (glibc's _Float128 declarations under C++).
  • clang: glibc declares its *f128 functions only for compilers it trusts with _Float128, which excludes clang. libm and libc export them regardless, so the header declares them itself when glibc doesn't (!__HAVE_FLOAT128).
  • Platform probe (ci: probe the build across Ubuntu releases, clang and macOS #46, stacked on this PR): f128 builds and passes the unit suite, Grover matrix and circuit smoke test on Ubuntu 22.04, 24.04 and 26.04, and on 24.04 under clang 18.

CI note: this PR targets #47's branch, so tests.yml (which runs on PRs into main or devel) doesn't run on it. Retarget it to devel once #47 merges.

macOS

arm64 macOS has no binary128 type at all. Getting f128 there needs a software implementation, which I'm exploring separately with Berkeley SoftFloat. That work will be Mac-only.

🤖 Generated with Claude Code

https://claude.ai/code/session_012q87VyzNBV8gtH2ntMeKoM

@github-actions

github-actions Bot commented Oct 14, 2026 •

Copy link
Copy Markdown

Claude Code review (CERIT, model coder) of 0aa09e3

I've reviewed the diff (all changed files are included in claude-review-input/pr.diff; nothing is truncated) and read the surrounding sources (leaf_primitive_double.h/.c, Makefile, hash.h, the include chains and the test sweep targets). Findings, most severe first:

  • src/interface_implementations/leaf_primitives/leaf_primitive_double.h:118-133 - The new __SIZEOF_FLOAT128__ branch has no check that the four *f128 symbols actually exist, and the header's own declarations are gated on defined(__GLIBC__) && !__HAVE_FLOAT128. On any libc that has __float128 but not the glibc f128 exports, the old __has_include(<quadmath.h>) #error is gone and the build fails much later and much less clearly. Concrete scenario: make (default LEAF_FLOAT_TYPE=3) on Alpine/musl x86_64 with GCC — GCC defines __SIZEOF_FLOAT128__ there, __GLIBC__ is undefined so no declarations are emitted, LEAF_ROUND/LEAF_SQRT/LEAF_POW/strfromf128 are called without prototypes (implicit-declaration warnings, and passing a __float128 argument to an unprototyped function is undefined behaviour), followed by undefined reference to roundf128 at link. The same happens on glibc older than ~2.27 (e.g. CentOS 7 with a devtoolset GCC, where the libquadmath build previously worked), except there __HAVE_FLOAT128 is also undefined, so the header's extern declarations are emitted, the compile is clean, and the only diagnostic is the linker error. A __GLIBC_PREREQ check (or a link-time #error when neither __HAVE_FLOAT128 nor a known-good libc is present) would restore the old fail-fast behaviour.

  • src/interface_implementations/leaf_primitives/leaf_primitive_double.h:7-15,122 - The GCC/glibc path depends on __STDC_WANT_IEC_60559_TYPES_EXT__ being defined before the first glibc <math.h>/<stdio.h> in the translation unit, because the header's fallback declarations only fire when !__HAVE_FLOAT128; when glibc declares them itself (__HAVE_FLOAT128 == 1 under GCC), nothing re-declares them if <stdio.h>/<math.h> was already included. Today this is only safe because every Makefile rule (BUDDY_DOUBLES_CFLAGS, SYLVAN_DOUBLES_CFLAGS, and all test rules) force-includes this header with -include before anything else, but the header is also included directly from src/norm_track.c:9 and src/interface_implementations/leaves/leaf_reim_double.h:13. A new source file that includes <stdio.h> before leaf_primitive_double.h and is compiled without the -include gets no strfromf128/roundf128 declarations at all and fails with implicit-declaration errors at the to_str_generic/snap call sites — the "Must precede <math.h>/<stdlib.h>" comment is the only thing enforcing the invariant, and the fallback block doesn't cover the case where it's violated.

  • src/interface_implementations/leaf_primitives/leaf_primitive_double.h:98-116 and leaf_primitive_double.c:48-49 - The new LEAF_QUAD_IS_LONG_DOUBLE branch is a complete second implementation of the default f128 leaf (typedefs, roundl/sqrtl/powl/fabsl, the 1e-28L/1e-32L epsilons, %.30Lg printing) that no test in this repository can compile or run: test-unit-leaf-types (Makefile:510-519) sweeps LEAF_FLOAT_TYPE 0-3, but on x86_64 every one of those takes the __float128 branch, so LDBL_MANT_DIG == 113 code is dead on all CI runners (the ARM coverage lives in the separate stacked PR ci: probe the build across Ubuntu releases, clang and macOS #46, not this one). A wrong function name or epsilon in that block merges with every check green. Since the whole point of the block is aarch64/riscv64/s390x, a cheap guard would be a compile-time assertion inside the branch (e.g. _Static_assert(sizeof(leaf_scalar_t) == 16 && LDBL_MANT_DIG == 113, ...), or a unit assertion that LEAF_SQRT2INV * LEAF_SQRT2INV is within LEAF_REL_EPS of 0.5), which at least pins the constants the branch claims to provide.

One thing I checked and believe is fine, for the record: replacing M_SQRT1_2q with the 36-digit literal 0.707106781186547524400844362104849039q (leaf_primitive_double.h:149) looks like it rounds to the same binary128 value — the true 1/√2 lies roughly 0.49 ulp above the nearest binary128 below it, and the literal is only ~0.006 ulp short, so it stays inside the same rounding interval. I can't execute a bit-exact comparison here, so treat that as high-confidence rather than verified; note also that the PR's "identical labels over 613 runs" evidence would not resolve a 1-ulp difference in this constant on its own, since the 30-digit printing only pins ~1e-30.
Automated and advisory: it may be wrong, and it does not block merging. Run.

ondrik and others added 2 commits October 28, 2026 00:15
No CPU does IEEE binary128 in hardware; the compiler emits soft-float
calls either way, so what f128 needed was a binary128 type and a few
functions, not libquadmath. It now uses
- long double where that already is binary128 (aarch64, riscv64 and
  s390x Linux), with the long double libm functions;
- otherwise __float128 (x86_64, ppc64le) with glibc's roundf128,
  sqrtf128, powf128 and strfromf128 in place of roundq, sqrtq, powq and
  quadmath_snprintf.
Neither needs GCC's libquadmath, so f128 now builds under clang too.
arm64 macOS has neither type and stops with a clear message.

glibc's *f128 functions and libquadmath share their implementation:
on 613 benchmark runs (classic, and --symbolic for LP-*), every res.dot
has the same labels as before. make test and the USE_CXX=1 Grover
matrix pass.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012q87VyzNBV8gtH2ntMeKoM
glibc declares roundf128, sqrtf128, powf128 and strfromf128 only for
compilers it trusts with _Float128, which excludes clang, so f128 failed
under clang (seen in #46). libm and libc export them regardless, and
_Float128 and __float128 share one ABI on these targets.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012q87VyzNBV8gtH2ntMeKoM

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant