Skip to content

Commit 5d558fc

Browse files
authored
Ensure that top-level statements entry point emits MethodImplAttributes.Async (#80314)
This was missing for TLS symbols. Fixes #80313.
1 parent 5afd849 commit 5d558fc

File tree

6 files changed

+146
-27
lines changed

6 files changed

+146
-27
lines changed

src/Compilers/CSharp/Portable/Symbols/Source/SourceMethodSymbolWithAttributes.cs

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1756,18 +1756,22 @@ internal override System.Reflection.MethodImplAttributes ImplementationAttribute
17561756
result |= (System.Reflection.MethodImplAttributes.Runtime | System.Reflection.MethodImplAttributes.InternalCall);
17571757
}
17581758

1759-
if (this.IsAsync && this.DeclaringCompilation.IsRuntimeAsyncEnabledIn(this))
1760-
{
1761-
// https://github.com/dotnet/roslyn/issues/79792: Use real value from MethodImplAttributes when available
1762-
// When a method is emitted using runtime async, we add MethodImplAttributes.Async to indicate to the
1763-
// runtime to generate the state machine
1764-
result |= (System.Reflection.MethodImplAttributes)0x2000;
1765-
}
1759+
AddAsyncImplAttributeIfNeeded(ref result);
17661760

17671761
return result;
17681762
}
17691763
}
17701764

1765+
protected void AddAsyncImplAttributeIfNeeded(ref System.Reflection.MethodImplAttributes result)
1766+
{
1767+
if (this.IsAsync && this.DeclaringCompilation.IsRuntimeAsyncEnabledIn(this))
1768+
{
1769+
// When a method is emitted using runtime async, we add MethodImplAttributes.Async to indicate to the
1770+
// runtime to generate the state machine
1771+
result |= System.Reflection.MethodImplAttributes.Async;
1772+
}
1773+
}
1774+
17711775
internal override int TryGetOverloadResolutionPriority()
17721776
=> GetEarlyDecodedWellKnownAttributeData()?.OverloadResolutionPriority ?? 0;
17731777
}

src/Compilers/CSharp/Portable/Symbols/Synthesized/SynthesizedSimpleProgramEntryPointSymbol.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,12 @@ public override string Name
125125

126126
internal override System.Reflection.MethodImplAttributes ImplementationAttributes
127127
{
128-
get { return default(System.Reflection.MethodImplAttributes); }
128+
get
129+
{
130+
var attributes = default(System.Reflection.MethodImplAttributes);
131+
AddAsyncImplAttributeIfNeeded(ref attributes);
132+
return attributes;
133+
}
129134
}
130135

131136
public override ImmutableArray<TypeParameterSymbol> TypeParameters

src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAsyncTests.cs

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,6 @@ namespace Microsoft.CodeAnalysis.CSharp.UnitTests.CodeGen
2323
[CompilerTrait(CompilerFeature.Async)]
2424
public class CodeGenAsyncTests : EmitMetadataTestBase
2525
{
26-
// https://github.com/dotnet/roslyn/issues/79792: Use the real value when possible
27-
private const MethodImplAttributes MethodImplOptionsAsync = (MethodImplAttributes)0x2000;
28-
2926
internal static string ExpectedOutput(string output, bool isRuntimeAsync = false)
3027
{
3128
return ExecutionConditionUtil.IsMonoOrCoreClr
@@ -332,7 +329,7 @@ void verify(ModuleSymbol module)
332329
{
333330
var test = module.ContainingAssembly.GetTypeByMetadataName("Test");
334331
var f = test.GetMethod("F");
335-
Assert.Equal(MethodImplOptionsAsync, f.ImplementationAttributes);
332+
Assert.Equal(MethodImplAttributes.Async, f.ImplementationAttributes);
336333
AssertEx.Equal(["<>c"], test.GetTypeMembers().SelectAsArray(t => t.Name));
337334
}
338335
}
@@ -396,7 +393,7 @@ void verify(ModuleSymbol module)
396393
{
397394
var test = module.ContainingAssembly.GetTypeByMetadataName("Test");
398395
var f = test.GetMethod("F");
399-
Assert.Equal(MethodImplOptionsAsync, f.ImplementationAttributes);
396+
Assert.Equal(MethodImplAttributes.Async, f.ImplementationAttributes);
400397
Assert.Empty(test.GetTypeMembers());
401398
}
402399
}
@@ -459,7 +456,7 @@ void verify(ModuleSymbol module)
459456
{
460457
var test = module.ContainingAssembly.GetTypeByMetadataName("Test");
461458
var f = test.GetMethod("F");
462-
Assert.Equal(MethodImplOptionsAsync, f.ImplementationAttributes);
459+
Assert.Equal(MethodImplAttributes.Async, f.ImplementationAttributes);
463460
AssertEx.Equal(["<>c"], test.GetTypeMembers().SelectAsArray(t => t.Name));
464461
}
465462
}
@@ -512,7 +509,7 @@ void verify(ModuleSymbol module)
512509
{
513510
var test = module.ContainingAssembly.GetTypeByMetadataName("Test");
514511
var f = test.GetMethod("F");
515-
Assert.Equal(MethodImplOptionsAsync, f.ImplementationAttributes);
512+
Assert.Equal(MethodImplAttributes.Async, f.ImplementationAttributes);
516513
AssertEx.Empty(test.GetTypeMembers());
517514
}
518515
}
@@ -632,7 +629,7 @@ void verify(ModuleSymbol module)
632629
{
633630
var test = module.ContainingAssembly.GetTypeByMetadataName("Test");
634631
var f = test.GetMethod("F");
635-
Assert.Equal(MethodImplOptionsAsync, f.ImplementationAttributes);
632+
Assert.Equal(MethodImplAttributes.Async, f.ImplementationAttributes);
636633
if (!useValueTask && useGeneric)
637634
{
638635
AssertEx.Equal(["<>c"], test.GetTypeMembers().SelectAsArray(t => t.Name));
@@ -767,7 +764,7 @@ void verify(ModuleSymbol module)
767764
{
768765
var test = module.ContainingAssembly.GetTypeByMetadataName("Test");
769766
var f = test.GetMethod("F");
770-
Assert.Equal(MethodImplOptionsAsync, f.ImplementationAttributes);
767+
Assert.Equal(MethodImplAttributes.Async, f.ImplementationAttributes);
771768
AssertEx.Empty(test.GetTypeMembers());
772769
}
773770
}
@@ -887,7 +884,7 @@ void verify(ModuleSymbol module)
887884
{
888885
var test = module.ContainingAssembly.GetTypeByMetadataName("Test");
889886
var f = test.GetMethod("F");
890-
Assert.Equal(MethodImplOptionsAsync, f.ImplementationAttributes);
887+
Assert.Equal(MethodImplAttributes.Async, f.ImplementationAttributes);
891888
AssertEx.Empty(test.GetTypeMembers());
892889
}
893890
}
@@ -1019,7 +1016,7 @@ void verify(ModuleSymbol module)
10191016
{
10201017
var test = module.ContainingAssembly.GetTypeByMetadataName("Test");
10211018
var f = test.GetMethod("F");
1022-
Assert.Equal(MethodImplOptionsAsync, f.ImplementationAttributes);
1019+
Assert.Equal(MethodImplAttributes.Async, f.ImplementationAttributes);
10231020
AssertEx.Empty(test.GetTypeMembers());
10241021
}
10251022
}
@@ -1130,7 +1127,7 @@ void verify(ModuleSymbol module)
11301127
{
11311128
var test = module.ContainingAssembly.GetTypeByMetadataName("Test");
11321129
var f = test.GetMethod("F");
1133-
Assert.Equal(MethodImplOptionsAsync, f.ImplementationAttributes);
1130+
Assert.Equal(MethodImplAttributes.Async, f.ImplementationAttributes);
11341131
AssertEx.Empty(test.GetTypeMembers());
11351132
}
11361133
}
@@ -1238,7 +1235,7 @@ void verify(ModuleSymbol module)
12381235
{
12391236
var test = module.ContainingAssembly.GetTypeByMetadataName("Test");
12401237
var f = test.GetMethod("F");
1241-
Assert.Equal(MethodImplOptionsAsync, f.ImplementationAttributes);
1238+
Assert.Equal(MethodImplAttributes.Async, f.ImplementationAttributes);
12421239
AssertEx.Empty(test.GetTypeMembers());
12431240
}
12441241
}

src/Compilers/CSharp/Test/Semantic/Semantics/TopLevelStatementsTests.cs

Lines changed: 91 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1104,7 +1104,7 @@ public void LocalDeclarationStatement_17()
11041104
CompileAndVerify(comp, expectedOutput: "Hi!");
11051105
}
11061106

1107-
[Fact]
1107+
[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/80313")]
11081108
public void LocalDeclarationStatement_18()
11091109
{
11101110
var text = @"
@@ -1133,6 +1133,50 @@ public void LocalDeclarationStatement_18()
11331133

11341134
comp = CreateCompilation(text, options: TestOptions.DebugExe);
11351135
comp.VerifyEmitDiagnostics();
1136+
1137+
comp = CreateRuntimeAsyncCompilation(text);
1138+
// https://github.com/dotnet/roslyn/issues/79791: Verify runtime async output
1139+
var verifier = CompileAndVerify(comp, expectedOutput: null, verify: Verification.Fails with
1140+
{
1141+
ILVerifyMessage = "[<Main>$]: Return value missing on the stack. { Offset = 0x2f }"
1142+
}, sourceSymbolValidator: validator);
1143+
verifier.VerifyIL("<top-level-statements-entry-point>", """
1144+
{
1145+
// Code size 48 (0x30)
1146+
.maxstack 1
1147+
.locals init (int V_0, //c
1148+
System.Runtime.CompilerServices.YieldAwaitable.YieldAwaiter V_1,
1149+
System.Runtime.CompilerServices.YieldAwaitable V_2)
1150+
IL_0000: ldc.i4.s -100
1151+
IL_0002: stloc.0
1152+
IL_0003: ldloca.s V_0
1153+
IL_0005: ldind.i4
1154+
IL_0006: call "void System.Console.Write(int)"
1155+
IL_000b: call "System.Runtime.CompilerServices.YieldAwaitable System.Threading.Tasks.Task.Yield()"
1156+
IL_0010: stloc.2
1157+
IL_0011: ldloca.s V_2
1158+
IL_0013: call "System.Runtime.CompilerServices.YieldAwaitable.YieldAwaiter System.Runtime.CompilerServices.YieldAwaitable.GetAwaiter()"
1159+
IL_0018: stloc.1
1160+
IL_0019: ldloca.s V_1
1161+
IL_001b: call "bool System.Runtime.CompilerServices.YieldAwaitable.YieldAwaiter.IsCompleted.get"
1162+
IL_0020: brtrue.s IL_0028
1163+
IL_0022: ldloc.1
1164+
IL_0023: call "void System.Runtime.CompilerServices.AsyncHelpers.UnsafeAwaitAwaiter<System.Runtime.CompilerServices.YieldAwaitable.YieldAwaiter>(System.Runtime.CompilerServices.YieldAwaitable.YieldAwaiter)"
1165+
IL_0028: ldloca.s V_1
1166+
IL_002a: call "void System.Runtime.CompilerServices.YieldAwaitable.YieldAwaiter.GetResult()"
1167+
IL_002f: ret
1168+
}
1169+
""");
1170+
1171+
static void validator(ModuleSymbol module)
1172+
{
1173+
var program = module.GlobalNamespace.GetTypeMember("Program");
1174+
Assert.NotNull(program);
1175+
1176+
var main = program.GetMethod("<Main>$");
1177+
Assert.NotNull(main);
1178+
Assert.Equal(MethodImplAttributes.Async, main.ImplementationAttributes & MethodImplAttributes.Async);
1179+
}
11361180
}
11371181

11381182
[Fact]
@@ -7825,7 +7869,7 @@ public void Return_03()
78257869
}
78267870
}
78277871

7828-
[Fact]
7872+
[Fact, WorkItem("https://github.com/dotnet/roslyn/issues/80313")]
78297873
public void Return_04()
78307874
{
78317875
var text = @"
@@ -7893,6 +7937,51 @@ public void Return_04()
78937937
</methods>
78947938
</symbols>", options: PdbValidationOptions.SkipConversionValidation);
78957939
}
7940+
7941+
comp = CreateRuntimeAsyncCompilation(text);
7942+
// https://github.com/dotnet/roslyn/issues/79791: Verify runtime async output
7943+
var verifier = CompileAndVerify(comp, expectedOutput: null, verify: Verification.Fails with
7944+
{
7945+
ILVerifyMessage = "[<Main>$]: Unexpected type on the stack. { Offset = 0x43, Found = Int32, Expected = ref '[System.Runtime]System.Threading.Tasks.Task`1<int32>' }"
7946+
}, sourceSymbolValidator: validator);
7947+
7948+
verifier.VerifyIL("<top-level-statements-entry-point>", """
7949+
{
7950+
// Code size 68 (0x44)
7951+
.maxstack 3
7952+
IL_0000: ldstr "hello "
7953+
IL_0005: call "void System.Console.Write(string)"
7954+
IL_000a: call "System.Threading.Tasks.TaskFactory System.Threading.Tasks.Task.Factory.get"
7955+
IL_000f: ldsfld "System.Func<int> Program.<>c.<>9__0_0"
7956+
IL_0014: dup
7957+
IL_0015: brtrue.s IL_002e
7958+
IL_0017: pop
7959+
IL_0018: ldsfld "Program.<>c Program.<>c.<>9"
7960+
IL_001d: ldftn "int Program.<>c.<<Main>$>b__0_0()"
7961+
IL_0023: newobj "System.Func<int>..ctor(object, System.IntPtr)"
7962+
IL_0028: dup
7963+
IL_0029: stsfld "System.Func<int> Program.<>c.<>9__0_0"
7964+
IL_002e: callvirt "System.Threading.Tasks.Task<int> System.Threading.Tasks.TaskFactory.StartNew<int>(System.Func<int>)"
7965+
IL_0033: call "int System.Runtime.CompilerServices.AsyncHelpers.Await<int>(System.Threading.Tasks.Task<int>)"
7966+
IL_0038: pop
7967+
IL_0039: ldarg.0
7968+
IL_003a: ldc.i4.0
7969+
IL_003b: ldelem.ref
7970+
IL_003c: call "void System.Console.Write(string)"
7971+
IL_0041: ldc.i4.s 11
7972+
IL_0043: ret
7973+
}
7974+
""");
7975+
7976+
static void validator(ModuleSymbol module)
7977+
{
7978+
var program = module.GlobalNamespace.GetTypeMember("Program");
7979+
Assert.NotNull(program);
7980+
7981+
var main = program.GetMethod("<Main>$");
7982+
Assert.NotNull(main);
7983+
Assert.Equal(MethodImplAttributes.Async, main.ImplementationAttributes & MethodImplAttributes.Async);
7984+
}
78967985
}
78977986

78987987
[Fact]
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using System.Reflection;
6+
using System.Runtime.CompilerServices;
7+
8+
namespace Microsoft.CodeAnalysis;
9+
10+
internal static class MethodImplAttributeExtensions
11+
{
12+
extension(MethodImplAttributes)
13+
{
14+
// https://github.com/dotnet/roslyn/issues/79792: Use the real value when possible
15+
public static MethodImplAttributes Async => (MethodImplAttributes)0x2000;
16+
}
17+
}
18+
19+
internal static class MethodImplOptionsExtensions
20+
{
21+
extension(MethodImplOptions)
22+
{
23+
// https://github.com/dotnet/roslyn/issues/79792: Use the real value when possible
24+
public static MethodImplOptions Async => (MethodImplOptions)0x2000;
25+
}
26+
}

src/Compilers/Core/Portable/Symbols/Attributes/CommonAttributeData.cs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -427,17 +427,15 @@ internal static void DecodeMethodImplAttribute<T, TAttributeSyntaxNode, TAttribu
427427
options = options & ~(MethodImplOptions)3;
428428
}
429429

430-
// https://github.com/dotnet/roslyn/issues/79792: Use the real value when possible
431-
const MethodImplOptions MethodImplOptionsAsync = (MethodImplOptions)0x2000;
432-
if ((options & MethodImplOptionsAsync) != 0)
430+
if ((options & MethodImplOptions.Async) != 0)
433431
{
434432
// Error if [MethodImpl(MethodImplOptions.Async)] is used directly on a method
435433
// We give an exception to the AsyncHelpers special type, as it manually implements the pattern as part of the
436434
// runtime's async support
437435
if ((InternalSpecialType)appliedToSymbol.ExtendedSpecialType != InternalSpecialType.System_Runtime_CompilerServices_AsyncHelpers)
438436
{
439437
arguments.Diagnostics.Add(messageProvider.CreateDiagnostic(messageProvider.ERR_MethodImplAttributeAsyncCannotBeUsed, arguments.AttributeSyntaxOpt.Location));
440-
options &= ~MethodImplOptionsAsync;
438+
options &= ~MethodImplOptions.Async;
441439
}
442440
}
443441
}

0 commit comments

Comments
 (0)