WIP: fix(runtime): translate macOS fault contexts on both CPU routes #436

Draft
mooki wants to merge 2 commits from mooki/Longshot:mac/fault-contexts into main
Member

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 reads
Linux'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) with darwin_fault.c and makes real faults on
pages it owns. On main the bridge does not compile (44 errors per architecture). Here it passes on arm64 and on x86_64
under 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, an
Intel 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_signal and install_signals (host/posix/win32_posix.c) read
    uc->uc_mcontext.gregs[REG_*], and the access kind from REG_ERR.

  • On Darwin, uc_mcontext is a pointer to a per-architecture __darwin_mcontext64, with the registers in __ss and
    the fault syndrome in __es. There is no gregs, greg_t or REG_*.

  • 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', then no member named 'gregs' in 'struct __darwin_mcontext64' and use 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) and report_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.c and .h (new), all inside #if defined(__APPLE__):

    • darwin_fault_context_from / _to. On x86_64 they carry every x64 integer register, Rip, Rsp and EFlags
      from and to __ss. On arm64 only Rip (the PC) and Rsp (the SP) are carried, with CONTEXT_CONTROL. The x64-only
      integer, 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_NODEFER with an empty mask, plus a per-thread guard. A fault
      inside a callback ends the process with _exit(128 + signal) instead of dispatching again. Synchronous faults stay
      deliverable, 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 of darwin_fault.h and four #if defined(__APPLE__) branches,
    in context_from, context_to, on_signal's access kind and install_signals. Each #else is main's text
    unchanged. 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): builds darwin_fault.c and the exception section cut verbatim from
    win32_posix.c, with the SDK and the layer's own headers. Its fixture makes real faults on owned anonymous pages and
    checks:

    • read and write addresses and access kinds, the PC in the faulting function, and the SP at entry;
    • a retry after the handler repairs the page, and a resume at a PC the handler changed;
    • a live x86 integer-register edit, and that arm64's other state comes back untouched;
    • vector order and removal, the unhandled filter, and both one- and three-argument previous handlers;
    • a child that makes its handler's own stack inaccessible must end, not loop.

    It builds the running Python's architecture by default, so the ordinary suite needs no Rosetta. Repeated --arch
    selects both routes; --source builds another tree's bridge. It skips on Windows and prints SKIP on Linux.

  • docs/development.md: a "macOS fault-context bridge" section with the context's meaning, the limits and the
    dual-route command.

  • Unchanged: profiler sampling and watchpoints, fibers, guest memory, the NVN write watch, the renderer, the build
    scripts and third_party/. darwin_fault.c is not in nx2/build.py's source lists yet; the later macOS build PR in
    this series (build(macos): classify Darwin helpers …) adds every darwin_*.c.

Flags and settings

No game, renderer or build switch. The new test's own options:

Flag What it does Default now Recommendation Helps on Doesn't help on Untested on
--arch (repeatable) the fixture's targets the running Python's architecture both, --arch arm64 --arch x86_64, to qualify the two routes where Rosetta is installed macOS Windows, Linux an Intel Mac
--source the tree whose bridge is built this checkout a tree of main, for the failing side comparisons
--rounds faults per case 256 keep 256
--case (repeatable) matrix, plain, handler-stack all three all three debugging one case
--out keeps the fixture's source, binaries and logs in a new folder a temporary folder set it for evidence

Measurements

main: 5e072fc8c2adadba5ea610148be1c47800ba0cca (2026-11-14)
this PR: mac/fault-contexts @ 7731dc0c18a34207c265a654064d0b6b52a390be
Hardware: 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, on main's tree (--source, a clean checkout of 5e072fc8) and
on this PR, interleaved: main, PR, main, PR. Then tests/run_all.py --jobs 6 on this PR, compared by test name with a
main run 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>.

main @ 5e072fc8 this PR @ 7731dc0c
tests/test_darwin_fault_context.py, run 1 exit 1: the bridge does not compile, 44 errors on arm64 and 44 on x86_64 exit 0: 6 of 6 cases pass
tests/test_darwin_fault_context.py, run 2 exit 1: the same exit 0: 6 of 6 cases pass
real faults per run, arm64 (16 KiB pages) none: no binary 2,048 recovered, plus the handler-stack child
real faults per run, x86_64 under Rosetta (4 KiB pages) none: no binary 2,048 recovered, plus the handler-stack child
full game-free suite 525 passed, 19 failed, 17 skipped 526 passed, 19 failed, 17 skipped
main @ 5e072fc8 (each run):
44 errors generated.
FAIL arm64 bridge compilation
44 errors generated.
FAIL x86_64 bridge compilation
FAIL Darwin fault bridge

this PR @ 7731dc0c (each run):
HANDLER_STACK entered=1 on_alternate_stack=1
PASS arm64 handler-stack
ROUNDTRIP pc_sp=1 integer_contract=1 untouched_machine_state=1 syndrome_classes=1
FAULT_BRIDGE mode=matrix rounds=256 page=16384 faults=1792 chained=512 SIGBUS=256 SIGSEGV=256 pass=1
PASS arm64 matrix
FAULT_BRIDGE mode=plain rounds=256 page=16384 faults=256 chained=256 SIGBUS=0 SIGSEGV=0 pass=1
PASS arm64 plain
HANDLER_STACK entered=1 on_alternate_stack=1
PASS x86_64 handler-stack
FAULT_BRIDGE mode=matrix rounds=256 page=4096 faults=1792 chained=512 SIGBUS=256 SIGSEGV=256 pass=1
PASS x86_64 matrix
FAULT_BRIDGE mode=plain rounds=256 page=4096 faults=256 chained=256 SIGBUS=0 SIGSEGV=0 pass=1
PASS x86_64 plain
PASS Darwin fault bridge

The 44 errors are 43 from the Linux context fields and one unused parameter under the test's -Werror. plain mode
does not count signals by kind, so its zeros are not "no signals"; matrix counts the real SIGBUS and SIGSEGV
deliveries. The handler-stack child ended by SIGILL natively (exit=-4) and by the guard's 128 + SIGBUS under
Rosetta (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). The
    waits 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 macOS linux):

    main @ 5e072fc8:     525 passed, 19 failed, 17 skipped in 177 s (linux, 6 at once)
    this PR @ 7731dc0c:  526 passed, 19 failed, 17 skipped in 233 s (linux, 6 at once)
    
    • By test name, the only difference is the new tests/test_darwin_fault_context.py (pass, native arm64 only, as
      run_all runs it). No other check changed status.
    • The same 19 checks fail on both. They are macOS portability failures of main, which other macOS pull requests
      address: 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.
    • No new crash report during the suite, and none during the four runs above (114 files before and after).

Not tested:

  • Windows and Linux. The non-Apple text of win32_posix.c and windows.h is main's; that is not a build there.
  • A macOS build or game run, a whole process with its renderer, the guest allocator's first-touch commits and the crash
    report's module lookup. The macOS build needs the other wave-1 PRs.
  • macOS 14.4 itself, an Intel Mac and arm64e. The deployment target is 14.4; it ran only on macOS 26.7.1.
  • An execute fault: the access kind 8 and the unrelated syndromes are checked with constructed contexts, not with a real
    fetch from non-executable memory.
  • Faults on many threads at once, handler registration racing a fault, all SIMD/FP registers seeded, and a handler
    that moves the SP to another stack.

Needs other testers:

  • Windows (llvm-mingw): build the host and run the game-free suite; expect no change, and the new test skipped (request
    to open).
  • Linux: build the host and run the game-free suite; expect no change, and the new test printing SKIP (request to
    open).
  • macOS 14.4 on Apple Silicon: python -I -B tests/test_darwin_fault_context.py --arch arm64 --arch x86_64; expect
    PASS Darwin fault bridge (request to open).

Screenshots

Nothing on screen.

Risk and rollback

  • Windows and Linux: no change in behaviour. Every new line is inside #if defined(__APPLE__) or in a new file.
  • macOS, arm64: this is not a full Win32 ARM64 CONTEXT. A caller that edits or reads an x64 integer register gets
    zero there. No caller does today (see Problem).
  • The guard assumes callbacks return normally, previous handlers included. It does not make logging, unwinding,
    locks or lazy TLS async-signal-safe.
  • The NVN write watch (wts_handler, nvn_native.c) still unprotects 4 KiB at a time and ignores the result. On a
    native 16 KiB host it needs its own page accounting before use. This PR does not change it.
  • Rollback: revert the two commits. There is no switch and no data to migrate.

Reviewer notes

  • Review 5e072fc8..7731dc0c: two commits (da567be4, 7731dc0c) and no dependency. Start with darwin_fault.c,
    then the four Apple branches in win32_posix.c, then the test's source cut and its fault instructions.
  • The second commit only makes the test native by default, documents the dual-route command and counts crash reports
    as regular files.
  • Written from Apple's SDK headers (sys/_types/_ucontext.h, mach/arm/_structs.h, mach/i386/_structs.h), the
    sigaction(2) and sigaltstack(2) pages, and the Arm and Intel architecture manuals for the syndrome bits. Nothing was
    copied from another project.

Issues

See #420.

Checklist

For everyone

  • One topic.
  • The title is a Conventional Commit (type(scope): description).
  • Rebased on current main. Based on 5e072fc8.
  • python -I -B tests/run_all.py run and passing (any failure that also fails on main is named above). On macOS:
    526 passed, 17 skipped, and the same 19 failures as main, named under Testing.
  • Tested in the game, or what wasn't is named under Testing. No game run; named under Testing.
  • Testing filled in: what was tested, what wasn't, and what needs other testers (with test requests). The test
    requests are still to open.
  • Measured: main and this PR at named commits, on the same PC, scene and settings, at least 2 interleaved runs each,
    the median reported. For a fix: the new test fails on main and passes here, in two interleaved runs each.
  • No game files, dumps, build output or generated files.
  • third_party/ untouched (a new folder with a PROVENANCE.md instead).
  • The license is OK: GPL-3.0-or-later, nothing incompatible and nothing decompiled.

If applicable

  • AI assistance recorded with Assisted-by: / Co-Authored-By: trailers.
  • Flags noted, and the switch reference updated (docs/development.md, the README). Test options only, documented in
    docs/development.md.
  • A game-free test added for the fix, failing on main and passing here.
  • Host code builds on both Windows (llvm-mingw) and Linux, with UTF-8 paths. Not built there; requests to open.
  • Docs updated.
**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 reads Linux'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`) with `darwin_fault.c` and makes real faults on pages it owns. On `main` the bridge does not compile (44 errors per architecture). Here it passes on arm64 and on x86_64 under 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, an Intel 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_signal` and `install_signals` (`host/posix/win32_posix.c`) read `uc->uc_mcontext.gregs[REG_*]`, and the access kind from `REG_ERR`. - On Darwin, `uc_mcontext` is a pointer to a per-architecture `__darwin_mcontext64`, with the registers in `__ss` and the fault syndrome in `__es`. There is no `gregs`, `greg_t` or `REG_*`. - 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'`, then `no member named 'gregs' in 'struct __darwin_mcontext64'` and `use 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`) and `report_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.c` and `.h` (new)**, all inside `#if defined(__APPLE__)`: - `darwin_fault_context_from` / `_to`. On x86_64 they carry every x64 integer register, `Rip`, `Rsp` and `EFlags` from and to `__ss`. On arm64 only `Rip` (the PC) and `Rsp` (the SP) are carried, with `CONTEXT_CONTROL`. The x64-only integer, 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_NODEFER` with an empty mask, plus a per-thread guard. A fault inside a callback ends the process with `_exit(128 + signal)` instead of dispatching again. Synchronous faults stay deliverable, 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 of `darwin_fault.h` and four `#if defined(__APPLE__)` branches, in `context_from`, `context_to`, `on_signal`'s access kind and `install_signals`. Each `#else` is `main`'s text unchanged. 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):** builds `darwin_fault.c` and the exception section cut verbatim from `win32_posix.c`, with the SDK and the layer's own headers. Its fixture makes real faults on owned anonymous pages and checks: - read and write addresses and access kinds, the PC in the faulting function, and the SP at entry; - a retry after the handler repairs the page, and a resume at a PC the handler changed; - a live x86 integer-register edit, and that arm64's other state comes back untouched; - vector order and removal, the unhandled filter, and both one- and three-argument previous handlers; - a child that makes its handler's own stack inaccessible must end, not loop. It builds the running Python's architecture by default, so the ordinary suite needs no Rosetta. Repeated `--arch` selects both routes; `--source` builds another tree's bridge. It skips on Windows and prints `SKIP` on Linux. - **`docs/development.md`:** a "macOS fault-context bridge" section with the context's meaning, the limits and the dual-route command. - **Unchanged:** profiler sampling and watchpoints, fibers, guest memory, the NVN write watch, the renderer, the build scripts and `third_party/`. `darwin_fault.c` is not in `nx2/build.py`'s source lists yet; the later macOS build PR in this series (`build(macos): classify Darwin helpers …`) adds every `darwin_*.c`. ## Flags and settings No game, renderer or build switch. The new test's own options: | Flag | What it does | Default now | Recommendation | Helps on | Doesn't help on | Untested on | |---|---|---|---|---|---|---| | `--arch` (repeatable) | the fixture's targets | the running Python's architecture | both, `--arch arm64 --arch x86_64`, to qualify the two routes where Rosetta is installed | macOS | Windows, Linux | an Intel Mac | | `--source` | the tree whose bridge is built | this checkout | a tree of `main`, for the failing side | comparisons | | | | `--rounds` | faults per case | 256 | keep 256 | | | | | `--case` (repeatable) | `matrix`, `plain`, `handler-stack` | all three | all three | debugging one case | | | | `--out` | keeps the fixture's source, binaries and logs in a new folder | a temporary folder | set it for evidence | | | | ## Measurements main: `5e072fc8c2adadba5ea610148be1c47800ba0cca` (2026-11-14) this PR: `mac/fault-contexts` @ `7731dc0c18a34207c265a654064d0b6b52a390be` Hardware: 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`, on `main`'s tree (`--source`, a clean checkout of `5e072fc8`) and on this PR, interleaved: main, PR, main, PR. Then `tests/run_all.py --jobs 6` on this PR, compared by test name with a `main` run 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>`. | | main @ 5e072fc8 | this PR @ 7731dc0c | |---|---|---| | `tests/test_darwin_fault_context.py`, run 1 | exit 1: the bridge does not compile, 44 errors on arm64 and 44 on x86_64 | exit 0: 6 of 6 cases pass | | `tests/test_darwin_fault_context.py`, run 2 | exit 1: the same | exit 0: 6 of 6 cases pass | | real faults per run, arm64 (16 KiB pages) | none: no binary | 2,048 recovered, plus the handler-stack child | | real faults per run, x86_64 under Rosetta (4 KiB pages) | none: no binary | 2,048 recovered, plus the handler-stack child | | full game-free suite | 525 passed, 19 failed, 17 skipped | 526 passed, 19 failed, 17 skipped | ``` main @ 5e072fc8 (each run): 44 errors generated. FAIL arm64 bridge compilation 44 errors generated. FAIL x86_64 bridge compilation FAIL Darwin fault bridge this PR @ 7731dc0c (each run): HANDLER_STACK entered=1 on_alternate_stack=1 PASS arm64 handler-stack ROUNDTRIP pc_sp=1 integer_contract=1 untouched_machine_state=1 syndrome_classes=1 FAULT_BRIDGE mode=matrix rounds=256 page=16384 faults=1792 chained=512 SIGBUS=256 SIGSEGV=256 pass=1 PASS arm64 matrix FAULT_BRIDGE mode=plain rounds=256 page=16384 faults=256 chained=256 SIGBUS=0 SIGSEGV=0 pass=1 PASS arm64 plain HANDLER_STACK entered=1 on_alternate_stack=1 PASS x86_64 handler-stack FAULT_BRIDGE mode=matrix rounds=256 page=4096 faults=1792 chained=512 SIGBUS=256 SIGSEGV=256 pass=1 PASS x86_64 matrix FAULT_BRIDGE mode=plain rounds=256 page=4096 faults=256 chained=256 SIGBUS=0 SIGSEGV=0 pass=1 PASS x86_64 plain PASS Darwin fault bridge ``` The 44 errors are 43 from the Linux context fields and one unused parameter under the test's `-Werror`. `plain` mode does not count signals by kind, so its zeros are not "no signals"; `matrix` counts the real SIGBUS and SIGSEGV deliveries. The handler-stack child ended by SIGILL natively (`exit=-4`) and by the guard's `128 + SIGBUS` under Rosetta (`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). The waits 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 macOS `linux`): ``` main @ 5e072fc8: 525 passed, 19 failed, 17 skipped in 177 s (linux, 6 at once) this PR @ 7731dc0c: 526 passed, 19 failed, 17 skipped in 233 s (linux, 6 at once) ``` - By test name, the only difference is the new `tests/test_darwin_fault_context.py` (pass, native arm64 only, as `run_all` runs it). No other check changed status. - The same 19 checks fail on both. They are macOS portability failures of `main`, which other macOS pull requests address: `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`. - No new crash report during the suite, and none during the four runs above (114 files before and after). **Not tested:** - Windows and Linux. The non-Apple text of `win32_posix.c` and `windows.h` is `main`'s; that is not a build there. - A macOS build or game run, a whole process with its renderer, the guest allocator's first-touch commits and the crash report's module lookup. The macOS build needs the other wave-1 PRs. - macOS 14.4 itself, an Intel Mac and arm64e. The deployment target is 14.4; it ran only on macOS 26.7.1. - An execute fault: the access kind 8 and the unrelated syndromes are checked with constructed contexts, not with a real fetch from non-executable memory. - Faults on many threads at once, handler registration racing a fault, all SIMD/FP registers seeded, and a handler that moves the SP to another stack. **Needs other testers:** - Windows (llvm-mingw): build the host and run the game-free suite; expect no change, and the new test skipped (request to open). - Linux: build the host and run the game-free suite; expect no change, and the new test printing `SKIP` (request to open). - macOS 14.4 on Apple Silicon: `python -I -B tests/test_darwin_fault_context.py --arch arm64 --arch x86_64`; expect `PASS Darwin fault bridge` (request to open). ## Screenshots Nothing on screen. ## Risk and rollback - **Windows and Linux:** no change in behaviour. Every new line is inside `#if defined(__APPLE__)` or in a new file. - **macOS, arm64:** this is not a full Win32 ARM64 `CONTEXT`. A caller that edits or reads an x64 integer register gets zero there. No caller does today (see Problem). - **The guard** assumes callbacks return normally, previous handlers included. It does not make logging, unwinding, locks or lazy TLS async-signal-safe. - **The NVN write watch** (`wts_handler`, `nvn_native.c`) still unprotects 4 KiB at a time and ignores the result. On a native 16 KiB host it needs its own page accounting before use. This PR does not change it. - **Rollback:** revert the two commits. There is no switch and no data to migrate. ## Reviewer notes - Review `5e072fc8..7731dc0c`: two commits (`da567be4`, `7731dc0c`) and no dependency. Start with `darwin_fault.c`, then the four Apple branches in `win32_posix.c`, then the test's source cut and its fault instructions. - The second commit only makes the test native by default, documents the dual-route command and counts crash reports as regular files. - Written from Apple's SDK headers (`sys/_types/_ucontext.h`, `mach/arm/_structs.h`, `mach/i386/_structs.h`), the `sigaction(2)` and `sigaltstack(2)` pages, and the Arm and Intel architecture manuals for the syndrome bits. Nothing was copied from another project. ## Issues See #420. ## Checklist ### For everyone - [x] One topic. - [x] The title is a Conventional Commit (`type(scope): description`). - [x] Rebased on current `main`. Based on 5e072fc8. - [x] `python -I -B tests/run_all.py` run and passing (any failure that also fails on `main` is named above). On macOS: 526 passed, 17 skipped, and the same 19 failures as `main`, named under Testing. - [x] Tested in the game, or what wasn't is named under Testing. No game run; named under Testing. - [ ] Testing filled in: what was tested, what wasn't, and what needs other testers (with test requests). The test requests are still to open. - [x] Measured: main and this PR at named commits, on the same PC, scene and settings, at least 2 interleaved runs each, the median reported. For a `fix`: the new test fails on `main` and passes here, in two interleaved runs each. - [x] No game files, dumps, build output or generated files. - [x] `third_party/` untouched (a new folder with a `PROVENANCE.md` instead). - [x] The license is OK: GPL-3.0-or-later, nothing incompatible and nothing decompiled. ### If applicable - [x] AI assistance recorded with `Assisted-by:` / `Co-Authored-By:` trailers. - [x] Flags noted, and the switch reference updated (docs/development.md, the README). Test options only, documented in `docs/development.md`. - [x] A game-free test added for the fix, failing on `main` and passing here. - [ ] Host code builds on both Windows (llvm-mingw) and Linux, with UTF-8 paths. Not built there; requests to open. - [x] Docs updated.
Decode Darwin signal contexts and access faults for arm64 and x86_64,
copy supported context edits back on signal return, and prevent recursive
callback dispatch when a fault handler itself faults. Keep the non-Apple
bridge text and the x64-shaped CONTEXT layout unchanged.

Add source-linked, game-free owned-memory fault tests for both targets,
including page repair, PC redirection, prior handlers and an inaccessible
handler stack. Document the native PC/SP contract and remaining limits.

Assisted-by: GPT-6 Astra
Co-Authored-By: GPT-6 Astra <[email protected]>
Assisted-by: Claude Opus 5.5
Co-Authored-By: Claude Opus 5.5 <[email protected]>
test(runtime): keep Darwin fault checks native by default
All checks were successful
CI / Python source checks (ubuntu-24.04, part 1/2) (pull_request) Has been skipped
CI / Python source checks (ubuntu-24.04, part 2/2) (pull_request) Has been skipped
CI / SPIR-V fixtures (global narrow load/store, direct surface atomics, F2I immediate) (pull_request) Has been skipped
Third-party parts / The same as upstream (pull_request) Has been skipped
CI / Conventional Commit title (pull_request) Successful in 4s
PR description / Description follows the template (pull_request_target) Successful in 5s
CI / Workflow lint (actionlint) (pull_request) Successful in 7s
Third-party parts / As their notes say (pull_request) Successful in 11s
Website / Website on Cloudflare (pull_request_target) Has been skipped
7731dc0c18
Do not make the ordinary Apple Silicon suite depend on Rosetta. Default
to the running Python process's architecture and retain explicit repeated
--arch options for dual-route qualification. Document that command and
count only files when reporting crash-report directory changes.

Assisted-by: GPT-6 Astra
Co-Authored-By: GPT-6 Astra <[email protected]>
Assisted-by: Claude Opus 5.5
Co-Authored-By: Claude Opus 5.5 <[email protected]>
All checks were successful
CI / Python source checks (ubuntu-24.04, part 1/2) (pull_request) Has been skipped
CI / Python source checks (ubuntu-24.04, part 2/2) (pull_request) Has been skipped
CI / SPIR-V fixtures (global narrow load/store, direct surface atomics, F2I immediate) (pull_request) Has been skipped
Third-party parts / The same as upstream (pull_request) Has been skipped
CI / Conventional Commit title (pull_request) Successful in 4s
PR description / Description follows the template (pull_request_target) Successful in 5s
CI / Workflow lint (actionlint) (pull_request) Successful in 7s
Third-party parts / As their notes say (pull_request) Successful in 11s
Website / Website on Cloudflare (pull_request_target) Has been skipped
This pull request is marked as a work in progress.
This branch is out-of-date with the base branch
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u 'mac/fault-contexts:mooki-mac/fault-contexts'
git switch 'mooki-mac/fault-contexts'
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
MagicBeanDev/Longshot!436
No description provided.