The native-module ABI and reclamation scheme each have three live, unreconciled answers
Источник:
dev/fera-research/docs/oc1/010-native-abi-and-reclamation-three-answers.md(read-only; сгенерировано scripts/sync-docs.mjs)
The native-module ABI and reclamation scheme each have three live, unreconciled answers
Section titled “The native-module ABI and reclamation scheme each have three live, unreconciled answers”-
kind: problem
-
area: modules
-
evidence — reclamation, three answers:
Source Answer ADR.md:812-813(ADR-020 addendum, Accepted)“Relink on hot-swap uses ERB (epoch-based reclamation) — new pointer table in a new epoch, old epoch drains before free” OQ.md:868-871(“final synthesis”,OQ.md:847claims “all contradictions resolved”)“Inter-module pointer tables = QSBR with quiescent points placed in the actor loops (our known safe points, not arbitrary Tokio tasks). Big-module unloads = Hazard Pointers… No guard ever held across .await. Timeout + HP fallback if a grace period stalls.”OPTIM-IDEAS-4-oq24.md:151-157EBR/epoch for “короткие lock-free host tables”, QSBR only for “контролируемый dedicated reader loop” — i.e. silently drops QSBR from the module-table role and picks a third answer -
native ABI, three answers:
Source Answer ADR.md:784-786“Flat, simple, hot-path types are #[repr(C)]structs passed by value across the ABI” ✔ADR.md:807-809“modules export tables of pointers ( static EXPORT: &VTable); the host resolves and patches importer slots at link time… a virtual-function call without vtable walking” — a vtable, and&VTableis a reference into the module’s dataOPTIM-IDEAS-4-oq24.md:89-90“Нельзя строить долгоживущий ABI на extern "Rust", Rust trait objects,String,Vec, references, closures, Rust vtables… нужен узкий версионированный C ABI:extern "C", фиксированные integer types,#[repr(C)]POD-структуры, opaque handles и явныеcreate/destroyв одном владельце памяти”OQ.md:878-880“(d) pin the Rust compiler version so our own modules compile without #[repr(C)]— a fixed rustc version makes the Rust ABI stable between our modules. Manifest consequence: module metadata must carry the required rustc version; the host checks it at load.”OQ.md:878-880andOPTIM-IDEAS-4:89are mutually exclusive for first-party native modules: one mandates#[repr(C)]POD + opaque handles, the other removes the need for#[repr(C)]entirely. AndOQ.md:878-880’s “manifest must carry the required rustc version” is a new manifest field with no ADR-021 amendment and no QA gate. -
OQ.md:847— “FINAL FORMULATION (2026-10-02, all contradictions resolved)” — the contradictions are not resolved. -
QA.md:129-132tests neither ABI rule nor reclamation rule. -
Acronym typo “ERB” for EBR at
ADR.md:812,OQ.md:639,OQ.md:741. -
ADR-020’s own dependency-graph duty is untouched by any of this:
ADR.md:795-796— “manifest layering and the dependency graph become load-bearing metadata (load-order correctness)”. -
severity: high
Суть / Substance
Section titled “Суть / Substance”The module boundary is the spine of this architecture — ADR-001 makes
every heavy subsystem swappable through it, ADR-020 sets its granularity,
ADR-021 versions it, ADR-024 distributes and hot-swaps it. Yet the two
most mechanical questions about it (what exactly crosses the ABI, and what
reclaims it) have three incompatible answers each, and the file that
claims to have closed them (OQ-024, marked “design settled /
all contradictions resolved”) is itself on one side of both
contradictions.
Why each position is not merely cosmetic:
-
#[repr(C)]vs pinned-rustc. With#[repr(C)]POD + opaque handles, a module built by any rustc is loadable, the ABI is stable forever, and the host validates it structurally (abi_version, struct sizes — exactly whatADR.md:830-831saysabi_versionis for). With a pinned rustc, stability comes from the compiler, and every module now carries a rustc requirement the host must check and the manifest must carry (OQ.md:880-881). That also quietly moves safety from structural validation to provenance — a worse position for a trust model whose entire threat story is module supply chain (ADR.md:100-114). And a pinned-rustc ABI is exactly the thingnylon-ring’s “explicit version + struct-size validation on load” (ADR.md:19-20) was chosen to avoid depending on.Additional defect in the pinned-rustc option, surfaced by oc5 (bus, seq 22) and independently consistent with finding 021: the pin is enforced by a self-reported string. “the host checks it at load” (
OQ.md:880-881) compares a manifest field saying “built with rustc X” against the host’s own version — nothing proves the dylib was produced by rustc X. Combined with ADR-004’s non-blocking hash warning (ADR.md:104-107) andQA.md:121-124’s mandated test that loading proceeds anyway, a hand-built or tampered module that merely claims the right rustc passes. So the mechanism as specified does not deliver the guarantee the corpus asserts for it (“a fixed rustc version makes the Rust ABI stable between our modules”). Fix, if the option is chosen: verify the artifact’s own embedded compiler metadata (e.g. the.comment/producer section or a build-id binding) rather than the manifest’s claim — which is one more reason the structural option (A) is the safer default. -
vtables vs pointer tables.
ADR.md:807static EXPORT: &VTableis a Rust reference to a vtable — i.e. exactly the constructsOPTIM-4:89forbids across a long-lived ABI (“references… Rust vtables”).OQ.md:875-876(b) “remove virtual tables in favor of link-time pointer tables” is the same disagreement from the other direction.ADR.md:807is dated 2026-09-30;OQ.md:874-883is dated 2026-10-01/02;OPTIM-4is the synthesis — so ADR-020 is the stale one, but nobody amended it. -
EBR vs QSBR vs split. They differ in where the quiescent point goes: EBR needs no participant cooperation (expensive epochs), QSBR needs participants to announce quiescence (cheap epochs, needs the actor loops instrumented —
OQ.md:868-869says exactly that), HP is precise but O(objects). SinceADR.md:807-813puts pointer tables in the host and the actor loops are ours (ADR.md:152-156), QSBR is the natural fit and EBR the safe default; but the choice determines whether our actor loops must be instrumented, which is a real code-shape commitment nobody has made explicitly. The three answers also disagree on whether “no guard across.await” (OQ.md:870) is a rule or an aspiration.
There is a fourth, quieter problem: the reclamation choice interacts with
my finding 009 (EBR/QSBR do not make dlclose safe). Whichever scheme
wins, the code-page policy is separate and currently unwritten — so “EBR”
in ADR-020 reads as if it covers unload safety, and it does not.
Варианты решения (tradeoffs)
Section titled “Варианты решения (tradeoffs)”- (A)
#[repr(C)]POD + opaque handles, C ABI, no vtables, QSBR for module pointer tables with quiescent points in the actor loops. Consistent withADR.md:784(kept), supersedesADR.md:807’s&VTable, and satisfiesOPTIM-4:89. Costs: instrumenting the actor loops for quiescent points (OQ.md:868); more boilerplate at the boundary; the fork plan inOQ.md:878-880(item (d)) is dropped. - (B) Pinned-rustc Rust ABI (
OQ.md:878-880). Cheapest calls (no marshaling at all), but stability is compiler-provenance-based, every module needs a rustc requirement in the manifest, and third-party native modules (dev mode,ADR.md:442-446) would need the same pin — a much stronger requirement to impose on strangers. Also directly contradictsnylon-ring’s raison d’être. - (C)
#[repr(C)]POD + pinned rustc as belt-and-braces. Both constraints hold; costs the ergonomics of (B) with none of its benefit. - (D) EBR everywhere (keep
ADR.md:812). Simplest conceptually, no actor-loop instrumentation; higher steady-state cost on hot paths and it still does not solvedlclose(finding 009).
Предлагаемое решение
Section titled “Предлагаемое решение”(A). It is the only option that keeps abi_version meaningful
(ADR.md:830-831), keeps third-party native modules plausible, and
matches the one document in the corpus that engaged with the actual
unsafe-code mechanics.
- ADR-020 addendum superseding
ADR.md:807-809: export a#[repr(C)]flat pointer table (not&VTable), opaque handles for anything owning memory, explicitcreate/destroyownership; perOPTIM-4:89-90. - ADR-020 addendum fixing reclamation to QSBR with quiescent points
in the actor loops + HP for large-module unload + the
“no guard across
.await” rule as normative (OQ.md:868-870), and explicitly deferring the code-page question to finding 009’s policy. - OQ-024 amendment removing item (d) (pin rustc / no
#[repr(C)]) and the resulting manifest field, or — if the author actually wants (B) — make that an explicit ADR with the third-party-consequence argument written down. Right now the choice is implied in one OQ bullet and contradicted by the file written to implement it. - Add the two missing QA gates: (a) every symbol crossing the native
ABI is
#[repr(C)]/opaque-handle — checkable with atrybuild/lint gate over the contract crate; (b) quiescent-point instrumentation is present in every actor loop that can hold a module pointer. - Mechanical: fix
ERB→EBRatADR.md:812,OQ.md:639,OQ.md:741.