binarrow #
binarrow is an experimental browser runtime for AArch64 Linux ELF executables. It aims to load a userspace process image, interpret AArch64 through an architecture-neutral IR, handle Linux system calls against explicit host interfaces, and eventually translate hot guest code to WebAssembly.
The project has completed the Phase 0 feasibility stage described in PLAN.md. The workspace now contains the first native static-ELF interpreter boundaries:
crates/runtime-core: host-independent process types, traps, and resource limits.crates/execution-ir: decoder-independent normalized operations and validated basic blocks.crates/aarch64: project-owned architectural state, SLEIGH decoding, P-code normalization, and bounded interpretation throughsvc.crates/elf: strict, host-independent ELF64/AArch64 validation andPT_LOADmetadata.crates/guest-memory: checked sparse memory regions, permissions, guard mappings, and committed-memory limits.crates/loader: static and interpreter-backed ELF segment mapping plus an LP64 Linux initial stack withargc,argv,envp, andauxv.crates/linux-abi: minimal AArch64 Linux syscall numbers and errno return encoding.crates/host-api: browser-independent terminal service traits.crates/linux-runtime: bounded process, signal, memory, terminal, and exit syscall dispatch plus the static-process execution loop.crates/memory-fs: bounded ephemeral files and offsets behind the host filesystem trait.crates/browser-runtime: wasm-bindgen bridge that runs the same static process inside a Worker.crates/cli: the nativebinarrow inspect <file>andbinarrow run <file>commands.crates/wasm-probe: generation of the Memory64 WebAssembly module used by browser tests.crates/wasm-backend: deterministic Tier-1 basic-block Wasm lowering and state ABI.web: a Worker-based Chrome feature report and Playwright smoke test.experiments/icicle: the pinned, isolated AArch64 decode/interpreter feasibility probe.experiments/icicle-wasm: a pinned P-code semantic probe compiled to raw browser Wasm.third_party/ghidra-aarch64: the pinned Apache-2.0 AArch64 SLEIGH source closure with license, notice, and provenance.tools/wasm-bindgen: pinned, repository-local generation of browser bindings.docs/decisions: architecture decision records, including the selective Icicle adoption decision.
The browser gate dynamically instantiates Memory64, proves that a Promise-bearing JSPI import suspends and resumes Wasm, dynamically compiles a real lifted AArch64 basic block that writes x0 = 42, and runs an ordinary static AArch64 Rust std program through musl startup, the production loader, SLEIGH interpreter, Linux runtime, and terminal boundary in a Worker. See the browser feasibility report for the original capability environment and diagnostics.
Prerequisites #
- Rust 1.93 (installed automatically by
rustupfromrust-toolchain.toml) - Node.js 24
- pnpm 10
- Chrome or Playwright's Chromium build for the browser smoke test
Checks #
cargo fmt --all --check
cargo clippy --workspace --all-targets --all-features -- -D warnings
cargo test --workspace --all-features
cargo check --workspace --all-targets --target wasm32-unknown-unknown
cargo check --manifest-path fuzz/Cargo.toml
pnpm --dir web install --frozen-lockfile
pnpm --dir web run check
pnpm --dir web test
Inspect an ELF before it enters the loader pipeline:
cargo run -p binarrow-cli -- inspect path/to/aarch64-static.elf
The inspector accepts little-endian ELF64 EM_AARCH64 ET_EXEC and ET_DYN
images, including a single validated PT_INTERP. It rejects malformed or
overlapping load segments, writable-executable mappings, duplicate or invalid
interpreter records, and entry points outside executable memory.
Run an ELF through the native interpreter and propagate its guest exit status:
cargo run -p binarrow-cli -- run path/to/aarch64-static.elf [guest arguments...]
Pass repeatable --env NAME=VALUE options before the executable when a guest
runtime needs an explicit environment; no host environment variables are
inherited implicitly.
--instruction-budget, --syscall-budget, --memory-limit,
--filesystem-limit, and the existing output/open-file limits bound guest
work before it crosses a host boundary.
Use --filesystem-output <snapshot> to persist the final /project state.
The CLI writes this snapshot after execution stops even when a resource limit
produces a diagnostic, so bounded compiler runs can retain their cache and
other completed filesystem mutations for a later run.
The runnable instruction/syscall profile remains fixture-driven. The current static Rust program adds single-thread atomics and barriers, 128-bit vector moves/stores, byte popcount and reduction, multiplication/division, and signed shifts to the earlier libc instruction path. The runtime implements openat, close, lseek, file read/write, bounded nonblocking pipe2, constrained vfork-style clone/wait4, PID queries, polling, deterministic process/signal setup, static-ELF execve, anonymous memory management, terminal output, and exit. Invalid guest arguments return Linux errno values; instruction, syscall, committed-memory, ephemeral-filesystem, open-file, and combined-output limits are enforced before host side effects.
The native CPython checkpoint is reproducible without committing its large
generated artifacts. guest-tests/cpython/build.sh creates a statically linked
AArch64 musl CPython 3.12.13 executable and standard-library image entirely
under .tmp; guest-tests/cpython/verify.sh runs the bounded version and
multi-file/package regressions, while guest-tests/cpython/verify-browser.sh
runs the packaged interpreter, standard library, project, and checksum-pinned
packaging wheel in Chromium. See
guest-tests/cpython/README.md for prerequisites
and packaging details.
The verified multi-file and third-party imports, package-image overlays,
traceback behavior, and current limitations are summarized in
docs/cpython-compatibility.md.
Phase 6 is complete for the initial static C scope. It includes static process replacement, a packaged Clang/LLD/musl
toolchain, browser-edited C source, linked compiler diagnostics, bounded pipes,
and a single-child spawn/exec model. guest-tests/spawn-exec suspends a parent,
runs a constrained vfork-style child that inherits open files and pipes, loads
a project ELF with close-on-exec handling, restores the parent, and reaps the
child's exact status. Native and Chromium hosts share the same regressions.
General fork/clone modes, concurrent children, and blocking pipe scheduling
remain future milestones.
Phase 7 adds interpreter-backed ELF execution through the guest musl dynamic
linker. guest-tests/dynamic-musl verifies shared-library search, file-backed
mapping, relocations, startup and late-loaded TLS, plus dlopen/dlsym in
native and Chromium-facing sessions. The dynamic CPython checkpoint packages
shared libpython3.12.so and _struct; its bounded native verifier imports the
extension through CPython's normal dlopen path. Both reproducible builds keep
their sources, tools, caches, and generated artifacts under .tmp.
Phase 8 now has a checksum-pinned official Rust 1.93.0 AArch64 musl host
distribution, a source-to-static-ELF checkpoint, a locked offline Cargo
dependency checkpoint, and minimal Cargo build-script execution.
toolchains/rust-musl/build.sh packages rustc, Cargo, the musl standard
library, LLD, and the required unwind/compiler-runtime compatibility DSO
entirely under .tmp. toolchains/rust-musl/verify.sh checks the dynamically
linked host compiler, compiles checked-in no_std Rust source, links its
persisted object, executes the result, resolves and compiles a pinned crates.io
dependency without guest networking, and verifies Fresh artifact replay from
an exported filesystem snapshot. The Cargo fixture compiles and runs a Rust
build.rs, includes its generated OUT_DIR source in a normal static std
binary, and keeps the completed build fresh across snapshot restore. The
verified crate tier and remaining limitations are documented in
docs/rust-compatibility.md. Procedural macros,
native dependencies, broader build-script behavior, and general-purpose guest
threading remain subsequent checkpoints.
The native verifier now invokes the Clang driver once rather than manually
staging -cc1 and LLD. Its musl posix_spawn path uses an inherited
blocking-mode close-on-exec pipe, runs both child executables, and emits a
static /project/driver-hello that exits 42 under one bounded instruction
budget. The browser acceptance still exercises the same packaged Clang, LLD,
sysroot, and output ELF in persisted stages, while the fast Chromium spawn
fixture covers the child/pipe machinery.
Browser-local build tools should place reusable artifacts under
/project/.cache. That namespace is included in the bounded OPFS-backed
project snapshot and therefore survives runs and reloads. The Clear build
cache action removes that tree atomically while preserving source, installed
toolchains, and other project files.
The browser build generates its Memory64, JSPI, and P-code .wasm probes before starting Vite. Generated artifacts are not committed. Select Uploaded AArch64 ELF to run an external static executable with a chosen argv[0] and one argument per line; the executable is transferred directly to the runtime Worker.
Run the opt-in Phase 4 interpreter/translator benchmark in Chromium with all temporary output inside the repository:
TMPDIR="$PWD/.tmp/rust-tmp" BINARROW_TRANSLATION_BENCHMARK=1 \
pnpm --dir web test tests/translation-benchmark.spec.ts
The default benchmark warms both paths and reports the median of five
278,528-instruction samples. Set BINARROW_TRANSLATION_ITERATIONS to change the
loop count.
Scope #
The browser controller can start the checked-in freestanding C, musl C, Rust std, filesystem, and infinite-loop fixtures or a user-supplied static AArch64 ELF with explicit instruction, syscall, output, committed-memory, and filesystem limits. Its source editor atomically saves /project/main.c into the same OPFS-backed snapshot consumed by browser-local Clang. Clang-format stderr records render as source diagnostics; selecting one focuses the editor at its reported line and column. Execution errors return stable diagnostic codes instead of rejected JavaScript calls. The Stop action terminates the active Worker, so even a guest that never reaches a syscall or yield point can be interrupted and the runtime restarted. Chromium verifies the C/Rust outputs, uploaded-ELF transfer, source and filesystem persistence, compiler-diagnostic navigation, deterministic counters, resource-limit diagnostic, and manual infinite-loop termination. Phase 4's bounded profiler offers hot blocks to the scalar Wasm backend; supported blocks compile and execute from a session-local module cache while unsupported blocks remain in the interpreter. The UI reports hotness, translated execution, fallback, compilation, cache-hit, and emitted-byte metrics. The interpreter's interim browser memory design is recorded in ADR-0002. See PLAN.md for the roadmap and docs/architecture.md for the current boundaries.
License #
Licensed under either the Apache License, Version 2.0 or the MIT license, at your option.