Skip to content

Commit e415cc0

Browse files
committed
[Emscripten-EH] Move JS unwinding implementation to src/lib/libunwind.js
Extract `_Unwind_*` functions and `uncaughtExceptionCount` / `exceptionLast` JS state from libcore.js and libexceptions.js into a dedicated `libunwind.js`. This new libunwind.js is included as long as wasm EH is not being used. Also, invert the dependency between `__cxa_throw` and `_Unwind_RaiseException` so that `__cxa_throw` now calls `_Unwind_RaiseException` rather than the other way around. This is important as it allows C programs (or rather non-C++ programs like Rust) to call `_Unwind_RaiseException` without linking as C++. Inspired by #27496
1 parent 4946c02 commit e415cc0

5 files changed

Lines changed: 79 additions & 31 deletions

File tree

src/lib/libcore.js

Lines changed: 0 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1733,31 +1733,7 @@ addToLibrary({
17331733
$jstoi_q__docs: '/** @suppress {checkTypes} */',
17341734
$jstoi_q: (str) => parseInt(str),
17351735

1736-
#if LINK_AS_CXX
1737-
// libunwind
17381736

1739-
_Unwind_Backtrace__deps: ['$getCallstack'],
1740-
_Unwind_Backtrace: (func, arg) => {
1741-
var trace = getCallstack();
1742-
var parts = trace.split('\n');
1743-
for (var i = 0; i < parts.length; i++) {
1744-
var ret = {{{ makeDynCall('iii', 'func') }}}(0, arg);
1745-
if (ret) return;
1746-
}
1747-
},
1748-
1749-
_Unwind_GetIPInfo: (context, ipBefore) => abort('Unwind_GetIPInfo'),
1750-
1751-
_Unwind_FindEnclosingFunction: (ip) => 0, // we cannot succeed
1752-
1753-
_Unwind_RaiseException__deps: ['__cxa_throw'],
1754-
_Unwind_RaiseException: (ex) => {
1755-
err('Warning: _Unwind_RaiseException is not correctly implemented');
1756-
return ___cxa_throw(ex, 0, 0);
1757-
},
1758-
1759-
_Unwind_DeleteException: (ex) => err('TODO: Unwind_DeleteException'),
1760-
#endif
17611737

17621738
// special runtime support
17631739

src/lib/libexceptions.js

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,6 @@
66

77
var LibraryExceptions = {
88
#if !WASM_EXCEPTIONS
9-
$uncaughtExceptionCount: '0',
10-
#if !DISABLE_EXCEPTION_CATCHING
11-
$exceptionLast: null,
12-
#endif
139
$exceptionCaught: ' []',
1410

1511
// This class is the exception metadata which is prepended to each thrown object (in WASM memory).
@@ -84,7 +80,7 @@ var LibraryExceptions = {
8480

8581
// Here, we throw an exception after recording a couple of values that we need to remember
8682
// We also remember that it was the last exception thrown as we need to know that later.
87-
__cxa_throw__deps: ['$ExceptionInfo', '$uncaughtExceptionCount',
83+
__cxa_throw__deps: ['$ExceptionInfo',
8884
#if !DISABLE_EXCEPTION_CATCHING
8985
'$exceptionLast',
9086
'__cxa_increment_exception_refcount',
@@ -99,6 +95,7 @@ var LibraryExceptions = {
9995
// 'throw' is used here.
10096
'$decrementExceptionRefcount', '$incrementExceptionRefcount',
10197
#endif
98+
'_Unwind_RaiseException',
10299
],
103100
__cxa_throw: (ptr, type, destructor) => {
104101
#if EXCEPTION_DEBUG
@@ -111,8 +108,7 @@ var LibraryExceptions = {
111108
___cxa_increment_exception_refcount(ptr);
112109
exceptionLast = new CppException(ptr);
113110
#endif
114-
uncaughtExceptionCount++;
115-
{{{ makeThrow() }}}
111+
__Unwind_RaiseException(ptr);
116112
},
117113

118114
// This exception will be caught twice, but while begin_catch runs twice,

src/lib/libunwind.js

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
/**
2+
* @license
3+
* Copyright 2026 The Emscripten Authors
4+
* SPDX-License-Identifier: MIT
5+
*/
6+
7+
#if WASM_EXCEPTIONS
8+
#error "Internal error! WASM_EXCEPTIONS should not be enabled when including libunwind.js."
9+
#endif
10+
11+
var LibraryUnwind = {
12+
$uncaughtExceptionCount: '0',
13+
#if !DISABLE_EXCEPTION_CATCHING
14+
$exceptionLast: null,
15+
#endif
16+
17+
_Unwind_Backtrace__deps: ['$getCallstack'],
18+
_Unwind_Backtrace: (func, arg) => {
19+
var trace = getCallstack();
20+
var parts = trace.split('\n');
21+
for (var i = 0; i < parts.length; i++) {
22+
var ret = {{{ makeDynCall('iii', 'func') }}}(0, arg);
23+
if (ret) return;
24+
}
25+
},
26+
27+
_Unwind_GetIPInfo: (context, ipBefore) => abort('Unwind_GetIPInfo'),
28+
29+
_Unwind_FindEnclosingFunction: (ip) => 0, // we cannot succeed
30+
31+
_Unwind_RaiseException__deps: ['$uncaughtExceptionCount',
32+
#if !DISABLE_EXCEPTION_CATCHING
33+
'$exceptionLast',
34+
#endif
35+
],
36+
_Unwind_RaiseException: (ex) => {
37+
#if !DISABLE_EXCEPTION_CATCHING
38+
if (!exceptionLast) {
39+
exceptionLast = ex;
40+
}
41+
#endif
42+
uncaughtExceptionCount++;
43+
{{{ makeThrow() }}}
44+
},
45+
46+
_Unwind_DeleteException: (ex) => err('TODO: Unwind_DeleteException'),
47+
};
48+
49+
addToLibrary(LibraryUnwind);

src/modules.mjs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,10 @@ function calculateLibraries() {
7171
}
7272
}
7373

74+
if (!WASM_EXCEPTIONS) {
75+
libraries.push('libunwind.js');
76+
}
77+
7478
if (!MINIMAL_RUNTIME) {
7579
libraries.push('libbrowser.js');
7680
libraries.push('libwget.js');

test/test_other.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8832,6 +8832,29 @@ def test_exceptions_c_linker(self):
88328832
# when not linking as C++.
88338833
self.assert_fail([EMCC, '-sSTRICT', test_file('other/test_exceptions_c_linker.c')], 'error: undefined symbol: __cxa_find_matching_catch_1')
88348834

8835+
@parameterized({
8836+
# TODO: Add wasm_eh modes once the libunwind Wasm EH followup PR lands
8837+
'': ([],),
8838+
'exceptions': (['-fexceptions'],),
8839+
})
8840+
def test_libunwind(self, cflags):
8841+
src = r'''
8842+
#include <unwind.h>
8843+
#include <stdio.h>
8844+
#include <assert.h>
8845+
8846+
static struct _Unwind_Exception exc;
8847+
8848+
int main() {
8849+
assert(&_Unwind_RaiseException != NULL);
8850+
printf("About to raise exception...\n");
8851+
_Unwind_RaiseException(&exc);
8852+
printf("ERROR: _Unwind_RaiseException returned!\n");
8853+
return 0;
8854+
}
8855+
'''
8856+
self.do_run(src, 'About to raise exception...\n', cflags=cflags, assert_returncode=NON_ZERO)
8857+
88358858
@with_all_eh_sjlj
88368859
@no_bun('https://github.com/emscripten-core/emscripten/issues/26197')
88378860
def test_exceptions_stack_trace_and_message(self):

0 commit comments

Comments
 (0)