Compiled interface metadata
Design note for the Cinch toolchain. Companion to Modules and compilation specification; where that chapter states obligations, this note fixes the mechanism.
Status: design accepted for implementation (roadmap slice P3 in
compiler/CONFORMANCE.md).
1 Why a separate artifact
Separate compilation needs one question answered per import: what does this module expose such that an importer can be checked, resolved, and linked without reading its source? The answer must cover five consumers at once:
| Consumer | Needs |
|---|---|
| Type checker | Exported schemes (with effect rows, using contexts, quantifiers), types, constructors, records, class signatures |
| Fixity pass | Operator precedences and associativity for exported operators |
| Dictionary resolution | Which instances exist, for coherence across modules |
| Code generator and linker | Symbol names, arities, calling convention identity |
| Trust reporting | Primitive axioms, foreign contracts, declared trusted assumptions (core specification, section 11) |
Source text is not an interface: it is larger, it changes with comments and layout, and it lets a recompilation cascade where an interface would not.
2 The artifact
Each compiled module produces a pair:
build/<Module>.o native object (LLVM IR compiled by clang)build/<Module>.cinchif compiled interface metadataand the toolchain additionally links the Rust runtime libcinchruntime.a
(runtime is Rust; see compiler/runtime/src/lib.rs for the runtime ABI
reference).
.cinchif is canonical JSON: UTF-8, keys in the fixed order below, one
canonical spelling for every type and row, no timestamps. Its content hash is
the module’s interface identity (iface_hash); rebuilds that change
nothing observable produce the same identity.
2.1 Content model
schema interface format version (integer)language language draft this artifact conforms toruntime_abi runtime ABI version (must match libcinchruntime.a)compiler compiler version that produced itmodule module namesource_hash hash of the canonical source textimports [ { module, iface_hash } ] for each import consumedexports values name -> scheme (quantifiers: type, row, region; using context; type with effect rows), plus symbol, arity types name -> { params, kind: data|record|opaque, constructors: [{ name, tag, fields: [type], erased: [bool] }], fields: [name] (records, declaration order) } signatures name -> { params, methods: [{ name, scheme }] } instances [{ id, class, target, ctx, methods: [symbol] }] fixities operator -> { assoc, prec } foreign [{ symbol, abi, external, type, effects, notes }] (FFI contracts; see the FFI specification for their obligations)assumptions primitives [ "cinch.prim.*" ] used by this module's code foreign external symbols relied on (the trusted boundary entries) declared trusted assumptions named by policy, if anyabi symbols exported name -> linker symbol (the compiler's quoted `<module>.<name>` convention) convention "uniform-word, curried apply stub" (see core.rs)assumptions.primitives is the acceptance manifest’s primitive-axiom list:
Nat/Int semantics, machine arithmetic and memo/thunk machinery are the
trusted kernel of the artifact; everything else is checked against them.
2.2 Identity and invalidation
source_hashidentifies the input;iface_hashidentifies the output contract. A module recompiles when its own source hash changes; its dependents recompile only wheniface_hashchanges. The identity is therefore computed over the contract withsource_hashandcompilerexcluded: a body-only edit or a compiler point release does not invalidate dependents. (Review finding, fixed: the first implementation hashed them in, which would have made every body edit stale the world.)importsrecords the exact interface identities a module checked against, so a build can prove that every consumer was checked against the interfaces actually linked (the “checked against” chain, an assumption-manifest requirement).
3 Consumption rules
- Importers read interfaces, not sources. The driver compiles each
module to
.o+.cinchif; checking a module loads the.cinchifof every import (recursively throughimportsfor transitive names). - Name visibility follows the language rules: a module’s own names,
then imported modules’ exports (unqualified and
M.xqualified). - Fixity merge: the importing module’s own fixity declarations shadow imported ones for its own code, matching the current parser behavior; the merged table is what its interface records for its own operators only.
- Instance coherence: two
instancesentries across the closed artifact set whose(class, target)unifies are a link-time error (“ambiguous instance”). Instances are unrestricted in where they may be declared for now (no orphan rule); global uniqueness at admission is the coherence guarantee. A later slice may add an orphan rule; if so it replaces this paragraph in both the interface schema and the modules specification. - Dictionary identity is per instance id: generated code refers to
"<module>.<instance>.mk"construction units; importers reference the symbol recorded inabi.symbolsrather than recomputing names. - Linking produces one binary from the module objects plus
libcinchruntime.a. The final artifact records the union ofassumptionsacross its modules and theruntime_abiit linked against (an artifact manifest, per core specification section 11).
4 Runtime and ABI coupling
- Value representation (uniform words, boxed ADTs, closure records with the
curried apply stub) is defined in
compiler/src/core.rsand mirrored by the runtime crate’s module docs. The interface’sabi.conventionnames this pairing; a change to either bumpsruntime_abi. - Symbols are the compiler’s quoted
<module>.<name>identifiers; interfaceabi.symbolsis the authority for what a linker should resolve, so codegen and metadata cannot drift silently.
5 Evolution hooks
Later slices extend the same artifact additively:
- FFI:
exports.foreigngains layout and ownership-role fields (the FFI specification’s binding obligations) andassumptions.foreigngains the trust profile of each external library. - Upgrade packages:
upgradedeclarations add record-view and migration metadata (field maps, schema compatibility evidence) so a replacement module can be checked against its predecessor’s interface. - Rewrites and optimization:
exports.rewriteslists optimizer rules with their justification symbols; cross-module inlining adds optionalbodiespayloads keyed byiface_hashso inlining cannot outlive its evidence. - Proofs:
exports.proofsrecords checked evidence identities and the kernel version that checked them (core specification section 11).
6 Rejected alternatives
- Shipping sources as interfaces: rejected above: rebuild cascades and no stable identity.
- A single program-wide metadata file: rejected: loses per-module identity, forces whole-program relinking of metadata, and cannot express “checked against exactly these interfaces”.
- Binary-only metadata: rejected for now: canonical JSON keeps the trust story reviewable by a human; a binary form can be added behind the same schema version later.
