The recorder keeps the volatile keys wineboot writes as [gex-record] reg
lines in a page run with WINEGEX_RECORD_HARDWARE=1; the build writes them
into the prefix's system.reg and user.reg and starts the page with
WINEGEX_NO_WINEBOOT=1. The 64-bit Wine pin moves to the commit that adds
both.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_c8c04c00da9a4525ab21b0b9f146c598
64-bit Wine now maps a program that can move at its dll-bases pin, so a
game's program whose base is beyond the process's space keeps one address,
and its unit, whatever the environment. The Wine pin moves to that commit.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_c8c04c00da9a4525ab21b0b9f146c598
The game's Wine runs under WINEGEX_NO_DESKTOP with the Wayland driver: no
explorer, so no rpcss, and RunServices no longer starts one. Windowed
programs keep "explorer". Wine pins move to the commits that add it.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_c8c04c00da9a4525ab21b0b9f146c598
A Worker's console.error comes marked, so the recorder's anchored
patterns never saw the shim's uncovered lines and an AOT-only project
built from a recording that reached code no unit holds. The marker is
stripped before matching. Code reached only through the function
table's lookup is recorded as well (it never misses in tier-1's
dispatch), and native Wine's return pad is not: the run leaves there,
and the build no longer asks for an entry at it.
--play S --keys T:KEY,... presses keys after the title's read set is
taken, so a game's play is in the recording; --console FILE keeps every
console line.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_c8c04c00da9a4525ab21b0b9f146c598
The process's top comes from its environment (default 8 GiB, a multiple
of 64 KiB from the high arena plus 512 MiB up to the memory's limit);
gex.space_top gives it to ntdll, which ends its space there instead of
probing. The PE data window follows the top. Natively space_top is 0.
wine64.pins: Wine whose ntdll takes gex's top.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_c8c04c00da9a4525ab21b0b9f146c598
The process that started the wineserver has no door in its Worker, and ran
its program again as a child, waiting on it with its own memory held for
the whole session (2 GiB with the 64-bit split). Wine now execs in place
after `gex.exec_fresh()`, and that exec goes on in a new Worker and
memory with the pid kept, as a forked child's does. Natively an exec is
in place already. The Wine pins carry the loader's side.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_c8c04c00da9a4525ab21b0b9f146c598
A recording whose loader is Wine's x86_64-unix one ejects as a wine64
project. It differs from a wine32 one in four ways:
- it ships the 64-bit NT trampolines' units, both the memory64 one and
the plain one, since a memory64 program runs beside Wine's own
processes;
- the runtime carries the memory64 shim and seed;
- the launch file says whether the page had the D3D device;
- resources default to whole.
The rest of the project, build and site is a wine32 one's.
Unit keys are matched for either guest memory width (rsrc key_with_mem,
the ABI hash per width), so identities and resource rekeying find
memory64 units. The launch file carries the bundle's user xattrs (the
memory64 mark on a program), which the site's rebuilt bundle drops; the
app library sets them with the tree's own. The recorder reads a Worker's
relayed lines ("[gex-console-error] ..."), so uncovered addresses are
no longer lost, and the session records the page's d3d.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_c8c04c00da9a4525ab21b0b9f146c598
<gex/i386.h> and rt/i386.c: gex_i386_call runs i386 code from the
program (cdecl or stdcall words, EDX:EAX or ST0), through gex's function
table to a return pad, as native Wine's gexrt does; GEX_I386_ENTRY and
gex_i386_bind let i386 code call the program back, its C running on the
guest thread's stack below the guest's frames, nested to any depth. The
link adds rt/i386.o and the container's flags (--guest-i386
--linux-space). install.sh builds the object against the gex_abi.h of an
x86 gexc; without one the flag is refused at link time.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_c8c04c00da9a4525ab21b0b9f146c598
A wasm32 program's container may say it runs i386 code of its own (a
JIT's) rather than Windows': header flag FLAG_LINUX_SPACE (8) beside
FLAG_GUEST_I386, wasm-elf.py --linux-space. The process translates i386
as 32-bit Wine's loader does and gets the trampoline page (the return pad
of calls into the guest), but keeps Linux's address space: no mmap top
below KUSER_SHARED_DATA natively, no Windows space in the page.
gex.i386_bind(addr, idx): i386 code calling or jumping to addr runs the
program's function at table index idx, bound like an export of the
install that holds the index (native and page).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_c8c04c00da9a4525ab21b0b9f146c598
The pinned musl gains the hooks GEX_SNAPSHOT=1 build-sysroot.sh builds
the snapshot flavour with. They are compiled only under GEX_SNAPSHOT: the
default sysroot is the same as the previous pin's, byte for byte.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_c8c04c00da9a4525ab21b0b9f146c598
The .NET path for Unity games (Mono for wasm64 as a stateful provider)
moves to the reference branch ref/mono. Gone with it:
- tools/unity-dotnet (the Mono build, its provider glue and sysroot);
- the provider's own wasm JIT: shims/native/src/wasmjit.rs, the page's
provider_jit_* imports and the browser shim's table reservations, and
the "provider-jit" share in the AOT share line;
- stopping guest threads for a provider's garbage collector (native
GuestStop, the browser's stop state, their syscall and safepoint hooks)
and the lookup ring kept for it;
- provider_guest_sym, provider_guest_stack_end and the wasm64 providers'
provider_try/provider_throw with their exception tag.
What other paths use stays: stateful providers (native Wine, eject
providers, wasm DLLs and programs), whole Linux libraries replaced by
wasm64 providers (the gexcc native libraries) with the imports they have,
ELF export providers, and memory64.
No key inputs.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_c8c04c00da9a4525ab21b0b9f146c598
WINE64_DIRK=1 links d3d11.so, the Unix side of the D3D shim (dxvk's
src/dirk/pe), into the loader. WINE64_EXTRA_DLLS=DIR links DLLs built outside
the tree (the shim's d3d11.dll and dxgi.dll, dxvk's tests/dirk/w64/build.sh)
into the native PE core, their skeletons under extra/, loaded only when a
prefix is told to. The Wine pin moves to winewayland.drv's
wayland_gex_client_surface, the client wl_surface id the shim presents to.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_c8c04c00da9a4525ab21b0b9f146c598
The snapshot flavour's programs call gex.syscall as the default build
does: libgex-snap no longer wraps the libc's calls. The link's asyncify
instruments what reaches gex.syscall, and the host wraps a recording
module's syscall import (it exports __gex_snap_blocks): while a snapshot
is pending, a call that can block unwinds the thread instead of being
made, and only such a call's wait, left for the snapshot, unwinds after
it. A thread whose record waits for its first such call makes it there,
the call's context kept across.
libgex-snap is an archive of its own (libgex-snap.a) that wcc and wcc64
link last, and the main thread's run calls the libc's start by name, kept
out of the run's root (--no-inline): the default build's data and table
slots keep their places in the flavour's module, so a restored session's
memory and table mean the same to the default build's.
A record holds 16 levels (SNAP_LEVELS), and the trace says when a thread
has more. A restored session refuses a snapshot (ProcessHost.snapshot,
snapshotAllowed): a new one comes from a fresh boot that records.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_c8c04c00da9a4525ab21b0b9f146c598
With the snapshot sysroot, wcc64 compiles Wine's PE side with the legacy
exception instructions and __GEX_SNAPSHOT__ too, and pelink.py runs
asyncify over the lowered winepe.wasm: what can reach gex.call_out, below
gexrt's __gexw_entry (each entry from the guest is a level's root, kept
apart from the C under it), its state and data the host's, then the
standard exception instructions. The default build is unchanged.
The host, for a winepe.wasm of the flavour: its system and Unix calls take
the host's route (the Unix side's direct entries declined), its call-outs
keep the module's per-thread state and stack pointer at the call, and an
entry whose call comes back unwinding is kept as a level of route 2 in the
record's upper half, beside that module's asyncify window, and entered
again rewound. A call-out's loop puts the count of entries back when an
exception reaches it (a trap passes the entries' own catches), and a door
call waiting on a contended lock while a snapshot is pending goes by the
pipe, whose wait stops.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_c8c04c00da9a4525ab21b0b9f146c598
All in the snapshot flavour (wcc64's snapshot build, the page's
asyncify flavour); a default build is byte-identical.
- A thread whose Wine call came through the ud2 fault route (the fault
hook, in place or staged from the call-out loop) now counts as stopped
at its root: the hook call counts depth and unwinds as the entry
function does.
- A thread waiting inside nested call-outs (a window procedure that runs
its own message loop inside a Wine call) stops at every level. The
program's gex.call_out is an asyncify import in the flavour: the inner
level records itself and throws to the call-out, which unwinds the
outer Wine frames to the outer root. Restore rewinds the outer frames,
then re-enters each level in turn. The record holds up to 8 levels.
- The kernel lists its file data and lazy chunks (k_snap_extents). A
snapshot stores each 64 KiB page that still matches the bundle's
source as a reference, and copies only the pages written since boot.
The snapshot names the bundle's identity (run-node: names, sizes and
modification times of the bundle's host files, no content hashing, so
it costs nothing before the stop), and restore refuses a different
bundle.
- GEX_TEST_UD2_ENTRY=1 (test only, in the page's unix_dispatcher import):
64-bit Wine's direct entries are never registered, so every call takes
the ud2 route. The shim is unchanged.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_c8c04c00da9a4525ab21b0b9f146c598
A wasm64 program built against the snapshot sysroot (build-sysroot64.sh
with GEX_SNAPSHOT=1) is instrumented by wcc64 after its memory64 lowering:
asyncify over what can reach a blocking call (forkreach.py's call graph;
--quoted takes the quoted names C++ leaves), then the standard exception
instructions. A thread of it stops at one of two roots (libgex-snap
entry64.c):
- the host's entry from guest code (an entry function of its own that
counts its depth and ends a snapshot's unwind in the outermost one,
from Wine's own call-out loop), restored by __gex_snap_resume64, which
rewinds the handler under a fresh entry and goes on at the context's PC;
- the libc's run of a thread's function or of the main thread's start
(__gex_snap_run_loop), for a thread that waits with no guest code below
it, restored by __gex_snap_resume_run.
The shim asks the page whether a waiting thread is at one (snap_ok), and
refuses the snapshot otherwise.
A restored session's threads wait, attached, until all are; the host then
adds the time since the session stopped (taken when every thread had
stopped) to the kernel's held time, which each process's monotonic clocks
and the x86 time-stamp counter skip. Snapshot files past 2 GiB are read and
scanned run by run.
Without GEX_SNAPSHOT the sysroot and wcc64's output are unchanged.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_c8c04c00da9a4525ab21b0b9f146c598
The flavour's thread records, the blocking-call choice, the park and the
restored start move from the libc into libgex-snap/snapshot.c, which only
the flavour's sysroot builds into libgex; the libc keeps four hooks. A
posix_spawn child gets no record (gex.snap_wanted).
A wait a snapshot stopped keeps the time it had left (Run::snap_left):
the call made again waits that long, not its whole timeout, and only that
call does. A restored session's monotonic clocks are held back by the
time it spent saved (gex-kernel MONOTONIC_HELD, the kernel's k_mono_held
for every process), so deadlines programs keep in them stay where they
were.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_c8c04c00da9a4525ab21b0b9f146c598
ProcessHost.snapshot stops every thread of every process where it waits:
the global kernel's snapshot word is set and every wait slot bumped; a
wasm32 thread built with the snapshot flavour leaves a wait it can leave
as if its call had not been made (snap_stop) and its frames unwind into
its record (gex.syscall_b), and its driver parks (gex.snap_park). The
kernel's memory and each process's memory (its nonzero pages) and module
store are copied, and the threads go on or stop. A thread that does not
stop in time refuses the snapshot. ProcessHost.restore runs a session
from one: the kernel's memory as it was, each process's memory, and each
thread in a Worker entering at the start its record holds
(gex_snap_thread); the wineserver's door is published again.
The flavour is a sysroot option (build-sysroot.sh GEX_SNAPSHOT=1); wcc
instruments what reaches gex.syscall_b only with that sysroot, and links
every other program as before, byte for byte. run-node: GEX_SNAP_OUT,
GEX_SNAP_AT, GEX_SNAP_IN.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_c8c04c00da9a4525ab21b0b9f146c598
A shared-mode link instruments every function that makes an indirect
call, since fork may be below it; in a large C++ program that is nearly
all of it. -fgex-no-fork (or GEXCC_NOFORK=1), per link and off by
default, links rt/nofork.c's hidden fork, vfork, _Fork, daemon and
forkpty, which fail with ENOSYS, and leaves asyncify out. A side module
built so has an empty gex.forkreach summary. A link that still imports
gex.fork_point is refused. posix_spawn, system and popen keep working.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_c8c04c00da9a4525ab21b0b9f146c598
Without them CMake's feature checks all fail to link, and the build fails
far from the cause: no -nostdinc++, so the new libc++ headers include the
installed ones and errno is undeclared in __thread/support/pthread.h.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_c8c04c00da9a4525ab21b0b9f146c598
The sysroot's headers compile under their installed names, the runtime and
libc++/libc++abi under /gex/tools/gexcc and /llvm, and GEXCC_BAKE_RUNNER
names the runner linked into containers (empty: none), so a package built
for an image records none of this machine's paths.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_c8c04c00da9a4525ab21b0b9f146c598
GLib calls GObject's class and instance initialisers with two arguments
where G_DEFINE_TYPE's take one, passes a signal handler user data it may
leave out, and takes g_free as a GFunc: wasm traps on each. gex-dylink
lenient-calls sends every call_indirect of a type with parameters to a
helper that makes the call as it was when the table's function has that
type (ref.test), else with the trailing arguments cut to the first type
the function has, else as before; a function returning a value called as
returning none has it dropped. The helpers make call_indirect calls, so
asyncify instruments them as any other function. gexcc runs it on a
shared module before wasm-opt when GEXCC_LENIENT_CALLS=1.
The native engines enable function references and GC for ref.test.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_c8c04c00da9a4525ab21b0b9f146c598
The repository that builds the wasm32 Debian system and the desktop demo's
guest side is named debdemo, not gex-debian. The READMEs, the web build's
messages and the comments say so; where they named a script inside it,
they name the repository.
No key inputs.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_c8c04c00da9a4525ab21b0b9f146c598
Comments named the test harness gex is developed with (its gate and bench
runner) and one still said a site's kind is w3; they say what they mean
without it.
No key inputs. wine32-dist.sh is in TOOLS_KEY and build-mesa-wasm.sh in
Mesa's stamp: the dist and Mesa rebuild.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_c8c04c00da9a4525ab21b0b9f146c598
Six comments named tests that live in the development repository, not
here (an audio loopback measurement, a pipe race, the configure corpus,
a GL probe, the single-thread check, a benchmark). They say what the
test showed instead.
No key inputs.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_c8c04c00da9a4525ab21b0b9f146c598
The NT layer is gone; what still said gex-nt: are the function tier's
messages about Wine's PE images (units, providers, link groups, native
wasm32 DLLs), natively and in the page. They say gex-pe: now. The readers
in the tree (record-wine32.mjs, the browser harness) take either, so an
older log still reads.
No key inputs.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_c8c04c00da9a4525ab21b0b9f146c598
The DLL band's comments named a script by the repository it used to live
in; it is tools/wine-wasm/wine32/dll-bases.py. The wasm32 sysroot's
comment named a local worktree for its musl fork; wine32.pins names it.
No key inputs. build-sysroot.sh is in the dist's SYSROOT_KEY, so the
wine32 sysroot and dist rebuild.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_c8c04c00da9a4525ab21b0b9f146c598
wine64 had no pins: its Wine tree and its LP64 sysroot's musl were
whatever trees the builder handed in, and build-sysroot64.sh's default musl
was a path outside the repository. wine64.pins names both forks and
commits. build-sysroot64.sh, given no musl tree, fetches MUSL64_COMMIT
through wine32/src.sh's mirror, as the wine32 sysroot does; wine64-dist.sh
still builds the tree it is given and says when that is not WINE64_COMMIT.
No key inputs. build-sysroot64.sh's text is in the gate's sysroot64 key, so
that sysroot (about a minute) and the wine64 dist on it rebuild.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_c8c04c00da9a4525ab21b0b9f146c598
A Wine that folds KUSER_SHARED_DATA asks for units built with
--wine32-kuser, and units.sh built them plain, so a --build-units run's
units were never found. It now builds the first image whose key the log
names plain and, if that is not the key asked for, with --wine32-kuser,
and builds the rest the way that matched. The NT trampoline page's unit
is built when the log names it missing, and gexc's own "PATH at BASE: no
function-tier unit" lines are read as well as the page's.
No key inputs (TOOLS_KEY leaves units.sh out).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_c8c04c00da9a4525ab21b0b9f146c598
The pin had stayed at a commit from before the gate's Wine moved on; it is
now the gate's Wine plus the header rename (to be pushed with this round).
Changes unit keys (the deps key hashes the pins).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_c8c04c00da9a4525ab21b0b9f146c598
- wcc and wcc64 map the gex tree to /gex, the sysroot to /sysroot and
GEX_WCC_MAP's pairs (the Wine builds: the source, build, tools and deps
trees to /wine...) with -ffile-prefix-map, so __FILE__ and lock names in
the Wine modules name no host directory.
- page-prefix.sh rewrites the registry's paths into the dist (the fonts in
share/wine/fonts) to the page's mount, /wine32 or /wine64.
Changes unit keys (the Wine images change, and so the dists and prefixes).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_c8c04c00da9a4525ab21b0b9f146c598
- WINE32_KUSER (gex_abi), GEX_WINE32_KUSER (the provider header), the
`:wine32kuser` unit tag; the eject kind "wine32" and the recording format
"gex-wine32-session" (the old "w3" and "gex-w3-session" still read).
- The page mounts 32-bit Wine's dist at /wine32 (tools/wine-wasm/run.sh,
page-run.mjs, units.sh; a recording's /w3 paths still map) and reports
"== wine32 page:".
- Comments no longer point at docs that are not in the repository; the
eject ones point at crates/gex-eject/README.md.
Changes unit keys (gex-abi, gex-cgen's header and tag).
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_c8c04c00da9a4525ab21b0b9f146c598
The NT layer's opengl32 is gone, and with it the table the generator read
its unix calls from, so guest/gl/build.sh stopped at a missing file. Out
go nt_table, emit_nt (opengl32's plans, version and extensions),
NT_TOLERATED, NT_UNCLAIMABLE, NT_VERSION_LIMIT and the Rust half of the
pname counts, which only that output used. Every other output is byte for
byte what it was (--check passes on the checked-in files).
No key inputs.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_c8c04c00da9a4525ab21b0b9f146c598
Meson asks with a test file and -c, and the query went to clang, whose
answer has no multiarch directory; meson then gave every program a run
path into the build machine's sysroot for each library found there. The
answer is gexcc's, nothing compiled, as GCC does.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_c8c04c00da9a4525ab21b0b9f146c598
gexcc-rpath prints or replaces a module's run path (dylink.0's runtime
path), in place and at the same size, as meson's depfixer does an ELF's
DT_RUNPATH at install. gexcc-objdump -p prints it as RUNPATH, so
dpkg-shlibdeps finds a library in a directory the run path names.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_c8c04c00da9a4525ab21b0b9f146c598
No key inputs (unit keys); rebuild inputs: the Mesa key (guest/gl,
web/mesa, build-mesa-wasm.sh), the Wine dist keys (tools/wine-wasm) and
the Xwayland key (gex-wayland), all rebuilt in this round anyway.
The docs/*.md and § references, plan labels (W0) and moved-test paths
come out; game names become neutral. No code change.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_c8c04c00da9a4525ab21b0b9f146c598