This is the target contract. An experimental binary version 63 now exists for the tested C/Go and portable scalar/record/enum subsets. It is not yet the complete contract below; see Implementation status.

.zir is a versioned, checked, serializable module representation. It is the shared input to native backends and the portable linker. It records module and symbol identities, imports/exports, resolved types, constants, function bodies, control flow, source locations, declared FFI, and host capability requirements. Backends may attach target-specific output metadata outside the language model, but they must not reinterpret source text to recover program meaning.

The Ziran compiler provides a canonical writer and a validating reader. The reader rejects unknown incompatible versions, truncated or malformed data, invalid symbol references, and invalid types with useful diagnostics. The writer produces deterministic bytes for equivalent checked modules. A backend given saved .zir has the same language semantics as one given the .zi source that produced it. Diagnostics retain source paths and spans where available.

The IR represents general calls, records, slots, imports, and capabilities. Scalar types use the current source names (s32, s64, float32, and float64, for example); int and float aliases are canonicalized before serialization. Earlier internal i32/i64/f32/f64 names are not accepted. It has no widget kind, #ui flag, DOM attribute cache, KSS node, or implicit Kryon module. A Kryon widget call has the same representation as a call to an unrelated imported function with the same type shape.

Version 57 retains optional architecture-specific #asm variants alongside each function's checked portable body and local block bodies. Block metadata binds named scalar/pointer locals or parameters, and checking validates every binding in its lexical scope. Concrete generic instances retain these variants and validate their operand types and portable bodies. C and Go may select a matching variant; other targets and the portable VM execute the checked body. The variants are explicit native metadata, never recovered from diagnostic statement text. Only annotated blocks carry the optional metadata payload; ordinary statements retain their previous encoding size. Earlier binary versions are rejected; regenerate saved IR and bundles from source. See assembly.

.zir is the compiler interchange and cache format. Portable distribution uses a linked .zx, not an unlinked .zir. See Bundle format.

Experimental version 52

Foreign imports retain typed parameters, return types and a checked go_results flag for packing native Go results into record fields in order. The checked go_field flag describes typed foreign Go field getters and receiverless package-value getters. Package-value getters have no parameters and read the target variable or constant on each call. Native interface identity is preserved, and getters cannot return owned vector storage. reads_arguments, written #reads, records that a foreign procedure only reads its string and byte arguments during the call; it occupies a slot format 59 reserved as zero, so older saved modules read as unmarked. go_defer schedules a native void call at the calling Go function's exit, including panic unwinding. These flags require explicit Go foreign targets and cannot be combined. Deferred calls must be standalone statements with no owned arguments or variadic parameters. Go method/field targets retain their receiver type, for example go:net/http.(*Request).RemoteAddr. Native emission uses these checked fields; the original foreign declaration string is diagnostic metadata.

Overloaded procedures each carry a unique name and share their source name in overload_name; checked calls already name the chosen overload.

A record type flagged is_results holds the results of a procedure with several results, in fields value_0, value_1, and so on; the source's multiple returns and bindings are already lowered to that record. When a polymorphic procedure's results depend on its type parameters, this record is an ordinary generic record template flagged is_results. Concrete applications retain the flag and use the same checked field access as other multiple results. A saved library can gain concrete types and procedure specializations while checking a source consumer; validation still compares every original declaration and its checked metadata in the original order.

Law records include structured evidence. Scalar theorem propositions and #proof certificates have separate expression graphs and ordered proof steps; they are not executable functions. Consumers retype those graphs and recheck each certificate against the checked procedure bodies. Cached evidence and annotations cannot authorize a proof. See Laws and scalar proofs.

The current writer emits ZIR followed by a zero byte, a four-byte little-endian version number, and the checked module records. Integers are unsigned LEB128, with signed fields zigzagged so -1 takes one byte. A string is written once: a tag of 0 is empty, an even tag is twice the length of a new string whose bytes follow, and an odd tag names an earlier string by twice its first-use index plus one. A source span stores its line as a delta from the previous span, flagged when its file changes (the path then follows), then its column and the deltas to its end. Process pointers and C struct padding are not serialized. The reader rejects wrong versions, truncated records, overlong or out-of-range integers, unknown string references, oversized fields, invalid references, and trailing bytes; each string reference counts its length against the reader's memory budget. The C and native Go commands can build saved .zir modules, including an imported two-module typed record call example. The experimental .zx bundler also consumes saved .zir for its restricted scalar, record, and enum subset. Native and bundle builds rerun the language checker on the saved expression graph. The reader rejects invalid references and cycles, and the checked serialization must match the saved bytes. Every build uses strict checking, including native builds from source. Checking is unconditional; --strict and --no-strict are rejected. Call argument expressions retain source order and store their source name and checked parameter index. Native emitters and the portable VM evaluate in source order, then pass values in parameter order. Procedure declarations retain their default expressions; omitted arguments are materialized as checked call expressions before serialization. Concrete defaults use checked procedures in the declaring module so their names keep that module's scope. Runtime-dependent global initializers lower to private checked startup functions. Their startup flag is serialized and validated; the linker retains them with reachable modules and their call dependencies. For checked functions, statement text is source metadata; the expression graph and explicit branch flags and loop target IDs determine behavior. The checker requires each named loop exit to point to a unique enclosing loop. Exhaustive enum if #complete value == { ... } is lowered to typed branches and an unreachable guard before writing IR, so saved modules contain no case selection statement to reinterpret. Source variant, payload match, postfix ?, guard, C-style switch and goto with labels, state blocks, C-style locals, and raw C statements are rejected. The validating reader rejects retired statement kinds. The postfix increment/decrement expression kind has been removed; version 41 rejects earlier files before graph validation. Variant fields, generated variant function slots, retained-state fields, and unused statement callee/argument text are absent from the IR schema. Generic record templates retain their parameter and field declarations. Explicit specializations are stored as ordinary concrete types. Direct applications such as Vec(u8) retain their template and argument identities in version 44, so checked types have the same identity across importing modules and saved IR. Older binary versions are rejected. Record field bodies preserve checked #go_tag string literals for Go reflection metadata. These tags do not change field storage in other native targets or the portable runtime. Entry linking preserves complete record layouts exposed by native foreign signatures, callbacks, size queries and pointer casts, including nested records and fixed-array element strides. A cast from raw native memory must keep fields that ordinary member expressions never read; removing them would change offsets. Records used only through checked member access may still omit unused fields. Record declarations retain #go_anonymous in version 54. Go emits these records as aliases of unnamed structs, preserving native reflection and JSON error types. Other targets retain their ordinary record storage and identity. Version 55 retains procedure #go_method names and their #go_results flag. The checker validates the local receiver record, native method conflicts and concrete result storage. Go emits a receiver adapter that calls the canonical procedure and optionally flattens its result record. Other targets keep the ordinary checked procedure; method metadata does not redefine its body. Record bodies retain using on contained fields. A checked member expression that names a promoted field records its concrete contained-field path; the reader verifies each intermediate field is a using field. Native backends and the portable runtime follow that path through the stored record layout. Union types preserve their shared-storage flag. C uses that flag for native declarations; Go and the portable linker currently reject union storage. Polymorphic procedures retain their $T signature and structured body. Templates also retain using parameters and statements until concrete specialization; checked instances contain ordinary member accesses. Concrete call specializations carry their type and generated identity; imported saved modules can instantiate them when a source caller uses a new type. Procedure and foreign declarations retain #must, and the checker rejects discarded results when reading source or saved IR. Specialized Vec(T) records carry an owned-storage bit. The reader validates their storage shape before allowing vector operations. Compile-time #if selection is complete in the frontend. Saved declarations carry no C preprocessor guard, and #assert failures are diagnosed before native or portable output. Assertions store their condition and message only; checking evaluates the condition again when reading saved IR. Type and constant records preserve export and file-private visibility for imported-name and loaded-file checking. Exported procedures retain an optional quoted linker symbol separately from their Ziran source name. Native function bodies use checked statement and expression graphs; statement text remains diagnostic metadata. Version 33 is experimental and has no compatibility promise. Expression records preserve whether #this selected the enclosing procedure, so checking saved IR keeps that binding even when a parameter has the same name.

Version 45 stores opaque foreign Go type targets (go:package.Type). These are checked named types and lower to Go aliases, preserving the imported type's methods and JSON behavior. They have no Ziran record fields or known layout. Other native targets reject them, and the portable runtime cannot store or execute them. Older binary versions are rejected.

Version 46 distinguishes native Go maps from records. Their checked body retains key and value types; Go emits a map alias with shared storage and a nil zero value. Other targets reject reachable maps. Predeclared any and error interfaces use the go:builtin foreign namespace without an import.

Version 58 makes a function's checked bit describe language validity only. Native symbol planning and emitter eligibility are separate passes. Source spellings remain diagnostic and serialization data; immutable parameter and field snapshots provide the declaration view used by compiler passes. Cache identities and compilation-context pointers are in-memory data and are never written to .zir or .zx.