-
Notifications
You must be signed in to change notification settings - Fork 206
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add documents on the Mono interpreter analysis #2857
base: feature/CoreclrInterpreter
Are you sure you want to change the base?
Changes from all commits
833e518
a4cff10
a1ba34a
64da4f2
3bceb5a
04bae85
9c624b5
5cb1d3d
e16ab63
030602d
96a9d00
29c0a23
8283984
bea8538
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
# Debugger integration with the interpreter | ||
## Debugger to interpreter calls | ||
### Mono | ||
The interpreter exposes a subset of functions implemented in the interp.c as an interface that Mono runtime calls into. The functions listed below are called by the debugger related code. | ||
* interp_set_resume_state | ||
* interp_get_resume_state | ||
* interp_set_breakpoint | ||
* interp_clear_breakpoint | ||
* interp_frame_get_jit_info | ||
* interp_frame_get_ip - dtto | ||
* interp_frame_get_local | ||
* interp_frame_get_this | ||
* interp_frame_get_arg | ||
* interp_start_single_stepping | ||
* interp_stop_single_stepping | ||
|
||
Mono supports debugger connection via the ICorDebug interface. The calls to that interface are translated to Mono debugging protocol that delivers messages to the debuggee side and calls some of the functions listed above to handle the specific operations. | ||
The interp_set_resume_state and interp_get_resume_state don't seem to be used in the ICorDebug related code paths. | ||
The interp_frame_get_jit_info and interp_frame_get_ip seem to be used during single step / breakpoint processing, but it is not clear how they precisely relate to the ICorDebug stuff. | ||
|
||
Here is how relevant ICorDebug interface methods are wired to the interpreter functions: | ||
* CordbJITILFrame::GetLocalVariable -> interp_frame_get_local | ||
* CordbJITILFrame::GetArgument(0) -> interp_frame_get_this | ||
* CordbJITILFrame::GetArgument(1..n) -> interp_frame_get_arg | ||
* CordbFunctionBreakpoint::Activate(true) -> interp_set_breakpoint | ||
* CordbFunctionBreakpoint::Activate(false) -> interp_clear_breakpoint | ||
* CordbStepper::Step, StepRange, StepOut -> interp_start_single_stepping | ||
* CordbStepper::Deactivate -> interp_stop_single_stepping | ||
|
||
### CoreCLR | ||
CoreCLR also uses the ICorDebug interface for debugger connection. Most of the calls to that interface are translated to IPC events and the debuggee side handles the events in Debugger::HandleIPCEvent. So we can handle the events stemming from some of the above mentioned ICorDebug interface methods there by calling the same interpreter functions that Mono calls. | ||
The CordbJITILFrame methods don't send IPC events though and rather uses DAC and remote memory access to get the variable and argument data. It ends up getting the variable locations using the IJitManager::GetBoundariesAndVars method. We would have a JIT manager for the interpreted code and the implementation of this method could use the DAC-ified interp_frame_get_local, interp_frame_get_this and interp_frame_get_arg to get the actual details. | ||
|
||
## Interpreter to debugger calls | ||
### Mono | ||
There are just two "events" that the interpreter notifies the debugger about: | ||
* Single stepping: when MINT_SDB_INTR_LOC IR opcode is executed, a trampoline obtained from mini_get_single_step_trampoline() is called. This trampoline ends up calling mono_component_debugger()->single_step_from_context. That in turn end up calling CordbProcess()->GetCallback()->StepComplete | ||
* Breakpoint hit: when MINT_SDB_BREAKPOINT IR opcode is executed a trampoline obtained from mini_get_breakpoint_trampoline() is called. This trampoline ends up calling mono_component_debugger()->breakpoint_from_context. That in turn end up calling CordbProcess()->GetCallback()->Breakpoint | ||
* System.Diagnostics.Debugger.Break(): when MINT_BREAK IR opcode is executed, a trampoline obtained from mono_component_debugger()->user_break is called. That in turn end up calling CordbProcess()->GetCallback()->Break | ||
### CoreCLR | ||
* Single stepping: when MINT_SDB_INTR_LOC IR opcode is executed, Debugger::SendStep will be called. That sends DB_IPCE_STEP_COMPLETE event and the debugger processes it by ICorDebugManagedCallback::StepComplete) | ||
* Breakpoint hit: when MINT_SDB_BREAKPOINT IR opcode is executed, Debugger::SendBreakpoint will be called (that sends DB_IPCE_BREAKPOINT and the debugger processes it by calling ICorDebugManagedCallback::Breakpoint) | ||
* System.Diagnostics.Debugger.Break(): when MINT_BREAK IR opcode is executed, Debugger::SendRawUserBreakpoint will be called (that sends DB_IPCE_USER_BREAKPOINT and the debugger processes it by calling ICorDebugManagedCallback::Break) | ||
|
||
## WASM debugger event loop | ||
The Mono debugger runs a debugger event loop that receives commands from the debugger in a separate thread. Since WASM is a single threaded environment, the debugger has to use a different mechanism. The Mono interpreter calls mono_component_debugger()->receive_and_process_command_from_debugger_agent() for each MINT_SDB_SEQ_POINT it interprets. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
# CoreCLR exception handling integration with the interpreter | ||
There are several parts of the exception handling that need to take into consideration presence of interpreter frames on the call stack. | ||
* Stack walking | ||
* Exception clauses enumeration | ||
* Exception handlers invocation | ||
* Resuming execution after a catch handler exits | ||
|
||
The stack walking changes are described in the [stack walking](stackwalk.md) document. | ||
Exception clauses enumeration in CoreCLR is agnostic of the actual low level details of EH clauses information storage. It uses an interface to a JIT manager that does the real work. As described in the stack walking document, a new InterpreterJitManager will be implemented for the interpreted code. | ||
To enumerate the EH clauses, it will use data provided by the InterpMethod::clauses/num_clauses. | ||
|
||
Regarding the exception handlers invocation, CoreCLR uses native helpers CallCatchFunclet, CallFinallyFunclet and CallFilterFunclet. These will need to be modified to recognize interpreted frames and call the related interpreter functions. CallFinallyFunclet would call interp_run_finally and CallFilterFunclet would call the interp_run_filter. As for the CallCatchFunclet, Mono handles calling catch funclets by calling interp_set_resume_state to record the catch handler IR code address and then returning to the mono_interp_exec_method that checks for this stored state and redirects execution to the catch handler if it is set. In CoreCLR, we may want to handle it differently, in a manner close to how interp_run_finally works. | ||
|
||
Resuming execution in the parent of a catch handler after the catch handler exits depends on whether the catch handler is in an interpreted code or in compiled managed code. | ||
For the compiled code case, the existing CoreCLR EH code will handle it without changes. It would just restore the context at the resume location. | ||
For the interpreted code though, CoreCLR will resume execution in the mono_interp_exec_method, then pop some InterpFrame instances from the local linked list (depending on how many interpreted managed frames need to be removed) and restore the interpreter SP from the last popped one and the interpreter IP will be set to the resume location IP. | ||
|
||
WASM doesn't support stack unwinding and context manipulation, so the resuming mechanism cannot use context restoring. Mono currently returns from the mono_handle_exception after the catch is executed back to the interp_throw. When the resume frame is in the interpreted frames belonging to the current mono_interp_exec_method, it uses the mechanism described in the previous paragraph to "restore" to the resume context. But when the resume frame is above all the frames belonging to the current mono_interp_exec_method, it exits from the mono_interp_exec_method and then throws a C++ exception (of int32 * type set to NULL) that will propagate through native frames until it is caught in a compiled managed code or in another interpreter function up the call chain (see usage of mono_llvm_catch_exception) where the propagation through the interpreted frames continues the same way as in the previous interpreted frames block. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How is C++ exception handling implemented in Wasm? Does Wasm have any primitives for stack unwinding to support C++ exception handling? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. WASM has exception handling support, see https://github.com/WebAssembly/exception-handling/tree/main/proposals/exception-handling There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, it adds new instructions, new tags section in the wasm format and modifies few existing sections. Note that the current wasm EH, which is implemented in most browser and engines, is deprecated. The new EH with the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. All of our current Wasm runtimes also support targeting VMs with no exception extensions. This is one of those cases where it is useful to think of the browser and WASI separately because the all the major browsers currently support the deprecated Wasm exception instructions and none of the WASI runtimes do. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To answer the unwinding part of the question, the stack unwinding is done by the VM itself and not by the running code. So after exception is thrown, the stack is unwound by VM and control flow is transferred to the catch instruction. On wasm, the clang/llvm handles C++ exceptions by inserting a call to a wrapper in the libunwind after the catch instruction. The wrapper then calls the libcxxabi's personality function. More details can be found in https://llvm.org/docs/ExceptionHandling.html#overview and in the comments on top of https://github.com/dotnet/llvm-project/blob/dotnet/main-19.x/llvm/lib/CodeGen/WasmEHPrepare.cpp |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am not a big fan of context restoring into C code as we currently do on mono. I think it would be simpler to always have a C++
try/catch
when we leave interpreter. Asking to resume into interpreter would be a simple throw. Catchers would check if the resume information is for the current block of linked interpreter frames, if it is not found then it would just rethrow. This is more or less the approach described below that we would need to take for wasm anyway.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't say that we would necessarily restore context into the C code, I said we would do that for the compiled managed code case. However, from perf point of view, it might be better to restore context back into the interp_throw on other than WASM. I have found unwinding native frames to be very costy, see my change #108480 where just getting rid of two levels of native code when propagating an exception resulted in a significant perf win.