Skip to content

Exclude runtime-async methods from built-in COM vtables and GUID calculations - #122195

Merged
jkoritzinsky merged 11 commits into
dotnet:mainfrom
jkoritzinsky:runtime-async-com
Dec 10, 2025
Merged

Exclude runtime-async methods from built-in COM vtables and GUID calculations#122195
jkoritzinsky merged 11 commits into
dotnet:mainfrom
jkoritzinsky:runtime-async-com

Conversation

@jkoritzinsky

@jkoritzinskyjkoritzinsky commented Dec 4, 2025

Copy link
Copy Markdown
Member

Adjust built-in COM to interact correctly with runtime-async.

For CCWs, async-call-conv methods are excluded from the vtable and CLSID/IID computation. This ensures that auto-generated IIDs/CLSIDs for the IClassX scenario are unchanged. Additionally, this ensures that the Task-returning (compiler-async call conv) variants are the variants present in the vtable, preserving existing behavior.

For RCWs, the async-call-conv methods are excluded from the vtable slot calculation and the async thunk variants are adjusted to not be treated as CLR->COM methods and instead call the Task-returning thunks on the ComImport interface (similar to how they behave regularly).

Also adds tests that run with both compiler async and runtime async to ensure equivalent behavior.

Fixes#121765

@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @dotnet/interop-contrib
See info in area-owners.md if you want to be subscribed.

Comment threadsrc/coreclr/vm/comtoclrcall.cpp

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

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 ensures that built-in COM interop correctly handles runtime-async methods by excluding them from vtable and GUID calculations. For CCWs, async-call-conv methods are omitted from the vtable and CLSID/IID computation to preserve backward compatibility with existing IClassX IIDs/CLSIDs. For RCWs, async-call-conv methods are excluded from vtable slot calculations, and async thunks are adjusted to call Task-returning interface methods using CALLVIRT.

Key changes:

  • Excludes runtime-async methods from COM vtable and interface ID generation
  • Adds SparseVTableMap::RecordExcludedMethod to track excluded async methods
  • Updates async thunk generation to use CALLVIRT for abstract ComImport methods
  • Adds comprehensive tests validating vtable layout for both compiler-async and runtime-async scenarios

Reviewed changes

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

Show a summary per file
FileDescription
src/coreclr/vm/methodtablebuilder.cppRecords async variant methods as excluded in COM vtables when building method tables
src/coreclr/vm/method.cppAdds assertion that async methods don't request COM slots
src/coreclr/vm/interoputil.cppConverts ThrowHR to assertion for async methods in stringified interface definitions
src/coreclr/vm/dispatchinfo.cppReturns NULL instead of throwing for runtime-async methods in IDispatch scenarios
src/coreclr/vm/comtoclrcall.cppRemoves redundant async method check and adds precondition assertions
src/coreclr/vm/commtmemberinfomap.cppSkips async methods when building COM member info maps and handles null entries
src/coreclr/vm/comcallablewrapper.cppFilters out async methods during CCW vtable layout for both classes and interfaces
src/coreclr/vm/clrtocomcall.cppFixes switch statement formatting and confirms async methods not supported on IDispatch
src/coreclr/vm/class.hDeclares RecordExcludedMethod for SparseVTableMap
src/coreclr/vm/class.cppImplements RecordExcludedMethod to track excluded vtable slots
src/coreclr/vm/asyncthunks.cppUses CALLVIRT for abstract task-returning variants (ComImport scenarios)
src/tests/Interop/COM/ServerContracts/Server.Contracts.hAdds COM interface definitions with runtime-generated IIDs for test validation
src/tests/Interop/COM/RuntimeAsync/RuntimeAsync.csTests that async methods don't modify CCW/RCW vtables
src/tests/Interop/COM/RuntimeAsync/TaskComServer.csImplements COM server with Task-returning methods for testing
src/tests/Interop/COM/RuntimeAsync/RuntimeAsyncNative.cppNative functions to validate vtable slot layout from C++
src/tests/Interop/COM/RuntimeAsync/RuntimeAsync.csprojTest project with runtime-async feature enabled
src/tests/Interop/COM/RuntimeAsync/CompilerAsync.csprojTest project using compiler async (baseline comparison)
src/tests/Interop/COM/RuntimeAsync/CMakeLists.txtBuild configuration for native test library
src/tests/Interop/CMakeLists.txtAdds RuntimeAsync subdirectory to build

Comment threadsrc/coreclr/vm/class.cpp Outdated
Comment threadsrc/coreclr/vm/commtmemberinfomap.cpp
Comment threadsrc/coreclr/vm/class.cpp Outdated
Comment threadsrc/coreclr/vm/clrtocomcall.cpp
Comment threadsrc/coreclr/vm/comcallablewrapper.cpp Outdated
Comment threadsrc/coreclr/vm/comcallablewrapper.cpp Outdated
Comment threadsrc/tests/Interop/COM/RuntimeAsync/TaskComServer.cs Outdated
Comment threadsrc/tests/Interop/COM/RuntimeAsync/TaskComServer.cs Outdated
Comment threadsrc/coreclr/vm/comcallablewrapper.cpp Outdated
Comment threadsrc/coreclr/vm/comcallablewrapper.cpp Outdated
Comment threadsrc/coreclr/vm/comcallablewrapper.cpp Outdated
Comment threadsrc/coreclr/vm/commtmemberinfomap.cpp Outdated
Comment threadsrc/coreclr/vm/comcallablewrapper.cpp Outdated

@AaronRobinsonMSFTAaronRobinsonMSFT left a comment

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.

Should we also update the COM source generator to avoid generating these signatures? Basically any interface marked with GeneratedComInterface can't have an async method?

Comment threadsrc/coreclr/vm/class.cpp
Comment threadsrc/coreclr/vm/class.cpp Outdated
Comment threadsrc/coreclr/vm/comtoclrcall.cpp
@jkoritzinsky

Copy link
Copy Markdown
MemberAuthor

Should we also update the COM source generator to avoid generating these signatures? Basically any interface marked with GeneratedComInterface can't have an async method?

I don't think we need to block anything for GeneratedComInterface, as using [return:MarshalUsing(typeof(Marshaller))] with an async Task or async Task<T> is entirely valid.

Unlike built-in COM, we don't need to worry about the runtime introducing additional synthetic members as we burn in vtable offsets at compile time (and even if we set them up at runtime, the async variants are hidden from reflection so we wouldn't accidentally see them).

Comment threadsrc/coreclr/vm/class.cpp Outdated
Co-authored-by: Aaron R Robinson <arobins@microsoft.com>
Comment threadsrc/coreclr/vm/class.cpp Outdated
Comment threadsrc/coreclr/vm/class.cpp Outdated

// other(arg)
pCode->EmitCALL(userFuncToken, localArg, 1);
if (pTaskReturningVariant->IsAbstract())

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.

When is this encountered? Do we have a test for this?

Copy link
Copy Markdown
MemberAuthor

Choose a reason for hiding this comment

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

I hit this in the RCW side of the tests I added.

Without this, the async variant tries to call the Task-returning CLR->COM interface method with the call instruction instead of callvirt, with results in an exception from the JIT for invalid IL.

Comment threadsrc/tests/Interop/COM/RuntimeAsync/RuntimeAsync.csproj
Comment threadsrc/coreclr/vm/clrtocomcall.cpp Outdated

@jkotasjkotas left a comment

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.

LGTM. Thank you for adding the tests

@jkoritzinsky
jkoritzinsky merged commit 55728d4 into dotnet:mainDec 10, 2025
98 of 103 checks passed
@jkoritzinsky
jkoritzinsky deleted the runtime-async-com branch December 10, 2025 21:46
jakobbotsch added a commit that referenced this pull request Jan 5, 2026
#122195 introduced a runtime async test that is the first one to run in
combination with other stress configurations. It revealed that when
leave callbacks are emitted we do not appropriately zero the async
continuation on standard returns.
Fix#122475
@github-actionsgithub-actionsBot locked and limited conversation to collaborators Jan 10, 2026
Sign up for freeto subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[RuntimeAsync] TODOs in COM area. Overall behavior of COM with Runtime Async enabled.

4 participants

@jkoritzinsky@jkotas@AaronRobinsonMSFT