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/simdvector 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.#targetprocedures get SSSE3, SSE4.1 and AVX2 clones in C behind a dispatcher that reads CPUID (and the OS's YMM state) once;ZIRAN_SIMD_TIERforces a lower tier, andtests/simd_target.shchecks each forced clone under gdb where it can. Checking rejects 256-bit vectors by value at a#targetprocedure's boundary and in its calls. C built for wasm32 with-msimd128uses 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 aQEMU_AARCH64here. -
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.shcovers 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.shchecks 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.shchecks 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.shchecks 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.pycan pair a preserved compiler with the current one alongside C and Go. Tool probes, compiler workers and the linker preferposix_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
usingedges 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.shcovers shared nodes, malformed backedges, record rewrites and distinct same-named declarations; the compiler type matrix checks concrete builtin value classification.bench/body_eligibility.pymeasures 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
.zirdata. It measures the payload and writes directly to files or pipes without a temporary payload file.tests/zx_portable_write.shchecks graph immutability, binary assets, certificates, persistent state, append streams and write failures;tests/bundle_libraries.shcovers shared and included code.bench/zx_metadata.pycompares 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.shcovers empty and binary output, positions, write errors and temporary-file fallback.bench/android_execution.pyprepares 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_asmandCGO_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.shcovers source/saved IR, package startup, concurrency, native test modules, output pruning and strict cgo pointer checking.bench/go_assembly.pymeasures whole-loop and per-operation placements: cgo transitions can dominate small operations, so native execution is opt-in. WithCGO_ENABLED=0, the same-tags=ziran_asmopt-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.scompanions.tests/go_direct_assembly.shchecks 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.shchecks 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.shchecks the schema, separate output, and failure reporting.bench/compiler_scaling.pyvalidates 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.shchecks 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.shcovers these failure paths and allocation-free out-of-memory reporting. Arbitrary external package tools and fatal signals remain outside that transport. -
The native
zirancommand, including its launcher and package child-process transport, andzi-fmtare implemented in Ziran. Their build compiles.zito native output without handwritten C command adapters or shell/AWK formatting policy. Package allocations use the language'ssize_ofinstead of C size helpers.tests/native_commands.shrebuilds the command from its source and saved IR, retaining version output, tool discovery from PATH, inspection commands, checking, and formatting.tests/package_process.shchecks 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.shchecks 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.shchecks 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_*.zimodule 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 inbootstrap/, then rebuild every tool from the maintained.zisources. A seed also keeps the generated headers of standard modules it uses, such asvec.h.make check-bootstrapverifies the seeds andmake update-bootstrapregenerates them. Eachtests/compiler_<module>.shchecks 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.shchecks 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.shchecks 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.shchecks native interface dispatch, mutation, failure paths and rejected method metadata. -
Typed Go
bindbindings 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.shchecks native execution, imported signatures, nil callbacks, declaration text independence and target reachability. Nativeretainbindings 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
assertbindings retain native interface checks, zero values on failure, typed nils and shared pointer/slice storage. Typedspawnbindings 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_gosupplies native typed channel handles, nonblocking sends and closure;std/context_goadds child cancellation and deadlines.tests/go_assert_spawn.shchecks 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:builtincgo_enabledselects application fallback paths, while direct unavailable C calls panic.tests/go_c_headers.shchecks 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
initbindings 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.shchecks 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
makeandappendcan return[]Map(string, Any)and slices of imported generic records without declaration-order failures.tests/imported_host_slices.shchecks 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.shcompares 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.shchecks 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.shchecks 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.shchecks 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.shchecks a 40-module graph with shared imports, unused state, global mutation, and source/saved bundle agreement. ziran featurespublishes 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, localTextViewmutation checks, record-field vector moves, procedure defaults, and procedure values.docs/FEATURES.jsonis byte-identical toziran features --json;tests/features.shchecks 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, andu64forms. Signed overflow and unsigned wraparound follow the operand width, the minimum signed integer divided by-1wraps 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. Runtimefloat32andfloat64division 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 astatic inlinecopy of each small public procedure that only uses what the header declares, which a function-like macro routes other modules' calls to; the defining.ckeeps the external definition. Plain-O2/-O3builds inline across modules without LTO and keep per-module object reuse.tests/cross_module_inline.shchecks C, Go, native and portable results, the inlined calls, C hosts taking addresses, C++ hosts and rebuilds after an inlined body changes.#inlineprocedures arealways_inlinein C, private onesstatic inline, and public ones keep their header copy past the size limit;#coldprocedures arenoinline, cold, get no header copy, and are//go:noinlinein Go. Saved IR keeps the modifier.tests/procedure_inlining.shchecks the attributes, out-of-line and inlined calls, C at-O0and-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. Areturnof 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.shchecks 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. Aforbody that redeclares its binder, or holds acontinuewhere 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.shchecks C, Go, native and portable results. Integer literals with_separators,0bbinary andu64values above thes64maximum 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.shchecks 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 asline := row * 16andat := line + offset, and a declaration without a value starts at zero. An enclosingiforwhilecondition, earlier&&and||operands and an earlierifending inreturn,breakorcontinuebound what they compare until it changes. Their comparisons of sums and products, such asinput.count < (height + 3) * stride + width + 4, prove indexes such asrow * stride + columnbelow a slice count or array length when no step of the guard or the index can wrap, sofirst + 1 + secondbounds neither term;tests/guarded_index_ranges.shchecks 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 enclosingwhilecondition until the body writes it again; a write followed bycontinue,break,returnorunreachablein its own block, such as the step aforloop runs before acontinue, does not count for reads after that block. When several facts bound a counter, such asi <= 31from a guard andi <= height - 1from the loop, each is tried. A factor's sign and the final comparison may take away up to three facts, sostride >= widthandwidth - 1 - j >= 0showstride >= 1, and products of 32-bit values such as(cast(s64)height - 1) * stridekeep their range when no corner overflows. A local declared fromifxis 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 ascast(s64)(height - 1)ins32, 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 asradius * stride. Anelsebranch knows that each branch before it in the chain was false, andx != cor a falsex == cmoves an end ofx's range that equalsc;tests/loop_guard_ranges.shchecks 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.countkeeps 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 boundsfor i: 0..frame.count - 1while the body stores through a slice; a record parameter's fields change only with the parameter.tests/field_guard_ranges.shchecks four proven functions and ten reads that keep their checks, after a store through another pointer, a*u64or byte view of the record, a global or a slice element, a call, even in anelse ifcondition, a store earlier in the loop, or a guard on a sum of fields that may wrap.tests/proven_index_ranges.shchecks 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 generalIntegerOpswitch, so compilers emit oneidivorsarplus the checks;tests/checked_integer_ops.shcovers every width on C, Go, native and portable bundles from source and saved IR, and rejectsIntegerOpin the C. Generated Go shifts by a nonconstant count in the operand's own type behind an inlinableshiftCountcheck, and the test rejectsintegerOpin the Go.tests/numeric_semantics.shchecks these cases from source and saved IR on C, Go, and.zx. Its stable conformance IDs are published byziran capabilities --json, described bytests/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/formatbuilds text on aVec(u8): overloadedAppend(*builder, value)for strings, integers, bools, and floats, andBuilderPrint(*builder, "format", args...), which expands to those calls and importsstd/formatfor its file.tests/format_builder.shchecks 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.shchecks source and saved IR on C, Go, and.zx;tests/large_array_literals.shchecks 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.shchecks 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 --lintwarns (codelint.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 (codelint.char) when au8is compared with a printable character's code written as a number, suggesting the#charliteral, and (codelint.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 abool.tests/lint_casts.sh,tests/lint_characters.sh, andtests/lint_flags.shpin 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.FindTypeand 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.shcovers 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 sanitizecompares every kept type and procedure answer with a fresh lookup. - Record fields are parsed into immutable snapshots shared by identical field
bodies.
TypeNextFieldcursors 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.shchecks this boundary, source/saved IR, all native targets, the VM, and bootstrap agreement..zirstill 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 rungives programs a 256 MiB stack.tests/portable_long_runs.shandtests/portable_limits.shcover these. - A generic record from another module may hold types only that module
imports: the user's
Bag(string)reads the library'sVec(K)field asvec.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.shchecks source and saved IR on C, Go, and.zx. std/fileimports the file operations for the native C target: file_linux on Linux, Android, the web, and Windows. It addsReadEntireFileandWriteEntireFile.tests/file_portable.shruns 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-scopestatic constin C, a packagevarin Go and a value the portable VM builds once and keeps. Go also declares a copy or passes the table by value from thatvar.tests/constant_tables.shchecks 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.[...], ...],#runevaluatesg[a][b] = vinto one, andGRID[i][j]indexes the table in place in C, Go and the portable VM, with no copied row;tests/nested_constant_tables.shchecks them on every target from source and saved IR. clz,ctz,popcountandbswaptake oneu8/u16/u32/u64and return that type, with the width as the count for zero. C emits__builtin_*helpers (with a portable fallback), Gomath/bits, and the portable runner computes them directly;tests/bit_builtins.shcompares them from source and saved IR.New(T)allocates a zeroedTand returns*T;free(p)releases it and ignores null. C usescalloc/free, Go usesnewand 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 asNeworfree(such as C'sfree) is called instead. Native callers remain responsible for raw pointer lifetimes.tests/heap_new.shchecks 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.shchecks source and saved IR on C, Go, and.zx. - Records take Jai operator procedures:
operator + :: (a: V, b: V) -> Vfor the binary operators+ - * / % == != < <= > >= & | ^ << >>. Several declarations of one operator are overloads;a += buses operator +,a != bfalls back to operator ==, and#symmetricalso 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.shchecks source and saved IR on C, Go, and.zx. printshows 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.shchecks 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..countwithcount: s32iss32; constant ranges stays64). Indexing withvalue[a..b]reports that slices are writtenvalue[start:end].tests/range_types.shchecks source and saved IR on C, Go, and.zx. std/hash_mapprovidesHashMap(K, V)withHashMapSet,HashMapGet,HashMapHas,HashMapRemove, andHashMapFreeover 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 ofVec(string)by their slice type.tests/hash_map.shchecks source and saved IR on C, Go, and.zx.printshows 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.zxagree. Flag enums still need a cast.tests/print_enums.shchecks source and saved IR on C, Go, and.zx.- Polymorphic procedures may bind several type parameters, each with its
own
$Name, as inPick :: (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.shchecks source and saved IR on C, Go, and.zx. - Fixed-array parameters
[N]$Tinfer their element type and enforce their declared capacity, including zero-capacity arrays. Bounds resolve in the defining module, and dependent[N]Tparameters 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.shchecks 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.shchecks source and saved IR on C, Go, and.zx. - Jai resizable arrays:
[..]Tisstd/vec'sVec(T), imported for the file automatically, andarray_add(*a, x),array_reset(*a),array_free(a), andarray_reset_keeping_memory(*a)are the matching Vec operations. Indexing,.count, andforwork as on Vec. A local[..]Tpassed where[]Tis 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.shchecks source and saved IR on C, Go, and.zx. - Procedures return several results as in Jai:
-> s32, s32or 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, fieldsvalue_0...), so every target and.zxrun 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.shchecks source/saved-IR equivalence, single evaluation, rejected result types/counts, and saved-library validation.tests/multiple_results.shchecks 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
float32intofloat64, at initializers, assignments, returns, arguments, and binary operators. The checker inserts an explicit cast into the checked graph, so C, Go, and.zxsee the same conversion. Narrowing, sign changes, and integer-to-float conversions still needcast.tests/implicit_widening.shchecks source and saved IR on C, Go, and.zx. - Jai
using record: Typeparameters and local declarations, plususing record;and nested paths such asusing 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 declaredusing field: Recordpromote 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-backedusingfields work in native targets. Polymorphic procedures preserve parameter, local, and imperativeusingdeclarations in saved templates and lower them for each concrete call. File-scopeusing value;, nested paths, andusing 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#loadboundaries are tested from source and saved IR in C, Go, and.zxbytests/data_scope_using.sh. Public globals from open and named imports can also be read, assigned, and opened with recordusingin procedure bodies.tests/imported_globals.shchecks 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 andusingfield 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_exporton 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.gofiles 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 usename: Typedeclarations.#export,#global, and C-stylestaticsource 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.shexercises 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
#mustafter 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_timeevaluates 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#ifcondition. - Jai
#caller_locationin a parameter default supplies the calling file's full path and physical line as aSource_Code_Locationrecord. 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#ifcalls capture the same caller path and line, including nested calls and explicit overrides;tests/caller_location.shcovers source and saved IR on all three targets. - Jai
#string DELIMITERraw 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-fmtpreserves 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
#ifbranches and#assertare 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
#ifxarm separators and still rejects C-styleforheaders. Source and saved IR execute alike in the portable runner, C and Go. ifandelse ifaccept Jai's optionalthenbefore a block or one statement. Inlineelsestatements 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_libraryand#foreignforms. The compiler resolves those library declarations to its existing host, C, or Go import model before saving IR. A trailing..anyparameter 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#externsource modifier is rejected. -
Source accepts Jai
#scope_file,#scope_module, and#scope_exportfor 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.#privateis 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.
zi2zirchecks and saves.zir;zi2candzi2gocompile.zior saved.zirinput. make checkpasses standalone non-UI programs, a two-module import, and an imported typed record call namedButtonthrough generated C and native Go. A local declaration binds its non-void result, with source and saved.zirbuilds across those targets and matching source/saved.zxexecution. The call is resolved from its declaration, not its name.- Jai procedure type aliases such as
Child :: #type (s32) -> ();andCompute :: #type (s32) -> s32;can be passed as callbacks to imported functions. Source and saved.zirbuilds 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#slotdeclaration 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 nesteddeferinside a deferred block still needs lowering support. - Jai inclusive integer ranges accept named binders, implicit
it,it_index, andfor <reverse iteration. Bounds run once, andbreak,continue, nested ranges, anddeferwork 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.zxsupportfor *pointer iteration for in-place element changes. Collection expressions run once and value bindings copy elements. C-style three-clause headers are rejected. Jaifor_expansioniterables remain unsupported. - Fixed arrays, borrowed slices, and strings now expose read-only Jai
.countvalues of typeint, including in range bounds. The inherited.lengthsource spelling is rejected. Single-statementif,while, andforbodies now normalize to checked blocks. Namedbreakandcontinuetarget enclosingforbinders or namedwhilecondition 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, andbundlediscover extensionless imports transitively. Repeated--module-path DIRoptions 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 itssrc/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. NamedAlias :: #import "module";andAlias :: #import, file "relative/path.zi";expose public procedures, constants, and record types throughAlias.Name; named imports do not leak unqualified names.Alias :: #import, dir "relative/directory";loads the directory'smodule.zi; ordinary module lookup also findsname/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.shchecks source and saved IR on C, Go, and.zx.#import, stringcompiles quoted or raw#stringsource 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
#importnow 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_callcallback parameters and results resolve imported type aliases in their declaring module. Source and saved IR are covered bytests/private_module_imports.sh. - Module inputs accept
.zisource and.zirIR only;.kry,.kir, and.krbare 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. zi2zirwrites experimental binary.zirversion 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
uniondeclarations, 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 withunsafetyped 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.shchecks 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 FILEbuilds an experimental version 26.zxfrom source or saved IR.zi2zx run FILEloads 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 withs32/int/bool/voidresults, and helper functions withs64/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, andu64bitwise operations and shifts, calls, arithmetic, comparisons, casts, assignments,if/else,while, lexical blocks,break,continue,unreachable, Jai-style enumif #completecases, 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 inmake 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 nestedchararrays, value-copy isolation, and portable bounds failures. Linked bundles retain needed compile-time definitions for bound expressions, including constants-only imports. IntegerNAME :: valuedefinitions 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, payloadmatch, postfix?,guard, C-styleswitchandgotowith labels,stateblocks, C-style locals, and raw C statements are rejected. Native C and Go builds check source unconditionally and reject--strictand--no-strict. The standardOptionandResulttemplates 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 runswitchandgotobindings, 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.shchecks 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.shchecks source and saved IR on all four targets. Linked globals and constants with the same native name also remain distinct;tests/imported_value_names.shchecks 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.shchecks source and saved IR on all four targets. Generic record templates use namedName :: Generic(Type)declarations or directGeneric(Type)annotations;std/pair.zialso providesPair. 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. Jaiifxconditional expressions, with or withoutthen, 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.shcompares source and saved.ziracross C, Go, and.zxwhile checking that unselected arms have no effects. Jai#ifxselects a constant, global initializer, or function expression arm before strict checking and saved-IR generation. It accepts the Jai semicolon beforeelse, nested selections, boolean and integer constants, constant aliases, and compiler-hostOScomparisons with.WINDOWS,.MACOS, or.LINUX; unused arms are not type-checked.#ifxalso selects record and fixed-array values in file constants, global initializers, local definitions, and call arguments on C, Go, and.zxfrom source and saved IR.#run,#if,#ifx, and#assertevaluate pure procedures with signed integer (up tos64), unsigned integer (up tou32), or boolean parameters/results. Local declarations and assignments, branches, boundedwhileand numeric range loops, nested calls, and imported calls work in#run,#if,#ifx, and#assert.#ifcan resolve imports declared before its condition while parsing, including importedsize_oflayouts. 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;#assertcan compare those results. String comparisons use the same escape and UTF-8 decoder as the portable runner. Source-selecting#ifcan use earlier#rundefinitions when their procedures and imports are available while parsing. Named imported real and string constants resolve in#ifand#runeven when the import declaration follows the condition. Their public visibility and private exclusion are checked from source and saved IR on C, Go, and.zxbytests/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#runscalars and#assert. Local record and fixed-array#rundefinitions also bind to checked literal graphs in procedure bodies, and direct file-scope initializers can use them.tests/compile_values.shchecks 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.shchecks nested records, arrays, compile-time#run,#assert, and#iffield 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 asLib.Point.{...}work in globals and bodies, including nested inferred record fields;tests/imported_aggregate_constants.shchecks 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#ifxselections 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 ... elseselects 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_ifand#elseforms are rejected. Conditional record fields and enum members are selected before their layouts are checked. Explicit enum members accept JaiMember :: valuedeclarations. Source and saved IR produce the same values in C, Go, and portable bundles. Named enums now use Jai's defaults64storage or an explicit integer backing type.enum_flagsassigns successive bits to members without an explicit value. The checker rejects members outside their backing range, andsize_ofuses the backing width. Source and saved IR agree across the portable runner and native targets. Jai#specifiedrequires explicit member values for bothenumandenum_flags, and saved IR records the modifier. Values aboves64remain unsupported in the current portable enum evaluator, even foru64backing. Typed flags accept integer initializers and assignments, qualifiedType.Member, and contextual.Memberreferences. 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 typedType.Memberand contextual.Memberexpressions. Within a procedure,using Enum;opens enum members for bare lookup with lexical scope. Data-scopeusing Enum;also opens them in procedures, file-scope constants and globals,#run,#assert, and later#ifconditions. File-private using declarations respect#loadboundaries. Bare members withoutusingare 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 unionusingfields 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 scalarif/casearms support terminal#through;with source, saved IR, portable, C and Go agreement. Integer, boolean, floating, and string cases accept a finalcase;default. Deferred actions run at case boundaries and before#through;. Unknown function-body directives are rejected.size_ofsupports scalars, pointers,string, borrowed slices, fixed arrays, records, unions, named concrete generic types, and nested applications such asWrapper(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 oldsizeofspelling is rejected in source. Jaiintmaps tos64. In function bodies,size_of(type_of(expression))uses the checker's inferred type and does not evaluate its operand. File-scopesize_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#loadresolve 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 laterenum_flagstypes also resolve in#ifand#run, including implicit flags, explicit values,#specifiedbacking types, and a later#load. Inactive and file-private flags stay hidden;tests/forward_type_conditions.shchecks 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_ofand#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#ifcalls to unconditional procedures with local declarations, assignments, bracedif/else if/else, and boundedwhileloops run through the compile-time evaluator.break,continue, calls to another later procedure, and returns work in the tested subset;#runaccepts these calls too. Multiline headers, defaults, named arguments, later constants, and later#loadfiles 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#runthat calls such a later procedure now folds before a following#iftests the result; loaded file-private procedures remain hidden from callers in other files.tests/file_type_of.shchecks source and saved IR on C, Go, and.zx. Standalonetype_ofvalues and broader forward#ifqueries remain unsupported. Foreign-record size queries still require their Jai layouts. Jai#lineresolves to the physical source line before checking and remains stable through saved IR, native targets, and.zx, including#loadfiles.#fileand#filepathresolve 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#runcalls; source use outside a procedure is rejected. Untyped integer locals inferred with:=now uses64storage, including when their values exceed 32 bits, across saved IR and all supported targets. Source, saved IR, native targets, and.zxagree 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_apicalls now appear as module/function capabilities in.zx. The loader verifies the capability list against the linked IR.build/libziran.aandinclude/ziran_host.hexpose 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.shcovers source and saved bundles, malformed host results, and native C and Go foreign calls.BundleRunchecks all required bindings before execution. Native Ziran hosts can enumerate capability names throughstd/bundle_host.--asset-dir NAME=DIRembeds 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 NAMEselects 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 kindVM_HOST_POINTER: the VM stores the address, compares handles againstnulland 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 astring; 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 boundedstd/text_buffer.zihelper 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 aTextViewof a local byte array is rejected;tests/aggregate_view_lifetime.shcovers 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*valuepointer 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. ATextViewreached 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.shcovers 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 checkcompares source and saved-IR bundles with C and Go on a string program and rejects out-of-range indexing. - Portable finished-builder strings and
TextViewsnapshots 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.VmInstanceLiveValueBytesincludes string storage.tests/string_storage.shchecks bounded storage over repeated runs and source/saved IR, plus allocation-balanced emptyBuilderFinishin 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, onebytes: 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 astringborrow its bytes under the text-view lifetime and mutation checks, which also reject moving the Text while a view is live.tests/owned_text.shcovers locals, fields,Vec(Text)elements, pointer iteration, allocation balance, AddressSanitizer and source/saved IR. - The checker keeps a
Textcall result in owning positions and rewrites it elsewhere intovec.TextDetach(result), which needs no backend support and keeps the old string semantics.BuilderFinishin an owning position becomesvec.TextFromBuilder.vec.Namecalls resolve from modules that reach std/vec only through other imports.tests/owned_text_results.shchecks 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 returnText;tests/std_text_results.shcounts their allocations on C and checks the VM and Go. - After checking, a detached
Textresult or string-positionBuilderFinishthat is only read within its statement moves into a scopedTextlocal declared before that statement and lends aTextView, so it is freed when its block closes. Reads are comparisons,.count, indexing, slices passed on,print,TextCopy, the appended value ofBuilderAppend, andstringparameters whose checked callee only reads them, found transitively with a cycle counted as retained. A read also passes throughifxarms, 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#readsforeign 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), inwhileconditions, inifxarms whose type is not a number orbool, and inforsteps. Anelse ifcondition is first split into anelseblock holding a plainif, and the second operand of&&or||, or a scalarifxarm, into a local assigned inside anif(text_cond_N,text_arm_N), so the call moves before that statement. Astringlocal declared from such a result becomes aTextlocal 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 aTextView. Saved IR holds the rewritten form.tests/text_scoped_arguments.shchecks 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,memcpyorlen, are renamed in the C and Go output;tests/runtime_local_names.shcovers both. - A slice of a Vec place,
v[a:b]with either bound optional, becomesVecSlice(v, a, b)in the checker, soforover a Vec record field, as infor h.itemsorfor it.tagsinsidefor * 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 asVecPush,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 inlast := popped.value, on C, Go and the VM.tests/vec_field_loops.shcovers 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 nestedType.{.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. EachBundleRunstarts fresh;BundleInstantiatepreserves globals across repeatedBundleInstanceRuncalls 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.shchecks 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
u32products that exceed the signed 64-bit range.tests/vm_scalar_plans.shchecks 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.pyadds 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.shcompares 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.shchecks 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:
*placetakes the address of a local, global, record field, or array element;<<pandp.*read and write through it;p.fieldreaches a pointed-to record; and pointers compare by target and withnull. 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.shchecks source and saved IR on C, Go, and.zx. - Compiler functions use short names without
Zirorzir_prefixes, including the IR serializer'sProgramWrite,ProgramRead, andPathIsIR. IR data types retainZirnames 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
ziranunless--pkgoverrides it. - Native Go no longer has an implicit Kryon package import or a separate
--runtime-implementationmode. Explicit Go extern targets generate package imports; host externs use a declared embedding interface. Source and saved.zirtests 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
nullfor explicitly typed raw pointers, in comparisons, assignments, calls, and pointer-returning conditionals. The checker rejects untypednullbindings and scalar assignments. Native pointer expressions use Jai*valuefor address-of andpointer.*or prefix<<pointerfor 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.ziragree. Raw pointers remain outside.zx. - The shared checker resolves
record_pointer.fieldagainst declared record fields, including imported records, and reports unknown fields. Checked pointer-member reads and writes now emit from source and saved.ziron native C and Go, including nested fields and computed pointer bases. The C-style->source operator is rejected. Type annotations require Jai prefix*Type; suffixType*andconstqualifiers 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 copiedsrc/kry_stdimplementation have been removed. JSON diagnostics no longer require Kryon code. #instanceis 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
Imageand record calls namedButtonare covered by standalone tests. The Go backend no longer drops standaloneBeginTreeandEndTreecalls or mapsCanvasandCanvasResultby name. The parser no longer recognizes oldappandrouteblock 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..zirand.zxstore 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 andtests/proofs.sh,tests/law_integers.sh,tests/compiler_proof_kernel.sh, andmake proof-model. - Named
#law NAME kind payload;obligations are implemented for thetype,bounds,effect,abi,custom, andforallkinds (forallis exhaustive over integer ranges, enum types, and bounded integer sequences ([N]LO..HI), with counterexamples and a 1,000,000-case budget):ziran checkprints one JSON result object per obligation,disprovedand unwaivedunknownresults fail the build,#law_waive NAME "reason";waives anunknown, and the tables serialize per module in.zir(byte-identical from source and saved IR) and as the linked closure in.zx, whereBundleLaw*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,<= Nover compile-time values), and thesizekind 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, orexternal— computed from its body to a fixpoint and serialized in its.zirsignature as ABI.#parallel forregions ride the range lowering into the checkedwhileand the region checker accepts a body only when every called procedure ispureorobserving, writes target iteration-local bindings, Vec mutations stay on region storage, regions do not nest, and the shape is afor; source and saved IR build byte-identical bundles everywhere. The native C99 target executes forward regions on pthreads throughziran_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 unlessZIRAN_PAR_THREADSchooses 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-leavingreturn/break/continueare rejected so no schedule can skip iterations.#parallel_gpu forregions 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.clowers a region and its callees to an OpenGL ES 3.1 compute shader andinclude/ziran_gpu.hruns it on the fastest device it measures, loading EGL and GLES at run time. Regions of 32-bit integers, float32, bytes and booleans withcapture[it]writes qualify; 64-bit values, nestedfor, 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.shcompares every device with the CPU. ziran explainexposes a stable registry for every diagnostic code currently emitted by the compiler, with textual and JSON output.ziran explain --listenumerates the registry, andtests/explain.shfails 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
Vecpaths 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.shchecks 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, wrappings32updates, 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 --sourceare covered bytests/package_resolution_cases.zi. - Polymorphic procedures bind
$Tthrough a direct, slice ([]$T), or pointer (*$T) parameter.tests/generic_slice_parameter.shruns 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.zibuilds an allocation-free heapsort,IsSorted,LowerBound, andBinarySearchon them;tests/sort.shchecks edge cases on.zxand 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
.zxlinker and verifier to all checked expressions and native features, including complete host capability coverage. The current bundle embeds checked.zirand 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.