Every guest thread's last system call (x8, x0-x2) and guest backtrace, for a hang: a thread blocked in a system call has left its registers in its context. The threads are kept in a registry (id, context, guest stack) as they are made. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
gex
gex runs unmodified Linux and Windows programs in a web browser. It translates their machine code to WebAssembly and runs it against its own implementation of the Linux kernel ABI. That kernel is written in Rust and compiled to WebAssembly as well. A program needs no source changes and no rebuild, and nothing in it knows it is not on a Linux machine.
$ file hello
hello: ELF 64-bit LSB executable, ARM aarch64, statically linked
$ gexc run hello
hello from aarch64
The browser is the target. gexc also runs programs natively on wasmtime,
for debugging and for building what a page loads ahead of time.
What runs
- Linux programs for AArch64, x86-64 and i386. Static and dynamically
linked ELF images, with musl or glibc. gex supports threads,
fork,exec, signals, pipes, sockets and a full filesystem (tmpfs, host passthrough,/proc,/dev). Each guest process gets its own address space. The kernel's shared state is one module that every process's Workers share. - wasm32 Linux programs.
tools/gexccis a C/C++ compiler for gex's wasm32 Linux target. It is used likegcc. The debdemo repository builds Debian packages with it and assembles a Debian rootfs around them. - Windows programs, through Wine.
tools/wine-wasmbuilds Wine's own Unix side as wasm32 for gex, plus a ready-to-run prefix. The Windows code itself is translated like any other x86 code. 32-bit Wine is the main path; 64-bit Wine (wine64.pins) is newer and has seen fewer programs. - Graphics.
gex-waylandis a Wayland compositor, andgex-xwmthe X window manager that a rootless Xwayland needs. Wayland and X11 programs share one desktop. Xwayland can run as native WebAssembly in the page, outside any guest. A guest's OpenGL calls go through gex's own libGL and libEGL (guest/gl) to Mesa, which is compiled to WebAssembly and draws with the browser's WebGPU (third_party/mesa, Gallium'sgex_wgpu). Desktop GL is supported up to 4.6. - In the page, or ahead of time. The same programs run in the browser
page (
web/) and natively undergexc. An ejected project (below) runs only what was built for it ahead of time, as a static site whose JavaScript library a page of your own imports.
How it works
guest ELF/PE ─▶ gex-ld ─▶ gex-xlate ─▶ gex-isa(-x86) ─▶ gex-lift(-x86) ─▶ gex-emit ─▶ wasm
load discover decode GIR tier 1
│
├─▶ gex-fjit function JIT (GIR → wasm)
└─▶ gex-cgen function tier (GIR → C → clang → wasm)
page: shims/browser (gex-kernel per process) + shims/kernel (shared kernel) + Mesa + Xwayland
- Guest virtual address equals linear-memory offset. gex is the kernel, so it allocates every guest address itself, inside the guest's wasm memory. A guest load is a wasm load, with no translation arithmetic.
- Three tiers, one ABI. Tier 1 translates each basic block into a wasm
function, at load time or when a branch first reaches it. Guest JITs and
late-loaded libraries need no special handling. The function JIT
(
gex-fjit) turns a recovered guest function into one wasm function. The function tier (gex-cgen) does the same through C and clang, ahead of time. All three tiers call each other through one function table. - Discovery is never load-bearing. Code the translator did not find lands on a stub, which translates it on demand. A wrong guess costs time, never a wrong branch.
- Ahead-of-time builds.
gexc run --recordrecords what a run loaded and executed.gexc ejectturns a recording into a project that builds everything ahead of time, and runs only what that build made.web/eject-site.mjsmakes the web build of an ejected project into a static site. The site is a JavaScript library that your own page imports.
Layout
| path | what it is |
|---|---|
crates/gexc |
the command-line driver |
crates/gex-isa, gex-isa-x86 |
AArch64 and x86-64 decoders |
crates/gex-ir |
GIR, the SSA IR translation goes through |
crates/gex-lift, gex-lift-x86 |
instruction semantics in GIR |
crates/gex-f80 |
exact x87 80-bit arithmetic |
crates/gex-emit |
the wasm writer and tier-1 code generation |
crates/gex-fjit |
the function JIT |
crates/gex-cgen, gex-split |
the function tier, and splitting its units into hot and cold parts |
crates/gex-units |
what a build ships of its function-tier units: whole, instrumented, or cut to a slot set |
crates/gex-xlate |
code discovery and the translation driver |
crates/gex-ld, gex-dylink |
ELF and PE loading; shared libraries for wasm32 programs |
crates/gex-kernel |
the Linux personality: syscalls, VFS, signals, memory map, processes |
crates/gex-abi, gex-mem, gex-atomic, gex-kmem |
the layout contract and guest-memory access |
crates/gex-cache, gex-key |
the compiled-module cache and the content keys every host agrees on |
crates/gex-wayland, gex-xwm, gex-gpu |
the compositor, the X window manager, WebGPU compositing |
crates/gex-gl |
OpenGL ES passthrough for the native host |
crates/gex-eject |
ejected projects, built and run ahead of time |
shims/browser |
the browser host: the kernel compiled to wasm, beside each guest |
shims/kernel |
the shared kernel module: process table, filesystem, pipes, sockets |
shims/hostwl |
the compositor for an X server that runs in the page |
shims/native |
the native host, on wasmtime |
shims/embed |
gex hosted in another program's wasm module: an emscripten host's memory, allocator and Workers (gex_abi::hosted) |
guest/ |
libraries installed into the guest: GL and EGL, GLX setup, libsteam_api |
web/ |
the page, its build, and the Mesa and Xwayland builds |
tools/gexcc |
the wasm32 C/C++ compiler and its sysroot |
tools/wine-wasm |
Wine for gex: dist, prefix, runner |
tools/eject |
recording a Windows program for eject |
tools/ (the rest) |
generators for the GL transport and compatibility tables, staging the host's Wine PE files, profile and unit helpers |
tests/ |
the lifters' differential fuzzers, and page and eject tests that run under Node |
third_party/ |
wasmtime (with local changes), Mesa, the Khronos GL registry and CTS |
Building
You need the Rust toolchain from rust-toolchain.toml (it installs the
wasm32-unknown-unknown target), clang and wasm-ld, and Node.js. Building
Mesa for the page also needs Emscripten. Running AArch64 tests needs an
aarch64-linux-musl cross toolchain, with qemu-aarch64 as the reference.
$ cargo build --release -p gexc # AArch64 guests
$ cargo build --release -p gexc --features x86 --target-dir target/x86
# x86-64 and i386 guests
$ gexc run <elf> [args...] # translate and run
$ gexc run --wayland SOCKET <elf> # with the compositor, at SOCKET
$ gexc build <elf> -o out.wasm # translate only
$ gexc disasm <elf> # disassemble
$ gexc ir <elf> --at 0x400abc # the GIR for one block
$ gexc run --strace <elf> # syscall trace
The page:
$ git submodule update --init third_party/mesa
$ web/build-all.sh # multi-process build, web/dist
$ web/build-all.sh --sp # single-thread build, web/dist-sp
$ node web/serve-dist.mjs 8088 --sp-port 8089
The multi-process build gives each guest thread its own Worker on shared
memory. It needs SharedArrayBuffer, so the page must be served with
COOP/COEP headers. The single-thread build runs every thread as a fiber in
one Worker, and works on any static host. See web/README.md for the
builds and web/src/README.md for the page's JavaScript API.
Wine needs the x86 gexc at target/x86/release/gexc, as built above
(or GEXC= naming another):
$ make -C tools/wine-wasm # the Wine dist and a prefix
$ tools/wine-wasm/run.sh app.exe # run a Windows program
Tests
Each crate's own tests run with cargo test -p <crate>. tests/diff-fuzz
and tests/diff-fuzz-x86 fuzz the lifters instruction by instruction against
QEMU. The check-*.mjs scripts in shims/browser/js and the scripts in
tests/browser and tests/eject run with node.
Licence
gex is free software under the GNU Affero General Public License, version 3
only (LICENSE). Copyright (C) 2026 Puter Technologies Inc. Third-party
code keeps its own licence; NOTICE lists it.