Skip to content

Commit 225496d

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 8dc2f00 commit 225496d

23 files changed

+185
-154
lines changed

emcc.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2444,6 +2444,11 @@ def check_memory_setting(setting):
24442444
'__cxa_is_pointer_type',
24452445
'__cxa_can_catch',
24462446

2447+
# __cxa_begin_catch depends on this but we can't use deps info in this
2448+
# case because that only works for user-level code, and __cxa_begin_catch
2449+
# can be used by the standard library.
2450+
'__cxa_increment_exception_refcount',
2451+
24472452
# Emscripten exception handling can generate invoke calls, and they call
24482453
# setThrew(). We cannot handle this using deps_info as the invokes are not
24492454
# 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: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
#include "cxxabi.h"
2+
#include "cxa_exception.h"
3+
#include "include/atomic_support.h"
4+
#include "fallback_malloc.h"
5+
#include "stdio.h"
6+
#include "assert.h"
7+
8+
#if 1
9+
#define DEBUG(...)
10+
#else
11+
#define DEBUG printf
12+
#endif
13+
14+
namespace __cxxabiv1 {
15+
16+
// Utility routines
17+
static
18+
inline
19+
__cxa_exception*
20+
cxa_exception_from_thrown_object(void* thrown_object)
21+
{
22+
DEBUG("cxa_exception_from_thrown_object %p -> %p\n", thrown_object, static_cast<__cxa_exception*>(thrown_object) - 1);
23+
return static_cast<__cxa_exception*>(thrown_object) - 1;
24+
}
25+
26+
// Note: This is never called when exception_header is masquerading as a
27+
// __cxa_dependent_exception.
28+
static
29+
inline
30+
void*
31+
thrown_object_from_cxa_exception(__cxa_exception* exception_header)
32+
{
33+
DEBUG("thrown_object_from_cxa_exception %p -> %p\n", exception_header, static_cast<void*>(exception_header + 1));
34+
return static_cast<void*>(exception_header + 1);
35+
}
36+
37+
// Round s up to next multiple of a.
38+
static inline
39+
size_t aligned_allocation_size(size_t s, size_t a) {
40+
return (s + a - 1) & ~(a - 1);
41+
}
42+
43+
static inline
44+
size_t cxa_exception_size_from_exception_thrown_size(size_t size) {
45+
return aligned_allocation_size(size + sizeof (__cxa_exception),
46+
alignof(__cxa_exception));
47+
}
48+
49+
extern "C" {
50+
51+
// Allocate a __cxa_exception object, and zero-fill it.
52+
// Reserve "thrown_size" bytes on the end for the user's exception
53+
// object. Zero-fill the object. If memory can't be allocated, call
54+
// std::terminate. Return a pointer to the memory to be used for the
55+
// user's exception object.
56+
void *__cxa_allocate_exception(size_t thrown_size) _NOEXCEPT {
57+
size_t actual_size = cxa_exception_size_from_exception_thrown_size(thrown_size);
58+
59+
char *raw_buffer =
60+
(char *)__aligned_malloc_with_fallback(actual_size);
61+
if (NULL == raw_buffer)
62+
std::terminate();
63+
__cxa_exception *exception_header =
64+
static_cast<__cxa_exception *>((void *)(raw_buffer));
65+
::memset(exception_header, 0, actual_size);
66+
return thrown_object_from_cxa_exception(exception_header);
67+
}
68+
69+
70+
// Free a __cxa_exception object allocated with __cxa_allocate_exception.
71+
void __cxa_free_exception(void *thrown_object) _NOEXCEPT {
72+
// Compute the size of the padding before the header.
73+
char *raw_buffer =
74+
((char *)cxa_exception_from_thrown_object(thrown_object));
75+
__aligned_free_with_fallback((void *)raw_buffer);
76+
}
77+
78+
/*
79+
If thrown_object is not null, atomically increment the referenceCount field
80+
of the __cxa_exception header associated with the thrown object referred to
81+
by thrown_object.
82+
83+
Requires: If thrown_object is not NULL, it is a native exception.
84+
*/
85+
void
86+
__cxa_increment_exception_refcount(void *thrown_object) _NOEXCEPT {
87+
if (thrown_object != NULL )
88+
{
89+
__cxa_exception* exception_header = cxa_exception_from_thrown_object(thrown_object);
90+
DEBUG("INC: %p refcnt=%zu\n", thrown_object, exception_header->referenceCount);
91+
std::__libcpp_atomic_add(&exception_header->referenceCount, size_t(1));
92+
}
93+
}
94+
95+
/*
96+
If thrown_object is not null, atomically decrement the referenceCount field
97+
of the __cxa_exception header associated with the thrown object referred to
98+
by thrown_object. If the referenceCount drops to zero, destroy and
99+
deallocate the exception.
100+
101+
Requires: If thrown_object is not NULL, it is a native exception.
102+
*/
103+
_LIBCXXABI_NO_CFI
104+
void __cxa_decrement_exception_refcount(void *thrown_object) _NOEXCEPT {
105+
if (thrown_object != NULL )
106+
{
107+
__cxa_exception* exception_header = cxa_exception_from_thrown_object(thrown_object);
108+
DEBUG("DEC: %p refcnt=%zu rethrown=%d\n", thrown_object, exception_header->referenceCount, exception_header->rethrown);
109+
assert(exception_header->referenceCount > 0);
110+
if (std::__libcpp_atomic_add(&exception_header->referenceCount, size_t(-1)) == 0 && !exception_header->rethrown)
111+
{
112+
DEBUG("DEL: %p\n", thrown_object);
113+
if (NULL != exception_header->exceptionDestructor)
114+
exception_header->exceptionDestructor(thrown_object);
115+
__cxa_free_exception(thrown_object);
116+
}
117+
}
118+
}
119+
120+
} // extern "C"
121+
122+
} // abi

system/lib/libcxxabi/src/cxa_noexception.cpp

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,18 @@ __cxa_rethrow_primary_exception(void* thrown_object) {
4343
std::terminate();
4444
}
4545

46-
bool
47-
__cxa_uncaught_exception() throw() { return false; }
46+
bool __cxa_uncaught_exception() throw() { return false; }
4847

49-
unsigned int
50-
__cxa_uncaught_exceptions() throw() { return 0; }
48+
unsigned int __cxa_uncaught_exceptions() throw() { return 0; }
49+
50+
void *__cxa_allocate_exception(size_t thrown_size) _NOEXCEPT {
51+
char* allocation = (char*)malloc(thrown_size + sizeof(__cxa_exception));
52+
return allocation + sizeof(__cxa_exception);
53+
}
54+
55+
void __cxa_free_exception(void *thrown_object) _NOEXCEPT {
56+
free(((char*)thrown_object) - sizeof(__cxa_exception));
57+
}
5158

5259
} // extern "C"
5360

0 commit comments

Comments
 (0)