Skip to content

Commit 783e260

Browse files
authored
[mono][wasm] Allow AOTing methods with catch/filter clauses. (#64867)
* [mono][wasm] Allow AOTing methods with catch clauses. This works as follows: * During EH, when a catch clause is found in an AOTed method, the EH state is saved into TLS and a c++ exception is thrown. * The C++ exception is caught by the AOTed method, and the landing pad code calls mono_llvm_resume_exception_il_state (). * That call will run the catch clause and the rest of the method code using the interpreter, storing the possible return value back into the AOTed method's stack frame. * After the call, the method skips the rest of its code, and returns immediately to its caller. * Fix console bench sample. * Fix landing pads. * Fix issues. * Add support for filter clauses. * Implement all wasm return conventions. * Fix arg/local write back. * Avoid throwing a c++ exception from do_jit_call () so the caller can clean up the interpreter stack. * Disable AOTing some more assemblies on CI. * Rename llvmonly EH functions to mini_llvmonly_ for clarity. * Improve unwinding through interpreter frames. Instead of throwing a c++ exception from mono_handle_exception () when an exception is caught in AOTed code, set context->has_resume_state, so the intepreter will normally unwind until exiting interpreted code. Then throw the c++ exception after the call to interp_exec_method () to unwind through the runtime code and the AOTed frames which can't handle the exception.
1 parent 5130645 commit 783e260

File tree

20 files changed

+407
-147
lines changed

20 files changed

+407
-147
lines changed

src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/System.Text.Json.SourceGeneration.Roslyn3.11.Tests.csproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22

33
<Import Project="System.Text.Json.SourceGeneration.Tests.targets" />
44

5+
<ItemGroup Condition="'$(ContinuousIntegrationBuild)' == 'true'">
6+
<HighAotMemoryUsageAssembly Include="System.Text.Json.SourceGeneration.Roslyn3.11.Tests.dll"/>
7+
</ItemGroup>
8+
59
<ItemGroup>
610
<ProjectReference Include="..\..\gen\System.Text.Json.SourceGeneration.Roslyn3.11.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
711
<ProjectReference Include="..\System.Text.Json.SourceGeneration.TestLibrary\System.Text.Json.TestLibrary.Roslyn3.11.csproj" />

src/libraries/System.Text.Json/tests/System.Text.Json.SourceGeneration.Tests/System.Text.Json.SourceGeneration.Roslyn4.0.Tests.csproj

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
<Project Sdk="Microsoft.NET.Sdk">
22

3-
<ItemGroup>
3+
<ItemGroup Condition="'$(ContinuousIntegrationBuild)' == 'true'">
44
<HighAotMemoryUsageAssembly Include="Microsoft.CodeAnalysis.CSharp.dll" />
5+
<HighAotMemoryUsageAssembly Include="System.Text.Json.SourceGeneration.Roslyn4.0.Tests.dll"/>
56
</ItemGroup>
67

78
<Import Project="System.Text.Json.SourceGeneration.Tests.targets" />

src/libraries/System.Text.Json/tests/System.Text.Json.Tests/System.Text.Json.Tests.csproj

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@
1717
<PropertyGroup>
1818
<DefineConstants Condition="$([MSBuild]::GetTargetFrameworkIdentifier('$(TargetFramework)')) == '.NETCoreApp'">$(DefineConstants);BUILDING_INBOX_LIBRARY</DefineConstants>
1919
</PropertyGroup>
20+
21+
<ItemGroup Condition="'$(ContinuousIntegrationBuild)' == 'true'">
22+
<HighAotMemoryUsageAssembly Include="System.Text.Json.Tests.dll"/>
23+
</ItemGroup>
24+
2025
<ItemGroup>
2126
<Compile Include="$(CommonTestPath)System\IO\WrappedMemoryStream.cs" Link="CommonTest\System\IO\WrappedMemoryStream.cs" />
2227
<Compile Include="..\Common\CollectionTests\CollectionTests.AsyncEnumerable.cs" Link="CommonTest\System\Text\Json\Tests\Serialization\CollectionTests\CollectionTests.AsyncEnumerable.cs" />

src/mono/mono/metadata/jit-icall-reg.h

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,14 @@ MONO_JIT_ICALL (mini_llvmonly_throw_nullref_exception) \
142142
MONO_JIT_ICALL (mini_llvmonly_throw_aot_failed_exception) \
143143
MONO_JIT_ICALL (mini_llvmonly_pop_lmf) \
144144
MONO_JIT_ICALL (mini_llvmonly_interp_entry_gsharedvt) \
145+
MONO_JIT_ICALL (mini_llvmonly_throw_exception) \
146+
MONO_JIT_ICALL (mini_llvmonly_rethrow_exception) \
147+
MONO_JIT_ICALL (mini_llvmonly_throw_corlib_exception) \
148+
MONO_JIT_ICALL (mini_llvmonly_resume_exception) \
149+
MONO_JIT_ICALL (mini_llvmonly_resume_exception_il_state) \
150+
MONO_JIT_ICALL (mini_llvmonly_load_exception) \
151+
MONO_JIT_ICALL (mini_llvmonly_clear_exception) \
152+
MONO_JIT_ICALL (mini_llvmonly_match_exception) \
145153
MONO_JIT_ICALL (mono_amd64_resume_unwind) \
146154
MONO_JIT_ICALL (mono_amd64_start_gsharedvt_call) \
147155
MONO_JIT_ICALL (mono_amd64_throw_corlib_exception) \
@@ -220,18 +228,11 @@ MONO_JIT_ICALL (mono_ldtoken_wrapper) \
220228
MONO_JIT_ICALL (mono_ldtoken_wrapper_generic_shared) \
221229
MONO_JIT_ICALL (mono_ldvirtfn) \
222230
MONO_JIT_ICALL (mono_ldvirtfn_gshared) \
223-
MONO_JIT_ICALL (mono_llvm_clear_exception) \
224-
MONO_JIT_ICALL (mono_llvm_load_exception) \
225-
MONO_JIT_ICALL (mono_llvm_match_exception) \
226-
MONO_JIT_ICALL (mono_llvm_resume_exception) \
227231
MONO_JIT_ICALL (mono_llvm_resume_unwind_trampoline) \
228-
MONO_JIT_ICALL (mono_llvm_rethrow_exception) \
229232
MONO_JIT_ICALL (mono_llvm_rethrow_exception_trampoline) \
230233
MONO_JIT_ICALL (mono_llvm_set_unhandled_exception_handler) \
231-
MONO_JIT_ICALL (mono_llvm_throw_corlib_exception) \
232234
MONO_JIT_ICALL (mono_llvm_throw_corlib_exception_abs_trampoline) \
233235
MONO_JIT_ICALL (mono_llvm_throw_corlib_exception_trampoline) \
234-
MONO_JIT_ICALL (mono_llvm_throw_exception) \
235236
MONO_JIT_ICALL (mono_llvm_throw_exception_trampoline) \
236237
MONO_JIT_ICALL (mono_llvmonly_init_delegate) \
237238
MONO_JIT_ICALL (mono_llvmonly_init_delegate_virtual) \

src/mono/mono/mini/aot-compiler.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13468,7 +13468,7 @@ static void aot_dump (MonoAotCompile *acfg)
1346813468
static const MonoJitICallId preinited_jit_icalls [] = {
1346913469
MONO_JIT_ICALL_mini_llvm_init_method,
1347013470
MONO_JIT_ICALL_mini_llvmonly_throw_nullref_exception,
13471-
MONO_JIT_ICALL_mono_llvm_throw_corlib_exception,
13471+
MONO_JIT_ICALL_mini_llvmonly_throw_corlib_exception,
1347213472
MONO_JIT_ICALL_mono_threads_state_poll,
1347313473
MONO_JIT_ICALL_mini_llvmonly_init_vtable_slot,
1347413474
MONO_JIT_ICALL_mono_helper_ldstr_mscorlib,

src/mono/mono/mini/ee.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
#ifndef __MONO_EE_H__
1515
#define __MONO_EE_H__
1616

17-
#define MONO_EE_API_VERSION 0x15
17+
#define MONO_EE_API_VERSION 0x16
1818

1919
typedef struct _MonoInterpStackIter MonoInterpStackIter;
2020

@@ -38,7 +38,7 @@ typedef gpointer MonoInterpFrameHandle;
3838
MONO_EE_CALLBACK (void, get_resume_state, (const MonoJitTlsData *jit_tls, gboolean *has_resume_state, MonoInterpFrameHandle *interp_frame, gpointer *handler_ip)) \
3939
MONO_EE_CALLBACK (gboolean, run_finally, (StackFrameInfo *frame, int clause_index, gpointer handler_ip, gpointer handler_ip_end)) \
4040
MONO_EE_CALLBACK (gboolean, run_filter, (StackFrameInfo *frame, MonoException *ex, int clause_index, gpointer handler_ip, gpointer handler_ip_end)) \
41-
MONO_EE_CALLBACK (gboolean, run_finally_with_il_state, (gpointer il_state, int clause_index, gpointer handler_ip, gpointer handler_ip_end)) \
41+
MONO_EE_CALLBACK (gboolean, run_clause_with_il_state, (gpointer il_state, int clause_index, gpointer handler_ip, gpointer handler_ip_end, MonoObject *ex, gboolean *filtered, MonoExceptionEnum clause_type)) \
4242
MONO_EE_CALLBACK (void, frame_iter_init, (MonoInterpStackIter *iter, gpointer interp_exit_data)) \
4343
MONO_EE_CALLBACK (gboolean, frame_iter_next, (MonoInterpStackIter *iter, StackFrameInfo *frame)) \
4444
MONO_EE_CALLBACK (MonoJitInfo*, find_jit_info, (MonoMethod *method)) \

src/mono/mono/mini/interp-stubs.c

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,8 @@ stub_run_filter (StackFrameInfo *frame, MonoException *ex, int clause_index, gpo
115115
}
116116

117117
static gboolean
118-
stub_run_finally_with_il_state (gpointer il_state, int clause_index, gpointer handler_ip, gpointer handler_ip_end)
118+
stub_run_clause_with_il_state (gpointer il_state, int clause_index, gpointer handler_ip, gpointer handler_ip_end, MonoObject *ex,
119+
gboolean *filtered, MonoExceptionEnum clause_type)
119120
{
120121
g_assert_not_reached ();
121122
}

src/mono/mono/mini/interp/interp-internals.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,7 @@ typedef struct {
210210
/* Lets interpreter know it has to resume execution after EH */
211211
gboolean has_resume_state;
212212
/* Frame to resume execution at */
213+
/* Can be NULL if the exception is caught in an AOTed frame */
213214
InterpFrame *handler_frame;
214215
/* IP to resume execution at */
215216
const guint16 *handler_ip;

src/mono/mono/mini/interp/interp.c

Lines changed: 82 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -92,8 +92,6 @@ struct FrameClauseArgs {
9292
* frame further down the stack.
9393
*/
9494
const guint16 *end_at_ip;
95-
/* When exiting this clause we also exit the frame */
96-
int exit_clause;
9795
/* Frame that is executing this clause */
9896
InterpFrame *exec_frame;
9997
};
@@ -429,6 +427,14 @@ interp_free_context (gpointer ctx)
429427
g_free (context);
430428
}
431429

430+
/* Continue unwinding if there is an exception that needs to be handled in an AOTed frame above us */
431+
static void
432+
check_pending_unwind (ThreadContext *context)
433+
{
434+
if (context->has_resume_state && !context->handler_frame)
435+
mono_llvm_cpp_throw_exception ();
436+
}
437+
432438
void
433439
mono_interp_error_cleanup (MonoError* error)
434440
{
@@ -2118,6 +2124,8 @@ interp_runtime_invoke (MonoMethod *method, void *obj, void **params, MonoObject
21182124

21192125
context->stack_pointer = (guchar*)sp;
21202126

2127+
check_pending_unwind (context);
2128+
21212129
if (context->has_resume_state) {
21222130
/*
21232131
* This can happen on wasm where native frames cannot be skipped during EH.
@@ -2220,10 +2228,12 @@ interp_entry (InterpEntryData *data)
22202228
if (rmethod->needs_thread_attach)
22212229
mono_threads_detach_coop (orig_domain, &attach_cookie);
22222230

2231+
check_pending_unwind (context);
2232+
22232233
if (mono_llvm_only) {
22242234
if (context->has_resume_state)
22252235
/* The exception will be handled in a frame above us */
2226-
mono_llvm_reraise_exception ((MonoException*)mono_gchandle_get_target_internal (context->exc_gchandle));
2236+
mono_llvm_cpp_throw_exception ();
22272237
} else {
22282238
g_assert (!context->has_resume_state);
22292239
}
@@ -2641,9 +2651,20 @@ do_jit_call (ThreadContext *context, stackval *ret_sp, stackval *sp, InterpFrame
26412651
* This happens when interp_entry calls mono_llvm_reraise_exception ().
26422652
*/
26432653
return;
2644-
MonoObject *obj = mono_llvm_load_exception ();
2654+
MonoJitTlsData *jit_tls = mono_get_jit_tls ();
2655+
if (jit_tls->resume_state.il_state) {
2656+
/*
2657+
* This c++ exception is going to be caught by an AOTed frame above us.
2658+
* We can't rethrow here, since that will skip the cleanup of the
2659+
* interpreter stack space etc. So instruct the interpreter to unwind.
2660+
*/
2661+
context->has_resume_state = TRUE;
2662+
context->handler_frame = NULL;
2663+
return;
2664+
}
2665+
MonoObject *obj = mini_llvmonly_load_exception ();
26452666
g_assert (obj);
2646-
mono_llvm_clear_exception ();
2667+
mini_llvmonly_clear_exception ();
26472668
mono_error_set_exception_instance (error, (MonoException*)obj);
26482669
return;
26492670
}
@@ -2958,6 +2979,8 @@ interp_entry_from_trampoline (gpointer ccontext_untyped, gpointer rmethod_untype
29582979
if (rmethod->needs_thread_attach)
29592980
mono_threads_detach_coop (orig_domain, &attach_cookie);
29602981

2982+
check_pending_unwind (context);
2983+
29612984
/* Write back the return value */
29622985
/* 'frame' is still valid */
29632986
mono_arch_set_native_call_context_ret (ccontext, &frame, sig, retp);
@@ -2996,8 +3019,10 @@ interp_compile_interp_method (MonoMethod *method, MonoError *error)
29963019
InterpMethod *imethod = mono_interp_get_imethod (method, error);
29973020
return_val_if_nok (error, NULL);
29983021

2999-
mono_interp_transform_method (imethod, get_context (), error);
3000-
return_val_if_nok (error, NULL);
3022+
if (!imethod->transformed) {
3023+
mono_interp_transform_method (imethod, get_context (), error);
3024+
return_val_if_nok (error, NULL);
3025+
}
30013026

30023027
return imethod->jinfo;
30033028
}
@@ -7269,6 +7294,8 @@ interp_parse_options (const char *options)
72697294
* interp_set_resume_state:
72707295
*
72717296
* Set the state the interpeter will continue to execute from after execution returns to the interpreter.
7297+
* If INTERP_FRAME is NULL, that means the exception is caught in an AOTed frame and the interpreter needs to
7298+
* unwind back to AOT code.
72727299
*/
72737300
static void
72747301
interp_set_resume_state (MonoJitTlsData *jit_tls, MonoObject *ex, MonoJitExceptionInfo *ei, MonoInterpFrameHandle interp_frame, gpointer handler_ip)
@@ -7286,8 +7313,10 @@ interp_set_resume_state (MonoJitTlsData *jit_tls, MonoObject *ex, MonoJitExcepti
72867313
mono_gchandle_free_internal (context->exc_gchandle);
72877314
context->exc_gchandle = mono_gchandle_new_internal ((MonoObject*)ex, FALSE);
72887315
/* Ditto */
7289-
if (ei)
7290-
*(MonoObject**)(frame_locals (context->handler_frame) + ei->exvar_offset) = ex;
7316+
if (context->handler_frame) {
7317+
if (ei)
7318+
*(MonoObject**)(frame_locals (context->handler_frame) + ei->exvar_offset) = ex;
7319+
}
72917320
context->handler_ip = (const guint16*)handler_ip;
72927321
}
72937322

@@ -7323,7 +7352,6 @@ interp_run_finally (StackFrameInfo *frame, int clause_index, gpointer handler_ip
73237352
memset (&clause_args, 0, sizeof (FrameClauseArgs));
73247353
clause_args.start_with_ip = (const guint16*)handler_ip;
73257354
clause_args.end_at_ip = (const guint16*)handler_ip_end;
7326-
clause_args.exit_clause = clause_index;
73277355
clause_args.exec_frame = iframe;
73287356

73297357
state_ip = iframe->state.ip;
@@ -7339,6 +7367,9 @@ interp_run_finally (StackFrameInfo *frame, int clause_index, gpointer handler_ip
73397367

73407368
iframe->next_free = next_free;
73417369
iframe->state.ip = state_ip;
7370+
7371+
check_pending_unwind (context);
7372+
73427373
if (context->has_resume_state) {
73437374
return TRUE;
73447375
} else {
@@ -7390,32 +7421,41 @@ interp_run_filter (StackFrameInfo *frame, MonoException *ex, int clause_index, g
73907421

73917422
context->stack_pointer = (guchar*)child_frame.stack;
73927423

7424+
check_pending_unwind (context);
7425+
73937426
/* ENDFILTER stores the result into child_frame->retval */
73947427
return retval.data.i ? TRUE : FALSE;
73957428
}
73967429

7430+
/* Returns TRUE if there is a pending exception */
73977431
static gboolean
7398-
interp_run_finally_with_il_state (gpointer il_state_ptr, int clause_index, gpointer handler_ip, gpointer handler_ip_end)
7432+
interp_run_clause_with_il_state (gpointer il_state_ptr, int clause_index, gpointer handler_ip, gpointer handler_ip_end,
7433+
MonoObject *ex, gboolean *filtered, MonoExceptionEnum clause_type)
73997434
{
74007435
MonoMethodILState *il_state = (MonoMethodILState*)il_state_ptr;
74017436
MonoMethodSignature *sig;
74027437
ThreadContext *context = get_context ();
7438+
stackval *orig_sp;
74037439
stackval *sp, *sp_args;
74047440
InterpMethod *imethod;
74057441
FrameClauseArgs clause_args;
74067442
ERROR_DECL (error);
74077443

7408-
// FIXME: Optimize this ? Its only used during EH
7409-
74107444
sig = mono_method_signature_internal (il_state->method);
74117445
g_assert (sig);
74127446

74137447
imethod = mono_interp_get_imethod (il_state->method, error);
74147448
mono_error_assert_ok (error);
74157449

7416-
sp_args = sp = (stackval*)context->stack_pointer;
7450+
orig_sp = sp_args = sp = (stackval*)context->stack_pointer;
7451+
7452+
gpointer ret_addr = NULL;
74177453

74187454
int findex = 0;
7455+
if (sig->ret->type != MONO_TYPE_VOID) {
7456+
ret_addr = il_state->data [findex];
7457+
findex ++;
7458+
}
74197459
if (sig->hasthis) {
74207460
if (il_state->data [findex])
74217461
sp_args->data.p = *(gpointer*)il_state->data [findex];
@@ -7451,32 +7491,41 @@ interp_run_finally_with_il_state (gpointer il_state_ptr, int clause_index, gpoin
74517491
if (header->num_locals)
74527492
memset (frame_locals (&frame) + imethod->local_offsets [0], 0, imethod->locals_size);
74537493
/* Copy locals from il_state */
7454-
int locals_start = sig->hasthis + sig->param_count;
7494+
int locals_start = findex;
74557495
for (int i = 0; i < header->num_locals; ++i) {
74567496
if (il_state->data [locals_start + i])
74577497
stackval_from_data (header->locals [i], (stackval*)(frame_locals (&frame) + imethod->local_offsets [i]), il_state->data [locals_start + i], FALSE);
74587498
}
74597499

74607500
memset (&clause_args, 0, sizeof (FrameClauseArgs));
74617501
clause_args.start_with_ip = (const guint16*)handler_ip;
7462-
clause_args.end_at_ip = (const guint16*)handler_ip_end;
7463-
clause_args.exit_clause = clause_index;
7502+
if (clause_type == MONO_EXCEPTION_CLAUSE_NONE || clause_type == MONO_EXCEPTION_CLAUSE_FILTER)
7503+
clause_args.end_at_ip = (const guint16*)clause_args.start_with_ip + 0xffffff;
7504+
else
7505+
clause_args.end_at_ip = (const guint16*)handler_ip_end;
74647506
clause_args.exec_frame = &frame;
74657507

7466-
// this informs MINT_ENDFINALLY to return to EH
7467-
*(guint16**)(frame_locals (&frame) + imethod->clause_data_offsets [clause_index]) = NULL;
7508+
if (clause_type == MONO_EXCEPTION_CLAUSE_NONE || clause_type == MONO_EXCEPTION_CLAUSE_FILTER)
7509+
*(MonoObject**)(frame_locals (&frame) + imethod->jinfo->clauses [clause_index].exvar_offset) = ex;
7510+
else
7511+
// this informs MINT_ENDFINALLY to return to EH
7512+
*(guint16**)(frame_locals (&frame) + imethod->clause_data_offsets [clause_index]) = NULL;
7513+
7514+
/* Set in mono_handle_exception () */
7515+
context->has_resume_state = FALSE;
74687516

74697517
interp_exec_method (&frame, context, &clause_args);
74707518

74717519
/* Write back args */
74727520
sp_args = sp;
74737521
findex = 0;
7522+
if (sig->ret->type != MONO_TYPE_VOID)
7523+
findex ++;
74747524
if (sig->hasthis) {
74757525
// FIXME: This
74767526
sp_args++;
74777527
findex ++;
74787528
}
7479-
findex = sig->hasthis ? 1 : 0;
74807529
for (int i = 0; i < sig->param_count; ++i) {
74817530
if (il_state->data [findex]) {
74827531
int size = stackval_to_data (sig->params [i], sp_args, il_state->data [findex], FALSE);
@@ -7494,11 +7543,19 @@ interp_run_finally_with_il_state (gpointer il_state_ptr, int clause_index, gpoin
74947543
}
74957544
mono_metadata_free_mh (header);
74967545

7497-
// FIXME: Restore stack ?
7498-
if (context->has_resume_state)
7499-
return TRUE;
7500-
else
7501-
return FALSE;
7546+
if (clause_type == MONO_EXCEPTION_CLAUSE_NONE && ret_addr) {
7547+
stackval_to_data (sig->ret, frame.retval, ret_addr, FALSE);
7548+
} else if (clause_type == MONO_EXCEPTION_CLAUSE_FILTER) {
7549+
g_assert (filtered);
7550+
*filtered = frame.retval->data.i;
7551+
}
7552+
7553+
memset (orig_sp, 0, (guint8*)context->stack_pointer - (guint8*)orig_sp);
7554+
context->stack_pointer = (guchar*)orig_sp;
7555+
7556+
check_pending_unwind (context);
7557+
7558+
return context->has_resume_state;
75027559
}
75037560

75047561
typedef struct {

0 commit comments

Comments
 (0)