Skip to content

Commit 8094f66

Browse files
author
Julien Couvreur
committed
Emit async-iterators with runtime-async when possible
1 parent ec4f06e commit 8094f66

File tree

37 files changed

+5293
-514
lines changed

37 files changed

+5293
-514
lines changed

src/Compilers/CSharp/Portable/BoundTree/BoundNode_Source.cs

Lines changed: 118 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// See the LICENSE file in the project root for more information.
44

55
using System.Collections.Generic;
6+
using System.Collections.Immutable;
67
using System.Text;
78
using Microsoft.CodeAnalysis.CSharp.Symbols;
89
using Roslyn.Utilities;
@@ -168,8 +169,9 @@ void appendSourceCore(BoundNode node, int indent, Dictionary<SynthesizedLocal, i
168169
case BoundConditionalGoto gotoStatement:
169170
{
170171
append("if (");
171-
append(gotoStatement.JumpIfTrue ? "" : "!");
172+
append(gotoStatement.JumpIfTrue ? "" : "!(");
172173
appendSource(gotoStatement.Condition);
174+
append(gotoStatement.JumpIfTrue ? "" : ")");
173175
append(") ");
174176

175177
append("goto ");
@@ -339,9 +341,22 @@ void appendSourceCore(BoundNode node, int indent, Dictionary<SynthesizedLocal, i
339341
}
340342
case BoundBinaryOperator binary:
341343
{
344+
string relation = (binary.OperatorKind & BinaryOperatorKind.OpMask) switch
345+
{
346+
BinaryOperatorKind.GreaterThan => ">",
347+
BinaryOperatorKind.GreaterThanOrEqual => ">=",
348+
BinaryOperatorKind.LessThan => "<",
349+
BinaryOperatorKind.LessThanOrEqual => "<=",
350+
BinaryOperatorKind.Equal => "==",
351+
BinaryOperatorKind.NotEqual => "!=",
352+
BinaryOperatorKind.Addition => "+",
353+
BinaryOperatorKind.Subtraction => "-",
354+
_ => binary.OperatorKind.ToString()
355+
};
356+
342357
appendSource(binary.Left);
343358
append(" ");
344-
append(binary.OperatorKind.ToString());
359+
append(relation);
345360
append(" ");
346361
appendSource(binary.Right);
347362
break;
@@ -473,6 +488,89 @@ void appendSourceCore(BoundNode node, int indent, Dictionary<SynthesizedLocal, i
473488
appendConstantValue(relationalPattern.ConstantValue);
474489
break;
475490
}
491+
case BoundObjectCreationExpression objectCreation:
492+
{
493+
append("new ");
494+
append(objectCreation.Type.Name);
495+
496+
if (objectCreation.Type is NamedTypeSymbol { TypeArgumentsWithAnnotationsNoUseSiteDiagnostics: var typeArguments }
497+
&& typeArguments.Length > 0)
498+
{
499+
appendTypeArguments(typeArguments);
500+
}
501+
502+
append("(");
503+
504+
bool first = true;
505+
foreach (var argument in objectCreation.Arguments)
506+
{
507+
if (!first)
508+
{
509+
append(", ");
510+
}
511+
first = false;
512+
appendSource(argument);
513+
}
514+
515+
append(")");
516+
break;
517+
}
518+
case BoundSwitchDispatch switchDispatch:
519+
{
520+
append("switch dispatch(");
521+
appendSource(switchDispatch.Expression);
522+
appendLine(")");
523+
appendLine("{");
524+
incrementIndent();
525+
526+
foreach (var (value, label) in switchDispatch.Cases)
527+
{
528+
append("case ");
529+
appendConstantValue(value);
530+
append(" => ");
531+
appendLine(label.ToString());
532+
}
533+
534+
append("default => ");
535+
appendLine(switchDispatch.DefaultLabel.ToString());
536+
decrementIndent();
537+
appendLine("}");
538+
break;
539+
}
540+
case BoundPropertyAccess propertyAccess:
541+
{
542+
var receiver = propertyAccess.ReceiverOpt;
543+
if (receiver != null)
544+
{
545+
appendSource(receiver);
546+
append(".");
547+
}
548+
append(propertyAccess.PropertySymbol.Name);
549+
break;
550+
}
551+
case BoundParameter parameter:
552+
{
553+
append(parameter.ParameterSymbol.Name);
554+
break;
555+
}
556+
case BoundBaseReference baseReference:
557+
{
558+
append("base");
559+
break;
560+
}
561+
case BoundPassByCopy passByCopy:
562+
{
563+
append("passByCopy ");
564+
appendSource(passByCopy.Expression);
565+
break;
566+
}
567+
case BoundAsOperator asOperator:
568+
{
569+
appendSource(asOperator.Operand);
570+
append(" as ");
571+
appendSource(asOperator.TargetType);
572+
break;
573+
}
476574
default:
477575
appendLine(node.Kind.ToString());
478576
break;
@@ -571,6 +669,24 @@ void appendConstantValue(ConstantValue? constantValueOpt)
571669
break;
572670
}
573671
}
672+
673+
void appendTypeArguments(ImmutableArray<TypeWithAnnotations> typeArguments)
674+
{
675+
append("<");
676+
bool first = true;
677+
foreach (var typeArgument in typeArguments)
678+
{
679+
if (!first)
680+
{
681+
append(", ");
682+
}
683+
684+
first = false;
685+
append(typeArgument.Type.Name);
686+
}
687+
688+
append(">");
689+
}
574690
}
575691
}
576692
#endif

src/Compilers/CSharp/Portable/Compilation/CSharpCompilation.cs

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -356,14 +356,17 @@ internal bool IsRuntimeAsyncEnabledIn(Symbol? symbol)
356356

357357
Debug.Assert(ReferenceEquals(method.ContainingAssembly, Assembly));
358358

359-
var methodReturn = method.ReturnType.OriginalDefinition;
360-
if (((InternalSpecialType)methodReturn.ExtendedSpecialType) is not (
361-
InternalSpecialType.System_Threading_Tasks_Task or
362-
InternalSpecialType.System_Threading_Tasks_Task_T or
363-
InternalSpecialType.System_Threading_Tasks_ValueTask or
364-
InternalSpecialType.System_Threading_Tasks_ValueTask_T))
365-
{
366-
return false;
359+
if (!method.IsAsyncReturningIAsyncEnumerable(this) && !method.IsAsyncReturningIAsyncEnumerator(this))
360+
{
361+
var methodReturn = method.ReturnType.OriginalDefinition;
362+
if (((InternalSpecialType)methodReturn.ExtendedSpecialType) is not (
363+
InternalSpecialType.System_Threading_Tasks_Task or
364+
InternalSpecialType.System_Threading_Tasks_Task_T or
365+
InternalSpecialType.System_Threading_Tasks_ValueTask or
366+
InternalSpecialType.System_Threading_Tasks_ValueTask_T))
367+
{
368+
return false;
369+
}
367370
}
368371

369372
return symbol switch
@@ -2854,8 +2857,7 @@ internal void RecordImport(ExternAliasDirectiveSyntax syntax)
28542857

28552858
private void RecordImportInternal(CSharpSyntaxNode syntax)
28562859
{
2857-
// Note: the suppression will be unnecessary once LazyInitializer is properly annotated
2858-
LazyInitializer.EnsureInitialized(ref _lazyImportInfos)!.
2860+
LazyInitializer.EnsureInitialized(ref _lazyImportInfos).
28592861
TryAdd(new ImportInfo(syntax.SyntaxTree, syntax.Kind(), syntax.Span), default);
28602862
}
28612863

src/Compilers/CSharp/Portable/Compiler/MethodBodySynthesizer.Lowered.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System;
66
using System.Collections.Immutable;
77
using System.Diagnostics;
8+
using System.Reflection;
89
using Microsoft.CodeAnalysis.PooledObjects;
910
using Roslyn.Utilities;
1011

@@ -227,6 +228,9 @@ internal override bool SynthesizesLoweredBoundBody
227228
get { return true; }
228229
}
229230

231+
public override bool IsAsync => false;
232+
internal override MethodImplAttributes ImplementationAttributes => default;
233+
230234
/// <summary>
231235
/// Given a SynthesizedExplicitImplementationMethod (effectively a tuple (interface method, implementing method, implementing type)),
232236
/// construct a BoundBlock body. Consider the tuple (Interface.Goo, Base.Goo, Derived). The generated method will look like:

src/Compilers/CSharp/Portable/Compiler/MethodCompiler.cs

Lines changed: 94 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
using System.Linq;
1313
using System.Threading;
1414
using System.Threading.Tasks;
15+
using System.Xml.Linq;
1516
using Microsoft.CodeAnalysis.CodeGen;
1617
using Microsoft.CodeAnalysis.CSharp.Emit;
1718
using Microsoft.CodeAnalysis.CSharp.Symbols;
@@ -769,26 +770,9 @@ private void CompileSynthesizedMethods(TypeCompilationState compilationState)
769770

770771
try
771772
{
772-
// Local functions can be iterators as well as be async (lambdas can only be async), so we need to lower both iterators and async
773-
IteratorStateMachine iteratorStateMachine;
774-
BoundStatement loweredBody = IteratorRewriter.Rewrite(methodWithBody.Body, method, methodOrdinal, stateMachineStateDebugInfoBuilder, variableSlotAllocatorOpt, compilationState, diagnosticsThisMethod, out iteratorStateMachine);
775-
StateMachineTypeSymbol stateMachine = iteratorStateMachine;
776-
777-
if (!loweredBody.HasErrors)
778-
{
779-
AsyncStateMachine asyncStateMachine = null;
780-
if (compilationState.Compilation.IsRuntimeAsyncEnabledIn(method))
781-
{
782-
loweredBody = RuntimeAsyncRewriter.Rewrite(loweredBody, method, compilationState, diagnosticsThisMethod);
783-
}
784-
else
785-
{
786-
loweredBody = AsyncRewriter.Rewrite(loweredBody, method, methodOrdinal, stateMachineStateDebugInfoBuilder, variableSlotAllocatorOpt, compilationState, diagnosticsThisMethod, out asyncStateMachine);
787-
}
788-
789-
Debug.Assert((object)iteratorStateMachine == null || (object)asyncStateMachine == null);
790-
stateMachine = stateMachine ?? asyncStateMachine;
791-
}
773+
BoundStatement loweredBody;
774+
StateMachineTypeSymbol stateMachine;
775+
(loweredBody, stateMachine) = lowerMethodBody(methodWithBody, compilationState, methodOrdinal, stateMachineStateDebugInfoBuilder, variableSlotAllocatorOpt, diagnosticsThisMethod);
792776

793777
SetGlobalErrorIfTrue(diagnosticsThisMethod.HasAnyErrors());
794778

@@ -844,6 +828,72 @@ private void CompileSynthesizedMethods(TypeCompilationState compilationState)
844828
compilationState.CurrentImportChain = oldImportChain;
845829
stateMachineStateDebugInfoBuilder.Free();
846830
}
831+
832+
static (BoundStatement loweredBody, StateMachineTypeSymbol stateMachine)
833+
lowerMethodBody(TypeCompilationState.MethodWithBody methodWithBody, TypeCompilationState compilationState, int methodOrdinal, ArrayBuilder<StateMachineStateDebugInfo> stateMachineStateDebugInfoBuilder, VariableSlotAllocator variableSlotAllocatorOpt, BindingDiagnosticBag diagnosticsThisMethod)
834+
{
835+
var method = methodWithBody.Method;
836+
if (method is SynthesizedStateMachineMoveNextMethod { IsAsync: true })
837+
{
838+
// "MoveNextAsync" is a runtime-async method, but it has already been lowered
839+
Debug.Assert(method.Name is WellKnownMemberNames.MoveNextAsyncMethodName);
840+
return (methodWithBody.Body, null);
841+
}
842+
843+
// Local functions can be iterators as well as be async (lambdas can only be async), so we need to lower both iterators and async
844+
IteratorStateMachine iteratorStateMachine;
845+
BoundStatement loweredBody = IteratorRewriter.Rewrite(methodWithBody.Body, method, methodOrdinal, stateMachineStateDebugInfoBuilder, variableSlotAllocatorOpt, compilationState, diagnosticsThisMethod, out iteratorStateMachine);
846+
StateMachineTypeSymbol stateMachine = iteratorStateMachine;
847+
if (!loweredBody.HasErrors
848+
&& !IsMissingYieldInAsyncIterator(loweredBody, method, diagnosticsThisMethod))
849+
{
850+
if (compilationState.Compilation.IsRuntimeAsyncEnabledIn(method))
851+
{
852+
if (method.IsAsyncReturningIAsyncEnumerable(method.DeclaringCompilation)
853+
|| method.IsAsyncReturningIAsyncEnumerator(method.DeclaringCompilation))
854+
{
855+
RuntimeAsyncIteratorStateMachine runtimeAsyncIteratorStateMachine;
856+
loweredBody = RuntimeAsyncIteratorRewriter.Rewrite(loweredBody, method,
857+
methodOrdinal, stateMachineStateDebugInfoBuilder, variableSlotAllocatorOpt, compilationState, diagnosticsThisMethod,
858+
out runtimeAsyncIteratorStateMachine);
859+
860+
Debug.Assert(runtimeAsyncIteratorStateMachine is not null);
861+
stateMachine = runtimeAsyncIteratorStateMachine;
862+
}
863+
else
864+
{
865+
loweredBody = RuntimeAsyncRewriter.Rewrite(loweredBody, method, compilationState, diagnosticsThisMethod);
866+
}
867+
}
868+
else
869+
{
870+
AsyncStateMachine asyncStateMachine;
871+
loweredBody = AsyncRewriter.Rewrite(loweredBody, method, methodOrdinal, stateMachineStateDebugInfoBuilder, variableSlotAllocatorOpt, compilationState, diagnosticsThisMethod, out asyncStateMachine);
872+
Debug.Assert((object)iteratorStateMachine == null || (object)asyncStateMachine == null);
873+
stateMachine = stateMachine ?? asyncStateMachine;
874+
}
875+
}
876+
877+
return (loweredBody, stateMachine);
878+
}
879+
}
880+
881+
private static bool IsMissingYieldInAsyncIterator(BoundStatement body, MethodSymbol method, BindingDiagnosticBag diagnostics)
882+
{
883+
CSharpCompilation compilation = method.DeclaringCompilation;
884+
bool isAsyncEnumerableOrEnumerator = method.IsAsyncReturningIAsyncEnumerable(compilation) ||
885+
method.IsAsyncReturningIAsyncEnumerator(compilation);
886+
887+
if (isAsyncEnumerableOrEnumerator && !method.IsIterator)
888+
{
889+
bool containsAwait = AsyncRewriter.AwaitDetector.ContainsAwait(body);
890+
diagnostics.Add(containsAwait ? ErrorCode.ERR_PossibleAsyncIteratorWithoutYield : ErrorCode.ERR_PossibleAsyncIteratorWithoutYieldOrAwait,
891+
method.GetFirstLocation());
892+
893+
return true;
894+
}
895+
896+
return false;
847897
}
848898

849899
/// <summary>
@@ -1588,26 +1638,42 @@ internal static BoundStatement LowerBodyOrInitializer(
15881638
BoundStatement bodyWithoutIterators = IteratorRewriter.Rewrite(bodyWithoutLambdas, method, methodOrdinal, stateMachineStateDebugInfoBuilder, lazyVariableSlotAllocator, compilationState, diagnostics,
15891639
out IteratorStateMachine iteratorStateMachine);
15901640

1591-
if (bodyWithoutIterators.HasErrors)
1641+
if (bodyWithoutIterators.HasErrors
1642+
|| IsMissingYieldInAsyncIterator(loweredBody, method, diagnostics))
15921643
{
15931644
return bodyWithoutIterators;
15941645
}
15951646

15961647
BoundStatement bodyWithoutAsync;
1597-
AsyncStateMachine asyncStateMachine = null;
1598-
if (compilationState.Compilation.IsRuntimeAsyncEnabledIn(method))
1648+
var compilation = compilationState.Compilation;
1649+
if (compilation.IsRuntimeAsyncEnabledIn(method))
15991650
{
1600-
bodyWithoutAsync = RuntimeAsyncRewriter.Rewrite(bodyWithoutIterators, method, compilationState, diagnostics);
1651+
if (method.IsAsyncReturningIAsyncEnumerable(compilation)
1652+
|| method.IsAsyncReturningIAsyncEnumerator(compilation))
1653+
{
1654+
RuntimeAsyncIteratorStateMachine runtimeAsyncIteratorStateMachine;
1655+
1656+
bodyWithoutAsync = RuntimeAsyncIteratorRewriter.Rewrite(bodyWithoutIterators, method,
1657+
methodOrdinal, stateMachineStateDebugInfoBuilder, lazyVariableSlotAllocator, compilationState, diagnostics,
1658+
out runtimeAsyncIteratorStateMachine);
1659+
1660+
Debug.Assert(runtimeAsyncIteratorStateMachine is not null);
1661+
stateMachineTypeOpt = runtimeAsyncIteratorStateMachine;
1662+
}
1663+
else
1664+
{
1665+
bodyWithoutAsync = RuntimeAsyncRewriter.Rewrite(bodyWithoutIterators, method, compilationState, diagnostics);
1666+
}
16011667
}
16021668
else
16031669
{
1670+
AsyncStateMachine asyncStateMachine = null;
16041671
bodyWithoutAsync = AsyncRewriter.Rewrite(bodyWithoutIterators, method, methodOrdinal, stateMachineStateDebugInfoBuilder, lazyVariableSlotAllocator, compilationState, diagnostics,
16051672
out asyncStateMachine);
1673+
Debug.Assert(iteratorStateMachine is null || asyncStateMachine is null);
1674+
stateMachineTypeOpt = (StateMachineTypeSymbol)iteratorStateMachine ?? asyncStateMachine;
16061675
}
16071676

1608-
Debug.Assert(iteratorStateMachine is null || asyncStateMachine is null);
1609-
stateMachineTypeOpt = (StateMachineTypeSymbol)iteratorStateMachine ?? asyncStateMachine;
1610-
16111677
return bodyWithoutAsync;
16121678
}
16131679
catch (BoundTreeVisitor.CancelledByStackGuardException ex)
@@ -1666,8 +1732,7 @@ private static MethodBody GenerateMethodBody(
16661732
bool isAsyncStateMachine;
16671733
MethodSymbol kickoffMethod;
16681734

1669-
if (method is SynthesizedStateMachineMethod stateMachineMethod &&
1670-
method.Name == WellKnownMemberNames.MoveNextMethodName)
1735+
if (method is SynthesizedStateMachineMoveNextMethod stateMachineMethod)
16711736
{
16721737
kickoffMethod = stateMachineMethod.StateMachineType.KickoffMethod;
16731738
Debug.Assert(kickoffMethod != null);

0 commit comments

Comments
 (0)