diff --git a/ChangeLog.md b/ChangeLog.md index 2d95be00c92a4..5558107b16a23 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -23,6 +23,10 @@ See docs/process.md for more on how version tagging works. - JavaScript library symbols can now use the `__force` and `__export` decorators to control inclusion and export visibility. (#27436) +- `-fwasm-exceptions` now links the Wasm EH runtime (libunwind and the + `__cpp_exception` tag) independently of C++ linking, so Wasm EH objects from + non-C++ frontends (e.g. rustc, which links via `emcc`) link without + requiring `em++` or `-sDEFAULT_TO_CXX`. 6.0.6 - 08/05/26 ---------------- diff --git a/system/lib/libcxxabi/src/__cpp_exception.S b/system/lib/libcxxabi/src/__cpp_exception.S deleted file mode 100644 index 27dd5a39f0dcf..0000000000000 --- a/system/lib/libcxxabi/src/__cpp_exception.S +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright 2025 The Emscripten Authors. All rights reserved. - * Emscripten is available under two separate licenses, the MIT license and the - * University of Illinois/NCSA Open Source License. Both these licenses can be - * found in the LICENSE file. - * - * Define the `__cpp_exception` Wasm EH tag which is used to implmenent C++ - * exception handling in LLVM. - */ - -#ifdef __wasm_exception_handling__ - -#ifdef __wasm64__ -#define PTR i64 -#else -#define PTR i32 -#endif - -.globl __cpp_exception -.tagtype __cpp_exception PTR -__cpp_exception: - -#endif // !__wasm_exception_handling__ diff --git a/system/lib/libunwind/src/Unwind-wasm.c b/system/lib/libunwind/src/Unwind-wasm.c index a8ce4852eb8e4..6ee4b1fd36ee4 100644 --- a/system/lib/libunwind/src/Unwind-wasm.c +++ b/system/lib/libunwind/src/Unwind-wasm.c @@ -31,6 +31,21 @@ _Unwind_RaiseException(_Unwind_Exception *exception_object) { __builtin_wasm_throw(0, exception_object); } +// Define the `__cpp_exception` symbol which `__builtin_wasm_throw` above will +// reference. This is defined here in `libunwind` as the single canonical +// definition for this API and it's required for users to ensure that there's +// only one copy of `libunwind` within a wasm module to ensure this is only +// defined once and exactly once. +__asm__(".globl __cpp_exception\n" +#if defined(__wasm32__) + ".tagtype __cpp_exception i32\n" +#elif defined(__wasm64__) + ".tagtype __cpp_exception i64\n" +#else +#error "Unsupported Wasm architecture" +#endif + "__cpp_exception:\n"); + /// Called by __cxa_end_catch. _LIBUNWIND_EXPORT void _Unwind_DeleteException(_Unwind_Exception *exception_object) { diff --git a/test/test_other.py b/test/test_other.py index 98023ceb288f7..604257840cf08 100644 --- a/test/test_other.py +++ b/test/test_other.py @@ -8833,9 +8833,10 @@ def test_exceptions_c_linker(self): self.assert_fail([EMCC, '-sSTRICT', test_file('other/test_exceptions_c_linker.c')], 'error: undefined symbol: __cxa_find_matching_catch_1') @parameterized({ - # TODO: Add wasm_eh modes once the libunwind Wasm EH followup PR lands '': ([],), 'exceptions': (['-fexceptions'],), + 'wasm_eh': (['-fwasm-exceptions'],), + 'wasm_legacy_eh': (['-fwasm-exceptions', '-sWASM_LEGACY_EXCEPTIONS'],), }) def test_libunwind(self, cflags): src = r''' @@ -14621,10 +14622,36 @@ def test_wasi_with_sjlj(self): # When using Wasm exception, SUPPORT_LONGJMP defaults to 'wasm', which does # not use the JS-based support. This should succeed. - # -fwasm-exceptions exports __cpp_exception, so this is necessary - self.set_setting('DEFAULT_TO_CXX') self.do_runf('core/test_longjmp.c', cflags=['-fwasm-exceptions']) + def test_cpp_exception_tag(self): + # Wasm EH throw/catch sites reference the `__cpp_exception` tag directly, + # regardless of source language (e.g. rustc objects), so an object whose + # only undefined symbol is the tag must still pull in its libunwind definition. + create_file('throw.S', ''' +.tagtype __cpp_exception i32 +.text +.globl throw_tag +throw_tag: + .functype throw_tag (i32) -> () + local.get 0 + throw __cpp_exception + end_function +''') + create_file('main.c', r''' + #include + void throw_tag(int); + int main(int argc, char* argv[]) { + if (argc > 100) { + throw_tag(argc); + } + printf("done\n"); + return 0; + } + ''') + # -mexception-handling is needed for the assembler to accept `throw`. + self.do_runf('main.c', 'done\n', cflags=['throw.S', '-fwasm-exceptions', '-mexception-handling']) + def test_memory_init_file_unsupported(self): self.assert_fail([EMCC, test_file('hello_world.c'), '-Werror', '--memory-init-file=1'], 'error: --memory-init-file is no longer supported') diff --git a/tools/system_libs.py b/tools/system_libs.py index 0ae34ebc06530..10f743d7c09a0 100644 --- a/tools/system_libs.py +++ b/tools/system_libs.py @@ -1686,7 +1686,6 @@ def get_files(self): 'stdlib_typeinfo.cpp', 'private_typeinfo.cpp', 'cxa_exception_js_utils.cpp', - '__cpp_exception.S', ] match self.eh_mode: case Exceptions.NONE: @@ -2484,8 +2483,13 @@ def add_sanitizer_libs(): add_library('libc++') if settings.LINK_AS_CXX or sanitize: add_library('libc++abi') - if settings.WASM_EXCEPTIONS: - add_library('libunwind') + if settings.WASM_EXCEPTIONS: + # Wasm EH objects can come from any language frontend (e.g. rust), so the + # unwinding runtime and the `__cpp_exception` tag it defines are linked + # independently of C++. When WASM_EXCEPTIONS is not enabled, `_Unwind_*` + # symbols are instead provided by JS stubs in libcore.js (under + # LINK_AS_CXX). + add_library('libunwind') if settings.PROXY_POSIX_SOCKETS: add_library('libsockets_proxy')