Skip to content

Commit 3efb144

Browse files
committed
Move parts of emscripten exception handling to native code. NFC
Specifically, this change moves the allocation and reference counting functions. This saves on code size but more importantly reduces the number and of complexity of imports/exports, which in turn helps with the wasm64 work I've been doing.
1 parent 7d9aceb commit 3efb144

24 files changed

+202
-155
lines changed

emcc.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2455,6 +2455,11 @@ def check_memory_setting(setting):
24552455
'__cxa_is_pointer_type',
24562456
'__cxa_can_catch',
24572457

2458+
# __cxa_begin_catch depends on this but we can't use deps info in this
2459+
# case because that only works for user-level code, and __cxa_begin_catch
2460+
# can be used by the standard library.
2461+
'__cxa_increment_exception_refcount',
2462+
24582463
# Emscripten exception handling can generate invoke calls, and they call
24592464
# setThrew(). We cannot handle this using deps_info as the invokes are not
24602465
# emitted because of library function usage, but by codegen itself.

src/jsifier.js

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -174,13 +174,15 @@ function ${name}(${args}) {
174174
warn('To build in STANDALONE_WASM mode without a main(), use emcc --no-entry');
175175
}
176176
}
177+
// We have already warned/errored about this function, so for the
178+
// purposes of Closure use, mute all type checks regarding this
179+
// function, marking as a variadic function that can take in anything
180+
// and return anything.
181+
// (not useful to warn/error multiple times)
182+
LibraryManager.library[ident + '__docs'] = '/** @type {function(...*):?} */';
177183
if (!RELOCATABLE) {
178184
// emit a stub that will fail at runtime
179185
LibraryManager.library[ident] = new Function(`err('missing function: ${ident}'); abort(-1);`);
180-
// We have already warned/errored about this function, so for the purposes of Closure use, mute all type checks
181-
// regarding this function, marking ot a variadic function that can take in anything and return anything.
182-
// (not useful to warn/error multiple times)
183-
LibraryManager.library[ident + '__docs'] = '/** @type {function(...*):?} */';
184186
} else {
185187
const target = `Module['${finalName}']`;
186188
let assertion = '';

src/library_exceptions.js

Lines changed: 11 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,9 @@ var LibraryExceptions = {
2222
//
2323
// excPtr - Thrown object pointer to wrap. Metadata pointer is calculated from it.
2424
$ExceptionInfo__docs: '/** @constructor */',
25-
$ExceptionInfo__deps: ['__cxa_is_pointer_type',
2625
#if EXCEPTION_DEBUG
27-
'$ptrToString',
26+
$ExceptionInfo__deps: ['$ptrToString'],
2827
#endif
29-
],
3028
$ExceptionInfo: function(excPtr) {
3129
this.excPtr = excPtr;
3230
this.ptr = excPtr - {{{ C_STRUCTS.__cxa_exception.__size__ }}};
@@ -47,10 +45,6 @@ var LibraryExceptions = {
4745
return {{{ makeGetValue('this.ptr', C_STRUCTS.__cxa_exception.exceptionDestructor, '*') }}};
4846
};
4947

50-
this.set_refcount = function(refcount) {
51-
{{{ makeSetValue('this.ptr', C_STRUCTS.__cxa_exception.referenceCount, 'refcount', 'i32') }}};
52-
};
53-
5448
this.set_caught = function (caught) {
5549
caught = caught ? 1 : 0;
5650
{{{ makeSetValue('this.ptr', C_STRUCTS.__cxa_exception.caught, 'caught', 'i8') }}};
@@ -77,34 +71,8 @@ var LibraryExceptions = {
7771
this.set_adjusted_ptr(0);
7872
this.set_type(type);
7973
this.set_destructor(destructor);
80-
this.set_refcount(0);
81-
this.set_caught(false);
82-
this.set_rethrown(false);
8374
}
8475

85-
this.add_ref = function() {
86-
#if SHARED_MEMORY
87-
Atomics.add(HEAP32, (this.ptr + {{{ C_STRUCTS.__cxa_exception.referenceCount }}}) >> 2, 1);
88-
#else
89-
var value = {{{ makeGetValue('this.ptr', C_STRUCTS.__cxa_exception.referenceCount, 'i32') }}};
90-
{{{ makeSetValue('this.ptr', C_STRUCTS.__cxa_exception.referenceCount, 'value + 1', 'i32') }}};
91-
#endif
92-
};
93-
94-
// Returns true if last reference released.
95-
this.release_ref = function() {
96-
#if SHARED_MEMORY
97-
var prev = Atomics.sub(HEAP32, (this.ptr + {{{ C_STRUCTS.__cxa_exception.referenceCount }}}) >> 2, 1);
98-
#else
99-
var prev = {{{ makeGetValue('this.ptr', C_STRUCTS.__cxa_exception.referenceCount, 'i32') }}};
100-
{{{ makeSetValue('this.ptr', C_STRUCTS.__cxa_exception.referenceCount, 'prev - 1', 'i32') }}};
101-
#endif
102-
#if ASSERTIONS
103-
assert(prev > 0);
104-
#endif
105-
return prev === 1;
106-
};
107-
10876
this.set_adjusted_ptr = function(adjustedPtr) {
10977
{{{ makeSetValue('this.ptr', C_STRUCTS.__cxa_exception.adjustedPtr, 'adjustedPtr', '*') }}};
11078
};
@@ -130,73 +98,6 @@ var LibraryExceptions = {
13098
};
13199
},
132100

133-
$exception_addRef: function (info) {
134-
#if EXCEPTION_DEBUG
135-
err('exception_addRef ' + ptrToString(info.excPtr));
136-
#endif
137-
info.add_ref();
138-
},
139-
140-
$exception_decRef__deps: ['__cxa_free_exception'
141-
#if EXCEPTION_DEBUG
142-
, '$exceptionLast', '$exceptionCaught'
143-
#endif
144-
],
145-
$exception_decRef: function(info) {
146-
#if EXCEPTION_DEBUG
147-
err('exception_decRef ' + ptrToString(info.excPtr));
148-
#endif
149-
// A rethrown exception can reach refcount 0; it must not be discarded
150-
// Its next handler will clear the rethrown flag and addRef it, prior to
151-
// final decRef and destruction here
152-
if (info.release_ref() && !info.get_rethrown()) {
153-
var destructor = info.get_destructor();
154-
if (destructor) {
155-
// In Wasm, destructors return 'this' as in ARM
156-
{{{ makeDynCall('ii', 'destructor') }}}(info.excPtr);
157-
}
158-
___cxa_free_exception(info.excPtr);
159-
#if EXCEPTION_DEBUG
160-
err('decref freeing exception ' + [ptrToString(info.excPtr), exceptionLast, 'stack', exceptionCaught]);
161-
#endif
162-
}
163-
},
164-
165-
// Exceptions
166-
__cxa_allocate_exception__sig: 'vi',
167-
__cxa_allocate_exception: function(size) {
168-
// Thrown object is prepended by exception metadata block
169-
return _malloc(size + {{{ C_STRUCTS.__cxa_exception.__size__ }}}) + {{{ C_STRUCTS.__cxa_exception.__size__ }}};
170-
},
171-
172-
__cxa_free_exception__deps: ['$ExceptionInfo'],
173-
__cxa_free_exception__sig: 'vi',
174-
__cxa_free_exception: function(ptr) {
175-
#if ABORTING_MALLOC || ASSERTIONS
176-
try {
177-
#endif
178-
return _free(new ExceptionInfo(ptr).ptr);
179-
#if ABORTING_MALLOC || ASSERTIONS
180-
} catch(e) {
181-
#if ASSERTIONS
182-
err('exception during __cxa_free_exception: ' + e);
183-
#endif
184-
}
185-
#endif
186-
},
187-
188-
__cxa_increment_exception_refcount__deps: ['$exception_addRef', '$ExceptionInfo'],
189-
__cxa_increment_exception_refcount: function(ptr) {
190-
if (!ptr) return;
191-
exception_addRef(new ExceptionInfo(ptr));
192-
},
193-
194-
__cxa_decrement_exception_refcount__deps: ['$exception_decRef', '$ExceptionInfo'],
195-
__cxa_decrement_exception_refcount: function(ptr) {
196-
if (!ptr) return;
197-
exception_decRef(new ExceptionInfo(ptr));
198-
},
199-
200101
// Here, we throw an exception after recording a couple of values that we need to remember
201102
// We also remember that it was the last exception thrown as we need to know that later.
202103
__cxa_throw__sig: 'viii',
@@ -243,7 +144,8 @@ var LibraryExceptions = {
243144
return type;
244145
},
245146

246-
__cxa_begin_catch__deps: ['$exceptionCaught', '$exception_addRef', '$uncaughtExceptionCount'],
147+
__cxa_begin_catch__deps: ['$exceptionCaught', '__cxa_increment_exception_refcount',
148+
'$uncaughtExceptionCount'],
247149
__cxa_begin_catch: function(ptr) {
248150
var info = new ExceptionInfo(ptr);
249151
if (!info.get_caught()) {
@@ -255,15 +157,15 @@ var LibraryExceptions = {
255157
#if EXCEPTION_DEBUG
256158
err('__cxa_begin_catch ' + [ptrToString(ptr), 'stack', exceptionCaught]);
257159
#endif
258-
exception_addRef(info);
160+
___cxa_increment_exception_refcount(info.excPtr);
259161
return info.get_exception_ptr();
260162
},
261163

262164
// We're done with a catch. Now, we can run the destructor if there is one
263165
// and free the exception. Note that if the dynCall on the destructor fails
264166
// due to calling apply on undefined, that means that the destructor is
265167
// an invalid index into the FUNCTION_TABLE, so something has gone wrong.
266-
__cxa_end_catch__deps: ['$exceptionCaught', '$exceptionLast', '$exception_decRef'],
168+
__cxa_end_catch__deps: ['$exceptionCaught', '$exceptionLast', '__cxa_decrement_exception_refcount', 'setThrew'],
267169
__cxa_end_catch__sig: 'v',
268170
__cxa_end_catch: function() {
269171
// Clear state flag.
@@ -277,16 +179,17 @@ var LibraryExceptions = {
277179
#if EXCEPTION_DEBUG
278180
err('__cxa_end_catch popped ' + [info, exceptionLast, 'stack', exceptionCaught]);
279181
#endif
280-
exception_decRef(info);
182+
___cxa_decrement_exception_refcount(info.excPtr);
281183
exceptionLast = 0; // XXX in decRef?
282184
},
283185

284186
__cxa_get_exception_ptr__deps: ['$ExceptionInfo'],
285187
__cxa_get_exception_ptr: function(ptr) {
188+
var rtn = new ExceptionInfo(ptr).get_exception_ptr();
286189
#if EXCEPTION_DEBUG
287-
err('__cxa_get_exception_ptr ' + ptrToString(ptr));
190+
err('__cxa_get_exception_ptr ' + ptrToString(ptr) + ' -> ' + ptrToString(rtn));
288191
#endif
289-
return new ExceptionInfo(ptr).get_exception_ptr();
192+
return rtn;
290193
},
291194

292195
__cxa_uncaught_exceptions__deps: ['$uncaughtExceptionCount'],
@@ -302,13 +205,13 @@ var LibraryExceptions = {
302205
throw exception;
303206
},
304207

305-
__cxa_current_primary_exception__deps: ['$exceptionCaught', '$exception_addRef'],
208+
__cxa_current_primary_exception__deps: ['$exceptionCaught', '__cxa_increment_exception_refcount'],
306209
__cxa_current_primary_exception: function() {
307210
if (!exceptionCaught.length) {
308211
return 0;
309212
}
310213
var info = exceptionCaught[exceptionCaught.length - 1];
311-
exception_addRef(info);
214+
___cxa_increment_exception_refcount(info.excPtr);
312215
return info.excPtr;
313216
},
314217

src/library_exceptions_stub.js

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,6 @@
77
var LibraryExceptions = {};
88

99
[
10-
'__cxa_allocate_exception',
11-
'__cxa_free_exception',
12-
'__cxa_increment_exception_refcount',
13-
'__cxa_decrement_exception_refcount',
1410
'__cxa_throw',
1511
'__cxa_rethrow',
1612
'llvm_eh_typeid_for',
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
//===------------------------- cxa_emscripten.cpp -------------------------===//
2+
//
3+
// Most code in the file is directly copied from cxa_exception.cpp.
4+
// TODO(sbc): consider merging them
5+
//
6+
// Notable changes:
7+
// __cxa_allocate_exception doesn't add get_cxa_exception_offset
8+
// __cxa_decrement_exception_refcount dosn't call the destructor if rethrown
9+
// Both of these changes are mirrored from the historical JS implemenation of
10+
// thse functions.
11+
//
12+
//===----------------------------------------------------------------------===//
13+
14+
#include "cxxabi.h"
15+
#include "cxa_exception.h"
16+
#include "include/atomic_support.h"
17+
#include "fallback_malloc.h"
18+
#include "stdio.h"
19+
#include "assert.h"
20+
21+
// Define to enable extra debugging on stderr.
22+
#if EXCEPTIONS_DEBUG
23+
#define DEBUG printf
24+
#else
25+
#define DEBUG(...)
26+
#endif
27+
28+
namespace __cxxabiv1 {
29+
30+
// Utility routines
31+
static
32+
inline
33+
__cxa_exception*
34+
cxa_exception_from_thrown_object(void* thrown_object)
35+
{
36+
DEBUG("cxa_exception_from_thrown_object %p -> %p\n",
37+
thrown_object, static_cast<__cxa_exception*>(thrown_object) - 1);
38+
return static_cast<__cxa_exception*>(thrown_object) - 1;
39+
}
40+
41+
// Note: This is never called when exception_header is masquerading as a
42+
// __cxa_dependent_exception.
43+
static
44+
inline
45+
void*
46+
thrown_object_from_cxa_exception(__cxa_exception* exception_header)
47+
{
48+
DEBUG("thrown_object_from_cxa_exception %p -> %p\n",
49+
exception_header, static_cast<void*>(exception_header + 1));
50+
return static_cast<void*>(exception_header + 1);
51+
}
52+
53+
// Round s up to next multiple of a.
54+
static inline
55+
size_t aligned_allocation_size(size_t s, size_t a) {
56+
return (s + a - 1) & ~(a - 1);
57+
}
58+
59+
static inline
60+
size_t cxa_exception_size_from_exception_thrown_size(size_t size) {
61+
return aligned_allocation_size(size + sizeof (__cxa_exception),
62+
alignof(__cxa_exception));
63+
}
64+
65+
extern "C" {
66+
67+
// Allocate a __cxa_exception object, and zero-fill it.
68+
// Reserve "thrown_size" bytes on the end for the user's exception
69+
// object. Zero-fill the object. If memory can't be allocated, call
70+
// std::terminate. Return a pointer to the memory to be used for the
71+
// user's exception object.
72+
void *__cxa_allocate_exception(size_t thrown_size) _NOEXCEPT {
73+
size_t actual_size = cxa_exception_size_from_exception_thrown_size(thrown_size);
74+
75+
char *raw_buffer =
76+
(char *)__aligned_malloc_with_fallback(actual_size);
77+
if (NULL == raw_buffer)
78+
std::terminate();
79+
__cxa_exception *exception_header =
80+
static_cast<__cxa_exception *>((void *)(raw_buffer));
81+
::memset(exception_header, 0, actual_size);
82+
return thrown_object_from_cxa_exception(exception_header);
83+
}
84+
85+
86+
// Free a __cxa_exception object allocated with __cxa_allocate_exception.
87+
void __cxa_free_exception(void *thrown_object) _NOEXCEPT {
88+
// Compute the size of the padding before the header.
89+
char *raw_buffer =
90+
((char *)cxa_exception_from_thrown_object(thrown_object));
91+
__aligned_free_with_fallback((void *)raw_buffer);
92+
}
93+
94+
/*
95+
If thrown_object is not null, atomically increment the referenceCount field
96+
of the __cxa_exception header associated with the thrown object referred to
97+
by thrown_object.
98+
99+
Requires: If thrown_object is not NULL, it is a native exception.
100+
*/
101+
void
102+
__cxa_increment_exception_refcount(void *thrown_object) _NOEXCEPT {
103+
if (thrown_object != NULL )
104+
{
105+
__cxa_exception* exception_header = cxa_exception_from_thrown_object(thrown_object);
106+
DEBUG("INC: %p refcnt=%zu\n", thrown_object, exception_header->referenceCount);
107+
std::__libcpp_atomic_add(&exception_header->referenceCount, size_t(1));
108+
}
109+
}
110+
111+
/*
112+
If thrown_object is not null, atomically decrement the referenceCount field
113+
of the __cxa_exception header associated with the thrown object referred to
114+
by thrown_object. If the referenceCount drops to zero, destroy and
115+
deallocate the exception.
116+
117+
Requires: If thrown_object is not NULL, it is a native exception.
118+
*/
119+
_LIBCXXABI_NO_CFI
120+
void __cxa_decrement_exception_refcount(void *thrown_object) _NOEXCEPT {
121+
if (thrown_object != NULL )
122+
{
123+
__cxa_exception* exception_header = cxa_exception_from_thrown_object(thrown_object);
124+
DEBUG("DEC: %p refcnt=%zu rethrown=%d\n", thrown_object,
125+
exception_header->referenceCount, exception_header->rethrown);
126+
assert(exception_header->referenceCount > 0);
127+
if (std::__libcpp_atomic_add(&exception_header->referenceCount, size_t(-1)) == 0 && !exception_header->rethrown)
128+
{
129+
DEBUG("DEL: %p\n", thrown_object);
130+
if (NULL != exception_header->exceptionDestructor)
131+
exception_header->exceptionDestructor(thrown_object);
132+
__cxa_free_exception(thrown_object);
133+
}
134+
}
135+
}
136+
137+
} // extern "C"
138+
139+
} // abi

system/lib/libcxxabi/src/cxa_noexception.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,17 @@ __cxa_uncaught_exception() throw() { return false; }
4949
unsigned int
5050
__cxa_uncaught_exceptions() throw() { return 0; }
5151

52+
#if __EMSCRIPTEN__
53+
// Under emscripten this code is also linked when building when
54+
// DISABLE_EXCEPTION_CATCHING is set but DISABLE_EXCEPTION_THROWING is not.
55+
// TODO(sbc): Perhaps just call std::terminate here. It could
56+
// just be some test code that needs updating.
57+
void *__cxa_allocate_exception(size_t thrown_size) _NOEXCEPT {
58+
char* allocation = (char*)malloc(thrown_size + sizeof(__cxa_exception));
59+
return allocation + sizeof(__cxa_exception);
60+
}
61+
#endif
62+
5263
} // extern "C"
5364

5465
// provide dummy implementations for the 'no exceptions' case.

0 commit comments

Comments
 (0)