Show HN: Kakehashi – Experimental userspace to run macOS binaries on Linux ARM

Aug 02, 2026 11:26 PM - 1 day ago 52

Userspace macOS ARM64 → Linux aarch64 translator furniture (CLI-first, nary JIT).

Load Darwin Mach-O connected Linux aarch64, representation a freestanding libSystem, translate BSD syscalls, and tally existent guests (clang probes, 7-Zip 7zz, curl, threads).

Live execution Linux aarch64 (bare metal, VM, Colima/Docker)
Dry-load / inspect Any big (including macOS)
Design reference docs/

Verified connected Docker/Colima and UTM (Linux aarch64). Install once:

cargo instal kakehashi # aliases from a checkout: cargo instal --path crates/kh-cli --force kh vessel ensure kh instal 7zip # Darwin 7zz → impermanent /usr/local/bin/7zz kh instal curl # Darwin curl → impermanent /usr/local/bin/curl

Relative -o / archive paths resoluteness against the host CWD of the kh process (create genitor dirs yourself, aliases trust connected auto-mkdir for O_CREAT). Through the bottle, /Volumes/linux/… bridges to the big guidelines (/ → big /).

# Version / help kh tally 7zz -- kh tally 7zz -- --help # Create archive (cwd-relative) kh tally 7zz -- a demo.7z README.md kh tally 7zz -- t demo.7z kh tally 7zz -- l demo.7z kh tally 7zz -- x -o./out demo.7z # Multi-thread compress (correctness gate) kh tally 7zz -- a -t7z -m0=lzma2 -mx=5 -mmt=4 mt.7z README.md kh tally 7zz -- t mt.7z # expect: Everything is Ok, exit 0

Docker helpers (artifacts nether big .tmp/kh-out/):

./scripts/docker-7zz.sh --help ./scripts/docker-7zz.sh a /Volumes/linux/out/demo.7z /Volumes/linux/src/README.md ls -lh .tmp/kh-out/demo.7z KAKEHASHI_HYPERCALL=1 ./scripts/docker-7zz.sh a -t7z -m0=lzma2 -mx=5 -mmt=4 \ /Volumes/linux/out/mt.7z /Volumes/linux/src/README.md ./scripts/docker-7zz.sh t /Volumes/linux/out/mt.7z
# Banner (G1) kh tally curl -- --version # HTTP GET → record (G3 / G5). Parents for -o are created erstwhile missing. kh tally curl -- -sS -o .tmp/kh-out/body http://example.com/ # expect: exit 0, ~559 bytes, HTML contains "Example Domain" wc -c .tmp/kh-out/body head -c 80 .tmp/kh-out/body; echo # HTTP to stdout kh tally curl -- -sS http://example.com/ | caput -c 80; echo # HTTPS GET (G4) — OpenSSL + vessel CA (from big aliases curl.se download) kh tally curl -- -sS -o .tmp/kh-out/https-body https://example.com/ wc -c .tmp/kh-out/https-body # Negative: bad / self-signed cert must neglect (rc ≠ 0) kh tally curl -- -sS -o /dev/null https://self-signed.badssl.com/; echo exit:$?

Docker helpers:

./scripts/docker-curl.sh --version ./scripts/docker-curl.sh -sS -o /Volumes/linux/out/body http://example.com/ ./scripts/docker-curl.sh -sS -o /Volumes/linux/out/https-body https://example.com/ ls -lh .tmp/kh-out/body .tmp/kh-out/https-body # Trace-first probe logs → .tmp/kh-curl-probe/ ./scripts/docker-curl-probe.sh --version # Option matrix (large tiers) → .tmp/kh-curl-options/ ./scripts/docker-curl-options.sh tier1 ./scripts/docker-curl-options.sh tier9-10 ./scripts/docker-curl-options.sh each # tier1..10

Harmless sound connected galore runs:

  • kh: unfastened neglect ENOENT(openat) path=/etc/ssl/openssl.cnf — OpenSSL optional config; HTTP/HTTPS still activity via the seeded CA bundle.
  • WARN … skip dylib … Security/CoreFoundation — Apple frameworks not successful the bottle; soft stubs screen the load path.
  • unresolved beardown symbol; bound to named missing trampoline — symbols not deed connected the happy path.

Details and gates: docs/curl.md.

Surface Notes
Clang / fixture probes tests/clang-probe/, tests/fixtures/
Multi-thread 7zz -mmt=4 Docker + UTM
Bottle + freestanding libSystem kh vessel guarantee embeds dylib
Unit tests + clippy cargo trial / clippy workspace (excl. kh-libsystem)

Not a merchandise declare (yet)

Full curl characteristic group (POST bodies, proxies, HTTP/3 end-to-end, each scheme), real Apple Security.framework, git / CLT, GUI, codesign. Next merchandise slice: git via kh instal xcode-tools — spot docs/git.md.

Crate Role
kakehashi Binary kh (install this)
kh-loader Mach-O parse, map, execute
kh-runtime Memory, traps, BSD syscalls, bottle; embeds freestanding libSystem.B.dylib
kh-libsystem Source for that dylib (aarch64-apple-darwin only; not a Linux big crate)

The impermanent dylib is vendored astatine crates/kh-runtime/resources/libSystem.B.dylib and compiled into the runtime pinch include_bytes!. Publishing kh-runtime ships the dylib; extremity users do not request a abstracted download.

  • Rust 1.88+
  • Linux aarch64 for unrecorded kh tally / kh trace
  • Page sizes: 4 KiB (containers) and 16 KiB (Asahi-class)
  • Optional: curl/wget + tar for kh instal 7zip / kh instal curl
cargo instal kakehashi # aliases from a checkout: cargo instal --path crates/kh-cli kh vessel ensure kh instal 7zip kh instal curl

Default root: ~/.local/share/kakehashi/bottle/ (override with KAKEHASHI_DATA_DIR / KAKEHASHI_ROOT).

Host Guest
…/bottle/ /
…/usr/local/bin/7zz /usr/local/bin/7zz
…/usr/local/bin/curl /usr/local/bin/curl
…/usr/lib/libSystem.B.dylib /usr/lib/libSystem.B.dylib
…/private/etc/ssl/cert.pem /etc/ssl/cert.pem (host CA aliases downloaded Mozilla)
…/Volumes/linux/… /Volumes/linux/… → big FS

Kakehashi runs impermanent codification natively connected the CPU. The taxation is the syscall boundary (TLS switch, alt stack, NEON save/restore, Rust dispatch) × how chatty the impermanent is — not an instruction emulator.

On Ubuntu aarch64 bare-metal (UTM), multi-file 7zz archive (-t7z -m0=lzma2 -mx=5 -mmt=4, ~8k files / ~240 MiB tree):

native Linux 7zz Darwin 7zz nether kh ratio
wall ~22.5 s ~118 s ~×5.2

On compression-heavy, few-file samples the spread is often overmuch smaller (~×1.1–1.2). The ample multi-file spread is dominated by path locomotion + per-syscall boundary, not “wrong LZMA”.

Hypercall is connected by default for each impermanent threads. Opt retired with KAKEHASHI_HYPERCALL=0 only for debug (residual svc→brk / SIGTRAP).

Why ~×5 is still useful successful CI

The merchandise extremity for CI is not “as accelerated arsenic autochthonal macOS,” but run Darwin CLI/tools connected inexpensive Linux aarch64 runners alternatively of scarce, expensive macOS capacity.

GitHub Actions hosted runners (private-repo overage rates, USD per minute; spot Actions runner pricing):

Runner Per-minute rate
Linux 2-core arm64 $0.005
Linux 2-core x64 $0.006
macOS 3–4 halfway (M1/Intel) $0.062
macOS larger (e.g. 12-core / M2 Pro) $0.077–$0.102

macOS modular is astir ×10–×12 the Linux arm64 infinitesimal complaint earlier any wall-time difference. Even if a occupation runs ×5 slower nether kh connected Linux arm64 than the aforesaid activity connected a macOS runner, billable costs tin still beryllium lower because the macOS infinitesimal is an bid of magnitude much expensive (illustrative: 5 × $0.005 ≈ $0.025 vs 1 × $0.062). Public-repo free minutes and self-hosted Linux amplify that further; macOS hosted capacity besides tends to queue longer and (on GitLab SaaS) is Premium/Ultimate / beta-gated.

When macOS runners still win: GUI, codesign/notarization, Xcode UI tests, or immoderate workload that is not a axenic CLI Darwin binary nether freestanding libSystem.

Gates, not benches: CI correctness is cargo trial / fume / 7zz -mmt=4, not “match autochthonal wall clock.” Perf activity is tracked in docs/roadmap.md.

Quick commencement (Docker / Colima connected Apple Silicon)

docker build -t kakehashi:dev -f Dockerfile.dev . docker tally --rm -v "$PWD":/src -w /src kakehashi:dev \ cargo test --workspace --exclude kh-libsystem # Full fume (build + clippy + trial + micro run) ./scripts/docker-smoke.sh
cargo build -p kakehashi --release cargo test --workspace --exclude kh-libsystem cargo clippy --workspace --exclude kh-libsystem --all-targets -- -D warnings # Maintainers: refresh embed aft freestanding ABI changes cargo build -p kh-libsystem --release --target aarch64-apple-darwin ./scripts/stage-libsystem.sh # → crates/kh-runtime/resources/libSystem.B.dylib

libSystem discovery: --libsystem → KAKEHASHI_LIBSYSTEM → paths adjacent to kh → crate resources/ → embedded bytes successful kh-runtime.

Goal Command Artifacts
Unit tests cargo trial --workspace --exclude kh-libsystem terminal
Docker smoke ./scripts/docker-smoke.sh ends pinch fume ok
Fixtures kh tally --expect-code … tests/fixtures/… see tests/fixtures/README.md
Clang probes kh tally --root tests/fixtures/bottle tests/clang-probe/puts_hello stdout hello
Real Darwin 7zz ./scripts/docker-7zz.sh … host .tmp/kh-out/
Real Darwin curl ./scripts/docker-curl.sh … host .tmp/kh-out/; probe → .tmp/kh-curl-probe/
Fair CPU bench ./scripts/bench-fair-local.sh host .tmp/kh-bench-fair/

.tmp/, .kh/, and target/ are gitignored.

Guest way ↔ big way (Docker helpers)

Bottle bridges the Linux FS arsenic /Volumes/linux/…:

Guest path Host
/Volumes/linux/src/README.md <repo>/README.md
/Volumes/linux/out/demo.7z <repo>/.tmp/kh-out/demo.7z (durable; default for docker-7zz.sh)
/Volumes/linux/tmp/… container /tmp/… — gone aft docker tally --rm
Script Purpose
scripts/stage-libsystem.sh Build merchandise → crates/kh-runtime/resources/
scripts/install-linux.sh Local build + instal kh + vessel ensure
scripts/docker-smoke.sh Smoke suite wrong Dockerfile image
scripts/docker-7zz.sh Darwin 7zz nether kh (outputs → .tmp/kh-out)
scripts/docker-curl.sh Darwin curl nether kh (same style arsenic docker-7zz)
scripts/docker-curl-probe.sh KH_CURL_PROBE=1 wrapper (logs → .tmp/kh-curl-probe)
scripts/docker-curl-options.sh Tiered curl emblem fume (tier1…tier10, tier9-10, each → .tmp/kh-curl-options)
scripts/docker-git.sh Apple git from CLT nether kh (swscan + .kh/data cache)
scripts/bench-fair-local.sh Native vs kh compress (artifacts → .tmp/kh-bench-fair)

Apache License 2.0. See LICENSE.txt and NOTICE.

This task is not derived from Darling. Do not vendor proprietary Apple SDKs aliases blobs.

More