All notable changes to LibPARI are documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
- TagBot workflow (
.github/workflows/TagBot.yml) to automatically create Git tags and GitHub releases when versions are merged into the Julia General registry. - CompatHelper workflow (
.github/workflows/CompatHelper.yml) to keep[compat]entries current via automated pull requests.
Restores the 3-OS × 2-Julia CI hard gate that turned red on commit
e6cfcb4 (0.15.0). The three failing test items and the
Documentation job are addressed in one patch release.
- Generator-related tests on a cold runner. The three
@testitems that spawngen/generate.jlas a subprocess (regenerating the bindings is byte-for-byte reproducibleandthe generator omits, skips, and excludes — never silentlyintest/generator_tests.jl;NFR-04 — the binding generator is byte-identical across runsintest/acceptance_tests.jl) now instantiate thegen/environment before launching the subprocess. CI runners (which never carry agen/Manifest.toml, since it is excluded by the**/Manifest.tomlglob) can now run the generator successfully on every cell of the matrix. The instantiation subprocess is launched withJULIA_LOAD_PATH=@:@stdlibviaaddenvto overridePkg.test()'s@-only LOAD_PATH inheritance, which would otherwise hide thePkgstdlib from the child process.
gen/Project.toml—PrecompileToolsis now a direct dep with a defensive[compat]bound of"1.0 - 1.2", capping the gen environment at the last Julia-1.10-safe series. The bound stops agen/Manifest.tomlresolved under Julia 1.12 from re-using PrecompileTools 1.3.4 on Julia 1.10 (seeupstream-bugs.md2026-06-04 entry).docsjob in.github/workflows/CI.yml— declarespermissions: contents: write.deploydocsneeds that scope on the workflow-issuedGITHUB_TOKENto push to thegh-pagesbranch when no repositoryDOCUMENTER_KEYis configured.
- New
upstream-bugs.mdentry for theBase.StaticData-on-Julia-1.10 defect in PrecompileTools 1.3.4 —src/invalidations.jl:15references the symbol unguarded; the defect is shielded for LibPARI by the newgen/Project.tomlcompat bound.
- Windows 64-bit (LLP64) ABI — the whole
ccallboundary. PARI redefineslongtolong long(64-bit) on Windows 64-bit (parigen.h:#ifdef _WIN64 / #define long long long), so the PARI word is pointer-sized on every platform. LibPARI, however, marshalled every PARI word andGENas Julia'sClong/Culong, which are only 32-bit on Windows — truncatingGENpointers and reading 32-bit type-tag words. This madewindows-latestfail at precompilation (bitcast: argument size does not match size of target type, then a_trap_reinterpretMethodError); Windows had never actually passed, only been masked by the formercontinue-on-error. The fix replacesClong→IntandCulong→UIntacross the entire ccall boundary — the hand-written core (trap.jl,conversions.jl,errors.jl,gen.jl,numeric.jl,evaluator.jl,concurrency.jl,lifecycle.jl), the binding generator (gen/generate.jl), and the ~11 200 regenerated bindings insrc/bindings.jl.Int/UIntare pointer-sized on every platform (Int64 on 64-bit, including Win64) and identical toClong/Culongon Linux/macOS LP64, so the change is a no-op there (verified: 311/311 on both Julia 1.10 and Julia release). The C trap shim now returnsintptr_t. The binding generator's output remains byte-identical across runs (reproducibility contract intact). The test suite's own raw PARIccalls (test/error_tests.jl,test/gen_tests.jl,test/gen_memory_tests.jl,test/conversions_tests.jl,test/bindings_tests.jl) were updated the same way — they had the identicalPtr{Clong}truncation. - Windows path separator in the gen-instantiate test prelude. The
prelude added in this release set
JULIA_LOAD_PATH=@:@stdlib; theLOAD_PATHseparator is;on Windows, so the value was malformed there and the instantiate subprocess could not find thePkgstdlib. The separator is now chosen per platform (Sys.iswindows() ? ';' : ':'). - Reproducibility tests vs. git CRLF on Windows. The generator
always writes
src/bindings.jlwith LF, but git'score.autocrlfchecks the committed (LF) file out as CRLF on the Windows runner, so the working-tree copy and the freshly generated file differed only by line endings — failing the byte-equality check in NFR-04 (test/acceptance_tests.jl) and the M4 reproducibility item (test/generator_tests.jl). Both comparisons now normalize\r\nto\nbefore comparing; generator determinism itself is unchanged (verified by the two-run check on Linux/macOS). - Concurrent-error stress crash on Windows. Concurrency-safe PARI
error handling requires LibPARI's C trap shim (a PARI error must
longjmpacross C frames only). The shim is not active on the Windows runner, so raising a PARI error concurrently from many threads routed through PARI's cross-threadmt_err_recoverand segfaulted (EXCEPTION_ACCESS_VIOLATION) — the documented upstream limitation (upstream-bugs.md). The multi-threaded concurrent-error testsets intest/concurrency_tests.jlare now gated onLibPARI._TRAP_AVAILABLE[]and skipped with a logged notice where the shim is unavailable; they re-enable automatically if a future toolchain makes the shim work on Windows. The other concurrency guarantees (cross-thread correctness, leak-safety, parallelism) still run on every platform, and single-threaded error handling is covered unconditionally bytest/error_tests.jl.
- On platforms without LibPARI's C trap shim (currently the Windows CI runner), error handling is single-threaded-safe only: a PARI error raised concurrently from multiple Julia threads is not guaranteed safe. Single-threaded use — the default — is fully supported on every platform. Linux and macOS validate the concurrent-error guarantees in CI.
test/concurrency_tests.jl"concurrent calls run in parallel across threads". A timing-based speedup floor is flaky by construction on shared CI runners: run26941877211measuredspeedup = 1.21(macOS, 3 threads) and run26944653380measuredspeedup = 0.84(ubuntu, 4 threads — parallel slower than serial, from vCPU oversubscription). UnderCI=truethe parallelism assertion is now advisory (the measured speedup is logged, only run-completion is asserted); a local developer still gets the strictspeedup > 1.3gate. The correctness, leak-safety, and error-safety concurrency tests continue to gate on every cell.
PARI_jll is now registered in the Julia General registry — LibPARI
installs with a plain Pkg.add("LibPARI") on Linux, macOS, and Windows.
- Cross-platform CI hard gate. The GitHub Actions
testjob is now a hard 3-OS matrix (Ubuntu, macOS, Windows × Julia 1.10 and 1) — any platform failure fails the build (REQ-QA-07, REQ-QA-08).Documentationis likewise a hard gate (REQ-DOC-01, REQ-DOC-06) and auto-deploys the Documenter.jl site to GitHub Pages from the default branch.
- Installation is now
Pkg.add("LibPARI"). The previousPkg.developworkaround for the unregisteredPARI_jllis no longer needed. README and the documentationGetting startedpage are updated accordingly. - The CI workflow's
continue-on-errorlines and the stubResolve PARI_jllsteps — placeholders that existed only until PARI_jll was registered — have been removed from both thetestanddocsjobs. - LibPARI's
Manifest.tomlnow resolvesPARI_jll2.17.3+0 from the registry instead of a local development path.
Gen is now a complete Julia Number.
- A complete
Numberinterface forGen.Gennow carries the full JuliaNumbercontract: it mixes with every standard Julia numeric type (Int,BigInt,Float64,BigFloat,Rational,Complex) in arithmetic and comparison; it provides thezero/oneidentities, ordering (<,<=,isless) andhash— soGens sort and serve asDict/Setkeys interchangeably with equal Julia numbers; the standard predicates (iszero,isone,isinteger,isfinite,isreal, …); the elementary operations (abs,sign,inv,conj,real,imag); and conversions to and fromFloat64,BigFloat,Rational, andComplex. AGenis now a drop-in Julia number in generic numeric code.
- An operation that does not apply to a
Gen's underlying PARI type (for example, ordering a polynomial, orFloat64of a vector) raises a catchablePariErrororInexactError. The change is purely additive — no public API change, andGen <: Numberis unchanged.
LibPARI is now fully parallel — concurrent libpari calls and
concurrency-safe PARI error handling.
- Parallel
libparicalls. LibPARI now runs one PARI context per Julia OS thread — PARI's per-thread context API (pari_thread_*), behind a pool of sticky worker tasks, one pinned to each thread. Concurrentlibparicalls issued from different Julia threads execute in parallel on multiple cores, instead of serializing onto the single worker of milestone M9. Single-threaded behaviour and the public API are unchanged. - Concurrency-safe PARI error handling. A PARI error raised on any
thread — including simultaneously from many threads — is caught as a
catchable
PariError, with no crash, hang, or corruption. Every error-prone call path (the generated bindings, the GP evaluator, the conversion layer) routes through a small per-thread C error trap. The trap is optional: where no C compiler is available LibPARI degrades gracefully to the single-threaded-safe path — installation never fails and no new hard prerequisite is introduced.
- PARI's transient-stack pointer
avmais read throughget_avma()(a thread-local read, correct on every worker context); cloned-object bookkeeping (gclone/gunclone) is funnelled to the primary worker, since PARI's clone list is per-context. - The generated bindings, the GP evaluator, and the conversion layer call
libparithrough the concurrency-safe error trap instead of a rawccall. ThePariErrortype, the error model, and the public API are unchanged.
An optional MCP connector — the first feature beyond the M0–M10 roadmap.
- An optional MCP (Model Context Protocol) connector, delivered as a
Julia package extension (
ext/LibPARIMCPExt.jl). When a user loadsModelContextProtocolalongside LibPARI,LibPARI.serve_mcp()starts an MCP server that exposes PARI/GP to MCP clients (AI assistants) through three tools:pari_eval(evaluate a GP expression — reaches every PARI function),pari_functions(list/search PARI functions), andpari_help(a function's help text). - A documentation page for the connector (
docs/src/mcp.md). test/mcp_tests.jl—@testitems covering activation, the three tools, optionality, and structured error handling.
Project.tomldeclaresModelContextProtocolas a weak dependency with aLibPARIMCPExtextension — the connector is strictly optional. Installing LibPARI does not installModelContextProtocol, and a process that does not load it pays no cost: no dependency, no connector code. Callingserve_mcpwithout it raises aMethodErrorwhose hint tells the user what to load.
Milestone M10 — Documentation & 0.11.0 release. The release milestone: LibPARI is now documented, verified across platforms, and shippable. M10 adds no new wrapper functionality — every computational capability landed in M1–M9.
- A documentation website built with Documenter.jl
(REQ-DOC-01): an introduction, a getting-started guide, and an API
reference for the hand-written public surface. Sources live in the
docs/sub-project; build withjulia --project=docs docs/make.jl. - The documentation build is strict — it executes every
jldoctestexample and verifies its output, and completes with no warnings; any warning or doctest mismatch fails the build (REQ-DOC-04, REQ-DOC-05). - A
docsCI job that builds the site and deploys it to GitHub Pages from the default branch (REQ-DOC-06). test/acceptance_tests.jl— the four non-functional acceptance checks, one@testitemper NFR: ≥ 1200 generated bindings (NFR-01, observed 1241+), no PARI heap leak across repeated calls (NFR-02), bounded constant per-call allocation (NFR-03), and byte-identical generator output (NFR-04).jldoctestexamples for thegen_fromandprotected_callpublic functions (REQ-DOC-03).
- CI runs the test suite on a cross-platform matrix — Linux, macOS, and Windows (REQ-QA-07).
- The stale M1-era
LibPARImodule docstring now describes the complete library.
- The CI
testanddocsjobs both keepcontinue-on-errorfor now: untilPARI_jllis registered (Yggdrasil PR #13771) the runners cannot fetch the PARI artifact, so neither job can pass — thedocsbuild runsusing LibPARIand executes doctests against live PARI. OncePARI_jllis registered, removing those two lines makes thetestjob the hard cross-platform gate REQ-QA-08 requires and lets thedocsjob deploy on every push. - Because CI cannot yet build the documentation, the site at
https://s-celles.github.io/LibPARI.jl is deployed manually from a
machine that has
PARI_jll. It auto-deploys via CI oncePARI_jllis registered. - Registration of LibPARI in the Julia General registry is prepared but
pending
PARI_jll's own registration — a registered package may not depend on an unregistered one.
0.10.0 - 2026-05-20
Milestone M9 — Platform & concurrency hardening. LibPARI is now safe to use from multi-threaded Julia.
- A dedicated PARI worker task that owns the library: every
libparicall is marshalled onto one sticky worker task and executed there. PARI's working state (avma, the stack, the recursion-depth base) is thread-local, so confining alllibpariwork to the single task that initialized PARI makes concurrent use from any number of Julia threads correct — no data race, no PARI stack corruption (REQ-PLT-03).Genresults stay type-stable across the marshalling boundary, andGenfinalization is marshalled to the worker too. test/concurrency_tests.jl— a multi-threaded stress test, run as its own step:julia --project=. --threads=auto test/concurrency_tests.jl.
- LibPARI's hand-written code is confirmed free of platform-specific
assumptions, so it is correct on every platform
PARI_jllprovides an artifact for (REQ-PLT-01).
- The
@testitemtest suite runs single-threaded. This is no longer a PARI limitation — LibPARI'slibparicalls are thread-safe — but a separate one: the TestItemRunner harness on Julia 1.12 crashes under the multi-threaded concurrent compilation of the suite. M9's multi-threaded correctness is instead verified by the dedicatedtest/concurrency_tests.jlstress test, run as its own--threads=autostep. - With the suite now reliably green, the
v0.2.0–v0.10.0release tags (deferred since M1) become createable; tagging is part of the M10 release.
0.9.0 - 2026-05-20
Milestone M8 — Type stability & precompilation. SciML-grade inference and a fast first call. M8 adds no new user-facing API — it hardens M0–M7.
- A type-stability audit —
test/inference_tests.jl— with@inferredchecks over a representative generated binding of each return convention and over every hand-written public function (the conversions, the arithmetic operators and==,gp_eval, the lifecycle/introspection API). The whole audited surface infers a concrete return type, neverAny(REQ-PERF-01, REQ-PERF-03). - A
PrecompileTools.jlprecompilation workload (src/precompile.jl) that exercises a representative subset of the API while the package precompiles, so the first call in a fresh session is fast (REQ-PERF-02). PrecompileToolsadded as a package dependency.
- Carried from M1: the test suite must run single-threaded
(
Pkg.test(julia_args=--threads=1)) — PARI's state is thread-local. Thread serialization is milestone M9 (REQ-PLT-03). Thev0.9.0tag is deferred (withv0.2.0–v0.8.0) until the suite is reliably green.
0.8.0 - 2026-05-20
Milestone M7 — GP expression evaluator. An escape hatch: evaluate an
arbitrary GP-language expression string and get a Gen back.
gp_eval(s::AbstractString)— evaluate a GP-language expression string with PARI's GP engine, returning the result as aGen(REQ-GP-01). It reaches every PARI capability, including the GP-closure-argument functions (sum,prod,intnum, …) that the generated bindings do not expose. GP variable assignments persist acrossgp_evalcalls — one shared GP environment.- A syntactically invalid GP string raises a catchable
PariErrorwith categorye_SYNTAXdescribing the parse error; a runtime failure during evaluation raises aPariErrortoo (REQ-GP-02). The library stays usable after a failed evaluation.
gp_evalis a developer escape hatch — it runs whatever GP code it is given; it is not sandboxed.- Carried from M1: the test suite must run single-threaded
(
Pkg.test(julia_args=--threads=1)) — PARI's state is thread-local. Thread serialization is milestone M9 (REQ-PLT-03). Thev0.8.0tag is deferred (withv0.2.0–v0.7.0) until the suite is reliably green.
0.7.0 - 2026-05-20
Milestone M6 — Idiomatic numeric API. Gen is now a first-class Julia
number: it computes with ordinary operators, mixes with Julia numbers, and
compares with ==.
Gen <: Number—Genis a subtype of Julia'sNumber.- Arithmetic operators
+,-,*,/,^and unary-onGenoperands, each computing the PARI result (gadd/gsub/gmul/gdiv/gpowgs/gpow/gneg) and returning aGen./is PARI's exact division — a rational result when the operands do not divide evenly. - Mixed
Gen/ Julia-number arithmetic: the Julia operand is promoted to aGen, in either operand order, viapromote_ruleandconvert. - Floating-point →
Genand rational →Genconstruction — extending M5's integer-only conversions — so promotion covers every JuliaNumber. ==onGenvalues returns PARI's mathematical-equality result (gequal); mixedGen/number==works through promotion.
- M6 delivers the operators of REQ-API-02 and the equality of REQ-API-04;
ordering comparisons (
<,<=), integer-division operators, and a consistenthashforGenare not included. - Carried from M1: the test suite must run single-threaded
(
Pkg.test(julia_args=--threads=1)) — PARI's state is thread-local. Thread serialization is milestone M9 (REQ-PLT-03). Thev0.7.0tag is deferred (withv0.2.0–v0.6.0) until the suite is reliably green.
0.6.0 - 2026-05-20
Milestone M5 — Type conversions. Julia integers and PARI Gen values now
convert into and out of each other, every Gen renders as text, and a
conversion that cannot be exact fails loudly.
Gen(x::Integer)andconvert(Gen, x)— convert a Julia integer of any magnitude to an integer-valuedGen(stoi/utoifor the machine-word range,strtoiover a decimal string for arbitrary magnitude) (REQ-CONV-01).BigInt(g::Gen)— convert an integer-valuedGento an exact JuliaBigInt, for integers of unbounded size (REQ-CONV-02).T(g::Gen)andconvert(T, g)forT <: Integer— convert aGento a fixed-width Julia integer type.- Textual representation:
show,print,string, and string interpolation render anyGenexactly as PARI's ownGENtostrdoes (REQ-CONV-03). - Lossy
Gen → Juliainteger conversion raises Julia's standardInexactError— a non-integral value, or an integer outside a fixed-width type's range, is rejected rather than silently truncated (REQ-CONV-04).
- M5's value conversions are integer-only; rational and floating-point
conversion is deferred to a later milestone. The textual representation
covers every
Genregardless. - Carried from M1: the test suite must run single-threaded
(
Pkg.test(julia_args=--threads=1)) — PARI's state is thread-local. Thread serialization is milestone M9 (REQ-PLT-03). Thev0.6.0tag is deferred (withv0.2.0–v0.5.0) until the suite is reliably green.
0.5.0 - 2026-05-20
Milestone M4 — Binding generator & function invocation. PARI's function
database is turned into a callable binding layer — the LibPARI.PARI
submodule now exposes the bulk of PARI's library functions.
LibPARI.PARI— an auto-generated submodule of 1241 bindings, one per eligible PARI library function. Each bindingccalls thelibpariC symbol through M3'sprotected_callover M2'sgen_from, returns aGen(or the matching scalar /nothing), and carries the function'spari.deschelp text as its docstring (FR-003/004/005/010).- A development-time binding generator (
gen/generate.jl, run in its owngen/environment) that reads PARI's machine-readablepari.descfrom thePARI_jllartifact and writessrc/bindings.jl. The output is a single committed, header-marked, JuliaFormatter-conformant file; two runs on the same PARI version are byte-identical (FR-001/002/006/007/008/009). - Calling conventions on the generated bindings: a precision-taking function
auto-supplies the working precision and accepts a
prec(bitprec/seriesprec) keyword override; optional arguments are exposed as keyword arguments carrying the defaults declared inpari.desc; output (&) arguments are returned with the primary result as a tuple (REQ-CALL-01/02/03). - Robust generation: records with no C entry point are omitted, GP-closure -argument functions are excluded, and unrecognized prototypes are skipped — each reported in a diagnostic. The generator never emits a broken binding or fails silently (FR-014/015/016).
- Carried from M1: the test suite must run single-threaded
(
Pkg.test(julia_args=--threads=1)) — PARI's state is thread-local. Thread serialization is milestone M9 (REQ-PLT-03). Thev0.5.0tag is deferred (withv0.2.0–v0.4.0) until the suite is reliably green.
0.4.0 - 2026-05-20
Milestone M3 — Error handling & the C-call boundary. PARI errors are trapped at the Julia/C boundary and surfaced as catchable Julia exceptions. No PARI computational functions are exposed yet.
PariError <: Exception— the catchable Julia exception raised when alibparicall signals a PARI error; carries PARI's message text and an error category (REQ-ERR-01).- Module-scoped
LibPARI.PariErrenum of PARI's error categories (e_SYNTAX…e_NONE, PARI'snumerr_t) (REQ-ERR-04). protected_call— runslibpariwork inside the error-trapping boundary: a PARIlongjmpis trapped (via thecb_pari_err_recovercallback) and cannot escape the Julia/C boundary; on error the PARI stack is restored to a consistent state so the library stays usable (REQ-ERR-02, REQ-ERR-03). This is the boundary the M4 binding generator will route every call through.
- Carried from M1/M2: the test suite must run single-threaded
(
Pkg.test(julia_args=--threads=1)) — PARI's state is thread-local. Thread serialization is milestone M9 (REQ-PLT-03). Thev0.4.0tag is deferred (withv0.2.0/v0.3.0) until the suite is reliably green.
0.3.0 - 2026-05-20
Milestone M2 — Gen value type & memory management. Introduces the
concrete Julia type wrapping a PARI object, with leak-free stack discipline.
No Julia↔Gen conversions or PARI computational functions yet.
Gen— a concretemutable structwrapping a PARI object. EachGenowns a clone of its object in PARI's persistent storage; a GC finalizer frees that clone exactly once, idempotently (REQ-MEM-01/02/03/04/06).gen_from— the capture-avma→ produce →gclone→ restore-avmadiscipline that keeps PARI's transient stack leak-free (REQ-MEM-05); the pattern the M4 binding generator will reuse.- Module-scoped
LibPARI.PariTypeenum of PARI's value types (T_INT,T_REAL,T_POL,T_VEC, …) andgentype, which reports aGen's PARI type (REQ-MEM-07/08).
- Carried from M1: the test suite must run single-threaded
(
Pkg.test(julia_args=--threads=1)) — PARI's state is thread-local andGenfinalizers may run on any thread. Thread serialization is milestone M9 (REQ-PLT-03). Thev0.3.0tag is deferred (withv0.2.0) until the suite is reliably green.
0.2.0 - 2026-05-20
Milestone M1 — Library lifecycle. PARI now initializes and shuts down automatically; no PARI computational functions are exposed yet.
- Automatic PARI initialization: loading
LibPARIinitializes PARI exactly once, from the module__init__, never during precompilation (REQ-INI-01/02/03). Usespari_init_optswith PARI's signal handlers and pthread engine disabled so they do not clobber the Julia runtime. pari_closeregistered withatexitso PARI's resources are released at normal process exit (REQ-INI-05).- Module-scoped
LibPARI.LibraryStateenum (UNINITIALIZED,INITIALIZED,CLOSED) and the introspection APIis_initialized,library_state, andstack_size. - Configurable PARI main-stack size via the
LIBPARI_STACK_SIZEenvironment variable (bytes), with an 8 MiB default (REQ-INI-04). - Safe initialization failures: an invalid
LIBPARI_STACK_SIZEraises a catchable Julia exception and leaves the library uninitialized (REQ-INI-06/07). DocStringExtensionsdependency; the public lifecycle API carries DocStringExtensions docstrings withjldoctestexamples.
- The full
Pkg.test()suite can intermittently crash withSIGSEGV: PARI's working state is thread-local and PARI is not yet safe to drive from a multi-threaded Julia process. Run tests single-threaded —Pkg.test(julia_args=--threads=1). Proper call serialization is milestone M9 (REQ-PLT-03). Thev0.2.0tag is deferred until the suite is reliably green.
0.1.0 - 2026-05-20
Milestone M0 — Project scaffolding & packaging baseline. A registrable, CI-ready package skeleton with the quality gates wired in before any PARI wrapper code exists. No PARI functionality is exposed yet.
Project.tomlwith a UUID,version = "0.1.0", and[compat]bounds onPARI_jll("2.17") andjulia("1.10").src/LibPARI.jlmodule stub importingPARI_jll(nopari_init— PARI lifecycle begins in M1).- TestItemRunner test harness (
test/runtests.jl) withtest/package/reserved for package-quality tests; smoke, Aqua, formatting, and metadata test items. .JuliaFormatter.tomlwith an 80-column margin..pre-commit-config.yamlrunning file-hygiene hooks and JuliaFormatter.- GitHub Actions CI workflow: a
formathard gate, plus atestjob and a docs job present as documented stubs. upstream-bugs.mdlog for bugs found in PARI /PARI_jll/ Yggdrasil.CHANGELOG.mdin "Keep a Changelog" format; Semantic Versioning adopted.
PARI_jllis not yet in the Julia General registry (Yggdrasil PR #13771); it is resolved from a local development build. Registering LibPARI is therefore deferred to a later milestone.