Skip to content

Commit 5fddb4b

Browse files
committed
Fix #155: switch WASM builds from legacy Asyncify to JSPI
cmake/Emscripten.cmake set both -fwasm-exceptions (compile + link) and -sASYNCIFY=1 (per-target link), and recent emsdk releases hard-fail the combination in wasm-opt with `__asyncify_get_call_index does not exist`, breaking the wasm.yml workflow. JSPI (JavaScript Promise Integration) is the upstream-recommended replacement for legacy Asyncify and is compatible with native WebAssembly exception handling, so the per-target flag now reads -sJSPI=1 and the no-longer-relevant myproject_WASM_ASYNCIFY_STACK_SIZE cache variable is dropped. A new CMake-script regression test stubs the target/global option commands, includes Emscripten.cmake under EMSCRIPTEN=TRUE, and asserts that the legacy flags are gone, JSPI is emitted, and -fwasm-exceptions remains.
1 parent a3971f5 commit 5fddb4b

3 files changed

Lines changed: 142 additions & 5 deletions

File tree

cmake/Emscripten.cmake

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,6 @@ if(EMSCRIPTEN)
4242
"Initial WASM memory in bytes (default: 32MB)")
4343
set(myproject_WASM_PTHREAD_POOL_SIZE "4" CACHE STRING
4444
"Pthread pool size for WASM builds (default: 4)")
45-
set(myproject_WASM_ASYNCIFY_STACK_SIZE "65536" CACHE STRING
46-
"Asyncify stack size in bytes (default: 64KB)")
4745

4846
# For Emscripten WASM builds, FTXUI requires pthreads and native exception handling
4947
# Set these flags early so they propagate to all dependencies
@@ -89,9 +87,11 @@ function(myproject_configure_wasm_target target)
8987
"-sUSE_PTHREADS=1"
9088
"-sPROXY_TO_PTHREAD=1"
9189
"-sPTHREAD_POOL_SIZE=${myproject_WASM_PTHREAD_POOL_SIZE}"
92-
# Enable asyncify for emscripten_sleep and async operations
93-
"-sASYNCIFY=1"
94-
"-sASYNCIFY_STACK_SIZE=${myproject_WASM_ASYNCIFY_STACK_SIZE}"
90+
# Enable JSPI (JavaScript Promise Integration) for emscripten_sleep
91+
# and other async operations. JSPI is the modern replacement for
92+
# legacy Asyncify (-sASYNCIFY=1) and, unlike Asyncify, is compatible
93+
# with native WebAssembly exception handling (-fwasm-exceptions).
94+
"-sJSPI=1"
9595
# Memory configuration
9696
"-sALLOW_MEMORY_GROWTH=1"
9797
"-sINITIAL_MEMORY=${myproject_WASM_INITIAL_MEMORY}"

test/CMakeLists.txt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,15 @@ add_test(
3434
COMMAND ${CMAKE_COMMAND} -P
3535
${CMAKE_CURRENT_SOURCE_DIR}/cmake/test_sanitizers_non_apple_includes_leak.cmake)
3636

37+
# CMake-script regression test for cmake/Emscripten.cmake (issue #155):
38+
# the legacy `-sASYNCIFY=1` flag is incompatible with `-fwasm-exceptions`
39+
# under modern emsdk; the WASM target must use the JSPI replacement.
40+
add_test(
41+
NAME cmake.emscripten.modern_async
42+
COMMAND ${CMAKE_COMMAND}
43+
-DTEST_TMP_DIR=${CMAKE_CURRENT_BINARY_DIR}/test_emscripten_modern_async
44+
-P ${CMAKE_CURRENT_SOURCE_DIR}/cmake/test_emscripten_modern_async.cmake)
45+
3746
# Provide a test to verify that the version being reported from the application
3847
# matches the version given to CMake. This will be important once you package
3948
# your program. Real world shows that this is the kind of simple mistake that is easy
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
#[[
2+
Regression test for #155: WASM builds were broken because
3+
cmake/Emscripten.cmake combined two flags that newer Emscripten releases
4+
reject in the same translation unit:
5+
6+
* -fwasm-exceptions (set via add_compile_options / add_link_options)
7+
* -sASYNCIFY=1 (set via target_link_options on each WASM target)
8+
9+
The link step ended in `wasm-opt` with
10+
`Fatal: Module::getFunction: __asyncify_get_call_index does not exist`
11+
because the legacy Asyncify transformation is incompatible with native
12+
WebAssembly exception handling.
13+
14+
Per the issue ("Prefer the modern replacement features") the project
15+
should use JSPI (JavaScript Promise Integration) instead, which is the
16+
upstream-recommended replacement for legacy Asyncify and works alongside
17+
-fwasm-exceptions.
18+
19+
This test simulates an EMSCRIPTEN configuration, stubs the CMake commands
20+
that touch real targets, includes Emscripten.cmake, and asserts that:
21+
22+
1. The legacy `-sASYNCIFY=1` flag is NOT emitted (regression guard).
23+
2. The legacy `-sASYNCIFY_STACK_SIZE` flag is NOT emitted (irrelevant
24+
for JSPI).
25+
3. The modern `-sJSPI=1` flag IS emitted on the WASM target.
26+
4. `-fwasm-exceptions` is still active on the global compile/link
27+
options so native EH continues to be used.
28+
]]
29+
30+
cmake_minimum_required(VERSION 3.21)
31+
32+
set(EMSCRIPTEN TRUE)
33+
34+
# Use the real source tree so EXISTS checks against the shell template pass
35+
# and Emscripten.cmake's input templates resolve correctly.
36+
set(CMAKE_SOURCE_DIR "${CMAKE_CURRENT_LIST_DIR}/../..")
37+
38+
# Caller (CTest) supplies an out-of-tree scratch dir via -DTEST_TMP_DIR=...;
39+
# fall back to the platform temp dir when the script is invoked manually.
40+
if(NOT DEFINED TEST_TMP_DIR OR TEST_TMP_DIR STREQUAL "")
41+
set(TEST_TMP_DIR "$ENV{TMPDIR}")
42+
if(TEST_TMP_DIR STREQUAL "")
43+
set(TEST_TMP_DIR "/tmp")
44+
endif()
45+
set(TEST_TMP_DIR "${TEST_TMP_DIR}/cmake_template_test_emscripten_modern_async")
46+
endif()
47+
set(CMAKE_BINARY_DIR "${TEST_TMP_DIR}")
48+
file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}")
49+
50+
set(CAPTURE_FILE "${CMAKE_BINARY_DIR}/captured_options.txt")
51+
file(WRITE "${CAPTURE_FILE}" "")
52+
53+
# Capture global compile / link options emitted at module-include time.
54+
function(add_compile_options)
55+
file(APPEND "${CAPTURE_FILE}" "ADD_COMPILE_OPTIONS:${ARGN}\n")
56+
endfunction()
57+
58+
function(add_link_options)
59+
file(APPEND "${CAPTURE_FILE}" "ADD_LINK_OPTIONS:${ARGN}\n")
60+
endfunction()
61+
62+
# Capture per-target link options. Drop the leading <target> <scope> args
63+
# so the captured payload is easy to grep.
64+
function(target_link_options)
65+
set(_args "${ARGN}")
66+
list(REMOVE_AT _args 0 1)
67+
file(APPEND "${CAPTURE_FILE}" "TARGET_LINK_OPTIONS:${_args}\n")
68+
endfunction()
69+
70+
# Stub out the rest of the target-affecting commands so script mode
71+
# doesn't try to look up a real CMake target.
72+
function(target_compile_definitions)
73+
endfunction()
74+
75+
function(get_target_property var target prop)
76+
set(${var} "${target}" PARENT_SCOPE)
77+
endfunction()
78+
79+
function(set_target_properties)
80+
endfunction()
81+
82+
function(add_custom_command)
83+
endfunction()
84+
85+
function(configure_file)
86+
endfunction()
87+
88+
function(set_property)
89+
endfunction()
90+
91+
include("${CMAKE_CURRENT_LIST_DIR}/../../cmake/Emscripten.cmake")
92+
93+
myproject_configure_wasm_target(
94+
fake_wasm_target
95+
TITLE "Fake"
96+
DESCRIPTION "Fake WASM target for regression test #155")
97+
98+
file(READ "${CAPTURE_FILE}" captures)
99+
message(STATUS "Captured Emscripten options:\n${captures}")
100+
101+
if(captures MATCHES "ASYNCIFY=1")
102+
message(
103+
FATAL_ERROR
104+
"Legacy -sASYNCIFY=1 must not be emitted: it is incompatible with "
105+
"-fwasm-exceptions and breaks WASM linking (#155). Captured:\n${captures}")
106+
endif()
107+
108+
if(captures MATCHES "ASYNCIFY_STACK_SIZE")
109+
message(
110+
FATAL_ERROR
111+
"-sASYNCIFY_STACK_SIZE is only meaningful for legacy Asyncify and "
112+
"should be removed alongside -sASYNCIFY=1 (#155). Captured:\n${captures}")
113+
endif()
114+
115+
if(NOT captures MATCHES "-sJSPI=1")
116+
message(
117+
FATAL_ERROR
118+
"Expected modern -sJSPI=1 flag (replacement for legacy Asyncify) on "
119+
"the WASM target (#155). Captured:\n${captures}")
120+
endif()
121+
122+
if(NOT captures MATCHES "-fwasm-exceptions")
123+
message(
124+
FATAL_ERROR
125+
"-fwasm-exceptions must remain enabled for WASM builds; the fix "
126+
"for #155 should switch async support, not disable native EH. "
127+
"Captured:\n${captures}")
128+
endif()

0 commit comments

Comments
 (0)