From 96d7b56ed30ea42bf861b602006afe678b9f4ea4 Mon Sep 17 00:00:00 2001 From: Heejin Ahn Date: Thu, 19 Feb 2026 00:15:09 +0000 Subject: [PATCH 01/19] [embind] Manage exceptions' states correctly `libemval.js` has two exception-related functions: `_emval_throw` and `_emval_from_current_cxa_exception`, but they haven't been correclty updating various exception-related states. #26276 tried to fix this but it only updated `exceptionLast`. This updates other internal states correctly. Some of them only apply to Emscripten EH, while others apply to both. 1. To make the uncaught exception count updatable for Wasm EH, this adds `___cxa_in/decrement_uncaught_exception` to `cxa_exception_js_utils.cpp`, and also adds a common interface `in/decrementUncaughtExceptionCount` in `libexceptions.js` so that you can call them regardless of which EH is used. For Wasm EH, the management of the uncaught exception count is done within libc++abi functions (`__cxa_throw`, `__cxa_rethrow`, ...), but here in embind we bypass those functions, so we have to manage them directly. 2. To make refcount work correctly, this adds calls to `in/decrementExceptionRefcount` to exception-related functions. Also, this fixes memory leaks by adding the destructor mechanism that decrements refcount when emvals are destroyed. `_emval_from_current_cxa_exception` is similar to `std::current_exception`, which returns `std::exception_ptr`, whose destructor decrements the refcount so that the exception can be destroyed: https://github.com/emscripten-core/emscripten/blob/62e22652509fbe7a00609ce48a653d0d66f27ba5/system/lib/libcxx/src/support/runtime/exception_pointer_cxxabi.ipp#L16 embind didn't have such a mechanism, so exceptions captured by `_emval_from_current_cxa_exception` leaked memory. This also fixes #26290. --- src/lib/libemval.js | 86 +++++++++++++------ src/lib/libexceptions.js | 20 +++++ src/lib/libsigs.js | 2 + .../libcxxabi/src/cxa_exception_js_utils.cpp | 11 +++ ...embind_throw_val_uncaught_and_refcount.cpp | 47 ++++++++++ ...embind_throw_val_uncaught_and_refcount.out | 10 +++ test/test_core.py | 4 + 7 files changed, 154 insertions(+), 26 deletions(-) create mode 100644 test/embind/test_embind_throw_val_uncaught_and_refcount.cpp create mode 100644 test/embind/test_embind_throw_val_uncaught_and_refcount.out diff --git a/src/lib/libemval.js b/src/lib/libemval.js index f2e1b9e42bf07..3a9eaf2c37913 100644 --- a/src/lib/libemval.js +++ b/src/lib/libemval.js @@ -11,6 +11,7 @@ var LibraryEmVal = { // Stack of handles available for reuse. $emval_freelist: [], + $emval_destructors: [], // Array of alternating pairs (value, refcount). // reserve 0 and some special values. These never get de-allocated. $emval_handles: [ @@ -80,13 +81,19 @@ var LibraryEmVal = { } }, - _emval_decref__deps: ['$emval_freelist', '$emval_handles'], + _emval_decref__deps: ['$emval_freelist', '$emval_handles', '$emval_destructors'], _emval_decref: (handle) => { if (handle > {{{ EMVAL_LAST_RESERVED_HANDLE }}} && 0 === --emval_handles[handle + 1]) { #if ASSERTIONS assert(emval_handles[handle] !== undefined, `Decref for unallocated handle.`); #endif + var value = emval_handles[handle]; emval_handles[handle] = undefined; + var destructor = emval_destructors[handle]; + if (destructor) { + emval_destructors[handle] = undefined; + destructor(value); + } emval_freelist.push(handle); } }, @@ -392,37 +399,50 @@ ${functionBody} return delete object[property]; }, - _emval_throw__deps: ['$Emval', -#if !WASM_EXCEPTIONS + _emval_is_cpp_exception__deps: ['$Emval'], + _emval_is_cpp_exception: (object) => { + object = Emval.toValue(object); +#if WASM_EXCEPTIONS + return object instanceof WebAssembly.Exception; +#else +#if EXCEPTION_STACK_TRACES + return object instanceof CppException; +#else + return object === object+0; // Check if it is a number +#endif +#endif + }, + + _emval_throw__deps: ['$Emval', '_emval_is_cpp_exception', +#if !DISABLE_EXCEPTION_THROWING && !WASM_EXCEPTIONS '$exceptionLast', + '$ExceptionInfo', + '$exnToPtr', +#endif +#if !DISABLE_EXCEPTION_THROWING || WASM_EXCEPTIONS + '$incrementUncaughtExceptionCount', +#endif +#if !DISABLE_EXCEPTION_CATCHING || WASM_EXCEPTIONS + '$incrementExceptionRefcount', #endif ], _emval_throw: (object) => { + var orig_object = object; object = Emval.toValue(object); -#if !WASM_EXCEPTIONS - // If we are throwing Emcripten C++ exception, set exceptionLast, as we do - // in __cxa_throw. When EXCEPTION_STACK_TRACES is set, a C++ exception will - // be an instance of EmscriptenEH, and when EXCEPTION_STACK_TRACES is not - // set, it will be a pointer (number). - // - // This is different from __cxa_throw() in libexception.js because - // __cxa_throw() is called from the user C++ code when the 'throw' keyword - // is used, and the value thrown is a C++ pointer. When - // EXCEPTION_STACK_TRACES is true, we wrap it with CppException. But this - // _emval_throw is called when we throw whatever is contained in 'object', - // which can be anything including a CppException object, or a number, or - // other JS object. So we don't use storeException() wrapper here and we - // throw it as is. -#if EXCEPTION_STACK_TRACES - if (object instanceof CppException) { - exceptionLast = object; - } -#else - if (object === object+0) { // Check if it is a number + if (__emval_is_cpp_exception(orig_object)) { +#if !DISABLE_EXCEPTION_THROWING && !WASM_EXCEPTIONS exceptionLast = object; - } + var info = new ExceptionInfo(exnToPtr(object)); + info.set_caught(false); + info.set_rethrown(false); +#endif +#if !DISABLE_EXCEPTION_THROWING || WASM_EXCEPTIONS + incrementUncaughtExceptionCount(); #endif +#if !DISABLE_EXCEPTION_CATCHING || WASM_EXCEPTIONS + incrementExceptionRefcount(object); #endif + } throw object; }, @@ -463,7 +483,12 @@ ${functionBody} })); }, - _emval_from_current_cxa_exception__deps: ['$Emval', '__cxa_rethrow'], + _emval_from_current_cxa_exception__deps: ['$Emval', '__cxa_rethrow', '$emval_destructors', +#if !DISABLE_EXCEPTION_THROWING || WASM_EXCEPTIONS + '$decrementUncaughtExceptionCount', + '$decrementExceptionRefcount', +#endif + ], _emval_from_current_cxa_exception: () => { try { // Use __cxa_rethrow which already has mechanism for generating @@ -472,7 +497,16 @@ ${functionBody} // with metadata optimised out otherwise. ___cxa_rethrow(); } catch (e) { - return Emval.toHandle(e); +#if !DISABLE_EXCEPTION_THROWING || WASM_EXCEPTIONS + // ___cxa_rethrow incremented uncaughtExceptionCount. + // Since we caught it in JS, we need to manually decrement it to balance. + decrementUncaughtExceptionCount(); +#endif + var handle = Emval.toHandle(e); +#if !DISABLE_EXCEPTION_THROWING || WASM_EXCEPTIONS + emval_destructors[handle] = decrementExceptionRefcount; +#endif + return handle; } }, }; diff --git a/src/lib/libexceptions.js b/src/lib/libexceptions.js index fd84b2e8114b6..462c76aa5019f 100644 --- a/src/lib/libexceptions.js +++ b/src/lib/libexceptions.js @@ -341,6 +341,16 @@ var LibraryExceptions = { return ___thrown_object_from_unwind_exception(unwind_header); }, + $incrementUncaughtExceptionCount__deps: ['__cxa_increment_uncaught_exception'], + $incrementUncaughtExceptionCount: () => { + ___cxa_increment_uncaught_exception(); + }, + + $decrementUncaughtExceptionCount__deps: ['__cxa_decrement_uncaught_exception'], + $decrementUncaughtExceptionCount: () => { + ___cxa_decrement_uncaught_exception(); + }, + $incrementExceptionRefcount__deps: ['__cxa_increment_exception_refcount', '$getCppExceptionThrownObjectFromWebAssemblyException'], $incrementExceptionRefcount: (ex) => { var ptr = getCppExceptionThrownObjectFromWebAssemblyException(ex); @@ -371,6 +381,16 @@ var LibraryExceptions = { return exn; }, + $incrementUncaughtExceptionCount__deps: ['$uncaughtExceptionCount'], + $incrementUncaughtExceptionCount: () => { + uncaughtExceptionCount++; + }, + + $decrementUncaughtExceptionCount__deps: ['$uncaughtExceptionCount'], + $decrementUncaughtExceptionCount: () => { + uncaughtExceptionCount--; + }, + $incrementExceptionRefcount__deps: ['$exnToPtr', '__cxa_increment_exception_refcount'], $incrementExceptionRefcount: (exn) => ___cxa_increment_exception_refcount(exnToPtr(exn)), diff --git a/src/lib/libsigs.js b/src/lib/libsigs.js index bb990b3f9c142..5a9e5f6628547 100644 --- a/src/lib/libsigs.js +++ b/src/lib/libsigs.js @@ -222,6 +222,8 @@ sigs = { __cxa_current_exception_type__sig: 'p', __cxa_current_primary_exception__sig: 'p', __cxa_end_catch__sig: 'v', + __cxa_decrement_uncaught_exception__sig: 'v', + __cxa_increment_uncaught_exception__sig: 'v', __cxa_rethrow__sig: 'v', __cxa_rethrow_primary_exception__sig: 'vp', __cxa_throw__sig: 'vppp', diff --git a/system/lib/libcxxabi/src/cxa_exception_js_utils.cpp b/system/lib/libcxxabi/src/cxa_exception_js_utils.cpp index 05d908d45577d..a01629e8ddbf4 100644 --- a/system/lib/libcxxabi/src/cxa_exception_js_utils.cpp +++ b/system/lib/libcxxabi/src/cxa_exception_js_utils.cpp @@ -123,6 +123,17 @@ char* __get_exception_terminate_message(void* thrown_object) { free(type); return result; } + +#ifdef __WASM_EXCEPTIONS__ +void __cxa_increment_uncaught_exception() throw() { + __cxa_get_globals()->uncaughtExceptions += 1; +} + +void __cxa_decrement_uncaught_exception() throw() { + __cxa_get_globals()->uncaughtExceptions -= 1; +} +#endif + } // extern "C" } // namespace __cxxabiv1 diff --git a/test/embind/test_embind_throw_val_uncaught_and_refcount.cpp b/test/embind/test_embind_throw_val_uncaught_and_refcount.cpp new file mode 100644 index 0000000000000..c4e3baa0c88ec --- /dev/null +++ b/test/embind/test_embind_throw_val_uncaught_and_refcount.cpp @@ -0,0 +1,47 @@ +#include +#include +#include + +using namespace emscripten; + +int main() { + val error = val::null(); + try { + throw std::runtime_error("test exception"); + } catch (const std::exception& e) { + // Capture the exception + error = val::take_ownership( + emscripten::internal::_emval_from_current_cxa_exception()); + } + + std::cout << "Captured exception." << std::endl; + + int uncaught_before = std::uncaught_exceptions(); + std::cout << "Uncaught before throw 1: " << uncaught_before << std::endl; + + // First throw + try { + std::cout << "Throwing 1..." << std::endl; + error.throw_(); + } catch (const std::exception& e) { + std::cout << "Caught 1: " << e.what() << std::endl; + int uncaught_during = std::uncaught_exceptions(); + std::cout << "Uncaught during catch 1: " << uncaught_during << std::endl; + } + + int uncaught_between = std::uncaught_exceptions(); + std::cout << "Uncaught between throws: " << uncaught_between << std::endl; + + // Second throw - if refcount was messed up, this might fail/crash + try { + std::cout << "Throwing 2..." << std::endl; + error.throw_(); + } catch (const std::exception& e) { + std::cout << "Caught 2: " << e.what() << std::endl; + int uncaught_during = std::uncaught_exceptions(); + std::cout << "Uncaught during catch 2: " << uncaught_during << std::endl; + } + + std::cout << "Done." << std::endl; + return 0; +} diff --git a/test/embind/test_embind_throw_val_uncaught_and_refcount.out b/test/embind/test_embind_throw_val_uncaught_and_refcount.out new file mode 100644 index 0000000000000..eb2c80ecbc4e3 --- /dev/null +++ b/test/embind/test_embind_throw_val_uncaught_and_refcount.out @@ -0,0 +1,10 @@ +Captured exception. +Uncaught before throw 1: 0 +Throwing 1... +Caught 1: test exception +Uncaught during catch 1: 0 +Uncaught between throws: 0 +Throwing 2... +Caught 2: test exception +Uncaught during catch 2: 0 +Done. diff --git a/test/test_core.py b/test/test_core.py index 7f2766fb1464e..9976eec64e153 100644 --- a/test/test_core.py +++ b/test/test_core.py @@ -7778,6 +7778,10 @@ def test_embind_wasm_workers(self): def test_embind_throw_cpp_exception(self): self.do_run_in_out_file_test('embind/test_embind_throw_cpp_exception.cpp', cflags=['-lembind', '-std=c++20']) + @with_all_eh_sjlj + def test_embind_throw_val_uncaught_and_refcount(self): + self.do_run_in_out_file_test('embind/test_embind_throw_val_uncaught_and_refcount.cpp', cflags=['-lembind', '-std=c++20']) + @parameterized({ '': ('DEFAULT', False), 'all': ('ALL', False), From c83c7a0538ace0c246629f2e3c87caadc40731cd Mon Sep 17 00:00:00 2001 From: Heejin Ahn Date: Sun, 22 Mar 2026 19:24:26 +0000 Subject: [PATCH 02/19] Automatic rebaseline of codesize expectations. NFC This is an automatic change generated by tools/maint/rebaseline_tests.py. The following (2) test expectation files were updated by running the tests with `--rebaseline`: ``` codesize/test_minimal_runtime_code_size_hello_embind.json: 14909 => 14961 [+52 bytes / +0.35%] codesize/test_minimal_runtime_code_size_hello_embind_val.json: 11642 => 11695 [+53 bytes / +0.46%] Average change: +0.40% (+0.35% - +0.46%) ``` --- .../test_minimal_runtime_code_size_hello_embind.json | 8 ++++---- .../test_minimal_runtime_code_size_hello_embind_val.json | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/test/codesize/test_minimal_runtime_code_size_hello_embind.json b/test/codesize/test_minimal_runtime_code_size_hello_embind.json index 6f8026cfbadb0..a76340ae3eb55 100644 --- a/test/codesize/test_minimal_runtime_code_size_hello_embind.json +++ b/test/codesize/test_minimal_runtime_code_size_hello_embind.json @@ -1,10 +1,10 @@ { "a.html": 548, "a.html.gz": 371, - "a.js": 7262, - "a.js.gz": 3324, + "a.js": 7314, + "a.js.gz": 3347, "a.wasm": 7099, "a.wasm.gz": 3257, - "total": 14909, - "total_gz": 6952 + "total": 14961, + "total_gz": 6975 } diff --git a/test/codesize/test_minimal_runtime_code_size_hello_embind_val.json b/test/codesize/test_minimal_runtime_code_size_hello_embind_val.json index 1140b98f22baa..7126e080feb41 100644 --- a/test/codesize/test_minimal_runtime_code_size_hello_embind_val.json +++ b/test/codesize/test_minimal_runtime_code_size_hello_embind_val.json @@ -1,10 +1,10 @@ { "a.html": 548, "a.html.gz": 371, - "a.js": 5353, - "a.js.gz": 2524, + "a.js": 5406, + "a.js.gz": 2544, "a.wasm": 5741, "a.wasm.gz": 2690, - "total": 11642, - "total_gz": 5585 + "total": 11695, + "total_gz": 5605 } From 2b321531ddf5eb34f3e6be52a69176d9a17f8adb Mon Sep 17 00:00:00 2001 From: Heejin Ahn Date: Sun, 22 Mar 2026 19:31:00 +0000 Subject: [PATCH 03/19] Make several Emscripten EH JS function available at DISABLE_EXCEPTION_THROWING --- src/lib/libexceptions.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib/libexceptions.js b/src/lib/libexceptions.js index 462c76aa5019f..368d21a57461f 100644 --- a/src/lib/libexceptions.js +++ b/src/lib/libexceptions.js @@ -284,7 +284,7 @@ var LibraryExceptions = { }, #endif -#if WASM_EXCEPTIONS || !DISABLE_EXCEPTION_CATCHING +#if WASM_EXCEPTIONS || !DISABLE_EXCEPTION_THROWING $getExceptionMessageCommon__deps: ['__get_exception_message', 'free', '$stackSave', '$stackRestore', '$stackAlloc'], $getExceptionMessageCommon: (ptr) => { var sp = stackSave(); @@ -369,7 +369,7 @@ var LibraryExceptions = { return getExceptionMessageCommon(ptr); }, -#elif !DISABLE_EXCEPTION_CATCHING +#elif !DISABLE_EXCEPTION_THROWING // When EXCEPTION_STACK_TRACES is set, the exception is an instance of // CppException, whereas EXCEPTION_STACK_TRACES is unset it is a raw pointer. $exnToPtr: (exn) => { From 0172b722f171c7151de049a0ee208ded3f3d1a81 Mon Sep 17 00:00:00 2001 From: Heejin Ahn Date: Mon, 23 Mar 2026 01:23:13 +0000 Subject: [PATCH 04/19] _emval_is_cpp_exception -> $emval_is_cpp_exception --- src/lib/libemval.js | 8 ++++---- src/lib/libsigs.js | 2 -- system/lib/libcxxabi/include/cxxabi.h | 3 +++ 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/lib/libemval.js b/src/lib/libemval.js index 3a9eaf2c37913..eb236709a24c3 100644 --- a/src/lib/libemval.js +++ b/src/lib/libemval.js @@ -399,8 +399,8 @@ ${functionBody} return delete object[property]; }, - _emval_is_cpp_exception__deps: ['$Emval'], - _emval_is_cpp_exception: (object) => { + $emval_is_cpp_exception__deps: ['$Emval'], + $emval_is_cpp_exception: (object) => { object = Emval.toValue(object); #if WASM_EXCEPTIONS return object instanceof WebAssembly.Exception; @@ -413,7 +413,7 @@ ${functionBody} #endif }, - _emval_throw__deps: ['$Emval', '_emval_is_cpp_exception', + _emval_throw__deps: ['$Emval', '$emval_is_cpp_exception', #if !DISABLE_EXCEPTION_THROWING && !WASM_EXCEPTIONS '$exceptionLast', '$ExceptionInfo', @@ -429,7 +429,7 @@ ${functionBody} _emval_throw: (object) => { var orig_object = object; object = Emval.toValue(object); - if (__emval_is_cpp_exception(orig_object)) { + if (emval_is_cpp_exception(orig_object)) { #if !DISABLE_EXCEPTION_THROWING && !WASM_EXCEPTIONS exceptionLast = object; var info = new ExceptionInfo(exnToPtr(object)); diff --git a/src/lib/libsigs.js b/src/lib/libsigs.js index 5a9e5f6628547..bb990b3f9c142 100644 --- a/src/lib/libsigs.js +++ b/src/lib/libsigs.js @@ -222,8 +222,6 @@ sigs = { __cxa_current_exception_type__sig: 'p', __cxa_current_primary_exception__sig: 'p', __cxa_end_catch__sig: 'v', - __cxa_decrement_uncaught_exception__sig: 'v', - __cxa_increment_uncaught_exception__sig: 'v', __cxa_rethrow__sig: 'v', __cxa_rethrow_primary_exception__sig: 'vp', __cxa_throw__sig: 'vppp', diff --git a/system/lib/libcxxabi/include/cxxabi.h b/system/lib/libcxxabi/include/cxxabi.h index 9ea93680f62e0..fc950de558eb6 100644 --- a/system/lib/libcxxabi/include/cxxabi.h +++ b/system/lib/libcxxabi/include/cxxabi.h @@ -176,6 +176,9 @@ __cxa_increment_exception_refcount(void *primary_exception) _LIBCXXABI_NOEXCEPT; extern _LIBCXXABI_FUNC_VIS void __cxa_decrement_exception_refcount(void *primary_exception) _LIBCXXABI_NOEXCEPT; +extern _LIBCXXABI_FUNC_VIS void __cxa_increment_uncaught_exception() _LIBCXXABI_NOEXCEPT; +extern _LIBCXXABI_FUNC_VIS void __cxa_decrement_uncaught_exception() _LIBCXXABI_NOEXCEPT; + // Apple extension to support std::uncaught_exception() extern _LIBCXXABI_FUNC_VIS bool __cxa_uncaught_exception() _LIBCXXABI_NOEXCEPT; extern _LIBCXXABI_FUNC_VIS unsigned int __cxa_uncaught_exceptions() _LIBCXXABI_NOEXCEPT; From ccd80940d770bc528b08a803b59a6dbedc1452ef Mon Sep 17 00:00:00 2001 From: Heejin Ahn Date: Mon, 23 Mar 2026 01:50:56 +0000 Subject: [PATCH 05/19] Revert unnecessary changes --- system/lib/libcxxabi/include/cxxabi.h | 3 --- 1 file changed, 3 deletions(-) diff --git a/system/lib/libcxxabi/include/cxxabi.h b/system/lib/libcxxabi/include/cxxabi.h index fc950de558eb6..9ea93680f62e0 100644 --- a/system/lib/libcxxabi/include/cxxabi.h +++ b/system/lib/libcxxabi/include/cxxabi.h @@ -176,9 +176,6 @@ __cxa_increment_exception_refcount(void *primary_exception) _LIBCXXABI_NOEXCEPT; extern _LIBCXXABI_FUNC_VIS void __cxa_decrement_exception_refcount(void *primary_exception) _LIBCXXABI_NOEXCEPT; -extern _LIBCXXABI_FUNC_VIS void __cxa_increment_uncaught_exception() _LIBCXXABI_NOEXCEPT; -extern _LIBCXXABI_FUNC_VIS void __cxa_decrement_uncaught_exception() _LIBCXXABI_NOEXCEPT; - // Apple extension to support std::uncaught_exception() extern _LIBCXXABI_FUNC_VIS bool __cxa_uncaught_exception() _LIBCXXABI_NOEXCEPT; extern _LIBCXXABI_FUNC_VIS unsigned int __cxa_uncaught_exceptions() _LIBCXXABI_NOEXCEPT; From 1d0c0282df7d08671e0ddd6e4b1534f0006be4d4 Mon Sep 17 00:00:00 2001 From: Heejin Ahn Date: Tue, 24 Mar 2026 05:02:53 +0000 Subject: [PATCH 06/19] Fix is_cpp_exception after #26523 --- src/lib/libemval.js | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/lib/libemval.js b/src/lib/libemval.js index eb236709a24c3..33b89d2035972 100644 --- a/src/lib/libemval.js +++ b/src/lib/libemval.js @@ -402,16 +402,13 @@ ${functionBody} $emval_is_cpp_exception__deps: ['$Emval'], $emval_is_cpp_exception: (object) => { object = Emval.toValue(object); -#if WASM_EXCEPTIONS - return object instanceof WebAssembly.Exception; -#else -#if EXCEPTION_STACK_TRACES +#if !DISABLE_EXCEPTION_CATCHING return object instanceof CppException; -#else - return object === object+0; // Check if it is a number -#endif +#elif WASM_EXCEPTIONS + return object instanceof WebAssembly.Exception; #endif - }, + return false; +j }, _emval_throw__deps: ['$Emval', '$emval_is_cpp_exception', #if !DISABLE_EXCEPTION_THROWING && !WASM_EXCEPTIONS From 6400263017a34a1ab3a1d5dd8cab2de32e6fd936 Mon Sep 17 00:00:00 2001 From: Heejin Ahn Date: Thu, 26 Mar 2026 22:57:30 +0000 Subject: [PATCH 07/19] DISABLE_EXCEPTION_CATCHING/THROWING fixes --- src/lib/libemval.js | 25 +++++++++++++++++++------ src/lib/libexceptions.js | 2 +- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/src/lib/libemval.js b/src/lib/libemval.js index 33b89d2035972..7e3b2fa134905 100644 --- a/src/lib/libemval.js +++ b/src/lib/libemval.js @@ -11,7 +11,9 @@ var LibraryEmVal = { // Stack of handles available for reuse. $emval_freelist: [], +#if !DISABLE_EXCEPTION_CATCHING || WASM_EXCEPTIONS $emval_destructors: [], +#endif // Array of alternating pairs (value, refcount). // reserve 0 and some special values. These never get de-allocated. $emval_handles: [ @@ -81,7 +83,11 @@ var LibraryEmVal = { } }, - _emval_decref__deps: ['$emval_freelist', '$emval_handles', '$emval_destructors'], + _emval_decref__deps: ['$emval_freelist', '$emval_handles', +#if !DISABLE_EXCEPTION_CATCHING || WASM_EXCEPTIONS + '$emval_destructors', +#endif + ], _emval_decref: (handle) => { if (handle > {{{ EMVAL_LAST_RESERVED_HANDLE }}} && 0 === --emval_handles[handle + 1]) { #if ASSERTIONS @@ -89,11 +95,13 @@ var LibraryEmVal = { #endif var value = emval_handles[handle]; emval_handles[handle] = undefined; +#if !DISABLE_EXCEPTION_CATCHING || WASM_EXCEPTIONS var destructor = emval_destructors[handle]; if (destructor) { emval_destructors[handle] = undefined; destructor(value); } +#endif emval_freelist.push(handle); } }, @@ -412,9 +420,10 @@ j }, _emval_throw__deps: ['$Emval', '$emval_is_cpp_exception', #if !DISABLE_EXCEPTION_THROWING && !WASM_EXCEPTIONS - '$exceptionLast', '$ExceptionInfo', - '$exnToPtr', +#endif +#if !DISABLE_EXCEPTION_CATCHING && !WASM_EXCEPTIONS + '$exceptionLast', #endif #if !DISABLE_EXCEPTION_THROWING || WASM_EXCEPTIONS '$incrementUncaughtExceptionCount', @@ -428,11 +437,13 @@ j }, object = Emval.toValue(object); if (emval_is_cpp_exception(orig_object)) { #if !DISABLE_EXCEPTION_THROWING && !WASM_EXCEPTIONS - exceptionLast = object; - var info = new ExceptionInfo(exnToPtr(object)); + var info = new ExceptionInfo(object.excPtr); info.set_caught(false); info.set_rethrown(false); #endif +#if !DISABLE_EXCEPTION_CATCHING && !WASM_EXCEPTIONS + exceptionLast = object; +#endif #if !DISABLE_EXCEPTION_THROWING || WASM_EXCEPTIONS incrementUncaughtExceptionCount(); #endif @@ -483,6 +494,8 @@ j }, _emval_from_current_cxa_exception__deps: ['$Emval', '__cxa_rethrow', '$emval_destructors', #if !DISABLE_EXCEPTION_THROWING || WASM_EXCEPTIONS '$decrementUncaughtExceptionCount', +#endif +#if !DISABLE_EXCEPTION_CATCHING || WASM_EXCEPTIONS '$decrementExceptionRefcount', #endif ], @@ -500,7 +513,7 @@ j }, decrementUncaughtExceptionCount(); #endif var handle = Emval.toHandle(e); -#if !DISABLE_EXCEPTION_THROWING || WASM_EXCEPTIONS +#if !DISABLE_EXCEPTION_CATCHING || WASM_EXCEPTIONS emval_destructors[handle] = decrementExceptionRefcount; #endif return handle; diff --git a/src/lib/libexceptions.js b/src/lib/libexceptions.js index c72cd927a2804..359cecf748e47 100644 --- a/src/lib/libexceptions.js +++ b/src/lib/libexceptions.js @@ -309,7 +309,7 @@ var LibraryExceptions = { }, #endif -#if WASM_EXCEPTIONS || !DISABLE_EXCEPTION_THROWING +#if WASM_EXCEPTIONS || !DISABLE_EXCEPTION_CATCHING $getExceptionMessageCommon__deps: ['__get_exception_message', 'free', '$stackSave', '$stackRestore', '$stackAlloc'], $getExceptionMessageCommon: (ptr) => { var sp = stackSave(); From 7cb05fb88d4f685dc9c6f726162f7c8b0afdf1e9 Mon Sep 17 00:00:00 2001 From: Heejin Ahn Date: Fri, 27 Mar 2026 00:30:23 +0000 Subject: [PATCH 08/19] Revive in/decrementUncaughtExceptionCount for Emscripten EH --- src/lib/libexceptions.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/lib/libexceptions.js b/src/lib/libexceptions.js index 359cecf748e47..e896347f99c65 100644 --- a/src/lib/libexceptions.js +++ b/src/lib/libexceptions.js @@ -395,6 +395,16 @@ var LibraryExceptions = { }, #elif !DISABLE_EXCEPTION_CATCHING + $incrementUncaughtExceptionCount__deps: ['$uncaughtExceptionCount'], + $incrementUncaughtExceptionCount: () => { + uncaughtExceptionCount++; + }, + + $decrementUncaughtExceptionCount__deps: ['$uncaughtExceptionCount'], + $decrementUncaughtExceptionCount: () => { + uncaughtExceptionCount--; + }, + $incrementExceptionRefcount__deps: ['__cxa_increment_exception_refcount'], $incrementExceptionRefcount: (exn) => ___cxa_increment_exception_refcount(exn.excPtr), From 10866acefd9393ea51c0cf01c48eb6b14769e447 Mon Sep 17 00:00:00 2001 From: Heejin Ahn Date: Fri, 27 Mar 2026 00:40:28 +0000 Subject: [PATCH 09/19] Expose in/decrementUncaughtExceptionCount when !DISABLE_EXCEPTION_THROWING --- src/lib/libexceptions.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/lib/libexceptions.js b/src/lib/libexceptions.js index e896347f99c65..95360fea4f818 100644 --- a/src/lib/libexceptions.js +++ b/src/lib/libexceptions.js @@ -394,7 +394,8 @@ var LibraryExceptions = { return getExceptionMessageCommon(ptr); }, -#elif !DISABLE_EXCEPTION_CATCHING +#else +#if !DISABLE_EXCEPTION_THROWING $incrementUncaughtExceptionCount__deps: ['$uncaughtExceptionCount'], $incrementUncaughtExceptionCount: () => { uncaughtExceptionCount++; @@ -404,7 +405,9 @@ var LibraryExceptions = { $decrementUncaughtExceptionCount: () => { uncaughtExceptionCount--; }, +#endif +#if !DISABLE_EXCEPTION_CATCHING $incrementExceptionRefcount__deps: ['__cxa_increment_exception_refcount'], $incrementExceptionRefcount: (exn) => ___cxa_increment_exception_refcount(exn.excPtr), @@ -414,6 +417,7 @@ var LibraryExceptions = { $getExceptionMessage__deps: ['$getExceptionMessageCommon'], $getExceptionMessage: (exn) => getExceptionMessageCommon(exn.excPtr), +#endif #endif }; From cb69eab255152a349194f0a79840a79529c6f004 Mon Sep 17 00:00:00 2001 From: Heejin Ahn Date: Fri, 27 Mar 2026 01:12:54 +0000 Subject: [PATCH 10/19] Automatic rebaseline of codesize expectations. NFC This is an automatic change generated by tools/maint/rebaseline_tests.py. The following (2) test expectation files were updated by running the tests with `--rebaseline`: ``` codesize/test_minimal_runtime_code_size_hello_embind.json: 14961 => 14909 [-52 bytes / -0.35%] codesize/test_minimal_runtime_code_size_hello_embind_val.json: 11695 => 11642 [-53 bytes / -0.45%] Average change: -0.40% (-0.45% - -0.35%) ``` --- .../test_minimal_runtime_code_size_hello_embind.json | 8 ++++---- .../test_minimal_runtime_code_size_hello_embind_val.json | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/test/codesize/test_minimal_runtime_code_size_hello_embind.json b/test/codesize/test_minimal_runtime_code_size_hello_embind.json index a76340ae3eb55..6f8026cfbadb0 100644 --- a/test/codesize/test_minimal_runtime_code_size_hello_embind.json +++ b/test/codesize/test_minimal_runtime_code_size_hello_embind.json @@ -1,10 +1,10 @@ { "a.html": 548, "a.html.gz": 371, - "a.js": 7314, - "a.js.gz": 3347, + "a.js": 7262, + "a.js.gz": 3324, "a.wasm": 7099, "a.wasm.gz": 3257, - "total": 14961, - "total_gz": 6975 + "total": 14909, + "total_gz": 6952 } diff --git a/test/codesize/test_minimal_runtime_code_size_hello_embind_val.json b/test/codesize/test_minimal_runtime_code_size_hello_embind_val.json index 7126e080feb41..1140b98f22baa 100644 --- a/test/codesize/test_minimal_runtime_code_size_hello_embind_val.json +++ b/test/codesize/test_minimal_runtime_code_size_hello_embind_val.json @@ -1,10 +1,10 @@ { "a.html": 548, "a.html.gz": 371, - "a.js": 5406, - "a.js.gz": 2544, + "a.js": 5353, + "a.js.gz": 2524, "a.wasm": 5741, "a.wasm.gz": 2690, - "total": 11695, - "total_gz": 5605 + "total": 11642, + "total_gz": 5585 } From 8ddb43b2bca5a70b3758ead1d78353fe90df8db9 Mon Sep 17 00:00:00 2001 From: Heejin Ahn Date: Fri, 27 Mar 2026 01:27:37 +0000 Subject: [PATCH 11/19] __cxa_in/decrement_uncaught_exception -> __in/decrement_uncaught_exception --- src/lib/libexceptions.js | 8 ++++---- system/lib/libcxxabi/src/cxa_exception_js_utils.cpp | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/lib/libexceptions.js b/src/lib/libexceptions.js index 95360fea4f818..afeaadd576a6d 100644 --- a/src/lib/libexceptions.js +++ b/src/lib/libexceptions.js @@ -366,14 +366,14 @@ var LibraryExceptions = { return ___thrown_object_from_unwind_exception(unwind_header); }, - $incrementUncaughtExceptionCount__deps: ['__cxa_increment_uncaught_exception'], + $incrementUncaughtExceptionCount__deps: ['__increment_uncaught_exception'], $incrementUncaughtExceptionCount: () => { - ___cxa_increment_uncaught_exception(); + ___increment_uncaught_exception(); }, - $decrementUncaughtExceptionCount__deps: ['__cxa_decrement_uncaught_exception'], + $decrementUncaughtExceptionCount__deps: ['__decrement_uncaught_exception'], $decrementUncaughtExceptionCount: () => { - ___cxa_decrement_uncaught_exception(); + ___decrement_uncaught_exception(); }, $incrementExceptionRefcount__deps: ['__cxa_increment_exception_refcount', '$getCppExceptionThrownObjectFromWebAssemblyException'], diff --git a/system/lib/libcxxabi/src/cxa_exception_js_utils.cpp b/system/lib/libcxxabi/src/cxa_exception_js_utils.cpp index a01629e8ddbf4..dcc63364acee9 100644 --- a/system/lib/libcxxabi/src/cxa_exception_js_utils.cpp +++ b/system/lib/libcxxabi/src/cxa_exception_js_utils.cpp @@ -125,11 +125,11 @@ char* __get_exception_terminate_message(void* thrown_object) { } #ifdef __WASM_EXCEPTIONS__ -void __cxa_increment_uncaught_exception() throw() { +void __increment_uncaught_exception() throw() { __cxa_get_globals()->uncaughtExceptions += 1; } -void __cxa_decrement_uncaught_exception() throw() { +void __decrement_uncaught_exception() throw() { __cxa_get_globals()->uncaughtExceptions -= 1; } #endif From c808c408ac22d9a4ace798194319c790c38c00df Mon Sep 17 00:00:00 2001 From: Heejin Ahn Date: Fri, 27 Mar 2026 01:43:22 +0000 Subject: [PATCH 12/19] emval_destructors -> emval_exception_decrefs --- src/lib/libemval.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/lib/libemval.js b/src/lib/libemval.js index 7e3b2fa134905..01c7300ae103a 100644 --- a/src/lib/libemval.js +++ b/src/lib/libemval.js @@ -12,7 +12,7 @@ var LibraryEmVal = { // Stack of handles available for reuse. $emval_freelist: [], #if !DISABLE_EXCEPTION_CATCHING || WASM_EXCEPTIONS - $emval_destructors: [], + $emval_exception_decrefs: [], #endif // Array of alternating pairs (value, refcount). // reserve 0 and some special values. These never get de-allocated. @@ -85,7 +85,7 @@ var LibraryEmVal = { _emval_decref__deps: ['$emval_freelist', '$emval_handles', #if !DISABLE_EXCEPTION_CATCHING || WASM_EXCEPTIONS - '$emval_destructors', + '$emval_exception_decrefs', #endif ], _emval_decref: (handle) => { @@ -96,9 +96,11 @@ var LibraryEmVal = { var value = emval_handles[handle]; emval_handles[handle] = undefined; #if !DISABLE_EXCEPTION_CATCHING || WASM_EXCEPTIONS - var destructor = emval_destructors[handle]; + // In case the value is a C++ exception, decrement the refcount, so the + // memory can be freed correctly + var destructor = emval_exception_decrefs[handle]; if (destructor) { - emval_destructors[handle] = undefined; + emval_exception_decrefs[handle] = undefined; destructor(value); } #endif @@ -491,7 +493,7 @@ j }, })); }, - _emval_from_current_cxa_exception__deps: ['$Emval', '__cxa_rethrow', '$emval_destructors', + _emval_from_current_cxa_exception__deps: ['$Emval', '__cxa_rethrow', '$emval_exception_decrefs', #if !DISABLE_EXCEPTION_THROWING || WASM_EXCEPTIONS '$decrementUncaughtExceptionCount', #endif @@ -514,7 +516,7 @@ j }, #endif var handle = Emval.toHandle(e); #if !DISABLE_EXCEPTION_CATCHING || WASM_EXCEPTIONS - emval_destructors[handle] = decrementExceptionRefcount; + emval_exception_decrefs[handle] = decrementExceptionRefcount; #endif return handle; } From 0139122080c037d89fa965f56ca8fcb8d9bfbf55 Mon Sep 17 00:00:00 2001 From: Heejin Ahn Date: Fri, 27 Mar 2026 01:48:30 +0000 Subject: [PATCH 13/19] isCppExceptionObject + Don't do Emval.toValue --- src/lib/libemval.js | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/lib/libemval.js b/src/lib/libemval.js index 01c7300ae103a..34507194f038a 100644 --- a/src/lib/libemval.js +++ b/src/lib/libemval.js @@ -409,9 +409,8 @@ ${functionBody} return delete object[property]; }, - $emval_is_cpp_exception__deps: ['$Emval'], - $emval_is_cpp_exception: (object) => { - object = Emval.toValue(object); + $isCppExceptionObject__deps: ['$Emval'], + $isCppExceptionObject: (object) => { #if !DISABLE_EXCEPTION_CATCHING return object instanceof CppException; #elif WASM_EXCEPTIONS @@ -420,7 +419,7 @@ ${functionBody} return false; j }, - _emval_throw__deps: ['$Emval', '$emval_is_cpp_exception', + _emval_throw__deps: ['$Emval', '$isCppExceptionObject', #if !DISABLE_EXCEPTION_THROWING && !WASM_EXCEPTIONS '$ExceptionInfo', #endif @@ -435,9 +434,8 @@ j }, #endif ], _emval_throw: (object) => { - var orig_object = object; object = Emval.toValue(object); - if (emval_is_cpp_exception(orig_object)) { + if (isCppExceptionObject(object)) { #if !DISABLE_EXCEPTION_THROWING && !WASM_EXCEPTIONS var info = new ExceptionInfo(object.excPtr); info.set_caught(false); From 1712f3bf5f12bbc83b14f17807ecb00ec7325244 Mon Sep 17 00:00:00 2001 From: Heejin Ahn Date: Fri, 27 Mar 2026 04:22:17 +0000 Subject: [PATCH 14/19] Remove stray character --- src/lib/libemval.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/libemval.js b/src/lib/libemval.js index 34507194f038a..56e46f5a83017 100644 --- a/src/lib/libemval.js +++ b/src/lib/libemval.js @@ -417,7 +417,7 @@ ${functionBody} return object instanceof WebAssembly.Exception; #endif return false; -j }, + }, _emval_throw__deps: ['$Emval', '$isCppExceptionObject', #if !DISABLE_EXCEPTION_THROWING && !WASM_EXCEPTIONS From 074cb6ee873b063faff11719d30b635b43bea42a Mon Sep 17 00:00:00 2001 From: Heejin Ahn Date: Fri, 27 Mar 2026 04:22:40 +0000 Subject: [PATCH 15/19] Fix dep --- src/lib/libemval.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/lib/libemval.js b/src/lib/libemval.js index 56e46f5a83017..5d4dc0d5ea843 100644 --- a/src/lib/libemval.js +++ b/src/lib/libemval.js @@ -491,12 +491,13 @@ ${functionBody} })); }, - _emval_from_current_cxa_exception__deps: ['$Emval', '__cxa_rethrow', '$emval_exception_decrefs', + _emval_from_current_cxa_exception__deps: ['$Emval', '__cxa_rethrow', #if !DISABLE_EXCEPTION_THROWING || WASM_EXCEPTIONS '$decrementUncaughtExceptionCount', #endif #if !DISABLE_EXCEPTION_CATCHING || WASM_EXCEPTIONS '$decrementExceptionRefcount', + '$emval_exception_decrefs', #endif ], _emval_from_current_cxa_exception: () => { From fa085a0bee1ca08e095ca60bb57592c362fae927 Mon Sep 17 00:00:00 2001 From: Heejin Ahn Date: Fri, 27 Mar 2026 04:38:48 +0000 Subject: [PATCH 16/19] Simplify _emval_throw and isCppExceptionObject --- src/lib/libemval.js | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/lib/libemval.js b/src/lib/libemval.js index 5d4dc0d5ea843..85acc8af18df8 100644 --- a/src/lib/libemval.js +++ b/src/lib/libemval.js @@ -409,15 +409,16 @@ ${functionBody} return delete object[property]; }, +#if !DISABLE_EXCEPTION_CATCHING || WASM_EXCEPTIONS $isCppExceptionObject__deps: ['$Emval'], $isCppExceptionObject: (object) => { #if !DISABLE_EXCEPTION_CATCHING return object instanceof CppException; -#elif WASM_EXCEPTIONS +#else // WASM_EXCEPTIONS return object instanceof WebAssembly.Exception; #endif - return false; }, +#endif _emval_throw__deps: ['$Emval', '$isCppExceptionObject', #if !DISABLE_EXCEPTION_THROWING && !WASM_EXCEPTIONS @@ -435,19 +436,15 @@ ${functionBody} ], _emval_throw: (object) => { object = Emval.toValue(object); +#if !DISABLE_EXCEPTION_CATCHING || WASM_EXCEPTIONS if (isCppExceptionObject(object)) { -#if !DISABLE_EXCEPTION_THROWING && !WASM_EXCEPTIONS +#if !DISABLE_EXCEPTION_CATCHING var info = new ExceptionInfo(object.excPtr); info.set_caught(false); info.set_rethrown(false); -#endif -#if !DISABLE_EXCEPTION_CATCHING && !WASM_EXCEPTIONS exceptionLast = object; #endif -#if !DISABLE_EXCEPTION_THROWING || WASM_EXCEPTIONS incrementUncaughtExceptionCount(); -#endif -#if !DISABLE_EXCEPTION_CATCHING || WASM_EXCEPTIONS incrementExceptionRefcount(object); #endif } From b3a57f58d144e819c99519dbfe40b0f66365699b Mon Sep 17 00:00:00 2001 From: Heejin Ahn Date: Fri, 27 Mar 2026 05:05:34 +0000 Subject: [PATCH 17/19] Fix deps --- src/lib/libemval.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/lib/libemval.js b/src/lib/libemval.js index 85acc8af18df8..716b8faa227f1 100644 --- a/src/lib/libemval.js +++ b/src/lib/libemval.js @@ -420,7 +420,10 @@ ${functionBody} }, #endif - _emval_throw__deps: ['$Emval', '$isCppExceptionObject', + _emval_throw__deps: ['$Emval', +#if !DISABLE_EXCEPTION_CATCHING || WASM_EXCEPTIONS + '$isCppExceptionObject', +#endif #if !DISABLE_EXCEPTION_THROWING && !WASM_EXCEPTIONS '$ExceptionInfo', #endif @@ -446,8 +449,8 @@ ${functionBody} #endif incrementUncaughtExceptionCount(); incrementExceptionRefcount(object); -#endif } +#endif throw object; }, From c4724dff121593b1302430a2f4f7a956f26cf1f6 Mon Sep 17 00:00:00 2001 From: Heejin Ahn Date: Fri, 27 Mar 2026 05:09:01 +0000 Subject: [PATCH 18/19] Simplify more --- src/lib/libemval.js | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/src/lib/libemval.js b/src/lib/libemval.js index 716b8faa227f1..ada9d19e87529 100644 --- a/src/lib/libemval.js +++ b/src/lib/libemval.js @@ -422,19 +422,13 @@ ${functionBody} _emval_throw__deps: ['$Emval', #if !DISABLE_EXCEPTION_CATCHING || WASM_EXCEPTIONS - '$isCppExceptionObject', -#endif -#if !DISABLE_EXCEPTION_THROWING && !WASM_EXCEPTIONS - '$ExceptionInfo', -#endif -#if !DISABLE_EXCEPTION_CATCHING && !WASM_EXCEPTIONS +#if !DISABLE_EXCEPTION_CATCHING '$exceptionLast', + '$ExceptionInfo', #endif -#if !DISABLE_EXCEPTION_THROWING || WASM_EXCEPTIONS - '$incrementUncaughtExceptionCount', -#endif -#if !DISABLE_EXCEPTION_CATCHING || WASM_EXCEPTIONS '$incrementExceptionRefcount', + '$incrementUncaughtExceptionCount', + '$isCppExceptionObject', #endif ], _emval_throw: (object) => { From f8c4137edb13d402176ef76683472ba9afe6510a Mon Sep 17 00:00:00 2001 From: Heejin Ahn Date: Fri, 27 Mar 2026 05:39:37 +0000 Subject: [PATCH 19/19] Change in/decrementUncaughtExceptionCount to aliases --- src/lib/libexceptions.js | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/lib/libexceptions.js b/src/lib/libexceptions.js index afeaadd576a6d..b04ebf7d3ad4e 100644 --- a/src/lib/libexceptions.js +++ b/src/lib/libexceptions.js @@ -367,14 +367,10 @@ var LibraryExceptions = { }, $incrementUncaughtExceptionCount__deps: ['__increment_uncaught_exception'], - $incrementUncaughtExceptionCount: () => { - ___increment_uncaught_exception(); - }, + $incrementUncaughtExceptionCount: '__increment_uncaught_exception', $decrementUncaughtExceptionCount__deps: ['__decrement_uncaught_exception'], - $decrementUncaughtExceptionCount: () => { - ___decrement_uncaught_exception(); - }, + $decrementUncaughtExceptionCount: '__decrement_uncaught_exception', $incrementExceptionRefcount__deps: ['__cxa_increment_exception_refcount', '$getCppExceptionThrownObjectFromWebAssemblyException'], $incrementExceptionRefcount: (ex) => {