-
Notifications
You must be signed in to change notification settings - Fork 203
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?
Add documents on the Mono interpreter analysis #2857
Conversation
This change adds document describing how the functions in the transform.c will be converted to use the Jit2EEInterface methods instead of Mono APIs.
This change adds document describing how the functions in the transform.c will be converted to use the Jit2EEInterface methods instead of Mono APIs.
First part from my notes
…melab into add-transform-doc
|
||
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. |
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.
docs/design/interpreter/stackwalk.md
Outdated
To enable seamless integration of interpreter with GC, exception handling and debugger, we need to be able to walk stack that contains interleaved sequences of interpreted and AOT / JIT compiled frames. Fortunately, the same StackFrameIterator is used by the GC, exception handling and debugger, so adding a support to walk stack containing interpreter frames to this StackFrameIterator will give us support for all of the three scenarios. | ||
|
||
Here is the list of necessary changes: | ||
* Add a new explicit frame, the InterpreterFrame. Instance of this frame would be created in the mono_interp_exec_method. It will enable stack frame iterator to move through the interpreted frames managed by that mono_interp_exec_method. Please note that interpreter calls to other interpreted code are not recursively calling mono_interp_exec_method and each interpreted frame is represented by an instance of InterpFrame (these form a linked list). |
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.
Are InterpreterFrame
and InterpFrame
different here? Not trying to quibble over names, but using names like this in these design documents is very difficult to parse. I find this sort of long form description difficult to understand - too many words and missing the logic.
As a suggestion in general, having basic steps (1), (2), ... (N) is much easier to follow and avoids encoding steps in long form descriptions.
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.
They are different. InterpFrame is a currently existing data structure in the Mono interpreter while the InterpreterFrame will be a new explicit frame derived from Frame in coreclr. I think we will end up renaming the InterpFrame to something else and use the InterpreterFrame for the explicit one, but I didn't want to rename the Mono structure for the sake of this document and I didn't have a better idea for naming of the new InterpreterFrame.
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 comment
The 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 comment
The 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 comment
The 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 exnref
type is being standardized and is becoming available or included as preview feature. Here you can see the feature extensions table https://webassembly.org/features/
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.
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 comment
The 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
docs/design/interpreter/stackwalk.md
Outdated
To enable seamless integration of interpreter with GC, exception handling and debugger, we need to be able to walk stack that contains interleaved sequences of interpreted and AOT / JIT compiled frames. Fortunately, the same StackFrameIterator is used by the GC, exception handling and debugger, so adding a support to walk stack containing interpreter frames to this StackFrameIterator will give us support for all of the three scenarios. | ||
|
||
Here is the list of necessary changes: | ||
* Add a new explicit frame, the InterpreterFrame. Instance of this frame would be created in the mono_interp_exec_method. It will enable stack frame iterator to move through the interpreted frames managed by that mono_interp_exec_method. Please note that interpreter calls to other interpreted code are not recursively calling mono_interp_exec_method and each interpreted frame is represented by an instance of InterpFrame (these form a linked list). |
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.
Should we make this sort of thing clear by having names like ClrInterpreterFrame and MonoInterpreterFrame?
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 guess that would make it even more confusing by naming existing Mono InterpFrame differently in the doc. I think we will end up renaming many data types in the interpreter, but if we rename it in this doc, the link between the doc and the real Mono state will be lost.
I think I'll just rename the InterpreterFrame here to something different to make the doc less ambiguous and keep the InterpFrame.
* mono_class_from_mono_type_internal - N.A., just use CORINFO_CLASS_HANDLE | ||
|
||
### mono_interp_jit_call_supported | ||
* Return false, we will not support JIT (at least not in first version) |
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.
Note that in some cases 'jit calls' can be calls into AOT-compiled code on mono. It still seems valid to scope this out in the first version though.
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.
This is good point, when I was writing this comment, I didn't know yet that that JIT call here means in fact AOT call and that it has JIT in the name for historical reasons.
* interp_get_ldind_for_mt | ||
* interp_get_stind_for_mt | ||
* generate_compacted_code | ||
* mono_jiterp_insert_ins |
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.
anything with 'jiterp' in the name can be safely ignored as it is specific to the wasm jiterpreter
* Both the transformation and interpretation use Mono type / method / vtable etc data structures | ||
* The arguments to the opcode are immediate constants (16, 32 or 64 bit), indexes into local variables table or indexes into a per-method special data items array. | ||
## Limitations | ||
* The local variable offsets in most of the IR codes are 16 bit unsigned integers, so a method can have max 65536 kB of local variables |
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 think this is a 'max stack frame size of 64kb', not just local variables since stuff like stackalloc eats the space too
* MINT_MOV_VT(u16 target_local, u16 source_local, u16 size) | ||
### Hot reload | ||
* MINT_METADATA_UPDATE_LDFLDA(u16 target_local, u16 source_local, u16 type_index, u16 fielddef_token_index) | ||
### JITerpreter |
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.
This is a WASM specific JIT, these opcodes are WASM only and can be ignored
This change adds documents on the Mono interpreter byte code, the transformation and execution phase, debugger, exception handling and stack walking.