// the platform

Two languages in,
native code out.

Everything between the source file and the executable is written for this project: the parsers, the intermediate language, the optimizer, the register allocators, the assemblers, and the linkers. There is no LLVM underneath, and no system toolchain is invoked to produce a binary.

// frontends

Zia, and a BASIC that isn’t a toy

Zia is the language the platform is built for and built in — Zanna Studio is written in it. BASIC is a second, complete frontend rather than a demo: it has classes, structured control flow, and file I/O, and it lowers to exactly the same IL.

module Hello;

bind Zanna.Terminal as Term;

func start() {
    var total = 0;
    for i in 1..6 {
        total = total + i;
    }
    Term.Say("Zanna says hello");
    Term.SayInt(total);
}

Every program on this page was compiled and run with the toolchain in the repository. The comments showing results are the output those programs actually produced.

What Zia has

  • Static typing with inference; classes with reference semantics, structs with copy semantics
  • Interfaces with runtime dispatch and default method bodies
  • Generics with interface constraints, enums with exhaustiveness checking
  • Lambdas, optionals (T?, ?., ??), Result[T], try/catch/finally
  • File-based modules with an explicit bind system
  • No raw pointers. Ptr is not part of the Zia source surface

What BASIC is for

  • A modernised QBasic-style dialect: LET, clean arrays, short-circuit booleans, lightweight objects
  • Structured control flow, SUB/FUNCTION, CLASS with SUB New, file and console I/O
  • Complete games ship in it — Tetris, Frogger, Pac-Man and Centipede are all BASIC demos in the tree
  • Cross-language calls are real: Zia declares expose foreign func and calls into a BASIC module

// zanna il 0.3.0

The thin waist

Both frontends compile to one typed intermediate language. Everything downstream — the verifier, the optimizer, the VM, both native backends — only ever sees IL. That is what keeps a new frontend from becoming a fork of the whole toolchain, and it is why the VM and a native binary are required to agree.

Design properties

  • Typed. i1, i16, i32, i64, f64, ptr, str, void, plus error and resume-token types used only for exception lowering
  • SSA with block parameters rather than phi nodes — values are passed along edges, which is easier to read and to verify
  • Explicit terminators, no fall-through. Every block ends in ret, br, cbr or similar
  • Checked arithmetic by default. Signed integer maths must use .ovf forms; the verifier rejects a bare add
  • Textual and hand-writable. You can write an .il file yourself and run it
  • Versioned. Every module opens with il 0.3.0

Why it is written down

The IL has a normative specification, and changes to opcodes, grammar or verifier rules go through a written design review before any code lands. The tools are held to the document rather than the document being back-filled from the tools.

Two registry dumps are generated from the live binary rather than maintained by hand, so they cannot drift from what the compiler actually implements: zanna --dump-opcodes and zanna --dump-runtime-api.

The optimizer, shown rather than claimed

mem2reg promotes stack slots to SSA registers. Because the IL uses block parameters, the result is unusually legible: the two stores and the load disappear, and the value arrives at Join as an argument. This is the real output of zanna il-opt before.il -o after.il --passes mem2reg.

before

il 0.3.0
func @main() -> i64 {
entry:
  %t0 = alloca 8
  %t1 = icmp_eq 0, 0
  cbr %t1, T, F
T:
  store i64, %t0, 2
  br Join
F:
  store i64, %t0, 3
  br Join
Join:
  %t2 = load i64, %t0
  ret %t2
}

after mem2reg

il 0.3.0
func @main() -> i64 {
entry:
  %t1 = icmp_eq 0, 0
  cbr %t1, T, F
T:
  br Join(2)
F:
  br Join(3)
Join(%t4:i64):
  ret %t4
}

// passes

One optimizer, shared by both languages

Passes are registered and composable, and you can run any subset by hand with zanna il-opt --passes. This is the default -O1 pipeline as the driver reports it:

simplify-cfg, mem2reg, simplify-cfg, sccp, constfold, peephole, dce,
simplify-cfg, sccp, inline, peephole, dce, simplify-cfg

The registered set is wider than any single pipeline uses — SSA promotion, sparse conditional constant propagation, global value numbering, loop rotation, loop-invariant code motion, induction variable simplification, loop unrolling, early CSE, reassociation, inlining, sibling-recursion elimination, dead store elimination, exception-handling cleanup, peephole rewriting, and CFG simplification.

zanna build defaults to -O1; zanna run defaults to -O0 to keep the edit-run loop fast. -O2 is the release profile.

// execution

A VM and two native backends that must agree

The VM

The primary development target: fast to start, deterministic, and step-budgetable. Dispatch is pluggable — function table, switch, or threaded computed-goto, which is the default where the compiler supports it.

There are in fact two engines. A tree-walking IL VM, and a bytecode VM that compiles IL to a compact form. The bytecode engine is parity-tested against the tree-walker across a shared IL corpus, comparing return values, runtime stdout, and trap kinds.

Native code

AArch64 targeting AAPCS64, validated end-to-end on Apple Silicon across the demo games. x86-64 targeting both System V AMD64 and Windows x64, with linear-scan allocation, validated on Windows with the full codegen suite passing.

Zanna writes its own ELF, Mach-O and PE objects, assembles them, links them, and emits DWARF — which is what makes a dependency-free native build possible in the first place.

Determinism is a rule, not an aspiration

  • The VM and a native executable must produce identical output for every defined program. That equivalence is enforced by tests.
  • macOS native linking targets Apple Silicon. macOS x86-64 is not a supported native-link target.
  • The bytecode VM is useful for bounded probes and tests; it is not a real-time game execution target. Games are built with --build-profile release.

// standard library

The runtime both languages share

One library, reachable from Zia and from BASIC, written in portable C with a stable ABI and no compiler dependencies. The table below is generated from the runtime definition files, so it tracks the code rather than the documentation.

DomainClassesFunctionsWhat it covers
Graphics3D641240Meshes, materials, lights, animation, terrain, water, navmesh, post-FX
GUI791137Desktop widget toolkit, themes, accessibility, code editor
Game3D61803World, entities, prefabs, character and camera controllers
Game527812D game systems, HUD widgets, particles, tilemaps
Graphics475732D canvas, pixels, images, colour, text
Collections31487Lists, maps, sets, sequences
Network27299TCP, UDP, HTTP, WebSocket, TLS
Input7265Keyboard, mouse, gamepads, action maps, chords
Math12264Vectors, matrices, quaternions, bit operations
IO16233Files, paths, directories, temporary storage
Game2D1143Scene documents, tilemaps, typed object properties
Audio9132Mixer, voices, music, synthesis, spatial audio
Localization10121Locales, number and date formatting, message catalogs
Graphics2D4109Vector paths, transforms, gradients
Data997JSON, CSV, structured serialisation
Crypto1081Hashing, digests, random, encoding

Abridged — the full inventory also covers threads, time, processes, PTYs, diagnostics, functional helpers, options and results. Run zanna --dump-runtime-api for the complete machine-readable contract.

// tooling

Built to be called by programs

Editors, scripts and coding agents are first-class consumers of this toolchain. Diagnostics are structured, exit codes are differentiated so a caller never has to parse prose, and the language servers speak both LSP and MCP.

Type-check without running

$ zanna check broken.zia --diagnostic-format=json
{
  "diagnostics": [{
    "severity": "error",
    "code":     "V-ZIA-UNDEFINED",
    "stage":    "sema",
    "message":  "Undefined identifier: mesage",
    "range": {
      "begin": { "line": 6, "column": 14 },
      "end":   { "line": 6, "column": 20 }
    },
    "source":  "    Term.Say(mesage);",
    "help":    "Declare the symbol, import it, or correct the spelling.",
    "notes":   [],
    "fixits":  []
  }]
}

Pretty-printed here; the tool emits one line, and carries absolute file paths and per-file ids that are trimmed above. Exit code 2 means compile or verification errors.

Evaluate a snippet

$ zanna eval '2 + 3 * 4' --json --type
{"success":true,"trapped":false,"resultType":"Integer","output":"14\n","error":"","type":"Integer"}

Ask the binary what it contains

Two inventories are generated from the live binary rather than maintained by hand, so they cannot drift from the implementation. They are what the editor completion, the language servers and the numbers on this site are all reading.

The IL has 84 opcodes, and the runtime registers 531 classes across 7,844 functions.

$ zanna --dump-opcodes | python3 -c 'import json,sys; d=json.load(sys.stdin); print(d["ilVersion"], len(d["opcodes"]))'
0.3.0 84

$ zanna --dump-runtime-api | python3 -c 'import json,sys; d=json.load(sys.stdin); print(len(d["classes"]), len(d["functions"]))'
531 7844

Run on 2026-08-05 against the tree at v0.2.99. Both dumps are JSON on one line; the counting is done here rather than eyeballed.

Look up a diagnostic code

$ zanna explain V-ZIA-UNDEFINED
V-ZIA-UNDEFINED (zia-sema)
  Use of an undefined identifier; may include a did-you-mean fix-it
Exit codeMeaning
0Success — no errors
1Usage error, or the target could not be resolved
2Compile or verification errors
3Runtime trap (zanna eval)

// where it stands

Solid, and rough

Working well

  • Both frontends compile substantial real programs — the demo games are tens of thousands of lines each
  • The IL, verifier and optimizer are the most settled part of the tree
  • AArch64 is validated end-to-end on Apple Silicon; x86-64 passes the full codegen suite on Windows
  • The machine-readable surfaces are generated from the binary and do not drift

Still moving

  • Language surface, diagnostics, IL rules and tooling all still change without notice
  • The IL reference is 0.3.0; the source tree is v0.2.99 and unreleased
  • There is no stable release, and no compatibility promise across versions