Skip to content

NO-REVIEW try to enable finalizers on top of CallDescrWorkerInternal change#124789

Closed
radekdoulik wants to merge 2 commits intodotnet:mainfrom
radekdoulik:clr-wasm-try-to-enable-finalizers
Closed

NO-REVIEW try to enable finalizers on top of CallDescrWorkerInternal change#124789
radekdoulik wants to merge 2 commits intodotnet:mainfrom
radekdoulik:clr-wasm-try-to-enable-finalizers

Conversation

@radekdoulik
Copy link
Member

No description provided.

To call it before we potentially put object references on stack,
because DoPrestub can trigger GC.
@radekdoulik radekdoulik added this to the Future milestone Feb 24, 2026
Copilot AI review requested due to automatic review settings February 24, 2026 11:22
@radekdoulik radekdoulik added NO-REVIEW Experimental/testing PR, do NOT review it area-VM-coreclr labels Feb 24, 2026
@dotnet-policy-service
Copy link
Contributor

Tagging subscribers to this area: @agocke
See info in area-owners.md if you want to be subscribed.

Copy link
Contributor

Copilot AI left a 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 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 CallDescrWorkerInternal and 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.

Comment on lines +308 to +312
#ifdef FEATURE_PORTABLE_ENTRYPOINTS
MethodDesc* pMethod = PortableEntryPoint::GetMethodDesc(m_pCallTarget);
(void)pMethod->GetInterpreterCodeWithPrestub();
#endif // FEATURE_PORTABLE_ENTRYPOINTS

Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
#ifdef FEATURE_PORTABLE_ENTRYPOINTS
MethodDesc* pMethod = PortableEntryPoint::GetMethodDesc(m_pCallTarget);
(void)pMethod->GetInterpreterCodeWithPrestub();
#endif // FEATURE_PORTABLE_ENTRYPOINTS

Copilot uses AI. Check for mistakes.
Comment on lines 17 to +19
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);
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines +1888 to +1890
GCX_PREEMP();
(void)DoPrestub(NULL /* MethodTable */, CallerGCMode::Coop);
return GetInterpreterCode();
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
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;

Copilot uses AI. Check for mistakes.
@@ -59,7 +59,7 @@ extern "C" void SystemJS_ExecuteFinalizationCallback()
{
GCX_COOP();
// TODO-WASM https://github.com/dotnet/runtime/issues/123712
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
// TODO-WASM https://github.com/dotnet/runtime/issues/123712

Copilot uses AI. Check for mistakes.
Comment on lines +193 to +196
#ifdef FEATURE_PORTABLE_ENTRYPOINTS
MethodDesc* pMethod = PortableEntryPoint::GetMethodDesc(pTargetAddress);
(void)pMethod->GetInterpreterCodeWithPrestub();
#endif // FEATURE_PORTABLE_ENTRYPOINTS
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-VM-coreclr NO-REVIEW Experimental/testing PR, do NOT review it

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants