Skip to content

Commit 31e76c2

Browse files
committed
Add exception stack system to runtime
Runtime ------- * An exception stack type, `jl_exc_stack_t` and associated manipulation functions has been added. Conceptually this stores a stack of (exception,backtrace) pairs. It's stored in a contiguous buffer so we can avoid reallocating when throwing and catching the same exception or set of exceptions repeatedly. Space for the exception and backtrace is allocated on demand inside `throw_internal`, after changing GC mode. Several variations were tried for allocating this sgorage, including allocating up front with malloc on the thread local state and copying during task switching. Keeping everything on the task seemed the simplest as it involves the least copying and keeps track of the exception stack buffers in a unified way. * The exception in transit is no longer a single pointer in the thread local storage. It's stored in `ptls->current_task->exc_stack` instead, along with associated backtrace. * `jl_current_exception()` is now the method to retreive the current exception from within a `JL_CATCH` dynamic context. * Several places where manual restoration of the exception and backtrace was done are no longer necessary because the stack system handles these automatically. This code is removed, including `jl_apply_with_saved_exception_state`. * `jl_eh_restore_state` has become non-inline. It seemed good to get this out of the public header. * `jl_sig_throw` is now used with `jl_throw_in_ctx` from signal handlers, to make the special circumstances clear and to avoid conflation with rethrow which now simply rethrows the existing exception stack. * Make rethrow outside a catch block into an error. * Use `ptls->previous_exception` to support `jl_exception_occurred` in embedding API. * finally lowering no longer includes a `foreigncall`, so expressions using finally can now be interpreted. Interpreter / Codegen --------------------- Mostly small changes here, to track the `:enter` and `:pop_exc` association with the SSAValue token. The token SSAValue slot is ideal here for storing the state of the exception stack at the `:enter`. GC -- Integrate exception and raw backtrace scanning as a special case into GC mark loop. This is necessary now that Task objects can refer to exception and backtrace data in raw form.
1 parent 57b46e7 commit 31e76c2

25 files changed

Lines changed: 386 additions & 167 deletions

src/ast.c

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -889,7 +889,9 @@ jl_value_t *jl_parse_eval_all(const char *fname,
889889
form = jl_pchar_to_string(fname, len);
890890
result = jl_box_long(jl_lineno);
891891
err = 1;
892+
goto finally; // skip jl_restore_exc_stack
892893
}
894+
finally:
893895
jl_get_ptls_states()->world_age = last_age;
894896
jl_lineno = last_lineno;
895897
jl_filename = last_filename;
@@ -901,7 +903,7 @@ jl_value_t *jl_parse_eval_all(const char *fname,
901903
jl_rethrow();
902904
else
903905
jl_rethrow_other(jl_new_struct(jl_loaderror_type, form, result,
904-
ptls->exception_in_transit));
906+
jl_current_exception()));
905907
}
906908
JL_GC_POP();
907909
return result;
@@ -1044,7 +1046,7 @@ static jl_value_t *jl_invoke_julia_macro(jl_array_t *args, jl_module_t *inmodule
10441046
margs[0] = jl_cstr_to_string("<macrocall>");
10451047
margs[1] = jl_fieldref(lno, 0); // extract and allocate line number
10461048
jl_rethrow_other(jl_new_struct(jl_loaderror_type, margs[0], margs[1],
1047-
ptls->exception_in_transit));
1049+
jl_current_exception()));
10481050
}
10491051
}
10501052
ptls->world_age = last_age;

src/cgutils.cpp

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2568,14 +2568,6 @@ static jl_cgval_t emit_new_struct(jl_codectx_t &ctx, jl_value_t *ty, size_t narg
25682568
}
25692569
}
25702570

2571-
static Value *emit_exc_in_transit(jl_codectx_t &ctx)
2572-
{
2573-
Value *pexc_in_transit = emit_bitcast(ctx, ctx.ptlsStates, T_pprjlvalue);
2574-
Constant *offset = ConstantInt::getSigned(T_int32,
2575-
offsetof(jl_tls_states_t, exception_in_transit) / sizeof(void*));
2576-
return ctx.builder.CreateInBoundsGEP(pexc_in_transit, ArrayRef<Value*>(offset), "jl_exception_in_transit");
2577-
}
2578-
25792571
static void emit_signal_fence(jl_codectx_t &ctx)
25802572
{
25812573
#if defined(_CPU_ARM_) || defined(_CPU_AARCH64_)

src/codegen.cpp

Lines changed: 52 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -287,7 +287,10 @@ static Function *jlgetfield_func;
287287
static Function *jlmethod_func;
288288
static Function *jlgenericfunction_func;
289289
static Function *jlenter_func;
290+
static Function *jlcurrent_exception_func;
290291
static Function *jlleave_func;
292+
static Function *jl_restore_exc_stack_func;
293+
static Function *jl_exc_stack_state_func;
291294
static Function *jlegal_func;
292295
static Function *jl_alloc_obj_func;
293296
static Function *jl_newbits_func;
@@ -782,9 +785,8 @@ static void emit_write_barrier(jl_codectx_t&, Value*, Value*);
782785

783786
static void jl_rethrow_with_add(const char *fmt, ...)
784787
{
785-
jl_ptls_t ptls = jl_get_ptls_states();
786-
if (jl_typeis(ptls->exception_in_transit, jl_errorexception_type)) {
787-
char *str = jl_string_data(jl_fieldref(ptls->exception_in_transit,0));
788+
if (jl_typeis(jl_current_exception(), jl_errorexception_type)) {
789+
char *str = jl_string_data(jl_fieldref(jl_current_exception(),0));
788790
char buf[1024];
789791
va_list args;
790792
va_start(args, fmt);
@@ -1772,7 +1774,13 @@ static jl_value_t *static_apply_type(jl_codectx_t &ctx, const jl_cgval_t *args,
17721774
size_t last_age = jl_get_ptls_states()->world_age;
17731775
// call apply_type, but ignore errors. we know that will work in world 1.
17741776
jl_get_ptls_states()->world_age = 1;
1775-
jl_value_t *result = jl_apply_with_saved_exception_state(v, nargs, 1);
1777+
jl_value_t *result;
1778+
JL_TRY {
1779+
result = jl_apply(v, nargs);
1780+
}
1781+
JL_CATCH {
1782+
result = NULL;
1783+
}
17761784
jl_get_ptls_states()->world_age = last_age;
17771785
return result;
17781786
}
@@ -1854,7 +1862,13 @@ static jl_value_t *static_eval(jl_codectx_t &ctx, jl_value_t *ex, int sparams=tr
18541862
size_t last_age = jl_get_ptls_states()->world_age;
18551863
// here we know we're calling specific builtin functions that work in world 1.
18561864
jl_get_ptls_states()->world_age = 1;
1857-
jl_value_t *result = jl_apply_with_saved_exception_state(v, n+1, 1);
1865+
jl_value_t *result;
1866+
JL_TRY {
1867+
result = jl_apply(v, n+1);
1868+
}
1869+
JL_CATCH {
1870+
result = NULL;
1871+
}
18581872
jl_get_ptls_states()->world_age = last_age;
18591873
JL_GC_POP();
18601874
return result;
@@ -3761,7 +3775,9 @@ static void emit_stmtpos(jl_codectx_t &ctx, jl_value_t *expr, int ssaval_result)
37613775
ConstantInt::get(T_int32, jl_unbox_long(args[0])));
37623776
}
37633777
else if (head == pop_exc_sym) {
3764-
// FIXME
3778+
jl_cgval_t exc_stack_state = emit_expr(ctx, jl_exprarg(expr, 0));
3779+
assert(exc_stack_state.V && exc_stack_state.V->getType() == T_size);
3780+
ctx.builder.CreateCall(prepare_call(jl_restore_exc_stack_func), exc_stack_state.V);
37653781
return;
37663782
}
37673783
else {
@@ -3916,8 +3932,7 @@ static jl_cgval_t emit_expr(jl_codectx_t &ctx, jl_value_t *expr, ssize_t ssaval)
39163932
bnd = jl_get_binding_for_method_def(mod, (jl_sym_t*)mn);
39173933
}
39183934
JL_CATCH {
3919-
jl_ptls_t ptls = jl_get_ptls_states();
3920-
jl_value_t *e = ptls->exception_in_transit;
3935+
jl_value_t *e = jl_current_exception();
39213936
// errors. boo. root it somehow :(
39223937
bnd = jl_get_binding_wr(ctx.module, (jl_sym_t*)jl_gensym(), 1);
39233938
bnd->value = e;
@@ -3986,9 +4001,9 @@ static jl_cgval_t emit_expr(jl_codectx_t &ctx, jl_value_t *expr, ssize_t ssaval)
39864001
Value *val = emit_jlcall(ctx, jlnew_func, typ, &argv[1], nargs - 1);
39874002
return mark_julia_type(ctx, val, true, ty);
39884003
}
3989-
else if (head == exc_sym) { // *ptls->exception_in_transit
4004+
else if (head == exc_sym) {
39904005
return mark_julia_type(ctx,
3991-
ctx.builder.CreateLoad(emit_exc_in_transit(ctx), /*isvolatile*/true),
4006+
ctx.builder.CreateCall(prepare_call(jlcurrent_exception_func)),
39924007
true, jl_any_type);
39934008
}
39944009
else if (head == copyast_sym) {
@@ -6179,6 +6194,14 @@ static std::unique_ptr<Module> emit_function(
61796194

61806195
assert(jl_is_long(args[0]));
61816196
int lname = jl_unbox_long(args[0]);
6197+
// Save exception stack depth at enter for use in pop_exc
6198+
6199+
Value *exc_stack_state =
6200+
ctx.builder.CreateCall(prepare_call(jl_exc_stack_state_func));
6201+
assert(!ctx.ssavalue_assigned.at(cursor));
6202+
ctx.SAvalues.at(cursor) = jl_cgval_t(exc_stack_state, NULL, false,
6203+
(jl_value_t*)jl_ulong_type, NULL);
6204+
ctx.ssavalue_assigned.at(cursor) = true;
61826205
CallInst *sj = ctx.builder.CreateCall(prepare_call(except_enter_func));
61836206
// We need to mark this on the call site as well. See issue #6757
61846207
sj->setCanReturnTwice();
@@ -6333,6 +6356,7 @@ static std::unique_ptr<Module> emit_function(
63336356
}
63346357
}
63356358
assert(found);
6359+
(void)found;
63366360
}
63376361
else {
63386362
terminator->removeFromParent();
@@ -7079,6 +7103,12 @@ static void init_julia_llvm_env(Module *m)
70797103
"jl_enter_handler", m);
70807104
add_named_global(jlenter_func, &jl_enter_handler);
70817105

7106+
jlcurrent_exception_func =
7107+
Function::Create(FunctionType::get(T_prjlvalue, false),
7108+
Function::ExternalLinkage,
7109+
"jl_current_exception", m);
7110+
add_named_global(jlcurrent_exception_func, &jl_current_exception);
7111+
70827112
#ifdef _OS_WINDOWS_
70837113
#if defined(_CPU_X86_64_)
70847114
juliapersonality_func = Function::Create(FunctionType::get(T_int32, true),
@@ -7118,6 +7148,18 @@ static void init_julia_llvm_env(Module *m)
71187148
"jl_pop_handler", m);
71197149
add_named_global(jlleave_func, &jl_pop_handler);
71207150

7151+
jl_restore_exc_stack_func =
7152+
Function::Create(FunctionType::get(T_void, T_size, false),
7153+
Function::ExternalLinkage,
7154+
"jl_restore_exc_stack", m);
7155+
add_named_global(jl_restore_exc_stack_func, &jl_restore_exc_stack);
7156+
7157+
jl_exc_stack_state_func =
7158+
Function::Create(FunctionType::get(T_size, false),
7159+
Function::ExternalLinkage,
7160+
"jl_exc_stack_state", m);
7161+
add_named_global(jl_exc_stack_state_func, &jl_exc_stack_state);
7162+
71217163
std::vector<Type *> args_2vals_callee_rooted(0);
71227164
args_2vals_callee_rooted.push_back(PointerType::get(T_jlvalue, AddressSpace::CalleeRooted));
71237165
args_2vals_callee_rooted.push_back(PointerType::get(T_jlvalue, AddressSpace::CalleeRooted));

src/dump.c

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2337,7 +2337,6 @@ static void jl_finalize_serializer(jl_serializer_state *s)
23372337
void jl_typemap_rehash(jl_typemap_t *ml, int8_t offs);
23382338
static void jl_reinit_item(jl_value_t *v, int how, arraylist_t *tracee_list)
23392339
{
2340-
jl_ptls_t ptls = jl_get_ptls_states();
23412340
JL_TRY {
23422341
switch (how) {
23432342
case 1: { // rehash IdDict
@@ -2390,7 +2389,7 @@ static void jl_reinit_item(jl_value_t *v, int how, arraylist_t *tracee_list)
23902389
jl_printf(JL_STDERR, "WARNING: error while reinitializing value ");
23912390
jl_static_show(JL_STDERR, v);
23922391
jl_printf(JL_STDERR, ":\n");
2393-
jl_static_show(JL_STDERR, ptls->exception_in_transit);
2392+
jl_static_show(JL_STDERR, jl_current_exception());
23942393
jl_printf(JL_STDERR, "\n");
23952394
}
23962395
}

src/gc.c

Lines changed: 56 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,7 @@ static void run_finalizer(jl_ptls_t ptls, jl_value_t *o, jl_value_t *ff)
234234
}
235235
JL_CATCH {
236236
jl_printf(JL_STDERR, "error in running finalizer: ");
237-
jl_static_show(JL_STDERR, ptls->exception_in_transit);
237+
jl_static_show(JL_STDERR, jl_current_exception());
238238
jl_printf(JL_STDERR, "\n");
239239
}
240240
}
@@ -313,27 +313,11 @@ static void jl_gc_run_finalizers_in_list(jl_ptls_t ptls, arraylist_t *list)
313313
jl_value_t **items = (jl_value_t**)list->items;
314314
size_t len = list->len;
315315
JL_UNLOCK_NOGC(&finalizers_lock);
316-
// from jl_apply_with_saved_exception_state; to hoist state saving out of the loop
317-
jl_value_t *exc = ptls->exception_in_transit;
318-
jl_array_t *bt = NULL;
319-
jl_array_t *bt2 = NULL;
320-
JL_GC_PUSH3(&exc, &bt, &bt2);
321-
if (ptls->bt_size > 0) {
322-
jl_get_backtrace(&bt, &bt2);
323-
ptls->bt_size = 0;
324-
}
325316
// run finalizers in reverse order they were added, so lower-level finalizers run last
326317
for (size_t i = len-4; i >= 2; i -= 2)
327318
run_finalizer(ptls, items[i], items[i + 1]);
328319
// first entries were moved last to make room for GC frame metadata
329320
run_finalizer(ptls, items[len-2], items[len-1]);
330-
ptls->exception_in_transit = exc;
331-
if (bt != NULL) {
332-
// This is sufficient because bt2 roots the managed values
333-
memcpy(ptls->bt_data, bt->data, jl_array_len(bt) * sizeof(void*));
334-
ptls->bt_size = jl_array_len(bt);
335-
}
336-
JL_GC_POP();
337321
// matches the jl_gc_push_arraylist above
338322
JL_GC_POP();
339323
}
@@ -1844,6 +1828,8 @@ STATIC_INLINE int gc_mark_scan_obj32(jl_ptls_t ptls, jl_gc_mark_sp_t *sp, gc_mar
18441828
goto obj32; \
18451829
case GC_MARK_L_stack: \
18461830
goto stack; \
1831+
case GC_MARK_L_excstack: \
1832+
goto excstack; \
18471833
case GC_MARK_L_module_binding: \
18481834
goto module_binding; \
18491835
default: \
@@ -1929,6 +1915,7 @@ JL_EXTENSION NOINLINE void gc_mark_loop(jl_ptls_t ptls, jl_gc_mark_sp_t sp)
19291915
gc_mark_label_addrs[GC_MARK_L_obj16] = gc_mark_laddr(obj16);
19301916
gc_mark_label_addrs[GC_MARK_L_obj32] = gc_mark_laddr(obj32);
19311917
gc_mark_label_addrs[GC_MARK_L_stack] = gc_mark_laddr(stack);
1918+
gc_mark_label_addrs[GC_MARK_L_excstack] = gc_mark_laddr(excstack);
19321919
gc_mark_label_addrs[GC_MARK_L_module_binding] = gc_mark_laddr(module_binding);
19331920
return;
19341921
}
@@ -2081,6 +2068,46 @@ stack: {
20812068
}
20822069
}
20832070

2071+
excstack: {
2072+
// Scan an exception stack
2073+
gc_mark_exc_stack_t *stackitr = gc_pop_markdata(&sp, gc_mark_exc_stack_t);
2074+
jl_exc_stack_t *exc_stack = stackitr->s;
2075+
size_t itr = stackitr->itr;
2076+
size_t i = stackitr->i;
2077+
while (itr > 0) {
2078+
size_t bt_size = jl_exc_stack_bt_size(exc_stack, itr);
2079+
uintptr_t *bt_data = jl_exc_stack_bt_data(exc_stack, itr);
2080+
while (i+2 < bt_size) {
2081+
if (bt_data[i] != (uintptr_t)-1) {
2082+
i++;
2083+
continue;
2084+
}
2085+
// found an interpreter frame to mark
2086+
new_obj = (jl_value_t*)bt_data[i+1];
2087+
uintptr_t nptr = 0;
2088+
if (gc_try_setmark(new_obj, &nptr, &tag, &bits)) {
2089+
stackitr->i = i + 3;
2090+
stackitr->itr = itr;
2091+
gc_repush_markdata(&sp, gc_mark_exc_stack_t);
2092+
goto mark;
2093+
}
2094+
i += 3;
2095+
}
2096+
// mark the exception
2097+
new_obj = jl_exc_stack_exception(exc_stack, itr);
2098+
itr = jl_exc_stack_next(exc_stack, itr);
2099+
i = 0;
2100+
uintptr_t nptr = 0;
2101+
if (gc_try_setmark(new_obj, &nptr, &tag, &bits)) {
2102+
stackitr->i = i;
2103+
stackitr->itr = itr;
2104+
gc_repush_markdata(&sp, gc_mark_exc_stack_t);
2105+
goto mark;
2106+
}
2107+
}
2108+
goto pop;
2109+
}
2110+
20842111
module_binding: {
20852112
// Scan a module. see `gc_mark_binding_t`
20862113
// Other fields of the module will be scanned after the bindings are scanned
@@ -2340,13 +2367,22 @@ mark: {
23402367
gc_mark_stack_push(&ptls->gc_cache, &sp, gc_mark_laddr(stack),
23412368
&stackdata, sizeof(stackdata), 1);
23422369
}
2370+
if (ta->exc_stack) {
2371+
gc_setmark_buf_(ptls, ta->exc_stack, bits, sizeof(jl_exc_stack_t) +
2372+
sizeof(uintptr_t)*ta->exc_stack->reserved_size);
2373+
gc_mark_exc_stack_t stackdata = {ta->exc_stack, ta->exc_stack->top, 0};
2374+
gc_mark_stack_push(&ptls->gc_cache, &sp, gc_mark_laddr(excstack),
2375+
&stackdata, sizeof(stackdata), 1);
2376+
}
23432377
const jl_datatype_layout_t *layout = jl_task_type->layout;
23442378
assert(layout->fielddesc_type == 0);
23452379
size_t nfields = layout->nfields;
23462380
assert(nfields > 0);
23472381
obj8_begin = (jl_fielddesc8_t*)jl_dt_layout_fields(layout);
23482382
obj8_end = obj8_begin + nfields;
2349-
gc_mark_obj8_t markdata = {new_obj, obj8_begin, obj8_end, (9 << 2) | 1 | bits};
2383+
// assume tasks always reference young objects: set lowest bit
2384+
uintptr_t nptr = (9 << 2) | 1 | bits;
2385+
gc_mark_obj8_t markdata = {new_obj, obj8_begin, obj8_end, nptr};
23502386
gc_mark_stack_push(&ptls->gc_cache, &sp, gc_mark_laddr(obj8),
23512387
&markdata, sizeof(markdata), 0);
23522388
obj8 = (gc_mark_obj8_t*)sp.data;
@@ -2441,7 +2477,8 @@ static void jl_gc_queue_thread_local(jl_gc_mark_cache_t *gc_cache, jl_gc_mark_sp
24412477
{
24422478
gc_mark_queue_obj(gc_cache, sp, ptls2->current_task);
24432479
gc_mark_queue_obj(gc_cache, sp, ptls2->root_task);
2444-
gc_mark_queue_obj(gc_cache, sp, ptls2->exception_in_transit);
2480+
if (ptls2->previous_exception)
2481+
gc_mark_queue_obj(gc_cache, sp, ptls2->previous_exception);
24452482
}
24462483

24472484
// mark the initial root set

src/gc.h

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -84,19 +84,21 @@ enum {
8484
GC_MARK_L_obj16,
8585
GC_MARK_L_obj32,
8686
GC_MARK_L_stack,
87+
GC_MARK_L_excstack,
8788
GC_MARK_L_module_binding,
8889
_GC_MARK_L_MAX
8990
};
9091

91-
/**
92-
* The `nptr` member of marking data records the number of pointers slots referenced by
93-
* an object to be used in the full collection heuristics as well as whether the object
94-
* references young objects.
95-
* `nptr >> 2` is the number of pointers fields referenced by the object.
96-
* The lowest bit of `nptr` is set if the object references young object.
97-
* The 2nd lowest bit of `nptr` is the GC old bits of the object after marking.
98-
* A `0x3` in the low bits means that the object needs to be in the remset.
99-
*/
92+
// The following structs (`gc_mark_*_t`) contain iterator state used for the
93+
// scanning of various object types.
94+
//
95+
// The `nptr` member records the number of pointers slots referenced by
96+
// an object to be used in the full collection heuristics as well as whether the object
97+
// references young objects.
98+
// `nptr >> 2` is the number of pointers fields referenced by the object.
99+
// The lowest bit of `nptr` is set if the object references young object.
100+
// The 2nd lowest bit of `nptr` is the GC old bits of the object after marking.
101+
// A `0x3` in the low bits means that the object needs to be in the remset.
100102

101103
// An generic object that's marked and needs to be scanned
102104
// The metadata might need update too (depend on the PC)
@@ -149,6 +151,13 @@ typedef struct {
149151
uintptr_t ub;
150152
} gc_mark_stackframe_t;
151153

154+
// Exception stack data
155+
typedef struct {
156+
jl_exc_stack_t *s; // Stack of exceptions
157+
size_t itr; // Iterator into exception stack
158+
size_t i; // Iterator into backtrace data for exception
159+
} gc_mark_exc_stack_t;
160+
152161
// Module bindings. This is also the beginning of module scanning.
153162
// The loop will start marking other references in a module after the bindings are marked
154163
typedef struct {
@@ -176,6 +185,7 @@ union _jl_gc_mark_data {
176185
gc_mark_obj16_t obj16;
177186
gc_mark_obj32_t obj32;
178187
gc_mark_stackframe_t stackframe;
188+
gc_mark_exc_stack_t excstackframe;
179189
gc_mark_binding_t binding;
180190
gc_mark_finlist_t finlist;
181191
};

0 commit comments

Comments
 (0)