Skip to content

Commit e88b142

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 e88b142

21 files changed

Lines changed: 168 additions & 92 deletions

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: 18 additions & 21 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
@@ -109,20 +106,20 @@ var LibraryExceptions = {
109106
info.init(type, destructor);
110107
#if !DISABLE_EXCEPTION_CATCHING
111108
___cxa_increment_exception_refcount(ptr);
112-
exceptionLast = new CppException(ptr);
109+
ptr = 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,
119115
// we early-exit from end_catch when the exception has been rethrown, so
120116
// pop that here from the caught exceptions.
121-
__cxa_rethrow__deps: ['$exceptionCaught', '$uncaughtExceptionCount',
117+
__cxa_rethrow__deps: ['$exceptionCaught',
122118
#if !DISABLE_EXCEPTION_CATCHING
123119
'$exceptionLast',
124120
'__cxa_increment_exception_refcount',
125121
#endif
122+
'_Unwind_RaiseException',
126123
],
127124
__cxa_rethrow: () => {
128125
if (!exceptionCaught.length) {
@@ -132,16 +129,15 @@ var LibraryExceptions = {
132129
var ptr = info.excPtr;
133130
info.set_rethrown(true);
134131
info.set_caught(false);
135-
uncaughtExceptionCount++;
136132
#if !DISABLE_EXCEPTION_CATCHING
137133
___cxa_increment_exception_refcount(ptr);
138134
#if EXCEPTION_DEBUG
139135
dbg('__cxa_rethrow: ' +
140136
[ptrToString(ptr), exceptionLast, 'stack', exceptionCaught]);
141137
#endif
142-
exceptionLast = new CppException(ptr);
138+
ptr = new CppException(ptr);
143139
#endif
144-
{{{ makeThrow() }}}
140+
__Unwind_RaiseException(ptr);
145141
},
146142

147143
llvm_eh_typeid_for: (type) => type,
@@ -212,11 +208,12 @@ var LibraryExceptions = {
212208
return info.get_type();
213209
},
214210

215-
__cxa_rethrow_primary_exception__deps: ['$ExceptionInfo', '$uncaughtExceptionCount',
211+
__cxa_rethrow_primary_exception__deps: ['$ExceptionInfo',
216212
#if !DISABLE_EXCEPTION_CATCHING
217213
'$exceptionLast',
218214
'__cxa_increment_exception_refcount',
219215
#endif
216+
'_Unwind_RaiseException',
220217
],
221218
__cxa_rethrow_primary_exception: (ptr) => {
222219
if (!ptr) return;
@@ -226,12 +223,11 @@ var LibraryExceptions = {
226223
var info = new ExceptionInfo(ptr);
227224
info.set_rethrown(true);
228225
info.set_caught(false);
229-
uncaughtExceptionCount++;
230226
#if !DISABLE_EXCEPTION_CATCHING
231227
___cxa_increment_exception_refcount(ptr);
232-
exceptionLast = new CppException(ptr);
228+
ptr = new CppException(ptr);
233229
#endif
234-
{{{ makeThrow('exceptionLast') }}}
230+
__Unwind_RaiseException(ptr);
235231
},
236232

237233
// Finds a suitable catch clause for when an exception is thrown.
@@ -293,19 +289,20 @@ var LibraryExceptions = {
293289
#endif
294290
},
295291

292+
__resumeException__deps: [
296293
#if !DISABLE_EXCEPTION_CATCHING
297-
__resumeException__deps: ['$exceptionLast'],
294+
'$exceptionLast',
298295
#endif
296+
'_Unwind_Resume',
297+
],
299298
__resumeException: (ptr) => {
300299
#if !DISABLE_EXCEPTION_CATCHING
301300
#if EXCEPTION_DEBUG
302301
dbg("__resumeException " + [ptrToString(ptr), exceptionLast]);
303302
#endif
304-
if (!exceptionLast) {
305-
exceptionLast = new CppException(ptr);
306-
}
303+
ptr = exceptionLast ?? new CppException(ptr);
307304
#endif
308-
{{{ makeThrow() }}}
305+
__Unwind_Resume(ptr);
309306
},
310307

311308
#endif

src/lib/libsigs.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,6 @@ sigs = {
213213
_Unwind_DeleteException__sig: 'vp',
214214
_Unwind_FindEnclosingFunction__sig: 'pp',
215215
_Unwind_GetIPInfo__sig: 'ppp',
216-
_Unwind_RaiseException__sig: 'ip',
217216
__asctime_r__sig: 'ppp',
218217
__assert_fail__sig: 'vppip',
219218
__call_sighandler__sig: 'vpi',

src/lib/libunwind.js

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
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+
exceptionLast = ex;
39+
uncaughtExceptionCount++;
40+
#endif
41+
{{{ makeThrow('ex') }}}
42+
},
43+
44+
#if !DISABLE_EXCEPTION_CATCHING
45+
_Unwind_Resume__deps: ['$exceptionLast'],
46+
#endif
47+
_Unwind_Resume: (ex) => {
48+
#if !DISABLE_EXCEPTION_CATCHING
49+
exceptionLast = ex;
50+
#endif
51+
{{{ makeThrow('ex') }}}
52+
},
53+
54+
_Unwind_DeleteException: (ex) => err('TODO: Unwind_DeleteException'),
55+
};
56+
57+
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');

src/parseTools.mjs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -664,7 +664,7 @@ export function makeReturn64(value) {
664664
return `(setTempRet0(${pair[1]}), ${pair[0]})`;
665665
}
666666

667-
function makeThrow() {
667+
function makeThrow(exc) {
668668
if (DISABLE_EXCEPTION_CATCHING) {
669669
if (ASSERTIONS) {
670670
var assertInfo =
@@ -678,7 +678,7 @@ function makeThrow() {
678678
return 'abort()';
679679
}
680680
}
681-
return 'throw exceptionLast;';
681+
return `throw ${exc};`;
682682
}
683683

684684
function charCode(char) {

test/codesize/test_codesize_cxx_ctors1.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
{
2-
"a.out.js": 19208,
3-
"a.out.js.gz": 8121,
2+
"a.out.js": 19198,
3+
"a.out.js.gz": 8107,
44
"a.out.nodebug.wasm": 134729,
55
"a.out.nodebug.wasm.gz": 51522,
6-
"total": 153937,
7-
"total_gz": 59643,
6+
"total": 153927,
7+
"total_gz": 59629,
88
"sent": [
99
"__cxa_throw",
1010
"_abort_js",

test/codesize/test_codesize_cxx_ctors2.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
{
2-
"a.out.js": 19185,
3-
"a.out.js.gz": 8104,
2+
"a.out.js": 19175,
3+
"a.out.js.gz": 8091,
44
"a.out.nodebug.wasm": 134158,
55
"a.out.nodebug.wasm.gz": 51187,
6-
"total": 153343,
7-
"total_gz": 59291,
6+
"total": 153333,
7+
"total_gz": 59278,
88
"sent": [
99
"__cxa_throw",
1010
"_abort_js",

test/codesize/test_codesize_cxx_except.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
{
2-
"a.out.js": 22895,
3-
"a.out.js.gz": 9075,
2+
"a.out.js": 22904,
3+
"a.out.js.gz": 9078,
44
"a.out.nodebug.wasm": 177195,
55
"a.out.nodebug.wasm.gz": 59099,
6-
"total": 200090,
7-
"total_gz": 68174,
6+
"total": 200099,
7+
"total_gz": 68177,
88
"sent": [
99
"__cxa_begin_catch",
1010
"__cxa_end_catch",

test/codesize/test_codesize_cxx_mangle.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
{
2-
"a.out.js": 22945,
3-
"a.out.js.gz": 9095,
2+
"a.out.js": 22954,
3+
"a.out.js.gz": 9099,
44
"a.out.nodebug.wasm": 243475,
55
"a.out.nodebug.wasm.gz": 81296,
6-
"total": 266420,
7-
"total_gz": 90391,
6+
"total": 266429,
7+
"total_gz": 90395,
88
"sent": [
99
"__cxa_begin_catch",
1010
"__cxa_end_catch",

0 commit comments

Comments
 (0)