From 2f712048015a87b42f9bc325d230998bcf3688d4 Mon Sep 17 00:00:00 2001 From: lateralusX Date: Mon, 6 Jul 2026 20:25:39 +0200 Subject: [PATCH 1/7] Add support for retrieving method native code start address. --- .../src/System/RuntimeHandles.cs | 10 ++++++ src/coreclr/vm/qcallentrypoints.cpp | 1 + src/coreclr/vm/runtimehandles.cpp | 33 +++++++++++++++++++ src/coreclr/vm/runtimehandles.h | 1 + .../AsyncStateMachineDiagnostics.cs | 9 ++++- .../src/System/RuntimeMethodHandle.cs | 8 +++++ src/mono/mono/metadata/icall-def.h | 1 + src/mono/mono/metadata/icall.c | 6 ++++ src/mono/mono/metadata/object-internals.h | 3 ++ src/mono/mono/mini/mini-runtime.c | 9 +++++ 10 files changed, 80 insertions(+), 1 deletion(-) diff --git a/src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs b/src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs index 7fc5dc96f17b0f..1b7134247e61c5 100644 --- a/src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs +++ b/src/coreclr/System.Private.CoreLib/src/System/RuntimeHandles.cs @@ -1292,6 +1292,16 @@ static RuntimeMethodHandleInternal GetStubIfNeededWorker(RuntimeMethodHandleInte [MethodImpl(MethodImplOptions.InternalCall)] internal static extern RuntimeMethodHandleInternal GetMethodFromCanonical(RuntimeMethodHandleInternal method, RuntimeType declaringType); + [LibraryImport(RuntimeHelpers.QCall, EntryPoint = "RuntimeMethodHandle_GetNativeCode")] + private static partial IntPtr GetNativeCode(RuntimeMethodHandleInternal method); + + internal static IntPtr GetNativeCodeInternal(IRuntimeMethodInfo method) + { + IntPtr value = GetNativeCode(method.Value); + GC.KeepAlive(method); + return value; + } + [MethodImpl(MethodImplOptions.InternalCall)] internal static extern bool IsGenericMethodDefinition(RuntimeMethodHandleInternal method); diff --git a/src/coreclr/vm/qcallentrypoints.cpp b/src/coreclr/vm/qcallentrypoints.cpp index f3cc5ef9ab1ebd..6f2dcf3af63bb7 100644 --- a/src/coreclr/vm/qcallentrypoints.cpp +++ b/src/coreclr/vm/qcallentrypoints.cpp @@ -176,6 +176,7 @@ static const Entry s_QCall[] = DllImportEntry(RuntimeMethodHandle_IsCAVisibleFromDecoratedType) DllImportEntry(RuntimeMethodHandle_Destroy) DllImportEntry(RuntimeMethodHandle_GetStubIfNeededSlow) + DllImportEntry(RuntimeMethodHandle_GetNativeCode) DllImportEntry(RuntimeMethodHandle_GetMethodBody) DllImportEntry(RuntimeModule_GetScopeName) DllImportEntry(RuntimeModule_GetFullyQualifiedName) diff --git a/src/coreclr/vm/runtimehandles.cpp b/src/coreclr/vm/runtimehandles.cpp index 1e1fb0c0222cae..a606d4df076ede 100644 --- a/src/coreclr/vm/runtimehandles.cpp +++ b/src/coreclr/vm/runtimehandles.cpp @@ -2005,6 +2005,39 @@ FCIMPL2(MethodDesc*, RuntimeMethodHandle::GetMethodFromCanonical, MethodDesc *pM } FCIMPLEND +extern "C" PCODE QCALLTYPE RuntimeMethodHandle_GetNativeCode(MethodDesc* pMethod) +{ + QCALL_CONTRACT; + + PCODE result = (PCODE)NULL; + + BEGIN_QCALL; + + _ASSERTE(pMethod != NULL); + + // Value-type state machines expose an unboxing-stub MethodDesc via reflection; the actual JITted/R2R MoveNext + // body lives on the wrapped (unboxed) MethodDesc. Unwrap first, then return the native code entry point of the + // real body (not the unboxing stub / precode) so the caller can use it as an IP that symbolicates like any + // other frame (perfmap / rundown / ProcessSymbol). GetNativeCodeAnyVersion keeps R2R/tiered/rejitted methods + // resolvable even if the default code slot is momentarily unset; GetInterpreterCodeFromEntryPointIfPresent then + // maps an interpreter entry (InterpreterPrecode stub) to the IR bytecode address that method events actually + // report, so the id symbolicates uniformly whether the method is JITted, R2R, or interpreted (0 if none). + if (pMethod->IsUnboxingStub()) + { + MethodDesc* pUnboxed = pMethod->GetExistingWrappedMethodDesc(); + if (pUnboxed != NULL) + { + pMethod = pUnboxed; + } + } + + result = GetInterpreterCodeFromEntryPointIfPresent(pMethod->GetNativeCodeAnyVersion()); + + END_QCALL; + + return result; +} + extern "C" void QCALLTYPE RuntimeMethodHandle_GetMethodBody(MethodDesc* pMethod, QCall::TypeHandle pDeclaringType, QCall::ObjectHandleOnStack result) { QCALL_CONTRACT; diff --git a/src/coreclr/vm/runtimehandles.h b/src/coreclr/vm/runtimehandles.h index 855e414a457a6d..1ac8932778ef8a 100644 --- a/src/coreclr/vm/runtimehandles.h +++ b/src/coreclr/vm/runtimehandles.h @@ -237,6 +237,7 @@ extern "C" void QCALLTYPE RuntimeMethodHandle_GetTypicalMethodDefinition(MethodD extern "C" void QCALLTYPE RuntimeMethodHandle_StripMethodInstantiation(MethodDesc * pMethod, QCall::ObjectHandleOnStack refMethod); extern "C" void QCALLTYPE RuntimeMethodHandle_Destroy(MethodDesc * pMethod); extern "C" MethodDesc* QCALLTYPE RuntimeMethodHandle_GetStubIfNeededSlow(MethodDesc* pMethod, QCall::TypeHandle declaringTypeHandle, QCall::ObjectHandleOnStack methodInstantiation); +extern "C" PCODE QCALLTYPE RuntimeMethodHandle_GetNativeCode(MethodDesc* pMethod); extern "C" void QCALLTYPE RuntimeMethodHandle_GetMethodBody(MethodDesc* pMethod, QCall::TypeHandle pDeclaringType, QCall::ObjectHandleOnStack result); class RuntimeFieldHandle diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncStateMachineDiagnostics.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncStateMachineDiagnostics.cs index 59f5e2c481c7d0..b6c161d36def0d 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncStateMachineDiagnostics.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncStateMachineDiagnostics.cs @@ -67,7 +67,14 @@ private static ulong ResolveMethodId() MethodInfo? methodInfo = typeof(TStateMachine).GetMethod("MoveNext", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (methodInfo is not null) { - return (ulong)methodInfo.MethodHandle.Value; +#if MONO + return (ulong)RuntimeMethodHandle.GetNativeCodeInternal(methodInfo.MethodHandle.Value); +#else + if (methodInfo is IRuntimeMethodInfo runtimeMethodInfo) + { + return (ulong)RuntimeMethodHandle.GetNativeCodeInternal(runtimeMethodInfo); + } +#endif } return 0; diff --git a/src/mono/System.Private.CoreLib/src/System/RuntimeMethodHandle.cs b/src/mono/System.Private.CoreLib/src/System/RuntimeMethodHandle.cs index ab265e5bd354eb..03b63e68758c94 100644 --- a/src/mono/System.Private.CoreLib/src/System/RuntimeMethodHandle.cs +++ b/src/mono/System.Private.CoreLib/src/System/RuntimeMethodHandle.cs @@ -35,6 +35,14 @@ public IntPtr GetFunctionPointer() return GetFunctionPointer(value); } + [MethodImpl(MethodImplOptions.InternalCall)] + private static extern IntPtr GetNativeCode(IntPtr m); + + internal static IntPtr GetNativeCodeInternal(IntPtr methodHandleValue) + { + return GetNativeCode(methodHandleValue); + } + public override bool Equals(object? obj) { if (obj == null || GetType() != obj.GetType()) diff --git a/src/mono/mono/metadata/icall-def.h b/src/mono/mono/metadata/icall-def.h index 0158888234e080..69dfe4f761eada 100644 --- a/src/mono/mono/metadata/icall-def.h +++ b/src/mono/mono/metadata/icall-def.h @@ -484,6 +484,7 @@ HANDLES_REUSE_WRAPPER(RFH_4, "SetValueInternal", ves_icall_RuntimeFieldInfo_SetV ICALL_TYPE(MHAN, "System.RuntimeMethodHandle", MHAN_1) HANDLES(MHAN_1, "GetFunctionPointer", ves_icall_RuntimeMethodHandle_GetFunctionPointer, gpointer, 1, (MonoMethod_ptr)) +HANDLES(MHAN_4, "GetNativeCode", ves_icall_RuntimeMethodHandle_GetNativeCode, gpointer, 1, (MonoMethod_ptr)) HANDLES(MAHN_3, "ReboxFromNullable", ves_icall_RuntimeMethodHandle_ReboxFromNullable, void, 2, (MonoObject, MonoObjectHandleOnStack)) HANDLES(MAHN_2, "ReboxToNullable", ves_icall_RuntimeMethodHandle_ReboxToNullable, void, 3, (MonoObject, MonoQCallTypeHandle, MonoObjectHandleOnStack)) diff --git a/src/mono/mono/metadata/icall.c b/src/mono/mono/metadata/icall.c index 5c8a35a367ca9c..6819acc56743fb 100644 --- a/src/mono/mono/metadata/icall.c +++ b/src/mono/mono/metadata/icall.c @@ -6250,6 +6250,12 @@ ves_icall_RuntimeMethodHandle_GetFunctionPointer (MonoMethod *method, MonoError return mono_method_get_unmanaged_wrapper_ftnptr_internal (method, FALSE, error); } +gpointer +ves_icall_RuntimeMethodHandle_GetNativeCode (MonoMethod *method, MonoError *error) +{ + return mono_get_runtime_callbacks ()->get_method_code_start (method); +} + void* mono_method_get_unmanaged_wrapper_ftnptr_internal (MonoMethod *method, gboolean only_unmanaged_callers_only, MonoError *error) { diff --git a/src/mono/mono/metadata/object-internals.h b/src/mono/mono/metadata/object-internals.h index a25b69923a8181..207f18cd5bc0b9 100644 --- a/src/mono/mono/metadata/object-internals.h +++ b/src/mono/mono/metadata/object-internals.h @@ -723,6 +723,9 @@ typedef struct { void (*get_exception_stats)(guint32 *exception_count); // Same as compile_method, but returns a MonoFtnDesc in llvmonly mode gpointer (*get_ftnptr)(MonoMethod *method, gboolean need_unbox, MonoError *error); + // Returns the start address of the method's already-present native code (JIT, AOT or interpreter), or NULL if + // the method has not been compiled/prepared. + gpointer (*get_method_code_start)(MonoMethod *method); void (*interp_jit_info_foreach)(InterpJitInfoFunc func, gpointer user_data); gboolean (*interp_sufficient_stack)(gsize size); void (*init_class) (MonoClass *klass); diff --git a/src/mono/mono/mini/mini-runtime.c b/src/mono/mono/mini/mini-runtime.c index 7cf63847ff61b9..4e652767d91a2e 100644 --- a/src/mono/mono/mini/mini-runtime.c +++ b/src/mono/mono/mini/mini-runtime.c @@ -3114,6 +3114,14 @@ mono_jit_search_all_backends_for_jit_info (MonoMethod *method, MonoJitInfo **out return code; } +static gpointer +get_method_code_start (MonoMethod *method) +{ + MonoJitInfo *ji = NULL; + mono_jit_search_all_backends_for_jit_info (method, &ji); + return ji ? mono_jit_info_get_code_start (ji) : NULL; +} + gpointer mono_jit_find_compiled_method_with_jit_info (MonoMethod *method, MonoJitInfo **ji) { @@ -4716,6 +4724,7 @@ mini_init (const char *filename) callbacks.get_weak_field_indexes = mono_aot_get_weak_field_indexes; #endif callbacks.find_jit_info_in_aot = mono_aot_find_jit_info; + callbacks.get_method_code_start = get_method_code_start; callbacks.metadata_update_published = mini_invalidate_transformed_interp_methods; callbacks.interp_jit_info_foreach = mini_interp_jit_info_foreach; callbacks.interp_sufficient_stack = mini_interp_sufficient_stack; From ae2b269a187d0d72e2f6180dd1814e922fb8abb8 Mon Sep 17 00:00:00 2001 From: lateralusX Date: Thu, 9 Jul 2026 19:09:31 +0200 Subject: [PATCH 2/7] Needed test adjustments + run async v1 tests on Mono. --- .../Runtime/CompilerServices/AsyncProfiler.cs | 2 +- .../AsyncProfilerTests.cs | 187 +++++++++++------- .../AsyncProfilerV1Tests.cs | 2 +- .../System.Threading.Tasks.Tests.csproj | 6 +- 4 files changed, 121 insertions(+), 76 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncProfiler.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncProfiler.cs index c646443c910719..bfe2002bee9a77 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncProfiler.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncProfiler.cs @@ -1230,7 +1230,7 @@ internal static partial class ContinuationWrapper #if !RUNTIME_ASYNC_SUPPORTED public static void InitInfo(ref Info info) { - info.ContinuationTable = 0; + info.ContinuationTable = ref Unsafe.NullRef(); info.ContinuationIndex = 0; } #endif diff --git a/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/System.Runtime.CompilerServices/AsyncProfilerTests.cs b/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/System.Runtime.CompilerServices/AsyncProfilerTests.cs index 1ebe969e4ca011..504b7b77c59188 100644 --- a/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/System.Runtime.CompilerServices/AsyncProfilerTests.cs +++ b/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/System.Runtime.CompilerServices/AsyncProfilerTests.cs @@ -112,14 +112,16 @@ public partial class AsyncProfilerTests // These tests use async Task methods with await instead of Task.Run blocking. public static bool IsRuntimeAsyncSupported => PlatformDetection.IsRuntimeAsyncSupported; - // StateMachine (StateMachineAsync_*) async-task instrumentation is opt-out on NativeAOT to avoid - // ~100KB of per-state-machine generic instantiation overhead. Tests that depend - // on StateMachine events must be gated on these properties so they are skipped on NAOT. + // StateMachine (StateMachineAsync_*) async-task instrumentation is the classic compiler-generated + // state machine (V1). It does not require runtime-async (V2), so it is supported on every runtime + // (CoreCLR and Mono) except NativeAOT, where it is opt-out to avoid ~100KB of per-state-machine + // generic instantiation overhead. Tests that depend on StateMachine events must be gated on these + // properties so they are skipped on NAOT. public static bool IsStateMachineAsyncSupported => - IsRuntimeAsyncSupported && !PlatformDetection.IsNativeAot; + !PlatformDetection.IsNativeAot; public static bool IsStateMachineAsyncAndThreadingSupported => - IsRuntimeAsyncAndThreadingSupported && !PlatformDetection.IsNativeAot; + IsStateMachineAsyncSupported && PlatformDetection.IsMultithreadingSupported; // Gate for tests that exercise a mixed V1 (StateMachine) + V2 (RuntimeAsync) chain: they need both // instrumentation paths, and V1 is disabled on NativeAOT, so require both conditions. @@ -229,109 +231,152 @@ s_getMethodFromNativeIPMethod is null ? typeof(StackFrame).GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic, null, new[] { typeof(IntPtr), typeof(bool) }, null) : null; - // The public 1-arg MethodBase.GetMethodFromHandle resolves the method but then deliberately - // throws ArgumentException when the declaring type is generic. The internal - // RuntimeType.GetMethodBase(RuntimeMethodHandle.GetMethodInfo()) it calls underneath has no - // such guard, so we invoke it directly to name StateMachine frames on generic declaring types. - private static readonly MethodInfo? s_runtimeMethodHandleGetMethodInfo = - typeof(RuntimeMethodHandle).GetMethod("GetMethodInfo", BindingFlags.Instance | BindingFlags.NonPublic); - - private static readonly MethodInfo? s_runtimeTypeGetMethodBase = - // typeof(object) is a RuntimeType instance; its runtime type is System.RuntimeType. - typeof(object).GetType().GetMethods(BindingFlags.Static | BindingFlags.NonPublic) - .FirstOrDefault(m => m.Name == "GetMethodBase" - && m.GetParameters() is { Length: 1 } p - && p[0].ParameterType.Name == "IRuntimeMethodInfo"); - private static string? GetMethodNameFromMethodId(AsyncCallstackType callstackType, ulong methodId) { if (methodId != 0) { - if (callstackType == AsyncCallstackType.Runtime) + if (s_getMethodFromNativeIPMethod is not null) { - if (s_getMethodFromNativeIPMethod is not null) + MethodBase? method = (MethodBase?)s_getMethodFromNativeIPMethod.Invoke(null, new object[] { (IntPtr)methodId }); + if (callstackType == AsyncCallstackType.Runtime) { - MethodBase? method = (MethodBase?)s_getMethodFromNativeIPMethod.Invoke(null, new object[] { (IntPtr)methodId }); return method?.Name; } - - if (s_stackFrameFromIPCtor is not null) + else { - StackFrame frame = (StackFrame)s_stackFrameFromIPCtor.Invoke(new object[] { (IntPtr)methodId, false })!; - DiagnosticMethodInfo? diagInfo = DiagnosticMethodInfo.Create(frame); - return diagInfo?.Name; + return ExtractStateMachineMethodName(method?.DeclaringType?.Name); } } - else if (callstackType == AsyncCallstackType.StateMachine) + + if (s_stackFrameFromIPCtor is not null) { - System.RuntimeMethodHandle handle = RuntimeMethodHandle.FromIntPtr((IntPtr)methodId); - MethodBase? method = null; - try + StackFrame frame = (StackFrame)s_stackFrameFromIPCtor.Invoke(new object[] { (IntPtr)methodId, false })!; + DiagnosticMethodInfo? diagInfo = DiagnosticMethodInfo.Create(frame); + if (callstackType == AsyncCallstackType.Runtime) { - method = MethodBase.GetMethodFromHandle(handle); + return diagInfo?.Name; } - catch (ArgumentException) + else { - // The 1-arg GetMethodFromHandle cannot resolve handles whose declaring - // type is generic (e.g. xUnit's TestClassRunner+<...>d__N); - // it requires the 2-arg overload with an explicit declaring type, which - // we cannot recover from a bare MethodId. Real ETW/EventPipe consumers - // get the declaring type from method-metadata rundown events. - // - // As a test-only fallback, resolve via the internal - // RuntimeType.GetMethodBase the public API uses before its generic guard, - // which does name methods on generic declaring types. This mirrors what a - // rundown-backed consumer would achieve. - method = ResolveCompilerMethodOnGenericType(handle); + return ExtractStateMachineMethodName(diagInfo?.DeclaringTypeName); } + } - if (method?.DeclaringType is Type declaringType) - { - string methodName = declaringType.Name; + // Mono fallback (no managed IP->method API): resolve via the reverse method-id map. + return ResolveStateMachineMethodNameFromId(methodId); + } - int start = methodName.IndexOf('<'); - int end = methodName.IndexOf('>'); + return null; + } - start++; - if (start > 0 && end > start) - { - methodName = methodName.Substring(start, end - start); - } + // Mono has no managed IP->method API, so the reflective resolvers above return null there and a + // frame's method id (a native code IP) can't be mapped back to a name. As a fallback we build a + // reverse map from method id -> async method name by scanning every compiler-generated async state + // machine reachable from the test class and computing the SAME id the runtime emits for a + // StateMachine frame, i.e. the native code of MoveNext (see AsyncStateMachineDiagnostics. + // ResolveMethodId). This covers frame name resolution and the console dump uniformly on Mono. + // + // The IntPtr overload of GetNativeCodeInternal only exists on Mono (CoreCLR's takes an + // IRuntimeMethodInfo), so this reflection yields null on CoreCLR/NativeAOT and the fallback is a + // no-op there, leaving the robust IP->name resolution untouched. + private static readonly MethodInfo? s_getNativeCodeInternalMethod = + typeof(RuntimeMethodHandle).GetMethod( + "GetNativeCodeInternal", + BindingFlags.Static | BindingFlags.NonPublic, + null, + new[] { typeof(IntPtr) }, + null); + + // Set of (resolved name, state machine MoveNext handle) for every async state machine reachable + // from the test class, built once. This includes not just async methods declared on the test class + // but also async lambdas and local functions, whose compiler-generated state machines are nested + // types (under the test class or its display classes) implementing IAsyncStateMachine. Any of these + // can appear as a continuation frame, so all must be covered. + private static readonly Lazy<(string Name, IntPtr MoveNextHandle)[]> s_stateMachineMoveNextMethods = + new(BuildStateMachineMoveNextMethods); + + // Resolved map: state machine frame method id (native code IP of MoveNext) -> async method name. + // Populated lazily because a MoveNext only has a native code IP once it has been JITted, which is + // guaranteed by the time we resolve frames (assertions run after the scenario executed). + private static readonly ConcurrentDictionary s_methodIdToName = new(); + + private static (string Name, IntPtr MoveNextHandle)[] BuildStateMachineMoveNextMethods() + { + var result = new List<(string, IntPtr)>(); + CollectStateMachineMoveNextMethods(typeof(AsyncProfilerTests), result); + return result.ToArray(); + } - return methodName; + // Recursively walks nested types looking for compiler-generated async state machines (types that + // implement IAsyncStateMachine) and records their MoveNext, named the same way IP->name resolution + // does on other runtimes (ExtractStateMachineMethodName over the state machine type name) so the + // fallback is indistinguishable from the primary resolver. + private static void CollectStateMachineMoveNextMethods(Type type, List<(string, IntPtr)> result) + { + foreach (Type nested in type.GetNestedTypes(BindingFlags.Public | BindingFlags.NonPublic)) + { + if (!nested.ContainsGenericParameters + && typeof(System.Runtime.CompilerServices.IAsyncStateMachine).IsAssignableFrom(nested)) + { + MethodInfo? moveNext = nested.GetMethod( + "MoveNext", + BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); + if (moveNext is not null) + { + result.Add((ExtractStateMachineMethodName(nested.Name) ?? nested.Name, moveNext.MethodHandle.Value)); } } - } - return null; + CollectStateMachineMoveNextMethods(nested, result); + } } - // Test-only fallback used when a compiler (StateMachine) MethodId handle cannot be resolved via the - // 1-arg MethodBase.GetMethodFromHandle because its declaring type is generic. Calls the - // internal RuntimeType.GetMethodBase(handle.GetMethodInfo()) the public API uses underneath, - // which has no generic-declaring-type guard, so it names methods on generic declaring types. - // Returns null if the internals are unavailable (e.g. NativeAOT) or resolution throws. - private static MethodBase? ResolveCompilerMethodOnGenericType(RuntimeMethodHandle handle) + // Mono fallback: resolve a StateMachine frame's method id to the async method name via the reverse + // map. On a miss we (re)scan, since more MoveNext methods may have been JITted since the last scan. + private static string? ResolveStateMachineMethodNameFromId(ulong methodId) { - if (s_runtimeMethodHandleGetMethodInfo is null || s_runtimeTypeGetMethodBase is null) + if (s_getNativeCodeInternalMethod is null || methodId == 0) { return null; } - try + if (s_methodIdToName.TryGetValue(methodId, out string? name)) { - object? methodInfo = s_runtimeMethodHandleGetMethodInfo.Invoke(handle, null); - if (methodInfo is null) + return name; + } + + foreach ((string methodName, IntPtr moveNextHandle) in s_stateMachineMoveNextMethods.Value) + { + object? nativeCode = s_getNativeCodeInternalMethod.Invoke(null, new object[] { moveNextHandle }); + if (nativeCode is IntPtr ip && ip != IntPtr.Zero) { - return null; + s_methodIdToName.TryAdd((ulong)ip, methodName); } - - return (MethodBase?)s_runtimeTypeGetMethodBase.Invoke(null, new object[] { methodInfo }); } - catch (Exception) + + return s_methodIdToName.TryGetValue(methodId, out name) ? name : null; + } + + // The compiler generates a state machine type named "d__N" (possibly nested + // and namespace-qualified). Extract the original async method name from between the angle + // brackets. Returns the input unchanged when it contains no angle brackets, or null when null. + private static string? ExtractStateMachineMethodName(string? declaringTypeName) + { + if (declaringTypeName is null) { return null; } + + int start = declaringTypeName.IndexOf('<'); + int end = declaringTypeName.IndexOf('>'); + + start++; + if (start > 0 && end > start) + { + return declaringTypeName.Substring(start, end - start); + } + + return declaringTypeName; } private static TestEventListener CreateListener(EventKeywords keywords) diff --git a/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/System.Runtime.CompilerServices/AsyncProfilerV1Tests.cs b/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/System.Runtime.CompilerServices/AsyncProfilerV1Tests.cs index c3f81419e32224..f970f8b20b7619 100644 --- a/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/System.Runtime.CompilerServices/AsyncProfilerV1Tests.cs +++ b/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/System.Runtime.CompilerServices/AsyncProfilerV1Tests.cs @@ -1270,7 +1270,7 @@ private static async Task StateMachineAsync_KeywordGatekeeping_Marker() await StateMachineAsync_InnerThrows(); } catch (InvalidOperationException) { } - await RuntimeAsync_SingleYield(); + await StateMachineAsync_SingleYield(); await Task.Delay(50); } diff --git a/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/System.Threading.Tasks.Tests.csproj b/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/System.Threading.Tasks.Tests.csproj index d1680d8f7b2a61..57f8c0c09a760f 100644 --- a/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/System.Threading.Tasks.Tests.csproj +++ b/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/System.Threading.Tasks.Tests.csproj @@ -60,14 +60,14 @@ - - + + - + From 210c5dafb339344272413ac2f5e78ffa9714bcc6 Mon Sep 17 00:00:00 2001 From: Johan Lorensson Date: Thu, 9 Jul 2026 19:22:43 +0200 Subject: [PATCH 3/7] Review feedback. Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- src/mono/mono/metadata/icall.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/mono/mono/metadata/icall.c b/src/mono/mono/metadata/icall.c index 6819acc56743fb..93a5831d893864 100644 --- a/src/mono/mono/metadata/icall.c +++ b/src/mono/mono/metadata/icall.c @@ -6253,7 +6253,8 @@ ves_icall_RuntimeMethodHandle_GetFunctionPointer (MonoMethod *method, MonoError gpointer ves_icall_RuntimeMethodHandle_GetNativeCode (MonoMethod *method, MonoError *error) { - return mono_get_runtime_callbacks ()->get_method_code_start (method); + MonoRuntimeCallbacks *callbacks = mono_get_runtime_callbacks (); + return callbacks->get_method_code_start ? callbacks->get_method_code_start (method) : NULL; } void* From cd8e23000dee8796e77f21d7c9861703ec2e4ccb Mon Sep 17 00:00:00 2001 From: lateralusX Date: Thu, 9 Jul 2026 19:40:31 +0200 Subject: [PATCH 4/7] Review feedback. --- src/coreclr/vm/runtimehandles.cpp | 11 +++++------ src/mono/mono/mini/mini-runtime.c | 4 ++-- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/src/coreclr/vm/runtimehandles.cpp b/src/coreclr/vm/runtimehandles.cpp index a606d4df076ede..e6e6232411f39c 100644 --- a/src/coreclr/vm/runtimehandles.cpp +++ b/src/coreclr/vm/runtimehandles.cpp @@ -2024,14 +2024,13 @@ extern "C" PCODE QCALLTYPE RuntimeMethodHandle_GetNativeCode(MethodDesc* pMethod // report, so the id symbolicates uniformly whether the method is JITted, R2R, or interpreted (0 if none). if (pMethod->IsUnboxingStub()) { - MethodDesc* pUnboxed = pMethod->GetExistingWrappedMethodDesc(); - if (pUnboxed != NULL) - { - pMethod = pUnboxed; - } + pMethod = pMethod->GetWrappedMethodDesc(); } - result = GetInterpreterCodeFromEntryPointIfPresent(pMethod->GetNativeCodeAnyVersion()); + if (pMethod != NULL) + { + result = GetInterpreterCodeFromEntryPointIfPresent(pMethod->GetNativeCodeAnyVersion()); + } END_QCALL; diff --git a/src/mono/mono/mini/mini-runtime.c b/src/mono/mono/mini/mini-runtime.c index 4e652767d91a2e..46e28a680b3cc7 100644 --- a/src/mono/mono/mini/mini-runtime.c +++ b/src/mono/mono/mini/mini-runtime.c @@ -3118,8 +3118,8 @@ static gpointer get_method_code_start (MonoMethod *method) { MonoJitInfo *ji = NULL; - mono_jit_search_all_backends_for_jit_info (method, &ji); - return ji ? mono_jit_info_get_code_start (ji) : NULL; + gpointer code = mono_jit_search_all_backends_for_jit_info (method, &ji); + return ji ? mono_jit_info_get_code_start (ji) : code; } gpointer From 69bbe8ea12c83eccf2638c4f8b87e4c524077d06 Mon Sep 17 00:00:00 2001 From: lateralusX Date: Fri, 10 Jul 2026 13:15:54 +0200 Subject: [PATCH 5/7] Cover all wrapper types + nested wrapper scenarios + tests. --- src/coreclr/vm/runtimehandles.cpp | 21 +++--- .../AsyncProfilerV1Tests.cs | 72 +++++++++++++++++++ 2 files changed, 80 insertions(+), 13 deletions(-) diff --git a/src/coreclr/vm/runtimehandles.cpp b/src/coreclr/vm/runtimehandles.cpp index e6e6232411f39c..30f7bc42853377 100644 --- a/src/coreclr/vm/runtimehandles.cpp +++ b/src/coreclr/vm/runtimehandles.cpp @@ -2015,22 +2015,17 @@ extern "C" PCODE QCALLTYPE RuntimeMethodHandle_GetNativeCode(MethodDesc* pMethod _ASSERTE(pMethod != NULL); - // Value-type state machines expose an unboxing-stub MethodDesc via reflection; the actual JITted/R2R MoveNext - // body lives on the wrapped (unboxed) MethodDesc. Unwrap first, then return the native code entry point of the - // real body (not the unboxing stub / precode) so the caller can use it as an IP that symbolicates like any - // other frame (perfmap / rundown / ProcessSymbol). GetNativeCodeAnyVersion keeps R2R/tiered/rejitted methods - // resolvable even if the default code slot is momentarily unset; GetInterpreterCodeFromEntryPointIfPresent then - // maps an interpreter entry (InterpreterPrecode stub) to the IR bytecode address that method events actually - // report, so the id symbolicates uniformly whether the method is JITted, R2R, or interpreted (0 if none). - if (pMethod->IsUnboxingStub()) + while (pMethod->IsWrapperStub()) { - pMethod = pMethod->GetWrappedMethodDesc(); + MethodDesc* pWrapped = pMethod->GetWrappedMethodDesc(); + if (pWrapped == NULL || pWrapped == pMethod) + { + break; + } + pMethod = pWrapped; } - if (pMethod != NULL) - { - result = GetInterpreterCodeFromEntryPointIfPresent(pMethod->GetNativeCodeAnyVersion()); - } + result = GetInterpreterCodeFromEntryPointIfPresent(pMethod->GetNativeCodeAnyVersion()); END_QCALL; diff --git a/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/System.Runtime.CompilerServices/AsyncProfilerV1Tests.cs b/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/System.Runtime.CompilerServices/AsyncProfilerV1Tests.cs index f970f8b20b7619..e2f54bb016c362 100644 --- a/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/System.Runtime.CompilerServices/AsyncProfilerV1Tests.cs +++ b/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/System.Runtime.CompilerServices/AsyncProfilerV1Tests.cs @@ -1709,6 +1709,78 @@ public void StateMachineAsync_ConfigureAwaitFalse() AssertExactlyOneCreateAndComplete(stream, markerCallstacks[0].DispatcherId, nameof(StateMachineAsync_ConfigureAwaitFalse_Marker)); } + // Generic async chain. Each method is generic over T and uses its parameter after the await, so the + // compiler emits a generic state machine (d__N`1). Instantiated with a reference type the JIT + // reaches the async body through the shared (__Canon) generic code, whose per-instantiation MethodDesc + // is an instantiating (wrapper) stub. The V1 methodId is the native code start of MoveNext, so + // RuntimeMethodHandle_GetNativeCode must peel wrapper stubs to the shared body's code for the id to map + // back to a managed method; otherwise a frame would carry a stub thunk address that resolves to null. + [RuntimeAsyncMethodGeneration(false)] + [MethodImpl(MethodImplOptions.NoInlining)] + private static async Task StateMachineAsync_GenericChain_Leaf_Marker(T value) + { + await Task.Delay(100).ConfigureAwait(false); + return value; + } + + [RuntimeAsyncMethodGeneration(false)] + [MethodImpl(MethodImplOptions.NoInlining)] + private static async Task StateMachineAsync_GenericChain_FramesResolveToSharedBody_Mid_Marker(T value) + { + T result = await StateMachineAsync_GenericChain_Leaf_Marker(value).ConfigureAwait(false); + return result; + } + + [RuntimeAsyncMethodGeneration(false)] + [MethodImpl(MethodImplOptions.NoInlining)] + private static async Task StateMachineAsync_GenericChain_FramesResolveToSharedBody_Marker(T value) + { + T result = await StateMachineAsync_GenericChain_FramesResolveToSharedBody_Mid_Marker(value).ConfigureAwait(false); + return result; + } + + // CoreCLR-only: the primary IP->name resolver (StackFrame.GetMethodFromNativeIP) exists only on CoreCLR, + // and the Mono reverse-map fallback (CollectStateMachineMoveNextMethods) deliberately skips generic state + // machines (ContainsGenericParameters), so a generic frame can't be named on Mono without proper rundown + // or JIT-map data. + // + // A reference type (string) reaches the shared __Canon body through an instantiating (wrapper) stub that + // RuntimeMethodHandle_GetNativeCode must peel; a value type (int) is fully specialized into its own code + // (no wrapper stub) and resolves directly. Both must symbolize to their managed names. + [ConditionalTheory(typeof(AsyncProfilerTests), nameof(IsStateMachineAsyncAndThreadingSupported), nameof(IsNotMonoRuntime))] + [InlineData(typeof(string))] + [InlineData(typeof(int))] + public void StateMachineAsync_GenericChain_FramesResolveToSharedBody(Type argType) + { + var events = CollectEvents(ResumeStateMachineAsyncCallstackKeyword | StateMachineAsyncCoreKeywords, () => + { + if (argType == typeof(int)) + { + RunScenarioAndFlush(() => StateMachineAsync_GenericChain_FramesResolveToSharedBody_Marker(42)); + } + else + { + RunScenarioAndFlush(() => StateMachineAsync_GenericChain_FramesResolveToSharedBody_Marker("value")); + } + }); + + // DumpAllEvents(events); + + var stream = ParseAllEvents(events); + + var markerCallstacks = stream.CallstacksWithMarker(AsyncEventID.ResumeStateMachineAsyncCallstack, nameof(StateMachineAsync_GenericChain_FramesResolveToSharedBody_Marker)); + AssertNotEmpty(stream, markerCallstacks); + + var frameNames = markerCallstacks[0].Frames + .Select(f => GetMethodNameFromMethodId(markerCallstacks[0].CallstackType, f.MethodId)) + .Where(n => n is not null) + .ToList(); + + // Every generic-chain frame must resolve to its managed name. + AssertContains(stream, nameof(StateMachineAsync_GenericChain_Leaf_Marker), frameNames); + AssertContains(stream, nameof(StateMachineAsync_GenericChain_FramesResolveToSharedBody_Mid_Marker), frameNames); + } + [RuntimeAsyncMethodGeneration(false)] [MethodImpl(MethodImplOptions.NoInlining)] private static async Task StateMachineAsync_FaultedTask_Inner_Marker() From c7494f14744828249d35e35ff3d463e5f9be8780 Mon Sep 17 00:00:00 2001 From: lateralusX Date: Fri, 10 Jul 2026 15:14:28 +0200 Subject: [PATCH 6/7] Fix Mono interpreter test failure. --- .../AsyncProfilerTests.cs | 73 ++++++++++++++++--- .../AsyncProfilerV1Tests.cs | 29 +++++++- 2 files changed, 90 insertions(+), 12 deletions(-) diff --git a/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/System.Runtime.CompilerServices/AsyncProfilerTests.cs b/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/System.Runtime.CompilerServices/AsyncProfilerTests.cs index 504b7b77c59188..a31e10e65eb576 100644 --- a/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/System.Runtime.CompilerServices/AsyncProfilerTests.cs +++ b/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/System.Runtime.CompilerServices/AsyncProfilerTests.cs @@ -128,6 +128,10 @@ public partial class AsyncProfilerTests public static bool IsStateMachineAsyncAndRuntimeAsyncAndThreadingSupported => IsStateMachineAsyncAndThreadingSupported && IsRuntimeAsyncAndThreadingSupported; + // Alias so tests that additionally require CoreCLR can list the exclusion as an extra ConditionalFact + // condition alongside the shared gates. + public static bool IsNotMonoRuntime => PlatformDetection.IsNotMonoRuntime; + private const string AsyncProfilerEventSourceName = "System.Runtime.CompilerServices.AsyncProfilerEventSource"; private const string WrapperNameTemplate = "Continuation_Wrapper_{0}"; private static readonly string WrapperNamePrefix = WrapperNameTemplate.Substring(0, WrapperNameTemplate.IndexOf("{0}", StringComparison.Ordinal)); @@ -296,8 +300,8 @@ s_getMethodFromNativeIPMethod is null new(BuildStateMachineMoveNextMethods); // Resolved map: state machine frame method id (native code IP of MoveNext) -> async method name. - // Populated lazily because a MoveNext only has a native code IP once it has been JITted, which is - // guaranteed by the time we resolve frames (assertions run after the scenario executed). + // Filled lazily on the first resolve miss, and eagerly by tests (SnapshotStateMachineMethodIdFor) that + // need to capture a method's tier-0 id before re-tiering; additive and keyed by address. private static readonly ConcurrentDictionary s_methodIdToName = new(); private static (string Name, IntPtr MoveNextHandle)[] BuildStateMachineMoveNextMethods() @@ -331,8 +335,62 @@ private static void CollectStateMachineMoveNextMethods(Type type, List<(string, } } + // Records the CURRENT native code start of every known state machine MoveNext into the id->name map. + // Used by the resolve path, which only has a raw frame address and so must consider every method. + // A MoveNext has no native code start until it has actually run, and a resume frame's method id is the + // code start of whatever version was current when the runtime first froze that id. The map is additive + // and keyed by address. No-op except on Mono. + private static void SnapshotStateMachineMethodIds() + { + if (s_getNativeCodeInternalMethod is null) + { + return; + } + + foreach ((string methodName, IntPtr moveNextHandle) in s_stateMachineMoveNextMethods.Value) + { + object? nativeCode = s_getNativeCodeInternalMethod.Invoke(null, new object[] { moveNextHandle }); + if (nativeCode is IntPtr ip && ip != IntPtr.Zero) + { + s_methodIdToName.TryAdd((ulong)ip, methodName); + } + } + } + + // Records the CURRENT native code start of a single async method's compiler-generated state machine + // MoveNext (found via its [AsyncStateMachine] attribute) into the id->name map, keyed to the async + // method's name so it matches normal resolution. On Mono the interpreter re-tiers a method after + // enough calls, replacing its code start, while the resume frames keep the initial (tier-0) id frozen + // at first run; a test that calls a method enough to trigger re-tiering can snapshot its id up front, + // while the method is still at its tier-0 version, so the frozen id stays resolvable afterwards. + // No-op except on Mono. + private static void SnapshotStateMachineMethodIdFor(MethodInfo asyncMethod) + { + if (s_getNativeCodeInternalMethod is null) + { + return; + } + + Type? stateMachineType = asyncMethod + .GetCustomAttribute()?.StateMachineType; + MethodInfo? moveNext = stateMachineType?.GetMethod( + "MoveNext", + BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); + if (moveNext is null) + { + return; + } + + object? nativeCode = s_getNativeCodeInternalMethod.Invoke(null, new object[] { moveNext.MethodHandle.Value }); + if (nativeCode is IntPtr ip && ip != IntPtr.Zero) + { + s_methodIdToName.TryAdd((ulong)ip, asyncMethod.Name); + } + } + // Mono fallback: resolve a StateMachine frame's method id to the async method name via the reverse - // map. On a miss we (re)scan, since more MoveNext methods may have been JITted since the last scan. + // map. On a miss we snapshot the current native code starts and retry, filling the map lazily as + // methods run (a MoveNext has no code start until it has first run). No-op except on Mono. private static string? ResolveStateMachineMethodNameFromId(ulong methodId) { if (s_getNativeCodeInternalMethod is null || methodId == 0) @@ -345,14 +403,7 @@ private static void CollectStateMachineMoveNextMethods(Type type, List<(string, return name; } - foreach ((string methodName, IntPtr moveNextHandle) in s_stateMachineMoveNextMethods.Value) - { - object? nativeCode = s_getNativeCodeInternalMethod.Invoke(null, new object[] { moveNextHandle }); - if (nativeCode is IntPtr ip && ip != IntPtr.Zero) - { - s_methodIdToName.TryAdd((ulong)ip, methodName); - } - } + SnapshotStateMachineMethodIds(); return s_methodIdToName.TryGetValue(methodId, out name) ? name : null; } diff --git a/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/System.Runtime.CompilerServices/AsyncProfilerV1Tests.cs b/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/System.Runtime.CompilerServices/AsyncProfilerV1Tests.cs index e2f54bb016c362..35b9d5d74de1c6 100644 --- a/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/System.Runtime.CompilerServices/AsyncProfilerV1Tests.cs +++ b/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/System.Runtime.CompilerServices/AsyncProfilerV1Tests.cs @@ -1508,6 +1508,15 @@ public void StateMachineAsync_CallstackOverflow_PreservesFullDepth() const int MaxFrames = byte.MaxValue; const int Iterations = 128; + // Warm up the recursive method so its state machine id is frozen at its tier-0 version, then + // snapshot that id before tracing. (Snapshot is a no-op on non-Mono, where ids resolve + // reflectively; the warmup itself runs on all runtimes.) + var warmupGate = new TaskCompletionSource(); + Task warmup = StateMachineAsync_RecursiveChainGated(2, warmupGate.Task); + warmupGate.SetResult(); + warmup.GetAwaiter().GetResult(); + SnapshotStateMachineMethodIdFor(typeof(AsyncProfilerTests).GetMethod(nameof(StateMachineAsync_RecursiveChainGated), BindingFlags.NonPublic | BindingFlags.Static)!); + var events = CollectEvents(ResumeStateMachineAsyncCallstackKeyword, () => { RunScenarioAndFlush(async () => @@ -1545,11 +1554,23 @@ public void StateMachineAsync_CallstackOverflow_PreservesFullDepth() } } + [RuntimeAsyncMethodGeneration(false)] + [MethodImpl(MethodImplOptions.NoInlining)] + private static async Task StateMachineAsync_CallstackStressWithVaryingDepths_Recurse(int depth) + { + if (depth <= 1) + { + await Task.Delay(100); + return; + } + await StateMachineAsync_CallstackStressWithVaryingDepths_Recurse(depth - 1); + } + [RuntimeAsyncMethodGeneration(false)] [MethodImpl(MethodImplOptions.NoInlining)] private static async Task StateMachineAsync_CallstackStressWithVaryingDepths_Marker(int depth) { - await StateMachineAsync_RecursiveChain(depth); + await StateMachineAsync_CallstackStressWithVaryingDepths_Recurse(depth); } [ConditionalFact(typeof(AsyncProfilerTests), nameof(IsStateMachineAsyncAndThreadingSupported))] @@ -1561,6 +1582,12 @@ public void StateMachineAsync_CallstackStressWithVaryingDepths() for (int i = 0; i < iterations; i++) depths[i] = rng.Next(1, 60); + // Warm up the recursive method so its state machine id is frozen at its tier-0 version, then + // snapshot that id before tracing. (Snapshot is a no-op on non-Mono, where ids resolve + // reflectively; the warmup itself runs on all runtimes.) + StateMachineAsync_CallstackStressWithVaryingDepths_Recurse(2).GetAwaiter().GetResult(); + SnapshotStateMachineMethodIdFor(typeof(AsyncProfilerTests).GetMethod(nameof(StateMachineAsync_CallstackStressWithVaryingDepths_Recurse), BindingFlags.NonPublic | BindingFlags.Static)!); + var events = CollectEvents(ResumeStateMachineAsyncCallstackKeyword | StateMachineAsyncCoreKeywords, () => { RunScenarioAndFlush(async () => From 0df4a60c9dd39989993f7a2da469c68218f22ebf Mon Sep 17 00:00:00 2001 From: lateralusX Date: Fri, 10 Jul 2026 16:22:40 +0200 Subject: [PATCH 7/7] Review feedback. --- .../Runtime/CompilerServices/AsyncStateMachineDiagnostics.cs | 4 ++-- .../System.Runtime.CompilerServices/AsyncProfilerTests.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncStateMachineDiagnostics.cs b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncStateMachineDiagnostics.cs index b6c161d36def0d..08c454c9d81977 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncStateMachineDiagnostics.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/AsyncStateMachineDiagnostics.cs @@ -68,11 +68,11 @@ private static ulong ResolveMethodId() if (methodInfo is not null) { #if MONO - return (ulong)RuntimeMethodHandle.GetNativeCodeInternal(methodInfo.MethodHandle.Value); + return (ulong)(nuint)RuntimeMethodHandle.GetNativeCodeInternal(methodInfo.MethodHandle.Value); #else if (methodInfo is IRuntimeMethodInfo runtimeMethodInfo) { - return (ulong)RuntimeMethodHandle.GetNativeCodeInternal(runtimeMethodInfo); + return (ulong)(nuint)RuntimeMethodHandle.GetNativeCodeInternal(runtimeMethodInfo); } #endif } diff --git a/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/System.Runtime.CompilerServices/AsyncProfilerTests.cs b/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/System.Runtime.CompilerServices/AsyncProfilerTests.cs index a31e10e65eb576..5cb822508f39f0 100644 --- a/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/System.Runtime.CompilerServices/AsyncProfilerTests.cs +++ b/src/libraries/System.Runtime/tests/System.Threading.Tasks.Tests/System.Runtime.CompilerServices/AsyncProfilerTests.cs @@ -352,7 +352,7 @@ private static void SnapshotStateMachineMethodIds() object? nativeCode = s_getNativeCodeInternalMethod.Invoke(null, new object[] { moveNextHandle }); if (nativeCode is IntPtr ip && ip != IntPtr.Zero) { - s_methodIdToName.TryAdd((ulong)ip, methodName); + s_methodIdToName.TryAdd((ulong)(nuint)ip, methodName); } } } @@ -384,7 +384,7 @@ private static void SnapshotStateMachineMethodIdFor(MethodInfo asyncMethod) object? nativeCode = s_getNativeCodeInternalMethod.Invoke(null, new object[] { moveNext.MethodHandle.Value }); if (nativeCode is IntPtr ip && ip != IntPtr.Zero) { - s_methodIdToName.TryAdd((ulong)ip, asyncMethod.Name); + s_methodIdToName.TryAdd((ulong)(nuint)ip, asyncMethod.Name); } }