diff --git a/.github/workflows/build-binaries.yml b/.github/workflows/build-binaries.yml
index d6f90143..e4210f3c 100644
--- a/.github/workflows/build-binaries.yml
+++ b/.github/workflows/build-binaries.yml
@@ -4,17 +4,25 @@
# Each platform builds from its OWN committed seed (boot/seed_mac / boot/seed_linux), so there is no
# cross-emit and no foreign toolchain — only the system `cc` and the vendored qbe:
# darwin-arm64 → macOS runner, native `build.sh`
+# darwin-amd64 → Intel macOS runner (macos-13), native `build.sh darwin-amd64` (shares the mac seed
+# IL with arm64 — the amd64 `syscall` class bit lives in the floor, not the IL)
# linux-arm64 → ubuntu-arm runner, native `build.sh linux-arm64`
+# linux-amd64 → ubuntu x86-64 runner, native `build.sh linux-amd64` (its OWN seed IL — x86-64's
+# linux syscall numbers + struct-stat layout differ from the generic arm64/riscv table)
# linux-riscv64 → ubuntu-arm runner, `build.sh linux-riscv64` cross-linked with the distro's
# gcc-riscv64-linux-gnu (shared linux IL, riscv floor)
# Every binary is stamped with the release version via lib/std/compiler/buildinfo.cf, so it reports
# itself through `std::comptime::version::compiler`.
#
-# STATUS: all three targets validated. darwin byte-reproduces the seed. Both linux targets are static
-# musl (the floor calls musl's `__init_libc`): linux-arm64 built + ran natively on a real aarch64 Linux
-# VM; linux-riscv64 cross-built (musl.cc toolchain) + ran under qemu-riscv64 — cf compiles+runs programs
-# and its embedded QBE works under the custom `_start` on both. The riscv leg downloads its musl cross
-# from musl.cc (the one external fetch here).
+# STATUS: darwin-arm64/darwin-amd64/linux-arm64/linux-amd64/linux-riscv64 validated. darwin
+# byte-reproduces the seed (arm64 natively; amd64 under Rosetta 2 — arm64/amd64 emit identical IL). The
+# linux targets are static musl (the floor calls musl's `__init_libc`): linux-arm64 built + ran natively
+# on a real aarch64 Linux VM; linux-amd64 built + ran under qemu-x86_64 (its own seed IL, distinct x86-64
+# syscall numbers); linux-riscv64 cross-built (musl cross toolchain) + ran under qemu-riscv64 — cf
+# compiles+runs programs and its embedded QBE works under the custom `_start` on all. The riscv leg
+# downloads its
+# musl cross toolchain from the cross-tools/musl-cross GitHub release (a reliable GitHub-hosted mirror;
+# the old musl.cc host chronically timed out from the runners), pinned by tag + verified by sha256.
name: build-binaries
on:
@@ -44,10 +52,18 @@ jobs:
runner: macos-14
target: darwin-arm64
experimental: false
+ - name: darwin-amd64
+ runner: macos-13 # macos-13 is the last Intel x86-64 image; macos-14+ are arm64
+ target: darwin-amd64
+ experimental: false
- name: linux-arm64
runner: ubuntu-24.04-arm
target: linux-arm64
experimental: false
+ - name: linux-amd64
+ runner: ubuntu-24.04 # GitHub's default x86-64 runner — native, no VM/qemu/cross
+ target: linux-amd64
+ experimental: false
- name: linux-riscv64
runner: ubuntu-24.04-arm
target: linux-riscv64
@@ -75,44 +91,40 @@ jobs:
if: runner.os == 'Linux'
run: sudo apt-get update && sudo apt-get install -y build-essential musl-tools
- # Cache the extracted toolchain (a static, versioned tarball) so we hit
- # the flaky musl.cc host at most once. Kept under $HOME so actions/cache
- # can read/restore it as the non-root runner user (no sudo, no /opt).
+ # Cache the extracted toolchain (a static, versioned tarball) so we fetch
+ # the release asset at most once. Kept under $HOME so actions/cache can
+ # read/restore it as the non-root runner user (no sudo, no /opt).
- name: Cache the riscv64 musl cross toolchain
id: riscv-toolchain
if: matrix.target == 'linux-riscv64'
uses: actions/cache@v4
with:
- path: ~/riscv64-linux-musl-cross
- key: riscv64-linux-musl-cross-20211123
+ path: ~/riscv64-unknown-linux-musl
+ key: riscv64-unknown-linux-musl-cross-20260823
- name: Download the riscv64 musl cross toolchain
if: matrix.target == 'linux-riscv64' && steps.riscv-toolchain.outputs.cache-hit != 'true'
run: |
- tgz=riscv64-linux-musl-cross.tgz
- # musl.cc is a single flaky host; download to a file (never pipe a
- # possibly-truncated stream into tar), retry, then fall back to the
- # official more.musl.cc mirror.
- ok=
- for url in \
- https://musl.cc/$tgz \
- https://more.musl.cc/x86_64-linux-musl/$tgz; do
- echo "Fetching $url"
- if curl -fL --connect-timeout 30 --retry 5 --retry-delay 10 \
- --retry-all-errors -o "$tgz" "$url"; then
- ok=1; break
- fi
- echo " failed, trying next mirror"
- done
- [ -n "$ok" ] || { echo "all musl.cc mirrors unreachable"; exit 1; }
- tar xzf "$tgz" -C "$HOME"
- rm -f "$tgz"
+ # Pinned cross-tools/musl-cross release — a reliable GitHub-hosted
+ # mirror (the old musl.cc host chronically timed out from the runners).
+ # Download to a file (never pipe a possibly-truncated stream into tar),
+ # retry, then verify against the release's published sha256.
+ tag=20260823
+ txz=riscv64-unknown-linux-musl.tar.xz
+ base=https://github.com/cross-tools/musl-cross/releases/download/$tag
+ sha=aa6630f73487e5d3eb6ffa861199c3d63dfe0db8af77452581628cd762c9a64d
+ curl -fL --connect-timeout 30 --retry 5 --retry-delay 10 \
+ --retry-all-errors -o "$txz" "$base/$txz"
+ echo "$sha $txz" | sha256sum -c -
+ tar xJf "$txz" -C "$HOME"
+ rm -f "$txz"
- name: Add the riscv64 toolchain to PATH
if: matrix.target == 'linux-riscv64'
run: |
- echo "$HOME/riscv64-linux-musl-cross/bin" >> "$GITHUB_PATH"
- echo "RISCV_CC=riscv64-linux-musl-gcc" >> "$GITHUB_ENV"
+ echo "$HOME/riscv64-unknown-linux-musl/bin" >> "$GITHUB_PATH"
+ echo "RISCV_CC=riscv64-unknown-linux-musl-gcc" >> "$GITHUB_ENV"
+ echo "RISCV_STRIP=riscv64-unknown-linux-musl-strip" >> "$GITHUB_ENV"
- name: Build
env:
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 716d2b9c..1f3df9c8 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -21,6 +21,8 @@ jobs:
outputs:
version: ${{ steps.compute.outputs.version }}
created: ${{ steps.compute.outputs.created }}
+ ring: ${{ steps.ring.outputs.ring }}
+ prerelease: ${{ steps.ring.outputs.prerelease }}
steps:
- uses: actions/checkout@v4
with:
@@ -59,6 +61,30 @@ jobs:
echo "created=false" >> "$GITHUB_OUTPUT"
else
./var/versioning changelog "$ring" > release-notes.md
+
+ # The tool credits commits by short sha `-
()`; git carries no GitHub handle.
+ # Resolve each bullet's sha to its author `@login` via the API and append it, so GitHub
+ # linkifies the mention (and builds its own Contributors list). A commit with no linked
+ # GitHub account falls back to the plain author name (no `@`). One API call per commit.
+ resolve_authors() {
+ while IFS= read -r line; do
+ case "$line" in
+ "- "*"("*")")
+ sha="$(printf '%s' "$line" | sed -n 's/.*(\([0-9a-f]\{7,40\}\))$/\1/p')"
+ if [ -n "$sha" ]; then
+ who="$(gh api "repos/{owner}/{repo}/commits/$sha" \
+ --jq 'if .author.login then "@" + .author.login else .commit.author.name end' \
+ 2>/dev/null || true)"
+ [ -n "$who" ] && line="$line - $who"
+ fi
+ ;;
+ esac
+ printf '%s\n' "$line"
+ done
+ }
+ resolve_authors < release-notes.md > release-notes.resolved.md
+ mv release-notes.resolved.md release-notes.md
+
prerelease=""
if [ "${{ steps.ring.outputs.prerelease }}" = "true" ]; then prerelease="--prerelease"; fi
# shellcheck disable=SC2086
@@ -78,3 +104,58 @@ jobs:
contents: write
with:
version: ${{ needs.version.outputs.version }}
+
+ # Rolling per-ring release: a release tagged with the bare ring name (nightly/rc/latest/stable) that
+ # is re-pointed to this commit and carries the just-built binaries under version-independent names
+ # `cf--`. This is what makes a STABLE download URL possible — GitHub embeds the
+ # release TAG in the download path, so "latest on a ring" needs a fixed tag, not just a fixed asset
+ # name. The versioned releases stay the canonical, immutable ones; these rolling releases are marked
+ # not-latest (and inherit the ring's prerelease flag) so they never displace them as "Latest".
+ # https://github.com//cf/releases/download//cf--
+ # The bare `` tags don't collide with the `-` tags `versioning` matches on.
+ promote:
+ needs: [version, binaries]
+ if: needs.version.outputs.created == 'true'
+ runs-on: ubuntu-24.04
+ permissions:
+ contents: write
+ steps:
+ - name: Refresh the rolling `${{ needs.version.outputs.ring }}` release
+ env:
+ GH_TOKEN: ${{ github.token }}
+ REPO: ${{ github.repository }}
+ RING: ${{ needs.version.outputs.ring }}
+ VERSION: ${{ needs.version.outputs.version }}
+ PRERELEASE: ${{ needs.version.outputs.prerelease }}
+ SHA: ${{ github.sha }}
+ run: |
+ set -eu
+ mkdir dist && cd dist
+ # Mirror the just-built versioned binaries under ring-based names, with fresh checksums.
+ for name in darwin-arm64 darwin-amd64 linux-arm64 linux-amd64 linux-riscv64; do
+ if gh release download "$VERSION" -R "$REPO" -p "cf-$VERSION-$name" 2>/dev/null; then
+ mv "cf-$VERSION-$name" "cf-$RING-$name"
+ sha256sum "cf-$RING-$name" > "cf-$RING-$name.sha256"
+ else
+ echo "note: no cf-$VERSION-$name asset — skipping"
+ fi
+ done
+ cat > notes.md <\`
+ where \`\` ∈ { darwin-arm64, darwin-amd64, linux-arm64, linux-amd64, linux-riscv64 }.
+ Each binary has a \`.sha256\` sibling. For an immutable pin, use the versioned release $VERSION.
+ EOF
+ # Move the rolling tag to this commit (delete + recreate) and attach the mirrored assets.
+ gh release delete "$RING" -R "$REPO" --cleanup-tag --yes 2>/dev/null || true
+ pre=""; [ "$PRERELEASE" = "true" ] && pre="--prerelease"
+ # shellcheck disable=SC2086
+ gh release create "$RING" -R "$REPO" \
+ --target "$SHA" \
+ --title "cf $RING (rolling)" \
+ --latest=false \
+ $pre \
+ --notes-file notes.md \
+ dist/*
diff --git a/boot/build.sh b/boot/build.sh
index eba5f619..41d3a7aa 100755
--- a/boot/build.sh
+++ b/boot/build.sh
@@ -1,7 +1,8 @@
#!/bin/sh
# Build cf — the self-hosted C! compiler (the keeper) — from a committed per-platform SEED.
#
-# build.sh [target] target: darwin-arm64 | linux-arm64 | linux-riscv64 (default: host)
+# build.sh [target] target: darwin-arm64 | darwin-amd64 | linux-arm64 | linux-amd64 | linux-riscv64
+# (default: host)
#
# The permanent trust root is `boot/seed_/` — the QBE IL + freestanding floor that cf emits FOR
# ITS OWN SOURCE (a self-reproducing fixpoint). The seed is per-OS because the compiler's own I/O bakes
@@ -26,19 +27,33 @@ out="$root/var/cf"
case "$(uname -s)/$(uname -m)" in
Darwin/arm64) host=darwin-arm64 ;;
+ Darwin/x86_64) host=darwin-amd64 ;;
Linux/aarch64 | Linux/arm64) host=linux-arm64 ;;
+ Linux/x86_64) host=linux-amd64 ;;
Linux/riscv64) host=linux-riscv64 ;;
*) echo "build: unsupported host $(uname -s)/$(uname -m)" >&2; exit 1 ;;
esac
target="${1:-$host}"
-# Per-platform knobs. `seed` is the OS seed dir; `qt` the qbe `-t` target; `fa` the floor's arch name
-# (floor.$fa.s in the seed); `cc`/`link`/`libs` the C toolchain + link recipe (floor owns `_start`).
-knobs() { # -> sets seed qt fa cc link libs
+# Per-platform knobs. `seed` is the OS seed dir; `sq` the seed IL filename inside it; `qt` the qbe `-t`
+# target; `fa` the floor's arch name (floor.$fa.s in the seed); `cc`/`link`/`libs` the C toolchain +
+# link recipe (floor owns `_start`); `march` extra flags threaded into BOTH the target C compile and
+# the link (so a cross build targets the right machine — e.g. `-arch x86_64` for darwin-amd64). Most
+# platforms share their OS seed's `cf.qbe`; linux-amd64 has its OWN IL (`sq`) because x86-64's linux
+# syscall numbers are baked differently than the generic arm64/riscv64 table.
+knobs() { # -> sets seed sq qt fa cc link libs march
+ march=""; sq="cf.qbe"
case "$1" in
darwin-arm64)
seed="$root/boot/seed_mac"; qt=arm64_apple; fa=arm64
cc="cc"; link="-nostdlib -lSystem -Wl,-e,_start"; libs="" ;;
+ darwin-amd64)
+ # Shares the mac seed IL (darwin arm64/amd64 use identical BSD syscall numbers — the amd64
+ # `syscall` class bit lives in the floor trampoline, not the IL); only the floor asm differs.
+ # `-arch x86_64` (in `march`) lets an Apple-Silicon host cross-compile the embedded QBE
+ # objects AND cross-assemble+link the Mach-O; run the result via Rosetta 2.
+ seed="$root/boot/seed_mac"; qt=amd64_apple; fa=amd64
+ cc="cc"; link="-nostdlib -lSystem -Wl,-e,_start"; libs=""; march="-arch x86_64" ;;
linux-arm64)
# musl (not glibc): static musl links cleanly under the floor's own `_start`, where glibc's
# static libc.a drags in crt/dynamic-loader machinery it never gets. The linux floor calls
@@ -48,6 +63,12 @@ knobs() { # -> sets seed qt fa cc link libs
# `-no-pie` because the floor's `_start` does no PIE self-relocation (some musl gccs default PIE).
seed="$root/boot/seed_linux"; qt=arm64; fa=arm64
cc="${LINUX_CC:-musl-gcc}"; link="-nostdlib -static -no-pie -Wl,-e,_start -Wl,-u,__init_libc"; libs="-lc -lgcc" ;;
+ linux-amd64)
+ # x86-64 linux syscall numbers differ from the generic arm64/riscv64 table, so linux-amd64
+ # has its OWN seed IL (cf.amd64.qbe), not the shared seed_linux/cf.qbe. Same static-musl link
+ # as the other linux targets; native `musl-gcc` on an x86-64 host already targets x86-64.
+ seed="$root/boot/seed_linux"; sq="cf.amd64.qbe"; qt=amd64_sysv; fa=amd64
+ cc="${LINUX_CC:-musl-gcc}"; link="-nostdlib -static -no-pie -Wl,-e,_start -Wl,-u,__init_libc"; libs="-lc -lgcc" ;;
linux-riscv64)
seed="$root/boot/seed_linux"; qt=rv64; fa=riscv64
cc="${RISCV_CC:-riscv64-linux-musl-gcc}"; link="-nostdlib -static -no-pie -Wl,-e,_start -Wl,-u,__init_libc"; libs="-lc -lgcc" ;;
@@ -57,7 +78,7 @@ knobs() { # -> sets seed qt fa cc link libs
# Host toolchain (for cf0 + the vendored QBE), resolved from the host knobs so the whole build uses one
# C compiler — cc on darwin, musl-gcc on linux (matching the embedded QBE objects to the libc we link).
-knobs "$host"; hseed="$seed"; hqt="$qt"; hfa="$fa"; hcc="$cc"; hlink="$link"; hlibs="$libs"
+knobs "$host"; hseed="$seed"; hsq="$sq"; hqt="$qt"; hfa="$fa"; hcc="$cc"; hlink="$link"; hlibs="$libs"
# The embedded QBE's C is built OPTIMIZED and without debug info (QBE's own Makefile defaults to
# `-g` and no `-O`): `-O2` makes cf's IL->asm step faster, and dropping `-g` shrinks the binary. This
@@ -77,10 +98,10 @@ trap 'rm -rf "$tmp"' EXIT
"$hcc" $cflags -I "$qbedir" -c "$root/boot/qbe_embed.c" -o "$tmp/qbe_embed_host.o"
# --- Stage 1: assemble the HOST seed into the bootstrap compiler cf0 (runs on this machine). ---
-for f in "$hseed/cf.qbe" "$hseed/floor.$hfa.s"; do
+for f in "$hseed/$hsq" "$hseed/floor.$hfa.s"; do
[ -f "$f" ] || { echo "build: seed missing: $f" >&2; exit 1; }
done
-"$qbe" -t "$hqt" -o "$tmp/cf0.prog.s" "$hseed/cf.qbe"
+"$qbe" -t "$hqt" -o "$tmp/cf0.prog.s" "$hseed/$hsq"
# shellcheck disable=SC2086
$hcc $hlink -o "$tmp/cf0" "$hseed/floor.$hfa.s" "$tmp/cf0.prog.s" $host_embed "$tmp/qbe_embed_host.o" $hlibs
@@ -98,16 +119,18 @@ else
# `main.o`, already excluded from $host_embed, stay excluded), giving each a unique object name.
mkdir -p "$tmp/tobj"; i=0
for o in $host_embed; do
- "$cc" $cflags -I "$qbedir" -c "${o%.o}.c" -o "$tmp/tobj/q$i.o"
+ # shellcheck disable=SC2086
+ "$cc" $march $cflags -I "$qbedir" -c "${o%.o}.c" -o "$tmp/tobj/q$i.o"
i=$((i + 1))
done
- "$cc" $cflags -I "$qbedir" -c "$root/boot/qbe_embed.c" -o "$tmp/tobj/embed.o"
+ # shellcheck disable=SC2086
+ "$cc" $march $cflags -I "$qbedir" -c "$root/boot/qbe_embed.c" -o "$tmp/tobj/embed.o"
tembed=$(ls "$tmp/tobj"/q*.o); tqembed="$tmp/tobj/embed.o"
fi
# --- Link the target cf: floor (owns _start) + program asm + embedded QBE + bridge. ---
# shellcheck disable=SC2086
-$cc $link -o "$out" "$tmp/cf.floor.s" "$tmp/cf.prog.s" $tembed "$tqembed" $libs
+$cc $march $link -o "$out" "$tmp/cf.floor.s" "$tmp/cf.prog.s" $tembed "$tqembed" $libs
# Release builds (`CF_STRIP=1`) strip the symbol table for a smaller artifact — the objects already
# carry no `-g`, so this is the last of the size. Local builds keep symbols (cf is debuggable with
diff --git a/boot/reseed.sh b/boot/reseed.sh
index 211bc3f8..9f06c8ee 100755
--- a/boot/reseed.sh
+++ b/boot/reseed.sh
@@ -76,7 +76,28 @@ regen_linux() { #
echo "reseed: FIXPOINT FAILED — linux arm64/riscv64 IL diverged (must be identical, generic ABI)" >&2
exit 1
fi
- echo "reseed: ok — linux seed regenerated (native fixpoint verified in CI) -> $lseed"
+ echo "reseed: ok — linux arm64/riscv64 seed regenerated (native fixpoint verified in CI) -> $lseed"
+
+ # linux-amd64 has its OWN IL: x86-64's linux syscall numbers (and struct-stat layout) differ from
+ # the generic arm64/riscv64 table, so it CANNOT share cf.qbe and is EXCLUDED from the identity check
+ # above. Emit its distinct seed; the self-hosting fixpoint is verified natively on CI (ubuntu x86-64).
+ "$1" --skip-embeds --target linux-amd64 "$src" "$lseed/cf.amd64.qbe" "$lseed/floor.amd64.s"
+ echo "reseed: ok — linux-amd64 seed regenerated (its own IL; native fixpoint verified in CI) -> $lseed"
+}
+
+# Regenerate the darwin-amd64 floor from the just-verified darwin compiler `$1`. darwin arm64 and amd64
+# emit BYTE-IDENTICAL IL — they share BSD syscall NUMBERS, and the amd64 `syscall` class bit lives in
+# the floor trampoline, not the IL — so amd64 REUSES the committed mac `cf.qbe`; we assert that identity
+# here and commit only the new `floor.amd64.s`. The amd64 fixpoint is verified natively under Rosetta 2
+# / CI (reseed runs on arm64 and cannot itself run an amd64 cf), but the emit is deterministic and
+# target-driven, so this compiler's amd64 emit is exactly what an amd64-native cf reproduces.
+regen_darwin_amd64() { #
+ "$1" --skip-embeds --target darwin-amd64 "$src" "$tmp/da.qbe" "$seed/floor.amd64.s"
+ if ! cmp -s "$tmp/da.qbe" "$seed/cf.qbe"; then
+ echo "reseed: FIXPOINT FAILED — darwin-amd64 IL diverged from the mac seed (must be identical)" >&2
+ exit 1
+ fi
+ echo "reseed: ok — darwin-amd64 floor regenerated, IL reuses the mac seed -> $seed/floor.amd64.s"
}
# 1. cf₀ from the CURRENT (committed) seed.
@@ -99,6 +120,7 @@ if [ "$transitional" -eq 0 ]; then
cp "$tmp/new.qbe" "$seed/cf.qbe"
cp "$tmp/new.floor.s" "$seed/floor.arm64.s"
echo "reseed: ok — mac seed regenerated and fixpoint verified -> $seed"
+ regen_darwin_amd64 "$tmp/cf_new"
regen_linux "$tmp/cf_new"
exit 0
fi
@@ -114,4 +136,5 @@ fi
cp "$tmp/chk.qbe" "$seed/cf.qbe"
cp "$tmp/chk.floor.s" "$seed/floor.arm64.s"
echo "reseed: ok — mac seed regenerated (transitional stage-2) and fixpoint verified -> $seed"
+regen_darwin_amd64 "$tmp/cf_newer"
regen_linux "$tmp/cf_newer"
diff --git a/boot/seed_linux/cf.amd64.qbe b/boot/seed_linux/cf.amd64.qbe
new file mode 100644
index 00000000..733277f3
--- /dev/null
+++ b/boot/seed_linux/cf.amd64.qbe
@@ -0,0 +1,197737 @@
+export data $cf_page = { l 0, l 0, l 0, l 0, l $f1432, l 0, l 0, l 0, l 0 }
+function l $cf_dyn_push_g(l %node, l %hdr, w %esize, l %reservefn) {
+@start
+ %ip =l alloc8 8
+ %es =l extuw %esize
+ %hlen =l add %hdr, 8
+ %hcap =l add %hdr, 16
+ %len =l loadl %hlen
+ %cap =l loadl %hcap
+ %full =w ceql %len, %cap
+ jnz %full, @grow, @store
+@grow
+ %dbl =l mul %cap, 2
+ %z =w ceql %cap, 0
+ %zl =l extuw %z
+ %z4 =l mul %zl, 4
+ %ncap =l add %dbl, %z4
+ %nbytes =l mul %ncap, %es
+ %ndata =l call %reservefn(l %node, l %nbytes)
+ %odata =l loadl %hdr
+ %obytes =l mul %len, %es
+ storel 0, %ip
+@copytop
+ %ci =l loadl %ip
+ %cdone =w cugel %ci, %obytes
+ jnz %cdone, @copydone, @copybody
+@copybody
+ %src =l add %odata, %ci
+ %dst =l add %ndata, %ci
+ %cb =w loadub %src
+ storeb %cb, %dst
+ %ci1 =l add %ci, 1
+ storel %ci1, %ip
+ jmp @copytop
+@copydone
+ storel %ndata, %hdr
+ storel %ncap, %hcap
+ jmp @store
+@store
+ %data =l loadl %hdr
+ %len2 =l loadl %hlen
+ %soff =l mul %len2, %es
+ %slot =l add %data, %soff
+ %len3 =l add %len2, 1
+ storel %len3, %hlen
+ ret %slot
+}
+function $cf_uint_append_g(l %node, l %vhdr, l %val, l %reservefn) {
+@start
+ %buf =l alloc4 24
+ %ip =l alloc8 8
+ %vp =l alloc8 8
+ storel %val, %vp
+ storel 0, %ip
+ %z =w ceql %val, 0
+ jnz %z, @zero, @extract
+@zero
+ %sz =l call $cf_dyn_push_g(l %node, l %vhdr, w 1, l %reservefn)
+ storeb 48, %sz
+ ret
+@extract
+ %v =l loadl %vp
+ %done =w ceql %v, 0
+ jnz %done, @emit, @digit
+@digit
+ %q =l udiv %v, 10
+ %r =l urem %v, 10
+ %rc =w copy %r
+ %ch =w add %rc, 48
+ %i =l loadl %ip
+ %da =l add %buf, %i
+ storeb %ch, %da
+ %i1 =l add %i, 1
+ storel %i1, %ip
+ storel %q, %vp
+ jmp @extract
+@emit
+ %cnt =l loadl %ip
+ storel %cnt, %vp
+@eloop
+ %k =l loadl %vp
+ %kz =w ceql %k, 0
+ jnz %kz, @edone, @estep
+@estep
+ %k1 =l sub %k, 1
+ %dap =l add %buf, %k1
+ %dch =w loadub %dap
+ %es =l call $cf_dyn_push_g(l %node, l %vhdr, w 1, l %reservefn)
+ storeb %dch, %es
+ storel %k1, %vp
+ jmp @eloop
+@edone
+ ret
+}
+function $cf_int_append_g(l %node, l %vhdr, l %val, l %reservefn) {
+@start
+ %neg =w csltl %val, 0
+ jnz %neg, @isneg, @pos
+@isneg
+ %s =l call $cf_dyn_push_g(l %node, l %vhdr, w 1, l %reservefn)
+ storeb 45, %s
+ %nv =l sub 0, %val
+ call $cf_uint_append_g(l %node, l %vhdr, l %nv, l %reservefn)
+ ret
+@pos
+ call $cf_uint_append_g(l %node, l %vhdr, l %val, l %reservefn)
+ ret
+}
+function $cf_bool_append_g(l %node, l %vhdr, l %val, l %reservefn) {
+@start
+ %nz =w cnel %val, 0
+ jnz %nz, @true, @false
+@true
+ %t0 =l call $cf_dyn_push_g(l %node, l %vhdr, w 1, l %reservefn)
+ storeb 116, %t0
+ %t1 =l call $cf_dyn_push_g(l %node, l %vhdr, w 1, l %reservefn)
+ storeb 114, %t1
+ %t2 =l call $cf_dyn_push_g(l %node, l %vhdr, w 1, l %reservefn)
+ storeb 117, %t2
+ %t3 =l call $cf_dyn_push_g(l %node, l %vhdr, w 1, l %reservefn)
+ storeb 101, %t3
+ ret
+@false
+ %f0 =l call $cf_dyn_push_g(l %node, l %vhdr, w 1, l %reservefn)
+ storeb 102, %f0
+ %f1 =l call $cf_dyn_push_g(l %node, l %vhdr, w 1, l %reservefn)
+ storeb 97, %f1
+ %f2 =l call $cf_dyn_push_g(l %node, l %vhdr, w 1, l %reservefn)
+ storeb 108, %f2
+ %f3 =l call $cf_dyn_push_g(l %node, l %vhdr, w 1, l %reservefn)
+ storeb 115, %f3
+ %f4 =l call $cf_dyn_push_g(l %node, l %vhdr, w 1, l %reservefn)
+ storeb 101, %f4
+ ret
+}
+function $cf_float_append_g(l %node, l %vhdr, d %val, l %reservefn) {
+@start
+ %bslot =l alloc8 8
+ stored %val, %bslot
+ %bits =l loadl %bslot
+ %exps =l shr %bits, 52
+ %exp =l and %exps, 2047
+ %mant =l and %bits, 4503599627370495
+ %special =w ceql %exp, 2047
+ jnz %special, @spec, @finite
+@spec
+ %isnan =w cnel %mant, 0
+ jnz %isnan, @nan, @inf
+@nan
+ %n0 =l call $cf_dyn_push_g(l %node, l %vhdr, w 1, l %reservefn)
+ storeb 78, %n0
+ %n1 =l call $cf_dyn_push_g(l %node, l %vhdr, w 1, l %reservefn)
+ storeb 97, %n1
+ %n2 =l call $cf_dyn_push_g(l %node, l %vhdr, w 1, l %reservefn)
+ storeb 78, %n2
+ ret
+@inf
+ %sgn =l shr %bits, 63
+ jnz %sgn, @neginf, @posinf
+@neginf
+ %mm0 =l call $cf_dyn_push_g(l %node, l %vhdr, w 1, l %reservefn)
+ storeb 45, %mm0
+ jmp @posinf
+@posinf
+ %i0 =l call $cf_dyn_push_g(l %node, l %vhdr, w 1, l %reservefn)
+ storeb 73, %i0
+ %i1 =l call $cf_dyn_push_g(l %node, l %vhdr, w 1, l %reservefn)
+ storeb 110, %i1
+ %i2 =l call $cf_dyn_push_g(l %node, l %vhdr, w 1, l %reservefn)
+ storeb 102, %i2
+ ret
+@finite
+ %vs =l alloc8 8
+ stored %val, %vs
+ %isneg =w cltd %val, d_0
+ jnz %isneg, @doneg, @pos
+@doneg
+ %s0 =l call $cf_dyn_push_g(l %node, l %vhdr, w 1, l %reservefn)
+ storeb 45, %s0
+ %vld =d loadd %vs
+ %vng =d neg %vld
+ stored %vng, %vs
+ jmp @pos
+@pos
+ %vv =d loadd %vs
+ %ip =l dtosi %vv
+ call $cf_uint_append_g(l %node, l %vhdr, l %ip, l %reservefn)
+ %dot =l call $cf_dyn_push_g(l %node, l %vhdr, w 1, l %reservefn)
+ storeb 46, %dot
+ %ipf =d sltof %ip
+ %frac =d sub %vv, %ipf
+ %fbuf =l alloc4 8
+ %fs =l alloc8 8
+ stored %frac, %fs
+ %ci =l alloc8 8
+ storel 0, %ci
+@floop
+ %i =l loadl %ci
+ %fdone =w csgel %i, 6
+ jnz %fdone, @trim, @fstep
+@fstep
+ %fr =d loadd %fs
+ %fr10 =d mul %fr, d_10.0
+ %dg =l dtosi %fr10
+ %dgc =l add %dg, 48
+ %faddr =l add %fbuf, %i
+ storeb %dgc, %faddr
+ %dgf =d sltof %dg
+ %rem =d sub %fr10, %dgf
+ stored %rem, %fs
+ %i1f =l add %i, 1
+ storel %i1f, %ci
+ jmp @floop
+@trim
+ %last =l alloc8 8
+ storel -1, %last
+ %tj =l alloc8 8
+ storel 0, %tj
+@tloop
+ %j =l loadl %tj
+ %tdone =w csgel %j, 6
+ jnz %tdone, @emit, @tstep
+@tstep
+ %ja =l add %fbuf, %j
+ %jc =w loadub %ja
+ %isz =w ceqw %jc, 48
+ jnz %isz, @tskip, @tset
+@tset
+ storel %j, %last
+@tskip
+ %j1 =l add %j, 1
+ storel %j1, %tj
+ jmp @tloop
+@emit
+ %ln =l loadl %last
+ %allz =w csltl %ln, 0
+ jnz %allz, @zero, @digits
+@zero
+ %z0 =l call $cf_dyn_push_g(l %node, l %vhdr, w 1, l %reservefn)
+ storeb 48, %z0
+ ret
+@digits
+ %ei =l alloc8 8
+ storel 0, %ei
+@eloop
+ %e =l loadl %ei
+ %eend =w csgtl %e, %ln
+ jnz %eend, @edone, @estep
+@estep
+ %ea =l add %fbuf, %e
+ %ec =w loadub %ea
+ %esl =l call $cf_dyn_push_g(l %node, l %vhdr, w 1, l %reservefn)
+ storeb %ec, %esl
+ %e1 =l add %e, 1
+ storel %e1, %ei
+ jmp @eloop
+@edone
+ ret
+}
+function $cf_str_append_g(l %node, l %vhdr, l %str, l %reservefn) {
+@start
+ %bytes =l loadl %str
+ %la =l add %str, 8
+ %lenw =w loadw %la
+ %len =l extuw %lenw
+ %ip =l alloc8 8
+ storel 0, %ip
+@sloop
+ %i =l loadl %ip
+ %d =w cugel %i, %len
+ jnz %d, @sdone, @sstep
+@sstep
+ %ba =l add %bytes, %i
+ %b =w loadub %ba
+ %sl =l call $cf_dyn_push_g(l %node, l %vhdr, w 1, l %reservefn)
+ storeb %b, %sl
+ %i1 =l add %i, 1
+ storel %i1, %ip
+ jmp @sloop
+@sdone
+ ret
+}
+function l $cf_str_eq(l %a, l %b) {
+@start
+ %la =l add %a, 8
+ %law =w loadw %la
+ %lb =l add %b, 8
+ %lbw =w loadw %lb
+ %lne =w cnew %law, %lbw
+ jnz %lne, @neq, @lenok
+@lenok
+ %len =l extuw %law
+ %ba =l loadl %a
+ %bb =l loadl %b
+ %ip =l alloc8 8
+ storel 0, %ip
+@eloop
+ %i =l loadl %ip
+ %done =w cugel %i, %len
+ jnz %done, @eq, @estep
+@estep
+ %pa =l add %ba, %i
+ %ca =w loadub %pa
+ %pb =l add %bb, %i
+ %cb =w loadub %pb
+ %bne =w cnew %ca, %cb
+ jnz %bne, @neq, @econt
+@econt
+ %i1 =l add %i, 1
+ storel %i1, %ip
+ jmp @eloop
+@eq
+ ret 1
+@neq
+ ret 0
+}
+data $sld0 = { b "docs", b 0 }
+data $sl0 = { l $sld0, l 4 }
+data $sld1 = { b "0.0.0-dev", b 0 }
+data $sl1 = { l $sld1, l 9 }
+data $sld2 = { b "[type] filter [down/enter] select [ctrl-c] quit", b 0 }
+data $sl2 = { l $sld2, l 51 }
+data $sld3 = { b "[j/k up/dn] move [enter] read [i/esc] search [r] reset [ctrl-c] quit", b 0 }
+data $sl3 = { l $sld3, l 76 }
+data $sld4 = { b "[j/k up/dn] scroll [esc] back [i] search [r] reset [ctrl-c] quit", b 0 }
+data $sl4 = { l $sld4, l 72 }
+data $sld5 = { b "cf: cannot find `", b 0 }
+data $sl5 = { l $sld5, l 17 }
+data $sld6 = { b "` on PATH", b 10, b 0 }
+data $sl6 = { l $sld6, l 10 }
+data $sld7 = { b 0 }
+data $sl7 = { l $sld7, l 0 }
+data $sld8 = { b "cc", b 0 }
+data $sl8 = { l $sld8, l 2 }
+data $sld9 = { b "x86_64", b 0 }
+data $sl9 = { l $sld9, l 6 }
+data $sld10 = { b "arm64", b 0 }
+data $sl10 = { l $sld10, l 5 }
+data $sld11 = { b "-arch", b 0 }
+data $sl11 = { l $sld11, l 5 }
+data $sld12 = { b "-nostdlib", b 0 }
+data $sl12 = { l $sld12, l 9 }
+data $sld13 = { b "-lSystem", b 0 }
+data $sl13 = { l $sld13, l 8 }
+data $sld14 = { b "-Wl,-e,_start", b 0 }
+data $sl14 = { l $sld14, l 13 }
+data $sld15 = { b "-o", b 0 }
+data $sl15 = { l $sld15, l 2 }
+data $sld16 = { b "ENTRY(_start)", b 10, b 0 }
+data $sl16 = { l $sld16, l 14 }
+data $sld17 = { b "SECTIONS {", b 10, b 0 }
+data $sl17 = { l $sld17, l 11 }
+data $sld18 = { b 9, b ". = 0x40080000;", b 10, b 0 }
+data $sl18 = { l $sld18, l 17 }
+data $sld19 = { b 9, b ".text : { *(.text._start) *(.text*) }", b 10, b 0 }
+data $sl19 = { l $sld19, l 39 }
+data $sld20 = { b 9, b ".rodata : { *(.rodata*) }", b 10, b 0 }
+data $sl20 = { l $sld20, l 27 }
+data $sld21 = { b 9, b ".data : { *(.data*) }", b 10, b 0 }
+data $sl21 = { l $sld21, l 23 }
+data $sld22 = { b 9, b ".bss (NOLOAD) : { *(.bss*) *(COMMON) }", b 10, b 0 }
+data $sl22 = { l $sld22, l 40 }
+data $sld23 = { b 9, b ". = ALIGN(16);", b 10, b 0 }
+data $sl23 = { l $sld23, l 16 }
+data $sld24 = { b 9, b ". = . + 0x10000;", b 10, b 0 }
+data $sl24 = { l $sld24, l 18 }
+data $sld25 = { b 9, b "_stack_top = .;", b 10, b 0 }
+data $sl25 = { l $sld25, l 17 }
+data $sld26 = { b 9, b "/DISCARD/ : { *(.comment) *(.note*) *(.eh_frame*) }", b 10, b 0 }
+data $sl26 = { l $sld26, l 53 }
+data $sld27 = { b "}", b 10, b 0 }
+data $sl27 = { l $sld27, l 2 }
+data $sld28 = { b "ld.lld", b 0 }
+data $sl28 = { l $sld28, l 6 }
+data $sld29 = { b "aarch64-none-elf-ld", b 0 }
+data $sl29 = { l $sld29, l 19 }
+data $sld30 = { b "cf: no ELF linker (`ld.lld` or `aarch64-none-elf-ld`) found for the bare target", b 10, b 0 }
+data $sl30 = { l $sld30, l 80 }
+data $sld31 = { b "clang", b 0 }
+data $sl31 = { l $sld31, l 5 }
+data $sld32 = { b "-target", b 0 }
+data $sl32 = { l $sld32, l 7 }
+data $sld33 = { b "aarch64-none-elf", b 0 }
+data $sl33 = { l $sld33, l 16 }
+data $sld34 = { b "-c", b 0 }
+data $sl34 = { l $sld34, l 2 }
+data $sld35 = { b "cf: cannot write the bare linker script", b 10, b 0 }
+data $sl35 = { l $sld35, l 40 }
+data $sld36 = { b "ld", b 0 }
+data $sl36 = { l $sld36, l 2 }
+data $sld37 = { b "-T", b 0 }
+data $sl37 = { l $sld37, l 2 }
+data $sld38 = { b "riscv64-linux-gnu", b 0 }
+data $sl38 = { l $sld38, l 17 }
+data $sld39 = { b "x86_64-linux-gnu", b 0 }
+data $sl39 = { l $sld39, l 16 }
+data $sld40 = { b "aarch64-linux-gnu", b 0 }
+data $sl40 = { l $sld40, l 17 }
+data $sld41 = { b "-static", b 0 }
+data $sl41 = { l $sld41, l 7 }
+data $sld42 = { b "-e", b 0 }
+data $sl42 = { l $sld42, l 2 }
+data $sld43 = { b "_start", b 0 }
+data $sl43 = { l $sld43, l 6 }
+data $sld44 = { b "cf: the embedded QBE backend failed on the emitted IL", b 10, b 0 }
+data $sl44 = { l $sld44, l 54 }
+data $sld45 = { b "cf: kept intermediates: ", b 0 }
+data $sl45 = { l $sld45, l 24 }
+data $sld46 = { b " ", b 0 }
+data $sl46 = { l $sld46, l 1 }
+data $sld47 = { b "--check", b 0 }
+data $sl47 = { l $sld47, l 7 }
+data $sld48 = { b "cf: usage: cf format [--check] ", b 10, b 0 }
+data $sl48 = { l $sld48, l 38 }
+data $sld49 = { b "c", b 0 }
+data $sl49 = { l $sld49, l 1 }
+data $sld50 = { b "compile", b 0 }
+data $sl50 = { l $sld50, l 7 }
+data $sld51 = { b "--dump-routing", b 0 }
+data $sl51 = { l $sld51, l 14 }
+data $sld52 = { b "-P", b 0 }
+data $sl52 = { l $sld52, l 2 }
+data $sld53 = { b "--preserve-intermediates", b 0 }
+data $sl53 = { l $sld53, l 24 }
+data $sld54 = { b "--skip-embeds", b 0 }
+data $sl54 = { l $sld54, l 13 }
+data $sld55 = { b "--set-version", b 0 }
+data $sl55 = { l $sld55, l 13 }
+data $sld56 = { b "cf: `--set-version` needs a value", b 10, b 0 }
+data $sl56 = { l $sld56, l 34 }
+data $sld57 = { b "cf: `-o` needs an output path", b 10, b 0 }
+data $sl57 = { l $sld57, l 30 }
+data $sld58 = { b "--target", b 0 }
+data $sl58 = { l $sld58, l 8 }
+data $sld59 = { b "cf: `--target` needs a value", b 10, b 0 }
+data $sl59 = { l $sld59, l 29 }
+data $sld60 = { b "darwin-arm64", b 0 }
+data $sl60 = { l $sld60, l 12 }
+data $sld61 = { b "darwin-amd64", b 0 }
+data $sl61 = { l $sld61, l 12 }
+data $sld62 = { b "linux-arm64", b 0 }
+data $sl62 = { l $sld62, l 11 }
+data $sld63 = { b "linux-amd64", b 0 }
+data $sl63 = { l $sld63, l 11 }
+data $sld64 = { b "linux-riscv64", b 0 }
+data $sl64 = { l $sld64, l 13 }
+data $sld65 = { b "bare-arm64", b 0 }
+data $sl65 = { l $sld65, l 10 }
+data $sld66 = { b "cf: unknown --target (expected `darwin-arm64`, `darwin-amd64`, `linux-arm64`, `linux-amd64`, `linux-riscv64`, or `bare-arm64`)", b 10, b 0 }
+data $sl66 = { l $sld66, l 127 }
+data $sld67 = { b "--root-size", b 0 }
+data $sl67 = { l $sld67, l 11 }
+data $sld68 = { b "cf: `--root-size` needs a value", b 10, b 0 }
+data $sl68 = { l $sld68, l 32 }
+data $sld69 = { b "cf: usage: cf [c|compile] [flags] -o