From 0a14c159b1954b67ab8adbff63d4dcedd6bba916 Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Mon, 29 Jun 2026 22:16:36 +0200 Subject: [PATCH 1/3] Fix async resumption stub with byref parameters It is not valid IL to initialize a byref local with `initobj`. Just load the value of these directly. We had a test for byref parameters, but it did not suspend and did not result in creation of a resumption stub. The test case requires suspension + NoInlining to guarantee a suspension point is created. --- src/coreclr/vm/jitinterface.cpp | 16 +++++++++---- src/tests/async/byref-param/byref-param.cs | 26 +++++++++++++++++++++- 2 files changed, 37 insertions(+), 5 deletions(-) diff --git a/src/coreclr/vm/jitinterface.cpp b/src/coreclr/vm/jitinterface.cpp index 485a0c336156d8..51a01af4426039 100644 --- a/src/coreclr/vm/jitinterface.cpp +++ b/src/coreclr/vm/jitinterface.cpp @@ -15092,10 +15092,18 @@ CORINFO_METHOD_HANDLE CEEJitInfo::getAsyncResumptionStub(void** entryPoint) while ((ty = msig.NextArg()) != ELEMENT_TYPE_END) { TypeHandle tyHnd = msig.GetLastTypeHandleThrowing(); - DWORD loc = pCode->NewLocal(LocalDesc(tyHnd)); - pCode->EmitLDLOCA(loc); - pCode->EmitINITOBJ(pCode->GetToken(tyHnd)); - pCode->EmitLDLOC(loc); + if (tyHnd.GetInternalCorElementType() == ELEMENT_TYPE_BYREF) + { + pCode->EmitLDC(0); + pCode->EmitCONV_U(); + } + else + { + DWORD loc = pCode->NewLocal(LocalDesc(tyHnd)); + pCode->EmitLDLOCA(loc); + pCode->EmitINITOBJ(pCode->GetToken(tyHnd)); + pCode->EmitLDLOC(loc); + } numArgs++; } diff --git a/src/tests/async/byref-param/byref-param.cs b/src/tests/async/byref-param/byref-param.cs index 6676f2051dbfbe..5bd068b32132a4 100644 --- a/src/tests/async/byref-param/byref-param.cs +++ b/src/tests/async/byref-param/byref-param.cs @@ -1,13 +1,14 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Runtime.CompilerServices; using System.Threading.Tasks; using Xunit; public class Async2ByrefParam { [Fact] - public static int TestEntryPoint() + public static int TestByrefParam() { return Test().GetAwaiter().GetResult(); } @@ -22,4 +23,27 @@ private static Task HasByrefParam(out int foo) foo = 47; return Task.FromResult(53); } + + [Fact] + public static int TestByrefParamWithSuspension() + { + return TestWithSuspension().GetAwaiter().GetResult(); + } + + private static async Task TestWithSuspension() + { + await Verify(new string('a', 100), out int val); + return val; + } + + // NoInlining ensures a suspension point in Verify + // (otherwise this would just tail-await) + [MethodImpl(MethodImplOptions.NoInlining)] + private static Task Verify(string source, out int length) + { + length = source.Length; + return DoAsync(); + } + + static async Task DoAsync() => await Task.Yield(); } From 031d943b4a565591b576d415893ca306b502b7e9 Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Mon, 29 Jun 2026 22:19:52 +0200 Subject: [PATCH 2/3] Managed type system fix --- .../TypeSystem/IL/Stubs/AsyncResumptionStub.cs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncResumptionStub.cs b/src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncResumptionStub.cs index c002140abff23b..102b21fbf02af6 100644 --- a/src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncResumptionStub.cs +++ b/src/coreclr/tools/Common/TypeSystem/IL/Stubs/AsyncResumptionStub.cs @@ -81,10 +81,18 @@ public override MethodIL EmitIL() foreach (var param in _targetMethod.Signature) { - var local = ilEmitter.NewLocal(param); - ilStream.EmitLdLoca(local); - ilStream.Emit(ILOpcode.initobj, ilEmitter.NewToken(param)); - ilStream.EmitLdLoc(local); + if (param.IsByRef) + { + ilStream.EmitLdc(0); + ilStream.Emit(ILOpcode.conv_u); + } + else + { + var local = ilEmitter.NewLocal(param); + ilStream.EmitLdLoca(local); + ilStream.Emit(ILOpcode.initobj, ilEmitter.NewToken(param)); + ilStream.EmitLdLoc(local); + } } ilStream.Emit(ILOpcode.call, ilEmitter.NewToken(_targetMethod)); From cc4d85cf15ce9725c847205cfc717e21e1b7ffbb Mon Sep 17 00:00:00 2001 From: Jakob Botsch Nielsen Date: Mon, 29 Jun 2026 22:26:08 +0200 Subject: [PATCH 3/3] Feedback --- src/coreclr/vm/jitinterface.cpp | 2 +- src/tests/async/byref-param/byref-param.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/coreclr/vm/jitinterface.cpp b/src/coreclr/vm/jitinterface.cpp index 51a01af4426039..a2d1867b160753 100644 --- a/src/coreclr/vm/jitinterface.cpp +++ b/src/coreclr/vm/jitinterface.cpp @@ -15092,7 +15092,7 @@ CORINFO_METHOD_HANDLE CEEJitInfo::getAsyncResumptionStub(void** entryPoint) while ((ty = msig.NextArg()) != ELEMENT_TYPE_END) { TypeHandle tyHnd = msig.GetLastTypeHandleThrowing(); - if (tyHnd.GetInternalCorElementType() == ELEMENT_TYPE_BYREF) + if (tyHnd.IsByRef()) { pCode->EmitLDC(0); pCode->EmitCONV_U(); diff --git a/src/tests/async/byref-param/byref-param.cs b/src/tests/async/byref-param/byref-param.cs index 5bd068b32132a4..0a005d1b4f6913 100644 --- a/src/tests/async/byref-param/byref-param.cs +++ b/src/tests/async/byref-param/byref-param.cs @@ -45,5 +45,5 @@ private static Task Verify(string source, out int length) return DoAsync(); } - static async Task DoAsync() => await Task.Yield(); + private static async Task DoAsync() => await Task.Yield(); }