diff --git a/src/lib/libcore.js b/src/lib/libcore.js index 3fd173a480749..cd6f66dee51fe 100644 --- a/src/lib/libcore.js +++ b/src/lib/libcore.js @@ -1733,31 +1733,7 @@ addToLibrary({ $jstoi_q__docs: '/** @suppress {checkTypes} */', $jstoi_q: (str) => parseInt(str), -#if LINK_AS_CXX - // libunwind - _Unwind_Backtrace__deps: ['$getCallstack'], - _Unwind_Backtrace: (func, arg) => { - var trace = getCallstack(); - var parts = trace.split('\n'); - for (var i = 0; i < parts.length; i++) { - var ret = {{{ makeDynCall('iii', 'func') }}}(0, arg); - if (ret) return; - } - }, - - _Unwind_GetIPInfo: (context, ipBefore) => abort('Unwind_GetIPInfo'), - - _Unwind_FindEnclosingFunction: (ip) => 0, // we cannot succeed - - _Unwind_RaiseException__deps: ['__cxa_throw'], - _Unwind_RaiseException: (ex) => { - err('Warning: _Unwind_RaiseException is not correctly implemented'); - return ___cxa_throw(ex, 0, 0); - }, - - _Unwind_DeleteException: (ex) => err('TODO: Unwind_DeleteException'), -#endif // special runtime support diff --git a/src/lib/libexceptions.js b/src/lib/libexceptions.js index 0f4c8d9dd21ad..e52ab43daef16 100644 --- a/src/lib/libexceptions.js +++ b/src/lib/libexceptions.js @@ -6,10 +6,6 @@ var LibraryExceptions = { #if !WASM_EXCEPTIONS - $uncaughtExceptionCount: '0', -#if !DISABLE_EXCEPTION_CATCHING - $exceptionLast: null, -#endif $exceptionCaught: ' []', // This class is the exception metadata which is prepended to each thrown object (in WASM memory). @@ -84,7 +80,7 @@ var LibraryExceptions = { // Here, we throw an exception after recording a couple of values that we need to remember // We also remember that it was the last exception thrown as we need to know that later. - __cxa_throw__deps: ['$ExceptionInfo', '$uncaughtExceptionCount', + __cxa_throw__deps: ['$ExceptionInfo', #if !DISABLE_EXCEPTION_CATCHING '$exceptionLast', '__cxa_increment_exception_refcount', @@ -99,6 +95,7 @@ var LibraryExceptions = { // 'throw' is used here. '$decrementExceptionRefcount', '$incrementExceptionRefcount', #endif + '_Unwind_RaiseException', ], __cxa_throw: (ptr, type, destructor) => { #if EXCEPTION_DEBUG @@ -109,20 +106,20 @@ var LibraryExceptions = { info.init(type, destructor); #if !DISABLE_EXCEPTION_CATCHING ___cxa_increment_exception_refcount(ptr); - exceptionLast = new CppException(ptr); + ptr = new CppException(ptr); #endif - uncaughtExceptionCount++; - {{{ makeThrow() }}} + __Unwind_RaiseException(ptr); }, // This exception will be caught twice, but while begin_catch runs twice, // we early-exit from end_catch when the exception has been rethrown, so // pop that here from the caught exceptions. - __cxa_rethrow__deps: ['$exceptionCaught', '$uncaughtExceptionCount', + __cxa_rethrow__deps: ['$exceptionCaught', #if !DISABLE_EXCEPTION_CATCHING '$exceptionLast', '__cxa_increment_exception_refcount', #endif + '_Unwind_RaiseException', ], __cxa_rethrow: () => { if (!exceptionCaught.length) { @@ -132,16 +129,15 @@ var LibraryExceptions = { var ptr = info.excPtr; info.set_rethrown(true); info.set_caught(false); - uncaughtExceptionCount++; #if !DISABLE_EXCEPTION_CATCHING ___cxa_increment_exception_refcount(ptr); #if EXCEPTION_DEBUG dbg('__cxa_rethrow: ' + [ptrToString(ptr), exceptionLast, 'stack', exceptionCaught]); #endif - exceptionLast = new CppException(ptr); + ptr = new CppException(ptr); #endif - {{{ makeThrow() }}} + __Unwind_RaiseException(ptr); }, llvm_eh_typeid_for: (type) => type, @@ -212,11 +208,12 @@ var LibraryExceptions = { return info.get_type(); }, - __cxa_rethrow_primary_exception__deps: ['$ExceptionInfo', '$uncaughtExceptionCount', + __cxa_rethrow_primary_exception__deps: ['$ExceptionInfo', #if !DISABLE_EXCEPTION_CATCHING '$exceptionLast', '__cxa_increment_exception_refcount', #endif + '_Unwind_RaiseException', ], __cxa_rethrow_primary_exception: (ptr) => { if (!ptr) return; @@ -226,12 +223,11 @@ var LibraryExceptions = { var info = new ExceptionInfo(ptr); info.set_rethrown(true); info.set_caught(false); - uncaughtExceptionCount++; #if !DISABLE_EXCEPTION_CATCHING ___cxa_increment_exception_refcount(ptr); - exceptionLast = new CppException(ptr); + ptr = new CppException(ptr); #endif - {{{ makeThrow('exceptionLast') }}} + __Unwind_RaiseException(ptr); }, // Finds a suitable catch clause for when an exception is thrown. @@ -293,19 +289,20 @@ var LibraryExceptions = { #endif }, + __resumeException__deps: [ #if !DISABLE_EXCEPTION_CATCHING - __resumeException__deps: ['$exceptionLast'], + '$exceptionLast', #endif + '_Unwind_Resume', + ], __resumeException: (ptr) => { #if !DISABLE_EXCEPTION_CATCHING #if EXCEPTION_DEBUG dbg("__resumeException " + [ptrToString(ptr), exceptionLast]); #endif - if (!exceptionLast) { - exceptionLast = new CppException(ptr); - } + ptr = exceptionLast ?? new CppException(ptr); #endif - {{{ makeThrow() }}} + __Unwind_Resume(ptr); }, #endif diff --git a/src/lib/libsigs.js b/src/lib/libsigs.js index 6a17a752a9e49..a6530f05d5962 100644 --- a/src/lib/libsigs.js +++ b/src/lib/libsigs.js @@ -213,7 +213,6 @@ sigs = { _Unwind_DeleteException__sig: 'vp', _Unwind_FindEnclosingFunction__sig: 'pp', _Unwind_GetIPInfo__sig: 'ppp', - _Unwind_RaiseException__sig: 'ip', __asctime_r__sig: 'ppp', __assert_fail__sig: 'vppip', __call_sighandler__sig: 'vpi', diff --git a/src/lib/libunwind.js b/src/lib/libunwind.js new file mode 100644 index 0000000000000..db11f26efbd02 --- /dev/null +++ b/src/lib/libunwind.js @@ -0,0 +1,57 @@ +/** + * @license + * Copyright 2026 The Emscripten Authors + * SPDX-License-Identifier: MIT + */ + +#if WASM_EXCEPTIONS +#error "Internal error! WASM_EXCEPTIONS should not be enabled when including libunwind.js." +#endif + +var LibraryUnwind = { + $uncaughtExceptionCount: '0', +#if !DISABLE_EXCEPTION_CATCHING + $exceptionLast: null, +#endif + + _Unwind_Backtrace__deps: ['$getCallstack'], + _Unwind_Backtrace: (func, arg) => { + var trace = getCallstack(); + var parts = trace.split('\n'); + for (var i = 0; i < parts.length; i++) { + var ret = {{{ makeDynCall('iii', 'func') }}}(0, arg); + if (ret) return; + } + }, + + _Unwind_GetIPInfo: (context, ipBefore) => abort('Unwind_GetIPInfo'), + + _Unwind_FindEnclosingFunction: (ip) => 0, // we cannot succeed + + _Unwind_RaiseException__deps: ['$uncaughtExceptionCount', +#if !DISABLE_EXCEPTION_CATCHING + '$exceptionLast', +#endif + ], + _Unwind_RaiseException: (ex) => { +#if !DISABLE_EXCEPTION_CATCHING + exceptionLast = ex; + uncaughtExceptionCount++; +#endif + {{{ makeThrow('ex') }}} + }, + +#if !DISABLE_EXCEPTION_CATCHING + _Unwind_Resume__deps: ['$exceptionLast'], +#endif + _Unwind_Resume: (ex) => { +#if !DISABLE_EXCEPTION_CATCHING + exceptionLast = ex; +#endif + {{{ makeThrow('ex') }}} + }, + + _Unwind_DeleteException: (ex) => err('TODO: Unwind_DeleteException'), +}; + +addToLibrary(LibraryUnwind); diff --git a/src/modules.mjs b/src/modules.mjs index a7341ef20f3d1..210305178484d 100644 --- a/src/modules.mjs +++ b/src/modules.mjs @@ -71,6 +71,10 @@ function calculateLibraries() { } } + if (!WASM_EXCEPTIONS) { + libraries.push('libunwind.js'); + } + if (!MINIMAL_RUNTIME) { libraries.push('libbrowser.js'); libraries.push('libwget.js'); diff --git a/src/parseTools.mjs b/src/parseTools.mjs index 96622dfceabfd..e4acfc1a4e360 100644 --- a/src/parseTools.mjs +++ b/src/parseTools.mjs @@ -664,7 +664,7 @@ export function makeReturn64(value) { return `(setTempRet0(${pair[1]}), ${pair[0]})`; } -function makeThrow() { +function makeThrow(exc) { if (DISABLE_EXCEPTION_CATCHING) { if (ASSERTIONS) { var assertInfo = @@ -678,7 +678,7 @@ function makeThrow() { return 'abort()'; } } - return 'throw exceptionLast;'; + return `throw ${exc};`; } function charCode(char) { diff --git a/test/codesize/test_codesize_cxx_ctors1.json b/test/codesize/test_codesize_cxx_ctors1.json index 10c01d13f63c6..0757dcd5099d3 100644 --- a/test/codesize/test_codesize_cxx_ctors1.json +++ b/test/codesize/test_codesize_cxx_ctors1.json @@ -1,10 +1,10 @@ { - "a.out.js": 19208, - "a.out.js.gz": 8121, + "a.out.js": 19198, + "a.out.js.gz": 8107, "a.out.nodebug.wasm": 134729, "a.out.nodebug.wasm.gz": 51522, - "total": 153937, - "total_gz": 59643, + "total": 153927, + "total_gz": 59629, "sent": [ "__cxa_throw", "_abort_js", diff --git a/test/codesize/test_codesize_cxx_ctors2.json b/test/codesize/test_codesize_cxx_ctors2.json index 95e7176a9ca7f..d1ae8cc748cf5 100644 --- a/test/codesize/test_codesize_cxx_ctors2.json +++ b/test/codesize/test_codesize_cxx_ctors2.json @@ -1,10 +1,10 @@ { - "a.out.js": 19185, - "a.out.js.gz": 8104, + "a.out.js": 19175, + "a.out.js.gz": 8091, "a.out.nodebug.wasm": 134158, "a.out.nodebug.wasm.gz": 51187, - "total": 153343, - "total_gz": 59291, + "total": 153333, + "total_gz": 59278, "sent": [ "__cxa_throw", "_abort_js", diff --git a/test/codesize/test_codesize_cxx_except.json b/test/codesize/test_codesize_cxx_except.json index 1cae264876594..b0f3ffae962aa 100644 --- a/test/codesize/test_codesize_cxx_except.json +++ b/test/codesize/test_codesize_cxx_except.json @@ -1,10 +1,10 @@ { - "a.out.js": 22895, - "a.out.js.gz": 9075, + "a.out.js": 22904, + "a.out.js.gz": 9078, "a.out.nodebug.wasm": 177195, "a.out.nodebug.wasm.gz": 59099, - "total": 200090, - "total_gz": 68174, + "total": 200099, + "total_gz": 68177, "sent": [ "__cxa_begin_catch", "__cxa_end_catch", diff --git a/test/codesize/test_codesize_cxx_mangle.json b/test/codesize/test_codesize_cxx_mangle.json index 49c646fc343d4..7f2ce633c4573 100644 --- a/test/codesize/test_codesize_cxx_mangle.json +++ b/test/codesize/test_codesize_cxx_mangle.json @@ -1,10 +1,10 @@ { - "a.out.js": 22945, - "a.out.js.gz": 9095, + "a.out.js": 22954, + "a.out.js.gz": 9099, "a.out.nodebug.wasm": 243475, "a.out.nodebug.wasm.gz": 81296, - "total": 266420, - "total_gz": 90391, + "total": 266429, + "total_gz": 90395, "sent": [ "__cxa_begin_catch", "__cxa_end_catch", diff --git a/test/codesize/test_codesize_cxx_noexcept.json b/test/codesize/test_codesize_cxx_noexcept.json index 650da9a60f6c5..dc368be4bfb04 100644 --- a/test/codesize/test_codesize_cxx_noexcept.json +++ b/test/codesize/test_codesize_cxx_noexcept.json @@ -1,10 +1,10 @@ { - "a.out.js": 19208, - "a.out.js.gz": 8121, + "a.out.js": 19198, + "a.out.js.gz": 8107, "a.out.nodebug.wasm": 136639, "a.out.nodebug.wasm.gz": 52140, - "total": 155847, - "total_gz": 60261, + "total": 155837, + "total_gz": 60247, "sent": [ "__cxa_throw", "_abort_js", diff --git a/test/codesize/test_codesize_cxx_wasmfs.json b/test/codesize/test_codesize_cxx_wasmfs.json index b68faf1c9b097..e8d25de16bbc4 100644 --- a/test/codesize/test_codesize_cxx_wasmfs.json +++ b/test/codesize/test_codesize_cxx_wasmfs.json @@ -1,10 +1,10 @@ { - "a.out.js": 6604, - "a.out.js.gz": 3154, + "a.out.js": 6594, + "a.out.js.gz": 3145, "a.out.nodebug.wasm": 174361, "a.out.nodebug.wasm.gz": 64898, - "total": 180965, - "total_gz": 68052, + "total": 180955, + "total_gz": 68043, "sent": [ "__cxa_throw", "_abort_js", diff --git a/test/codesize/test_codesize_hello_O0.json b/test/codesize/test_codesize_hello_O0.json index 8ad48f5be16de..84b78a36b3cac 100644 --- a/test/codesize/test_codesize_hello_O0.json +++ b/test/codesize/test_codesize_hello_O0.json @@ -1,10 +1,10 @@ { - "a.out.js": 23471, - "a.out.js.gz": 8555, + "a.out.js": 23494, + "a.out.js.gz": 8565, "a.out.nodebug.wasm": 15115, "a.out.nodebug.wasm.gz": 7464, - "total": 38586, - "total_gz": 16019, + "total": 38609, + "total_gz": 16029, "sent": [ "fd_write" ], diff --git a/test/codesize/test_codesize_hello_dylink_all.json b/test/codesize/test_codesize_hello_dylink_all.json index cdb63a9263342..a8cc65ed1b26f 100644 --- a/test/codesize/test_codesize_hello_dylink_all.json +++ b/test/codesize/test_codesize_hello_dylink_all.json @@ -1,7 +1,7 @@ { - "a.out.js": 267525, + "a.out.js": 267896, "a.out.nodebug.wasm": 588311, - "total": 855836, + "total": 856207, "sent": [ "IMG_Init", "IMG_Load", @@ -212,6 +212,12 @@ "XSendEvent", "XSetWMHints", "XStoreName", + "_Unwind_Backtrace", + "_Unwind_DeleteException", + "_Unwind_FindEnclosingFunction", + "_Unwind_GetIPInfo", + "_Unwind_RaiseException", + "_Unwind_Resume", "__asctime_r", "__assert_fail", "__call_sighandler", diff --git a/test/codesize/test_codesize_minimal_O0.expected.js b/test/codesize/test_codesize_minimal_O0.expected.js index 1b627a7f0a2cf..3c2b4c0164320 100644 --- a/test/codesize/test_codesize_minimal_O0.expected.js +++ b/test/codesize/test_codesize_minimal_O0.expected.js @@ -1030,6 +1030,7 @@ missingLibrarySymbols.forEach(missingLibrarySymbol) 'emClearImmediate_deps', 'emClearImmediate', 'promiseMap', + 'uncaughtExceptionCount', 'Browser', 'requestFullscreen', 'setCanvasSize', diff --git a/test/codesize/test_codesize_minimal_O0.json b/test/codesize/test_codesize_minimal_O0.json index 5cf6c7417b280..80fb306ad0307 100644 --- a/test/codesize/test_codesize_minimal_O0.json +++ b/test/codesize/test_codesize_minimal_O0.json @@ -1,10 +1,10 @@ { - "a.out.js": 18680, - "a.out.js.gz": 6732, + "a.out.js": 18703, + "a.out.js.gz": 6742, "a.out.nodebug.wasm": 1015, "a.out.nodebug.wasm.gz": 602, - "total": 19695, - "total_gz": 7334, + "total": 19718, + "total_gz": 7344, "sent": [], "imports": [], "exports": [ diff --git a/test/codesize/test_unoptimized_code_size.json b/test/codesize/test_unoptimized_code_size.json index 76798f8761f3a..9e4745e439d93 100644 --- a/test/codesize/test_unoptimized_code_size.json +++ b/test/codesize/test_unoptimized_code_size.json @@ -1,16 +1,16 @@ { - "hello_world.js": 54550, - "hello_world.js.gz": 17328, + "hello_world.js": 54578, + "hello_world.js.gz": 17345, "hello_world.wasm": 15115, "hello_world.wasm.gz": 7464, "no_asserts.js": 23629, "no_asserts.js.gz": 8288, "no_asserts.wasm": 12229, "no_asserts.wasm.gz": 6004, - "strict.js": 51701, - "strict.js.gz": 16338, + "strict.js": 51729, + "strict.js.gz": 16348, "strict.wasm": 15115, "strict.wasm.gz": 7461, - "total": 172339, - "total_gz": 62883 + "total": 172395, + "total_gz": 62910 } diff --git a/test/core/test_exceptions_uncaught_4.cpp b/test/core/test_exceptions_uncaught_4.cpp index b59859936fdcd..b0f9f1468695e 100644 --- a/test/core/test_exceptions_uncaught_4.cpp +++ b/test/core/test_exceptions_uncaught_4.cpp @@ -2,11 +2,22 @@ #include struct DestructorTester { + int id; ~DestructorTester() { - printf("Destructor Uncaught: %d\n", std::uncaught_exceptions()); + printf("Destructor %d Uncaught: %d\n", id, std::uncaught_exceptions()); } }; +void foo(std::exception_ptr p) { + DestructorTester dt1{1}; + std::rethrow_exception(p); +} + +void bar(std::exception_ptr p) { + DestructorTester dt2{2}; + foo(p); +} + int main() { std::exception_ptr p; try { @@ -17,8 +28,7 @@ int main() { printf("Before Uncaught: %d\n", std::uncaught_exceptions()); try { - DestructorTester dt; - std::rethrow_exception(p); + bar(p); } catch (...) { printf("In catch Uncaught: %d\n", std::uncaught_exceptions()); } diff --git a/test/core/test_exceptions_uncaught_4.out b/test/core/test_exceptions_uncaught_4.out index ff68d1b2a2815..a04d70a96c1aa 100644 --- a/test/core/test_exceptions_uncaught_4.out +++ b/test/core/test_exceptions_uncaught_4.out @@ -1,4 +1,5 @@ Before Uncaught: 0 -Destructor Uncaught: 1 +Destructor 1 Uncaught: 1 +Destructor 2 Uncaught: 1 In catch Uncaught: 0 After Uncaught: 0 diff --git a/test/test_other.py b/test/test_other.py index 35087221614db..055a5182d4d72 100644 --- a/test/test_other.py +++ b/test/test_other.py @@ -8832,6 +8832,28 @@ def test_exceptions_c_linker(self): # when not linking as C++. 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'],), + }) + def test_libunwind(self, cflags): + src = r''' + #include + #include + #include + + static struct _Unwind_Exception exc; + + int main() { + printf("About to raise exception...\n"); + _Unwind_RaiseException(&exc); + printf("ERROR: _Unwind_RaiseException returned!\n"); + return 0; + } + ''' + self.do_run(src, 'About to raise exception...\n', cflags=cflags, assert_returncode=NON_ZERO) + @with_all_eh_sjlj @no_bun('https://github.com/emscripten-core/emscripten/issues/26197') def test_exceptions_stack_trace_and_message(self): diff --git a/tools/maint/gen_sig_info.py b/tools/maint/gen_sig_info.py index 52707167a61f3..eb730c768dfc1 100755 --- a/tools/maint/gen_sig_info.py +++ b/tools/maint/gen_sig_info.py @@ -181,6 +181,9 @@ def ignore_symbol(s, cxx): '__stack_pointer', '__stack_high', '__stack_low', # legacy aliases, not callable from native code. 'stackSave', 'stackRestore', 'stackAlloc', 'getTempRet0', 'setTempRet0', + # _Unwind_* functions receive JS CppException objects from JS calls (__cxa_throw), + # so they should not have __sig parameter conversions injected by jsifier. + '_Unwind_RaiseException', '_Unwind_Resume', }: return True return (cxx and s == '__asctime_r') or s.startswith('__cxa_find_matching_catch')