Thin C-ABI surface for Mathless modules (Phase 1). No VM, no interpreter (D02/D11).
| Namespace | Owner | Example |
|---|---|---|
ml_* |
runtime / reserved | ml_module_abi_version |
mlx_* |
user module exports | mlx_discount |
-
A dynamic host resolves exports by name via
GetProcAddress(Windows, measured) ordlsym(POSIX, not yet: there is no.sotarget). Two of the three host directories below do not —hosts/c-host-linkis bound by the linker from the.lib, andhosts/delphi-hostconsumes a generated unit whose functions areexternal ML_MODULE, bound when the program loads. The name still has to match; what differs is who resolves it and when, and that is why a missing reserved symbol fails at link or at startup there rather than returning NULL. -
ml_module_abi_version() -> u32: hosts are required to refuse a major mismatch. The reference C host does refuse, on every module it loads, before the first call (hosts/c-host/host.c, acceptance D). For a third-party host it stays a contract — nothing in the module enforces it, and the Rust oracle only asserts the value matches the compiler constant. -
ml_iface_hash_<module>() -> u64: the module's interface fingerprint — a hash over its host-visible contract (exported signatures including parameter names, and the error table). The name carries the module stem, sodiscount.dllanswers toml_iface_hash_discountand nothing answers to a bareml_iface_hash. That is not cosmetic: every module exporting the same bare name meant a linked host could bind only one of them and the linker chose silently (SPEC-qualified-iface-hash, #215).ml_abi.hin this directory says the same. A host that was built against a different interface must refuse the module. Both reference C hosts do, before the first call, on every module they load — and the drifted module is refused with the symbols still resolving and the ABI version still matching, which is the whole point. Both hosts are cited because both check:hosts/c-host/host.cgate()on the dynamic path, andhosts/c-host-link/host.c:42-45on the linked one, which comparesml_iface_hash_discount()againstML_DISCOUNT_IFACE_HASHand exits 3 (acceptance D). The generated header pins the expected value asML_<MODULE>_IFACE_HASH. Like the version above, for a third-party host this is a contract: nothing in the module enforces it (SPEC-iface-hash§5.1). -
ml_abi.h: the hand-written C header in this directory. Not compiler output —mlcemits.dll/.h/.pas/.liband never copies this file, which is whyLICENSE-OUTPUT-EXCEPTION§3 names it as not Compiler Output.Which binding is verified how is stated in
ml_abi.hitself, not here. This paragraph used to carry a second copy of that status and the two drifted —ml_abi.hlearned the qualified fingerprint name on 2026-09-12 and this file did not. One copy is better than two for the ordinary reason: two copies rot apart and nobody notices which.⚠ One copy is not a guarded copy.
doc_claims.rsreadsml_abi.hfor the reserved declarations and the error-constant shape; the prose about which binding CI proves is checked by nothing, there or here. So the Delphi-only-locally caveat could still go stale without a test failing. Removing the duplicate halves that exposure; it does not close it. The canonical statement of which gates run where is.github/workflows/ci.yml.