x-lang

Bundle checklist

Recurring defects in lang bundles and their tools. Each entry names the defect, how to check a bundle for it, and the remedy. An entry tied to a release says which; it can go once every bundle declares a later one. The build itself is described in Crafting a Lang, and what a bundle may rely on in The Lang Contract.

Garbage collection and memory

G1. Collection is the application’s

x collects only when asked. A bundle that drives its own loop (a game, a REPL, a line filter) places its own (Heap collect).

G2. A spec file split to fit memory

Splitting a spec file lowers its peak without lowering what the code leaks.

G3. An object kept only in a second base

An object made on this heap and stored only inside another base (Base make, make-tok, a base cell) is freed by the collect after next. The image writer’s child collects after every include, so it shows there first.

G4. More slots than set-units!

A %make-obj TYPE n with more slots than the type’s set-units! leaves the extra slots unmarked: a use-after-free that waits for a collect.

G5. Allocation in a mark hook

A Heap mark-hook! body that allocates crashes the collector.

G6. Continuations outside the object ceiling

A call/cc stack copy is malloc’d, so X_ALLOC_LIMIT_OBJS does not count it; a generator loop can reach gigabytes under the ceiling.

G7. A large source file’s load garbage

A file’s load garbage lives until the file ends: a large evaluator file can take a load from 0.3M to over 20M objects, and an image write to several gigabytes.

G8. A runner knob that outlives its cause

SPEC_SEAM_COLLECT=0, SPEC_BATCH and X_ALLOC_LIMIT_OBJS in a bundle’s tests/spec-runner.sh each answer one defect. Once the declared release fixes it, the knob only costs memory: x-python’s peak fell from 5.9 to 3.2 GB with seam collects back on.

G9. An object ceiling above the runner’s memory

The default ceiling, 300M objects (about 14 GB), is close to a 16 GB hosted runner. A job past the runner’s memory dies with no tally: spec-gate: killed by SIGTERM, or exit 143.

G10. Measurement that allocates

(Heap count) walks the heap (about 1.2 s at 72M objects); each prim-ref call makes about 20 KB.

State images and CI

I1. A bundle without CI

A bundle with no CI workflow is checked only by whoever runs its suite last, on one machine and one release; its pull requests show no checks.

I2. The suite boots from source on CI

Bundle CI runs an x-lang checkout (.x-lang/x.sh). A checkout’s x.sh reads lib/ from the current directory, so a bundle script that runs "$X" from the bundle directory works with an installed x and fails on CI. When that script is an image probe, the suite falls back to booting from source.

I3. A probe every declared release passes

A capability probe for a fix the declared release already carries can only fail for another reason, and its message then names the wrong one.

I4. Runner logic copied across bundles

A block copied into many bundles’ tests/spec-runner.sh carries one defect into all of them.

I5. A stale image after an edit

x -l NAME boots from an image keyed by the bundle’s path, not its contents. The spec runner’s image is keyed on contents.

I6. Modules the image key does not hash

The image key hashes .x files; modules with another extension (x-r5rs’s .scm) do not rewrite the image.

I7. NUL bytes in a module-level buffer

The image writer copies strings to their first NUL, so a module-level buffer holding a NUL comes back short.

I8. Per-process state fixed at load

A second base, a session, $$, colour or a signal handler fixed at load is wrong in every process that loads the image.

I9. A missing import the specs cannot see

The spec harness loads the whole platform and the kit lint preloads sibling modules, so a module with a missing import passes every spec and fails from its image or in x-os.

I10. A pin moved past an untested release

Specs and the runner

S1. A spec file past the CI time limit

A job’s time limit is TIMEOUT_UNIT_SECS times the files in its bucket (one file with SPEC_BATCH=1, scaled by @timeout-scale), and a hosted Linux runner is about twice as slow as an M-series Mac.

S2. “interpreter died mid-batch”

S3. Globals shared between spec files

A batch runs several files in one process, and a top-level def in a spec rebinds a root global (lit, %base, an applet name). The symptom is Error: () or a 139 far from the cause.

S4. A spec run that runs nothing

A spec file passed through SPEC_PATH (a directory) runs 0 tests and passes. “0 tests, 0 failed” is a non-run.

S5. A heavy file in a bucket

An undeclared heavy file pushes its neighbours past the bucket’s timeout.

Code

Performance