# Compiler

The compiler is the Gizlang package `compiler/`. Its `package.json` names each module (no sha256:
git owns the bytes). The compiler has one entry, `compile(input: bytes, w: MutableView<i64>, out: MutableView<u64>): i64`.
It changes source, a canonical graph or a package into LLVM IR or a diagnostic. The C driver
(`runtime/compiler_driver.c`) only reads files, allocates the workspace and writes the result.

## Contents

1. [Pipeline](#pipeline)
2. [Workspace and limits](#workspace-and-limits)
3. [Driver](#driver)
4. [Stable names](#stable-names)
5. [Native and live units](#native-and-live-units)
6. [Edits](#edits)
7. [Bootstrap](#bootstrap)
8. [Tests](#tests)
9. [Canonical graph](#canonical-graph)

## Pipeline

| Stage | Modules |
| --- | --- |
| Scan bytes into tokens. | `lexer` |
| Parse source into nodes. | `parser`, `syntax` (node arena) |
| Or import a `.gizgraph`. | `graph`, `json`, `index` |
| Or load a package (two passes, refer to [Packages](#packages)). | `package`, `integrity` (sha256) |
| Add the SDK modules that a program imports. | `sdk`, `sdk_sources` (generated) |
| Resolve modules, imports and exports. | `modules` |
| Specialize generics. Then check types, effects, borrows, contracts and constants. | `specialization`, `checker`, `queries` |
| Lower markup (TSX) to `gizlang:html` calls on a hidden buffer, when the checker gets to it. | `markup` |
| Emit LLVM IR. | `llvm` (programs, functions, statements and the recursive expression emitter), `emit` (shared emit pieces), `managed` (rooting), `heaptype` (heap types, layouts, trace), `places` (in-place analysis), `runtimecall` (runtime and C calls), `ownership` (buffers and frames), `entry` (process main, test runner, live link, host exports), `ir`, `text` |
| Remove the internal functions that nothing reaches. This step does not apply to live generations, whose units are keyed. | `prune` |
| Or print source, inspect, or plan. | `projection`, `inspection` |
| Entry, tool protocol, error names. | `main` |

### Markup lowering

1. The lexer scans markup with its tag and children states. A `<` opens a tag only where an
   expression can start.
2. The parser makes a `NODE_ELEMENT`. A component tag is already a call. The parser declares a
   hidden `let` buffer before the statement that holds the element.
3. The checker changes each element in place into `markupOpen`, `markupText`, `markupChild`,
   `markupAttribute` and `markupClose` calls on that buffer. It joins the fixed parts into
   `FLAG_MARKUP` literals in scratch nodes. It keeps the element as written in word 13, for
   projection.
4. After this step, only ordinary calls and string literals remain.

### JavaScript entry

A JavaScript or TypeScript program is not compiled: it runs on the VM ([VM](vm.md), `vm/`), as a Python program does.
`giz FILE.js` (`.mjs`, `.cjs`, `.ts`, `.mts`, `.cts`, an npm bin, or a folder with a package `main`) builds the
VM and runs the script; `giz build FILE.js -o app` writes the VM with the files of the script's project. Node's
own `lib/` runs on the VM over the bindings in `vm/node/`. The speed plan is in JS server ecosystem and
[the final design note](vm.md).

**Checks.**

- test262, by section.
- The top 1,000 npm packages, each with its own test suite, published per package. The target is a pass rate of about
  100%.
- For each package: the speed against Node and Bun.

### Python entry

A Python program is not compiled: it runs on the VM ([PYTHON](../guides/javascript-python.md), [VM](vm.md)). `build FILE.py` is refused.

### JVM entry

Status: design. Nothing is built yet. Java and every JVM language (Kotlin, Scala, Clojure)
enter as **bytecode**: class files and JARs from Maven Central, Gradle or Maven builds. The ecosystem ships bytecode,
not source, so one front end covers all of them. It uses the same pipeline as the
[JavaScript entry](#javascript-entry): one front end, profiles, guards, generic bodies and live tier-up.

**Requirements.**
1. **Run unchanged.** `giz run app.jar`, or a Maven or Gradle project, runs as HotSpot runs it (Java 21 and 25
   LTS), with Spring Boot, Quarkus and Micronaut apps unmodified.
2. **Import from Gizlang.** `import {Lists} from "maven:com.google.guava:guava"` gives typed, direct calls, with the
   types taken from the class files.
3. **Speed and footprint.** HotSpot's peak throughput, with native-image's start time (milliseconds) and memory,
   and no warm-up.
4. **No configuration.** Reflection, resources, proxies and serialization work without hand-written configuration
   files.

**What we take and what we overcome.**

| System | Take | Overcome |
| --- | --- | --- |
| GraalVM Native Image | Closed-world AOT; heap snapshot at build time; instant start and a small footprint | Reflection, JNI, proxy and resource configuration by hand or by a tracing agent; long builds; lower peak than HotSpot without PGO; dynamic class loading refused. We take the profile from the project's own tests automatically, keep generic bodies for what the profile missed, and load new classes as live units. |
| HotSpot C2 | Speculative specialization (class hierarchy analysis, inline caches, escape analysis) gives top peak | Warm-up, JIT memory, deoptimization machinery. We specialize ahead of time under guards and tier up by live swap. |
| Project Leyden, CRaC, OpenJ9 AOT cache | Shift work to build time; keep the trained state | They stay on the JVM, and CRaC needs checkpoint discipline. We compile to native once and keep the state through `gizlang:live`. |
| TeaVM, J2ObjC, Kotlin/Native | Bytecode or source to another target works | Partial class libraries and different semantics. We compile OpenJDK's own class library from bytecode, so the semantics are the JDK's. |

**Design.**
- **Class library.** `java.base` and the rest of OpenJDK's library compile from OpenJDK's own class files (GPLv2 with
  the Classpath Exception, which allows linking with our runtime). A small native layer replaces HotSpot intrinsics
  and JNI calls with Gizlang SDK calls (files, sockets, threads, time).
- **Objects and dispatch.** Classes have fixed layouts, so fields are direct offsets. Virtual and interface calls are
  devirtualized by class hierarchy analysis over the closed world, under a guard that a newly loaded class
  invalidates (an epoch, as for JavaScript prototypes).
- **Dynamic features.**
  - **Reflection and `MethodHandle`:** metadata is kept for the classes that the profile or any string-constant
    analysis reaches; for all others it is generated lazily.
  - **`invokedynamic`:** lambdas and string concatenation are resolved at build time; the other bootstrap methods run
    once and are cached.
  - **Dynamic proxies:** generated as live units.
  - **Class loading:** `Class.forName` and custom class loaders load bytecode at run time as live units through the
    same compiler.
- **Memory and threads.**
  - The Java Memory Model (volatile, final fields, happens-before) maps to atomics.
  - `synchronized` uses thin locks that inflate under contention.
  - Virtual threads lower onto Gizlang tasks; platform threads onto system threads.
  - The collector keeps weak, soft and phantom references and `Cleaner` with Java semantics; `finalize` is
    supported where code uses it.
- **Native code.** JNI and the FFM API (`java.lang.foreign`) call through `gizlang:native`.
- **Cache and prebuilt.** Keyed by artifact hash, the dependency lock (Gradle or Maven), compiler, target and profile.
  gizstack.com serves prebuilt units for Maven Central artifacts that are licensed for it, as for npm.

**Checks.**
- OpenJDK's jtreg tests, by area. The JCK is licensed separately: we do not claim "Java SE compatible" without it.
- The top 1,000 Maven Central artifacts' own tests.
- Spring PetClinic and Quarkus quickstarts run unmodified.
- DaCapo and Renaissance against HotSpot and GraalVM Native Image, for start time, memory and throughput,
  measured alternately on one machine.

### Host libraries

Status: design (Biseo for the language maintainers). It holds for JavaScript, Python and the JVM alike. Each ecosystem's
standard library has the same three parts: code in the language itself, a native boundary, and C or C++ below that
boundary. Each part gets one answer, the same for the three languages.

| Part | Node | CPython | OpenJDK | Answer |
| --- | --- | --- | --- | --- |
| Library code in the language | `lib/*.js` (MIT) | `Lib/*.py` (PSF) | class files (GPLv2 + CE) | Compiled unchanged. Fetched by version and hash, never copied into the repository or edited. Its own tests are the measure. |
| Native boundary | `internalBinding(name)` | C modules (`_io`, `posix`, `_hashlib`, `zlib`, `_socket`, …) | JNI natives, HotSpot intrinsics | One thin adapter per binding, onto the Gizlang SDK. The only native code written for a language. |
| C/C++ below the boundary | V8 bindings, libuv, OpenSSL, zlib, llhttp, ada, simdutf, c-ares, nghttp2, brotli | libc wrappers, OpenSSL, zlib, expat | HotSpot, libjava | Not compiled. Each one duplicates an SDK capability. |

Node's JavaScript dependencies that its lib requires as `internal/deps/...` (undici for fetch, Request, Response, Headers,
WebSocket and EventSource; acorn; minimatch) are library code too: fetched with lib/ at the same commit and placed where
js2c places them (tools/cli/bundle.giz `nodeLibrary`).

**Why.**
- *One implementation per capability.* Files, processes, sockets, TLS, HTTP, time, randomness, hashes, ciphers,
  compression, text codecs (UTF-8/16, base64, hex) and the WHATWG URL parser live once in the SDK, in Gizlang.
  Three languages share them, and the SDK's own programs use them too. A capability the SDK lacks (brotli,
  RSA and ECDSA, HTTP/2) is added to the SDK once. It is never added to one language's layer.
- *The library's semantics come from the library.* The APIs, argument validation, error codes and messages, and event
  order come from Node's or CPython's or OpenJDK's own code, by construction. A rewrite chases them one bug at a time.
  Node's error codes and messages are what npm packages test against.
- *The C below the boundary is not reused.* Node's `src/` is written against the V8 API. Its dependencies each duplicate
  an SDK capability, so compiling them would give two implementations of each capability and a C toolchain in
  every user's build.
- *Python and Java need nothing new.* A Python or Java adapter maps its own boundary onto the same SDK calls. Marshalling
  is one compiler rule per language, not hand code per function: bytes to `Uint8Array`/`bytes`/`byte[]`, strings,
  numbers, and an `IoError` to the language's exception with errno, code, syscall and path.

**Speed.** The library code costs nothing extra, for four reasons.
- Node's `primordials` are frozen copies of builtins, and the world is closed. The compiler binds them as intrinsics:
  `FunctionPrototypeCall(f, x)` becomes a direct call, with no guard.
- Hot work (codecs, hashing, compression, file and socket I/O) runs in SDK code.
- Dead code goes at link time: a program keeps only the library functions it reaches.
- Node's library runs on the VM, which boots it at the first builtin `require` or Node global ([VM](vm.md)).

**Not done.**
- The hand-written Node API layer is not kept. A JS shim of `fs`, `stream` or `buffer` is a second implementation of
  Node's semantics. The former `sdk/node` adapters are gone; Node's `lib` runs over `vm/node/`.
- V8 (see Embedded V8 above), and any C dependency, unless a measured gap remains after the SDK path.

**Order.** Each step is accepted by the library's own tests.
1. Node's library boots lazily on the VM (built).
2. The Node loader: `internal/*` resolution, `primordials`, `internalBinding` adapters for `path`, `events`,
   `util`, `buffer`, `string_decoder`, `url`, `fs`, `os`, `process_methods`, `crypto` (hashes), `zlib` and `spawn_sync`.
3. Node's `test/parallel` for those modules, then the top npm packages' own suites.
4. The remaining adapters: `tcp_wrap`, `tls_wrap`, `http_parser`, `cares_wrap`, `fs_event_wrap`, and `worker`.
5. CPython's C modules and OpenJDK's natives over the same SDK calls.

## Workspace and limits

All compiler data is in one `i64` workspace:

- A header of 128 words.
- Four words for each token.
- Nodes of 16 words: kind, token, type, a, b, c, d, next, binding, flags, scope, emitted, and
  four words specific to the kind.

`compiler/layout.giz` names each header slot, node kind, flag and error code. **Read it before
you read any other module.**

- Checker and lowering loop stacks, graph import depth, projection traversal, and inspection
  counters and indexes have separate header slots.
- The preparation step clears the full header for each request. Each phase initializes its
  own scratch area when it starts.
- Tokens stay indexes into the token table. Node identities stay aligned arena offsets. The
  named syntax owners access them.
- The compiler allocates nothing on the heap. It never recurses past fixed depth limits (`E_LIMIT`).
- For this reason, the compiler has its own small text, JSON, hex and Base64 readers (`text`,
  `json`, `lexer.hexValue`). The SDK readers allocate and grow values, and the workspace does
  not permit this. Each reader exists one time in the compiler.

### Driver limits

| Limit | Value |
| --- | --- |
| Input | 16 MiB |
| Selected source | 16,000,000 bytes |
| Workspace | 128,000,000 `i64` words |
| Files in package selection and transport | 1,024 |
| Fields in a record or in one union payload | 64. Union cases share payload storage, so their total field count is not limited by this value. |
| Nesting depth | 16 |
| Output | Starts at 128,000,000 words |

- The output grows only when the shared text codec requests a precise capacity. The host
  checks the growth arithmetic and allocation, then compiles the same immutable input again.
  A semantic `E_LIMIT` failure never causes growth.
- A record-limit diagnostic names the type or case, the measured count and the limit.
- The flattened size of a record or a union is not limited. A record value is one LLVM aggregate. A
  union is a tag word and the words of its largest case. A read or a write of a case field that is a
  record goes through a slot of the union type in the entry block of the function (`ir.giz`
  `unionSlot`): one store, one `getelementptr` and one load or store. LLVM's SROA then keeps only the
  parts that are used in registers. Thus the code of a case access does not grow with the leaves. A
  record of more than six words goes to a call as a pointer (`emit.giz` `byReference`), and a returned
  aggregate goes through a pointer to the result.
- Checker diagnostics keep the first generic instance that fails. Thus a later invalid
  specialization points to its own caller, also when an earlier valid specialization shares
  the same template tokens.
- Named function values use the ordinary closure adapter with their own parameter scope.
  Synthesized adapters have no captures of caller locals. Real lambdas keep their capture and
  parameter-shadow checks.
- The growing integrated DB graph reached the former output limit. After this correction it
  passed the source check. This is not a claim of bounded RSS.

## Driver

```sh
gizlang-compiler --check FILE                  # .giz or .gizgraph
gizlang-compiler --explain FILE                # the same, errors as text (explain.giz)
gizlang-compiler --emit-llvm FILE -o OUT.ll
gizlang-compiler --check-package package.json  # run from the package directory
gizlang-compiler --emit-llvm-package package.json -o OUT.ll
gizlang-compiler --emit-llvm-debug FILE -o OUT.ll  # with source places for a debugger (-package too)
gizlang-compiler --project FILE -o OUT.giz     # canonical source projection
gizlang-compiler --inspect FILE -o OUT.json    # entities and dependencies
gizlang-compiler --outline FILE -o OUT.json    # declarations, effects, docs, annotations, hash
gizlang-compiler --outline-package package.json -o OUT.json
gizlang-compiler --edit FILE -o OUT < REQUEST   # edit transaction (--edit-package too)
gizlang-compiler --emit-tool REQUEST -o OUT    # GIZLANG-TOOL-1 request
gizlang-compiler --imports FILE -o OUT.json    # import and re-export specifiers
```

`bin/giz` wraps these modes.

### Debug information

`--emit-llvm-debug` is `--emit-llvm` with the source places as comments. Each function starts with
`; giz.function OFFSET LINE COLUMN NAME`, and each statement has `; giz.at OFFSET LINE COLUMN` before it. OFFSET is in
the prepared input; LINE and COLUMN are in the file. After the module, `; giz.file START END PATH` names each file by its
bytes. A named local or parameter has `; giz.local SLOT LINE COLUMN NAME TYPE` (or `; giz.param`) when it is
bound, and before it each record type it holds has `; giz.record IR-TYPE NAME`, then a tab, a field name and its type
for each field. TYPE is `i64`, `u8`, `f64`, `bool`, `string`, `bytes`, `Vec<T>`, a record's IR type, or `?` for
another type. Other emits write none of them. `giz build --debug` (tools/cli/debuginfo.giz) turns them into DWARF and links
the module whole: the unit split does not carry the metadata.

### Diagnostics and warnings

- A diagnostic is one JSON line on stderr, with exit status 1.
- `--explain` (used by `giz check`) writes the same errors as text. Each error is one line
  `LINE:COLUMN: error CODE in DECLARATION: meaning`, with the reason (`W_DETAIL`: expected and
  actual types, argument counts, a missing case or field), the source line, a caret and a hint.
- Lines count in the checked file. In a package, lines count in the module that contains the
  error, and the module path starts the line.
- A check that passes prints its warnings, if any, on stdout:
  `{"warnings":[{code, stable_id, location, message}]}`. With `--explain`, the warnings are
  text lines with `warning` in place of `error`.
- The only warning that the driver gives is `W_DEPRECATED` (`compiler/doc.giz`). Thus a
  warning never changes a build.
- An error inside embedded SDK code is reported at the call in the checked file that reaches
  it. As text, that line names the SDK function. As JSON, `call_site` is beside the SDK `location`.
- A JSON `location` or `call_site` addresses the prepared input, which starts with the tool header.
  Its `source` gives the span in the checked file: a package module names its path, a single file has the path `""`.
- An error that has one sure fix carries `fix`: `{"location": L, "text": T}`, which replaces the span L with T.
  Today only `E_IMMUTABLE` of an assigned `const` local has one: `let` in place of its `const`. Editors offer it
  as a quick fix.
- If `main` has no parameters, the program gets a process `main` that prints the result:
  `%lld`, `%llu`, `%.17g`, or 0 or 1 for a `bool`.

### Docs and annotations

Docs and annotations are workspace roots (`NODE_DOC`, `NODE_ANNOTATION`). They are before the
declaration that they belong to, and their target is in `binding`. No node word or header
word was added for them.

- Graph v8 imports documentation and nominal annotations into the same roots. This includes
  the field and function `@was` names that Live migration uses.
- Graph v7 keeps its explicit three-field schema and has no metadata.

### Packages

The driver runs the entry two times for a package:

1. `GIZLANG-PACKAGE-PATHS-1` lets `package.giz` parse the manifest and list the selected
   relative paths.
2. The driver opens only those paths, with directory-relative opens that do not follow links
   (no `..`, no symbolic links, no FIFOs). It sends their bytes back as `GIZLANG-PACKAGE-1`.
3. The compiler checks every pin (`E_PACKAGE_INTEGRITY`) before it parses. The package's own
   files may go unpinned; a dependency's manifest and files may not (`E_PACKAGE`).

### Library modules

The toolchain libraries are GizHW (`hw/`), GizML (`ml/`), GizFS (`fs/`), GizDB (`db/`) and GizWeb
(`web/`). Each is a package with its own name: a program imports it by that name, not by a relative path, as it
imports a dependency (`package:module`).

| Import | File |
| --- | --- |
| `gizdb`, `gizweb`, `gizfs` | `db/main.giz`, `web/main.giz`, `fs/main.giz`: the public facade. |
| `gizdb:PATH`, `gizweb:PATH`, `gizfs:PATH`, `gizml:PATH`, `gizhw:PATH` | `db/PATH.giz`, …: for example `gizml:tensor` is `ml/tensor.giz`. |

- `gizlang:NAME` is only the standard library (the SDK). A dependency cannot take a library's name.

- The CLI finds these files when it reads the imports of a script (`sources.giz libraryFile`). It
  gives each one the module name `_gizlang_lib_` + the folder path, with `/` as `__`
  (`libraryModule`; `librarySpecifier` is the way back).
- The compiler resolves a library name to the module of that name (`modules.giz libraryOf`,
  `libraryMatches`). The compiler and the CLI keep the same table of names and folders. A name with no file fails with the path that is missing.
- A library file that imports a sibling by a relative path gets the same module, because a module
  is one file.
- Library modules are not SDK modules. They get no SDK permissions, and they do not count against
  the limit of 62 SDK modules.
- A release ships the libraries as interfaces and objects, not as source
  (`builder.giz libraryArtifacts`):
  1. The builder makes one package for each library, in the order `LIBRARY_ORDER` (hw, ml, fs, db, web): each imports
     only the ones before it. A package holds the library's files and the interfaces of the
     libraries before it, each with its `gizlang:binary` grant, as an installed CLI gives them.
     Each module has the publication fields `["*"]`.
  2. The compiler checks each package once. With the selection `*`,
     `--interface-module-package` gives the interface of each module, and
     `--emit-library-module-package` gives one IR of the library, in which only the exports of
     its modules are global. Each IR is a unit of its own in the units store, so a change to one
     library builds only that library's object (a storage fix: 78 s instead of 264 s).
  3. The interfaces go below `lib/gizlang/libraries`, with each import changed to its
     library name. The objects go into `libgizlang_libraries.a`.
  4. The installed CLI finds `gizdb:PATH` (and the others) below `lib/gizlang/libraries`. It grants each
     interface its `gizlang:binary` functions and links the library archive before the SDK
     archive. The library objects have a section for each function, and the link removes the
     sections that the program does not reach (`--gc-sections`).
- In a publication field, `"*"` approves each declaration of the module that its public API
  needs. It makes no declaration a root of its own. The SDK names each declaration for review.
- Known gap: the wasm target has no library archive.

### Tool requests

A tool request has the form `GIZLANG-TOOL-1\n<operation>\n<payload>`. Tool requests are
read-only. A tool payload cannot request files.

| Operation | Result |
| --- | --- |
| `check`, `explain` | Diagnostics. |
| `project` | Canonical source. |
| `inspect` | Entities and dependencies. |
| `plan <0..4> <stable-id>` | The impact plan for an edit of one graph entity. |
| `regenerate-sdk` | `sdk_sources.giz`, the module names of `sdk/package.json`. The module sources are not in the compiler: the host appends them to each request (the bundle beside the binary, `BINARY.sdk`). |
| `imports` | A JSON array of the import and re-export specifiers, in source order, before resolution. |

`imports` parses source declarations with the same lexer, parser and Unicode string decoder. The native driver gives it
as `--imports FILE -o OUTPUT`. The script traversal of the CLI uses this metadata. It does not interpret source lines.
Empty named imports stay dependencies. Native library declarations are not included.

### Resident check

`gizlang --serve` is one process that keeps the checked program between requests. It reads lines on stdin.
`gizlang --serve-socket PATH PID` returns once it listens on the Unix socket PATH; a process of its own session then takes
connections there, one after the other, with the same requests, until the process PID ends. A fast `live` host uses one
(below).

| Request | Meaning |
| --- | --- |
| `open check\|explain PACKAGE.JSON` | Check the package, as `--check-package` or `--explain-package`. The program stays in the workspace, with the text that the compiler checked (the input and what it added to it). |
| `move PACKAGE.JSON` | The program is now this package. INCREMENTAL is 1 when its manifest is the one checked; else nothing is kept until the next open or edit. |
| `edit RELATIVE-PATH` | The file under the package folder changed on disk. |
| `emit OUTPUT` | Write the kept program's live IR to OUTPUT, the bytes that `--emit-live-package` writes. |
| `quit` | End. |

Each reply is a line `ok|err INCREMENTAL LENGTH` and then LENGTH bytes: the output of the check, or its diagnostic.
INCREMENTAL is 1 when the kept program took the edit.

An edit is a delta after the kept text: the new text of the file. The compiler scans and parses it behind the old tokens
(`compiler/resident.giz`), so every token and node of the program stays where it was. When the file differs from the old
one only in the bodies of functions, those functions take the new bodies and are checked again (`recheckBody`); nothing
else is checked. Any other difference (a header, a type, a declaration added or removed, a generic or native function, a
file that does not scan or parse) leaves the program as it was (`W_DELTA_AT` is -1) and the driver checks the whole
program again. A body that does not check also makes the driver check the whole program (W_DELTA_AT -2), so a diagnostic
is the one of a fresh check. Slots 132 to 134 of the header carry the delta offset and the kept text; slot 135 is
`W_SDK_LOADED_HIGH`.

After an edit, the program is the one a fresh check of the new text makes: the header nodes of an edited function take
the new header's tokens, and the nodes of its old body, and those of the delta that the program did not take, become
scratch, which every pass over the heap skips (`settle`). A unit key counts a node by the bytes of its token, so a
token that a lowering spelled is told by its bytes, not by its number (`units.giz` `place`). String constants are written
in the order of their names. So the resident `emit` writes the same bytes as `--emit-live-package`.

An emit changes the workspace. So a child process of the server runs it, on a copy-on-write copy of the workspace, and
replies; the server keeps the checked program for the next edit. The emit request is `W_DELTA_AT` = -3
(`RESIDENT_EMIT`).

The units a live generation does not emit are the ones that its `// gizlang-cached:` line lists. The lists are no source:
`emit OUTPUT UNITS` names their file, and `--emit-live` reads it from `GIZLANG_LIVE_UNITS`. The driver puts them after
the program, at `W_UNITS_AT`. In the entry, they changed it every generation, so the resident compiler checked the
program whole each time.

A fast `live` host builds each generation through its resident compiler (`tools/cli/compile.giz` `residentEmit`): the
CLI sends `move` with the generation's package, an `edit` for each file that changed since the last generation and for
the entry, and `emit`. When it cannot, the generation is emitted whole, which also explains a failure.

Test: `tests/compiler/resident`.

## Stable names

### Source ranges

The parser records the first and last lexical token of each source declaration in the
numeric index owner (`names.recordSourceRange`, `W_SOURCE_RANGES`).

- Content hashes, meaning hashes and declaration edit spans use that range.
- Lexical tokens added after synthetic documentation tokens do not change the range and
  identity of an unchanged declaration.
- Metadata roots stay separate. Edit spans still include their bound docs and annotations.
- Derived nodes without a parser-recorded source range use the earlier scan.
- Canonical graph JSON has no lexical declaration spans. Graph unit and interface keys
  conservatively include the full tokenized graph. Declaration meanings also include the
  stable IR name.
- White space does not invalidate graph units. Any change of graph content invalidates every
  graph unit. This conservative limit prevents stale linked code and distinguishes empty graph
  functions. It does not claim fine-grained reuse of the graph cache.

### Definition digest

The `definition_digest` of the outline is a conservative 256-bit definition of a declaration
(64 hex digits). It is independent of the local 64-bit cache hints and of the source-edit CAS hash.

- `meaning` shares its dependency discovery and SCC traversal between the two modes.
- Strong mode frames the declaration modifiers, the bound annotation tokens, the body and
  the referenced 256-bit definitions through the `integrity` SHA owner of the compiler.
- The shapes of SCC members set their order. Ties conservatively keep the source order.
- Graph mode frames all graph tokens and a member ordinal.
- Unsupported declaration forms return `null`. When the scratch area is full, the outline is refused.
- The digest is not mathematical equivalence. It is not an artifact signature, a persistent
  layout compatibility, or a distributed Live barrier.

Verification:

- The strengthened definition reaches a new bootstrap fixed point.
- `tools/units` passes the raw SHA oracle of the native transport, lexical formatting,
  consistent renaming, dependency and SCC changes, declaration modifiers, bound annotation
  changes and scratch refusal.
- The `tools/units` scenario also compiles an internal Gizlang range fixture through the
  compiler transport driver. This is a prerequisite of representation only.
- Persistent callers must require exactly 32 digest bytes.

### IR names

IR names do not depend on node addresses (`compiler/names.giz`).

- A function has the name of its module and its name. An instance also has its type
  arguments. A closure has the function that contains it and its capture, parameter and
  result types.
- A type has the name of its layout key.
- Nodes with the same identity are numbered in node order, so names stay unique.
- Equal string constants share one name and one definition.
- Locals (`%p`, `%task`, ...) are numbered in each function, in the order of first use.

Thus, when you add or change one function, the IR of every other function stays
byte-identical. Per-function compile caches depend on this. `tests/tools/stable/run.giz` is
the contract.

## Native and live units

### Constructors

- Record and union constructors consume each owned operand in source evaluation order, before
  later operands can change its origin.
- Partial construction uses the owned result slot. Thus a failure releases every completed field.
- The order of named fields stays the source order, independently of the physical field layout.
- No additional value store or copy rule is added.
- The native constructor regression covers Map snapshots, positional and named records, union
  payloads and interrupted construction.

### Fill loops

The scalar fill lowering recognizes a literal pushed into a new empty Vec in a counted loop
that starts at zero.

- It accepts only a literal or a name as the upper bound, and only one `push` statement.
- Calls in bounds, aliases, computed elements and additional statements keep the ordinary
  loop semantics.
- The literal fill and `filled` share one owner of allocation and lowering.
- `tests/language/control/fill-loop` specifies negative and empty bounds and observable calls in bounds.

### Build model

Every build uses one object store and one rule: an object is compiled once for each (IR bytes, flags, backend,
target), and a build that finds it does not compile it again. The store is `$GIZLANG_CACHE`
(`~/.cache/gizlang` by default). Sessions, slots, CI and release builds on one machine share it.

| Store | Holds | Key |
| --- | --- | --- |
| `units/KEY-sdk.ll` | the IR of one SDK module (`sdkModuleIRs`) | the compiler binary, the SDK manifest, the module and its SDK closure |
| `units/KEY-sdk.o`, `units/KEY-release.o` | the object of one SDK module (`moduleObjects`) | the IR bytes, the optimization, the flags, the backend and the target |
| `units/KEY-O2-TARGET.o` | one ThinLTO part of a large module (`partObjects`) | the part bytes, the flags, the backend and the target |
| `units/KEY-runtime.o`, `runtime/libuv-KEY.a` | one C file of the runtime, and libuv (`bin/giz` `runtime_archive`) | the preprocessed source, the flags and the compiler binary; for libuv, its pin, the flags and the compiler |
| `units/kKEY-COMPILER-O0.o` | one live unit ([Live units](#live-units)) | the unit key |
| `thinlto/` | the code that lld made from ThinLTO parts | the LLVM key of each module and its imports |
| `linked/KEY` | a linked program (`storedLink`) | the bytes of everything that the link reads, and its arguments |
| `compilers/`, `tools/` | the compiler and the toolchain programs ([Shared caches](#shared-caches)) | their sources |

- **Keys hold content, not paths.** A key holds the digest of the backend binary, never the path of a build folder.
  Thus a release that starts in a clean folder uses the objects of the last build.
- **Large modules compile in units.** IR larger than 1 MB is cut at the compiler's unit marks (`partObjects`,
  tools/cli/split.giz). Each unit is compiled at `-O2` with `-flto=thin` and kept in `units/` by its bytes. The linker
  optimizes across the units with ThinLTO and keeps its output in `thinlto/`. One SDK module is up to 21 MB of IR: one
  clang took 36 s for it on 16 cores (221 s on a loaded host), and the units take 14 s. An edit keeps every other unit
  byte-identical, so it compiles only the units that changed.
- **The compiler links in units too.** bin/giz `ir_objects` cuts the compiler's IR (33 MB) with `tools/split`, one
  unit for each function (split.giz EACH). A compiler code change compiles one unit and links in 1.3 s. llvm-split's 32
  parts each imported from all the others, so any change missed the whole ThinLTO cache: a 26 s link.
- **The compiler is its IR.** bin/giz `compiler_ir` hashes `build/gizlang.ll` and the runtime compiler C files.
  The tools and the prebuilt SDK are keyed by it, not by the compiler sources, so a comment change rebuilds neither.
- **An SDK module stays one object.** `moduleObjects` joins the parts of a module with `ld.lld -r` and makes local
  again the symbols that the split made global (`llvm-objcopy --localize-hidden`). Thus each module keeps its
  internal helpers, and an archive of the modules links as before.
- **The SDK emits in groups.** `sdkModuleIRs` gives the modules whose IR is missing to at most `emitJobs` compiler
  processes. Each one checks the SDK once and emits each module of its group (`--emit-library-modules-package`). One
  process per module checked the SDK 49 times: 46 s after a compiler change, 23 s in one process, 3.7 s in five.
  Each process also emits the IR prologue once (llvm.giz `emitPrologue`: the whole-program passes, types, layouts and
  strings, which read no module selection) and then only each module's functions. All 49 modules in one process went
  from 11.6 s to 7.1 s with byte-identical output.
- **A program link is the last step.** `linkParts` (the CLI) and `linkNative` (the release) link the parts of the
  program with the objects of the SDK and the runtime. They keep the result in `linked/`. If nothing that the link
  reads changed, the program is a copy.
- **Three links use the same objects.**
  - `run` and `build` link the program at `-O2`. `--fast` links it with the prebuilt SDK archive and the shared objects of its library units.
  - A release links each toolchain program statically, with ThinLTO and its cache.
  - `giz live` links live units at `-O0` into a shared object for each generation.
- **The runtime library is an archive of stored objects.** `bin/giz runtime-archive CLANG ARCHIVER OUTPUT FLAGS` makes it
  for any target; the host build uses the same function. An unchanged runtime compiles nothing (0.15 s), and an
  edit to one file compiles that file (0.3 s). One serial build of all files took 645 s in a release (2026-10-13).
- **One slot for each build.** A release construction holds one build slot for all of its work. Its compiles and
  its 8 programs run in parallel in that slot, in groups of `buildJobs`.

Known gap: the separate prebuilt archive. The plan is that live units become the only path for SDK code, and that the
prebuilt archive is then removed.

### Live units

The compiler compiles the functions of a live generation in units (`compiler/units.giz`).

- Each function belongs to the unit `hash(name) % parts`. A unit has approximately 32 functions.
- The key of a unit is a hash of:
  - the names and signatures (with inferred effects) of its functions;
  - the source text that they come from (the function of a closure, the template of an instance);
  - the signatures of the functions that they call;
  - the shared interface: native declarations, types, interfaces and constants.
- Thus a signature edit rebuilds the units of the function and of its callers. A body edit
  that changes inferred effects also rebuilds the units of its callers.
- `giz live` lists the keys that it has compiled in a last source line. The compiler then
  emits only a `declare` for the functions of those units.
- `tools/cli/split.giz` splits the IR into the units, a base unit (helpers, entries) and a data
  unit (globals).
- The CLI (`tools/cli/units.giz`, in its own process: only clang and the linker run) uses
  `$GIZLANG_CACHE/units/kKEY-COMPILER-O0.o` again for listed units, and caches the other units
  by content. `$GIZLANG_CACHE` is `~/.cache/gizlang` by default, one store for each machine.
  The key also includes the sources of the compiler and the runtime, and the target (`compiler_id`).

`tests/tools/units/run.giz` is the contract: for all kinds of edits, an unchanged key means
byte-identical IR, and a partial generation emits the same units as a full generation.

### Shared caches

- `bin/giz` keeps bootstrapped compilers in `$GIZLANG_CACHE/compilers/KEY` (the binary and its
  IR). KEY is the hash of the compiler sources, `runtime/glibc235.c` and the clang version. No CPU
  identity enters a key: every build is for the baseline CPU ([the language reference](../reference/language.md)). It does not include the
  seed, because the fixed point makes one compiler from one source.
  A changed compiler source gives a new KEY, so a stored compiler is never used for other sources.
- The compiler sources include `compiler/sdk.pin`, not the SDK. The compiler is built from the SDK
  modules that it imports as that file names them: the git blob of each one at the last
  `bin/giz pin compiler`. Thus an edit to `sdk/` never bootstraps. The compiler reads the
  current SDK beside it (`gizlang.sdk`) for the programs that it compiles. `giz ci` also runs
  `bin/giz bootstrap --current-sdk`, which checks the fixed point against the current SDK.
- It keeps toolchain programs in `$GIZLANG_CACHE/tools/NAME-KEY`. The key is the compiler key,
  the SDK modules that the program imports and the program sources.
- A new build folder copies a stored compiler and CLI instead of a bootstrap and a link. A build
  folder whose compiler or tool is current but not in the store puts it back.
  `bin/giz bootstrap` always bootstraps and checks the fixed point, then stores the result.
- A write goes to a temporary name and is published with one rename. When two writers store the
  same KEY, the first one stays. A compiler is stored only if its sources did not change during
  the bootstrap.
- A hit or a use touches the entry. The CLI evicts, at most once a day, every entry that nobody touched for seven
  days (`tools/cli/files.giz` `pruneArtifacts`), in every cache folder of the store: a file by itself in the folders
  of files, a whole entry in `compilers/`, `tools/` and `runtime/`. An entry is renamed out of the way and then
  removed, so no reader copies half of it. Test data, package sources and the bench store are not caches and keep
  what they hold.
- Test passes proved on a different machine are read from `$GIZLANG_SHARED_CACHE`. This is the
  store of that machine as a folder, or served over HTTP. The CLI never writes there.
- A shared pass skips only a test whose key matches. The key is the meaning of the test, the
  sources of the compiler and the runtime, clang and the target.
- An executable comes from the shared cache only when it is signed (`NAME.sig`, written by a
  machine with `GIZLANG_SIGNING_KEY`) with a key in `GIZLANG_TRUSTED_KEYS`. If not, the CLI
  builds it on this machine.
- The same rule applies to a missing keyed live unit (`units/NAME.o.sig`), before clang
  compiles the units that are still missing.

### Build locks

- Cold executable builds share a native file lock for each content key under `$GIZLANG_CACHE/locks`.
- A caller that waits checks the published executable again. Thus concurrent cold requests
  compile it one time.
- Process exit releases the lock after a failure.
- Empty lock files are not removed by executable eviction. They stay in place, so that
  competing lock inodes cannot occur.
- Warm hits keep their direct path.
- `tests/tools/scripts/cache_build.giz` checks concurrent compilation, changed keys, failed
  builders and repair through the CLI suite. It does not measure link speed, and it adds no
  other executable cache.

## Edits

Agents and tools change a program by declaration, not by text offsets (`compiler/transaction.giz`).

### Outline

`giz outline FILE` (or a `package.json`) prints each declaration on a line: its file and line, its
signature and inferred effects, and its doc summary under it. `giz outline --json FILE` lists every
declaration (`name`, `kind`, byte span, inferred `effects`, `doc`, `annotations`, fields, cases, parameters,
result) and the source `hash`, for scripts.

The pending 2026-10-12 source batch adds checked `files`, `symbols` and `bindings` metadata to
the same outline:

- `declarations`, hashes and transport byte spans keep their edit meaning.
- Each symbol has a file-relative `location` and `name_location`.
- Each binding names a symbol index and its file-relative use span.
- Bindings follow nodes that the checker resolved and generic templates. They never follow
  matches of spelling.
- Only declarations in the supplied source or package transport are shown. Embedded private
  SDK declarations are not shown.
- Unsupported targets have no binding.
- Diagnostic byte locations also include the exact selected source span when it is available.
  Otherwise the span is null.

### Completion

`gizlang --complete FILE -o OUT` (or `--complete-package`) completes a member. The editor writes the
member `__complete__` at the cursor (`p.__complete__`). The check types `p` and stops at that member,
and OUT lists what the type of `p` offers:

```json
{"receiver":"Point","items":[{"label":"x","kind":"field","detail":"i64"},{"label":"norm","kind":"method","detail":"i64"}]}
```

- The fields of a record come first, then the methods of its type that are visible there.
- A Vec also has `length`, its builtins `map`, `push` and `slice`, and the exported functions of
  `gizlang:collections` that take a Vec first (`v.filter(f)`). A string has `length` and the functions of
  `gizlang:text` that take a string first.
- `detail` is the type of a field or the result of a function. A function also has `parameters`
  (`[{"name":"by","type":"i64"}]`), without the receiver; signature help reads them.
- When the check stops before the member, there are no items.

### Hints

`gizlang --hints FILE -o OUT` (or `--hints-package`) gives the type of each local whose type is not
written: `const x = e;`, `let x = e;` and the item of `for (const x of v)`. Each hint is at the name of the
local. When the check stops, the locals that it typed are still listed.

```json
{"hints":[{"location":{"path":"main.giz","start":110,"end":111},"label":": i64"}]}
```

An instance of a generic function repeats its locals, each with its type in that instance.

### Context

`giz context FILE WORD...` gives, in one call, what a change about those words must read:

- In full, every declaration whose name or code contains a word (in an identifier, in any case).
- The first line of each declaration that they use or that uses them.

For example, for depot and a request about holds, the output is 6 KB of the 21 KB program
(`tests/tools/edit`).

### Edit requests

`giz edit FILE` reads one request on stdin:

```json
{"base": "<hash>", "edits": [{"replace": "Point.move", "with": "function Point.move(...) {...}"},
                             {"delete": "unused"}, {"insert": "function added(): i64 { return 1; }"}]}
```

- To name a declaration of another module of the program, use its file relative to FILE, as
  the report names it: `{"replace": "lib/money.giz:Money.add", "with": "..."}`.
- `"from"` puts an insertion at the end of that module, not of FILE:
  `{"insert": "...", "from": "./lib/money.giz"}`. Write the path as an import in FILE writes it.

#### Rename

`{"rename": "total", "to": "totalCents"}` renames a declaration of FILE and every use of it.

- A use is each identifier with the same spelling. Member and method names after a dot, and
  the field names of types, are not uses.
- The new name must not occur anywhere in FILE. Thus nothing is captured, and the program has
  the same meaning.
- A request that renames holds only renames. Each rename starts from the base, and no new name
  is used two times.
- To rename a method, replace it.

`{"rename": "Counter.label", "to": "title"}` renames a record field by the checked types, not
by the spelling. It changes the declaration, every member bound to it, and the field names of
its object literals. The `label` fields of other types do not change. The doc of the field
gets `@was label`, so live state keeps the value ([SYSTEM](system.md#live-state)).

#### Move

`{"move": "Money", "to": "./money.giz"}` moves a declaration (a type with its methods) to a
different module of the program. `"from"` names its module when it is not FILE. Write both as
an import in FILE writes them.

- The text and docs of the declaration move to the end of the new module.
- What it uses from its old module is exported there and imported into the new module.
- Every importer, also the old module, imports it from the new module. A namespace use
  `old.Money` becomes `Money`.
- It is exported when another module uses it.
- A name that the new module already binds refuses the move (`E_NAME`). A re-export of the old
  module also refuses it (`E_MODULE`).
- A request holds one move.

#### Modernize

`{"modernize": ""}` rewrites old spellings to the modern ones in FILE and every module of its
program. The checker records a rewrite only where it proves that both spellings have the same
types and meaning:

| Old | Modern | Where |
| --- | --- | --- |
| `Option.some(x)` | `x` (`({ ... })` after `=>`) | an `Option` is expected; an object literal with no `kind` |
| `Option.none()` | `undefined` | an `Option` is expected |
| `let o = Option.none<T>()` | `let o: T \| undefined = undefined` | a local with no written type |
| `join(v, ",")`, `Text.trim(s)` | `v.join(",")`, `s.trim()` | a prelude function that is a method of the first argument's type |
| `newMap<K, V>()`, `ns.newX()` | `new Map<K, V>()`, `new ns.X()` | a constructor function of a type that the call site names |
| `v.sorted(f)` | `v.toSorted(f)` | the call reaches the prelude function by its former name (modern.giz `renamed`) |
| `x.unwrap()` | `x!`, or `x` where `x !== undefined` holds | `x` is an `Option` or a `Result` (`x` alone: an `Option`) |
| `x.isSome()` | `x !== undefined` | `x` is an `Option` |
| `return x;` after `while (true) {...}` or a return | nothing | the function has a written type |
| `x.kind === "some"`, `!x.isSome()` | `x !== undefined`, `x === undefined` | `x` is an `Option` |
| `(i: Item): bool => i.ok` | `i => i.ok` | the call or the expected type gives the parameter types (pass `{"modernize": "lambdas"}`) |
| `"n=" + string(n)`, `` `a` + string(n) `` | `` `n=${n}` ``, `` `a${n}` `` | a string `+` chain with text, a template or `string(x)`, and values |
| `string(n)`, `string(s)` | `` `${n}` ``, `s` | `n` a number or bool, `s` a string; `string(bytes)` (a UTF-8 decode) stays |
| `match (e) { Option.some(x) => {A} Option.none() => {B} }` | `const x = e; if (x !== undefined) {A} else {B}` | `e` is an `Option`; `if (e !== undefined)` when A does not read `x`, which no other token of the function may spell; an empty arm goes |

- An SDK import (`gizlang:...`) or a prelude name that the program no longer uses is removed. A relative
  import stays, as other code can need the methods of its module. `{"modernize": "sdk"}` (`giz fmt` sends it
  for a module of the SDK, under the package.json of `@gizlang/sdk`) keeps all imports: the SDK builds its modules
  without the prelude.
- An edit inside another edit waits for the next request. `giz fmt` sends the request up
  to 6 times, until nothing changes.
- An edit with `"from"` (a unit path) rewrites only that file; several such edits name several files. `giz fmt`
  names its files, one request per program and round: a module they import is not rewritten (it is its own run).
- The request holds no other edit. The base may be empty. `{"modernize": "lambdas"}` makes only the lambda
  rewrites, so a lambda whose types do not come back without its annotations keeps the other rewrites of its file;
  `giz fmt` sends it after the other rounds.
- `{"modernize": "bytes"}` (`giz fmt --bytes`) makes only the byte-name rewrites of a string's members: `s.length`,
  `s[i]`, `s.slice(a, b)`, `s.indexOf(x)` and `s.lastIndexOf(x)` become `s.byteLength`, `s.byteAt(i)`,
  `s.byteSlice(a, b)`, `s.byteIndexOf(x)` and `s.byteLastIndexOf(x)`. The other passes make none of them.
- A module whose rewritten program fails the check keeps its old text; the others change.

#### Transaction rules


The request is one transaction over the whole program of FILE: FILE and every module that it
imports (the relative imports of a script, or the modules of a package).

1. The request applies only to the program that its `base` names. Otherwise the result is `E_CONFLICT`.
2. Each name must identify exactly one declaration (`E_NAME`).
3. The edited program must pass the full check: types, effects, ownership and contracts.
4. Each edit is a set of splices of the base. In a package, the compiler frames and pins each
   changed file again. Thus it checks the edited program exactly as its files will be read.
5. A rejection changes nothing. The `stable_id` of its diagnostic names the declaration at fault.
6. On success, each changed file is replaced. This occurs only if no file of the program
   changed in the meantime. The pins of a package follow.

The report lists:

- the `changed`, `added` and `removed` declarations of the program;
- every function whose effect row changed (`before` and `after`);
- the `affected` functions: the changed functions and their callers, transitively;
- the `tests` among them, which `giz test FILE` runs again;
- the `files` written.

`tests/tools/edit/run.giz` is the contract.

## Bootstrap

`bootstrap/compiler.ll.xz` is the IR of the compiler, compiled by itself. `giz bootstrap`
does these steps:

1. It builds the seed.
2. It compiles `compiler/` with the seed (stage1).
3. It compiles `compiler/` with stage1 (stage2).
4. It requires that the IR of stage1 and stage2 is the same.

A change to code generation shows first in stage2. In that case, the bootstrap builds stage3
with stage2, and requires that the IR of stage2 and stage3 is the same.

### Procedure to change the compiler

1. Edit `compiler/*.giz`. A new file also goes in `compiler/package.json` (its path; no sha256).
2. Run `bin/giz bootstrap && bin/giz test`.
3. Commit the sources. Run `bin/giz pin compiler` first only to build the compiler from the current SDK
   (it rewrites `compiler/sdk.pin`).

Do not commit a new seed with each change. Any seed that compiles the sources bootstraps them
to a fixed point. Commit `bin/giz seed` output only when the committed seed cannot
bootstrap the sources (refer to [New language features](#new-language-features)).

### Local loop and CI

The local loop is only the procedure above (nothing that is not necessary
before a push).

CI (`bin/giz ci`, ours: a fresh clone of each new `main`) runs every check:
the live, edit, stable, unit and fault suites, the reference goldens, and the regression and
agent benchmarks. Run one of those locally only when the change is in its area. While you
work, use `bin/giz test NAME` for only the cases that a change touches.

`bin/giz test` runs cases and suites in sequence at -O0. Each build owns its backend
parallelism:

- Backend workers and ThinLTO jobs use the lower of `Task.cores()` and one worker for each
  two GiB of half the observed available memory.
- If the memory capacity is unknown, one worker is admitted.
- `System.availableMemory()` owns the physical headroom of Linux, including every visible
  cgroup ancestor. This observation decreases concurrency. It is not a reservation.
- `bench/perf` measures optimized code.
- `test --shard I/N` distributes independent tests across machines.

### CI resource limits

The execution host requires a systemd user manager. CI owns `gizlang-ci.slice` on that host:

| Limit | Value |
| --- | --- |
| Hard memory | 8 GiB |
| Memory pressure threshold | 6 GiB |
| Swap | 0 |
| Tasks | 128 |
| CPU | 2 CPU equivalents |

- Bootstrap, dependency installation, test builds and delegated suite scopes all join this slice.
- CI sets `GIZLANG_THREADS=1`.
- `$GIZLANG_CI/runner.lock` serializes coordinator runs. On the execution host,
  `$XDG_RUNTIME_DIR/gizlang-ci-admission.lock` protects the slice.
- CI runs where builds and tests run. The coordinator writes a temporary slots file with only the
  remote `build` hosts of `GIZLANG_SLOTS_CONFIG`. The slot scheduler
  selects the host with the most free slots and memory, and waits while all are full.
- Only when the file has no remote `build` host does CI run locally. Then the coordinator checks
  local physical headroom against the 8 GiB budget plus a 4 GiB reserve, and refuses with exit
  status 3 if the headroom is low or unknown.
- The coordinator sends a temporary Gizlang suite through `bin/giz test`. The suite and its
  copy of the coordinator are in `tests/ci-execution`, not in `tools/cli`: the launcher builds
  `tools/cli`, and a second `ci.giz` there fails it. The suite removes its driver files, then runs
  bootstrap, dependency installation and all tests. Nested builds keep the selected slot.
- A local execution host checks headroom again before it installs the slice limits.
  Remote slots use the bounded scope and the fleet headroom policy. CI does not compare the
  remaining capacity of their own 8 GiB scope with a second 12 GiB admission requirement.
- The coordinator stays outside the execution slice. It records an admission refusal,
  a signal, or an infrastructure exit as `error`. The reason names the step. A headroom
  refusal includes available, budget, reserve and required bytes. Test failures remain `fail`.
- The slot wait and execution have a one-hour deadline. `timeout` returns exit status 124
  at the deadline. It sends SIGKILL after a further 30 seconds if the command does not stop.
- Each attempt has one row in `results/index.tsv`. CI publishes an `error` row before each
  coordinator step, then replaces that row with the observed result. If the coordinator itself
  is killed, the row keeps the last step and states that its signal or exit code is unavailable.
  A process cannot observe its own SIGKILL.
- `ci next` uses the last indexed verdict. It retries an `error` at most twice, for three
  attempts in total. The row stays `error` after that limit. It does not retry `pass` or `fail`.
  After main has a terminal result, it retries older errors within the same bound.
  `ci run COMMIT` permits an explicit further attempt. A log file alone does not stop a retry.
- `ci status` prints the rows and their reasons. Each row also has an attempt identity.
  An infrastructure error does not update `results/full.time`.
- The result log contains launcher output. A failed temporary suite also exposes step output.
  A successful temporary suite exposes its summary.
- Other workloads can change after admission. The slice limits the share of CI, but it does
  not keep memory for unrelated services.
- A slice OOM records an error. When CI stops the slice that it owns, the remaining descendants stop.

### New language features

A new language feature that the compiler itself will use takes two commits:

1. The first commit teaches the feature to the compiler without use of it, and stores a new seed.
2. The second commit uses it.

Module constants came into the compiler in this way. The second commit replaced the magic
numbers of the compiler with `layout.giz` names. Its IR was identical to the IR of the
previous compiler.

IR names are stable ([IR names](#ir-names)). Thus, to prove that a refactor changes nothing,
compare the IR of the two compilers.

## Tests

Each golden case has an `.expected` file. A golden case is a `.giz` file, a package folder or a
`.gizgraph` file anywhere under `tests/` ([tests/README](../../tests/README.md)). The `.expected`
file contains `ok`, `error CODE`, the output of the program, or `runtime RNNN`.

`bin/giz test` compiles, links, runs and compares each case. It also runs every example,
and checks that `sdk_sources.giz` agrees with `sdk/package.json`.

### Result reuse

- Only cases and test functions that the compiler certifies as pure can use a passing result
  again. The reuse key includes the compiler, the runtime, the sources and the golden.
  Artifact reuse also includes the emitted IR and the link recipe.
- Cases and test functions with effects run again.
- Every scenario suite (a folder of `tests/` with `run.giz`) runs on each selected run.
- A repository read set does not prove that the environment, hardware, external services,
  cgroups or clocks are unchanged.
- Earlier cache timings do not certify this implementation.

### Sharding and optimized links

- `giz test --shard I/N` runs every Nth case and suite, from the Ith. Several machines can
  share a run. The wall time is then one bootstrap plus the slowest shard.
- An -O2 link divides IR larger than 8 MiB into parts of approximately 8 MiB, to a maximum of
  32 parts. Smaller IR uses one `clang -O2` (`tools/cli/toolchain.giz`).
- CPU admission independently limits concurrent optimizer children and ThinLTO jobs, also for
  a single worker. The LLVM splitter sets the actual balance of the parts.
- This is a source policy.

### Ownership of link and file operations

- `tools/cli/toolchain.giz` owns the link policy for native programs, shared generations and Wasm,
  including split objects; `tools/cli/levels.giz` owns the CPU level copies of vector functions.
- `bin/giz` keeps only the prerequisite seed bootstrap to a fixed point, the build of the
  native C and libuv runtime, and the link of the first CLI executable.
- Steady CLI file operations use the bounded copy, tree and atomic publication owners of the SDK.
- HTTP downloads use the URL and streaming HTTP codecs of the SDK.
- Package discovery and pinning use directory capabilities. Worker counts use `Task.cores`.
  CI timestamps use `Time.iso`.
- The compression of the seed still runs the external `xz` tool, because the SDK has no XZ
  encoder. Its output uses the same staged publisher.

### How goldens are written

- Write a golden by hand from the specification. Check it against the native run.
- Each `tests/language/graphs/NAME.giz` keeps its committed canonical `NAME.gizgraph`.
- Each case also has a second route to its golden, with no second implementation to maintain:
  its canonical projection must be a fixed point and print the golden at -O2.
  Thus this check is sharded and cached with the case.

## Canonical graph

`.gizgraph` files are versioned JSON: `gizlang.graph.v7` or `gizlang.graph.v8`.

- Every declaration and node has a stable ID. Edges are `{"$node": id}`.
- Roots are `fn`, `record`, `variant`, `generic`, `protocol`, `module` and `const`.
- A constant use is `{"kind": "name", "data": {"const": id}}`.
- The compiler reads graphs as well as source. `tests/language/graphs` holds its fixtures.

### Graph v8 metadata

v8 adds one required top-level `metadata` array, beside `format`, `nodes` and `roots`.

- Each ordered entry has `target` (a stable graph ID), an optional `doc` (normalized
  documentation text) and `annotations` (an array).
- An annotation has `type` (`{"$type": record-id}`) and `arguments` (ordinary literal graph edges).
- Targets are declarations, fields, union cases or interface methods. Metadata cannot bind to
  other executable nodes.
- The shared checker validates documentation tags, the nominal types of annotations, literal
  kinds and argument count.
- Unknown targets, duplicate docs and extra schema fields fail explicitly.
- The importer decodes only documentation literals, through the shared Unicode codec, into
  bounded token backing bytes. It does not parse generated program text.
- Projection, outline and Live use the metadata roots and keep `@was` origins.

The compiler has no public exporter from source to graph. The supported graph routes are
committed graphs and the projection from graph to source.
