Skip to content

Route QCall exceptions through marshalling - #132420

Draft
davidwrighton wants to merge 20 commits into
dotnet:mainfrom
davidwrighton:fixup_qcalls
Draft

Route QCall exceptions through marshalling#132420
davidwrighton wants to merge 20 commits into
dotnet:mainfrom
davidwrighton:fixup_qcalls

Conversation

@davidwrighton

@davidwrighton davidwrighton commented Aug 17, 2026

Copy link
Copy Markdown
Member

Summary

  • route exceptions from BEGIN_QCALL/END_QCALL through an out QCallExceptionStatus value using a stateful source-generated custom marshaller
  • capture the managed throwable in runtime TLS, return an integer pending status to managed code, and clear and rethrow the throwable from QCallExceptionStatusMarshaller.FromUnmanaged
  • migrate only QCalls that use the exception-handling macros, explicitly initialize the status to zero, and leave non-throwing or otherwise unwrapped QCalls unchanged
  • locate the enclosing InlinedCallFrame during native exception capture instead of retaining release-build frame and current-thread locals
  • preserve binder-visible IL-stub helper signatures with managed wrappers and align native declarations across Windows x86, Apple, Mono, browser, and WASI builds
  • regenerate the checked-in browser and WASI CoreCLR call helpers for the updated QCall ABI

Testing

  • build.cmd clr+libs -c Checked (0 warnings, 0 errors)
  • build.cmd mono+libs (0 warnings, 0 errors)
  • build.cmd clr+libs -os browser -c Debug (0 warnings, 0 errors)
  • build.cmd clr.corelib -os osx -a x64 -c Debug (0 warnings, 0 errors)
  • build.cmd clr.runtime -a x86 -c Checked (0 warnings, 0 errors)
  • src\tasks\WasmAppBuilder\generate-coreclr-helpers.cmd -c Debug
  • AmbiguousImplementationException runtime test (exit code 100; exercises 12 sequential exceptions)
  • migration audit: 360 original BEGIN_QCALL implementations migrated and no original non-BEGIN_QCALL implementations migrated
  • git diff --check

The WASI build compiles the affected CoreCLR and generated helper translation units, then fails at the existing final-link issues for PAL_ProbeMemory, shm_open, and shm_unlink.

Note

This pull request description was generated by GitHub Copilot.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 97d405d-8e39-4e76-afe4-2f94b74ecfa2
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 97d405d-8e39-4e76-afe4-2f94b74ecfa2
@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.

Copilot AI 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 changes CoreCLR QCall exception propagation by routing exceptions thrown inside BEGIN_QCALL/END_QCALL through an out QCallException status value that is custom-marshalled back to managed code. The native side captures throwables onto the managed Thread, and the managed marshaller rethrows during unmarshalling, while leaving non-BEGIN_QCALL QCalls on the prior behavior.

Changes:

  • Introduce QCallException + QCallExceptionMarshaller and update managed QCall signatures to include an out QCallException for BEGIN_QCALL-based QCalls.
  • Update native QCall entrypoints and exception macros to capture exceptions instead of dispatching/rethrowing directly across the boundary.
  • Add the per-thread storage needed to temporarily root the captured throwable and rethrow it from managed unmarshalling.

Reviewed changes

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

Show a summary per file
File Description
src/libraries/System.Private.CoreLib/src/System/Threading/Wasi/WasiFinalizerScheduler.cs Add conditional out QCallException to WASI finalizer QCalls.
src/libraries/System.Private.CoreLib/src/System/Threading/WaitHandle.Windows.cs Pass conditional out _ to updated reentrant wait QCall.
src/libraries/System.Private.CoreLib/src/System/Threading/Thread.cs Update CurrentOSThreadId QCall invocation to include conditional out _.
src/libraries/System.Private.CoreLib/src/System/Threading/Lock.cs Use updated Environment.GetProcessorCount signature under CORECLR.
src/libraries/System.Private.CoreLib/src/System/Runtime/Loader/AssemblyLoadContext.cs Thread out QCallException through multiple tracing/ALC QCalls.
src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/TypeMapLazyDictionary.cs Add out QCallException to QCalls used by type map cache population/lookups.
src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/ObjectiveC/ObjectiveCMarshal.cs Pass out _ to updated ObjC marshal QCalls.
src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/Marshal.NoCom.cs Extend internal signature with conditional out _ under CORECLR.
src/libraries/System.Private.CoreLib/src/System/Runtime/InteropServices/ComWrappers.cs Call updated RegisterIsRootedCallback under CORECLR.
src/libraries/System.Private.CoreLib/src/System/Reflection/Assembly.cs Update tracing QCall invocations to include out _.
src/libraries/System.Private.CoreLib/src/System/Environment.cs Update ProcessorCount initialization to use CORECLR QCall signature.
src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/TraceLogging/XplatEventLogger.cs Add out QCallException plumbing to xplat EventSource QCalls.
src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/EventPipeEventProvider.cs Add conditional out _ to EventPipeInternal QCalls.
src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/EventPipeEventDispatcher.Threads.cs Add conditional out _ to EventPipe session wait/disable QCalls.
src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/EventPipeEventDispatcher.cs Add conditional out _ to EventPipe dispatcher QCalls.
src/libraries/System.Private.CoreLib/src/System/Diagnostics/Tracing/EventPipe.cs Add conditional out _ to EventPipe enable QCall.
src/libraries/System.Private.CoreLib/src/System/AppContext.cs Add conditional out _ to host property QCall path.
src/coreclr/vm/weakreferencenative.h Add QCallException* to COM weak ref QCall signatures.
src/coreclr/vm/weakreferencenative.cpp Add QCallException* parameter to COM weak ref QCall implementations.
src/coreclr/vm/wasm/entrypoints.h Add QCallException* to WASI finalizer exported entrypoints.
src/coreclr/vm/versionresilienthashcode.h Add QCallException* to version-resilient hash QCall signature.
src/coreclr/vm/versionresilienthashcode.cpp Add QCallException* to version-resilient hash QCall implementation.
src/coreclr/vm/varargsnative.h Add QCallException* to varargs iterator QCall signatures.
src/coreclr/vm/varargsnative.cpp Add QCallException* to varargs iterator QCall implementations.
src/coreclr/vm/unsafeaccessors.cpp Add QCallException* to unsafe accessor resolver QCall.
src/coreclr/vm/tailcallhelp.h Add QCallException* to tailcall arg buffer allocation QCall.
src/coreclr/vm/tailcallhelp.cpp Add QCallException* to tailcall arg buffer allocation QCall implementation.
src/coreclr/vm/stubhelpers.h Add QCallException* to multiple interop/stub helper QCall signatures.
src/coreclr/vm/stackwalk.h Add QCallException* to AppendExceptionStackFrame QCall signature.
src/coreclr/vm/reflectioninvocation.h Add QCallException* to reflection invocation QCall signatures.
src/coreclr/vm/qcall.h Update QCall guidance/comments and route BEGIN/END macros to capture dispatcher.
src/coreclr/vm/qcall.cpp Add QCallException::SetThrowable implementation and thread rooting logic.
src/coreclr/vm/olevariant.h Add QCallException* to VARIANT record conversion QCall signature.
src/coreclr/vm/olevariant.cpp Add QCallException* to VARIANT record conversion QCall implementation.
src/coreclr/vm/object.h Add m_QCallException field + setter on ThreadBaseObject.
src/coreclr/vm/nativelibrarynative.h Add QCallException* to NativeLibrary load-by-name QCall signature.
src/coreclr/vm/nativelibrarynative.cpp Add QCallException* to NativeLibrary load-by-name QCall implementation.
src/coreclr/vm/nativeeventsource.h Add QCallException* to EventSource/perf tracing QCall signatures.
src/coreclr/vm/multicorejit.h Add QCallException* to MultiCoreJIT QCall signatures.
src/coreclr/vm/multicorejit.cpp Add QCallException* to MultiCoreJIT QCall implementations.
src/coreclr/vm/mlinfo.cpp Add QCallException* to StubHelpers_CreateCustomMarshaler QCall implementation.
src/coreclr/vm/method.hpp Add QCallException* to unsafe accessor resolver declaration.
src/coreclr/vm/managedmdimport.hpp Add QCallException* to metadata enum QCall signature.
src/coreclr/vm/managedmdimport.cpp Add QCallException* to metadata enum QCall implementation.
src/coreclr/vm/loaderallocator.hpp Add QCallException* to loader allocator destroy QCall signature.
src/coreclr/vm/loaderallocator.cpp Add QCallException* to loader allocator destroy QCall implementation.
src/coreclr/vm/JitQCallHelpers.h Add QCallException* to JIT helper QCall signatures.
src/coreclr/vm/jithelpers.cpp Add QCallException* to JIT helper QCalls and adjust handler installation in rare paths.
src/coreclr/vm/interoplibinterface.h Add QCallException* to ObjC/Java marshal QCall signatures.
src/coreclr/vm/interoplibinterface_objc.cpp Add QCallException* to ObjC marshal QCall implementations.
src/coreclr/vm/interoplibinterface_java.cpp Add QCallException* to Java marshal QCall implementations.
src/coreclr/vm/interoplibinterface_comwrappers.h Add QCallException* to ComWrappers QCall signatures.
src/coreclr/vm/interoplibinterface_comwrappers.cpp Add QCallException* to ComWrappers QCall implementations.
src/coreclr/vm/ilmarshalers.h Add QCallException* to safe array marshaler QCall signatures.
src/coreclr/vm/ilmarshalers.cpp Add QCallException* to safe array marshaler QCall implementations.
src/coreclr/vm/finalizerthread.cpp Add QCallException* to WASI finalizer QCall implementations.
src/coreclr/vm/exceptmacros.h Introduce QCallException native struct and capture-dispatcher exception macros.
src/coreclr/vm/exceptionhandlingqcalls.h Add QCallException* to exception-handling-related QCall signatures.
src/coreclr/vm/exceptionhandling.cpp Add QCallException* to exception-handling-related QCall implementations.
src/coreclr/vm/excep.cpp Add QCallException* to message QCalls and introduce QCall-specific unwind helper.
src/coreclr/vm/eventpipeinternal.h Add QCallException* to EventPipeInternal QCall signatures.
src/coreclr/vm/eventpipeinternal.cpp Add QCallException* to EventPipeInternal QCall implementations.
src/coreclr/vm/debugdebugger.h Add QCallException* to debugger/stacktrace QCall signatures.
src/coreclr/vm/debugdebugger.cpp Add QCallException* to debugger/stacktrace QCall implementations.
src/coreclr/vm/customattribute.h Add QCallException* to custom attribute QCall signatures.
src/coreclr/vm/customattribute.cpp Add QCallException* to custom attribute QCall implementations.
src/coreclr/vm/corelib.h Add corelib field mapping for new thread _qcallException field.
src/coreclr/vm/coreassemblyspec.cpp Add QCallException* to assembly spec initialization QCall signature.
src/coreclr/vm/comsynchronizable.h Add QCallException* to thread/lock-related QCall signatures.
src/coreclr/vm/comsynchronizable.cpp Add QCallException* to thread/lock-related QCall implementations.
src/coreclr/vm/commodule.h Add QCallException* to Reflection.Emit module builder QCall signatures.
src/coreclr/vm/commodule.cpp Add QCallException* to Reflection.Emit module builder QCall implementations.
src/coreclr/vm/comdependenthandle.h Add QCallException* to dependent handle QCall signatures.
src/coreclr/vm/comdependenthandle.cpp Add QCallException* to dependent handle QCall implementations.
src/coreclr/vm/comdelegate.h Add QCallException* to delegate-related QCall signatures.
src/coreclr/vm/comdelegate.cpp Add QCallException* to delegate-related QCall implementations.
src/coreclr/vm/clrconfignative.h Add QCallException* to CLR config QCall signature.
src/coreclr/vm/clrconfignative.cpp Add QCallException* to CLR config QCall implementation.
src/coreclr/vm/arraynative.h Add QCallException* to array creation/ctor QCall signatures.
src/coreclr/vm/arraynative.cpp Add QCallException* to array creation/ctor QCall implementations.
src/coreclr/vm/appdomainnative.hpp Add QCallException* to AppDomain/AppContext/string intern QCall signatures.
src/coreclr/vm/appdomainnative.cpp Add QCallException* to AppDomain/AppContext/string intern QCall implementations.
src/coreclr/System.Private.CoreLib/System.Private.CoreLib.csproj Include new QCallException.cs in CoreCLR CoreLib build.
src/coreclr/System.Private.CoreLib/src/System/Variant.cs Add out QCallException to COM VARIANT-related QCalls.
src/coreclr/System.Private.CoreLib/src/System/ValueType.cs Add out QCallException to ValueType helper QCalls.
src/coreclr/System.Private.CoreLib/src/System/TypeLoadException.CoreCLR.cs Add out QCallException to message-formatting QCall.
src/coreclr/System.Private.CoreLib/src/System/Threading/ObjectHeader.CoreCLR.cs Add out QCallException to lock-object QCall.
src/coreclr/System.Private.CoreLib/src/System/String.CoreCLR.cs Add out QCallException to string intern/is-interned QCalls.
src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CreateUninitializedCache.CoreCLR.cs Add out QCallException to create-uninitialized info QCall.
src/coreclr/System.Private.CoreLib/src/System/RuntimeType.CoreCLR.cs Add out QCallException to multiple RuntimeType/reflection QCalls.
src/coreclr/System.Private.CoreLib/src/System/RuntimeType.BoxCache.cs Add out QCallException to boxing cache QCall.
src/coreclr/System.Private.CoreLib/src/System/Runtime/InteropServices/TrackerObjectManager.CoreCLR.cs Add out QCallException to wrapper-cache registration QCall.
src/coreclr/System.Private.CoreLib/src/System/Runtime/InteropServices/ObjectiveCMarshal.CoreCLR.cs Add out QCallException to ObjC marshal QCalls.
src/coreclr/System.Private.CoreLib/src/System/Runtime/InteropServices/NativeLibrary.CoreCLR.cs Add out QCallException to NativeLibrary load QCall.
src/coreclr/System.Private.CoreLib/src/System/Runtime/InteropServices/Java/JavaMarshal.CoreCLR.cs Add out QCallException to Java marshal QCalls.
src/coreclr/System.Private.CoreLib/src/System/Runtime/InteropServices/GCHandle.CoreCLR.cs Add out QCallException to GCHandle QCalls.
src/coreclr/System.Private.CoreLib/src/System/Runtime/InteropServices/ComWrappers.CoreCLR.cs Add out QCallException to ComWrappers QCalls.
src/coreclr/System.Private.CoreLib/src/System/Runtime/ExceptionServices/InternalCalls.cs Add out QCallException to EH-related QCall internal calls.
src/coreclr/System.Private.CoreLib/src/System/Runtime/DependentHandle.cs Add out QCallException to dependent handle QCalls.
src/coreclr/System.Private.CoreLib/src/System/Runtime/ControlledExecution.CoreCLR.cs Add out QCallException to ThreadNative_Abort QCall.
src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/VirtualDispatchHelpers.cs Add out QCallException to virtual dispatch resolver QCall.
src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/StaticsHelpers.cs Add out QCallException to thread statics QCalls.
src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/QCallException.cs Introduce managed QCall exception status type + custom marshaller.
src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/InitHelpers.cs Add out QCallException to init-class QCall.
src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/GenericsHelpers.cs Add out QCallException to generic handle worker QCall.
src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/CastHelpers.cs Add out QCallException to cast helper QCalls.
src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncHelpers.CoreCLR.cs Add out QCallException to async continuation QCall.
src/coreclr/System.Private.CoreLib/src/System/Reflection/TypeNameResolver.CoreCLR.cs Add out QCallException to unsafe accessor resolver QCall.
src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimeModule.cs Add out QCallException to module name/types QCalls.
src/coreclr/System.Private.CoreLib/src/System/Reflection/RuntimeCustomAttributeData.cs Add out QCallException to custom attribute QCalls.
src/coreclr/System.Private.CoreLib/src/System/Reflection/MethodBase.CoreCLR.cs Add out QCallException to GetCurrentMethod QCall.
src/coreclr/System.Private.CoreLib/src/System/Reflection/Metadata/MetadataUpdater.cs Add out QCallException to metadata update QCalls.
src/coreclr/System.Private.CoreLib/src/System/Reflection/Metadata/AssemblyExtensions.cs Add out QCallException to raw metadata QCall.
src/coreclr/System.Private.CoreLib/src/System/Reflection/MdImport.cs Add out QCallException to metadata import enum QCall.
src/coreclr/System.Private.CoreLib/src/System/Reflection/LoaderAllocator.cs Add out QCallException to loader allocator destroy QCall.
src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/RuntimePropertyBuilder.cs Add out _ to Reflection.Emit QCall invocations.
src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/RuntimeParameterBuilder.cs Add out _ to Reflection.Emit QCall invocations.
src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/RuntimeMethodBuilder.cs Add out _ to Reflection.Emit QCall invocations.
src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/RuntimeFieldBuilder.cs Add out _ to Reflection.Emit QCall invocations.
src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/RuntimeEventBuilder.cs Add out _ to Reflection.Emit QCall invocations.
src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/RuntimeAssemblyBuilder.cs Add out QCallException to dynamic assembly creation QCall.
src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/DynamicMethod.CoreCLR.cs Add out _ to compile-method QCall invocation.
src/coreclr/System.Private.CoreLib/src/System/Reflection/Emit/DynamicILGenerator.cs Add out _ to destroy handle QCall invocation.
src/coreclr/System.Private.CoreLib/src/System/Reflection/AssemblyName.CoreCLR.cs Add out QCallException to assembly spec init QCall.
src/coreclr/System.Private.CoreLib/src/System/Reflection/Assembly.CoreCLR.cs Add out QCallException to assembly lookup QCalls.
src/coreclr/System.Private.CoreLib/src/System/Object.CoreCLR.cs Add out _ to clone allocation QCall invocation.
src/coreclr/System.Private.CoreLib/src/System/IO/Stream.CoreCLR.cs Add out QCallException to overridden-check QCall.
src/coreclr/System.Private.CoreLib/src/System/IO/FileLoadException.CoreCLR.cs Add out QCallException to file load message QCalls.
src/coreclr/System.Private.CoreLib/src/System/Exception.CoreCLR.cs Add out QCallException to exception-related QCalls.
src/coreclr/System.Private.CoreLib/src/System/Environment.CoreCLR.cs Add out QCallException to Environment QCalls (Exit/FailFast/GetProcessorCount).
src/coreclr/System.Private.CoreLib/src/System/Enum.CoreCLR.cs Add out QCallException to enum values/names QCall.
src/coreclr/System.Private.CoreLib/src/System/Diagnostics/StackTrace.CoreCLR.cs Add out QCallException to stack trace frames QCall.
src/coreclr/System.Private.CoreLib/src/System/Diagnostics/StackFrame.CoreCLR.cs Add out QCallException to method-desc-from-IP QCall.
src/coreclr/System.Private.CoreLib/src/System/Diagnostics/Debugger.cs Add out QCallException to debugger break/notification QCalls.
src/coreclr/System.Private.CoreLib/src/System/Delegate.CoreCLR.cs Add out QCallException to delegate binding/construct QCalls.
src/coreclr/System.Private.CoreLib/src/System/ComAwareWeakReference.CoreCLR.cs Add out QCallException to COM weak ref QCalls.
src/coreclr/System.Private.CoreLib/src/System/CLRConfig.cs Add out QCallException to CLR config QCall.
src/coreclr/System.Private.CoreLib/src/System/Array.CoreCLR.cs Add out QCallException to array creation/ctor QCalls.
src/coreclr/System.Private.CoreLib/src/System/ArgIterator.cs Add out QCallException to varargs iterator QCalls.
src/coreclr/System.Private.CoreLib/src/System/AppContext.CoreCLR.cs Add out QCallException to host property QCall.
src/coreclr/System.Private.CoreLib/src/Internal/VersionResilientHashCode.CoreCLR.cs Add out QCallException to version-resilient hash QCall.
src/coreclr/System.Private.CoreLib/src/Internal/Runtime/CompilerHelpers/ThrowHelpers.cs Add out QCallException to throw-helper QCalls.
src/coreclr/nativeaot/Runtime.Base/src/System/Runtime/StackFrameIterator.cs Add conditional out _ to EH QCall internal calls.
src/coreclr/nativeaot/Runtime.Base/src/System/Runtime/ExceptionHandling.cs Add conditional out _ to EH QCall internal calls.


if (exceptionPending == 1)
{
Exception exception = _thread.GetAndClearQCallException();
Comment on lines +345 to +349
{ \
GCX_COOP(); \
OBJECTREF throwable = ExInfo::CreateThrowable(ex.GetExceptionRecord(), FALSE); \
qcallError->SetThrowable(throwable); \
} \
@davidwrighton davidwrighton reopened this Aug 17, 2026
@azure-pipelines

Copy link
Copy Markdown
Azure Pipelines:
Successfully started running 3 pipeline(s).
13 pipeline(s) were filtered out due to trigger conditions.
There may be pipelines that require an authorized user to comment /azp run to run.

namespace System.Runtime.CompilerServices
{
[NativeMarshalling(typeof(QCallExceptionMarshaller))]
internal readonly struct QCallException

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.

Naming nit: The name makes it look like an exception type per .NET naming guidelines, but it is not actually an exception type. Copilot suggests QCallExceptionStatus as a better name:


Yes. QCallException is misleading because the type is an out-only marshalling protocol/status value, not an exception object.

My preferred name would be:

internal readonly struct QCallExceptionStatus

This communicates that:

  • it is associated with QCall exception propagation;
  • it is a status/result value;
  • it is not itself throwable or derived from Exception.

Other reasonable alternatives:

Name Comments
QCallExceptionStatus Best overall; explicit and reasonably concise.
QCallExceptionPropagation Emphasizes that its purpose is transferring a pending exception across the boundary. A little abstract for a value type.
QCallExceptionResult Good if the parameter is viewed as an invocation result, although “result” may imply the exception itself is returned.
QCallThrowStatus Short and accurately describes the behavior, but “throw” may sound like an operation rather than a status.
QCallErrorStatus Avoids calling it an exception, but loses the important distinction that it carries managed exceptions rather than ordinary error codes.
QCallExceptionToken Suggests an opaque token, which is not quite accurate because the marshaller retrieves the exception from the managed thread.
QCallExceptionCarrier Describes the mechanism, but sounds like it contains the exception, which the empty managed struct does not.

I would also consider renaming the native pointer consistently:

QCallExceptionStatus* qcallExceptionStatus

or, if using the shorter managed name:

QCallThrowStatus* qcallThrowStatus

For example:

[NativeMarshalling(typeof(QCallExceptionStatusMarshaller))]
internal readonly struct QCallExceptionStatus
{
}
private static partial void SomeQCall(
    ...,
    out QCallExceptionStatus exceptionStatus);

The strongest alternative if you want to avoid Exception entirely is probably:

QCallThrowStatus

However, I would favor QCallExceptionStatus because it preserves the semantic connection to the managed exception being propagated while making clear that the type itself is merely a status marker.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 97d405d-8e39-4e76-afe4-2f94b74ecfa2
Copilot AI review requested due to automatic review settings August 18, 2026 00:15

Copilot AI 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

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

Comment on lines 62 to 63
private static IntPtr AllocateReferenceTrackingHandle(object obj)
=> AllocateReferenceTrackingHandle(ObjectHandleOnStack.Create(ref obj));
Copilot AI review requested due to automatic review settings August 18, 2026 19:45

Copilot AI 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

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

Suppressed comments (1)

src/coreclr/System.Private.CoreLib/src/System/Runtime/CompilerServices/QCallException.cs:26

  • QCallExceptionMarshaller.FromUnmanaged only treats exceptionPending == 1 as an exception. Since the native representation is an INT32 flag, using a non-zero check (!= 0) is more robust against future expansion or accidental non-1 values, and avoids silently skipping exception propagation if the flag is ever set to another non-zero value.

Add managed forwarding overloads for helpers invoked by native IL stub generation so their CoreLib binder signatures remain stable after adding QCall exception parameters.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 97d405d-8e39-4e76-afe4-2f94b74ecfa2
Keep the CoreCLR QCall exception argument and Mono zero-argument call in separately formatted conditional branches so both CoreLib builds satisfy style validation.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 97d405d-8e39-4e76-afe4-2f94b74ecfa2
Update browser and WASI interpreter-to-managed thunks for the QCall exception argument and refresh generated P/Invoke tables.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 97d405d-8e39-4e76-afe4-2f94b74ecfa2
Copilot AI review requested due to automatic review settings August 19, 2026 20:53

Copilot AI 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

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

Suppressed comments (1)

src/coreclr/vm/exceptmacros.h:220

  • QCallException is custom-marshalled as an int on the managed side, but the native side now defines it as a struct. Since the managed/native ABI contract depends on this staying a single INT32, add a static_assert to prevent accidental layout changes from silently breaking QCall exception propagation.

Rename the managed and native status carrier to QCallExceptionStatus and update its custom marshaller. Restore runtime-neutral Objective-C wrappers, align the AssemblyName declaration, and remove exception capture from the non-throwing GC mode helper.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 97d405d-8e39-4e76-afe4-2f94b74ecfa2
Copilot AI review requested due to automatic review settings August 19, 2026 22:57

Copilot AI 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

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

Suppressed comments (1)

src/coreclr/vm/excep.cpp:6713

  • OBJECTREF throwable created from ExInfo::CreateThrowable(...) is used without GCPROTECT. In cooperative mode, the thread can be suspended for GC before the reference is published to a scanned root (TLS), which risks losing the throwable before SetThrowable stores it.

Comment thread src/coreclr/vm/excep.cpp Outdated
Comment on lines +6698 to +6700
OBJECTREF orThrowable = CLRException::GetThrowableFromException(pException);
CONSISTENCY_CHECK(orThrowable != NULL);
pQCallException->SetThrowable(orThrowable);
@davidwrighton

Copy link
Copy Markdown
Member Author

I built Release runtime + libraries at the mainline parent (a0f44dfcb92) and at this branch head (780f494666e), then benchmarked short successful QCalls in both host orders. The averaged deltas were -0.38% for string.IsInterned (hit), +0.53% for string.IsInterned (miss), and +0.78% for string.Intern (hit), all below 1 ns. Run-order variation was larger than the measured deltas, so this test found no consistent success-path performance regression above the local noise floor.

Benchmark source, full result table, methodology, and reproduction commands: https://gist.github.com/davidwrighton/3596168f4db88a825f6038ccecac13fe

Note

This benchmark analysis and comment were generated with GitHub Copilot.

Comment on lines +1 to +44
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Diagnostics;
using System.Runtime.ExceptionServices;
using System.Runtime.InteropServices.Marshalling;
using System.Threading;

namespace System.Runtime.CompilerServices
{
[NativeMarshalling(typeof(QCallExceptionStatusMarshaller))]
internal readonly struct QCallExceptionStatus
{
}

[CustomMarshaller(typeof(QCallExceptionStatus), MarshalMode.ManagedToUnmanagedOut, typeof(QCallExceptionStatusMarshaller))]
internal struct QCallExceptionStatusMarshaller
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void FromUnmanaged(int exceptionPending)
{
if (exceptionPending == 1)
{
HandleException();
}
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public QCallExceptionStatus ToManaged() => default;

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Free() { }

[MethodImpl(MethodImplOptions.NoInlining)]
private static void HandleException()
{
Exception exception = Thread.GetAndClearQCallException();

// Throw during unmarshalling so QCall exception propagation remains as close as
// possible to throwing directly from native code, as QCalls did previously.
ExceptionDispatchInfo.Throw(exception);
}
}
}

This comment was marked as resolved.

Resolve QCall signature conflicts by preserving upstream API updates while retaining explicit exception-status propagation for QCalls that use BEGIN_QCALL.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 97d405d-8e39-4e76-afe4-2f94b74ecfa2
Squash the LibraryImportErrorHandler branch changes into one independently revertable commit, including hidden-return-value handling and generator tests.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 97d405d-8e39-4e76-afe4-2f94b74ecfa2
Return pointer-sized exception statuses from native QCalls and process them through LibraryImport hidden-return error handlers. Preserve exact exceptions with GC handles, recreate special exceptions through the runtime, remove thread-local storage, and regenerate WebAssembly helpers.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 97d405d-8e39-4e76-afe4-2f94b74ecfa2
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 97d405d-8e39-4e76-afe4-2f94b74ecfa2
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 97d405d-8e39-4e76-afe4-2f94b74ecfa2
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: b31492bf-57a2-4c93-8e29-1bc8e555289d
Release the caught native exception after transferring its throwable to the QCall status, and preserve the previous customer-visible COM exception stack trace.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot-Session: df97bfcc-e3cb-470b-be04-ac63dfebcb4a
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants