WIP: fix(runtime): translate macOS fault contexts on both CPU routes #436
No reviewers
Labels
No labels
area/accessibility
area/audio
area/input
area/install
area/menu
area/renderer
area/saves
area/upscaling
good first issue
gpu/amd
gpu/igpu
gpu/intel
gpu/nvidia
help wanted
kind
bug
kind
chore
kind
docs
kind
feature
kind
performance
kind
question
needs measurements
platform/linux
platform/steam-deck
platform/windows
preview
priority
critical
priority
high
priority
low
status
blocked
status
confirmed
status
duplicate
status
invalid
status
needs-info
status
wontfix
testing wanted
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
MagicBeanDev/Longshot!436
Loading…
Reference in a new issue
No description provided.
Delete branch "mooki/Longshot:mac/fault-contexts"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
What this is for: the macOS port (#420). LS turns host CPU faults into Win32 exceptions in
host/posix/win32_posix.c; first-touch commits, crash reports and the NVN write watch go through it. That bridge readsLinux's signal context, so it does not compile for macOS, either natively on Apple Silicon or as x86_64 under Rosetta.
This PR adds the Darwin side for both CPU routes. The Windows and Linux code is unchanged. On its own it does not make the
macOS host build or run: the whole file still stops at
linux/futex.h, which the waits PR removes. What is measured:a new game-free test builds the real bridge (cut from
win32_posix.c) withdarwin_fault.cand makes real faults onpages it owns. On
mainthe bridge does not compile (44 errors per architecture). Here it passes on arm64 and on x86_64under Rosetta, two interleaved runs each. Also measured: the full game-free suite on an Apple M4 Max (macOS 26.7.1),
compared by test name with
main. Not measured: Windows, Linux, a macOS build or game run, macOS 14.4 itself, anIntel Mac.
Why
A Mac cannot build LS's POSIX host while its fault bridge only knows Linux's signal context. This is the fault part of the
port.
Problem
context_from,context_to,on_signalandinstall_signals(host/posix/win32_posix.c) readuc->uc_mcontext.gregs[REG_*], and the access kind fromREG_ERR.On Darwin,
uc_mcontextis a pointer to a per-architecture__darwin_mcontext64, with the registers in__ssandthe fault syndrome in
__es. There is nogregs,greg_torREG_*.So the bridge does not compile for macOS on either route. Main's first errors, arm64:
host/posix/win32_posix.c:1105:11: error: unknown type name 'greg_t', thenno member named 'gregs' in 'struct __darwin_mcontext64'anduse of undeclared identifier 'REG_RAX'.The callers that need a working bridge, at
5e072fc8:commit_on_touch(host/mkw_glue.c:82) uses the fault address to commit, then retries the instruction;report_crash(mkw_glue.c:121) andreport_fatal(:242) log the PC and the exception information;log_crash_filter(host/hos_thread.c:251) forwards the record to the previous filter unchanged;wts_handler(host/nvn_native.c:816) uses the access kind, address and PC.None of them reads a host general-purpose register on Darwin. The crash report's guest registers come from
hos_ctx(), not from the host context.What changed
host/posix/darwin_fault.cand.h(new), all inside#if defined(__APPLE__):darwin_fault_context_from/_to. On x86_64 they carry every x64 integer register,Rip,RspandEFlagsfrom and to
__ss. On arm64 onlyRip(the PC) andRsp(the SP) are carried, withCONTEXT_CONTROL. The x64-onlyinteger, flags and debug slots are zero there, not aliases of ARM registers. All other native state stays in the
signal frame, untouched on return.
darwin_fault_access: 0 for a read, 1 for a write, 8 for an instruction fetch. It reads x86 trap 14's error bits,or ARM's instruction/data-abort class and WnR bit. Any other syndrome is not a write.
darwin_fault_action:SA_SIGINFO | SA_ONSTACK | SA_NODEFERwith an empty mask, plus a per-thread guard. A faultinside a callback ends the process with
_exit(128 + signal)instead of dispatching again. Synchronous faults staydeliverable, because blocking one can retry it forever. An existing alternate signal stack is used; none is created.
host/posix/win32_posix.c: an Apple-only include ofdarwin_fault.hand four#if defined(__APPLE__)branches,in
context_from,context_to,on_signal's access kind andinstall_signals. Each#elseismain's textunchanged. Handler order, removal, the unhandled filter and the forwarding to the previous handler are unchanged.
Both SIGBUS and SIGSEGV enter the bridge.
host/posix/windows.h: an Apple-only comment on the context's meaning on arm64. No field, size or constant changed.tests/test_darwin_fault_context.py(new): buildsdarwin_fault.cand the exception section cut verbatim fromwin32_posix.c, with the SDK and the layer's own headers. Its fixture makes real faults on owned anonymous pages andchecks:
It builds the running Python's architecture by default, so the ordinary suite needs no Rosetta. Repeated
--archselects both routes;
--sourcebuilds another tree's bridge. It skips on Windows and printsSKIPon Linux.docs/development.md: a "macOS fault-context bridge" section with the context's meaning, the limits and thedual-route command.
Unchanged: profiler sampling and watchpoints, fibers, guest memory, the NVN write watch, the renderer, the build
scripts and
third_party/.darwin_fault.cis not innx2/build.py's source lists yet; the later macOS build PR inthis series (
build(macos): classify Darwin helpers …) adds everydarwin_*.c.Flags and settings
No game, renderer or build switch. The new test's own options:
--arch(repeatable)--arch arm64 --arch x86_64, to qualify the two routes where Rosetta is installed--sourcemain, for the failing side--rounds--case(repeatable)matrix,plain,handler-stack--outMeasurements
main:
5e072fc8c2adadba5ea610148be1c47800ba0cca(2026-11-14)this PR:
mac/fault-contexts@7731dc0c18a34207c265a654064d0b6b52a390beHardware: Apple M4 Max, 16 cores, 64 GiB, macOS 26.7.1 (25G241); Apple clang 21.0.0; Python 3.14.10. No GPU used.
Scene: none. Game-free: owned anonymous memory.
Method: the new test with
--arch arm64 --arch x86_64, onmain's tree (--source, a clean checkout of5e072fc8) andon this PR, interleaved: main, PR, main, PR. Then
tests/run_all.py --jobs 6on this PR, compared by test name with amainrun on the same Mac.How to reproduce:
python -I -B tests/test_darwin_fault_context.py --arch arm64 --arch x86_64, then the same with--source <a checkout of main>.5e072fc87731dc0ctests/test_darwin_fault_context.py, run 1tests/test_darwin_fault_context.py, run 2The 44 errors are 43 from the Linux context fields and one unused parameter under the test's
-Werror.plainmodedoes not count signals by kind, so its zeros are not "no signals";
matrixcounts the real SIGBUS and SIGSEGVdeliveries. The handler-stack child ended by SIGILL natively (
exit=-4) and by the guard's128 + SIGBUSunderRosetta (
exit=138); neither timed out.Testing
Tested:
The new test, both ways, both routes, twice each, as above.
The whole file still stops at its first include on this PR, as on
main:host/posix/win32_posix.c:12:10: fatal error: 'linux/futex.h' file not found(arm64 and x86_64, syntax only). Thewaits PR removes that include; the exception section itself compiles here, inside the test.
The whole suite,
tests/run_all.py, 6 at once, on the same Mac (the runner labels macOSlinux):tests/test_darwin_fault_context.py(pass, native arm64 only, asrun_allruns it). No other check changed status.main, which other macOS pull requestsaddress:
test_appimage_package_retry,test_collect_wide,test_control,test_framegen_resources,test_game_layout,test_low_memory_notice,test_mem_gate,test_mem_gate_children,test_mirror_trim,test_module_ids,test_oot_state_tap,test_pad_layout,test_pcache_header_read,test_pipeline_cache_file,test_pipeline_cache_save,test_pipeline_play_cache,test_prof_symbols,test_program_cache_cleanup,test_program_cache_one_variant.Not tested:
win32_posix.candwindows.hismain's; that is not a build there.report's module lookup. The macOS build needs the other wave-1 PRs.
fetch from non-executable memory.
that moves the SP to another stack.
Needs other testers:
to open).
SKIP(request toopen).
python -I -B tests/test_darwin_fault_context.py --arch arm64 --arch x86_64; expectPASS Darwin fault bridge(request to open).Screenshots
Nothing on screen.
Risk and rollback
#if defined(__APPLE__)or in a new file.CONTEXT. A caller that edits or reads an x64 integer register getszero there. No caller does today (see Problem).
locks or lazy TLS async-signal-safe.
wts_handler,nvn_native.c) still unprotects 4 KiB at a time and ignores the result. On anative 16 KiB host it needs its own page accounting before use. This PR does not change it.
Reviewer notes
5e072fc8..7731dc0c: two commits (da567be4,7731dc0c) and no dependency. Start withdarwin_fault.c,then the four Apple branches in
win32_posix.c, then the test's source cut and its fault instructions.as regular files.
sys/_types/_ucontext.h,mach/arm/_structs.h,mach/i386/_structs.h), thesigaction(2)andsigaltstack(2)pages, and the Arm and Intel architecture manuals for the syndrome bits. Nothing wascopied from another project.
Issues
See #420.
Checklist
For everyone
type(scope): description).main. Based on5e072fc8.python -I -B tests/run_all.pyrun and passing (any failure that also fails onmainis named above). On macOS:526 passed, 17 skipped, and the same 19 failures as
main, named under Testing.requests are still to open.
the median reported. For a
fix: the new test fails onmainand passes here, in two interleaved runs each.third_party/untouched (a new folder with aPROVENANCE.mdinstead).If applicable
Assisted-by:/Co-Authored-By:trailers.docs/development.md.mainand passing here.View command line instructions
Checkout
From your project repository, check out a new branch and test the changes.