This page reports the local repository as it exists now. Architecture, .zir, and .zx define the target state.

Test citations below include historical verification. The lean-suite pruning removed redundant scripts; those fixtures remain in Git history. Run make check for the complete current suite.

Working now

  • std/simd vector types and operations run with identical bits in native C (GCC and Clang vectors with SSE2, SSSE3, SSE4.1, AVX2 and NEON intrinsics, or the lane loops TinyCC and other compilers use), Go lane loops, bundles on the native engine and the portable VM, from source and saved IR. #target procedures get SSSE3, SSE4.1 and AVX2 clones in C behind a dispatcher that reads CPUID (and the OS's YMM state) once; ZIRAN_SIMD_TIER forces a lower tier, and tests/simd_target.sh checks each forced clone under gdb where it can. Checking rejects 256-bit vectors by value at a #target procedure's boundary and in its calls. C built for wasm32 with -msimd128 uses SIMD128 instructions and gives the same lanes under wasmtime as a build without them; bundles on a WebAssembly host run SIMD through the portable VM. ARMv7 builds with NEON, with VFP only and with scalar lanes give the same results on an ARMv7 phone; AArch64 builds hold NEON instructions but run only under a QEMU_AARCH64 here.

  • GCC object-cache preparation pipes the empty compile probe, allowing its compiler and assembler to overlap without a temporary assembly file. It keeps the previous compiler, math-library, assembler and checksum information and fingerprints the complete derived command options, including installed specs changes. Clang retains its ordinary single compile probe. Failed, incomplete, ambiguous or oversized probe output falls back to normal compilation. tests/native_build_cache.sh covers changed compiler configuration and library versions, both probe paths and failure fallback; process profiles count the probe operations. Runtime-include scanning keeps literal token lengths in its table rather than computing them for every emitted identifier. A successful piped identity probe also enables piped object compilation, overlapping compiler and assembler stages for each generated translation unit. Existing explicit flags are retained without adding a duplicate object-compile flag; unsupported or failed probes keep ordinary compilation. Cache identities include this compile policy, and profiles count piped object attempts. The native-cache test observes actual native-driver arguments, unchanged-object reuse, explicit flags and failed-probe fallback. Linker selection/pass-through flags (-fuse-ld=, -Wl,, -Xlinker) and tool prefixes (-B) also retain object reuse. The probe fingerprints the selected compiler/assembler and include roots; changed prefix tools invalidate their objects. Linking runs on every build, regenerates requested maps and checks current library inputs. Source/saved-IR tests cover these options, prefix-tool replacement, option values and missing-library failure. The identity hashes only environment variables a compiler reads, so another shell or build-queue ticket reuses objects, and accepts a #! compiler wrapper by its content. Adding a generated C file (a split unit or a new module) rebuilds only that file. tests/native_object_reuse.sh checks a one-module edit rebuilds one object across these cases.

  • Generated C public headers carry lightweight string value and caller location definitions. Sources include string operations, bounds checks and standard-library helpers when their emitted code uses them, reducing the preprocessing needed by numeric modules. Runtime headers bring their own dependencies into the output directory, so generated code remains directly buildable without Ziran's include directory. Native string representation, embedded NUL bytes, checked bounds and float formatting remain the same. tests/native_runtime_headers.sh checks source and saved IR, exported ABI values, standalone C builds and stable copied-header timestamps.

  • C emission collects a module in a memory stream where supported (a temporary stream elsewhere) and resolves runtime includes and numeric helpers while writing its final temporary output. Memory streams avoid writing and rereading an intermediate C file. Identical generated files keep their timestamps; large source and saved-IR modules are executed by tests/native_runtime_headers.sh.

  • Native C compilation uses up to sixteen available CPU workers, bounded by the source count and by the process's CPU affinity on Linux. It refills each slot independently, including when another file is slow. Linux child-completion descriptors avoid periodic wakeups when available; other systems retain an owned-PID wait path. JSON compiler logs remain in source order, failed builds drain started jobs and retain the existing executable, and unrelated children remain waitable by their caller. tests/native_build_parallel.sh checks both output modes, blocked workers, truncation, failure and fallback; the native object-cache tests cover incremental reuse and changed inputs. Profiles separate native toolchain preparation, objects and linking and count compilation attempts and reuse. bench/native_builds.py can pair a preserved compiler with the current one alongside C and Go. Tool probes, compiler workers and the linker prefer posix_spawnp, with fork/exec retained when spawning fails. Capture pipes, inherited build locks and owned-child waits keep the same behavior. The parallel-build checks also cover missing tools, executable scripts without a shebang, forced spawn unavailability and child-held locks after their builder exits. Profiles count spawn and fork launches separately; gains depend on the host and workload. Android hosts below API 28 keep fork/exec without references to unavailable spawn symbols; optional NDK checks link both paths for all four Android ABIs. Tool identities are sorted separately from ordered include roots, so moving the assembler's verbose command across the compiler's search diagnostics does not invalidate otherwise reusable objects. The cache checks reproduce both output schedules with the same options and include-root precedence. When a complete GNU assembler/BFD version banner splits cc1's compiler prefix from its GMP library versions, the fingerprint reconstructs those two records without dropping their contents. Checks cover the actual unchanged-build path, changed versions and ordered options, CRLF and incomplete or unfamiliar records; unrecognized interleaving remains a conservative miss. Profiles separate cache lookup and recording from the overall object stage. Dependency-name lists retain insertion order, grow geometrically and index exact duplicates once a list reaches 32 items. Sorting or canonicalizing names discards that index. The cache retains its existing content hashes, search-order validation and metadata format; hash buckets use equivalent 32-bit arithmetic to avoid wide multiplies on 32-bit compiler hosts.

  • Qualified field types traverse reachable imports with an indexed visited set and an explicit, growing depth-first stack. The former 512-module cutoff is removed, while the caller's type visibility checks and symbol generation invalidation remain in place. Checks cover 2,049-module wide graphs, a 4,096-module cycle on a 256 KiB thread stack, and first/last fields in a 554-module program from source, saved IR and bundles. Native foreign layout checks retain the full 2,048-byte record ABI.

  • Public type re-exports use an iterative graph walk instead of a bounded recursive search. Entry-local types keep file/module shadowing precedence; transitive lookup follows public using edges and stops at module locals. Private imports remain usable locally without exporting their type names. Conflicting declarations remain ambiguous even when another route supplies one of them. Checks cover source/saved cyclic 64-module chains, native and portable execution, and 4,096-module walks on a 256 KiB thread stack.

  • Body eligibility checks reuse immutable primitive-record layout facts and check each reached typed expression once per pass, including shared graphs. Answers remain local to that pass, so rewritten bodies and other lookup scopes are checked again. tests/body_eligibility.sh covers shared nodes, malformed backedges, record rewrites and distinct same-named declarations; the compiler type matrix checks concrete builtin value classification. bench/body_eligibility.py measures paired checking, C emission and bundle construction from source and saved IR, requiring unchanged output bytes and independent native/VM arithmetic results. It can include offline real package checks. This does not establish general Go/C performance parity.

  • Portable bundle writing omits native assembly templates while retaining the checked bodies and full native .zir data. It measures the payload and writes directly to files or pipes without a temporary payload file. tests/zx_portable_write.sh checks graph immutability, binary assets, certificates, persistent state, append streams and write failures; tests/bundle_libraries.sh covers shared and included code. bench/zx_metadata.py compares bundle size, packing and fresh-host loading with a preserved compiler, including a reader/writer crossover and ordinary bundles without assembly. Its large comment annotations are a stress case.

  • C translates assembly operand aliases to positional references, including aliases that are host keywords. Further direct Go architecture/instruction coverage and full platform parity remain open. Compiler output buffering also supports hosts without Unix memory streams and write-only callback streams; Windows builds use native directory creation and accept backslash input paths. tests/text_stream.sh covers empty and binary output, positions, write errors and temporary-file fallback. bench/android_execution.py prepares matched C/Go controls and checks device timings against an independent oracle; these single-workload results do not establish overall C/Go parity or Android compilation speed.

  • Go output optionally uses the shared GNU assembly emitter through cgo when built with -tags=ziran_asm and CGO_ENABLED=1. Ordinary builds retain the checked Go body behind a compile-time-disabled branch. Native function and local-block bridges preserve CPU guards, GNU constraints, labels and caller memory aliases. Flat record results cross as primitive fields; pointer-bearing objects and unsupported local pointer operands keep their checked bodies. tests/go_assembly.sh covers source/saved IR, package startup, concurrency, native test modules, output pruning and strict cgo pointer checking. bench/go_assembly.py measures whole-loop and per-operation placements: cgo transitions can dominate small operations, so native execution is opt-in. With CGO_ENABLED=0, the same -tags=ziran_asm opt-in now emits direct AMD64 Go ABI0 assembly for represented numeric functions and register-result blocks. GNU alternative columns select one complete representable operand column, retaining tied operands. Fixed registers, integer/float widths, flat result records, local labels and AT&T dialect alternatives retain their checked bindings. Generated Go declarations describe the argument/result layout; leaf instructions preserve the stack, frame and goroutine registers. CPU selection is cached from CPUID and checks OS-enabled extended register state before AVX variants. An available but unrepresented earlier variant retains the checked body instead of selecting a later variant out of order. Pointer results, pointer operands, memory-result blocks, stack-changing instructions, external calls/data and other unrepresented GNU forms keep the checked Go body in this path. Existing cgo builds retain their native bridge. On the tested Go 1.24 toolchain, long direct assembly loops can delay Go scheduling and garbage collection; the cgo path remains available. Native Go assembly uses package builds (go build -tags=ziran_asm .), where Go also compiles the generated .s companions. tests/go_direct_assembly.sh checks mixed ABI layouts, CPU predicates against the C compiler, native dispatch, unsupported variants, GC/stack stress and cross compilation.

  • Generic functions and local blocks retain assembly through concrete specialization, including saved-library imports. Each instance validates operands and its portable body. Caller-owned pointer/array types retain their identity, while fixed library types remain in the defining scope. tests/generic_assembly.sh checks C/Go assembly and forced fallback, VM fallback, flat records, multiple results and invalid instances.

  • Opt-in compiler process profiles record inclusive loading, parsing, checking, native emission, and IR writing, with record-field parsing counts and workspace allocation requests. tests/compiler_profile.sh checks the schema, separate output, and failure reporting. bench/compiler_scaling.py validates module graphs and local/imported generic specializations from source and saved IR on native C and .zx, retains phase profiles and whole-command samples, and can check real package entries. These are measured workloads, not a claim about every application or target.

  • JSON diagnostics carry a schema version, structured initializer, assignment, argument, and return type details, and related declaration locations where available. Missing names include suggestions and source edits when the lexer confirms one unambiguous identifier on the reported line. tests/diagnostic_metadata.sh checks the metadata and verifies that applying the suggested name edits produces a checked program. The VS Code extension shows related locations and offers Quick Fixes guarded by the original text. Compiler tools, the native package launcher, and formatter honor JSON mode before argument validation and report ordinary input/output, allocation, and native toolchain failures through the versioned schema. Invalid UTF-8 filesystem/log bytes are escaped. Native C compilation drains captured output even after its bounded log fills. tests/tool_diagnostics.sh covers these failure paths and allocation-free out-of-memory reporting. Arbitrary external package tools and fatal signals remain outside that transport.

  • The native ziran command, including its launcher and package child-process transport, and zi-fmt are implemented in Ziran. Their build compiles .zi to native output without handwritten C command adapters or shell/AWK formatting policy. Package allocations use the language's size_of instead of C size helpers. tests/native_commands.sh rebuilds the command from its source and saved IR, retaining version output, tool discovery from PATH, inspection commands, checking, and formatting. tests/package_process.sh checks source and saved IR, literal arguments, child-only environment changes, working directories, exit/signal status, truncation with pipe draining, and rejected arguments. The formatter preserves strings, raw-string delimiter columns, and nested comments; it writes through a temporary file beside the source and retains permissions and symlinks. tests/formatter.sh checks semantic preservation, idempotence, filesystem errors, a saved-IR command rebuild, and scanner agreement on C, Go, and .zx. The parser, checker, native backends, and portable runtime still contain C; the compiler is not yet fully self-hosted.

  • Portable slices and arrays of imported records compare their element type identities across module aliases, retaining shared slice storage when callers and callees use different spellings for the same record. tests/portable_imported_record_slices.sh checks aliases, returned slices, shared mutation, empty views, array value copies, generic record elements, and rejection of distinct same-named records from source and saved IR.

  • Self-hosting moves the compiler core into Ziran one rule set at a time. Each cmd/compiler_*.zi module owns its rules; C calls the generated code through a thin boundary that keeps IR storage, spans and diagnostics, and the replaced C is deleted. Fresh builds first compile a bootstrap compiler from the seeds in bootstrap/, then rebuild every tool from the maintained .zi sources. A seed also keeps the generated headers of standard modules it uses, such as vec.h. make check-bootstrap verifies the seeds and make update-bootstrap regenerates them. Each tests/compiler_<module>.sh checks source and saved IR on C, Go and .zx, a deterministic input corpus, seed agreement and the C boundary.

Module Rules it owns
compiler_scan.zi Tokens and cursor positions; the C lexer's comment skipping
compiler_text.zi Whitespace and identifier bytes, trimming, integer literals (decimal, 0x, 0b, octal, _), C string escapes, top-level argument splitting, UTF-8, print formats, operator names, name distance
compiler_source.zi Raw strings, comments, statement kinds and separators, braces, procedure headers and modifiers
compiler_declaration.zi Parameters, imports and using clauses, foreign declarations, multiple results, defaults, BuilderPrint, #symmetric, local renames
compiler_enum.zi Enum members, values and checked constant arithmetic
compiler_type.zi Type spelling, record fields, #go_tag, array capacities, foreign type symbols
compiler_expression.zi Operator rules, #char, and statement expression parts
compiler_parse.zi Expression and initializer grammar building shared IR directly, spans, nesting limits and typed initializer fields
compiler_statement.zi Loop and control headers, then/else, #if headers, name references
compiler_proof_kernel.zi Scalar proof terms, polynomials modulo 2^w, order facts, case splits and the proof budget
compiler_proof.zi Theorem propositions and #proof certificates over checked IR: pure scalar procedures unfold into kernel terms
compiler_law.zi Effect-class derivation and effect-law evaluation over checked IR, using shared compiler services
compiler_bundle.zi The .zx container: header, entry names, capability, law and waiver tables, the asset section and every limit, written and read by one walk
compiler_ir.zi The IR records C shares: modules, functions, statements, expressions, types, laws and proofs, their kinds and buffer sizes. zir.h includes the generated header, spelled for C with #c
compiler_services.zi The C services Ziran IR modules call: kept text, source paths, diagnostics, symbol lookups and import storage
compiler_link.zi Import aliases and resolution, file-private renaming of procedures, constants, globals and types with their references, and the #inline self-call check

C files in cmd/zir, with line counts on 2026-11-07. Boundary means the rules live in Ziran and C keeps storage, spans and diagnostics. C means there is no Ziran counterpart yet. Expression parsing no longer uses callback slots; quoted searches also reuse compiler_source.zi instead of a second C scanner.

Area Files (lines) Status
Frontend boundary zir_token.c 70, zir_text.c 431, zir_enum.c 131, zir_type.c 470, zir_parse_declaration.c 926, zir_cleanup.c 1272, zir_expr.c 386 Boundary; expression grammar builds shared IR in compiler_parse.zi
Parser main loop zir_parse.c 732, zir_parse_source.c 2274, zir_parse_discover.c 1713 C, calling the Ziran scanners per line
Compile-time evaluation zir_parse_typed.c 2227, zir_parse_condition.c 739, zir_parse_eval.c 649 C; one typed evaluator serves #if, #ifx, #assert, #run and constants
Checker zir_check.c 1644, zir_check_expr.c 2987, zir_check_statement.c 2210, zir_check_text.c 2050, zir_check_value.c 1733, zir_check_link.c 877, zir_check_program.c 1044, zir_check_function.c 931, zir_check_simd.c 367 Boundary for import linking, file-private names and #inline self-calls (compiler_link.zi); the rest is C
Borrows, laws zir_borrow.c 2005, zir_law.c 1871 Boundary for effect classes and effect laws (compiler_law.zi); proofs moved to compiler_proof.zi; the remaining law checks are C
C and Go output zir_emit.c 2053, zir_emit_expr.c 2144, zir_emit_statement.c 2096, zir_emit_range.c 2417, zir_emit_value.c 1316, zir_emit_call.c 1234, zir_emit_simd_go.c 1261, zir_emit_gpu.c 843, zir_emit_simd.c 458, zir_asm.c 1996, zir_simd.c 562, zir_runtime.c 274 C
IR, loading, storage zir.c 2392, zir_serial.c 1811, zir_load.c 1285, zir_storage.c 474, zir_edit.c 338, zir_body.c 286, zir_packages.c 275, zir_sha256.c 258, zir_diagnostic.c 236, zir_profile.c 229, zir_context.c 166, zir_files.c 154, zir_memory.c 131, zir_native.c 127, zir_document.c 120, zir_overlay.c 43, zir_semantic.c 29 C
Bundles zir_bundle.c 2986, zir_bundle_assets.c 103 Boundary for the .zx format and its table checks; IR linking, pruning, law evaluation and capability discovery are C
VM zir_vm_eval.c 3697, zir_vm_run.c 2079, zir_vm_verify.c 1442, zir_vm.c 1352, zir_vm_simd.c 667 C
Bundle hosts zir_host.c 844, zir_zx_host.c 783, zir_zx_native_run.c 896, zir_zx_native_lower.c 721, zir_zx_native.c 506, zir_zx_wasm.c 149, zir_zx_wasm_web.c 136 C platform glue

The expression-parser slice reduces handwritten code from 1,558 lines to 1,160, including its 35-line services boundary and assembly metadata retention (433 parser lines removed, 398 net). Sharing quoted searches removes another 12 lines, including the C declaration. Counts exclude generated seeds and tests. The parser main loop, discovery passes and typed compile-time evaluator still run in C; their migration remains unfinished. The compiler is not yet self-hosted.

The effect-law slice removes 246 handwritten C lines and adds 233 Ziran lines. Including its header, shared services and proof diagnostic cleanup, compiler sources shrink by six lines; build rules add fourteen. Generated seeds, tests and documentation are excluded from these counts.

  • Portable imported callback signatures resolve their parameter and return types in their defining module. Record and enum parameters and record results retain their identities across module aliases. Aggregate field checks also retain type identities through pointers, slices, and arrays instead of accepting an unrelated same-named record. The corpus exercises imported callback records, enum parameters, and record results from source and saved IR alongside native targets.

  • Imported polymorphic procedures specialized for caller-owned records retain calls to their defining module's private helpers, including recursive generic helpers and helpers from loaded files. Nested specializations keep the caller's file scope when selecting an import alias. Generated helper identities stay out of the text and JSON API, and private source names stay inaccessible to importers. Procedure types with the same name in different modules, or names that are native target keywords, receive distinct valid native identities. tests/named_generic_dependencies.sh checks these cases from source and saved IR on C, Go, and .zx. C includes private named-module dependencies in generated implementation files, keeping them out of public headers; tests/private_module_imports.sh checks C output.

  • Ordinary Ziran procedures implement native Go interfaces with #go_method "Name"; the first parameter supplies a local record receiver. Generated methods call the canonical checked procedure and optionally flatten its result record with #go_results. Source and saved IR retain value/pointer receivers, native error and panic identity, and ordinary calls on C and the portable VM. tests/go_method_exports.sh checks native interface dispatch, mutation, failure paths and rejected method metadata.

  • Typed Go bind bindings capture leading procedure arguments and return a checked procedure for the remaining arguments. Native Go closures preserve captured values, shared pointer/slice storage, result records, errors and panic identity, including fully bound and void procedures. Source and saved IR validate parameter and return types and reject owned vectors and C callbacks. tests/go_bind.sh checks native execution, imported signatures, nil callbacks, declaration text independence and target reachability. Native retain bindings preserve a value under Go's managed lifetime without copying backing data; the same tests cover string/slice/record storage identity, returning native text through a pointer and rejected retention signatures.

  • Typed Go assert bindings retain native interface checks, zero values on failure, typed nils and shared pointer/slice storage. Typed spawn bindings start capture-free Ziran callbacks in native Go goroutines, capture arguments before starting, and reject result-bearing callbacks, mismatched signatures and owned vectors. std/channel_go supplies native typed channel handles, nonblocking sends and closure; std/context_go adds child cancellation and deadlines. tests/go_assert_spawn.sh checks source and saved IR, asynchronous startup, native identities, cancellation, panic cleanup and rejected bindings.

  • Native Go header bindings use go:C/<relative-header> and preserve the C header's declared ABI and type layout. Scalar/pointer adapters, C field and constant access, and deferred cleanup survive source and saved IR lowering. Generated build-tag companions keep ordinary procedures available without cgo; go:builtin cgo_enabled selects application fallback paths, while direct unavailable C calls panic. tests/go_c_headers.sh checks native allocation, pointer mutation, size_t, field setters, error/panic cleanup, strict cgo pointer checking, no-cgo execution, invalid ABI/header declarations, declaration-text independence and portable entry pruning.

  • Native Go init bindings preserve package initialization before generated globals, once per imported package. Blank imports share package identity with named function and type imports. Source and saved IR validate the nullary void signature and preserve initializer metadata independently of declaration text. Go executable entries now have generated provenance and tracked output ownership. tests/go_package_init.sh checks initialization order, repeated calls, import deduplication, no-cgo execution, entry pruning and stale entries.

  • Foreign slice returns resolve generic element types after linking and type normalization. Native Go make and append can return []Map(string, Any) and slices of imported generic records without declaration-order failures. tests/imported_host_slices.sh checks source and saved IR in both module orders, native map/record types, allocated empty slices and malformed types.

  • Go constants retain escaped quotes, backslashes and punctuation inside string literals. Previously an escaped quote could end string translation early, removing semicolons or rewriting text inside a native constant even though checked uses retained the original string. tests/go_constant_strings.sh compares native constants and checked values from source and saved IR, including raw multiline strings.

  • Native C library modules route through C and cgo on Go. Go supports scalar and pointer C signatures and retains native link flags in generated output. tests/native_c_ffi.sh checks source and saved IR; the OQS and SQLite packages additionally exercise their real engines on both backends.

  • Procedure-type parameter normalization is implemented in cmd/compiler_declaration.zi, including named and unnamed parameters, nested type applications, bounded output, and the 16-parameter limit. Defaults on procedure-type parameters now fail with a direct syntax diagnostic. The C parser retains declaration storage and source spans. tests/compiler_declaration.sh checks source/saved IR on all four targets, the C boundary, and reproducible bootstrap output.

  • Go enum lowering retains every enum declaration and member instead of silently stopping at 32 types or 64 members. tests/large_enums.sh checks a 96-member enum and 40 further enum types from source and saved IR on C, Go, and .zx.
  • Bundle re-export lookup visits shared dependencies once per query instead of repeatedly following every import path, and has no 32-hop cutoff. tests/bundle_global_reachability.sh checks a 40-module graph with shared imports, unused state, global mutation, and source/saved bundle agreement.
  • ziran features publishes a versioned, stable-ID feature registry with syntax, status, target support, limits, accepted and rejected examples, rejection behavior, and evidence links. Its 15 contracts cover compile-time assertions, laws and execution, CPU parallelism and GPU fallback, printing, heap allocation, named imports, integer width conformance and widening, automatic vector cleanup, local TextView mutation checks, record-field vector moves, procedure defaults, and procedure values. docs/FEATURES.json is byte-identical to ziran features --json; tests/features.sh checks schema, sorted unique IDs, evidence, target entries, every accepted example from source and saved IR on each advertised target, and every explicit rejection.
  • Source and saved-IR portable runs use the declared width for integer conversions between all eight s8, u8, s16, u16, s32, u32, s64, and u64 forms. Signed overflow and unsigned wraparound follow the operand width, the minimum signed integer divided by -1 wraps to itself, and its remainder is zero. Shifting by a negative count or by the operand's bit width or more fails at execution on every target. Runtime float32 and float64 division can produce infinity or NaN on native and portable targets; NaN comparisons and infinities retain floating semantics, while converting a nonfinite float to an integer remains an error. Compile-time casts and arithmetic fold the tested boundaries with the same width rules. Generated C headers carry a static inline copy of each small public procedure that only uses what the header declares, which a function-like macro routes other modules' calls to; the defining .c keeps the external definition. Plain -O2/-O3 builds inline across modules without LTO and keep per-module object reuse. tests/cross_module_inline.sh checks C, Go, native and portable results, the inlined calls, C hosts taking addresses, C++ hosts and rebuilds after an inlined body changes. #inline procedures are always_inline in C, private ones static inline, and public ones keep their header copy past the size limit; #cold procedures are noinline, cold, get no header copy, and are //go:noinline in Go. Saved IR keeps the modifier. tests/procedure_inlining.sh checks the attributes, out-of-line and inlined calls, C at -O0 and -O2, Go, native and portable results, and the rejected forms. A C call returning an array whose result initializes a local or is assigned to a stable array lvalue passes that destination as the callee's result buffer instead of a zero-filled temporary that is copied afterwards; a declaration whose arguments name the new binding keeps the temporary. A return of an array-valued call of the same type passes the caller's result buffer on, and any other returned array is copied into it once from its own storage rather than through a zero-filled temporary. A local array passed by value to a Ziran procedure goes in as it is, since the callee copies every array parameter as it starts; foreign procedures, procedure values and arguments a later argument could change still get the caller's copy. tests/array_result_in_place.sh checks C, Go, native and portable results and the generated calls, arguments and returns. A shadowing declaration's initializer reads the outer binding on every target: generated C computes it into a temporary before the new local is in scope. A for body that redeclares its binder, or holds a continue where a nested redeclaration is in scope, lowers to a separate cursor, and a top-level redeclaration gets its own block, so the step never advances the shadow. tests/shadowed_binding_reads_outer.sh checks C, Go, native and portable results. Integer literals with _ separators, 0b binary and u64 values above the s64 maximum share one parser across the checker, constant folding, range lowering, proofs, the VM verifier, evaluator and global initializers, and the C and Go emitters; C header constants are written in decimal. tests/integer_literal_spellings.sh checks C, Go, native and portable results. Generated C drops the bounds check on an array, slice or string index it proves in range from integer types, literals, &, |, ^, %, /, >>, << by a constant, +, - and *, and from loop conditions. A local's range follows its initializer through any chain of locals, such as line := row * 16 and at := line + offset, and a declaration without a value starts at zero. An enclosing if or while condition, earlier && and || operands and an earlier if ending in return, break or continue bound what they compare until it changes. Their comparisons of sums and products, such as input.count < (height + 3) * stride + width + 4, prove indexes such as row * stride + column below a slice count or array length when no step of the guard or the index can wrap, so first + 1 + second bounds neither term; tests/guarded_index_ranges.sh checks four proven functions and ten reads that keep their checks. A local or parameter counts only when every write to it is a visible assignment and nothing takes its address. A counter is bounded by an enclosing while condition until the body writes it again; a write followed by continue, break, return or unreachable in its own block, such as the step a for loop runs before a continue, does not count for reads after that block. When several facts bound a counter, such as i <= 31 from a guard and i <= height - 1 from the loop, each is tried. A factor's sign and the final comparison may take away up to three facts, so stride >= width and width - 1 - j >= 0 show stride >= 1, and products of 32-bit values such as (cast(s64)height - 1) * stride keep their range when no corner overflows. A local declared from ifx is proven for each branch, a counter an index shrinks with is replaced by its lower bound, and a guard comparison with a narrow step that might wrap, such as cast(s64)(height - 1) in s32, counts where the other facts show it did not. The search keeps only facts linked to the index's own values, so extra guard comparisons never lose a proof, and a fact times a bounded value covers products such as radius * stride. An else branch knows that each branch before it in the chain was false, and x != c or a false x == c moves an end of x's range that equals c; tests/loop_guard_ranges.sh checks thirteen proven functions and ten reads that keep their checks. A counter stays nonnegative when it only grows by literal steps in that loop. A slice or string count stays fixed while nothing reassigns the view. An integer field of a parameter counts too: through a pointer, frame.count keeps one value from a read to a later one when no call and no store through a pointer, slice or global can run between them, so a guard on it bounds for i: 0..frame.count - 1 while the body stores through a slice; a record parameter's fields change only with the parameter. tests/field_guard_ranges.sh checks four proven functions and ten reads that keep their checks, after a store through another pointer, a *u64 or byte view of the record, a global or a slice element, a call, even in an else if condition, a store earlier in the loop, or a guard on a sum of fields that may wrap. tests/proven_index_ranges.sh checks the unchecked reads and stores, and checks that twelve unprovable cases still abort on C, Go, native and portable targets. Generated C divides, takes remainders and shifts by a nonconstant operand through a checked helper of the operand's width instead of the general IntegerOp switch, so compilers emit one idiv or sar plus the checks; tests/checked_integer_ops.sh covers every width on C, Go, native and portable bundles from source and saved IR, and rejects IntegerOp in the C. Generated Go shifts by a nonconstant count in the operand's own type behind an inlinable shiftCount check, and the test rejects integerOp in the Go. tests/numeric_semantics.sh checks these cases from source and saved IR on C, Go, and .zx. Its stable conformance IDs are published by ziran capabilities --json, described by tests/numeric_conformance.json, and kept synchronized with markers in the differential test. Mixed-width narrowing/widening after addition and u8 bitwise AND/OR/XOR are included.
  • std/format builds text on a Vec(u8): overloaded Append(*builder, value) for strings, integers, bools, and floats, and BuilderPrint(*builder, "format", args...), which expands to those calls and imports std/format for its file. tests/format_builder.sh checks source and saved IR on C, Go, and .zx.
  • Statements may be longer than 4 KB, such as a table of records written as one array literal: source text is kept whole through parsing, checking, and saved IR with no length cap (saved IR is bounded only by its 256 MiB read budget, which writers charge too), and C store an array literal too long for one initializer element by element. Before, such a statement failed with "expression is not supported by language checking" or silently. tests/long_literals.sh checks source and saved IR on C, Go, and .zx; tests/large_array_literals.sh checks 20,000-, 70,000- and 260,000-element tables, which parse and check in linear time. A file-scope variable initializer, written out or naming a constant table of any length, is kept whole instead of passing through 4 KiB buffers that rejected it, and C and Go define the variable with its values. tests/large_constant_globals.sh checks 3,000-, 4,096- and 6,000-element globals, one from an imported table, on C, Go, native and the VM from source and saved IR.
  • ziran check --lint warns (code lint.cast) about casts to a value's own type and casts that only widen a whole typed initializer, assignment, argument, or result; it leaves casts that set arithmetic width, choose an inferred type, or pick an overload. It also warns (code lint.char) when a u8 is compared with a printable character's code written as a number, suggesting the #char literal, and (code lint.bool) when an integer local only ever holds 0, 1, or a bool cast to an integer and is only compared with 0 or 1, suggesting a bool. tests/lint_casts.sh, tests/lint_characters.sh, and tests/lint_flags.sh pin the rules. Inbe, Kryon, KSS, and Workbook have had their unneeded casts, character codes, and integer flags rewritten this way.
  • IR nodes refer to interned names instead of holding them. Expression and statement names and types and function parameter lists are kept text: an expression node is 120 bytes (was 600), a statement 96 (336), and a function 1,168 (9,344). Parameter lists are split once and shared (ParametersOf), and the portable runner keeps each signature it parses. FindType and procedure resolution keep entry-scope answers, including misses and ambiguous procedure results, until types, procedures, imports, or modules change. Names and lookup file spellings are immutable keys; declaration growth, private-procedure renaming, linking and program cleanup invalidate answers before their stored pointers can be reused. tests/symbol_lookup.sh covers file scopes, aliases, cyclic diamonds, ambiguity, visibility changes, relocation and independent concurrent programs. The earlier name/type changes reduced checking Inbe from 3.9 s and 466 MB to 2.9 s and 222 MB with byte-identical IR and generated C for Inbe, Workbook, Kryon, and KSS. make sanitize compares every kept type and procedure answer with a fresh lookup.
  • Record fields are parsed into immutable snapshots shared by identical field bodies. TypeNextField cursors index those snapshots, so checking, all native emitters, and the portable verifier/runner reuse the same fields without reparsing each walk. Copied records share a snapshot; rewriting a body and starting a new walk selects a new one. Malformed suffixes still return an error after any valid prefix. tests/compiler_type.sh checks this boundary, source/saved IR, all native targets, the VM, and bootstrap agreement. .zir still stores textual field bodies; this does not replace the remaining textual declaration or import lowering.
  • The portable runner has no statement budget (only the web playground bounds a run), holds as many locals as a function declares, takes the language's 64 parameters, and nests calls until the C stack is nearly full where the stack's bounds are known; zi2zx run gives programs a 256 MiB stack. tests/portable_long_runs.sh and tests/portable_limits.sh cover these.
  • A generic record from another module may hold types only that module imports: the user's Bag(string) reads the library's Vec(K) field as vec.Vec(string), and every copy of one application gets the same name. Records applied through a named import, Pairs.Pair(s32, string), work too. tests/generic_record_imports.sh checks source and saved IR on C, Go, and .zx.
  • std/file imports the file operations for the native C target: file_linux on Linux, Android, the web, and Windows. It adds ReadEntireFile and WriteEntireFile. tests/file_portable.sh runs it on C from source and saved IR.
  • An index read of a constant number or boolean array (TABLE :: s32.[...] or #run s32.[...], any length) reads one shared read-only table: a file-scope static const in C, a package var in Go and a value the portable VM builds once and keeps. Go also declares a copy or passes the table by value from that var. tests/constant_tables.sh checks C, Go, native and portable bundles from source and saved IR, value copies, bounds and that the read copies nothing. A function may read one table in any number of loops, beside widening casts and .count: binding a table grows the expression graph when checking has used the room reserved for it. Nested arrays are tables too: rows are written [3]s32.[s32.[...], ...], #run evaluates g[a][b] = v into one, and GRID[i][j] indexes the table in place in C, Go and the portable VM, with no copied row; tests/nested_constant_tables.sh checks them on every target from source and saved IR.
  • clz, ctz, popcount and bswap take one u8/u16/u32/u64 and return that type, with the width as the count for zero. C emits __builtin_* helpers (with a portable fallback), Go math/bits, and the portable runner computes them directly; tests/bit_builtins.sh compares them from source and saved IR.
  • New(T) allocates a zeroed T and returns *T; free(p) releases it and ignores null. C uses calloc/free, Go uses new and its collector, and the portable runner keeps the storage alive while a pointer reaches it and stops cleanly on a read after free or a second free. A procedure or foreign procedure the program declares as New or free (such as C's free) is called instead. Native callers remain responsible for raw pointer lifetimes. tests/heap_new.sh checks source and saved IR on C, Go, and .zx, including records with text, arrays, and pointers.
  • A procedure may declare procedures inside it. The parser hoists each to file scope under a private name (zi_local_Outer_Name, kept out of API listings) and renames its uses in the rest of the enclosing procedure; as in Jai, it may recurse and nest but cannot use the enclosing procedure's locals, which the unresolved-name error says. tests/local_procedures.sh checks source and saved IR on C, Go, and .zx.
  • Records take Jai operator procedures: operator + :: (a: V, b: V) -> V for the binary operators + - * / % == != < <= > >= & | ^ << >>. Several declarations of one operator are overloads; a += b uses operator +, a != b falls back to operator ==, and #symmetric also accepts the two arguments swapped. Operators reach callers through open and named imports, and a record operation without one reports that an operator procedure is needed. tests/operators.sh checks source and saved IR on C, Go, and .zx.
  • print shows a record as {x = 1, y = 2}: nested records in braces, strings quoted, enums by name, fixed arrays by element (up to 16), and Vecs and slices by count. The call becomes a call of a generated procedure taking the same arguments, so each is evaluated once; a value holding a Vec is passed by address instead of moved. tests/print_records.sh checks source and saved IR on C, Go, and .zx.
  • A local declaration may use type_of(expression) as its type, and a for range counts in its bounds' type (0..count with count: s32 is s32; constant ranges stay s64). Indexing with value[a..b] reports that slices are written value[start:end]. tests/range_types.sh checks source and saved IR on C, Go, and .zx.
  • std/hash_map provides HashMap(K, V) with HashMapSet, HashMapGet, HashMapHas, HashMapRemove, and HashMapFree over string and integer keys, written in portable Ziran. Polymorphic procedures bind type parameters from generic record parameters such as *HashMap($K, $V); record instances a specialization names after the program pass get their fields then. A bundle keeps a record field every module's copy of one type application uses, and Go compares copies of Vec(string) by their slice type. tests/hash_map.sh checks source and saved IR on C, Go, and .zx.
  • print shows an enum value by its member name, and a value outside the enum as (invalid Enum). The checker routes the argument through a generated name procedure in the enum's module, created only for enums a program prints, so every target and .zx agree. Flag enums still need a cast. tests/print_enums.sh checks source and saved IR on C, Go, and .zx.
  • Polymorphic procedures may bind several type parameters, each with its own $Name, as in Pick :: (a: $A, b: $B) -> A. Every call infers each parameter and gets its own specialization, placed with the caller when one of its types is only visible there. A name bound twice is an error. tests/generic_parameters.sh checks source and saved IR on C, Go, and .zx.
  • Fixed-array parameters [N]$T infer their element type and enforce their declared capacity, including zero-capacity arrays. Bounds resolve in the defining module, and dependent [N]T parameters and return types are checked after specialization without resolving a bound type as a same-named local alias. Generic record fields and generated result records preserve their bound types too, and record array capacities resolve in the defining module before instances copy the fields into a caller. Array arguments keep ordinary value semantics. tests/generic_array_parameter.sh checks named/open imports, scalars, records, generic array fields, independent type parameters, multiple results, array aliases, source/saved IR and separately saved libraries on C, Go, and .zx. Nested-array elements and returns work on the native targets; the portable linker explicitly rejects unsupported nested-array returns. The test also rejects slices, scalars, mismatched capacities and element types, unresolved/negative bounds, and duplicate binders.
  • Procedures may be overloaded: several declarations may share a name when their parameters differ. A call chooses the overload whose parameters take its arguments best (exact types, then an untyped literal's usual type, then the closest widening) and gets that overload's defaults; no match or an equal match is an error. Overloads work through open and named imports. The choice is made in checking, so saved IR and every target call the chosen procedure directly. Exported and polymorphic procedures cannot be overloaded. tests/overloads.sh checks source and saved IR on C, Go, and .zx.
  • Jai resizable arrays: [..]T is std/vec's Vec(T), imported for the file automatically, and array_add(*a, x), array_reset(*a), array_free(a), and array_reset_keeping_memory(*a) are the matching Vec operations. Indexing, .count, and for work as on Vec. A local [..]T passed where []T is expected lends a view (VecSlice) instead of moving. Vecs of fixed arrays are rejected; wrap the array in a record. Nested Vecs ([..][..]T) and Vecs of records that own Vecs work. tests/resizable_arrays.sh checks source and saved IR on C, Go, and .zx.
  • Procedures return several results as in Jai: -> s32, s32 or named -> (low: s32, high: s32), return a, b, q, r := F(), q, r = F(), and _ to skip one. A call used as one value gives its first result. The results travel in a generated record shared by procedures with the same result types (Results__s32__s32, fields value_0...), so every target and .zx run them unchanged. Returning another module's results directly needs binding them first. Polymorphic procedures also return several results: result records specialize their fields using the procedure's bound types, including mixed types, records, arrays, and slices. Named and open imports, defaults, nested calls, direct generic forwarding, and a separately saved library work on C, Go, and .zx. tests/generic_multiple_results.sh checks source/saved-IR equivalence, single evaluation, rejected result types/counts, and saved-library validation. tests/multiple_results.sh checks source and saved IR on C, Go, and .zx.
  • Lossless numeric conversions are implicit: a narrower integer into a wider one of the same signedness, an unsigned integer into a wider signed type, and float32 into float64, at initializers, assignments, returns, arguments, and binary operators. The checker inserts an explicit cast into the checked graph, so C, Go, and .zx see the same conversion. Narrowing, sign changes, and integer-to-float conversions still need cast. tests/implicit_widening.sh checks source and saved IR on C, Go, and .zx.
  • Jai using record: Type parameters and local declarations, plus using record; and nested paths such as using entity.position; in a procedure body, promote record fields into lexical lookup. Explicit local bindings shadow promoted fields; conflicting promoted names are diagnosed. Struct fields declared using field: Record promote contained fields through nested records and concrete generic applications. Reads and writes retain their nested storage layout in checked IR; source and saved IR agree in C, Go, and .zx. Pointer-backed using fields work in native targets. Polymorphic procedures preserve parameter, local, and imperative using declarations in saved templates and lower them for each concrete call. File-scope using value;, nested paths, and using value: Record; promote fields of module-local record and scalar-union globals in procedure bodies. Forward global declarations, filters, local shadowing, ambiguity diagnostics, and file-private #load boundaries are tested from source and saved IR in C, Go, and .zx by tests/data_scope_using.sh. Public globals from open and named imports can also be read, assigned, and opened with record using in procedure bodies. tests/imported_globals.sh checks source and saved IR across C, Go, and .zx, along with ambiguous imports and private global visibility. Named and open imported globals keep their declaring record type when a consumer defines a same-named type; direct and using field access agree across all four targets. Checked IR retains open-import type ownership with a private alias. using,only(...)/except(...)/map(...) modifiers filter and rename promoted record fields and opened enum members on local and data-scope using declarations, in source and saved IR across C, Go, and .zx; a hidden or unmapped name stays unresolved. File-scope enum filters also apply in constants, global initializers, #run, #assert, and #if; conflicting maps of one exposed name are diagnosed even when they open members from the same enum. using Alias :: #import "Module"; re-exports the module's public procedures, types, and constants into unqualified scope beside the alias: local declarations shadow the re-export, and two using-imports exposing one name are ambiguous. Name :: #as (source: Type) -> Result; declares a checked conversion that the checker applies implicitly at initializers, assignments, returns, and call arguments when the types are otherwise incompatible: the expression is rewritten into an ordinary call, the graph is re-laid out for saved IR, and two applicable conversions are ambiguous. Literals that already widen natively never trigger a conversion.
  • Jai-style #program_export on its own line or inline exports a procedure's native symbol. A quoted linker name works in C; Go emits a typed native adapter with the requested name. Native testing modules generate _test.go files with discoverable test, fuzz and benchmark signatures. Source and saved-IR checks preserve failure assertions, pointer/slice/error/panic identity and module-name collision handling. File-scope variables use name: Type declarations. #export, #global, and C-style static source forms are rejected. Source and saved IR retain these exports and globals across native and portable builds.
  • File-scope variable initializers and constants pass through the Jai expression parser and shared type checker during checking, including nested record fields and fixed-array elements. A mismatched string, boolean, or nested initializer now fails before native output or portable bundling; tests/portable_globals.sh exercises these diagnostics. C-style compound literals and ternary expressions are rejected there before native output, including from saved IR. The Go backend no longer carries C-style compound-literal rewrites, and no longer translates C casts, NULL, or C scalar type aliases in file-scope expressions. The source reader no longer joins C-style adjacent string literals or multi-line ?: fragments.
  • Portable file-scope array initializers use .[...] literals with the declared array type for context: scalar elements and record elements (Type.{.field = value}) fold in the VM, serialize through .zx, and lower to brace initializers in C99 (designated, with string fields expanded to {data, length} pairs) and Go composite literals (native strings).
  • Jai #must after a procedure result type requires callers to use the result. The checker enforces it for ordinary, imported, polymorphic, and foreign procedures in source and saved IR.
  • Jai #compile_time evaluates to true in supported compile-time procedure execution and false in C, Go, and portable runtime code. It is a boolean expression, not a declaration constant or direct #if condition.
  • Jai #caller_location in a parameter default supplies the calling file's full path and physical line as a Source_Code_Location record. Inferred and explicitly typed defaults work through imports, polymorphic calls, and #load; explicit arguments override the default. Source and saved IR agree in C, Go, and portable builds. Compile-time #run, #assert, and local or imported #if calls capture the same caller path and line, including nested calls and explicit overrides; tests/caller_location.sh covers source and saved IR on all three targets.
  • Jai #string DELIMITER raw multiline literals preserve their body bytes, including whitespace and the newline before the closing delimiter. Source and saved IR execute identically on the portable runner, C and Go. zi-fmt preserves raw bodies and closing delimiter columns. The 4095 character source line limit counts the physical lines the author wrote, body lines included, and is reported at the real line. A string literal has no length limit, as a constant or a global initializer, on the portable runner, C and Go, and later diagnostics keep their lines.
  • Nested block comments and inline // comments are stripped without changing quoted strings or source line locations. Unclosed comments are diagnosed in entry and loaded files; source and saved IR agree across portable, C, and Go execution.
  • Legacy # comments are rejected at file, type, and function scope. Use // or nested /* ... */ comments.
  • Compile-time #if branches and #assert are resolved by the frontend. Declaration guards and target-specific assertion fallbacks have been removed; a selected failing assertion stops source or saved-IR checking before C, Go, or portable output.
  • Semicolons separate multiple declarations or statements on one source line, including one-line record bodies and function bodies. The parser retains #ifx arm separators and still rejects C-style for headers. Source and saved IR execute alike in the portable runner, C and Go.
  • if and else if accept Jai's optional then before a block or one statement. Inline else statements also work, including after a closing brace. Multiple braced arms, nested arms, and empty blocks may share a source line; source and saved IR agree in portable, C and Go execution.
  • Source foreign declarations use Jai's #system_library and #foreign forms. The compiler resolves those library declarations to its existing host, C, or Go import model before saving IR. A trailing ..any parameter declares a C variadic foreign procedure: calls must provide its fixed arguments and may provide additional arguments. Source and saved IR lower tested variadic calls to C; Go rejects the declaration. The old #extern source modifier is rejected.
  • Source accepts Jai #scope_file, #scope_module, and #scope_export for following procedures, globals, types, and constants. Private declarations are excluded from imported lookup, native headers hide private types and constants, and private globals use internal linkage. Scope visibility survives saved IR. #private is rejected. Loaded files join their caller's module; file-private declarations, imports, and system libraries are visible only to their own source file, including compile-time conditions and size queries. Separate loaded files may reuse private procedure, constant, global, and type names; checked IR assigns distinct identities before native or portable output. Constant aliases, native global initializers, record construction, and local shadowing retain the correct file-local binding in source and saved IR.

  • The compiler frontend and C/Go backends have been extracted into a separate Ziran repository. zi2zir checks and saves .zir; zi2c and zi2go compile .zi or saved .zir input.

  • make check passes standalone non-UI programs, a two-module import, and an imported typed record call named Button through generated C and native Go. A local declaration binds its non-void result, with source and saved .zir builds across those targets and matching source/saved .zx execution. The call is resolved from its declaration, not its name.
  • Jai procedure type aliases such as Child :: #type (s32) -> (); and Compute :: #type (s32) -> s32; can be passed as callbacks to imported functions. Source and saved .zir builds execute named callbacks with void, scalar, and record results in C, Go, and .zx. Capture-free values can be stored in records, fixed arrays, and globals, and returned from functions. Entry builds retain their named targets and remove unread record fields from closed-program layouts. The former #slot declaration and capturing body syntax are rejected.
  • Context-inferred .{...} record literals work in declarations, assignments, returns, record fields, and arguments to imported functions. Source and saved IR produce the same result in C, Go, and .zx; missing type context and unknown fields are rejected.
  • Jai defer { ... } blocks run their statements in order on scope exit, including early returns and loop exits. Source and saved IR agree in C, Go, and .zx. Control transfer or a nested defer inside a deferred block still needs lowering support.
  • Jai inclusive integer ranges accept named binders, implicit it, it_index, and for < reverse iteration. Bounds run once, and break, continue, nested ranges, and defer work across source and saved IR, C, Go, and .zx. Fixed arrays and borrowed slices also support value and index bindings, reverse iteration, and nested loop control across those targets. C, Go, and .zx support for * pointer iteration for in-place element changes. Collection expressions run once and value bindings copy elements. C-style three-clause headers are rejected. Jai for_expansion iterables remain unsupported.
  • Fixed arrays, borrowed slices, and strings now expose read-only Jai .count values of type int, including in range bounds. The inherited .length source spelling is rejected. Single-statement if, while, and for bodies now normalize to checked blocks. Named break and continue target enclosing for binders or named while condition binders; defer cleanup and range advancement apply when those controls cross nested loops.
  • Go fixed arrays of pointers retain prefix pointer syntax and the declaring module's type names. Source and saved IR preserve scalar, indirect, nested, imported-record and native Go pointers, including zero values and shared storage. C keeps its own pointer declarators.
  • check, ir, build, and bundle discover extensionless imports transitively. Repeated --module-path DIR options locate ordinary libraries outside an app root; explicitly supplied modules take precedence. A separate app root and two-module library are tested from source and saved .zir, including generated C and .zx. Kryon's drag policy test loads its src/ui/ library from an app entry file through the same path, across C, Go, and portable bundles.
  • Jai #import, file "relative/path.zi"; resolves a source file relative to its importer. Source and saved IR link the same module, and C, Go, and portable builds agree. Named Alias :: #import "module"; and Alias :: #import, file "relative/path.zi"; expose public procedures, constants, and record types through Alias.Name; named imports do not leak unqualified names. Alias :: #import, dir "relative/directory"; loads the directory's module.zi; ordinary module lookup also finds name/module.zi. Source and saved IR produce the same C, Go, and portable output for this form. Nested #load "relative/file.zi"; adds declarations to the current module, keeps source file diagnostics, and resolves imports relative to each loaded file. Cycles and missing files are rejected; inactive compile-time branches skip loads. Unconditional nested loads are discovered before compile-time branch selection, so earlier conditions can use later public constants and type layouts from those files. File-private names remain local to their loaded file; tests/forward_load_conditions.sh checks source and saved IR on C, Go, and .zx. #import, string compiles quoted or raw #string source as an embedded module, with named and unqualified imports. Compile-time branches inside embedded source can call modules imported earlier in that source. Source and saved IR bundles agree, and C and Go execute the generated modules.
  • Ordinary #import now accepts only module identifiers. C header paths and C-style angled imports are rejected, including from saved IR; unknown calls, fields, and stored types no longer pass checking merely because a C header was imported. Downstream C-backed modules that used header imports require source migration.
  • Native C headers include imported modules when their types appear in exported records, callbacks, or procedure signatures, including arrays and pointers. Imports used only by private record and callback types stay under the generated private guard; implementation-only imports stay in the implementation file. #c_call callback parameters and results resolve imported type aliases in their declaring module. Source and saved IR are covered by tests/private_module_imports.sh.
  • Module inputs accept .zi source and .zir IR only; .kry, .kir, and .krb are rejected by the shared loader. Kryon owns the UI test fixtures; Ziran keeps a non-UI array and UTF-8 byte law fixture that runs from source and saved IR through native C and Go.
  • zi2zir writes experimental binary .zir version 41 after checking all input modules together. C and Go can read saved modules without reparsing .zi; their imports are relinked from serialized module identities. The reader rejects malformed headers, versions, truncated data, invalid structural references, expression cycles, retired statement kinds. Retained-state fields are absent from the IR schema. C, Go, and bundle builds rerun strict language checking on saved expression graphs and compare the resulting serialization byte for byte with the input. The tested typed subset takes behavior from those graphs and serialized branch flags even when stored statement text differs. Deterministic output is checked on a sample, and a mixed source/IR C build runs.
  • Jai union declarations, including generic unions, retain their shared field storage and maximum-field layout in checked .zir. C compiles source and saved IR unions with overlapping fields. Unions whose fields are all scalars or enums also build and run on Go, through an aligned byte backing with unsafe typed access, and in portable bundles, where the VM keeps raw bits in one slot and reinterprets every member read, write, and compound assignment through the declared field type. Go union storage now uses the largest scalar field's alignment; tests/union.sh checks the exact 1-, 2-, 4-, and 8-byte sizes and alignments on C and Go, plus independent union value copies across all four targets. Unions with string, record, array, or pointer fields remain C-only.
  • zi2zx bundle --root DIR --entry module:function -o FILE builds an experimental version 26 .zx from source or saved IR. zi2zx run FILE loads and executes the validated scalar, plain record, enum, and fixed-array subset with no host bindings. Graphical programs use an embedding host; Kryon's reusable desktop player supplies its rendering and input capabilities. The test runs an imported two-module call and compares bundle bytes from source and saved IR. The current runner supports zero-argument entry functions with s32/int/bool/void results, and helper functions with s64/u8/u32/u64/float32/float64/string, enum, or plain record results. It supports scalar, enum, string, record, and fixed-array parameters and locals, nested record fields, defaults and record and array literals, value copies, member and array reads and writes, scalar compound assignments, s64, u32, and u64 bitwise operations and shifts, calls, arithmetic, comparisons, casts, assignments, if/else, while, lexical blocks, break, continue, unreachable, Jai-style enum if #complete cases, and returns. Complete enum cases lower to checked branches with a trap for values created by an out-of-range integer cast. It rejects host imports outside the scalar/string/plain-record subset and unsupported statements before writing a bundle. Loop, branch, and real arithmetic programs are compared against generated C and Go in make check. The bundle loader also reruns the language checker and laws before execution. The linker follows direct calls and drops unreachable functions and modules, while retaining reachable record and enum declarations. Entry linking now removes effect-free constant branches, short-circuited expressions, false loops, and statements after unconditional exits before tracing calls or stored procedure values. Fixed arrays with numeric or resolved constant-expression bounds, including imported constants and nested arrays in records, are tested through source and saved IR in C, Go, and .zx. Tests cover ASCII values in nested char arrays, value-copy isolation, and portable bounds failures. Linked bundles retain needed compile-time definitions for bound expressions, including constants-only imports. Integer NAME :: value definitions in the checked arithmetic subset can be used in function expressions. Imported and negative values fold to checked IR literals. String literal definitions and aliases also fold into checked expressions, including in saved IR and portable bundles. The checker rejects local bindings that would collide with generated C definitions. Portable slices borrow fixed-array storage. Source and saved IR bundles and generated C and Go agree on range creation, nested views, function parameters and returns, .count, indexed reads and writes, and array replacement while a view is live. Bounds failures reject execution. The checker rejects slices in records and globals, host slice returns, and returns that borrow a local array. Unresolved fixed-array bounds remain outside the portable subset. Native C and Go targets also permit a temporary slice of a fixed array field reached through a direct record-pointer parameter. The caller owns that record for the call; the checker rejects returning the slice. A string or slice stored in that record belongs to the caller, so it may be returned, including through checked calls. Portable bundles still reject pointer programs. Borrowed arrays retain record elements written by called functions after those functions return; the portable slice test covers replacing a nested record in a live view across calls. The test runs record and enum functions across imported modules. Source #enum, variant, payload match, postfix ?, guard, C-style switch and goto with labels, state blocks, C-style locals, and raw C statements are rejected. Native C and Go builds check source unconditionally and reject --strict and --no-strict. The standard Option and Result templates are now generic records with explicit status fields. The checked native emitters escape target keywords used as parameter or local names; source and saved IR tests run switch and goto bindings, including a generated-name collision, across C and Go. Go globals keep source spelling when legal; native emitters escape target keyword globals, including collisions with the generated escape name. Same-named globals in linked modules, and globals whose target keyword escapes collide across modules, receive distinct native names. tests/imported_global_names.sh checks reads, writes, source, and saved IR on C, Go, and .zx. A Go global and type whose names differ only by case or underscores remain distinct. Constants use the same keyword and collision handling without defining C keywords as macros; source and saved IR tests include dependent constants and fixed-array bounds. Constants shared by linked modules, or whose target keyword escapes collide across modules, receive distinct C macro and Go constant names; tests/imported_constant_names.sh checks source and saved IR on all four targets. Linked globals and constants with the same native name also remain distinct; tests/imported_value_names.sh checks source and saved IR on all four targets. Globals and constants whose native names collide with linked functions, including C program exports, receive distinct names; tests/native_function_value_names.sh checks source and saved IR on all four targets. Generic record templates use named Name :: Generic(Type) declarations or direct Generic(Type) annotations; std/pair.zi also provides Pair. Concrete record fields work in source, saved IR, C, Go, and .zx. Top-level boolean && and || expressions in simple statements and loop conditions retain short-circuit evaluation, including nested forms. Jai ifx conditional expressions, with or without then, replace C-style ?:; top-level conditional expressions with typed destinations or loop conditions preserve selected-arm evaluation. Conditional expressions inside positional and named call arguments run across the native targets and portable bundles. Entry linking preserves argument positions when it folds a constant conditional or short-circuit expression. tests/nested_lazy_call.sh compares source and saved .zir across C, Go, and .zx while checking that unselected arms have no effects. Jai #ifx selects a constant, global initializer, or function expression arm before strict checking and saved-IR generation. It accepts the Jai semicolon before else, nested selections, boolean and integer constants, constant aliases, and compiler-host OS comparisons with .WINDOWS, .MACOS, or .LINUX; unused arms are not type-checked. #ifx also selects record and fixed-array values in file constants, global initializers, local definitions, and call arguments on C, Go, and .zx from source and saved IR. #run, #if, #ifx, and #assert evaluate pure procedures with signed integer (up to s64), unsigned integer (up to u32), or boolean parameters/results. Local declarations and assignments, branches, bounded while and numeric range loops, nested calls, and imported calls work in #run, #if, #ifx, and #assert. #if can resolve imports declared before its condition while parsing, including imported size_of layouts. Other imported calls and dependent definitions resolve after module linking; their literal values and selected arms are saved in .zir. Pure string and floating-point expressions, including imported calls, local assignments, and default parameters, work in #run; #assert can compare those results. String comparisons use the same escape and UTF-8 decoder as the portable runner. Source-selecting #if can use earlier #run definitions when their procedures and imports are available while parsing. Named imported real and string constants resolve in #if and #run even when the import declaration follows the condition. Their public visibility and private exclusion are checked from source and saved IR on C, Go, and .zx by tests/imported_typed_constants.sh. Pure compile-time procedures can now return named records and fixed arrays. Their evaluated fields, array elements, and array counts can feed #run scalars and #assert. Local record and fixed-array #run definitions also bind to checked literal graphs in procedure bodies, and direct file-scope initializers can use them. tests/compile_values.sh checks nested records, fixed arrays, bounds rejection, runtime record arguments and globals, and source/saved-IR execution on C, Go, and .zx. Public aggregate definitions from named, open, and using imports retain their declaring record types in runtime expressions and direct globals; tests/imported_aggregate_constants.sh checks nested records, arrays, compile-time #run, #assert, and #if field selection, a later named import in #if, private visibility, and source/saved IR across the same targets. Imported procedures with record parameters or results map those types into the caller's namespace; the same test covers direct calls, including parameter and result types shadowed by local records. Qualified record literals such as Lib.Point.{...} work in globals and bodies, including nested inferred record fields; tests/imported_aggregate_constants.sh checks source and saved IR on all four targets. Conflicting consumer record names retain the imported owner through named, open, and using imports; nested record constants, arrays, calls, and parameter passing agree on all targets. Pure file-scope initializers now fold imported record-field and fixed-array selections, scalar expressions built from them, and tested #ifx selections before native or portable output. Bounded pure procedure calls also fold in globals, including a call declared after the global; effectful calls are rejected by the portable bundle. Named, open, and using imports retain the aggregate type owner. General file-scope initialization remains open; effects, unbounded execution, and general metaprogramming are unsupported. Integer conditions and typed expressions use the same bounded procedure evaluator, including a shared instruction budget across nested calls; the separate integer-only procedure interpreter has been removed. One-line function bodies are parsed as ordinary statements instead of being silently skipped. Jai #if ... else #if ... else selects file-scope declarations and function statements at parse time. Unselected branches do not enter source or saved IR; source and saved bundles, C and Go agree for nested constant branches. The old #else_if and #else forms are rejected. Conditional record fields and enum members are selected before their layouts are checked. Explicit enum members accept Jai Member :: value declarations. Source and saved IR produce the same values in C, Go, and portable bundles. Named enums now use Jai's default s64 storage or an explicit integer backing type. enum_flags assigns successive bits to members without an explicit value. The checker rejects members outside their backing range, and size_of uses the backing width. Source and saved IR agree across the portable runner and native targets. Jai #specified requires explicit member values for both enum and enum_flags, and saved IR records the modifier. Values above s64 remain unsupported in the current portable enum evaluator, even for u64 backing. Typed flags accept integer initializers and assignments, qualified Type.Member, and contextual .Member references. The checker validates flag arithmetic, bitwise operations, equality, and corresponding compound assignments. Portable execution and C and Go output agree on these operations. Regular enum members also have typed Type.Member and contextual .Member expressions. Within a procedure, using Enum; opens enum members for bare lookup with lexical scope. Data-scope using Enum; also opens them in procedures, file-scope constants and globals, #run, #assert, and later #if conditions. File-private using declarations respect #load boundaries. Bare members without using are rejected in procedure bodies and file-scope global initializers; checked IR lowers opened members to values before native or portable execution. File-scope record and union using fields also work in pure global initializers that read earlier initialized globals. The checker folds those startup values for C, Go, and .zx, respects local shadowing, and rejects ambiguous or file-private names. General global initialization and promotion from imported globals still need coverage. Call arguments and returns accept typed and contextual members. Enum and scalar if/case arms support terminal #through; with source, saved IR, portable, C and Go agreement. Integer, boolean, floating, and string cases accept a final case; default. Deferred actions run at case boundaries and before #through;. Unknown function-body directives are rejected. size_of supports scalars, pointers, string, borrowed slices, fixed arrays, records, unions, named concrete generic types, and nested applications such as Wrapper(Box(s32)). It folds in constants, array bounds, globals, #run, #if, and #ifx. Function-body expressions stay symbolic through source checking and saved IR, execute in portable bundles, and lower to target-native size operations in C and Go; tests cover the target-dependent slice layout. The old sizeof spelling is rejected in source. Jai int maps to s64. In function bodies, size_of(type_of(expression)) uses the checker's inferred type and does not evaluate its operand. File-scope size_of(type_of(expression)) works for previously declared same-module globals, record fields, and procedure calls in constants, global initializers, #run, #assert, #if, and #ifx; the operand is not run. Forward and imported operands also work in tested file-scope constants, global initializers, #run, and #ifx; imported operands work in #if. Unconditional procedure headers and single-line typed globals in the same file or a later #load resolve forward #if size_of(type_of(...)) queries without evaluating the operand. Procedure parameter lists may span lines; global member queries can use a later record type. Inactive and file-private names stay hidden. Unconditional later enum_flags types also resolve in #if and #run, including implicit flags, explicit values, #specified backing types, and a later #load. Inactive and file-private flags stay hidden; tests/forward_type_conditions.sh checks source and saved IR on C, Go, and .zx. The same test covers a later generic record and a one-line concrete alias without a semicolon in #if size_of and #run size_of; aliases inside inactive branches or private loaded files stay hidden. Later records, enums, and generic records with #if-selected bodies also resolve for supported forward size queries and opened enum members, including a type from a later #load. The selected branch alone reaches checked IR; private loaded types stay hidden. Acyclic forward dependencies between conditional records or enums resolve after their prerequisites are found; unresolved conditions fail without selecting a branch. Forward #if calls to unconditional procedures with local declarations, assignments, braced if/else if/else, and bounded while loops run through the compile-time evaluator. break, continue, calls to another later procedure, and returns work in the tested subset; #run accepts these calls too. Multiline headers, defaults, named arguments, later constants, and later #load files work. A procedure in a loaded file can use that file's private constant; executed effects are rejected, and recursion and loops share an instruction budget. Other body forms still need discovery. A file-scope #run that calls such a later procedure now folds before a following #if tests the result; loaded file-private procedures remain hidden from callers in other files. tests/file_type_of.sh checks source and saved IR on C, Go, and .zx. Standalone type_of values and broader forward #if queries remain unsupported. Foreign-record size queries still require their Jai layouts. Jai #line resolves to the physical source line before checking and remains stable through saved IR, native targets, and .zx, including #load files. #file and #filepath resolve to the full source filename and containing directory, including in loaded files. #procedure_name() resolves to the declared procedure name in bodies, defaults, and pure #run calls; source use outside a procedure is rejected. Untyped integer locals inferred with := now use s64 storage, including when their values exceed 32 bits, across saved IR and all supported targets. Source, saved IR, native targets, and .zx agree on tested success and error paths. Kryon's headless suite exercises these paths through ordinary imports; widget and platform coverage is tracked in the Kryon repository.
  • Reachable #foreign host_api calls now appear as module/function capabilities in .zx. The loader verifies the capability list against the linked IR. build/libziran.a and include/ziran_host.h expose an opaque bundle handle, capability enumeration, and bindings for integer, real, string, enum, plain record, and void calls. Record fields retain declared names and types, including nested records; returned fields are checked before use. Numeric, boolean, string, and plain-record slice parameters can cross a synchronous host call. The host edits a typed element array, which the VM validates and copies back into the borrowed Ziran array. Source and saved bundles and generated C and Go run the same buffer and record slice examples. The VM rejects overlapping mutable host slice arguments and malformed returned elements. Empty fixed arrays can be borrowed as zero-length slices for synchronous host calls. Host-returned zero-length slices may have no elements; oversized returned lengths fail before allocation. Fixed arrays also cross the portable host boundary by value, including zero-length arrays and arrays of records with array fields. Returned shapes are checked recursively; tests/host_arrays.sh covers source and saved bundles, malformed host results, and native C and Go foreign calls. BundleRun checks all required bindings before execution. Native Ziran hosts can enumerate capability names through std/bundle_host. --asset-dir NAME=DIR embeds sorted, canonical relative file paths and binary contents. Both the C API and native Ziran wrapper expose those resources; malformed paths, duplicates, ordering, lengths and truncation are rejected. Source and saved-IR packaging is byte-identical, including binary and empty assets. --define NAME selects the same source branches as native compilers. The CLI runner also checks required bindings before execution. Source and saved-IR bundles, unused extern pruning, list tampering, missing binding preflight, malformed record returns, and pointer dereference rejection are tested. Declared pointer types cross host calls as opaque handles with kind VM_HOST_POINTER: the VM stores the address, compares handles against null and each other, and never dereferences them, so the host owns the storage behind a handle. Pointer-bearing records, including host-returned records, bundle and run with that contract. Array and slot host calls remain unsupported.
  • Portable strings now carry immutable UTF-8 bytes with exact byte lengths, including embedded nulls. The verifier and interpreter cover literals, equality, read-only byte indexing and ranges, .count, parameters, returns, and record fields. text[low:high] returns a borrowed byte range as a string; bounds are checked in the portable VM and generated C and Go. Callers must choose UTF-8 codepoint boundaries when slicing text. TextView([]u8) borrows a byte slice as text in native C; Go and the bundle runner copy it into managed storage. The bounded std/text_buffer.zi helper reads a NUL-terminated buffer through this operation. The dedicated gate exercises C, Go, bundles, and invalid non-byte arguments. Borrow origins now flow through record/array values and checked calls, so returning a record containing a TextView of a local byte array is rejected; tests/aggregate_view_lifetime.sh covers literal, nested, array-field, assigned-field, and call-result escapes. The mutation gate follows local bindings, field paths, aliases, checked-call results, and record-return aliases. It permits writes to sibling record fields while protecting the borrowed field, its array storage, and whole-aggregate replacement. View-bearing globals retain their global-backing origin across functions and alias chains: local-backed views cannot escape into global storage, and mutation of a tracked backing global is rejected in any function; tracking scales to the program's global count. A view through a local *value pointer tracks its pointee and rejects mutation through either the original binding or the pointer while live. Local copies of pointer parameters preserve the same backing identity. A TextView reached through an opaque local pointer is rejected when its backing lifetime cannot be proved; copying bytes into owned local storage provides an explicit lifetime. Separate pointer and slice parameters with the same type are possible aliases, preserving sibling-field precision. Checked calls summarize transitive parameter and global writes; temporary text arguments stay live through later argument evaluation and the call. Unknown callbacks conservatively write reference arguments. tests/text_call_mutation.sh covers these cases and permitted sibling-field writes. Differently typed pointer aliases and arbitrary host-backed storage still need a complete cross-target contract. Raw native FFI has no checked memory-effect summary. make check compares source and saved-IR bundles with C and Go on a string program and rejects out-of-range indexing.
  • Portable finished-builder strings and TextView snapshots carry their allocation owner through copies, ranges, records, arrays, and host-returned ranges of text arguments. Unreachable snapshots are collected at top-level statement boundaries and completed top-level calls; literal caches and live globals remain valid. VmInstanceLiveValueBytes includes string storage. tests/string_storage.sh checks bounded storage over repeated runs and source/saved IR, plus allocation-balanced empty BuilderFinish in C. Empty finished builders release reserved native storage in C. Nonempty finished native strings still retain their bytes for process lifetime.
  • Owned Text (std/vec, one bytes: Vec(u8) field) moves and drops as a Vec-owning record on C, Go and the VM. TextView(text) and implicit lending of a Text place to a string borrow its bytes under the text-view lifetime and mutation checks, which also reject moving the Text while a view is live. tests/owned_text.sh covers locals, fields, Vec(Text) elements, pointer iteration, allocation balance, AddressSanitizer and source/saved IR.
  • The checker keeps a Text call result in owning positions and rewrites it elsewhere into vec.TextDetach(result), which needs no backend support and keeps the old string semantics. BuilderFinish in an owning position becomes vec.TextFromBuilder. vec.Name calls resolve from modules that reach std/vec only through other imports. tests/owned_text_results.sh checks both kinds of caller and counts allocations. The std text producers in text_tools, float_text, json_document, unicode, utf8, stdio_linux and file_tree_linux return Text; tests/std_text_results.sh counts their allocations on C and checks the VM and Go.
  • After checking, a detached Text result or string-position BuilderFinish that is only read within its statement moves into a scoped Text local declared before that statement and lends a TextView, so it is freed when its block closes. Reads are comparisons, .count, indexing, slices passed on, print, TextCopy, the appended value of BuilderAppend, and string parameters whose checked callee only reads them, found transitively with a cycle counted as retained. A read also passes through ifx arms, string locals, a callee that only returns its parameter when the call's result is read, and a field of a record literal passed to a callee whose by-value record parameter only reads that field, through further calls that take the whole record. Foreign and template callees retain, except that a #reads foreign procedure only reads its string and byte arguments; checked IR keeps the marker in the import's former reserved slot, so format 61 is unchanged. The move is skipped after an earlier call or address in the statement, after a global read when the moved call may write any global (through an assignment, a pointer, an address, a global argument or a foreign, template or recursive callee, found transitively), in while conditions, in ifx arms whose type is not a number or bool, and in for steps. An else if condition is first split into an else block holding a plain if, and the second operand of && or ||, or a scalar ifx arm, into a local assigned inside an if (text_cond_N, text_arm_N), so the call moves before that statement. A string local declared from such a result becomes a Text local when its name has one binding in the function, no defer follows in its block and every use after the declaration inside that block reads it the same way; each use then lends a TextView. Saved IR holds the rewritten form. tests/text_scoped_arguments.sh checks C allocation balance, AddressSanitizer, Go and the VM from source and saved IR.
  • Locals and parameters spelled like a C library or Go builtin name that generated code calls, such as free, memcpy or len, are renamed in the C and Go output; tests/runtime_local_names.sh covers both.
  • A slice of a Vec place, v[a:b] with either bound optional, becomes VecSlice(v, a, b) in the checker, so for over a Vec record field, as in for h.items or for it.tags inside for * h.parts, needs no backend support. The view borrows its root binding against moves, and the borrow check rejects writes that replace the viewed Vec or an enclosing place, such as VecPush, VecFree, assignment, or a call whose mutation summary reaches it. Element writes and sibling fields stay free. A member holding Vecs moves like a Vec member, as in last := popped.value, on C, Go and the VM. tests/vec_field_loops.sh covers loops, slices, moves, rejections, allocation balance, AddressSanitizer and source/saved IR.
  • Portable bundles retain referenced module globals, including records and fixed arrays. Explicit initializers run before the first statement of every BundleRun: integer and boolean literals, compile-time constant expressions over defines, float literals, string literals, and nested Type.{.field = value} record and fixed-array literals fold into the global's slot, with mutable initialized globals across calls and byte-identical source and saved-IR bundles; C and Go emit the same initializers natively (designated initializers in C, struct literals in Go). Checked runtime initializers can call procedures and read globals. Source and saved IR agree on C, Go, and .zx; imported modules initialize before consumers, including imports used only for startup effects, and each portable instance initializes once. String globals and string fields in record and array initializers decode escapes and retain their bytes for the instance lifetime. Tests compare contents across repeated runs and fresh instances. Native C and Go lower nested record and array initializers from the parsed expression tree; the cross-target test covers reordered fields and string escapes. The VM reads and writes globals across imported function calls, and reclaims replaced values without losing globals reached by a caller. Source and saved .zir, .zx, C and Go tests cover value isolation and repeated mutation. Each BundleRun starts fresh; BundleInstantiate preserves globals across repeated BundleInstanceRun calls on one instance.
  • Portable execution borrows read-only record and array parameters and reclaims temporary values after ordinary function calls while retaining copied results. A repeated nested call over a 4,096-record array checks value isolation without exhausting the VM's allocation limit. Replaced record and array storage is reclaimed after a value copy; the portable VM currently allows up to 1 GiB each of tracked record and array allocations so large checked parsers such as Kryon's KSS parser can execute.
  • Small records whose fixed-width primitive fields occupy at most sixteen bytes use the existing VM value payload. Arrays of these records store packed field bytes and slot-validity bits, avoiding a separate allocation and tagged value for every field. Shared instance metadata describes field names and widths; host record calls still marshal declared named fields. Field pointers retain logical locations in locals, globals, nested records and packed arrays. Value copies, replacement of borrowed arrays, vector growth and expired pointers retain their checked behavior. Unions, records containing references, abstract integers and larger records keep ordinary record storage. tests/vm_inline_records.sh checks the actual representation, integer boundaries, floating-point bits, layout-table growth, aggressive collection, source/saved IR and C/Go results.
  • Portable scalar plans retain checked operations and coercion policies for short binary expressions as well as larger expression trees. Repeated execution still reads current bindings and preserves branch selection, numeric widths, and depth checks. Integer arithmetic uses unsigned intermediates before narrowing, including u32 products that exceed the signed 64-bit range. tests/vm_scalar_plans.sh checks cold and cached plans, numeric boundaries, rejected divisors and shifts, source/saved IR, and independent instance lifetimes. Numeric payload helpers are visible to each evaluator part so these reads and conversions can inline without passing whole values across compilation units. Abstract integer division and remainder preserve wrapping at the signed minimum divided by negative one. The execution benchmark also covers the standard-library JSON parser with an independently decoded Python oracle. Arithmetic dispatch and scalar-plan conversion borrow their existing operand slots internally, avoiding whole-value argument copies while retaining the same canonical arithmetic and conversion rules. bench/vm_native.py adds matched generated C/Go controls with checked bodies and batch checksums, exposing the remaining interpreter gap without claiming full native parity. Plan registers can borrow normalized locals and identity conversions for the duration of a plan. Shared numeric operations write their live scalar fields directly into destination slots, leaving inactive container words untouched. Numeric literals snapshot their live fields before another literal can replace the same cache entry. Calls, mutation and collection remain outside plans; source/saved checks also exercise aliased coercion destinations and forced literal-cache eviction. Ordinary numeric operations, including compound assignments, enter the same numeric worker directly after checking their operand kinds and width policy. They borrow completed operand snapshots and write into an explicit destination instead of passing and returning whole tagged values across the private C call boundary. Compound assignments reuse the rooted target snapshot captured before the right side runs, preserving mutation order. Destinations may alias either operand; numeric, string, enum and error-path checks cover this behavior. Other values retain the general dispatcher. Standalone field and index reads use the same expression plans. Direct fields retain a checked position, guarded against the current field name and count; other shapes and nested paths retain ordinary lookup. Union reads still reinterpret their shared storage. Plans retain metadata and read current values on every execution. Scope cleanup checks a binding's value kind before classifying owned record types, avoiding ownership lookups for scalar, string and slice locals. Numeric and immutable-string procedures can also retain one instruction stream for local declarations and assignments, nested branches, blocks, loops, named break/continue, and returns. Local references use lexical slots and numeric literals retain their checked values; inputs and selected branches are read anew on every call. The stream shares the numeric operation and conversion rules with expression plans, preserves statement/loop step limits and scope depth. Primitive instructions allocate no VM values. Per-instance call metadata owns these plans with an 8 MiB instruction budget. Procedures with globals, address-taking or mutable container operations outside calls retain ordinary execution; rejected or oversized plans execute no partial instructions. tests/vm_function_plans.sh compares dynamic inputs, numeric errors, scoped and named control flow, wide jump targets, and every statement-budget boundary against canonical execution from source and saved IR. String parameters and locals retain current immutable views, including their storage owner. Dedicated instructions handle empty strings, length, checked byte indexing and equality without adding string-kind tests to numeric operations or stores. Bounds errors, NUL/UTF-8 bytes, changing arguments, string returns and short-circuit paths match canonical execution at every tested statement budget. String literals, slicing, mutable aggregates and other unrepresented operations retain ordinary execution. Calls returning primitive values, strings or void can suspend the instruction stream in the ordinary checked evaluator. Argument order, mutable aliases, current procedure bindings, host capabilities, recursion and failures retain their existing behavior. These plans snapshot local reads and root temporary registers across calls, clearing only the registers they use at source statement collection boundaries. Procedures without calls retain a separate borrowing and numeric fast path. Ordinary Ziran calls allocate no host argument buffer; host calls take that buffer only when needed. The procedure corpus also compares calls that mutate their caller, named and indirect calls, recursion, allocated string results, forced collection and all tested step limits, with source/saved C/Go controls.
  • Fresh record and fixed-array results can transfer their storage through local returns, direct calls, literals, initializers, and assignments. The VM retains the value-copy rules for existing bindings and globals. This transfer path excludes aggregates containing owned vectors and storage reached by pointers or borrowed array views. tests/vm_return_storage.sh checks allocation counts and value isolation, including replacement through existing views, nested fields, heap and global reads, and source/saved-IR agreement with native C.
  • The portable runner has pointers to its own storage: *place takes the address of a local, global, record field, or array element; <<p and p.* read and write through it; p.field reaches a pointed-to record; and pointers compare by target and with null. A record or array whose address was taken stays alive while a pointer can reach it, and a pointer at a local fails cleanly once its call has returned. Pointers returned by host capabilities stay opaque handles: a bundle can store, compare, and pass them back, but not read through them. Pointer casts and arithmetic remain outside the portable subset. tests/portable_pointers.sh checks source and saved IR on C, Go, and .zx.
  • Compiler functions use short names without Zir or zir_ prefixes, including the IR serializer's ProgramWrite, ProgramRead, and PathIsIR. IR data types retain Zir names for now.
  • The inherited runtime type and enum-member lookup stubs are gone. Local and imported declarations now supply those names without a compiler fallback.
  • Native Go no longer treats ordinary imported record types as types from the legacy Kryon Go runtime. The standalone record-call test asserts that no Kryon package import is generated.
  • Native Go now uses declared record fields and ordinary call resolution even when a record or function has a name previously reserved for Kryon UI. A standalone source and saved-IR test covers these name collisions. Generated Go defaults to package ziran unless --pkg overrides it.
  • Native Go no longer has an implicit Kryon package import or a separate --runtime-implementation mode. Explicit Go extern targets generate package imports; host externs use a declared embedding interface. Source and saved .zir tests run both forms without Kryon.
  • The shared emitter now has only C and Go targets. Its unused JavaScript path and hardcoded Kryon calls have been removed.
  • Native C and Go accept Jai null for explicitly typed raw pointers, in comparisons, assignments, calls, and pointer-returning conditionals. The checker rejects untyped null bindings and scalar assignments. Native pointer expressions use Jai *value for address-of and pointer.* or prefix <<pointer for dereference, including writes through a pointer. ++/-- are rejected. The checker rejects addresses of temporaries and dereferences of non-pointers. Raw pointers can also be indexed on C and Go native targets when the caller knows the allocation bounds. Source and saved .zir agree. Raw pointers remain outside .zx.
  • The shared checker resolves record_pointer.field against declared record fields, including imported records, and reports unknown fields. Checked pointer-member reads and writes now emit from source and saved .zir on native C and Go, including nested fields and computed pointer bases. The C-style -> source operator is rejected. Type annotations require Jai prefix *Type; suffix Type* and const qualifiers are rejected in source and saved IR. Existing downstream Kryon source using those spellings still needs migration before cutover. Raw pointers remain outside .zx; broader host-bound native bodies still need checked lowering.
  • The inherited #intrinsic "web" modifier and its two name-based browser shims are gone. Browser behavior now requires an explicit host or target extern. C output no longer injects Kryon's inspection header for ordinary functions, and generated temporaries and header guards use Ziran names. Retired intrinsic, capability, and host import tags have no slot in the new IR schema; host calls use ordinary extern imports.
  • Unknown directives, function modifiers, declaration modifiers, and top-level forms are rejected generically; this includes old #ui, #style, and app/route forms. The embedded Kryon runtime declarations and copied src/kry_std implementation have been removed. JSON diagnostics no longer require Kryon code.
  • #instance is rejected as a language modifier. Its special statement and capture fields, retained-state emission, and IR serialization have been removed; a state library can supply that behavior through ordinary calls.
  • The parser no longer creates inline closures. Their capture fields, bundle cloning, backend wrappers, and VM parent frames have been removed. Named procedure values remain supported.
  • UI tree and DOM property fields have been removed from the statement IR. App, route, style, and inspector metadata have been removed from the IR and backend entry paths. Generic calls named Image and record calls named Button are covered by standalone tests. The Go backend no longer drops standalone BeginTree and EndTree calls or maps Canvas and CanvasResult by name. The parser no longer recognizes old app and route block words. Source, saved-IR, and portable bundle tests cover these names as ordinary declarations and calls.

  • Scalar #law NAME theorem bindings => condition; and #proof NAME { ... } certificates support reflexivity, modular polynomial normalization, exact order, boolean case splits, unfolding pure acyclic scalar procedures, equality rewriting, and checked theorem application. Missing/unsupported proofs and exhausted budgets are unknown; invalid certificates and cycles cannot be waived. Evidence distinguishes structural checks, concrete evaluation, finite exhaustion, and full-domain scalar kernel proofs. .zir and .zx store and recheck certificates and verification dependencies; forged graph references, steps, and evidence are rejected. Lean 4.34.1 validates the mathematical rules and an independent interpreter checks the kernel's claims. The kernel (cmd/compiler_proof_kernel.zi) and procedure projection remain trusted, and arbitrary-sequence induction remains future work. See LAWS.md and tests/proofs.sh, tests/law_integers.sh, tests/compiler_proof_kernel.sh, and make proof-model.

  • Named #law NAME kind payload; obligations are implemented for the type, bounds, effect, abi, custom, and forall kinds (forall is exhaustive over integer ranges, enum types, and bounded integer sequences ([N]LO..HI), with counterexamples and a 1,000,000-case budget): ziran check prints one JSON result object per obligation, disproved and unwaived unknown results fail the build, #law_waive NAME "reason"; waives an unknown, and the tables serialize per module in .zir (byte-identical from source and saved IR) and as the linked closure in .zx, where BundleLaw* accessors expose them and laws keep the types, procedures, and externs they name. Effect laws compare the derived class (#law Name effect Proc == pure; against pure, observing, mutating, or external), bounds laws compare a fixed-array bound (Type == N, >= N, <= N over compile-time values), and the size kind compares a type's laid-out byte size the same way; comparison payloads keep their procedures and type fields in linked bundles. Every checked procedure also carries a derived effect class — pure, observing, mutating, or external — computed from its body to a fixpoint and serialized in its .zir signature as ABI. #parallel for regions ride the range lowering into the checked while and the region checker accepts a body only when every called procedure is pure or observing, writes target iteration-local bindings, Vec mutations stay on region storage, regions do not nest, and the shape is a for; source and saved IR build byte-identical bundles everywhere. The native C99 target executes forward regions on pthreads through ziran_parallel.h: each region becomes a file-scope worker over contiguous index chunks that reaches captured fixed arrays through a pointer and other captures by value, the thread count is the processor count unless ZIRAN_PAR_THREADS chooses 1-64, and files containing workers compile with -pthread. Natively run bundles use the engine's worker pool. Reverse regions, the portable VM, and Go run serially with an emitted downgrade marker, as the backend-duties clause permits; region-leaving return/break/continue are rejected so no schedule can skip iterations. #parallel_gpu for regions validate before offload per the GPU clause: pointer storage, pointer-bearing call arguments, and allocator-backed Vec mutation are rejected, and external-class calls were already barred by the region rules. On the native C target on Linux and Android, cmd/zir/zir_emit_gpu.c lowers a region and its callees to an OpenGL ES 3.1 compute shader and include/ziran_gpu.h runs it on the fastest device it measures, loading EGL and GLES at run time. Regions of 32-bit integers, float32, bytes and booleans with capture[it] writes qualify; 64-bit values, nested for, globals, records, signed or variable division and foreign calls keep the CPU worker path with an emitted marker. A failed bounds check, a used up loop budget or a refused shader discards device results and reruns the region on the CPU. The ZX engine, the portable VM and Go do not offload. tests/gpu_offload.sh compares every device with the CPU.
  • ziran explain exposes a stable registry for every diagnostic code currently emitted by the compiler, with textual and JSON output. ziran explain --list enumerates the registry, and tests/explain.sh fails if a new emitted code is omitted. Native C and Go lowering reports malformed module state and output-file failures through registered target codes, including JSON lines for unwritable output destinations.
  • Whole local aggregates, fresh aggregate call results, vector-bearing record-literal fields, and plain record-member Vec paths rooted at local aggregates can move through declaration, assignment, argument passing, and return. C, Go, and the portable VM recursively drop vectors reached through record fields and fixed arrays; a member move clears only the native source field and invalidates only the corresponding VM slot. tests/aggregate_vec_safety.sh checks direct and nested member paths, sibling-field usability, branch returns, use-after-move, global-source, indexed/pointer limits, overwrite rejection, allocator balance, source/saved-IR equality, and all four execution paths.
  • Validated benchmark harnesses record compilation and runtime samples with command lines, versions, hashes, hardware, and checked outputs. The multilingual UTF-8 harness compares source and saved IR across C, Go, .zx, and handwritten C, Go, Rust, Java, JavaScript, and Python implementations. The record-call harness covers fixed arrays, record value parameters, field access, wrapping s32 updates, and an ordinary call in every round. It compares source and saved IR through generated C, Go, and .zx, plus the available comparison languages.
  • Package manifests and lockfiles can pin source-only dependencies without a Ziran manifest. Resolution, checkout, map generation, drift detection, and ziran add --source are covered by tests/package_resolution_cases.zi.
  • Polymorphic procedures bind $T through a direct, slice ([]$T), or pointer (*$T) parameter. tests/generic_slice_parameter.sh runs slice binders over scalars, 64-bit values, records, and subslices through source and saved IR on .zx, C, and Go, runs pointer binders natively, and rejects non-slice and non-pointer arguments. std/sort.zi builds an allocation-free heapsort, IsSorted, LowerBound, and BinarySearch on them; tests/sort.sh checks edge cases on .zx and C.

Still required

  • Finish compiler self-hosting: declaration parsing, semantic checking, IR persistence, proof validation, native lowering, portable linking, and the runtime still need to move from C to Ziran. The generated bootstrap modules currently cover token, text, source scanning, declaration syntax, enum evaluation, type spelling and field parsing, expression and initializer grammar, foreign declarations and target spelling, and statement and compile-time branch syntax. Declaration IR construction and semantic checking still require migration.
  • Finish Jai parity and specialization: cover remaining expression forms, exact foreign array ABI behavior (including nonempty .data), generic fields whose types depend on specialization parameters, and broader procedure-value support.
  • Complete ordinary-library procedure values and imports. Named capture-free callbacks and stored procedure values already work across native targets and .zx; procedure-valued defaults and the remaining general import forms do not.
  • Move remaining target-specific declaration and import lowering from text into typed IR, then enforce target capability errors uniformly.
  • Extend the .zx linker and verifier to all checked expressions and native features, including complete host capability coverage. The current bundle embeds checked .zir and follows direct calls, but it remains a subset.
  • Complete ownership edge cases: unknown heap and host-backed view aliases, indexed and pointer-backed field moves, and reclaimable storage detached by BuilderFinish.
  • Expand numerical conformance IDs and the initial feature registry to the full language surface, complete JSON diagnostic fields, and cross-target capability reporting.
  • Complete native backend parity, FFI capability checks, law coverage, and downstream application gates, then make the planned single breaking cutover. See Migration.