-
Notifications
You must be signed in to change notification settings - Fork 5.1k
Interpreter EH implementation #116046
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
Interpreter EH implementation #116046
Conversation
Tagging subscribers to this area: @BrzVlad, @janvorli, @kg |
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.
Pull Request Overview
This PR adds full exception-handling (EH) support to the interpreter, covering both compilation and execution phases. It introduces new IR opcodes, builds EH metadata, implements funclet-based EH in the interpreter, and adds tests for various EH scenarios.
- Added support for EH opcodes (throw, rethrow, leave, filters, finally) and IR-level exception clauses
- Updated interpreter execution (
InterpExecMethod
) to dispatch filters, catches, and finally blocks - Built and emitted EH information (funclet offsets, EH tables) and wired new EH helpers
Reviewed Changes
Copilot reviewed 16 out of 16 changed files in this pull request and generated 2 comments.
Show a summary per file
File | Description |
---|---|
src/coreclr/vm/prestub.cpp | Introduce retVal storage for interpreter return value |
src/coreclr/vm/jithelpers.cpp | Swap in DispatchRethrownManagedException call |
src/coreclr/vm/interpexec.h | Extend InterpExecMethod signature with EH args |
src/coreclr/vm/interpexec.cpp | Handle new INTOP_* opcodes and null/overflow checks |
src/coreclr/vm/exceptionhandling.h | Declare DispatchRethrownManagedException overloads |
src/coreclr/vm/exceptionhandling.cpp | Implement DispatchRethrownManagedException and adjust dispatch logic |
src/coreclr/vm/eetwain.cpp | Flesh out InterpreterCodeManager::CallFunclet |
src/coreclr/vm/codeman.h | Update GetFuncletStartOffsets declaration |
src/coreclr/vm/codeman.cpp | Implement funclet start address/offset enumeration |
src/coreclr/interpreter/intops.def | Add EH-related INTOP definitions |
src/coreclr/interpreter/intops.cpp | Fix CEEOpcodeSize boundary condition |
src/coreclr/interpreter/eeinterp.cpp | Invoke BuildEHInfo() during compilation |
src/coreclr/interpreter/compileropt.cpp | Correct ilOffset propagation and SetSVar call |
src/coreclr/interpreter/compiler.h | Extend compiler IR and block structures for EH |
Comments suppressed due to low confidence (3)
src/coreclr/vm/exceptionhandling.cpp:2138
- Removing the GCX_PREEMP_NO_DTOR switch back to preemptive GC in the non-unwind (search) path can leave the runtime in cooperative mode and break stack walking. Reintroduce a mode switch before returning ExceptionContinueSearch.
else if (IS_UNWINDING(pExceptionRecord->ExceptionFlags))
src/coreclr/vm/jithelpers.cpp:1357
- The identifier
exceptionFrame
is not declared in this scope, leading to a compile error. You need to pass a valid CONTEXT* (e.g., capture it or fetch it from the exception tracker) toDispatchRethrownManagedException
.
DispatchRethrownManagedException(exceptionFrame.GetContext());
src/coreclr/vm/interpexec.h:77
- There are now two overloads of
InterpExecMethod
(one with defaultExceptionClauseArgs
and the original three-argument version), which can be confusing. Consider removing the three-argument prototype and relying solely on the default parameter.
void InterpExecMethod(InterpreterFrame *pInterpreterFrame, InterpMethodContextFrame *pFrame, InterpThreadContext *pThreadContext, ExceptionClauseArgs *pExceptionClauseArgs = NULL);
aee764e
to
02cb97b
Compare
This change implements EH support in the interpreter compiler and execution parts. Here is a summary of the changes: On the compilation side: * Adds support for CEE_THROW, CEE_RETHROW, CEE_ENDFILTER, CEE_ENDFINALLY, CEE_LEAVE and CEE_ISINST opcodes * Adds building of EH info with IR code offsets * Implements proper funclet handling in the same way the JIT does. All handlers and filters are moved to the end of the method and out of any try ranges recursively to enable proper behavior of the EH. * Fixes a bug related to wrong SVar and and an issue with IL offset of inserted instructions in AllocOffsets * Fixes a bug in the CEEOpcodeSize - off by one check for the end of the code On the execution side: * Add funclet start address extraction * Add calling funclets in the interpreted code * Removed GCX_PREEMP_NO_DTOR from the CallDescrWorkerUnwindFrameChainHandler, because it was not correct * Added new IR opcodes: * INTOP_LOAD_FRAMEVAR to load parent frame stack pointer in filter funclets that need to run in the context of the parent, but at the top of the current interpreter stack. * INTOP_RETHROW to rethrow an exception * INTOP_CALL_FINALLY to call finally funclet in non-exceptional cases * INTOP_LEAVE_FILTER to exit a filter funclet and return the filter result * INTOP_LEAVE_CATCH to exit a catch handler and return the resume address * Added calls to COMPlusThrow for division by zero, stack overflow and few other exceptions where the interpreter had just TODO and assert for adding those. * Modified the InterpExecMethod so that extra information can be passed in case of a funclet invocation. It also adds tests to verify various interesting EH scenarios Here are some more details on moving out the funclets: For each finally funclet, we create a finally call island that stays in the code where the original finally was. That island calls the finally and then branches to the next (outer) finally if any. The last finally call island branches to the actual leave target. The interpreter compiler generates a separate sequence of finally call islands for each leave instruction target. And when the leave is executed, it jumps to the beginning or into the middle of the chain. In other words, for example, if all leave instructions in the method go to the same target, there would be just one call finally island chain.
AddIns(INTOP_LOAD_FRAMEVAR); | ||
PushInterpType(InterpTypeI, NULL); | ||
m_pLastNewIns->SetDVar(m_pStackPointer[-1].var); | ||
EmitStind(interpType, m_pVars[var].clsHnd, m_pVars[var].offset, true /* reverseSVarOrder */); |
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.
If we need the indirect from LoadVar we should have it here too i think
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 sure what you mean, can you please explain it in more detail?
There is some problem with the IL to native map generation when running with checked build of the runtime. Debug build works fine. I am looking into it. |
The call finally islands were not classified by the clause they are in. Their instructions had IL offsets set to the offset of the finally block, but they need to have no IL offset as they are not generated from IL.
I've fixed the problem with the checked build of the tests. The problem was that the call finally islands didn't have the clauseType set and so when generating native form of the EH info, they were missing in the try regions ranges. |
I forgot to comment when I finished going over this, it all LGTM. I'm fine with landing it. |
Had this written down after a call a while ago with @janvorli. Might be useful for visualization.
|
Looks great! Thanks! |
This change implements EH support in the interpreter compiler and execution parts.
Here is a summary of the changes:
On the compilation side:
On the execution side:
It also adds tests to verify various interesting EH scenarios
Here are some more details on moving out the funclets: For each finally funclet, we create a finally call island that stays in the code where the original finally was. That island calls the finally and then branches to the next (outer) finally if any. The last finally call island branches to the actual leave target.
The interpreter compiler generates a separate sequence of finally call islands for each leave instruction target. And when the leave is executed, it jumps to the beginning or into the middle of the chain. In other words, for example, if all leave instructions in the method go to the same target, there would be just one call finally island chain.