# libxml-rs
**Phase 16: Performance parity — SIMD structural scanning, differential courts, and the five-consumer drop-in.**
Custodial native-Rust reimplementation of the **libxml2 + libxslt** ecosystem:
a forensic reconstruction of observable behavior, implemented in native Rust,
with C ABI compatibility for drop-in replacement.
This is **not** an XML crate. This is **not** an XSLT crate. This is **not** a wrapper.
This is a custodial forensic archive and native-Rust reimplementation of
the complete observable behavior of libxml2 and libxslt across their
historical lifetimes.
---
## Current Status: Phase 16 — performance parity
Phase 16 treats performance as a forensic parity surface: every claim is a
measured, statistically classified row against a **frozen canonical oracle**
(libxml2 2.15.3 + libxslt 1.1.45, built into the `libxml-rs/phase14-debian`
court image), with the convention `speedup = oracle_time / candidate_time`.
Evidence lives in `atlas/PERFORMANCE_ATLAS.md` / `.json` and
`courts/receipts/phase-16/` (baseline, profiling, per-change receipts, raw
matrices). Work so far, in phase order:
- **§16.0/16.4 — baseline + profiling**: `PHASE-16-BASELINE.md`/`.json`
(host/build fingerprints, frozen-oracle identity), `PHASE-16-4-PROFILING.md`.
- **§16.5/16.6 — scalar engine**: XML 2.11/3.3.3 byte-model fidelity;
owned-or-span text tokens (character runs reference the base input instead
of copying); decode-once scanner loops; contiguous printable-ASCII text
scanning; duplicate-attribute detection without per-name clones;
allocator bridge caches the hook-slot *address*; borrowed synchronous
memory input; bulk ASCII name + whitespace scanning. Also removed a
default-handler O(N²) text-merge and a bare-`&` infinite loop found along
the way.
- **§16.7 — SIMD structural scanning** (`src/xml/parser/scan/`): the
printable-ASCII text-run classifier is runtime-dispatched across three
byte-identical backends — scalar reference, AVX2 (32-byte lanes), and
AVX-512BW (64-byte k-masks) — selected once per process
(`LIBXML_RS_SCAN_BACKEND=scalar|avx2|avx512|auto` diagnostic override).
The §16.7.7 differential court parses 1191 adversarial + fixture
documents under every backend and the oracle: backend-invariance
(stdout+stderr) is byte-identical and candidate parse trees match the
oracle byte-for-byte.
- **Bugs the court caught and fixed**: a release-only crash (the streamed
generic-error asm trampolines declared caller-saved argument registers as
plain inputs — LLVM reused a stale register, so every
`XML_ERR_INVALID_ENCODING` raise freed a stack pointer) and seven
error-path parity gaps (incomplete UTF-8 at EOF, the `Bytes:` dump
length, the `]]>` caret at the run start, NUL/control characters in text
and attribute values, `+` no longer accepted in XML names, self-closing
`/` requiring `>`, end tags requiring `>`).
- **§16.7.5 — AVX-512 policy evidence**: an alternating-order, taskset-pinned
matrix shows no end-to-end AVX-512 advantage over AVX2 on this Zen 5 host
for element-heavy documents; `auto` therefore stays on AVX2 (no
manufactured width win). Raw rows:
`courts/receipts/phase-16/raw/scan-backend-matrix.csv`.
- **Known blocker (documented)**: `xmlParseChunk` re-parses the whole
accumulated input per non-final chunk (`helpers.rs parse_chunk`, the
SP-14.3.1-6 PHP expat-compat design), which is O(N²) on long chunked
feeds (oracle: linear 1.6 GB/s; candidate 96 s vs 0.05 s at 80 MB). It
blocks the lxml `iterparse` large-document test and the §16.12.3 lxml
iterparse consumer benchmark — the next big mover
(`courts/receipts/phase-16/16-7-7-push-reparse.md`).
- **Performance infrastructure**: Criterion benches (`benches/`),
provider-isolated oracle/candidate harness + Pareto matrix
(`tools/bench/`), per-backend fuzz targets (`fuzz/`), and the
five-consumer court surface (xmllint, xsltproc, python3-lxml,
ruby-nokogiri, PHP) with the corpus/court machinery in
`courts/suites/phase16/`.
Latest gates: `cargo test --lib` 1282 passed / 0 failed; PHP six-extension
six-gate 1250 passed / 0 failed; CLI xmllint differential 46/48 (2
pre-existing); per-backend parse fuzz clean.
### Published artifacts
- crates.io: [`libxml-rs`](https://crates.io/crates/libxml-rs) `0.1.0-alpha.49`
- GitHub: <https://github.com/infinityabundance/libxml-rs>
### Oracle verification
The frozen canonical oracle — libxml2 2.15.3 and libxslt 1.1.45 — is built
from source into the `libxml-rs/phase14-debian` court image (12 further
historical libxml2 releases live under `oracle/historical/` for the Phase-11
epoch matrix). The candidate (`libxml-rs`) does not link against system
libxml2/libxslt — verified by the oracle contamination court.
### Phase 15 — four-consumer drop-in status
Phase 15 drove the consumer drop-in push toward zero failures across the
four runtime consumers (lxml, php, nokogiri, debian) against the frozen
oracle. Snapshot (`courts/receipts/phase-15/FOUR-CONSUMER-STATUS.md`, plus
`CURRENT-STATE.md` / `LXML-COURT-BASELINE.md`):
- **php — 0 failures (sealed in Phase 14)**; re-gated after every shared
engine change (currently 1250/0).
- **lxml** — the candidate gap clustered in the XSLT/EXSLT engine and
validation surfaces; the oracle passes the full suite (2007 tests / 0
failures), the candidate is blocked on the Phase-16 push-parse item
above for its large-`iterparse` test.
- **nokogiri** — the oracle passes its suite (2831 runs / 0 failures);
candidate crash sites (XPath extension-function dispatch, SAX context
creation) were isolated and fixed progressively.
- **debian / drop-in** — runtime substitution verified in the debian court
container; ltrace-driven symbol analysis drove the missing-surface fills
(xmlBuf API, legacy SAX1 handlers, exported global aliases, xz/encoding
helpers) and the versioned-SONAME coexistence work.
### Phase 14 — downstream custodian validation courts
Phase 14 built the consumer validation courts (lxml / Nokogiri / PHP /
Debian inside minimal docker VMs) and drove the PHP six-extension gate from
321 failures to **zero (NTS and ZTS)** — DOM (classic + modern),
SimpleXML, XML expat-compat, XMLReader, XMLWriter and XSL, family by family
(receipts `courts/receipts/phase-14/php-14-NN-*/`; the ZTS seal required the
R-000177 cross-DSO loader-slot bridge so the whole-archive libxslt facade
sees the streams loader PHP registers through the core). Green-gate
configuration: 1290 tests / 1250 passed / 40 skipped / 0 failed, identical
file list on both sides. The frozen-oracle court image
(`libxml-rs/phase14-debian`, libxml2 2.15.3 + libxslt 1.1.45) and the
consumer source trees became the substrate every later phase gates on.
### Phase 11 (historical)
Phase 11 delivers the cross-version archaeology of **§41 (historical oracle matrix), §42
(version fingerprints), §51 (multi-version triangulation)**. Per §85:
> *Deliverable: The project can explain how current behavior came to exist.*
A configurable oracle matrix (`oracle/historical/`) builds 12 historical libxml2
releases (2.7.8 → 2.15.0) plus 5 libxslt releases (1.1.26 → 1.1.42) from the archaeology
git clones and runs a 28-case behavioral corpus against every oracle and the system
2.15.3/1.1.45 binaries, capturing byte-exact stdout/stderr/exit and per-version sha256
fingerprints:
- **`oracle/historical/build.sh`** — era-tolerant oracle builder (era tag spellings, autotools modernizations, `--without-threads` for modern glibc)
- **`oracle/historical/run_matrix.sh`** — matrix runner + per-case epoch grouping; `results/matrix.json` holds version→case→sha256
- **`atlas/SEMANTIC_EPOCHS.md`** — the epoch map: 10 behavioral epochs (E-001…E-008 + exit-code epochs) correlated with the exact upstream commits/NEWS that created them
- **`courts/suites/historical/HIST-EPOCH-*.json`** — §43 casefiles; **`courts/receipts/historical-matrix-2026-08-29.json`** — §44 receipt
Headline findings (all correlated to upstream commits in the atlas):
- `xmllint --xpath` node-set output gained newline separators in **2.9.10** (commit `da35eeae`, an upstream-documented breaking change)
- The 2.9.10 parser regression (`EndTag: '</' not found`) was fixed in **2.9.11** (`de5b624f`); the second parse-error diagnostic was dropped entirely in **2.12**
- A **2.13.0** cluster: parser/validation exit codes reworked (1→4, 4→3), entity-in-attribute errors reported twice, entity-decl debug nodes became `TEXT compact` (`8d04f0ee`)
- **2.15.0**: `--html` dumps became single-line (newline writes removed from `HTMLtree.c`); `--valid` with no DTD returns exit 0
- libxslt core transform output is a **stable epoch** — byte-identical from 1.1.26 (2009) to 1.1.45
### Phase 11.1 (historical)
Phase 11.1 (11.1-A … 11.1-Z) sealed the forensic surface census and parity
closure, and the **11.1-Z.1 evidence/packaging amendment** (0.1.0-alpha.34)
sealed the three-DSO ELF contract and the residual-ledger corrections.
**11.1-Z.2** (0.1.0-alpha.35) added the function-signature ABI plane: the
**ABI-FUNCTION-SIGNATURE** court mirrors every export across the oracle
header / candidate header / actual Rust `extern "C"` signature (3259
compared, 0 findings) and caught a whole defect class — the allocator hooks
(`xmlMemSetup`/`xmlGcMemSetup` missing `mallocAtomicFunc` and the `int`
return, two sources of allocator truth), shifted register layouts
(`xmlC14NExecute`, `xmlAutomataNewCountTrans`) and 20+ stale pre-2.10
signatures. The **ALLOCATOR-HOOK** differential court proves the merged
single-source-of-truth allocator byte-identical with the oracle. The
**CUSTODIAN-COMMENTARY-DRIFT** court pins every residual/court/epoch/
receipt reference in source commentary and bans embedded mutable counts.
The **DSO-STATE-COHERENCE** court pins a documented bounded divergence
(R-000177): the whole-archive libxslt/libexslt facades are the only
consumer-linkable construction, so they carry private copies of the core
state — hooks/globals installed through one DSO are not observed by the
others (the ELF contract itself — SONAMEs, NEEDED chains, export surfaces —
is verified by DSO-LOADER).
**11.1-Z.3** (0.1.0-alpha.36) closed the allocator-UB and proof-scope
accounts. The default allocator was invalid-layout UB (Rust `std::alloc`
with fabricated `Layout`s); it is now plain libc `malloc`/`realloc`/`free`/
`strdup` (upstream 2.15.0 `globals.c` defaults), untracked exactly like the
oracle, with the debug-named surface (`xmlMemMalloc`/`*Loc`) keeping the
registry — R-000178, proven by the new **ALLOCATOR-DEFAULT-001** differential
court (byte-identical incl. `xmlMemSize`/`xmlMemUsed`/`xmlMemBlocks`
exactness). The **ABI-FUNCTION-SIGNATURE** court was hardened to be
fail-closed and oracle-isolated: fully separated compile environments with
per-include-origin contamination checking, zero silent omissions (every
header and every declaration accounted), and two fingerprints —
SOURCE_PROTOTYPE (canonical base, pointer depth, pointee const/restrict,
typedef identity, FULL fn-pointer signatures) and MACHINE_ABI (class, width,
signedness, depth) — which found 25+ real candidate divergences that the
old lossy normalization could not see: signedness (`xmlBufferGrow`/
`xmlDictSetLimit`/`xmlOutputBufferGetSize`), wrong arities
(`xmlValidateAttributeDecl` 4→3 args), wrong returns (`xmlXPathCastBooleanToNumber`
int→double, `xmlCheckVersion` int→void, `xmlInitThreads` int→void), wrong
fn-pointer typedefs (`xsltPreComputeFunction`/`xsltTopLevelFunction`), an
invented `xmlTextReaderGetAttributeIndex` API (removed), `char`/`xmlChar`
pointee drifts, and the `xmlAttributeTable` typedef identities. The
**CUSTODIAN-COMMENTARY-DRIFT** census now GATES the verdict on zero
unaccounted safety sites via proof scopes: 1811/1811 unsafe functions and
5235/5235 unsafe blocks accounted (local proof 2514, enclosing proof scope
2720, classified-generated 1), plus the rustdoc gate
(`RUSTDOCFLAGS="-D warnings" cargo doc`), and the `xslHandleDebugger`
non-obligation is reclassified HEADER_DECLARED_ORACLE_DSO_ABSENT (the
oracle DSO itself does not export it).
**11.1-W (generated parity matrix)** replaced every hand-typed
headline count with evidence: `tools/evidence/generate_all.py` regenerates all
six canonical ledgers (`atlas/PARITY_MATRIX.json`, `atlas/SURFACE_RECONCILIATION.json`,
`atlas/API_PARITY_LEDGER.json`, `atlas/ABI_PARITY_LEDGER.json`,
`atlas/HISTORICAL_SURFACE_EPOCHS.json`, `atlas/PARITY_OBLIGATIONS.json`) and
their Markdown views, `--check` proves byte-reproducibility, and
`tools/evidence/readme_counts.py` generates the Project Status and
test-coverage tables in this README. The obligations ledger now covers all
three oracle projects (libxml2 2.15.3, libxslt 1.1.45, libexslt 0.8.25):
**0 missing** across 1683 obligations, with the residual ledger at
**96 FIXED / 4 OPEN** (100 residuals; the OPEN entries are R-000157
UNRESOLVED — iconv/ICU-only encodings, a real executed-platform gap pending
an iconv/ICU backend — R-000168, the unexecuted-platform obligation,
R-000177, the cross-DSO state partitioning of the facades (a deliberately-
open Phase-12 architectural target), and R-000179, the versioned-distro
binary contract gap).
The 11.1-Z.1 amendment shipped three real ELF DSOs (core `libxml2.so.16` +
post-link facades `libxslt.so.1` and `libexslt.so.0` with the upstream NEEDED
chain), fixed the parity-matrix per-project DSO accounting, bound the
generated-evidence input identities (Doxygen inventories + headers), corrected
the verification-ladder state machine, and added the real
PREPROCESSOR-SURFACE / AST-SURFACE courts.
### Phase 12 (real downstream substitution)
Phase 12 proved the ecosystem cannot tell the custodian implementation apart
from upstream — the Phase-11 seal is a `1c35e4e1…`-rooted functional tree
(commits `fed3cd03` and `d2f72696`, crate `0.1.0-alpha.37`):
- **EXPORT-SURFACE-DISPOSITION**: every candidate DSO export classified
CURRENT_ORACLE_EXPORT / HISTORICAL_COMPAT_EXPORT / CUSTODIAN_EXTENSION /
INTERNAL_LEAK; the shipped `libxml2.so.16` is the exact executed-oracle
surface (version scripts hide the leaks), with the staticlib kept as the
full implementation surface.
- **ELF-VERSIONING + BINARY-SUBSTITUTION** (14/14): the upstream
`LIBXML2_1.x` named-version graph (27 nodes) for `libxslt.so.1` with the
oracle's per-symbol node assignment; consumers compiled and linked against
the ORIGINAL oracle DSOs run unchanged against the candidate DSOs via
runtime substitution — DT_VERNEED satisfied, no ld.so warnings.
- **DYNSYM-SURFACE** (12/12) and **DSO-BOUNDARY-LINT**: the loader-visible
surface is the intended contract and nothing escaped from the Rust
implementation; the xslt layer may only reach libxml2 state through the
sanctioned public ABI gateway (R-000177 machine lint).
- **STATIC-SUBSTITUTION** (13/13) and **DOCKER-SUBSTITUTION** (17/17, inside
a minimal VM): static-linked and containerized unmodified external C
consumers (xmllint/xsltproc builds, testWriter.c-style sources) link and
run byte-identically.
- **EXTERNAL-CONSUMERS** (15/15): pkg-config/xml2-config/xslt-config
substitution for downstream build systems.
### Phase 13 (hostile audit)
Phase 13 attacked the candidate with NULLs, extremes and UB-adjacent defined
behavior, court by court, requiring byte-identical stdout/stderr/exit vs the
system oracle (probe receipts in `courts/receipts/phase-13/`):
- **HOSTILE-ABI** (72 NULL/extreme-size attacks): fixed real buffer/limits
divergences — `xmlReadMemory` INT_MAX wild-read rejection, upstream-faithful
`xmlBufferCreate/CreateSize`, `xmlParseChunk` NULL/negative →
XML_ERR_ARGUMENT, `buf_add`/`buf_add_head` edge contract (R-000191).
- **HOSTILE-OWNERSHIP** (O1–O12): `xmlNewNode`/`xmlNewPI` NULL-name
rejection, `create_int_subset` NULL-doc unattached DTD, `utf8_strlen(NULL)`
→ -1 (R-000192).
- **HOSTILE-ALLOCATOR** (H1–H6, size-based failure injection): real ownership
bug — `xmlStrcat`/`xmlStrncat` now free the old buffer on realloc failure
(R-000193).
- **HOSTILE-CALLBACKS** (C1–C10): real bugs — `xmlSAXUserParseMemory/File`
copy the caller's SAX into own storage (was freeing stack), return errNo,
parent-input error-context fallback, I/O source-failure propagation
(R-000194).
- **HOSTILE-FAILURE** (F1–F10): missing `xmlRegexpPtr`/`xmlRegExecCtxtPtr`
typedefs, streamed depth-limit error with source window, the legacy
entity-loop "cur input" tail, XPath compile diagnostics with byte offsets,
`xmlParseDTD` I/O warning, invalid-regexp → NULL (R-000195).
- **HOSTILE-THREADS** (T1–T3): the error-handler slots and the other 14
TLS-era globals were GLOBAL data; upstream 2.15 keeps them thread-local
(`xmlGetThreadLocalStorage`) and exports only the `__xml*` accessor
FUNCTIONS. The candidate moved all 18 to `thread_local!` cells
(`src/xml/globals/tls.rs`), removed the data-symbol exports and switched
the headers to the upstream macro/accessor contract — a handler installed
in one thread is no longer visible from another (R-000190).
- **HOSTILE-ORACLE-CONTAMINATION** (dimension 7): readelf/nm/probe prove the
candidate DSOs carry no system libxml2/libxslt/libexslt dependency and
every symbol resolves inside the candidate directory.
The Phase-13 courts found and permanently courted each defect class; the
hostile receipts, the TLS-globals model and the header contract changes are
sealed with the Phase-13 commit.
### Phase 10 (historical)
Phase 10 delivered the **`xmllint`** and **`xmlcatalog`** command-line tools
(§36):
Both tools are faithful native-Rust ports of the upstream programs, verified
byte-for-byte against the system libxml2 2.15.3 binaries:
- **`xmllint` CLI** — `--debug`, `--copy`, `--format`, `--valid`/`--postvalid`, `--dtdvalid`, `--xpath`/`--xpath0`, `--xinclude`, `--html`/`--xmlout`, `--noent`, `--no-compact`, `--encode`, `--recover`, `--dropdtd`, `--pedantic`, `--noout`, `--quiet`, `--nonet`, `--huge`, … with upstream exit codes (0/1/3/4) and the upstream `file:line: parser error : MSG` + source-line/caret diagnostics
- **`xmlcatalog` CLI** — `--create`, `--add`, `--del`, `--resolve` (system/public/URI), `--noout`, `--shell` (interactive `resolve`/`system`/`public`/`add`/`del`/`dump`/`help`/`exit`), SGML→XML catalog conversion, upstream exit codes
- **Compact text nodes** — the parser reproduces libxml2's `XML_PARSE_COMPACT` inline text storage (≤15 bytes in the node struct), so `--debug` dumps show `TEXT compact` exactly like the oracle; merged/entity-interrupted text is correctly non-compact
- **DTD validation diagnostics** — `--valid` output (messages, caret placement, exit codes) matches the oracle for both no-DTD and declaration errors
- **Entity expansion** — `--noent` re-parses declared-entity content through the input stack (nested references and markup entities included), matching upstream trees
- **HTML serialization** — meta-charset insertion, upstream formatting rules (p/pre/param never formatted, single-child and inline elements inline), HTML document headers
- **1299 passing tests**: `cargo test --lib` — 0 failures
- **Differential oracle parity**: a 44-case CLI suite (`target/difftest_summary.sh`) is **byte-identical** to the system tools (stdout + stderr + exit codes), plus a 30+ case edge corpus (entities, DTDs, HTML, compact/no-compact, debug dumps, XPath)
### Phase 9 (historical)
Phase 9 delivered the **complete EXSLT module set** (§35) and the
**`xsltproc` command-line tool** (§36):
All seven EXSLT modules are implemented natively and registered through the
process-wide EXSLT registry (`exsltRegisterAll`, mirroring upstream):
- **`exsl:` Common** — `exsl:node-set` (with real result-tree-fragment support), `exsl:object-type`
- **`math:`** — `math:max`, `math:min`, `math:sin`, `math:cos`, `math:tan`, `math:constant`, `math:power`, `math:sqrt`, `math:log`, `math:random`, …
- **`set:`** — `set:difference`, `set:intersection`, `set:distinct`, `set:has-same-node`, `set:leading`, `set:trailing`
- **`str:`** — `str:tokenize`, `str:padding`, `str:concat`, `str:split`, `str:replace`, `str:align`, `str:encode-uri`, `str:decode-uri`
- **`dyn:`** — `dyn:evaluate`, `dyn:element`, `dyn:attribute`, `dyn:map`, `dyn:call`, …
- **`date:`** — `date:date-time`, `date:date`, `date:time`, `date:year`, `date:month-in-year`, `date:day-in-month`, `date:day-of-week-in-month`, `date:format-date`, `date:add`, `date:difference`, `date:seconds`, `date:day-name`, …
- **`func:`** — `func:function`, `func:result` (extension-function declarations)
- **`exsltRegisterAll` C ABI export** — mirrors upstream; `xsltproc` calls it at startup
- **`xsltproc` CLI** — full option surface (`--param`, `--stringparam`, `--output`, `--noout`, `--html`, `--encoding`, `--xinclude`, `--profile`, `--maxdepth`, `--maxvars`, `--nonet`, `--nowrite`, …) with upstream exit codes (1–11)
- **RTF support** — variables with inline content become context-owned result tree fragments; `exsl:node-set($var)/path` navigation works
- **1299 passing tests**: `cargo test --lib` — 0 failures
- **Differential oracle parity**: a 12-case `xsltproc` corpus (basic transform, `count()`/AVTs, `exsl:node-set`/`math:`/`set:`/`str:`, predicates, attribute string-values, `xsl:if`/`xsl:when`, numbering, descending `xsl:sort`, `key()`, `call-template` with params, `method="html"`) is **byte-identical** to the system libxslt 1.1.45 `xsltproc` (stdout + exit codes)
### Underlying subsystem fixes landed during Phase 9
| XPath core functions in XSLT | xslt | The transform context now registers the XPath 1.0 core function library — before, every XPath function call (`count()`, `substring()`, …) failed as unknown |
| AVT evaluation | xslt | `{expr}` attribute value templates evaluated in literal attributes and `xsl:element`/`xsl:attribute`/`xsl:processing-instruction` names (XSLT 1.0 §7.6.2) |
| RTF variable ownership | xslt | Inline variable content is deep-copied into a context-owned RVT (freed exactly once at context teardown) — fixes a double-free and enables `exsl:node-set` |
| Node string-value | tree | `node_get_content` concatenates all descendant text (XPath 1.0 string-value), not just direct text children |
| Caller parameter format | xslt | `xsltApplyStylesheet` params parsed as upstream `(name, value)` pairs with `{uri}name` namespace form |
| `date:` no-arg default | exslt | `date:date()`/`date:time()` and the component functions default to the current date-time (EXSLT spec) |
### Phase 8 (historical)
Phase 8 implemented the complete native-Rust **XSLT 1.0 engine**: stylesheet
compilation, template matching, pattern compilation, variable/parameter binding,
keys, sorting, numbering, imports/includes, extensions, security, serialization,
and the transform runtime (§31–§34). The XSLT engine operates exclusively on
the Rust libxml implementation — no upstream libxml2/libxslt is loaded, linked,
or shelled out to (§31):
```text
Rust CLI → Rust libxslt compatibility layer → Rust XSLT engine
→ Rust XPath implementation → Rust libxml tree/parser/serializer
```
Subsystem fixes landed during Phase 8:
| SAX2 namespace resolution | parser | `startElementNs` receives split prefix/localname + resolved URIs; element and attribute namespaces now attached by the tree builder |
| Absolute path evaluation | XPath | `/root/item` evaluates from the document node, not the root element (XPath 1.0 `/` semantics) |
| XPath C ABI helpers | ABI | `xmlXPathObjectCopy`, `xmlXPathCastToString`, `xmlXPathCastStringToNumber`, `xmlXPathCmpNodes`, `xmlXPathNodeSetCreate` |
| Node content getter | tree | `node_get_content` (upstream `xmlNodeGetContent` semantics) |
| Template-content ownership | xslt | Template content belongs to the stylesheet document; `xsltFreeTemplate` no longer double-frees it |
| `node()`/`text()` patterns | xslt | Bare node-test calls translate to steps with correct §5.5 priorities |
| `match="/"` semantics | xslt | Matches only the document node, not the root element |
All six Phase 8 residuals are documented in [`atlas/RESIDUAL_LEDGER.md`](atlas/RESIDUAL_LEDGER.md) (R-000101–R-000106).
---
## Test coverage by subsystem
| XPath 1.0 | 128 |
| XML parser + SAX | 121 |
| Encoding | 79 |
| URI | 70 |
| XML Schema (XSD) | 64 |
| DTD validation | 56 |
| RELAX NG | 56 |
| XSLT patterns | 46 |
| I/O | 45 |
| Regex | 44 |
| Schematron | 40 |
| C14N | 39 |
| XML Reader | 38 |
| Tree/ownership | 36 |
| DTD | 35 |
| HTML | 33 |
| Entities | 31 |
| XSLT transform | 27 |
| XInclude | 23 |
| Catalog | 22 |
| XML Writer | 20 |
| Automata | 16 |
| XPointer | 15 |
| XSLT numbering | 14 |
| ABI allocator | 13 |
| Debug | 11 |
| Char validation | 9 |
| Compatibility profiles | 9 |
| EXSLT dates | 9 |
| Hash | 9 |
| ABI (xml2 exports) | 8 |
| ABI (xslt exports) | 8 |
| List | 8 |
| Dictionary | 7 |
| EXSLT saxon | 7 |
| Globals | 7 |
| Serialization | 7 |
| EXSLT strings | 6 |
| String | 6 |
| EXSLT math | 5 |
| Errors | 5 |
| XSLT compiler | 5 |
| XSLT params | 5 |
| XSLT security | 5 |
| EXSLT common | 4 |
| EXSLT sets | 4 |
| XSLT serialization | 4 |
| XSLT stylesheet | 4 |
| XSLT variables/params | 4 |
| EXSLT registry | 3 |
| XSLT sorting | 3 |
| ABI data globals | 2 |
| EXSLT dynamic | 2 |
| Memory | 2 |
| Threads | 2 |
| XSLT extensions | 2 |
| XSLT keys | 2 |
| XSLT namespace alias | 2 |
| XSLT whitespace | 2 |
| abi::exports_html | 2 |
| abi::structs | 2 |
| EXSLT functions | 1 |
| XSLT documents | 1 |
| XSLT imports | 1 |
| XSLT misc (attrs) | 1 |
| abi::exports_parser | 1 |
| fuzz::fuzz_html_read_memory_no_panic | 1 |
| fuzz::fuzz_xml_read_memory_no_panic | 1 |
| **Total (1299 passing, 0 failed)** | |
---
## Project Structure
```
libxml-rs/
├── Cargo.toml # Single Cargo package (no workspace)
├── src/
│ ├── lib.rs # Library entry point
│ ├── abi/ # C ABI compatibility layer (§4, §14)
│ ├── xml/ # libxml2 implementation (§1, §3, §31)
│ │ └── parser/ # scanner (scalar/AVX2/AVX-512 §16.7), tokenizer, state
│ ├── xslt/ # libxslt implementation (§31–§34)
│ ├── exslt/ # EXSLT modules (§35)
│ ├── compatibility/ # Historical profiles, quirks, platform (§68, §69)
│ ├── internal/ # Internal helpers
│ └── bin/ # CLI tools: xmllint, xmlcatalog, xsltproc (§36)
├── include/ # Compatible C headers (§15)
├── benches/ # Criterion microbenchmarks (§16.2)
├── examples/ # ABI-mirror / allocator probe measurements
├── atlas/ # Forensic archive (§7–§12) + generated evidence
│ ├── PARITY_MATRIX.md # Current parity status
│ ├── RESIDUAL_LEDGER.md # Unexplained differences
│ ├── PERFORMANCE_ATLAS.md # Phase-16 performance evidence spec
│ └── … # release/api/abi/symbols/config inventories, epochs
├── oracle/ # Reproducible upstream build environment (§39)
├── courts/ # Differential testing framework (§40–§50)
│ ├── suites/ # Court suites incl. phase14 (consumers) + phase16
│ ├── receipts/ # Execution receipts (phase-14/15/16 …)
│ └── cli/ # xmllint/xmlcatalog/xsltproc CLI differential runner
├── tools/
│ ├── bench/ # Provider-isolated Pareto harness + docker court (§16.2)
│ ├── evidence/ # Generated-ledger/README-count generators
│ ├── packaging/ # facade-gen.sh (whole-archive libxslt/libexslt),
│ │ # linker wrapper, version-script profiles
│ └── … # archaeology, courts
├── fuzz/ # cargo-fuzz targets (parse/html/xpath; ASan)
├── docker/ # Reproducible Docker oracle images
├── docs/ # Technical documentation
└── archaeology/ # Upstream git clones (immutable, offline)
```
---
## Build
```sh
cargo build # Build the library and CLI binaries
cargo build --lib # Build only the library
cargo test --lib # Run library tests (1299 passing)
cargo build --release # Optimized build (LTO, panic=abort)
# Test C consumer compilation against our headers:
gcc -I include courts/suites/sanity/ABI-STRUCT-NODE-0001-abicheck.c -o /tmp/abicheck
clang -I include courts/suites/sanity/ABI-ENUM-0001-enumcheck.c -o /tmp/enumcheck
# Release packaging: build the core, then regenerate the whole-archive
# libxslt.so.1/libexslt.so.0 facades (always re-run after core edits — the
# facade link is timestamp-skipped by cargo):
cargo build --release --lib
sh tools/packaging/facade-gen.sh target/release
# Build the frozen-oracle court image (libxml2 2.15.3 + libxslt 1.1.45):
docker build -f docker/Dockerfile.oracle -t libxml-rs/phase14-debian:1 docker/
```
The crate builds as `cdylib` + `staticlib` + `rlib`; the build script also
emits `libxml-2.0.pc`/`libxslt.pc` pkg-config files, `xml2-config`/`xslt-config`
scripts, and SONAME symlinks into the target directory.
---
## License
Licensed under either of:
- MIT license ([LICENSE-MIT](LICENSE-MIT))
- Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE))
at your option.
---
## Project Status
| API completeness | libxml2 1395 oracle functions, 1395 fully reconciled; libxslt 232/232 reconciled; libexslt 13 oracle functions (evidence: atlas/PARITY_MATRIX.json, atlas/API_PARITY_LEDGER.json) |
| ABI compatibility | 0 mismatches across 937 measured entities (struct/enum layouts), verdict PASS (evidence: atlas/ABI_PARITY_LEDGER.json) |
| Parity obligations | 1683 obligations; 0 missing, 292 parity-verified by per-symbol courts (evidence: atlas/PARITY_OBLIGATIONS.json) |
| Subsystem census | 85 subsystems classified; verdicts: IMPLEMENTED_UNVERIFIED 43, PARTIAL 42 (evidence: atlas/SUBSYSTEM_CENSUS.json) |
| Surface reconciliation | libxml2: doxygen 1374 / AST 1403 / DSO 1395 functions; libxslt: 235 / 231 / 232 (evidence: atlas/SURFACE_RECONCILIATION.json) |
| Historical surface epochs | libxml2 2785 entities across 11 boundaries (evidence: atlas/HISTORICAL_SURFACE_EPOCHS.json) |
| Test coverage | 1299 passing, 0 failed, 1 ignored (`cargo test --lib`, evidence: atlas/TEST_COUNTS.json) |
| C headers | gcc & clang header-compile courts green (596/596, evidence: courts/receipts/header-compile-*) |
| CLI parity | `xmllint` + `xmlcatalog` + `xsltproc` differential oracle parity (evidence: courts/receipts/CLI-*) |
| Oracle infrastructure | 12 historical libxml2 + 5 libxslt oracles + system 2.15.3/1.1.45/0.8.25 oracles; evidence: oracle/historical, atlas/DOXYGEN_SURFACE_ATLAS.json |
| Downstream testing | Phase 12 sealed: EXTERNAL-CONSUMERS 15/15, ELF-VERSIONING + BINARY-SUBSTITUTION 14/14, DYNSYM-SURFACE 12/12, STATIC-SUBSTITUTION 13/13, DOCKER-SUBSTITUTION 17/17, EXPORT-SURFACE-DISPOSITION (evidence: courts/receipts/phase-12/) |
| Hostile audit | Phase 13 sealed: HOSTILE-ABI / OWNERSHIP / ALLOCATOR / CALLBACKS / FAILURE / THREADS / ORACLE-CONTAMINATION differential courts byte-identical vs the system oracle (evidence: courts/receipts/phase-13/) |
See [`atlas/PARITY_MATRIX.md`](atlas/PARITY_MATRIX.md) for the detailed,
evidence-bounded parity matrix and [`atlas/RESIDUAL_LEDGER.md`](atlas/RESIDUAL_LEDGER.md)
for the residual ledger.