Skip to content

Latest commit

 

History

3 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

gluon — user-space NT synchronization for Wine on Apple Silicon

gluon is an esync/fsync/NTSync-class synchronization backend for Wine on macOS (Apple Silicon). It routes NT sync primitives — events, semaphores, mutexes — through shared memory + os_sync_wait_on_address instead of a wineserver RPC, and falls back to wineserver for everything it doesn't own. Turn it on with WINEGLUON=1.

Why it matters (measured, on an M5)

Every NT wait/signal in stock Wine is an RPC the single-threaded wineserver must service. Under sync-heavy load that pins a CPU core and serializes the whole prefix. gluon moves those ops into shared memory, so they never reach the server.

Real AAA benchmark — Unigine Superposition 1.1 (DirectX 11), 30 s steady-state render window:

WINEGLUON wineserver CPU as % of a core
off 10.32 CPU-s ~34%
on 0.04 CPU-s ~0.1%

712,000 sync waits over ~85 s were handled client-side; with gluon off those are all wineserver RPCs. Superposition renders through D3D11 → wined3d's Vulkan backend → MoltenVK → Metal — no DXVK needed.

Sync-bound micro-benchmark (job-system frame loop):

workers wineserver CPU off → on throughput off → on
2 65% → 2.5% 17k → 690k fps (40×)
8 66% → 0.0% 5.1k → 28k fps (5.5×)
16 66% → 0.2% 2.5k → 18k fps (7×)

Uncontended op cost (the hot path), standalone PoC on the M5: set_event 4 ns · signaled wait 4 ns · sem acquire+release 8 ns · mutex acquire+release 9 ns — vs ~2.8 µs for the socket RPC gluon replaces.

GPU-bound fps itself is unchanged (sync isn't the frame limiter — the honest control); the win is the wineserver-CPU headroom, exactly what esync/fsync deliver. Full methodology and every raw number: docs/BENCHMARKS.md.

Architecture

 one shared-memory region per Wine prefix  (/wine-<inode>-gluon)
 ┌───────────────────────────────────────────────────────────────┐
 │ header    magic                                               │
 │ channels[8192]  per thread: { seq (sleep word), sleeping,     │
 │                               apc_count, owned-mutex list }   │
 │ nodes[8192*64]  intrusive wait-list nodes (one set / channel) │
 │ objects[65536]  { type, state, extra, waiter-list head, ... } │
 └───────────────────────────────────────────────────────────────┘

 wineserver (server/gluon.c)          ntdll client (dlls/ntdll/unix/gluon.c)
 ─────────────────────────────        ──────────────────────────────────────
 owns naming, handles, lifetime,      hot path, no server round trip:
 slot + channel allocation,           - create/open  → server assigns a slot,
 death cleanup (abandonment walk        returns its shm index with the handle
 at kill_thread), and the wake        - set/reset/release  → atomics on the slot
 bridge for server-side waits         - wait  → try-acquire (lock-free), else
                                        register on each object's waiter list,
                                        recheck, sleep on the channel's seq via
                                        os_sync_wait_on_address; signalers poke
                                        the channel (skipping the syscall if the
                                        waiter is still spinning)

Key properties (all validated — see below):

  • Uncontended ops make zero syscalls. That is where the CPU savings come from.
  • Server owns lifetime. The NT handle is identity; the server holds the slot until the last handle closes, so the client cache needs no refcount and no generation check — the PoC's per-op pinning is dropped at the Wine boundary.
  • Honest fallback. Any handle gluon doesn't own (files, processes, threads, message queues, keyed events, …) makes the whole wait fall back to server_select, with a +gluon TRACE. The single-threaded server can still wait on gluon objects via a wake bridge.
  • NT semantics parity. wait-all (all-or-nothing with rollback), mutex abandonment (WAIT_ABANDONED on owner death, incl. kill -9 across processes), alertable waits / APCs, and NT status codes.
  • Graceful table exhaustion. When the 64k-slot table fills, creates transparently fall back to normal server objects — no spurious out-of-resources failure.

What's verified

  • winetest conformance: kernel32:sync, ntdll:sync, ntdll:om show zero gluon-only failures vs the server baseline (getting there fixed four real bugs — see wine-tests/README.md §2 and patch 0005).
  • kill -9 abandonment end-to-end: a process holding a named mutex is kill -9'd, another process's waiter gets WAIT_ABANDONED — including recursive-ownership and wait-all variants, and an off-mode control (wine-tests/run.sh).
  • wine64 notepad and winecfg run cleanly under WINEGLUON=1, behavior identical with it off.
  • Standalone PoC: correctness suite + a 10M-op randomized stress test, ThreadSanitizer-clean, on macOS arm64 and Linux.

Layout

patches/      the release. 0000 = macOS-26/Xcode-26 build prep for the
              CrossOver 22.1.1 tree; 0001–0007 = the gluon feature as a
              logically-ordered series (LGPL-2.1, matching Wine). See
              patches/README.md for the apply-and-build recipe.
poc/          standalone C11 reference implementation of the core
              (gluon.h/.c) + its tests and benchmark. MIT-licensed.
docs/         BUILD.md   — build Wine + the graphics stack on Apple Silicon
              INTEGRATION.md — the esync→gluon design map (hook sites,
                               protocol, fallback matrix, death cleanup)
              BENCHMARKS.md — full results + methodology
              PLAN.md    — the phased project plan
wine-tests/   the PE test programs and harness scripts (abandonment,
              conformance diff, sync-load + GPU benchmarks).

Licensing

  • patches/ (the Wine backend): LGPL-2.1-or-later, matching Wine. Every new file carries the LGPL header; modified files keep Wine's.
  • poc/ (the standalone core): MIT (poc/LICENSE).

Quick start

# 1. reproduce the buildable baseline and apply gluon (see patches/README.md)
# 2. build wine64 (see docs/BUILD.md §3)
# 3. run something:
WINEGLUON=1 ./loader/wine64 notepad
# 4. see the effect:
WINEGLUON=1 WINEDEBUG=+gluon ./loader/wine64 your.exe 2>&1 | grep -c 'waiting for'

Requires macOS 14.4+ (for os_sync_wait_on_address); gluon compiles out on other platforms. Built and measured on macOS 26 / Apple M5.

Contributing / Future work

Issues and test reports are welcome — especially from M1 / M2 / M3 Macs. Everything here was built and measured on an M5; confirming the wineserver-CPU win (and shaking out any wake-channel / os_sync timing differences) on earlier Apple Silicon would be genuinely useful. Please include your macOS version, chip, WINEDEBUG=+gluon fallback counts, and the workload.

Roadmap, roughly in priority order:

  1. Rebase onto modern Wine with new-style wow64 (--enable-archs=i386,x86_64). This tree is win64-only, so 32-bit titles (e.g. Unigine Heaven) can't run at all. wow64 unlocks the large back catalogue of 32-bit games and is the single biggest capability gain.
  2. Server-bound wait acceleration (queue / thread / process handles). These currently fall back to server_select by design (INTEGRATION.md D3); routing them through gluon's shared-memory channels would move the remaining steady-state waits — and the message-loop path — off the server too.
  3. Port to current CrossOver / Kegworks trees. The patches target CrossOver 22.1.1 / Wine 7.7; forward-porting to the Wine base those ship today would make gluon usable in the stacks most people actually run, alongside their D3DMetal/DXVK graphics translation.

See docs/INTEGRATION.md for the design map and the open decisions each of these touches.

About

NTSync-class synchronization for Wine on Apple Silicon — wineserver 34%→0.1% CPU on real DX11 workloads.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages