x-lang

Bootstrap tool closure

The self-hosting scorecard: every external tool the build system actually invokes, measured rather than grepped. The long-term goal is that each row below is either implemented in x, absorbed as an x-coreutils applet, or explicitly ruled a permanent external.

Progress (2026-09-16)

Measured fresh on this date. 37 of the 45 rows have a registered, spec-checked implementation in x, and they carry 98.5% of the logged invocations (20,096 of 20,398):

Pipelines of x tools compose today: ... | x -l awk '{print $1}' | x -l coreutils -- sort | x -l coreutils -- uniq -c.

Still external — 8 rows, 302 invocations: cc (the JIT’s compiler tier), shasum, codesign/sysctl (platform, permanent), git/curl (fetch, out of scope), tar, strip.

Implementation is not adoption. The figure above says the tool exists in x and its suite passes; it does not say the build calls it. The build still invokes the system binaries throughout — this measurement counts the system tool at every one of those rows.

Adoption is not uniformly available, either. A call site is post-x when x is built and runnable by the time it fires; those can take an applet, and the work is the call site alone. A call site is pre-x when it runs before there is an x to call — the wrapper’s own boot guard, engine acquisition, the image-directory name computed ahead of require_engine. Those cannot switch at all. Among the integrity checks the barrier is not ordering but meaning: a digest that decides whether to trust x is worth nothing if x computes it. tools/engine/engine.pin.xon states the same rule for itself — it is “read by a shell script that runs BEFORE any engine exists and so cannot use x to parse it.”

Closure therefore has a floor above zero, and 45 of 45 was never the target.

What moved since 2026-08-31

The first measurement ran against a tree that carried the engine as a submodule and compiled it. The engine is now a separate project, acquired rather than carried, and tools/engine/engine.pin.xon names a prebuilt artifact for both platforms CI runs (darwin arm64, linux x86-64). The build rule takes the source branch only when the engine directory has a Makefile:

if [ -f $(ENGINE_DIR)/Makefile ]; then $(MAKE) -C $(ENGINE_DIR); ...

With a prebuilt artifact linked, it does not, and make compiles nothing. Measured: zero invocations of cc, clang, ld, as, strip or codesign across the whole build phase. The compiler tier did not get cheaper; it moved to the engine’s own repo and CI.

cc has not left the closure, though. It reappears in the test phase, 127 invocations, where the JIT’s cc-hosted tier compiles at runtime — the middle rung of the asm → cc-hosted → twins ladder. That is a different thing from a build dependency, and the two should not be read as one row.

Other movement: the spec runner’s NUL escaper is new, stat and touch have dropped out, find went from 22 invocations to 315, and the crypto pair doubled to 330.

Union: 45 tools, ranked by invocation count

Measured 2026-09-16 on macOS (Darwin 25.6.0), 4,455 logging wrappers, three phases. “in x” marks a row with a registered implementation — not one the build calls.

count tool phase(s) in x
4976 grep test x
3307 mv test x (but see caveats — this row is noise)
2380 sed build, test, install x
2024 awk test, install x
1631 sort test, install x
837 cat test, install x
696 tr test, install x
675 dirname test, install x
432 join test x
324 date test x
315 find test, install x
299 rm test, install x
292 head build, test, install x
283 cut test, install x
278 basename build, test, install x
255 sh build, test, install x
181 mkdir test, install x
170 sha256sum test, install x
165 timeout test x
162 perl test x (the NUL escaper, now tools/dev/nul-escape.x)
160 shasum test, install external
127 cc test external
104 xargs test x
85 comm test x
60 mktemp test x
33 readlink build, test, install x
26 install install x
25 diff test, install x
21 uname test x
14 wc test, install x
10 ln build, test, install x
10 cp build, test, install x
8 which test x
4 uniq test x
4 make build, test, install x
4 git test, install external
4 curl test external
4 cmp build, test, install x
3 tar test external
3 ls test, install x
2 sysctl test external
2 nproc test x
1 strip install external
1 fold test x
1 codesign install external

Per phase: build 9 tools / 23 invocations; test 42 / 19,934 (3143 tests, 0 failed); install 27 / 441.

Reading it by self-hosting tier

Caveats

Reproducing

For every executable in every PATH directory, write a wrapper into a shim directory (first PATH hit wins) that logs its own name and execs the real binary:

printf '#!/bin/sh\necho %s >> %s\nexec %s "$@"\n' "$name" "$log" "$real"

Then run each phase with the shim directory prepended to PATH, snapshotting the log between phases:

  1. rm -f x-bin && make — the build. (With an engine checkout rather than an artifact, touch one of its src/*.c first to force compile + link + sign.)
  2. make test-fast
  3. make install DESTDIR=<sandbox>

sort | uniq -c | sort -rn over the combined logs yields the table above. Empty /tmp/x-asm-* first, or the mv row measures the cache rather than the build. Re-measure after adding or retiring a tool, and when widening coverage to the full make test and release targets.