Skip to content

[GIAC] Bump to v2.0.2 — GIAC_TYPE_ON_8BITS default, align GCC to v10, enable GSL/LAPACK/GLPK/PARI - #13717

Open
s-celles wants to merge 13 commits into
JuliaPackaging:masterfrom
s-celles:giac-2.0.2-type-on-8bits
Open

[GIAC] Bump to v2.0.2 — GIAC_TYPE_ON_8BITS default, align GCC to v10, enable GSL/LAPACK/GLPK/PARI#13717
s-celles wants to merge 13 commits into
JuliaPackaging:masterfrom
s-celles:giac-2.0.2-type-on-8bits

Conversation

@s-celles

@s-celles s-celles commented May 13, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Bumps GIAC_jll to v2.0.2.
  • Pins the source SHA to s-celles/giac@64fdcefb, the new tip of the fork's dev branch, which defaults GIAC_TYPE_ON_8BITS=1 in src/dispatch.h. That replaces the historical unsigned char type:5; unsigned char type_unused:3; bitfield in class gen with a plain unsigned char type;.
  • Bumps preferred_gcc_version from v"8" to v"10" to match libgiac_julia_jll's recipe.

Why

The bitfield layout was access-fused differently across GCC versions. GCC's optimizer combines adjacent bitfield writes (g.type = ...; g.subtype = ...;) into a wider store using version-dependent bit placements. GCC 8 (current GIAC_jll) and GCC 10 (libgiac_julia_jll) disagree on which bits hold type, which produces a real user-visible ABI bug downstream:

  • libgiac.so writes a gen tagged _REAL (3)
  • libgiac_julia_jll (the CxxWrap wrapper) reads it back and sees _DOUBLE_ (1)
  • Result on Windows: evalf(pi, 50) returned by Giac.jl is mis-tagged, breaking float() and other conversions. Tracking in s-celles/Giac.jl#22.

A multi-platform C++ probe (investigation in s-celles/libgiac-julia-wrapper#5) ran with both modes (bitfield default vs -DGIAC_TYPE_ON_8BITS) on Linux GCC 12, macOS clang, and Windows MSYS2 GCC 15.2. Same-compiler builds produced correct _REAL tagging in both modes; the bug only manifests when libgiac and consumer were compiled with different GCC versions — exactly the production scenario.

With GIAC_TYPE_ON_8BITS=1, the type field is a plain byte at offset 0; both GCC 8 and GCC 10 emit a trivial byte store/load. No fusion, no version-dependent packing. The ABI becomes compiler-invariant.

The macro is set in dispatch.h (which is installed alongside gen.h), so every consumer of GIAC_jll automatically sees the same layout libgiac.so was compiled with — no implicit "remember to -D it on your side too" contract.

Cost

3 bits of mantissa lost on inline doubles stored within a gen (48 → 45). sizeof(gen) unchanged at 64 bits.

Coordinated downstream change

L/libgiac_julia/build_tarballs.jl should be bumped after this PR lands to pick up the new GIAC_jll ABI. The pinned commit there (s-celles/libgiac-julia-wrapper@490207923b75678ace5409e16ed5bc134bd9c7d9) doesn't need to change — only the version and the GIAC_jll compat range. I'll open that follow-up PR after this one merges.

Verification

  • Built locally with julia build_tarballs.jl --verbose --deploy=local x86_64-linux-gnu-cxx11 → success in 4m13s, all 3 products (libgiac.so, icas, share/giac/aide_cas) produced; audit clean; libstdc++ auto-detected to system (GCC 10 ABI).
  • giac's own CI on the fork green on Linux + macOS + Windows MSYS2 (unit + integration tests) for the merged patch — run 25812396543.

Test plan (for CI here)

  • All Yggdrasil platforms build successfully

s-celles added 6 commits May 13, 2026 18:36
Pins to JuliaGiac/giac@64fdcefb (PR #1 on that fork, merged into
dev) which sets GIAC_TYPE_ON_8BITS=1 by default in dispatch.h,
replacing the historical `type:5 + type_unused:3` bitfield in
`class gen` with a plain 8-bit `unsigned char`. The default ships
through the installed header so every consumer of GIAC_jll sees
the same gen layout libgiac.so was compiled with — no implicit
"remember to -D the macro too" contract.

The bitfield form was access-fused differently by GCC 8 vs GCC 10,
producing a real ABI bug in the downstream JLL pair: a gen tagged
_REAL by libgiac (built here with GCC 8) was read back as _DOUBLE_
by libgiac_julia_jll (GCC 10), breaking MPFR float conversions on
Windows (Giac.jl#22, libgiac-julia-wrapper#5 investigation).

Two coordinated changes prevent regressions of the same class:
  1. New giac default replaces the bitfield with a full byte —
     compiler-invariant layout regardless of fusion choices.
  2. preferred_gcc_version bumped 8 → 10 to match
     libgiac_julia_jll's recipe.

Cost noted by giac's author: 3 mantissa bits on inline doubles
encoded in a gen (48 → 45). sizeof(gen) unchanged.

Verified:
  - Built locally on x86_64-linux-gnu-cxx11 in 4m13s, all 3
    products (libgiac, icas, aide_cas) produced.
  - giac's own CI green on Linux, macOS, Windows MSYS2 post-merge
    (unit + integration tests).
Switch -Dgsl from disabled to enabled and add GSL_jll 2.8.1 as a
dependency. Validated by a clean cross-build for x86_64-linux-gnu;
icas and libgiac.so auto-map to libgsl.so.28 and libgslcblas.so.0
from GSL_jll.
Switch -Dlapack from disabled to enabled and add OpenBLAS32_jll
(LP64 ABI) plus CompilerSupportLibraries_jll as dependencies.
OpenBLAS_jll itself is ILP64 with _64_ symbol suffixes that Giac
does not expect.

Meson's dependency('blas')/dependency('lapack') only look for
blas.pc/lapack.pc; the script now drops self-contained wrappers
in ${prefix}/lib/pkgconfig before meson setup (skipped on macOS,
where Giac picks up the Accelerate framework).

Validated by a clean cross-build for x86_64-linux-gnu; eigenvals
on a 300x300 float matrix completes in ~70 ms (vs seconds without
LAPACK).
Switch -Dglpk from disabled to enabled and add GLPK_jll 5.0.1 as a
dependency. GLPK_jll ships no pkg-config file, so the script drops
a self-contained glpk.pc in ${prefix}/lib/pkgconfig before meson
setup.

Validated by a clean cross-build for x86_64-linux-gnu; libgiac.so
auto-maps to libglpk.so.40 and lpsolve returns the expected
optima on small LP test problems.
GLPK_jll doesn't ship a riscv64 binary, so building Giac with
-Dglpk=enabled fails on those targets. Gate the GLPK option (and the
glpk.pc shim) behind a target check so riscv64 falls back to
-Dglpk=disabled while every other platform keeps GLPK enabled.
Restrict GLPK_jll to platforms that ship artifacts, mirroring the
GLPK_OPT=disabled branch in the script.
@s-celles

Copy link
Copy Markdown
Contributor Author

#13754 should probably be considered before this. So marking this one as draft

@s-celles
s-celles marked this pull request as draft May 18, 2026 06:49
GLPK_jll now ships riscv64-linux-gnu artifacts (JuliaPackaging#13754),
so drop the platform filter and conditional and bump compat to 5.0.2.
@s-celles s-celles changed the title [GIAC] Bump to v2.0.2 — GIAC_TYPE_ON_8BITS default, align GCC to v10 [GIAC] Bump to v2.0.2 — GIAC_TYPE_ON_8BITS default, align GCC to v10, enable GSL/LAPACK/GLPK May 19, 2026
@s-celles
s-celles marked this pull request as ready for review May 19, 2026 06:34
@s-celles

s-celles commented May 21, 2026

Copy link
Copy Markdown
Contributor Author

#13771 should also be considered before this.
So re-converting as draft also - sorry for noise

@s-celles
s-celles marked this pull request as draft May 21, 2026 14:40
@s-celles s-celles changed the title [GIAC] Bump to v2.0.2 — GIAC_TYPE_ON_8BITS default, align GCC to v10, enable GSL/LAPACK/GLPK [GIAC] Bump to v2.0.2 — GIAC_TYPE_ON_8BITS default, align GCC to v10, enable GSL/LAPACK/GLPK/PARI Jun 4, 2026
@imciner2

imciner2 commented Jun 5, 2026

Copy link
Copy Markdown
Member

I see that #13771 has been merged - is this ready to continue?

@s-celles

s-celles commented Jun 5, 2026

Copy link
Copy Markdown
Contributor Author

Hum... it doesn't seems to be so easy than what I was expecting (see previous Buildkite runs).
Moreover I'm currently quite busy. That's why I'm keeping that as draft.

s-celles added a commit to JuliaGiac/Giac.jl that referenced this pull request Aug 1, 2026
main was red on both Windows runners: 23 failures, all in
test_libpari_ext.jl, all in reals. Every other part of the bridge passes
there.

Cause — a known ABI bug between the two binaries, not a Giac quirk and
not something this bridge can work around. `class gen` stored its tag as
a bitfield, `unsigned char type:5; unsigned char type_unused:3;`. GCC
fuses adjacent bitfield writes into one wider store and picks the bit
placement in a version-dependent way. GIAC_jll is built with GCC 8 and
libgiac_julia_jll with GCC 10, so they disagree about which bits hold
`type`: libgiac writes a gen tagged _REAL and the wrapper reads back
_DOUBLE_. The suite observes it directly:

    Expression: Giac.giac_type(wide) == REAL
    Evaluated:  DOUBLE == REAL

Everything else follows. Giac.jl believes it holds a Float64, prints at
the global `Digits` — default 12 — and every real crosses truncated to
twelve significant digits:

    expected  3.1415926535897932384626433832795028842
    obtained  3.14159265359 0000062

identically at 64, 128, 192, 256, 384, 512 and 1024 bits, because 53 bits
is all a mis-tagged DOUBLE ever had. Raising `Digits` would change
nothing: the precision is lost at the tag, not at the printer.

Fix in flight upstream: JuliaPackaging/Yggdrasil#13717 bumps GIAC_jll to
v2.0.2 with GIAC_TYPE_ON_8BITS=1, making `type` a plain byte at offset 0
and the ABI compiler-invariant, followed by a libgiac_julia_jll bump.
Same root cause as #22 and the probe in #26.

The assertions are marked `@test_broken` rather than skipped, so that
when both JLLs land the suite reports unexpected passes and tells whoever
did it to delete the markers.

One thing this did establish about the bridge itself: its decode verifies
itself by re-encoding the candidate and comparing printed forms, and that
check is blind here. Re-encoding the truncated value also prints twelve
digits, the forms agree, and a wrong answer is confirmed. The check
establishes the printer's self-consistency, not its fidelity — the two
coincide only where the tag is right. Worth stating, because I had
described that check in the docs as what makes a text-mediated step safe.

The documentation claimed Giac's printer emits a REAL at the value's own
precision "rather than at a global setting", presented as measured fact.
It was measured on Linux only. Corrected, with a dedicated section, a
warning admonition on the reals section, and an entry in the limitations
list.
s-celles added a commit to JuliaGiac/Giac.jl that referenced this pull request Aug 1, 2026
* fix(test): mark the bridge's real round trips broken on Windows

main was red on both Windows runners: 23 failures, all in
test_libpari_ext.jl, all in reals. Every other part of the bridge passes
there.

Cause. On Linux and macOS, GIAC renders a REAL at *that value's*
precision — 156 significant digits for a 512-bit value — and the global
`Digits` has no effect on it. The bridge reads a Giac REAL back by
decoding its printed decimal and relies on exactly that.

On Windows, GIAC honours `Digits` instead, default 12. Every real crossed
truncated to twelve significant digits:

    expected  3.1415926535897932384626433832795028842
    obtained  3.14159265359 0000062

at every precision asked for — 64, 128, 192, 256, 384, 512, 1024 all give
the same twelve digits, so it is a ceiling, not a rounding difference.
`Digits` governs GIAC's parser on Windows too, so
`convert(GiacExpr, ::BigFloat)` cannot even store a wide value: it lands
on DOUBLE rather than REAL.

Worth recording plainly, because it is the more useful lesson: the
decode *verifies itself* by re-encoding the candidate and comparing
printed forms, and that check was blind here. Re-encoding the truncated
value also prints twelve digits, the forms agree, and a wrong answer is
confirmed. The check establishes the printer's self-consistency, not its
fidelity; the two coincide only where the printer is faithful. A guard
that inspires confidence without providing it is worse than none.

The assertions are marked `@test_broken` rather than skipped, so the
suite errors if they start passing and tells whoever fixed the platform
difference to remove the marker.

The documentation claimed Giac's printer emits a REAL at the value's own
precision "rather than at a global setting", presented as measured fact.
It was measured on Linux only. Corrected, with a dedicated section, a
warning admonition on the reals section, and an entry in the limitations
list.

Not attempted here: raising `Digits` around the read, which might restore
Windows. That needs a Windows runner to judge and belongs on its own
branch — this one only stops main lying about the state of the tests.

* fix(test): mark the bridge's real round trips broken on Windows

main was red on both Windows runners: 23 failures, all in
test_libpari_ext.jl, all in reals. Every other part of the bridge passes
there.

Cause — a known ABI bug between the two binaries, not a Giac quirk and
not something this bridge can work around. `class gen` stored its tag as
a bitfield, `unsigned char type:5; unsigned char type_unused:3;`. GCC
fuses adjacent bitfield writes into one wider store and picks the bit
placement in a version-dependent way. GIAC_jll is built with GCC 8 and
libgiac_julia_jll with GCC 10, so they disagree about which bits hold
`type`: libgiac writes a gen tagged _REAL and the wrapper reads back
_DOUBLE_. The suite observes it directly:

    Expression: Giac.giac_type(wide) == REAL
    Evaluated:  DOUBLE == REAL

Everything else follows. Giac.jl believes it holds a Float64, prints at
the global `Digits` — default 12 — and every real crosses truncated to
twelve significant digits:

    expected  3.1415926535897932384626433832795028842
    obtained  3.14159265359 0000062

identically at 64, 128, 192, 256, 384, 512 and 1024 bits, because 53 bits
is all a mis-tagged DOUBLE ever had. Raising `Digits` would change
nothing: the precision is lost at the tag, not at the printer.

Fix in flight upstream: JuliaPackaging/Yggdrasil#13717 bumps GIAC_jll to
v2.0.2 with GIAC_TYPE_ON_8BITS=1, making `type` a plain byte at offset 0
and the ABI compiler-invariant, followed by a libgiac_julia_jll bump.
Same root cause as #22 and the probe in #26.

The assertions are marked `@test_broken` rather than skipped, so that
when both JLLs land the suite reports unexpected passes and tells whoever
did it to delete the markers.

One thing this did establish about the bridge itself: its decode verifies
itself by re-encoding the candidate and comparing printed forms, and that
check is blind here. Re-encoding the truncated value also prints twelve
digits, the forms agree, and a wrong answer is confirmed. The check
establishes the printer's self-consistency, not its fidelity — the two
coincide only where the tag is right. Worth stating, because I had
described that check in the docs as what makes a text-mediated step safe.

The documentation claimed Giac's printer emits a REAL at the value's own
precision "rather than at a global setting", presented as measured fact.
It was measured on Linux only. Corrected, with a dedicated section, a
warning admonition on the reals section, and an entry in the limitations
list.
s-celles added a commit to JuliaGiac/libgiac-julia-wrapper that referenced this pull request Aug 1, 2026
Nothing watched them. Three of the four pinned actions are behind, one by
three majors:

    actions/checkout           v4  ->  v7.0.1
    julia-actions/setup-julia  v2  ->  v3.0.2
    julia-actions/cache        v2  ->  v3.2.0
    msys2/setup-msys2          v2  ->  v2.32.0   (current major)

The last is the Windows toolchain, and Windows is where this wrapper's
ABI behaviour actually matters — the `_REAL` / `_DOUBLE_` mis-tagging
tracked in JuliaPackaging/Yggdrasil#13717. A CI whose toolchain drifts
quietly is a poor judge of whether that fix works.

One PR per action rather than a combined bump, so a failure is
attributable. That matters most when crossing a major: a bot updates the
version string, never the configuration the new major expects.
`setup-julia` v3 rejects an explicit `arch: x64` on Apple Silicon —
harmless here, since this matrix does not pin `arch`, but it broke the
equivalent bump in JuliaGiac/Giac.jl#52.

Scope, stated in the file so this is not mistaken for coverage:
Dependabot has no meson ecosystem, so the C++ dependencies stay
unwatched. This covers the four GitHub Actions and nothing else.

.gitignore needed one line. It ignores everything by default (`*` on line
1) and allows files back a pattern at a time; the allowlist reached
`.github/workflows/*.yml` but not the root of `.github/`, and GitHub
requires the config at exactly `.github/dependabot.yml`. Verified that no
other file changes status.
s-celles referenced this pull request in JuliaGiac/Giac.jl Aug 7, 2026
* fix(docs): silence the api/core.md HTML size-threshold warning

api/core.md legitimately exceeds Documenter's 100 KiB soft threshold
(103.68 KiB) carrying the full core-API docstring set plus worked
examples. size_threshold_ignore is Documenter's own recommended
alternative to raising the general limit.

missing_docs stays in warnonly for the ~2000 auto-generated
Giac.Commands functions: checkdocs_ignored_modules was tried, but it
drops the whole submodule from Documenter's known bindings and breaks
the @refs that already resolve for invoke_cmd, hold_cmd, release and
Giac.Commands itself.

AI assistance was used for this change.

* chore: bump version to 0.14.3

Patch release: accumulated fixes and additions since 0.14.2 (LibPARI
bridge extension, TermInterface head/children, STRNG conversion fix,
CI/compat fixes, docs size-threshold fix). No deliberate API changes
— the STRNG behaviour change corrects output to match the documented
conversion table, so this stays a patch.

AI assistance was used for this change.

* docs(changelog): record the ModelContextProtocol 0.6 compat bump

#60/#61 landed on main after this release branch was cut; folding them
into the 0.14.3 Unreleased block along with the LibPARI 0.18.1 release
that unblocked them.

AI assistance was used for this change.
s-celles and others added 2 commits August 17, 2026 19:38
The giac fork moved to the JuliaGiac organisation; update the GitSource
URL accordingly (the old one still redirects, but the recipe should name
the canonical repository).

Bump the pinned commit to 4034ebe, which adds two fixes needed by this
PR's -Dpari=enabled:

  - GCC <= 10 misparsed PARI's `long rank(GEN x);` as a deduction guide
    for std::rank under -std=c++17, breaking 15 of the 18 platforms in
    build 29961. Only riscv64 (forced to GCC 14) and the two FreeBSD
    targets (clang) escaped it.

  - is_prime() relayed PARI's boolean unchanged, so linking PARI
    downgraded every answer above INT_MAX from "proven prime" (2) to
    "probably prime" (1). giac's own check/testcas covers this.

With both, giac's test suite is 8/8 with PARI enabled, on the same
revision that was 5/8 before.

Also correct the now-stale "disabling all optional dependencies" comment.

Assisted by AI
Bump the pinned commit to 42d064e, which adds the two fixes that kept
x86_64-apple-darwin and aarch64-apple-darwin from configuring:

  - host_machine.subsystem() aborts outright when a cross file leaves
    `subsystem` unset, which BinaryBuilder's darwin toolchain does:
    "meson.build:132:20: ERROR: Subsystem not defined or could not be
    autodetected". Replaced by an explicit apple_subsystem option. The call
    could not distinguish Mac Catalyst from a native desktop build anyway --
    both report 'macos' -- so native macOS was being compiled with Catalyst
    flags.

  - cc.find_library('pari', dirs: [...]) narrows the search to the given
    directories rather than adding to them, so on darwin PARI was looked for
    only under Homebrew prefixes that do not exist in the sandbox:
    "Library pari found: NO". Linux was spared only because `dirs` was empty
    there. Now the default path is searched first.

Verified locally with BinaryBuilder on the real darwin toolchain, not just
by reading logs: x86_64-apple-darwin builds end to end, "Library pari found:
YES", audit clean, GIAC.v2.0.2.x86_64-apple-darwin.tar.gz produced.

Assisted by AI
@s-celles
s-celles marked this pull request as ready for review August 19, 2026 08:09
@s-celles

Copy link
Copy Markdown
Contributor Author

GM @imciner2

I think this is ready for review.
But the full chain of PR may be a bit complicated... sorry about that!

TL;DR — three PRs, and the order matters. Only the first one is "urgent".

# Where What
1 General#164788 cap libgiac_julia_jll v0.5.0 to GIAC_jll 2.0.1
2 this PR GIAC_jll 2.0.2
3 #14478 libgiac_julia v0.5.1, rebuilt against 2.0.2

Why the ordering

GIAC 2.0.2 changes the memory layout of giac::gen: the type tag moves from a 5-bit bitfield to a plain byte (GIAC_TYPE_ON_8BITS), because GCC 8 and GCC 10 packed that bitfield differently. libgiac_julia_jll is a CxxWrap shim compiled against those headers, so it is bound to one GIAC ABI.

The already-published v0.5.0 declares compat = "2.0.1", which resolves as [2.0.1, 3.0.0). So the moment 2.0.2 is registered, and until the cap lands, the resolver can pair the old wrapper with the new library — no error at install
or load, just misread gen tags (REAL seen as DOUBLE). That is exactly the corruption 2.0.2 exists to remove, so General#164788 wants to go in first.

Nothing blocks this PR itself; #14478 stays draft until 2.0.2 is registered, since BinaryBuilder resolves GIAC_jll from General.

What is in this PR beyond the version bump

Enabling PARI turned out to need three upstream fixes, all now in the pinned commit:

  • GCC ≤ 10 parses PARI's long rank(GEN x); as a deduction guide for std::rank under -std=c++17, which broke 15 of 18 platforms. Reproduced on GCC 10.1.0 and 10.2.0, both fixed; 10.5.0 and clang were never affected.
  • host_machine.subsystem() aborts configuration when a cross file leaves subsystem unset, as BinaryBuilder's darwin toolchain does.
  • cc.find_library('pari', dirs: [...]) narrows the search rather than widening it, so darwin looked for PARI only under Homebrew prefixes.

Validation

CI here is green on all 18 platforms. Beyond that, I built the whole chain locally with BinaryBuilder and ran Giac.jl against the resulting pair (GIAC_jll 2.0.2 + libgiac_julia_jll 0.5.1, both --deploy=local):

Test Summary: | Pass  Broken  Total
Giac.jl       | 9507       2   9509
     Testing Giac tests passed

which is the one thing CI cannot show, since it builds each JLL in isolation and never loads them together.

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.

2 participants