From d012a98cf45a40cfcd02db52025b6bc4aa5e2796 Mon Sep 17 00:00:00 2001 From: kevmoo Date: Sat, 14 Mar 2026 13:46:00 -0700 Subject: [PATCH 1/4] Fix wasm2c exception testing on macOS This commit addresses the assertion failures during exception testing in `wasm2c` on macOS: `Assertion failed: (!(ss.ss_flags & SS_ONSTACK) && "attempt to deallocate altstack while in use")` Exception handling in wasm2c uses `wasm_rt_try(target)` which mapped to `sigsetjmp(buf, 1)`. However, on macOS (XNU), performing nested `sigsetjmp(..., 1)` and `siglongjmp` across threads with an allocated alternate signal stack can erroneously cause the kernel to preserve the `SS_ONSTACK` flag in the thread state, even when the exception did not originate from the signal handler. Since WebAssembly exception handling relies strictly on `setjmp`/`longjmp` for control flow and does not need to restore signal masks (unlike stack exhaustion traps, which do need the mask to unblock `SIGSEGV`), the fix is to simply avoid saving the signal mask for exception boundaries. We split `WASM_RT_SETJMP_SETBUF` into two variants: - `WASM_RT_SETJMP_TRAP_SETBUF`: Uses `sigsetjmp(buf, 1)` for traps (safely handles `SIGSEGV`). - `WASM_RT_SETJMP_EXN_SETBUF`: Uses `sigsetjmp(buf, 0)` for Wasm exceptions (bypasses `SS_ONSTACK` bug). This also reverts commit 74cc83c1c8b1d87f9461acb29be086ba7bfb30b7 (which temporarily disabled `macos-latest` in CI), as the tests now pass successfully. Fixes #2654 --- .github/workflows/build.yml | 6 ++---- wasm2c/wasm-rt.h | 29 +++++++++++++++++++++++++---- 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 31204ee7fa..cb84a0beaa 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -30,9 +30,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - # Temporarily replaced macos-latest with macos-14. - # See https://github.com/WebAssembly/wabt/issues/2654 - os: [ubuntu-latest, macos-14, windows-latest] + os: [ubuntu-latest, macos-latest, windows-latest] steps: - uses: actions/setup-python@v5 with: @@ -45,7 +43,7 @@ jobs: if: matrix.os == 'ubuntu-latest' - name: install ninja (osx) run: brew install ninja - if: startsWith(matrix.os, 'macos-') + if: matrix.os == 'macos-13' || matrix.os == 'macos-latest' - name: install ninja (win) run: choco install ninja if: matrix.os == 'windows-latest' diff --git a/wasm2c/wasm-rt.h b/wasm2c/wasm-rt.h index bce33ce8ea..9ae08c22ec 100644 --- a/wasm2c/wasm-rt.h +++ b/wasm2c/wasm-rt.h @@ -572,13 +572,34 @@ typedef struct { } wasm_rt_jmp_buf; #ifndef _WIN32 -#define WASM_RT_SETJMP_SETBUF(buf) sigsetjmp(buf, 1) +#define WASM_RT_SETJMP_TRAP_SETBUF(buf) sigsetjmp(buf, 1) + +/** + * On macOS XNU, there is a bug where nested `sigsetjmp` and `siglongjmp` + * across threads that have an allocated alternate signal stack (`SS_ONSTACK`) + * will erroneously cause the kernel to preserve the `SS_ONSTACK` flag in the + * thread state. This happens even if the exception did not originate from + * the signal handler, leading to an assertion failure when trying to free + * the alternate signal stack. + * + * To bypass this, we use `sigsetjmp(buf, 0)` for Wasm exceptions. Since Wasm + * exceptions are purely for control flow and do not need to restore signal + * masks (unlike Wasm trap handlers recovering from a SIGSEGV), avoiding the + * signal mask prevents XNU from applying the buggy `SS_ONSTACK` persistence. + * + * See: https://github.com/WebAssembly/wabt/issues/2654 + * See: https://github.com/golang/go/issues/44501 + */ +#define WASM_RT_SETJMP_EXN_SETBUF(buf) sigsetjmp(buf, 0) #else -#define WASM_RT_SETJMP_SETBUF(buf) setjmp(buf) +#define WASM_RT_SETJMP_TRAP_SETBUF(buf) setjmp(buf) +#define WASM_RT_SETJMP_EXN_SETBUF(buf) setjmp(buf) #endif #define WASM_RT_SETJMP(buf) \ - ((buf).initialized = true, WASM_RT_SETJMP_SETBUF((buf).buffer)) + ((buf).initialized = true, WASM_RT_SETJMP_TRAP_SETBUF((buf).buffer)) +#define WASM_RT_SETJMP_EXN(buf) \ + ((buf).initialized = true, WASM_RT_SETJMP_EXN_SETBUF((buf).buffer)) #ifndef _WIN32 #define WASM_RT_LONGJMP_UNCHECKED(buf, val) siglongjmp(buf, val) @@ -604,7 +625,7 @@ WASM_RT_NO_RETURN void wasm_rt_trap(wasm_rt_trap_t); /** Return a human readable error string based on a trap type. */ const char* wasm_rt_strerror(wasm_rt_trap_t trap); -#define wasm_rt_try(target) WASM_RT_SETJMP(target) +#define wasm_rt_try(target) WASM_RT_SETJMP_EXN(target) /** WebAssembly's default page size (64 KiB) */ #define WASM_DEFAULT_PAGE_SIZE 65536 From 4f133d2f54f452c7e17e64162b2fd0b6b007b5ca Mon Sep 17 00:00:00 2001 From: kevmoo Date: Sat, 14 Mar 2026 14:06:45 -0700 Subject: [PATCH 2/4] a bit more context --- wasm2c/wasm-rt.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/wasm2c/wasm-rt.h b/wasm2c/wasm-rt.h index 9ae08c22ec..c531e13bc4 100644 --- a/wasm2c/wasm-rt.h +++ b/wasm2c/wasm-rt.h @@ -578,7 +578,8 @@ typedef struct { * On macOS XNU, there is a bug where nested `sigsetjmp` and `siglongjmp` * across threads that have an allocated alternate signal stack (`SS_ONSTACK`) * will erroneously cause the kernel to preserve the `SS_ONSTACK` flag in the - * thread state. This happens even if the exception did not originate from + * thread state. This happens because `siglongjmp` fails to call `_sigunaltstack` + * to clear the kernel state, even if the exception did not originate from * the signal handler, leading to an assertion failure when trying to free * the alternate signal stack. * From b2f27035a944eb3b481e5f52388dd02051bba1a5 Mon Sep 17 00:00:00 2001 From: kevmoo Date: Sat, 14 Mar 2026 14:15:18 -0700 Subject: [PATCH 3/4] make the mac bits a bit more consistent --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index cb84a0beaa..554d4ab47f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -43,7 +43,7 @@ jobs: if: matrix.os == 'ubuntu-latest' - name: install ninja (osx) run: brew install ninja - if: matrix.os == 'macos-13' || matrix.os == 'macos-latest' + if: matrix.os == 'macos-latest' - name: install ninja (win) run: choco install ninja if: matrix.os == 'windows-latest' From 7c273def3a46b869ec9d08ccdf34d58268cc7522 Mon Sep 17 00:00:00 2001 From: kevmoo Date: Sun, 15 Mar 2026 20:41:58 -0700 Subject: [PATCH 4/4] smaller note --- wasm2c/wasm-rt.h | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/wasm2c/wasm-rt.h b/wasm2c/wasm-rt.h index c531e13bc4..c6859f738a 100644 --- a/wasm2c/wasm-rt.h +++ b/wasm2c/wasm-rt.h @@ -578,16 +578,8 @@ typedef struct { * On macOS XNU, there is a bug where nested `sigsetjmp` and `siglongjmp` * across threads that have an allocated alternate signal stack (`SS_ONSTACK`) * will erroneously cause the kernel to preserve the `SS_ONSTACK` flag in the - * thread state. This happens because `siglongjmp` fails to call `_sigunaltstack` - * to clear the kernel state, even if the exception did not originate from - * the signal handler, leading to an assertion failure when trying to free - * the alternate signal stack. - * - * To bypass this, we use `sigsetjmp(buf, 0)` for Wasm exceptions. Since Wasm - * exceptions are purely for control flow and do not need to restore signal - * masks (unlike Wasm trap handlers recovering from a SIGSEGV), avoiding the - * signal mask prevents XNU from applying the buggy `SS_ONSTACK` persistence. - * + * thread state + * * See: https://github.com/WebAssembly/wabt/issues/2654 * See: https://github.com/golang/go/issues/44501 */