[GIAC] Bump to v2.0.2 — GIAC_TYPE_ON_8BITS default, align GCC to v10, enable GSL/LAPACK/GLPK/PARI - #13717
[GIAC] Bump to v2.0.2 — GIAC_TYPE_ON_8BITS default, align GCC to v10, enable GSL/LAPACK/GLPK/PARI#13717s-celles wants to merge 13 commits into
Conversation
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.
|
#13754 should probably be considered before this. So marking this one as draft |
GLPK_jll now ships riscv64-linux-gnu artifacts (JuliaPackaging#13754), so drop the platform filter and conditional and bump compat to 5.0.2.
|
#13771 should also be considered before this. |
|
I see that #13771 has been merged - is this ready to continue? |
|
Hum... it doesn't seems to be so easy than what I was expecting (see previous Buildkite runs). |
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.
* 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.
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.
* 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.
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
|
GM @imciner2 I think this is ready for review. TL;DR — three PRs, and the order matters. Only the first one is "urgent".
Why the orderingGIAC 2.0.2 changes the memory layout of The already-published v0.5.0 declares Nothing blocks this PR itself; #14478 stays draft until 2.0.2 is registered, since BinaryBuilder resolves What is in this PR beyond the version bumpEnabling PARI turned out to need three upstream fixes, all now in the pinned commit:
ValidationCI 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 ( which is the one thing CI cannot show, since it builds each JLL in isolation and never loads them together. |
Summary
GIAC_jlltov2.0.2.s-celles/giac@64fdcefb, the new tip of the fork'sdevbranch, which defaultsGIAC_TYPE_ON_8BITS=1insrc/dispatch.h. That replaces the historicalunsigned char type:5; unsigned char type_unused:3;bitfield inclass genwith a plainunsigned char type;.preferred_gcc_versionfromv"8"tov"10"to matchlibgiac_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 (currentGIAC_jll) and GCC 10 (libgiac_julia_jll) disagree on which bits holdtype, which produces a real user-visible ABI bug downstream:libgiac.sowrites agentagged_REAL(3)libgiac_julia_jll(the CxxWrap wrapper) reads it back and sees_DOUBLE_(1)evalf(pi, 50)returned byGiac.jlis mis-tagged, breakingfloat()and other conversions. Tracking ins-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_REALtagging 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, thetypefield 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 alongsidegen.h), so every consumer ofGIAC_jllautomatically sees the same layoutlibgiac.sowas compiled with — no implicit "remember to-Dit 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.jlshould be bumped after this PR lands to pick up the newGIAC_jllABI. The pinned commit there (s-celles/libgiac-julia-wrapper@490207923b75678ace5409e16ed5bc134bd9c7d9) doesn't need to change — only the version and theGIAC_jllcompat range. I'll open that follow-up PR after this one merges.Verification
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).Test plan (for CI here)