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.
x collects only when asked. A bundle that drives its own loop (a game, a
REPL, a line filter) places its own (Heap collect).
Heap collect. Run a long session and watch the footprint. Two
(Heap count) readings with no collect between them give the objects one
step makes.%cu-sweep-at
sweeps every 2048 bytes or 64 lines). A collect costs about 0.1 s in a spec
process and about 1.6 s at 39M live objects, so a collect per key or per
short line costs more than the work.Splitting a spec file lowers its peak without lowering what the code leaks.
/usr/bin/time -l on
macOS, /usr/bin/time -v on Linux). A peak in gigabytes for a short file
points at the code under test.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.
Base make, make-tok, base-bind, and %set-first!
into a base’s cell. A spec that runs two (Heap collect)s and then reads
through the base.(Heap pin! obj), which answers nil, or a copy on this heap
(x-ash’s %sh-tok-own).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.
%make-obj slot counts against its set-units!.
The symptom is a nil inside a list the code has just built.set-units! in the same edit that adds the slot.A Heap mark-hook! body that allocates crashes the collector.
mark-hook! and read each hook body.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.
call/cc.(Cont capture f); two or three generator-heavy cases a spec
file.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.
x --image -l NAME.x/boot/tower-compiled for compiled reader analysers.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.
tests/spec-runner.sh for each knob and its comment’s
issue number; read whether the issue is closed in the declared release.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.
X_ALLOC_LIMIT_OBJS; each raise is scoped to
the one path that needs it.(Heap count) walks the heap (about 1.2 s at 72M objects); each prim-ref
call makes about 20 KB.
prim-ref inside fn bodies and loops.prim-ref once at load; for a cheap object count,
(%reflect-base-cell (lit alloc-count)).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.
gh api repos/OWNER/REPO/contents/.github/workflows answers
404, or no workflow calls x-lang’s bundle.yml..github/workflows/ci.yml calling
jonruttan/x-lang/.github/workflows/bundle.yml@main on push to main, pull
requests and a weekly schedule. It runs tests/spec-gate.sh against the
declared release, and against main except on a pull request, on Linux
x86-64, Linux arm64 and macOS unless the os input names other runners.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.
image-build: writing and no “boots from
source” line. In the bundle, every "$X" run outside the platform runner.(cd "$("$X" --share-dir)" && "$X" -f FILE), and print a probe’s stderr
when it fails.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.
tests/, the release that brought the fix,
against (requires-release ...) in lang.xon.A block copied into many bundles’ tests/spec-runner.sh carries one defect
into all of them.
md5 the same block across the bundles’ runners.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.
X_LANG_DIR.~/.cache/x/images/.The image key hashes .x files; modules with another extension (x-r5rs’s
.scm) do not rewrite the image.
IMG_KEY_EXT in
tests/spec-runner.sh.IMG_KEY_EXT.The image writer copies strings to their first NUL, so a module-level buffer holding a NUL comes back short.
%make-str, Bytes of-hex with 00,
Bytes of-list (0. A test that fails from the image and passes with
IMG=0.A second base, a session, $$, colour or a signal handler fixed at load is
wrong in every process that loads the image.
unnameable: 0; X_IMG_WHO=1 names the
holders.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.
x -l NAME ...) or in
docker.io/jonruttan/x-os:latest.(requires-release ...) moves; for
a failure on the main leg only, git log v<declared>..origin/main --
tools/lang-kit.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.
ulimit -s 65520. Remedy: a tail shape.allocation limit exceeded is a runaway loop.Unterminated input is a source file rewritten during the run.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.
grep -E '\(def NAME( |$)' over the bundle and lib/;
IMG=0 SPEC_BATCH=8 reproduces a CI bucket.%SUBJECT-name; each case sets what it
reads.A spec file passed through SPEC_PATH (a directory) runs 0 tests and
passes. “0 tests, 0 failed” is a non-run.
An undeclared heavy file pushes its neighbours past the bucket’s timeout.
# @weight N and # @timeout-scale N on the heavy file.(first ()) and (rest ()): the globals are guarded from
x-lang#1135, but a load-time alias taken into an image is not. Test
null? on argv and split results first.eq? on vectors is neither identity nor structure: same?.Str8 sub takes a start and a length. helium has no int?,
abs, min or max.match clause holds one form; further forms are dropped./dev/zero.$(shell, ifeq or $(CURDIR); values
computed at recipe time with $$(...).match 0, if 10, let 120, cond 140; match
for four or more arms.(Sys environ) once a process.List fold or reverse entry is the dear part of a short walk; a
local accumulating walk is about 10x faster (x-python#79).Vector make is about 390 objects a slot; Str8 split makes about
0.5 MB a 160-byte line (prefilter with index-of).Assoc find about
25,000: hot slots through obj-ref, lookups as plain walks.