Skip to content

[wasm][coreclr] Cache calli cookies#127016

Open
radekdoulik wants to merge 15 commits intodotnet:mainfrom
radekdoulik:pe-cookie-cache
Open

[wasm][coreclr] Cache calli cookies#127016
radekdoulik wants to merge 15 commits intodotnet:mainfrom
radekdoulik:pe-cookie-cache

Conversation

@radekdoulik
Copy link
Copy Markdown
Member

@radekdoulik radekdoulik commented Apr 16, 2026

Avoid repeated expensive calls to get calli cookie by caching it

Checked in HelloWorld

CalliCookie cache: 18 hits, 17 misses, 35 total (51.4% hit rate)

Avoid repeated expensive calls to get calli cookie by caching it
@dotnet-policy-service
Copy link
Copy Markdown
Contributor

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

Copy link
Copy Markdown
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 reduces overhead in the WASM interpreter call path by caching the computed calli cookie on the MethodDesc, avoiding repeated (and potentially expensive) cookie computation for repeated invocations of the same managed method.

Changes:

  • Cache and reuse a per-MethodDesc calli cookie in InvokeManagedMethod on WASM.
  • Extend MethodDescCodeData (portable entrypoints + interpreter builds) to store a CalliCookie.
  • Add MethodDesc::{GetCalliCookie, SetCalliCookie} APIs to manage the cached value thread-safely.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.

File Description
src/coreclr/vm/wasm/helpers.cpp Uses MethodDesc-cached cookie to avoid repeated GetCookieForCalliSig calls.
src/coreclr/vm/method.hpp Adds CalliCookie storage in MethodDescCodeData and declares accessor APIs under the relevant feature flags.
src/coreclr/vm/method.cpp Implements thread-safe cookie get/set using EnsureCodeDataExists + interlocked/volatile operations.

Comment thread src/coreclr/vm/wasm/helpers.cpp
Comment thread src/coreclr/vm/wasm/helpers.cpp
Comment thread src/coreclr/vm/method.cpp Outdated
…Cookie typedef

- Define InterpreterCalliCookie typedef: function pointer on WASM,
  CallStubHeader* on non-WASM (Jan's feedback)
- Unify SetCalliCookie/GetCalliCookie API, removing the separate
  SetCallStub/GetCallStub and FEATURE_PORTABLE_ENTRYPOINTS branching
  in MethodDesc
- Cache calli cookie on targetMethod in the WASM calli path in
  interpexec.cpp (Jan's feedback)
- Re-read cookie after SetCalliCookie in wasm/helpers.cpp to use
  the race-winning value (Aaron's feedback)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings April 26, 2026 20:29
Copy link
Copy Markdown
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

Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.

Comment thread src/coreclr/vm/wasm/helpers.cpp
Comment thread src/coreclr/vm/jitinterface.cpp Outdated
Comment thread src/coreclr/vm/interpexec.cpp Outdated
Comment thread src/coreclr/vm/interpexec.cpp Outdated
Copilot AI review requested due to automatic review settings April 27, 2026 13:09
Copy link
Copy Markdown
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

Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (3)

src/coreclr/vm/method.hpp:272

  • MethodDescCodeData stores the cached cookie as void*, but the accessor/mutator APIs are typed as InterpreterCalliCookie (which is a function pointer on WASM). This forces repeated casts between function pointers and void* and can trip toolchain warnings / UB concerns. Consider storing the field as InterpreterCalliCookie (or a union/std::atomic<InterpreterCalliCookie>-style storage) so SetCalliCookie/GetCalliCookie don’t need object-pointer casts on portable-entrypoint builds.
#endif // FEATURE_INTERPRETER
#if defined(_DEBUG) && defined(ALLOW_SXS_JIT)
    PatchpointInfo *AltJitPatchpointInfo;
#endif // _DEBUG && ALLOW_SXS_JIT

src/coreclr/vm/method.cpp:290

  • SetCalliCookie currently casts InterpreterCalliCookie to void* for the interlocked CAS. On WASM InterpreterCalliCookie is a function pointer, and function-pointer <-> void* casts are non-standard and may be rejected/warned under some toolchains (especially with -Werror). If possible, make m_codeData->CalliCookie be typed as InterpreterCalliCookie so the CAS can operate on the correctly-typed field without these casts.
    IfFailRet(EnsureCodeDataExists(NULL));

    _ASSERTE(m_codeData != NULL);
    VolatileStoreWithoutBarrier(&m_codeData->AltJitPatchpointInfo, pInfo);
    return S_OK;
}

src/coreclr/vm/wasm/helpers.cpp:598

  • StringToWasmSigThunkHash stores values as void*, but LookupThunk now uses an InterpreterCalliCookie local and passes its address to Lookup via (void**)&thunk. This relies on type-punning a function-pointer object as void* storage. A safer pattern here would be to look up into a void* temp and then cast the returned value to InterpreterCalliCookie for the return.
}

extern "C" void RhpInterfaceDispatch8()
{
    PORTABILITY_ASSERT("RhpInterfaceDispatch8 is not implemented on wasm");

Comment thread src/coreclr/vm/interpexec.h
Comment thread src/coreclr/vm/wasm/helpers.cpp Outdated
Copilot AI review requested due to automatic review settings April 28, 2026 03:46
Comment thread src/coreclr/vm/wasm/helpers.cpp Outdated
Comment thread src/coreclr/vm/interpexec.cpp Outdated
Comment thread src/coreclr/vm/jitinterface.cpp Outdated
Comment thread src/coreclr/vm/method.cpp Outdated
Comment thread src/coreclr/vm/method.hpp Outdated
Copy link
Copy Markdown
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

Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.

Comment thread src/coreclr/vm/wasm/helpers.cpp Outdated
Comment thread src/coreclr/vm/method.hpp
Comment thread src/coreclr/vm/method.cpp
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings April 28, 2026 04:03
Copy link
Copy Markdown
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

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.

Comments suppressed due to low confidence (1)

src/coreclr/vm/wasm/helpers.cpp:747

  • On WASM, InvokeCalliStub calls pParam->cookie as a function pointer with 4 parameters, but InterpreterCalliCookie (currently) is a function-pointer typedef with 3 parameters under FEATURE_PORTABLE_ENTRYPOINTS. This indicates the cookie is not a single, consistently-typed function pointer; treating it as such risks calling a thunk with the wrong signature (UB).

Recommend keeping the cookie opaque (void*/TADDR) and casting to the exact thunk signature at the specific call site (or split into distinct cookie types for the 3-arg vs 4-arg thunk variants).

    PCODE actualFtn = (PCODE)PortableEntryPoint::GetActualCode(pParam->ftn);
    ((void(*)(PCODE, int8_t*, int8_t*, PCODE))pParam->cookie)(actualFtn, pParam->pArgs, pParam->pRet, pParam->ftn);
}

void InvokeUnmanagedCalli(PCODE ftn, InterpreterCalliCookie cookie, int8_t *pArgs, int8_t *pRet)
{
    _ASSERTE(ftn != (PCODE)NULL);
    _ASSERTE(cookie != NULL);
    ((void(*)(PCODE, int8_t*, int8_t*))cookie)(ftn, pArgs, pRet);

Comment thread src/coreclr/vm/interpexec.h
Comment thread src/coreclr/vm/jitinterface.cpp
Comment on lines +739 to +740
MethodDesc* md = PortableEntryPoint::GetMethodDesc(pParam->ftn);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
MethodDesc* md = PortableEntryPoint::GetMethodDesc(pParam->ftn);

unused

NOINLINE static void CallFunc_F64_F64_F64_RetF64_PE(PCODE pcode, int8_t* pArgs, int8_t* pRet, PCODE pPortableEntryPointContext)
NOINLINE static void CallFunc_F64_F64_F64_RetF64_PE(PCODE pPortableEntryPoint, int8_t* pArgs, int8_t* pRet)
{
PCODE pcode = (PCODE)PortableEntryPoint::GetActualCode(pPortableEntryPoint);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
PCODE pcode = (PCODE)PortableEntryPoint::GetActualCode(pPortableEntryPoint);

{
PCODE pcode = (PCODE)PortableEntryPoint::GetActualCode(pPortableEntryPoint);
alignas(16) int framePointer = TERMINATE_R2R_STACK_WALK;
double (*fptr)(int*, double, double, double, PCODE) = (double (*)(int*, double, double, double, PCODE))pcode;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Suggested change
double (*fptr)(int*, double, double, double, PCODE) = (double (*)(int*, double, double, double, PCODE))pcode;
double (*fptr)(int*, double, double, double, PCODE) = (double (*)(int*, double, double, double, PCODE))PortableEntryPoint::GetActualCode(pPortableEntryPoint);;

We do not really need a local var for this.

If you would like to have a local var, the type should be void* (same as what PortableEntryPoint::GetActualCode returns)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

arch-wasm WebAssembly architecture area-VM-coreclr

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants