NO-REVIEW try to enable finalizers on top of CallDescrWorkerInternal change#124789
NO-REVIEW try to enable finalizers on top of CallDescrWorkerInternal change#124789radekdoulik wants to merge 2 commits intodotnet:mainfrom
Conversation
To call it before we potentially put object references on stack, because DoPrestub can trigger GC.
|
Tagging subscribers to this area: @agocke |
There was a problem hiding this comment.
Pull request overview
This PR attempts to enable Browser/WASM finalization callbacks and refactors WASM portable-entrypoint call paths to prestub/initialize interpreter code outside of CallDescrWorkerInternal (to avoid GC-safety issues during argument setup).
Changes:
- Removes the in-worker prestub path from
CallDescrWorkerInternaland replaces it with an assertion that interpreter code is already available. - Adds a new
MethodDesc::GetInterpreterCodeWithPrestub()helper and calls it from several invocation paths to force interpreter-code initialization. - Enables Browser/WASM finalization callback execution by calling
ManagedThreadBase::KickOff(FinalizerThreadWorkerIteration, ...).
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| src/coreclr/vm/wasm/calldescrworkerwasm.cpp | Removes prestub-in-worker and assumes interpreter code is already initialized. |
| src/coreclr/vm/reflectioninvocation.cpp | Forces prestub/interpreter code initialization before reflection invoke call frame setup. |
| src/coreclr/vm/method.hpp | Introduces GetInterpreterCodeWithPrestub() helper. |
| src/coreclr/vm/finalizerthread.cpp | Enables WASM Browser finalization callback to run finalizer iteration. |
| src/coreclr/vm/callhelpers.cpp | Attempts to prestub/initialize interpreter code before certain VM->managed call helpers. |
| #ifdef FEATURE_PORTABLE_ENTRYPOINTS | ||
| MethodDesc* pMethod = PortableEntryPoint::GetMethodDesc(m_pCallTarget); | ||
| (void)pMethod->GetInterpreterCodeWithPrestub(); | ||
| #endif // FEATURE_PORTABLE_ENTRYPOINTS | ||
|
|
There was a problem hiding this comment.
MethodDescCallSite::CallTargetWorker explicitly warns that it must not trigger a GC because the incoming argument array is not GC-protected. GetInterpreterCodeWithPrestub() may call DoPrestub and trigger GC, violating that requirement and potentially corrupting GC refs in pArguments. The prestub/interpreter-code initialization needs to be moved to a point where arguments are still protected (e.g., before constructing/filling the ARG_SLOT array) or otherwise guaranteed non-GC.
| #ifdef FEATURE_PORTABLE_ENTRYPOINTS | |
| MethodDesc* pMethod = PortableEntryPoint::GetMethodDesc(m_pCallTarget); | |
| (void)pMethod->GetInterpreterCodeWithPrestub(); | |
| #endif // FEATURE_PORTABLE_ENTRYPOINTS |
| MethodDesc* pMethod = PortableEntryPoint::GetMethodDesc(pCallDescrData->pTarget); | ||
| InterpByteCodeStart* targetIp = pMethod->GetInterpreterCode(); | ||
| if (targetIp == NULL) | ||
| { | ||
| GCX_PREEMP(); | ||
| (void)pMethod->DoPrestub(NULL /* MethodTable */, CallerGCMode::Coop); | ||
| targetIp = pMethod->GetInterpreterCode(); | ||
| } | ||
| _ASSERTE(targetIp != nullptr); |
There was a problem hiding this comment.
CallDescrWorkerInternal now only _ASSERTEs that GetInterpreterCode() is non-null. In release builds, a null targetIp would be passed to ExecuteInterpretedMethodWithArgs, likely causing a crash. If the intent is to require prestubbing earlier, this should be enforced in all builds (fail fast) and/or targetIp should be obtained via a guaranteed-initializing path, with the initialization performed before any unprotected argument references are placed in the call frame.
| GCX_PREEMP(); | ||
| (void)DoPrestub(NULL /* MethodTable */, CallerGCMode::Coop); | ||
| return GetInterpreterCode(); |
There was a problem hiding this comment.
GetInterpreterCodeWithPrestub() can return NULL even after DoPrestub() (e.g., if interpreter code is poisoned/unavailable), but current call sites ignore the return value and later assume interpreter code exists. Consider asserting/checking that interpreter code is non-null after prestub (ideally in all builds) so failures are caught deterministically rather than surfacing later as null derefs.
| GCX_PREEMP(); | |
| (void)DoPrestub(NULL /* MethodTable */, CallerGCMode::Coop); | |
| return GetInterpreterCode(); | |
| // If interpreter code has been explicitly poisoned, it is a misuse to call | |
| // GetInterpreterCodeWithPrestub; there is no valid interpreter code to obtain. | |
| if (IsInterpreterCodePoisoned()) | |
| { | |
| _ASSERTE(!"GetInterpreterCodeWithPrestub called on method with poisoned interpreter code"); | |
| return NULL; | |
| } | |
| GCX_PREEMP(); | |
| (void)DoPrestub(NULL /* MethodTable */, CallerGCMode::Coop); | |
| targetIp = GetInterpreterCode(); | |
| // After prestub, interpreter code should be available unless it was poisoned. | |
| _ASSERTE(targetIp != NULL || IsInterpreterCodePoisoned()); | |
| return targetIp; |
| @@ -59,7 +59,7 @@ extern "C" void SystemJS_ExecuteFinalizationCallback() | |||
| { | |||
| GCX_COOP(); | |||
| // TODO-WASM https://github.com/dotnet/runtime/issues/123712 | |||
There was a problem hiding this comment.
The TODO-WASM comment referencing issue 123712 is now misleading because the finalization kickoff is enabled immediately below it. Either remove the TODO or update it to describe the remaining limitation/work item so future readers don’t assume finalization is still disabled here.
| // TODO-WASM https://github.com/dotnet/runtime/issues/123712 |
| #ifdef FEATURE_PORTABLE_ENTRYPOINTS | ||
| MethodDesc* pMethod = PortableEntryPoint::GetMethodDesc(pTargetAddress); | ||
| (void)pMethod->GetInterpreterCodeWithPrestub(); | ||
| #endif // FEATURE_PORTABLE_ENTRYPOINTS |
There was a problem hiding this comment.
DispatchCallSimple is called with an argument buffer (pSrc / __pArgs) that often contains raw object pointers produced by OBJECTREF_TO_ARGHOLDER. Adding GetInterpreterCodeWithPrestub() here can trigger a GC via DoPrestub, which can invalidate those unprotected object pointers before the call is made. The prestub/interpreter-code initialization needs to happen earlier, before arguments are materialized as unmanaged pointers (or in a path that guarantees GC cannot occur).
No description provided.