diff --git a/src/Compilers/CSharp/Portable/Binder/Binder_Await.cs b/src/Compilers/CSharp/Portable/Binder/Binder_Await.cs index a78d7999a783f..c53c5b486ca1f 100644 --- a/src/Compilers/CSharp/Portable/Binder/Binder_Await.cs +++ b/src/Compilers/CSharp/Portable/Binder/Binder_Await.cs @@ -39,7 +39,7 @@ private BoundAwaitExpression BindAwait(BoundExpression expression, SyntaxNode no // non-void return type T, the await-expression is classified as a value of type T. TypeSymbol awaitExpressionType = info.GetResult?.ReturnType ?? (hasErrors ? CreateErrorType() : Compilation.DynamicType); - return new BoundAwaitExpression(node, expression, info, awaitExpressionType, hasErrors); + return new BoundAwaitExpression(node, expression, info, debugInfo: default, awaitExpressionType, hasErrors); } internal void ReportBadAwaitDiagnostics(SyntaxNodeOrToken nodeOrToken, BindingDiagnosticBag diagnostics, ref bool hasErrors) diff --git a/src/Compilers/CSharp/Portable/BoundTree/BoundAwaitExpressionDebugInfo.cs b/src/Compilers/CSharp/Portable/BoundTree/BoundAwaitExpressionDebugInfo.cs new file mode 100644 index 0000000000000..3eb3412dec8fd --- /dev/null +++ b/src/Compilers/CSharp/Portable/BoundTree/BoundAwaitExpressionDebugInfo.cs @@ -0,0 +1,36 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using Microsoft.CodeAnalysis.CodeGen; + +namespace Microsoft.CodeAnalysis.CSharp; + +/// +/// Debug info associated with to support EnC. +/// +/// The id of the generated await. +/// +/// The number of async state machine states to reserve. +/// +/// Any time multiple s might be associated with the same syntax node +/// we need to make sure that the same number of state machine states gets allocated for the node, +/// regardless of the actual number of s that get emitted. +/// +/// To do so one or more of the emitted s may +/// reserve additional dummy state machine states so that the total number of states +/// (one for each plus total reserved states) is constant +/// regardless of semantics of the syntax node. +/// +/// E.g. `await foreach` produces at least one and at most two s: +/// one for MoveNextAsync and the other for DisposeAsync. +/// +/// If the enumerator is async-disposable it produces two s with +/// set to 0. +/// +/// If the enumerator is not async-disposable it produces a single with +/// set to 1. +/// +/// The states are only reserved in DEBUG builds. +/// +internal readonly record struct BoundAwaitExpressionDebugInfo(AwaitDebugId AwaitId, byte ReservedStateMachineCount); diff --git a/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml b/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml index 6ed9b23bca448..b240da9267c7d 100644 --- a/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml +++ b/src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml @@ -690,6 +690,7 @@ + diff --git a/src/Compilers/CSharp/Portable/Generated/BoundNodes.xml.Generated.cs b/src/Compilers/CSharp/Portable/Generated/BoundNodes.xml.Generated.cs index 15f6b47398925..b344d2914ce5a 100644 --- a/src/Compilers/CSharp/Portable/Generated/BoundNodes.xml.Generated.cs +++ b/src/Compilers/CSharp/Portable/Generated/BoundNodes.xml.Generated.cs @@ -2136,7 +2136,7 @@ public BoundAwaitableInfo Update(BoundAwaitableValuePlaceholder? awaitableInstan internal sealed partial class BoundAwaitExpression : BoundExpression { - public BoundAwaitExpression(SyntaxNode syntax, BoundExpression expression, BoundAwaitableInfo awaitableInfo, TypeSymbol type, bool hasErrors = false) + public BoundAwaitExpression(SyntaxNode syntax, BoundExpression expression, BoundAwaitableInfo awaitableInfo, BoundAwaitExpressionDebugInfo debugInfo, TypeSymbol type, bool hasErrors = false) : base(BoundKind.AwaitExpression, syntax, type, hasErrors || expression.HasErrors() || awaitableInfo.HasErrors()) { @@ -2146,20 +2146,22 @@ public BoundAwaitExpression(SyntaxNode syntax, BoundExpression expression, Bound this.Expression = expression; this.AwaitableInfo = awaitableInfo; + this.DebugInfo = debugInfo; } public new TypeSymbol Type => base.Type!; public BoundExpression Expression { get; } public BoundAwaitableInfo AwaitableInfo { get; } + public BoundAwaitExpressionDebugInfo DebugInfo { get; } [DebuggerStepThrough] public override BoundNode? Accept(BoundTreeVisitor visitor) => visitor.VisitAwaitExpression(this); - public BoundAwaitExpression Update(BoundExpression expression, BoundAwaitableInfo awaitableInfo, TypeSymbol type) + public BoundAwaitExpression Update(BoundExpression expression, BoundAwaitableInfo awaitableInfo, BoundAwaitExpressionDebugInfo debugInfo, TypeSymbol type) { - if (expression != this.Expression || awaitableInfo != this.AwaitableInfo || !TypeSymbol.Equals(type, this.Type, TypeCompareKind.ConsiderEverything)) + if (expression != this.Expression || awaitableInfo != this.AwaitableInfo || debugInfo != this.DebugInfo || !TypeSymbol.Equals(type, this.Type, TypeCompareKind.ConsiderEverything)) { - var result = new BoundAwaitExpression(this.Syntax, expression, awaitableInfo, type, this.HasErrors); + var result = new BoundAwaitExpression(this.Syntax, expression, awaitableInfo, debugInfo, type, this.HasErrors); result.CopyAttributes(this); return result; } @@ -10938,7 +10940,7 @@ internal abstract partial class BoundTreeRewriter : BoundTreeVisitor BoundExpression expression = (BoundExpression)this.Visit(node.Expression); BoundAwaitableInfo awaitableInfo = (BoundAwaitableInfo)this.Visit(node.AwaitableInfo); TypeSymbol? type = this.VisitType(node.Type); - return node.Update(expression, awaitableInfo, type); + return node.Update(expression, awaitableInfo, node.DebugInfo, type); } public override BoundNode? VisitTypeOfOperator(BoundTypeOfOperator node) { @@ -12746,12 +12748,12 @@ public NullabilityRewriter(ImmutableDictionary.GetInstance(); diff --git a/src/Compilers/CSharp/Portable/Lowering/AsyncRewriter/AsyncMethodToStateMachineRewriter.cs b/src/Compilers/CSharp/Portable/Lowering/AsyncRewriter/AsyncMethodToStateMachineRewriter.cs index dec45c13ff114..67f3255bb682e 100644 --- a/src/Compilers/CSharp/Portable/Lowering/AsyncRewriter/AsyncMethodToStateMachineRewriter.cs +++ b/src/Compilers/CSharp/Portable/Lowering/AsyncRewriter/AsyncMethodToStateMachineRewriter.cs @@ -380,7 +380,7 @@ private BoundBlock VisitAwaitExpression(BoundAwaitExpression node, BoundExpressi // if(!($awaiterTemp.IsCompleted)) { ... } F.If( condition: F.Not(GenerateGetIsCompleted(awaiterTemp, isCompletedMethod)), - thenClause: GenerateAwaitForIncompleteTask(awaiterTemp))); + thenClause: GenerateAwaitForIncompleteTask(awaiterTemp, node.DebugInfo))); BoundExpression getResultCall = MakeCallMaybeDynamic( F.Local(awaiterTemp), getResult, @@ -450,9 +450,10 @@ private BoundExpression GenerateGetIsCompleted(LocalSymbol awaiterTemp, MethodSy return F.Call(F.Local(awaiterTemp), getIsCompletedMethod); } - private BoundBlock GenerateAwaitForIncompleteTask(LocalSymbol awaiterTemp) + private BoundBlock GenerateAwaitForIncompleteTask(LocalSymbol awaiterTemp, BoundAwaitExpressionDebugInfo debugInfo) { - AddResumableState(awaiterTemp.GetDeclaratorSyntax(), out StateMachineState stateNumber, out GeneratedLabelSymbol resumeLabel); + var awaitSyntax = awaiterTemp.GetDeclaratorSyntax(); + AddResumableState(awaitSyntax, debugInfo.AwaitId, out StateMachineState stateNumber, out GeneratedLabelSymbol resumeLabel); TypeSymbol awaiterFieldType = awaiterTemp.Type.IsVerifierReference() ? F.SpecialType(SpecialType.System_Object) @@ -485,6 +486,15 @@ private BoundBlock GenerateAwaitForIncompleteTask(LocalSymbol awaiterTemp) blockBuilder.Add( GenerateReturn(false)); + if (F.Compilation.Options.EnableEditAndContinue) + { + for (int i = 0; i < debugInfo.ReservedStateMachineCount; i++) + { + AddResumableState(awaitSyntax, new AwaitDebugId((byte)(debugInfo.AwaitId.RelativeStateOrdinal + 1 + i)), out _, out var dummyResumeLabel); + blockBuilder.Add(F.Label(dummyResumeLabel)); + } + } + blockBuilder.Add( F.Label(resumeLabel)); diff --git a/src/Compilers/CSharp/Portable/Lowering/IteratorRewriter/IteratorMethodToStateMachineRewriter.cs b/src/Compilers/CSharp/Portable/Lowering/IteratorRewriter/IteratorMethodToStateMachineRewriter.cs index 5f75666a8248d..1cfae5948d2b1 100644 --- a/src/Compilers/CSharp/Portable/Lowering/IteratorRewriter/IteratorMethodToStateMachineRewriter.cs +++ b/src/Compilers/CSharp/Portable/Lowering/IteratorRewriter/IteratorMethodToStateMachineRewriter.cs @@ -333,7 +333,7 @@ public override BoundNode VisitYieldReturnStatement(BoundYieldReturnStatement no // : ; // // this.state = finalizeState; - AddResumableState(node.Syntax, out StateMachineState stateNumber, out GeneratedLabelSymbol resumeLabel); + AddResumableState(node.Syntax, awaitId: default, out StateMachineState stateNumber, out GeneratedLabelSymbol resumeLabel); _currentFinallyFrame.AddState(stateNumber); var rewrittenExpression = (BoundExpression)Visit(node.Expression); @@ -464,12 +464,12 @@ private IteratorFinallyFrame PushFrame(BoundTryStatement statement) { var syntax = statement.Syntax; - if (slotAllocatorOpt?.TryGetPreviousStateMachineState(syntax, out var finalizeState) != true) + if (slotAllocatorOpt?.TryGetPreviousStateMachineState(syntax, awaitId: default, out var finalizeState) != true) { finalizeState = _nextFinalizeState--; } - AddStateDebugInfo(syntax, finalizeState); + AddStateDebugInfo(syntax, awaitId: default, finalizeState); var finallyMethod = MakeSynthesizedFinally(finalizeState); var newFrame = new IteratorFinallyFrame(_currentFinallyFrame, finalizeState, finallyMethod, _yieldsInTryAnalysis.Labels(statement)); diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Await.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Await.cs index 8b19828417d34..c791821e87fc7 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Await.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_Await.cs @@ -3,6 +3,7 @@ // See the LICENSE file in the project root for more information. using System.Collections.Immutable; +using Microsoft.CodeAnalysis.CodeGen; using Microsoft.CodeAnalysis.CSharp.Symbols; namespace Microsoft.CodeAnalysis.CSharp @@ -19,9 +20,9 @@ public BoundExpression VisitAwaitExpression(BoundAwaitExpression node, bool used return RewriteAwaitExpression((BoundExpression)base.VisitAwaitExpression(node)!, used); } - private BoundExpression RewriteAwaitExpression(SyntaxNode syntax, BoundExpression rewrittenExpression, BoundAwaitableInfo awaitableInfo, TypeSymbol type, bool used) + private BoundExpression RewriteAwaitExpression(SyntaxNode syntax, BoundExpression rewrittenExpression, BoundAwaitableInfo awaitableInfo, TypeSymbol type, BoundAwaitExpressionDebugInfo debugInfo, bool used) { - return RewriteAwaitExpression(new BoundAwaitExpression(syntax, rewrittenExpression, awaitableInfo, type) { WasCompilerGenerated = true }, used); + return RewriteAwaitExpression(new BoundAwaitExpression(syntax, rewrittenExpression, awaitableInfo, debugInfo, type) { WasCompilerGenerated = true }, used); } /// diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_ForEachStatement.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_ForEachStatement.cs index 6a6203f3c642c..1d5f4a7509e5d 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_ForEachStatement.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_ForEachStatement.cs @@ -6,6 +6,7 @@ using System.Collections.Immutable; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeGen; using Microsoft.CodeAnalysis.CSharp.Symbols; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.PooledObjects; @@ -14,6 +15,9 @@ namespace Microsoft.CodeAnalysis.CSharp { internal partial class LocalRewriter { + private static readonly AwaitDebugId s_moveNextAsyncAwaitId = new AwaitDebugId(RelativeStateOrdinal: 0); + private static readonly AwaitDebugId s_disposeAsyncAwaitId = new AwaitDebugId(RelativeStateOrdinal: 1); + /// /// This is the entry point for foreach-loop lowering. It delegates to /// RewriteEnumeratorForEachStatement @@ -208,14 +212,22 @@ private BoundStatement RewriteForEachEnumerator( var rewrittenBodyBlock = CreateBlockDeclaringIterationVariables(iterationVariables, iterationVarDecl, rewrittenBody, forEachSyntax); BoundExpression rewrittenCondition = SynthesizeCall( - methodArgumentInfo: enumeratorInfo.MoveNextInfo, - syntax: forEachSyntax, - receiver: boundEnumeratorVar, - allowExtensionAndOptionalParameters: isAsync); + methodArgumentInfo: enumeratorInfo.MoveNextInfo, + syntax: forEachSyntax, + receiver: boundEnumeratorVar, + allowExtensionAndOptionalParameters: isAsync); + + var disposalFinallyBlock = GetDisposalFinallyBlock(forEachSyntax, enumeratorInfo, enumeratorType, boundEnumeratorVar, out var hasAsyncDisposal); if (isAsync) { Debug.Assert(awaitableInfo is { GetResult: { } }); - rewrittenCondition = RewriteAwaitExpression(forEachSyntax, rewrittenCondition, awaitableInfo, awaitableInfo.GetResult.ReturnType, used: true); + + // We need to be sure that when the disposal isn't async we reserve an unused state machine state number for it, + // so that await foreach always produces 2 state machine states: one for MoveNextAsync and the other for DisposeAsync. + // Otherwise, EnC wouldn't be able to map states when the disposal changes from having async dispose to not, or vice versa. + var debugInfo = new BoundAwaitExpressionDebugInfo(s_moveNextAsyncAwaitId, ReservedStateMachineCount: (byte)(hasAsyncDisposal ? 0 : 1)); + + rewrittenCondition = RewriteAwaitExpression(forEachSyntax, rewrittenCondition, awaitableInfo, awaitableInfo.GetResult.ReturnType, debugInfo, used: true); } BoundStatement whileLoop = RewriteWhileStatement( @@ -228,9 +240,22 @@ private BoundStatement RewriteForEachEnumerator( BoundStatement result; - if (enumeratorInfo.NeedsDisposal) + if (disposalFinallyBlock != null) { - BoundStatement tryFinally = WrapWithTryFinallyDispose(forEachSyntax, enumeratorInfo, enumeratorType, boundEnumeratorVar, whileLoop); + // try { + // while (e.MoveNext()) { + // V v = (V)(T)e.Current; -OR- (D1 d1, ...) = (V)(T)e.Current; + // /* loop body */ + // } + // } + // finally { + // /* dispose of e */ + // } + BoundStatement tryFinally = new BoundTryStatement( + forEachSyntax, + tryBlock: new BoundBlock(forEachSyntax, locals: ImmutableArray.Empty, statements: ImmutableArray.Create(whileLoop)), + catchBlocks: ImmutableArray.Empty, + finallyBlockOpt: disposalFinallyBlock); // E e = ((C)(x)).GetEnumerator(); // try { @@ -275,14 +300,20 @@ private bool TryGetDisposeMethod(SyntaxNode forEachSyntax, ForEachEnumeratorInfo /// - interface-based disposal (the enumerator type converts to IDisposable/IAsyncDisposable) /// - we need to do a runtime check for IDisposable /// - private BoundStatement WrapWithTryFinallyDispose( + /// Finally block, or null if none should be emitted. + private BoundBlock? GetDisposalFinallyBlock( CSharpSyntaxNode forEachSyntax, ForEachEnumeratorInfo enumeratorInfo, TypeSymbol enumeratorType, BoundLocal boundEnumeratorVar, - BoundStatement rewrittenBody) + out bool hasAsyncDisposal) { - Debug.Assert(enumeratorInfo.NeedsDisposal); + hasAsyncDisposal = false; + + if (!enumeratorInfo.NeedsDisposal) + { + return null; + } NamedTypeSymbol? idisposableTypeSymbol = null; bool isImplicit = false; @@ -295,7 +326,7 @@ private BoundStatement WrapWithTryFinallyDispose( // This is a temporary workaround for https://github.com/dotnet/roslyn/issues/39948 if (disposeMethod is null) { - return rewrittenBody; + return null; } idisposableTypeSymbol = disposeMethod.ContainingType; @@ -313,7 +344,6 @@ private BoundStatement WrapWithTryFinallyDispose( containingType: _factory.CurrentType, location: enumeratorInfo.Location); - BoundBlock finallyBlockOpt; if (isImplicit || !(enumeratorInfo.PatternDisposeInfo is null)) { Conversion receiverConversion = enumeratorType.IsStructType() ? @@ -344,6 +374,7 @@ private BoundStatement WrapWithTryFinallyDispose( // await /* disposeCall */ disposeCallStatement = WrapWithAwait(forEachSyntax, disposeCall, disposeAwaitableInfoOpt); _sawAwaitInExceptionHandler = true; + hasAsyncDisposal = true; } else { @@ -373,7 +404,7 @@ private BoundStatement WrapWithTryFinallyDispose( hasErrors: false); } - finallyBlockOpt = new BoundBlock(forEachSyntax, + return new BoundBlock(forEachSyntax, locals: ImmutableArray.Empty, statements: ImmutableArray.Create(alwaysOrMaybeDisposeStmt)); } @@ -429,27 +460,10 @@ private BoundStatement WrapWithTryFinallyDispose( // IDisposable d = e as IDisposable; // if (d != null) d.Dispose(); - finallyBlockOpt = new BoundBlock(forEachSyntax, + return new BoundBlock(forEachSyntax, locals: ImmutableArray.Create(disposableVar), statements: ImmutableArray.Create(disposableVarDecl, ifStmt)); } - - // try { - // while (e.MoveNext()) { - // V v = (V)(T)e.Current; -OR- (D1 d1, ...) = (V)(T)e.Current; - // /* loop body */ - // } - // } - // finally { - // /* dispose of e */ - // } - BoundStatement tryFinally = new BoundTryStatement(forEachSyntax, - tryBlock: new BoundBlock(forEachSyntax, - locals: ImmutableArray.Empty, - statements: ImmutableArray.Create(rewrittenBody)), - catchBlocks: ImmutableArray.Empty, - finallyBlockOpt: finallyBlockOpt); - return tryFinally; } /// @@ -459,7 +473,8 @@ private BoundStatement WrapWithTryFinallyDispose( private BoundStatement WrapWithAwait(SyntaxNode forEachSyntax, BoundExpression disposeCall, BoundAwaitableInfo disposeAwaitableInfoOpt) { TypeSymbol awaitExpressionType = disposeAwaitableInfoOpt.GetResult?.ReturnType ?? _compilation.DynamicType; - var awaitExpr = RewriteAwaitExpression(forEachSyntax, disposeCall, disposeAwaitableInfoOpt, awaitExpressionType, used: false); + var debugInfo = new BoundAwaitExpressionDebugInfo(s_disposeAsyncAwaitId, ReservedStateMachineCount: 0); + var awaitExpr = RewriteAwaitExpression(forEachSyntax, disposeCall, disposeAwaitableInfoOpt, awaitExpressionType, debugInfo, used: false); return new BoundExpressionStatement(forEachSyntax, awaitExpr); } diff --git a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_UsingStatement.cs b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_UsingStatement.cs index eb1915e4adda0..904ded1a899ca 100644 --- a/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_UsingStatement.cs +++ b/src/Compilers/CSharp/Portable/Lowering/LocalRewriter/LocalRewriter_UsingStatement.cs @@ -478,7 +478,7 @@ private BoundExpression GenerateDisposeCall( _sawAwaitInExceptionHandler = true; TypeSymbol awaitExpressionType = awaitOpt.GetResult?.ReturnType ?? _compilation.DynamicType; - disposeCall = RewriteAwaitExpression(resourceSyntax, disposeCall, awaitOpt, awaitExpressionType, false); + disposeCall = RewriteAwaitExpression(resourceSyntax, disposeCall, awaitOpt, awaitExpressionType, debugInfo: default, used: false); } } diff --git a/src/Compilers/CSharp/Portable/Lowering/SpillSequenceSpiller.cs b/src/Compilers/CSharp/Portable/Lowering/SpillSequenceSpiller.cs index dfe215468f343..9bfb3a387e2f3 100644 --- a/src/Compilers/CSharp/Portable/Lowering/SpillSequenceSpiller.cs +++ b/src/Compilers/CSharp/Portable/Lowering/SpillSequenceSpiller.cs @@ -722,7 +722,7 @@ public override BoundNode VisitAwaitExpression(BoundAwaitExpression node) // the spilling will occur in the enclosing node. BoundSpillSequenceBuilder builder = null; var expr = VisitExpression(ref builder, node.Expression); - return UpdateExpression(builder, node.Update(expr, node.AwaitableInfo, node.Type)); + return UpdateExpression(builder, node.Update(expr, node.AwaitableInfo, node.DebugInfo, node.Type)); } public override BoundNode VisitSpillSequence(BoundSpillSequence node) diff --git a/src/Compilers/CSharp/Portable/Lowering/StateMachineRewriter/MethodToStateMachineRewriter.cs b/src/Compilers/CSharp/Portable/Lowering/StateMachineRewriter/MethodToStateMachineRewriter.cs index 464b9a6ed9e8c..9e8d545d5e069 100644 --- a/src/Compilers/CSharp/Portable/Lowering/StateMachineRewriter/MethodToStateMachineRewriter.cs +++ b/src/Compilers/CSharp/Portable/Lowering/StateMachineRewriter/MethodToStateMachineRewriter.cs @@ -206,22 +206,22 @@ protected override BoundExpression FramePointer(SyntaxNode syntax, NamedTypeSymb return result; } #nullable enable - protected void AddResumableState(SyntaxNode awaitOrYieldReturnSyntax, out StateMachineState state, out GeneratedLabelSymbol resumeLabel) - => AddResumableState(_resumableStateAllocator, awaitOrYieldReturnSyntax, out state, out resumeLabel); + protected void AddResumableState(SyntaxNode awaitOrYieldReturnSyntax, AwaitDebugId awaitId, out StateMachineState state, out GeneratedLabelSymbol resumeLabel) + => AddResumableState(_resumableStateAllocator, awaitOrYieldReturnSyntax, awaitId, out state, out resumeLabel); - protected void AddResumableState(ResumableStateMachineStateAllocator allocator, SyntaxNode awaitOrYieldReturnSyntax, out StateMachineState stateNumber, out GeneratedLabelSymbol resumeLabel) + protected void AddResumableState(ResumableStateMachineStateAllocator allocator, SyntaxNode awaitOrYieldReturnSyntax, AwaitDebugId awaitId, out StateMachineState stateNumber, out GeneratedLabelSymbol resumeLabel) { - stateNumber = allocator.AllocateState(awaitOrYieldReturnSyntax); - AddStateDebugInfo(awaitOrYieldReturnSyntax, stateNumber); + stateNumber = allocator.AllocateState(awaitOrYieldReturnSyntax, awaitId); + AddStateDebugInfo(awaitOrYieldReturnSyntax, awaitId, stateNumber); AddState(stateNumber, out resumeLabel); } - protected void AddStateDebugInfo(SyntaxNode node, StateMachineState state) + protected void AddStateDebugInfo(SyntaxNode node, AwaitDebugId awaitId, StateMachineState state) { Debug.Assert(SyntaxBindingUtilities.BindsToResumableStateMachineState(node) || SyntaxBindingUtilities.BindsToTryStatement(node), $"Unexpected syntax: {node.Kind()}"); int syntaxOffset = CurrentMethod.CalculateLocalSyntaxOffset(node.SpanStart, node.SyntaxTree); - _stateDebugInfoBuilder.Add(new StateMachineStateDebugInfo(syntaxOffset, state)); + _stateDebugInfoBuilder.Add(new StateMachineStateDebugInfo(syntaxOffset, awaitId, state)); } protected void AddState(StateMachineState stateNumber, out GeneratedLabelSymbol resumeLabel) diff --git a/src/Compilers/CSharp/Portable/Lowering/StateMachineRewriter/ResumableStateMachineStateAllocator.cs b/src/Compilers/CSharp/Portable/Lowering/StateMachineRewriter/ResumableStateMachineStateAllocator.cs index ce443a1429a01..685dc8e1b6fc4 100644 --- a/src/Compilers/CSharp/Portable/Lowering/StateMachineRewriter/ResumableStateMachineStateAllocator.cs +++ b/src/Compilers/CSharp/Portable/Lowering/StateMachineRewriter/ResumableStateMachineStateAllocator.cs @@ -43,13 +43,13 @@ public ResumableStateMachineStateAllocator(VariableSlotAllocator? slotAllocator, _nextState = slotAllocator?.GetFirstUnusedStateMachineState(increasing) ?? firstState; } - public StateMachineState AllocateState(SyntaxNode awaitOrYieldReturnSyntax) + public StateMachineState AllocateState(SyntaxNode awaitOrYieldReturnSyntax, AwaitDebugId awaitId) { Debug.Assert(SyntaxBindingUtilities.BindsToResumableStateMachineState(awaitOrYieldReturnSyntax)); int direction = _increasing ? +1 : -1; - if (_slotAllocator?.TryGetPreviousStateMachineState(awaitOrYieldReturnSyntax, out var state) == true) + if (_slotAllocator?.TryGetPreviousStateMachineState(awaitOrYieldReturnSyntax, awaitId, out var state) == true) { #if DEBUG // two states of the new state machine should not match the same state of the previous machine: diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAsyncIteratorTests.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAsyncIteratorTests.cs index aa7f9f5f2557b..778817985654a 100644 --- a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAsyncIteratorTests.cs +++ b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAsyncIteratorTests.cs @@ -1619,12 +1619,15 @@ async System.Collections.Generic.IAsyncEnumerator M() }"; var expected = new[] { - // (4,60): error CS8652: The feature 'async streams' is not available in C# 7.3. Please use language version 8.0 or greater. + // 1.cs(2,2): error CS8370: Feature 'nullable reference types' is not available in C# 7.3. Please use language version 8.0 or greater. + // #nullable disable + Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion7_3, "nullable").WithArguments("nullable reference types", "8.0").WithLocation(2, 2), + // 0.cs(4,60): error CS8370: Feature 'async streams' is not available in C# 7.3. Please use language version 8.0 or greater. // async System.Collections.Generic.IAsyncEnumerator M() Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion7_3, "M").WithArguments("async streams", "8.0").WithLocation(4, 60), - // (34,2): error CS8652: The feature 'nullable reference types' is not available in C# 7.3. Please use language version 8.0 or greater. + // 1.cs(21,2): error CS8370: Feature 'nullable reference types' is not available in C# 7.3. Please use language version 8.0 or greater. // #nullable disable - Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion7_3, "nullable").WithArguments("nullable reference types", "8.0").WithLocation(34, 2) + Diagnostic(ErrorCode.ERR_FeatureNotAvailableInVersion7_3, "nullable").WithArguments("nullable reference types", "8.0").WithLocation(21, 2) }; var comp = CreateCompilationWithTasksExtensions(new[] { source, AsyncStreamsTypes }, parseOptions: TestOptions.Regular7_3); comp.VerifyDiagnostics(expected); diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAsyncLocalsTests.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAsyncLocalsTests.cs index 7403c1d4816fd..9f35f02b9440e 100644 --- a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAsyncLocalsTests.cs +++ b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAsyncLocalsTests.cs @@ -335,48 +335,45 @@ public async Task M(IDisposable disposable) }); vd.VerifyPdb("C.M", @" - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -"); + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +", options: PdbValidationOptions.ExcludeDocuments); } [Fact] diff --git a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAwaitForeachTests.cs b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAwaitForeachTests.cs index 4acb278d5d927..5c7354071d03d 100644 --- a/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAwaitForeachTests.cs +++ b/src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAwaitForeachTests.cs @@ -2513,7 +2513,7 @@ public class DerivedEnumerator : Enumerator, System.IAsyncDisposable verifier.VerifyIL("C.
d__0.System.Runtime.CompilerServices.IAsyncStateMachine.MoveNext()", @" { - // Code size 262 (0x106) + // Code size 273 (0x111) .maxstack 3 .locals init (int V_0, System.Threading.CancellationToken V_1, @@ -2528,118 +2528,124 @@ .locals init (int V_0, { // sequence point: IL_0007: ldloc.0 - IL_0008: brfalse.s IL_000c - IL_000a: br.s IL_0011 - IL_000c: br IL_009a + IL_0008: brfalse.s IL_0012 + IL_000a: br.s IL_000c + IL_000c: ldloc.0 + IL_000d: ldc.i4.1 + IL_000e: beq.s IL_0017 + IL_0010: br.s IL_001c + IL_0012: br IL_00a5 + IL_0017: br IL_00a5 // sequence point: { - IL_0011: nop + IL_001c: nop // sequence point: await foreach - IL_0012: nop + IL_001d: nop // sequence point: new C() - IL_0013: ldarg.0 - IL_0014: newobj ""C..ctor()"" - IL_0019: ldloca.s V_1 - IL_001b: initobj ""System.Threading.CancellationToken"" - IL_0021: ldloc.1 - IL_0022: call ""C.Enumerator C.GetAsyncEnumerator(System.Threading.CancellationToken)"" - IL_0027: stfld ""C.Enumerator C.
d__0.<>s__1"" + IL_001e: ldarg.0 + IL_001f: newobj ""C..ctor()"" + IL_0024: ldloca.s V_1 + IL_0026: initobj ""System.Threading.CancellationToken"" + IL_002c: ldloc.1 + IL_002d: call ""C.Enumerator C.GetAsyncEnumerator(System.Threading.CancellationToken)"" + IL_0032: stfld ""C.Enumerator C.
d__0.<>s__1"" // sequence point: - IL_002c: br.s IL_005c + IL_0037: br.s IL_0067 // sequence point: var i - IL_002e: ldarg.0 - IL_002f: ldarg.0 - IL_0030: ldfld ""C.Enumerator C.
d__0.<>s__1"" - IL_0035: callvirt ""int C.Enumerator.Current.get"" - IL_003a: stfld ""int C.
d__0.5__2"" + IL_0039: ldarg.0 + IL_003a: ldarg.0 + IL_003b: ldfld ""C.Enumerator C.
d__0.<>s__1"" + IL_0040: callvirt ""int C.Enumerator.Current.get"" + IL_0045: stfld ""int C.
d__0.5__2"" // sequence point: { - IL_003f: nop + IL_004a: nop // sequence point: Write($""Got({i}) ""); - IL_0040: ldstr ""Got({0}) "" - IL_0045: ldarg.0 - IL_0046: ldfld ""int C.
d__0.5__2"" - IL_004b: box ""int"" - IL_0050: call ""string string.Format(string, object)"" - IL_0055: call ""void System.Console.Write(string)"" - IL_005a: nop + IL_004b: ldstr ""Got({0}) "" + IL_0050: ldarg.0 + IL_0051: ldfld ""int C.
d__0.5__2"" + IL_0056: box ""int"" + IL_005b: call ""string string.Format(string, object)"" + IL_0060: call ""void System.Console.Write(string)"" + IL_0065: nop // sequence point: } - IL_005b: nop + IL_0066: nop // sequence point: in - IL_005c: ldarg.0 - IL_005d: ldfld ""C.Enumerator C.
d__0.<>s__1"" - IL_0062: callvirt ""System.Threading.Tasks.Task C.Enumerator.MoveNextAsync()"" - IL_0067: callvirt ""System.Runtime.CompilerServices.TaskAwaiter System.Threading.Tasks.Task.GetAwaiter()"" - IL_006c: stloc.2 + IL_0067: ldarg.0 + IL_0068: ldfld ""C.Enumerator C.
d__0.<>s__1"" + IL_006d: callvirt ""System.Threading.Tasks.Task C.Enumerator.MoveNextAsync()"" + IL_0072: callvirt ""System.Runtime.CompilerServices.TaskAwaiter System.Threading.Tasks.Task.GetAwaiter()"" + IL_0077: stloc.2 // sequence point: - IL_006d: ldloca.s V_2 - IL_006f: call ""bool System.Runtime.CompilerServices.TaskAwaiter.IsCompleted.get"" - IL_0074: brtrue.s IL_00b6 - IL_0076: ldarg.0 - IL_0077: ldc.i4.0 - IL_0078: dup - IL_0079: stloc.0 - IL_007a: stfld ""int C.
d__0.<>1__state"" + IL_0078: ldloca.s V_2 + IL_007a: call ""bool System.Runtime.CompilerServices.TaskAwaiter.IsCompleted.get"" + IL_007f: brtrue.s IL_00c1 + IL_0081: ldarg.0 + IL_0082: ldc.i4.0 + IL_0083: dup + IL_0084: stloc.0 + IL_0085: stfld ""int C.
d__0.<>1__state"" // async: yield - IL_007f: ldarg.0 - IL_0080: ldloc.2 - IL_0081: stfld ""System.Runtime.CompilerServices.TaskAwaiter C.
d__0.<>u__1"" - IL_0086: ldarg.0 - IL_0087: stloc.3 - IL_0088: ldarg.0 - IL_0089: ldflda ""System.Runtime.CompilerServices.AsyncTaskMethodBuilder C.
d__0.<>t__builder"" - IL_008e: ldloca.s V_2 - IL_0090: ldloca.s V_3 - IL_0092: call ""void System.Runtime.CompilerServices.AsyncTaskMethodBuilder.AwaitUnsafeOnCompleted, C.
d__0>(ref System.Runtime.CompilerServices.TaskAwaiter, ref C.
d__0)"" - IL_0097: nop - IL_0098: leave.s IL_0105 + IL_008a: ldarg.0 + IL_008b: ldloc.2 + IL_008c: stfld ""System.Runtime.CompilerServices.TaskAwaiter C.
d__0.<>u__1"" + IL_0091: ldarg.0 + IL_0092: stloc.3 + IL_0093: ldarg.0 + IL_0094: ldflda ""System.Runtime.CompilerServices.AsyncTaskMethodBuilder C.
d__0.<>t__builder"" + IL_0099: ldloca.s V_2 + IL_009b: ldloca.s V_3 + IL_009d: call ""void System.Runtime.CompilerServices.AsyncTaskMethodBuilder.AwaitUnsafeOnCompleted, C.
d__0>(ref System.Runtime.CompilerServices.TaskAwaiter, ref C.
d__0)"" + IL_00a2: nop + IL_00a3: leave.s IL_0110 // async: resume - IL_009a: ldarg.0 - IL_009b: ldfld ""System.Runtime.CompilerServices.TaskAwaiter C.
d__0.<>u__1"" - IL_00a0: stloc.2 - IL_00a1: ldarg.0 - IL_00a2: ldflda ""System.Runtime.CompilerServices.TaskAwaiter C.
d__0.<>u__1"" - IL_00a7: initobj ""System.Runtime.CompilerServices.TaskAwaiter"" - IL_00ad: ldarg.0 - IL_00ae: ldc.i4.m1 - IL_00af: dup - IL_00b0: stloc.0 - IL_00b1: stfld ""int C.
d__0.<>1__state"" - IL_00b6: ldarg.0 - IL_00b7: ldloca.s V_2 - IL_00b9: call ""bool System.Runtime.CompilerServices.TaskAwaiter.GetResult()"" - IL_00be: stfld ""bool C.
d__0.<>s__3"" - IL_00c3: ldarg.0 - IL_00c4: ldfld ""bool C.
d__0.<>s__3"" - IL_00c9: brtrue IL_002e + IL_00a5: ldarg.0 + IL_00a6: ldfld ""System.Runtime.CompilerServices.TaskAwaiter C.
d__0.<>u__1"" + IL_00ab: stloc.2 + IL_00ac: ldarg.0 + IL_00ad: ldflda ""System.Runtime.CompilerServices.TaskAwaiter C.
d__0.<>u__1"" + IL_00b2: initobj ""System.Runtime.CompilerServices.TaskAwaiter"" + IL_00b8: ldarg.0 + IL_00b9: ldc.i4.m1 + IL_00ba: dup + IL_00bb: stloc.0 + IL_00bc: stfld ""int C.
d__0.<>1__state"" + IL_00c1: ldarg.0 + IL_00c2: ldloca.s V_2 + IL_00c4: call ""bool System.Runtime.CompilerServices.TaskAwaiter.GetResult()"" + IL_00c9: stfld ""bool C.
d__0.<>s__3"" IL_00ce: ldarg.0 - IL_00cf: ldnull - IL_00d0: stfld ""C.Enumerator C.
d__0.<>s__1"" - IL_00d5: leave.s IL_00f1 + IL_00cf: ldfld ""bool C.
d__0.<>s__3"" + IL_00d4: brtrue IL_0039 + IL_00d9: ldarg.0 + IL_00da: ldnull + IL_00db: stfld ""C.Enumerator C.
d__0.<>s__1"" + IL_00e0: leave.s IL_00fc } catch System.Exception { // async: catch handler, sequence point: - IL_00d7: stloc.s V_4 - IL_00d9: ldarg.0 - IL_00da: ldc.i4.s -2 - IL_00dc: stfld ""int C.
d__0.<>1__state"" - IL_00e1: ldarg.0 - IL_00e2: ldflda ""System.Runtime.CompilerServices.AsyncTaskMethodBuilder C.
d__0.<>t__builder"" - IL_00e7: ldloc.s V_4 - IL_00e9: call ""void System.Runtime.CompilerServices.AsyncTaskMethodBuilder.SetException(System.Exception)"" - IL_00ee: nop - IL_00ef: leave.s IL_0105 + IL_00e2: stloc.s V_4 + IL_00e4: ldarg.0 + IL_00e5: ldc.i4.s -2 + IL_00e7: stfld ""int C.
d__0.<>1__state"" + IL_00ec: ldarg.0 + IL_00ed: ldflda ""System.Runtime.CompilerServices.AsyncTaskMethodBuilder C.
d__0.<>t__builder"" + IL_00f2: ldloc.s V_4 + IL_00f4: call ""void System.Runtime.CompilerServices.AsyncTaskMethodBuilder.SetException(System.Exception)"" + IL_00f9: nop + IL_00fa: leave.s IL_0110 } // sequence point: } - IL_00f1: ldarg.0 - IL_00f2: ldc.i4.s -2 - IL_00f4: stfld ""int C.
d__0.<>1__state"" + IL_00fc: ldarg.0 + IL_00fd: ldc.i4.s -2 + IL_00ff: stfld ""int C.
d__0.<>1__state"" // sequence point: - IL_00f9: ldarg.0 - IL_00fa: ldflda ""System.Runtime.CompilerServices.AsyncTaskMethodBuilder C.
d__0.<>t__builder"" - IL_00ff: call ""void System.Runtime.CompilerServices.AsyncTaskMethodBuilder.SetResult()"" - IL_0104: nop - IL_0105: ret -}", sequencePoints: "C+
d__0.MoveNext", source: source + s_IAsyncEnumerable); + IL_0104: ldarg.0 + IL_0105: ldflda ""System.Runtime.CompilerServices.AsyncTaskMethodBuilder C.
d__0.<>t__builder"" + IL_010a: call ""void System.Runtime.CompilerServices.AsyncTaskMethodBuilder.SetResult()"" + IL_010f: nop + IL_0110: ret +} +", sequencePoints: "C+
d__0.MoveNext", source: source + s_IAsyncEnumerable); } [Fact] @@ -4219,7 +4225,7 @@ static async System.Threading.Tasks.Task Main() verifier.VerifyIL("C.
d__0.System.Runtime.CompilerServices.IAsyncStateMachine.MoveNext()", @" { - // Code size 272 (0x110) + // Code size 283 (0x11b) .maxstack 3 .locals init (int V_0, System.Threading.CancellationToken V_1, @@ -4234,124 +4240,129 @@ .locals init (int V_0, { // sequence point: IL_0007: ldloc.0 - IL_0008: brfalse.s IL_000c - IL_000a: br.s IL_0011 - IL_000c: br IL_0096 + IL_0008: brfalse.s IL_0012 + IL_000a: br.s IL_000c + IL_000c: ldloc.0 + IL_000d: ldc.i4.1 + IL_000e: beq.s IL_0017 + IL_0010: br.s IL_001c + IL_0012: br IL_00a1 + IL_0017: br IL_00a1 // sequence point: { - IL_0011: nop + IL_001c: nop // sequence point: ICollection c = new Collection(); - IL_0012: ldarg.0 - IL_0013: newobj ""Collection..ctor()"" - IL_0018: stfld ""ICollection C.
d__0.5__1"" + IL_001d: ldarg.0 + IL_001e: newobj ""Collection..ctor()"" + IL_0023: stfld ""ICollection C.
d__0.5__1"" // sequence point: await foreach - IL_001d: nop + IL_0028: nop // sequence point: c - IL_001e: ldarg.0 - IL_001f: ldarg.0 - IL_0020: ldfld ""ICollection C.
d__0.5__1"" - IL_0025: ldloca.s V_1 - IL_0027: initobj ""System.Threading.CancellationToken"" - IL_002d: ldloc.1 - IL_002e: callvirt ""IMyAsyncEnumerator ICollection.GetAsyncEnumerator(System.Threading.CancellationToken)"" - IL_0033: stfld ""IMyAsyncEnumerator C.
d__0.<>s__2"" + IL_0029: ldarg.0 + IL_002a: ldarg.0 + IL_002b: ldfld ""ICollection C.
d__0.5__1"" + IL_0030: ldloca.s V_1 + IL_0032: initobj ""System.Threading.CancellationToken"" + IL_0038: ldloc.1 + IL_0039: callvirt ""IMyAsyncEnumerator ICollection.GetAsyncEnumerator(System.Threading.CancellationToken)"" + IL_003e: stfld ""IMyAsyncEnumerator C.
d__0.<>s__2"" // sequence point: - IL_0038: br.s IL_0058 + IL_0043: br.s IL_0063 // sequence point: var i - IL_003a: ldarg.0 - IL_003b: ldarg.0 - IL_003c: ldfld ""IMyAsyncEnumerator C.
d__0.<>s__2"" - IL_0041: callvirt ""int IMyAsyncEnumerator.Current.get"" - IL_0046: stfld ""int C.
d__0.5__3"" + IL_0045: ldarg.0 + IL_0046: ldarg.0 + IL_0047: ldfld ""IMyAsyncEnumerator C.
d__0.<>s__2"" + IL_004c: callvirt ""int IMyAsyncEnumerator.Current.get"" + IL_0051: stfld ""int C.
d__0.5__3"" // sequence point: { - IL_004b: nop - // sequence point: Write($""Got ""); - IL_004c: ldstr ""Got "" - IL_0051: call ""void System.Console.Write(string)"" IL_0056: nop + // sequence point: Write($""Got ""); + IL_0057: ldstr ""Got "" + IL_005c: call ""void System.Console.Write(string)"" + IL_0061: nop // sequence point: } - IL_0057: nop + IL_0062: nop // sequence point: in - IL_0058: ldarg.0 - IL_0059: ldfld ""IMyAsyncEnumerator C.
d__0.<>s__2"" - IL_005e: callvirt ""System.Threading.Tasks.Task IMyAsyncEnumerator.MoveNextAsync()"" - IL_0063: callvirt ""System.Runtime.CompilerServices.TaskAwaiter System.Threading.Tasks.Task.GetAwaiter()"" - IL_0068: stloc.2 + IL_0063: ldarg.0 + IL_0064: ldfld ""IMyAsyncEnumerator C.
d__0.<>s__2"" + IL_0069: callvirt ""System.Threading.Tasks.Task IMyAsyncEnumerator.MoveNextAsync()"" + IL_006e: callvirt ""System.Runtime.CompilerServices.TaskAwaiter System.Threading.Tasks.Task.GetAwaiter()"" + IL_0073: stloc.2 // sequence point: - IL_0069: ldloca.s V_2 - IL_006b: call ""bool System.Runtime.CompilerServices.TaskAwaiter.IsCompleted.get"" - IL_0070: brtrue.s IL_00b2 - IL_0072: ldarg.0 - IL_0073: ldc.i4.0 - IL_0074: dup - IL_0075: stloc.0 - IL_0076: stfld ""int C.
d__0.<>1__state"" + IL_0074: ldloca.s V_2 + IL_0076: call ""bool System.Runtime.CompilerServices.TaskAwaiter.IsCompleted.get"" + IL_007b: brtrue.s IL_00bd + IL_007d: ldarg.0 + IL_007e: ldc.i4.0 + IL_007f: dup + IL_0080: stloc.0 + IL_0081: stfld ""int C.
d__0.<>1__state"" // async: yield - IL_007b: ldarg.0 - IL_007c: ldloc.2 - IL_007d: stfld ""System.Runtime.CompilerServices.TaskAwaiter C.
d__0.<>u__1"" - IL_0082: ldarg.0 - IL_0083: stloc.3 - IL_0084: ldarg.0 - IL_0085: ldflda ""System.Runtime.CompilerServices.AsyncTaskMethodBuilder C.
d__0.<>t__builder"" - IL_008a: ldloca.s V_2 - IL_008c: ldloca.s V_3 - IL_008e: call ""void System.Runtime.CompilerServices.AsyncTaskMethodBuilder.AwaitUnsafeOnCompleted, C.
d__0>(ref System.Runtime.CompilerServices.TaskAwaiter, ref C.
d__0)"" - IL_0093: nop - IL_0094: leave.s IL_010f + IL_0086: ldarg.0 + IL_0087: ldloc.2 + IL_0088: stfld ""System.Runtime.CompilerServices.TaskAwaiter C.
d__0.<>u__1"" + IL_008d: ldarg.0 + IL_008e: stloc.3 + IL_008f: ldarg.0 + IL_0090: ldflda ""System.Runtime.CompilerServices.AsyncTaskMethodBuilder C.
d__0.<>t__builder"" + IL_0095: ldloca.s V_2 + IL_0097: ldloca.s V_3 + IL_0099: call ""void System.Runtime.CompilerServices.AsyncTaskMethodBuilder.AwaitUnsafeOnCompleted, C.
d__0>(ref System.Runtime.CompilerServices.TaskAwaiter, ref C.
d__0)"" + IL_009e: nop + IL_009f: leave.s IL_011a // async: resume - IL_0096: ldarg.0 - IL_0097: ldfld ""System.Runtime.CompilerServices.TaskAwaiter C.
d__0.<>u__1"" - IL_009c: stloc.2 - IL_009d: ldarg.0 - IL_009e: ldflda ""System.Runtime.CompilerServices.TaskAwaiter C.
d__0.<>u__1"" - IL_00a3: initobj ""System.Runtime.CompilerServices.TaskAwaiter"" - IL_00a9: ldarg.0 - IL_00aa: ldc.i4.m1 - IL_00ab: dup - IL_00ac: stloc.0 - IL_00ad: stfld ""int C.
d__0.<>1__state"" - IL_00b2: ldarg.0 - IL_00b3: ldloca.s V_2 - IL_00b5: call ""bool System.Runtime.CompilerServices.TaskAwaiter.GetResult()"" - IL_00ba: stfld ""bool C.
d__0.<>s__4"" - IL_00bf: ldarg.0 - IL_00c0: ldfld ""bool C.
d__0.<>s__4"" - IL_00c5: brtrue IL_003a + IL_00a1: ldarg.0 + IL_00a2: ldfld ""System.Runtime.CompilerServices.TaskAwaiter C.
d__0.<>u__1"" + IL_00a7: stloc.2 + IL_00a8: ldarg.0 + IL_00a9: ldflda ""System.Runtime.CompilerServices.TaskAwaiter C.
d__0.<>u__1"" + IL_00ae: initobj ""System.Runtime.CompilerServices.TaskAwaiter"" + IL_00b4: ldarg.0 + IL_00b5: ldc.i4.m1 + IL_00b6: dup + IL_00b7: stloc.0 + IL_00b8: stfld ""int C.
d__0.<>1__state"" + IL_00bd: ldarg.0 + IL_00be: ldloca.s V_2 + IL_00c0: call ""bool System.Runtime.CompilerServices.TaskAwaiter.GetResult()"" + IL_00c5: stfld ""bool C.
d__0.<>s__4"" IL_00ca: ldarg.0 - IL_00cb: ldnull - IL_00cc: stfld ""IMyAsyncEnumerator C.
d__0.<>s__2"" - IL_00d1: leave.s IL_00f4 + IL_00cb: ldfld ""bool C.
d__0.<>s__4"" + IL_00d0: brtrue IL_0045 + IL_00d5: ldarg.0 + IL_00d6: ldnull + IL_00d7: stfld ""IMyAsyncEnumerator C.
d__0.<>s__2"" + IL_00dc: leave.s IL_00ff } catch System.Exception { // async: catch handler, sequence point: - IL_00d3: stloc.s V_4 - IL_00d5: ldarg.0 - IL_00d6: ldc.i4.s -2 - IL_00d8: stfld ""int C.
d__0.<>1__state"" - IL_00dd: ldarg.0 - IL_00de: ldnull - IL_00df: stfld ""ICollection C.
d__0.5__1"" - IL_00e4: ldarg.0 - IL_00e5: ldflda ""System.Runtime.CompilerServices.AsyncTaskMethodBuilder C.
d__0.<>t__builder"" - IL_00ea: ldloc.s V_4 - IL_00ec: call ""void System.Runtime.CompilerServices.AsyncTaskMethodBuilder.SetException(System.Exception)"" - IL_00f1: nop - IL_00f2: leave.s IL_010f + IL_00de: stloc.s V_4 + IL_00e0: ldarg.0 + IL_00e1: ldc.i4.s -2 + IL_00e3: stfld ""int C.
d__0.<>1__state"" + IL_00e8: ldarg.0 + IL_00e9: ldnull + IL_00ea: stfld ""ICollection C.
d__0.5__1"" + IL_00ef: ldarg.0 + IL_00f0: ldflda ""System.Runtime.CompilerServices.AsyncTaskMethodBuilder C.
d__0.<>t__builder"" + IL_00f5: ldloc.s V_4 + IL_00f7: call ""void System.Runtime.CompilerServices.AsyncTaskMethodBuilder.SetException(System.Exception)"" + IL_00fc: nop + IL_00fd: leave.s IL_011a } // sequence point: } - IL_00f4: ldarg.0 - IL_00f5: ldc.i4.s -2 - IL_00f7: stfld ""int C.
d__0.<>1__state"" + IL_00ff: ldarg.0 + IL_0100: ldc.i4.s -2 + IL_0102: stfld ""int C.
d__0.<>1__state"" // sequence point: - IL_00fc: ldarg.0 - IL_00fd: ldnull - IL_00fe: stfld ""ICollection C.
d__0.5__1"" - IL_0103: ldarg.0 - IL_0104: ldflda ""System.Runtime.CompilerServices.AsyncTaskMethodBuilder C.
d__0.<>t__builder"" - IL_0109: call ""void System.Runtime.CompilerServices.AsyncTaskMethodBuilder.SetResult()"" - IL_010e: nop - IL_010f: ret + IL_0107: ldarg.0 + IL_0108: ldnull + IL_0109: stfld ""ICollection C.
d__0.5__1"" + IL_010e: ldarg.0 + IL_010f: ldflda ""System.Runtime.CompilerServices.AsyncTaskMethodBuilder C.
d__0.<>t__builder"" + IL_0114: call ""void System.Runtime.CompilerServices.AsyncTaskMethodBuilder.SetResult()"" + IL_0119: nop + IL_011a: ret }", sequencePoints: "C+
d__0.MoveNext", source: source); } diff --git a/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueStateMachineTests.cs b/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueStateMachineTests.cs index f90b37fa014a8..007e10002de4a 100644 --- a/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueStateMachineTests.cs +++ b/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/EditAndContinueStateMachineTests.cs @@ -6417,6 +6417,897 @@ .locals init (int V_0, }"); } + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/69805")] + public void UpdateAwaitForEach_AsyncDisposableEnumerator() + { + var source0 = MarkedSource(@" +using System.Collections.Generic; +using System.Threading.Tasks; + +class C +{ + async Task F() + { + await foreach (var x in Iterator()) + { + Body(1); + } + + End(); + } + + IAsyncEnumerable Iterator() => null; + static void Body(int x) {} + static void End() { } +} +"); + var source1 = MarkedSource(@" +using System.Collections.Generic; +using System.Threading.Tasks; + +class C +{ + async Task F() + { + await foreach (var x in Iterator()) + { + Body(2); + } + + End(); + } + + IAsyncEnumerable Iterator() => null; + static void Body(int x) { } + static void End() { } +}"); + var asyncStreamsTree = Parse( + AsyncStreamsTypes, options: (CSharpParseOptions)source0.Tree.Options, filename: "AsyncStreams.cs"); + + var compilation0 = CreateCompilationWithTasksExtensions(new[] { source0.Tree, asyncStreamsTree }, options: ComSafeDebugDll); + var compilation1 = compilation0.WithSource([source1.Tree, asyncStreamsTree]); + + var v0 = CompileAndVerify(compilation0); + v0.VerifyDiagnostics(); + var md0 = ModuleMetadata.CreateFromImage(v0.EmittedAssemblyData); + + var f0 = compilation0.GetMember("C.F"); + var f1 = compilation1.GetMember("C.F"); + + var generation0 = EmitBaseline.CreateInitialBaseline(md0, v0.CreateSymReader().GetEncMethodDebugInfo); + + v0.VerifyPdb("C.F", @" + + + + + + + + + + + + + + + + + + + + +", options: PdbValidationOptions.ExcludeDocuments); + + v0.VerifyMethodBody("C.d__0.System.Runtime.CompilerServices.IAsyncStateMachine.MoveNext", @" +{ + // Code size 477 (0x1dd) + .maxstack 3 + .locals init (int V_0, + System.Threading.CancellationToken V_1, + System.Runtime.CompilerServices.ValueTaskAwaiter V_2, + System.Threading.Tasks.ValueTask V_3, + C.d__0 V_4, + object V_5, + System.Runtime.CompilerServices.ValueTaskAwaiter V_6, + System.Threading.Tasks.ValueTask V_7, + System.Exception V_8) + // sequence point: + IL_0000: ldarg.0 + IL_0001: ldfld ""int C.d__0.<>1__state"" + IL_0006: stloc.0 + .try + { + // sequence point: + IL_0007: ldloc.0 + IL_0008: brfalse.s IL_0012 + IL_000a: br.s IL_000c + IL_000c: ldloc.0 + IL_000d: ldc.i4.1 + IL_000e: beq.s IL_0014 + IL_0010: br.s IL_0019 + IL_0012: br.s IL_0048 + IL_0014: br IL_0143 + // sequence point: { + IL_0019: nop + // sequence point: await foreach + IL_001a: nop + // sequence point: Iterator() + IL_001b: ldarg.0 + IL_001c: ldarg.0 + IL_001d: ldfld ""C C.d__0.<>4__this"" + IL_0022: call ""System.Collections.Generic.IAsyncEnumerable C.Iterator()"" + IL_0027: ldloca.s V_1 + IL_0029: initobj ""System.Threading.CancellationToken"" + IL_002f: ldloc.1 + IL_0030: callvirt ""System.Collections.Generic.IAsyncEnumerator System.Collections.Generic.IAsyncEnumerable.GetAsyncEnumerator(System.Threading.CancellationToken)"" + IL_0035: stfld ""System.Collections.Generic.IAsyncEnumerator C.d__0.<>s__1"" + // sequence point: + IL_003a: ldarg.0 + IL_003b: ldnull + IL_003c: stfld ""object C.d__0.<>s__2"" + IL_0041: ldarg.0 + IL_0042: ldc.i4.0 + IL_0043: stfld ""int C.d__0.<>s__3"" + // sequence point: + IL_0048: nop + .try + { + // sequence point: + IL_0049: ldloc.0 + IL_004a: brfalse.s IL_004e + IL_004c: br.s IL_0050 + IL_004e: br.s IL_00b1 + // sequence point: + IL_0050: br.s IL_006c + // sequence point: var x + IL_0052: ldarg.0 + IL_0053: ldarg.0 + IL_0054: ldfld ""System.Collections.Generic.IAsyncEnumerator C.d__0.<>s__1"" + IL_0059: callvirt ""int System.Collections.Generic.IAsyncEnumerator.Current.get"" + IL_005e: stfld ""int C.d__0.5__4"" + // sequence point: { + IL_0063: nop + // sequence point: Body(1); + IL_0064: ldc.i4.1 + IL_0065: call ""void C.Body(int)"" + IL_006a: nop + // sequence point: } + IL_006b: nop + // sequence point: in + IL_006c: ldarg.0 + IL_006d: ldfld ""System.Collections.Generic.IAsyncEnumerator C.d__0.<>s__1"" + IL_0072: callvirt ""System.Threading.Tasks.ValueTask System.Collections.Generic.IAsyncEnumerator.MoveNextAsync()"" + IL_0077: stloc.3 + IL_0078: ldloca.s V_3 + IL_007a: call ""System.Runtime.CompilerServices.ValueTaskAwaiter System.Threading.Tasks.ValueTask.GetAwaiter()"" + IL_007f: stloc.2 + // sequence point: + IL_0080: ldloca.s V_2 + IL_0082: call ""bool System.Runtime.CompilerServices.ValueTaskAwaiter.IsCompleted.get"" + IL_0087: brtrue.s IL_00cd + IL_0089: ldarg.0 + IL_008a: ldc.i4.0 + IL_008b: dup + IL_008c: stloc.0 + IL_008d: stfld ""int C.d__0.<>1__state"" + // async: yield + IL_0092: ldarg.0 + IL_0093: ldloc.2 + IL_0094: stfld ""System.Runtime.CompilerServices.ValueTaskAwaiter C.d__0.<>u__1"" + IL_0099: ldarg.0 + IL_009a: stloc.s V_4 + IL_009c: ldarg.0 + IL_009d: ldflda ""System.Runtime.CompilerServices.AsyncTaskMethodBuilder C.d__0.<>t__builder"" + IL_00a2: ldloca.s V_2 + IL_00a4: ldloca.s V_4 + IL_00a6: call ""void System.Runtime.CompilerServices.AsyncTaskMethodBuilder.AwaitUnsafeOnCompleted, C.d__0>(ref System.Runtime.CompilerServices.ValueTaskAwaiter, ref C.d__0)"" + IL_00ab: nop + IL_00ac: leave IL_01dc + // async: resume + IL_00b1: ldarg.0 + IL_00b2: ldfld ""System.Runtime.CompilerServices.ValueTaskAwaiter C.d__0.<>u__1"" + IL_00b7: stloc.2 + IL_00b8: ldarg.0 + IL_00b9: ldflda ""System.Runtime.CompilerServices.ValueTaskAwaiter C.d__0.<>u__1"" + IL_00be: initobj ""System.Runtime.CompilerServices.ValueTaskAwaiter"" + IL_00c4: ldarg.0 + IL_00c5: ldc.i4.m1 + IL_00c6: dup + IL_00c7: stloc.0 + IL_00c8: stfld ""int C.d__0.<>1__state"" + IL_00cd: ldarg.0 + IL_00ce: ldloca.s V_2 + IL_00d0: call ""bool System.Runtime.CompilerServices.ValueTaskAwaiter.GetResult()"" + IL_00d5: stfld ""bool C.d__0.<>s__5"" + IL_00da: ldarg.0 + IL_00db: ldfld ""bool C.d__0.<>s__5"" + IL_00e0: brtrue IL_0052 + // sequence point: + IL_00e5: leave.s IL_00f3 + } + catch object + { + // sequence point: + IL_00e7: stloc.s V_5 + IL_00e9: ldarg.0 + IL_00ea: ldloc.s V_5 + IL_00ec: stfld ""object C.d__0.<>s__2"" + IL_00f1: leave.s IL_00f3 + } + // sequence point: + IL_00f3: ldarg.0 + IL_00f4: ldfld ""System.Collections.Generic.IAsyncEnumerator C.d__0.<>s__1"" + IL_00f9: brfalse.s IL_0168 + IL_00fb: ldarg.0 + IL_00fc: ldfld ""System.Collections.Generic.IAsyncEnumerator C.d__0.<>s__1"" + IL_0101: callvirt ""System.Threading.Tasks.ValueTask System.IAsyncDisposable.DisposeAsync()"" + IL_0106: stloc.s V_7 + IL_0108: ldloca.s V_7 + IL_010a: call ""System.Runtime.CompilerServices.ValueTaskAwaiter System.Threading.Tasks.ValueTask.GetAwaiter()"" + IL_010f: stloc.s V_6 + // sequence point: + IL_0111: ldloca.s V_6 + IL_0113: call ""bool System.Runtime.CompilerServices.ValueTaskAwaiter.IsCompleted.get"" + IL_0118: brtrue.s IL_0160 + IL_011a: ldarg.0 + IL_011b: ldc.i4.1 + IL_011c: dup + IL_011d: stloc.0 + IL_011e: stfld ""int C.d__0.<>1__state"" + // async: yield + IL_0123: ldarg.0 + IL_0124: ldloc.s V_6 + IL_0126: stfld ""System.Runtime.CompilerServices.ValueTaskAwaiter C.d__0.<>u__2"" + IL_012b: ldarg.0 + IL_012c: stloc.s V_4 + IL_012e: ldarg.0 + IL_012f: ldflda ""System.Runtime.CompilerServices.AsyncTaskMethodBuilder C.d__0.<>t__builder"" + IL_0134: ldloca.s V_6 + IL_0136: ldloca.s V_4 + IL_0138: call ""void System.Runtime.CompilerServices.AsyncTaskMethodBuilder.AwaitUnsafeOnCompletedd__0>(ref System.Runtime.CompilerServices.ValueTaskAwaiter, ref C.d__0)"" + IL_013d: nop + IL_013e: leave IL_01dc + // async: resume + IL_0143: ldarg.0 + IL_0144: ldfld ""System.Runtime.CompilerServices.ValueTaskAwaiter C.d__0.<>u__2"" + IL_0149: stloc.s V_6 + IL_014b: ldarg.0 + IL_014c: ldflda ""System.Runtime.CompilerServices.ValueTaskAwaiter C.d__0.<>u__2"" + IL_0151: initobj ""System.Runtime.CompilerServices.ValueTaskAwaiter"" + IL_0157: ldarg.0 + IL_0158: ldc.i4.m1 + IL_0159: dup + IL_015a: stloc.0 + IL_015b: stfld ""int C.d__0.<>1__state"" + IL_0160: ldloca.s V_6 + IL_0162: call ""void System.Runtime.CompilerServices.ValueTaskAwaiter.GetResult()"" + IL_0167: nop + // sequence point: + IL_0168: ldarg.0 + IL_0169: ldfld ""object C.d__0.<>s__2"" + IL_016e: stloc.s V_5 + IL_0170: ldloc.s V_5 + IL_0172: brfalse.s IL_0191 + IL_0174: ldloc.s V_5 + IL_0176: isinst ""System.Exception"" + IL_017b: stloc.s V_8 + IL_017d: ldloc.s V_8 + IL_017f: brtrue.s IL_0184 + IL_0181: ldloc.s V_5 + IL_0183: throw + IL_0184: ldloc.s V_8 + IL_0186: call ""System.Runtime.ExceptionServices.ExceptionDispatchInfo System.Runtime.ExceptionServices.ExceptionDispatchInfo.Capture(System.Exception)"" + IL_018b: callvirt ""void System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()"" + IL_0190: nop + IL_0191: ldarg.0 + IL_0192: ldfld ""int C.d__0.<>s__3"" + IL_0197: pop + IL_0198: ldarg.0 + IL_0199: ldnull + IL_019a: stfld ""object C.d__0.<>s__2"" + IL_019f: ldarg.0 + IL_01a0: ldnull + IL_01a1: stfld ""System.Collections.Generic.IAsyncEnumerator C.d__0.<>s__1"" + // sequence point: End(); + IL_01a6: call ""void C.End()"" + IL_01ab: nop + IL_01ac: leave.s IL_01c8 + } + catch System.Exception + { + // sequence point: + IL_01ae: stloc.s V_8 + IL_01b0: ldarg.0 + IL_01b1: ldc.i4.s -2 + IL_01b3: stfld ""int C.d__0.<>1__state"" + IL_01b8: ldarg.0 + IL_01b9: ldflda ""System.Runtime.CompilerServices.AsyncTaskMethodBuilder C.d__0.<>t__builder"" + IL_01be: ldloc.s V_8 + IL_01c0: call ""void System.Runtime.CompilerServices.AsyncTaskMethodBuilder.SetException(System.Exception)"" + IL_01c5: nop + IL_01c6: leave.s IL_01dc + } + // sequence point: } + IL_01c8: ldarg.0 + IL_01c9: ldc.i4.s -2 + IL_01cb: stfld ""int C.d__0.<>1__state"" + // sequence point: + IL_01d0: ldarg.0 + IL_01d1: ldflda ""System.Runtime.CompilerServices.AsyncTaskMethodBuilder C.d__0.<>t__builder"" + IL_01d6: call ""void System.Runtime.CompilerServices.AsyncTaskMethodBuilder.SetResult()"" + IL_01db: nop + IL_01dc: ret +} +"); + + var diff1 = compilation1.EmitDifference( + generation0, + ImmutableArray.Create(SemanticEdit.Create(SemanticEditKind.Update, f0, f1, GetSyntaxMapFromMarkers(source0, source1), preserveLocalVariables: true))); + + diff1.VerifyIL("C.d__0.System.Runtime.CompilerServices.IAsyncStateMachine.MoveNext", @" +{ + // Code size 477 (0x1dd) + .maxstack 3 + .locals init (int V_0, + System.Threading.CancellationToken V_1, + System.Runtime.CompilerServices.ValueTaskAwaiter V_2, + System.Threading.Tasks.ValueTask V_3, + C.d__0 V_4, + object V_5, + System.Runtime.CompilerServices.ValueTaskAwaiter V_6, + System.Threading.Tasks.ValueTask V_7, + System.Exception V_8) + IL_0000: ldarg.0 + IL_0001: ldfld ""int C.d__0.<>1__state"" + IL_0006: stloc.0 + .try + { + IL_0007: ldloc.0 + IL_0008: brfalse.s IL_0012 + IL_000a: br.s IL_000c + IL_000c: ldloc.0 + IL_000d: ldc.i4.1 + IL_000e: beq.s IL_0014 + IL_0010: br.s IL_0019 + IL_0012: br.s IL_0048 + IL_0014: br IL_0143 + IL_0019: nop + IL_001a: nop + IL_001b: ldarg.0 + IL_001c: ldarg.0 + IL_001d: ldfld ""C C.d__0.<>4__this"" + IL_0022: call ""System.Collections.Generic.IAsyncEnumerable C.Iterator()"" + IL_0027: ldloca.s V_1 + IL_0029: initobj ""System.Threading.CancellationToken"" + IL_002f: ldloc.1 + IL_0030: callvirt ""System.Collections.Generic.IAsyncEnumerator System.Collections.Generic.IAsyncEnumerable.GetAsyncEnumerator(System.Threading.CancellationToken)"" + IL_0035: stfld ""System.Collections.Generic.IAsyncEnumerator C.d__0.<>s__1"" + IL_003a: ldarg.0 + IL_003b: ldnull + IL_003c: stfld ""object C.d__0.<>s__2"" + IL_0041: ldarg.0 + IL_0042: ldc.i4.0 + IL_0043: stfld ""int C.d__0.<>s__3"" + IL_0048: nop + .try + { + IL_0049: ldloc.0 + IL_004a: brfalse.s IL_004e + IL_004c: br.s IL_0050 + IL_004e: br.s IL_00b1 + IL_0050: br.s IL_006c + IL_0052: ldarg.0 + IL_0053: ldarg.0 + IL_0054: ldfld ""System.Collections.Generic.IAsyncEnumerator C.d__0.<>s__1"" + IL_0059: callvirt ""int System.Collections.Generic.IAsyncEnumerator.Current.get"" + IL_005e: stfld ""int C.d__0.5__4"" + IL_0063: nop + IL_0064: ldc.i4.2 + IL_0065: call ""void C.Body(int)"" + IL_006a: nop + IL_006b: nop + IL_006c: ldarg.0 + IL_006d: ldfld ""System.Collections.Generic.IAsyncEnumerator C.d__0.<>s__1"" + IL_0072: callvirt ""System.Threading.Tasks.ValueTask System.Collections.Generic.IAsyncEnumerator.MoveNextAsync()"" + IL_0077: stloc.3 + IL_0078: ldloca.s V_3 + IL_007a: call ""System.Runtime.CompilerServices.ValueTaskAwaiter System.Threading.Tasks.ValueTask.GetAwaiter()"" + IL_007f: stloc.2 + IL_0080: ldloca.s V_2 + IL_0082: call ""bool System.Runtime.CompilerServices.ValueTaskAwaiter.IsCompleted.get"" + IL_0087: brtrue.s IL_00cd + IL_0089: ldarg.0 + IL_008a: ldc.i4.0 + IL_008b: dup + IL_008c: stloc.0 + IL_008d: stfld ""int C.d__0.<>1__state"" + IL_0092: ldarg.0 + IL_0093: ldloc.2 + IL_0094: stfld ""System.Runtime.CompilerServices.ValueTaskAwaiter C.d__0.<>u__1"" + IL_0099: ldarg.0 + IL_009a: stloc.s V_4 + IL_009c: ldarg.0 + IL_009d: ldflda ""System.Runtime.CompilerServices.AsyncTaskMethodBuilder C.d__0.<>t__builder"" + IL_00a2: ldloca.s V_2 + IL_00a4: ldloca.s V_4 + IL_00a6: call ""void System.Runtime.CompilerServices.AsyncTaskMethodBuilder.AwaitUnsafeOnCompleted, C.d__0>(ref System.Runtime.CompilerServices.ValueTaskAwaiter, ref C.d__0)"" + IL_00ab: nop + IL_00ac: leave IL_01dc + IL_00b1: ldarg.0 + IL_00b2: ldfld ""System.Runtime.CompilerServices.ValueTaskAwaiter C.d__0.<>u__1"" + IL_00b7: stloc.2 + IL_00b8: ldarg.0 + IL_00b9: ldflda ""System.Runtime.CompilerServices.ValueTaskAwaiter C.d__0.<>u__1"" + IL_00be: initobj ""System.Runtime.CompilerServices.ValueTaskAwaiter"" + IL_00c4: ldarg.0 + IL_00c5: ldc.i4.m1 + IL_00c6: dup + IL_00c7: stloc.0 + IL_00c8: stfld ""int C.d__0.<>1__state"" + IL_00cd: ldarg.0 + IL_00ce: ldloca.s V_2 + IL_00d0: call ""bool System.Runtime.CompilerServices.ValueTaskAwaiter.GetResult()"" + IL_00d5: stfld ""bool C.d__0.<>s__5"" + IL_00da: ldarg.0 + IL_00db: ldfld ""bool C.d__0.<>s__5"" + IL_00e0: brtrue IL_0052 + IL_00e5: leave.s IL_00f3 + } + catch object + { + IL_00e7: stloc.s V_5 + IL_00e9: ldarg.0 + IL_00ea: ldloc.s V_5 + IL_00ec: stfld ""object C.d__0.<>s__2"" + IL_00f1: leave.s IL_00f3 + } + IL_00f3: ldarg.0 + IL_00f4: ldfld ""System.Collections.Generic.IAsyncEnumerator C.d__0.<>s__1"" + IL_00f9: brfalse.s IL_0168 + IL_00fb: ldarg.0 + IL_00fc: ldfld ""System.Collections.Generic.IAsyncEnumerator C.d__0.<>s__1"" + IL_0101: callvirt ""System.Threading.Tasks.ValueTask System.IAsyncDisposable.DisposeAsync()"" + IL_0106: stloc.s V_7 + IL_0108: ldloca.s V_7 + IL_010a: call ""System.Runtime.CompilerServices.ValueTaskAwaiter System.Threading.Tasks.ValueTask.GetAwaiter()"" + IL_010f: stloc.s V_6 + IL_0111: ldloca.s V_6 + IL_0113: call ""bool System.Runtime.CompilerServices.ValueTaskAwaiter.IsCompleted.get"" + IL_0118: brtrue.s IL_0160 + IL_011a: ldarg.0 + IL_011b: ldc.i4.1 + IL_011c: dup + IL_011d: stloc.0 + IL_011e: stfld ""int C.d__0.<>1__state"" + IL_0123: ldarg.0 + IL_0124: ldloc.s V_6 + IL_0126: stfld ""System.Runtime.CompilerServices.ValueTaskAwaiter C.d__0.<>u__2"" + IL_012b: ldarg.0 + IL_012c: stloc.s V_4 + IL_012e: ldarg.0 + IL_012f: ldflda ""System.Runtime.CompilerServices.AsyncTaskMethodBuilder C.d__0.<>t__builder"" + IL_0134: ldloca.s V_6 + IL_0136: ldloca.s V_4 + IL_0138: call ""void System.Runtime.CompilerServices.AsyncTaskMethodBuilder.AwaitUnsafeOnCompletedd__0>(ref System.Runtime.CompilerServices.ValueTaskAwaiter, ref C.d__0)"" + IL_013d: nop + IL_013e: leave IL_01dc + IL_0143: ldarg.0 + IL_0144: ldfld ""System.Runtime.CompilerServices.ValueTaskAwaiter C.d__0.<>u__2"" + IL_0149: stloc.s V_6 + IL_014b: ldarg.0 + IL_014c: ldflda ""System.Runtime.CompilerServices.ValueTaskAwaiter C.d__0.<>u__2"" + IL_0151: initobj ""System.Runtime.CompilerServices.ValueTaskAwaiter"" + IL_0157: ldarg.0 + IL_0158: ldc.i4.m1 + IL_0159: dup + IL_015a: stloc.0 + IL_015b: stfld ""int C.d__0.<>1__state"" + IL_0160: ldloca.s V_6 + IL_0162: call ""void System.Runtime.CompilerServices.ValueTaskAwaiter.GetResult()"" + IL_0167: nop + IL_0168: ldarg.0 + IL_0169: ldfld ""object C.d__0.<>s__2"" + IL_016e: stloc.s V_5 + IL_0170: ldloc.s V_5 + IL_0172: brfalse.s IL_0191 + IL_0174: ldloc.s V_5 + IL_0176: isinst ""System.Exception"" + IL_017b: stloc.s V_8 + IL_017d: ldloc.s V_8 + IL_017f: brtrue.s IL_0184 + IL_0181: ldloc.s V_5 + IL_0183: throw + IL_0184: ldloc.s V_8 + IL_0186: call ""System.Runtime.ExceptionServices.ExceptionDispatchInfo System.Runtime.ExceptionServices.ExceptionDispatchInfo.Capture(System.Exception)"" + IL_018b: callvirt ""void System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()"" + IL_0190: nop + IL_0191: ldarg.0 + IL_0192: ldfld ""int C.d__0.<>s__3"" + IL_0197: pop + IL_0198: ldarg.0 + IL_0199: ldnull + IL_019a: stfld ""object C.d__0.<>s__2"" + IL_019f: ldarg.0 + IL_01a0: ldnull + IL_01a1: stfld ""System.Collections.Generic.IAsyncEnumerator C.d__0.<>s__1"" + IL_01a6: call ""void C.End()"" + IL_01ab: nop + IL_01ac: leave.s IL_01c8 + } + catch System.Exception + { + IL_01ae: stloc.s V_8 + IL_01b0: ldarg.0 + IL_01b1: ldc.i4.s -2 + IL_01b3: stfld ""int C.d__0.<>1__state"" + IL_01b8: ldarg.0 + IL_01b9: ldflda ""System.Runtime.CompilerServices.AsyncTaskMethodBuilder C.d__0.<>t__builder"" + IL_01be: ldloc.s V_8 + IL_01c0: call ""void System.Runtime.CompilerServices.AsyncTaskMethodBuilder.SetException(System.Exception)"" + IL_01c5: nop + IL_01c6: leave.s IL_01dc + } + IL_01c8: ldarg.0 + IL_01c9: ldc.i4.s -2 + IL_01cb: stfld ""int C.d__0.<>1__state"" + IL_01d0: ldarg.0 + IL_01d1: ldflda ""System.Runtime.CompilerServices.AsyncTaskMethodBuilder C.d__0.<>t__builder"" + IL_01d6: call ""void System.Runtime.CompilerServices.AsyncTaskMethodBuilder.SetResult()"" + IL_01db: nop + IL_01dc: ret +}"); + } + + [Fact] + [WorkItem("https://github.com/dotnet/roslyn/issues/69805")] + public void UpdateAwaitForEach_NonDisposableEnumerator() + { + var source0 = MarkedSource(@" +using System.Collections.Generic; +using System.Threading.Tasks; + +class C +{ + async Task F() + { + await foreach (var x in Iterator()) + { + Body(1); + } + + End(); + } + + IAsyncEnumerable Iterator() => null; + static void Body(int x) {} + static void End() { } +} +"); + var source1 = MarkedSource(@" +using System.Collections.Generic; +using System.Threading.Tasks; + +class C +{ + async Task F() + { + await foreach (var x in Iterator()) + { + Body(2); + } + + End(); + } + + IAsyncEnumerable Iterator() => null; + static void Body(int x) { } + static void End() { } +}"); + var asyncStreamsTree = Parse( + NonDisposableAsyncEnumeratorDefinition + CommonAsyncStreamsTypes, options: (CSharpParseOptions)source0.Tree.Options, filename: "AsyncStreams.cs"); + + var compilation0 = CreateCompilationWithTasksExtensions(new[] { source0.Tree, asyncStreamsTree }, options: ComSafeDebugDll); + var compilation1 = compilation0.WithSource(new[] { source1.Tree, asyncStreamsTree }); + + var v0 = CompileAndVerify(compilation0); + v0.VerifyDiagnostics(); + var md0 = ModuleMetadata.CreateFromImage(v0.EmittedAssemblyData); + + var f0 = compilation0.GetMember("C.F"); + var f1 = compilation1.GetMember("C.F"); + + var generation0 = EmitBaseline.CreateInitialBaseline(md0, v0.CreateSymReader().GetEncMethodDebugInfo); + + // Both states are allocated eventhough only a single await is emitted: + v0.VerifyPdb("C.F", @" + + + + + + + + + + + + + + + + + + +", options: PdbValidationOptions.ExcludeDocuments); + + v0.VerifyMethodBody("C.d__0.System.Runtime.CompilerServices.IAsyncStateMachine.MoveNext", @" +{ + // Code size 266 (0x10a) + .maxstack 3 + .locals init (int V_0, + System.Threading.CancellationToken V_1, + System.Runtime.CompilerServices.ValueTaskAwaiter V_2, + System.Threading.Tasks.ValueTask V_3, + C.d__0 V_4, + System.Exception V_5) + // sequence point: + IL_0000: ldarg.0 + IL_0001: ldfld ""int C.d__0.<>1__state"" + IL_0006: stloc.0 + .try + { + // sequence point: + IL_0007: ldloc.0 + IL_0008: brfalse.s IL_0012 + IL_000a: br.s IL_000c + IL_000c: ldloc.0 + IL_000d: ldc.i4.1 + IL_000e: beq.s IL_0017 + IL_0010: br.s IL_0019 + IL_0012: br IL_0098 + IL_0017: br.s IL_0098 + // sequence point: { + IL_0019: nop + // sequence point: await foreach + IL_001a: nop + // sequence point: Iterator() + IL_001b: ldarg.0 + IL_001c: ldarg.0 + IL_001d: ldfld ""C C.d__0.<>4__this"" + IL_0022: call ""System.Collections.Generic.IAsyncEnumerable C.Iterator()"" + IL_0027: ldloca.s V_1 + IL_0029: initobj ""System.Threading.CancellationToken"" + IL_002f: ldloc.1 + IL_0030: callvirt ""System.Collections.Generic.IAsyncEnumerator System.Collections.Generic.IAsyncEnumerable.GetAsyncEnumerator(System.Threading.CancellationToken)"" + IL_0035: stfld ""System.Collections.Generic.IAsyncEnumerator C.d__0.<>s__1"" + // sequence point: + IL_003a: br.s IL_0056 + // sequence point: var x + IL_003c: ldarg.0 + IL_003d: ldarg.0 + IL_003e: ldfld ""System.Collections.Generic.IAsyncEnumerator C.d__0.<>s__1"" + IL_0043: callvirt ""int System.Collections.Generic.IAsyncEnumerator.Current.get"" + IL_0048: stfld ""int C.d__0.5__2"" + // sequence point: { + IL_004d: nop + // sequence point: Body(1); + IL_004e: ldc.i4.1 + IL_004f: call ""void C.Body(int)"" + IL_0054: nop + // sequence point: } + IL_0055: nop + // sequence point: in + IL_0056: ldarg.0 + IL_0057: ldfld ""System.Collections.Generic.IAsyncEnumerator C.d__0.<>s__1"" + IL_005c: callvirt ""System.Threading.Tasks.ValueTask System.Collections.Generic.IAsyncEnumerator.MoveNextAsync()"" + IL_0061: stloc.3 + IL_0062: ldloca.s V_3 + IL_0064: call ""System.Runtime.CompilerServices.ValueTaskAwaiter System.Threading.Tasks.ValueTask.GetAwaiter()"" + IL_0069: stloc.2 + // sequence point: + IL_006a: ldloca.s V_2 + IL_006c: call ""bool System.Runtime.CompilerServices.ValueTaskAwaiter.IsCompleted.get"" + IL_0071: brtrue.s IL_00b4 + IL_0073: ldarg.0 + IL_0074: ldc.i4.0 + IL_0075: dup + IL_0076: stloc.0 + IL_0077: stfld ""int C.d__0.<>1__state"" + // async: yield + IL_007c: ldarg.0 + IL_007d: ldloc.2 + IL_007e: stfld ""System.Runtime.CompilerServices.ValueTaskAwaiter C.d__0.<>u__1"" + IL_0083: ldarg.0 + IL_0084: stloc.s V_4 + IL_0086: ldarg.0 + IL_0087: ldflda ""System.Runtime.CompilerServices.AsyncTaskMethodBuilder C.d__0.<>t__builder"" + IL_008c: ldloca.s V_2 + IL_008e: ldloca.s V_4 + IL_0090: call ""void System.Runtime.CompilerServices.AsyncTaskMethodBuilder.AwaitUnsafeOnCompleted, C.d__0>(ref System.Runtime.CompilerServices.ValueTaskAwaiter, ref C.d__0)"" + IL_0095: nop + IL_0096: leave.s IL_0109 + // async: resume + IL_0098: ldarg.0 + IL_0099: ldfld ""System.Runtime.CompilerServices.ValueTaskAwaiter C.d__0.<>u__1"" + IL_009e: stloc.2 + IL_009f: ldarg.0 + IL_00a0: ldflda ""System.Runtime.CompilerServices.ValueTaskAwaiter C.d__0.<>u__1"" + IL_00a5: initobj ""System.Runtime.CompilerServices.ValueTaskAwaiter"" + IL_00ab: ldarg.0 + IL_00ac: ldc.i4.m1 + IL_00ad: dup + IL_00ae: stloc.0 + IL_00af: stfld ""int C.d__0.<>1__state"" + IL_00b4: ldarg.0 + IL_00b5: ldloca.s V_2 + IL_00b7: call ""bool System.Runtime.CompilerServices.ValueTaskAwaiter.GetResult()"" + IL_00bc: stfld ""bool C.d__0.<>s__3"" + IL_00c1: ldarg.0 + IL_00c2: ldfld ""bool C.d__0.<>s__3"" + IL_00c7: brtrue IL_003c + IL_00cc: ldarg.0 + IL_00cd: ldnull + IL_00ce: stfld ""System.Collections.Generic.IAsyncEnumerator C.d__0.<>s__1"" + // sequence point: End(); + IL_00d3: call ""void C.End()"" + IL_00d8: nop + IL_00d9: leave.s IL_00f5 + } + catch System.Exception + { + // sequence point: + IL_00db: stloc.s V_5 + IL_00dd: ldarg.0 + IL_00de: ldc.i4.s -2 + IL_00e0: stfld ""int C.d__0.<>1__state"" + IL_00e5: ldarg.0 + IL_00e6: ldflda ""System.Runtime.CompilerServices.AsyncTaskMethodBuilder C.d__0.<>t__builder"" + IL_00eb: ldloc.s V_5 + IL_00ed: call ""void System.Runtime.CompilerServices.AsyncTaskMethodBuilder.SetException(System.Exception)"" + IL_00f2: nop + IL_00f3: leave.s IL_0109 + } + // sequence point: } + IL_00f5: ldarg.0 + IL_00f6: ldc.i4.s -2 + IL_00f8: stfld ""int C.d__0.<>1__state"" + // sequence point: + IL_00fd: ldarg.0 + IL_00fe: ldflda ""System.Runtime.CompilerServices.AsyncTaskMethodBuilder C.d__0.<>t__builder"" + IL_0103: call ""void System.Runtime.CompilerServices.AsyncTaskMethodBuilder.SetResult()"" + IL_0108: nop + IL_0109: ret +}"); + + var diff1 = compilation1.EmitDifference( + generation0, + ImmutableArray.Create(SemanticEdit.Create(SemanticEditKind.Update, f0, f1, GetSyntaxMapFromMarkers(source0, source1), preserveLocalVariables: true))); + + diff1.VerifyIL("C.d__0.System.Runtime.CompilerServices.IAsyncStateMachine.MoveNext", @" +{ + // Code size 266 (0x10a) + .maxstack 3 + .locals init (int V_0, + System.Threading.CancellationToken V_1, + System.Runtime.CompilerServices.ValueTaskAwaiter V_2, + System.Threading.Tasks.ValueTask V_3, + C.d__0 V_4, + System.Exception V_5) + IL_0000: ldarg.0 + IL_0001: ldfld ""int C.d__0.<>1__state"" + IL_0006: stloc.0 + .try + { + IL_0007: ldloc.0 + IL_0008: brfalse.s IL_0012 + IL_000a: br.s IL_000c + IL_000c: ldloc.0 + IL_000d: ldc.i4.1 + IL_000e: beq.s IL_0017 + IL_0010: br.s IL_0019 + IL_0012: br IL_0098 + IL_0017: br.s IL_0098 + IL_0019: nop + IL_001a: nop + IL_001b: ldarg.0 + IL_001c: ldarg.0 + IL_001d: ldfld ""C C.d__0.<>4__this"" + IL_0022: call ""System.Collections.Generic.IAsyncEnumerable C.Iterator()"" + IL_0027: ldloca.s V_1 + IL_0029: initobj ""System.Threading.CancellationToken"" + IL_002f: ldloc.1 + IL_0030: callvirt ""System.Collections.Generic.IAsyncEnumerator System.Collections.Generic.IAsyncEnumerable.GetAsyncEnumerator(System.Threading.CancellationToken)"" + IL_0035: stfld ""System.Collections.Generic.IAsyncEnumerator C.d__0.<>s__1"" + IL_003a: br.s IL_0056 + IL_003c: ldarg.0 + IL_003d: ldarg.0 + IL_003e: ldfld ""System.Collections.Generic.IAsyncEnumerator C.d__0.<>s__1"" + IL_0043: callvirt ""int System.Collections.Generic.IAsyncEnumerator.Current.get"" + IL_0048: stfld ""int C.d__0.5__2"" + IL_004d: nop + IL_004e: ldc.i4.2 + IL_004f: call ""void C.Body(int)"" + IL_0054: nop + IL_0055: nop + IL_0056: ldarg.0 + IL_0057: ldfld ""System.Collections.Generic.IAsyncEnumerator C.d__0.<>s__1"" + IL_005c: callvirt ""System.Threading.Tasks.ValueTask System.Collections.Generic.IAsyncEnumerator.MoveNextAsync()"" + IL_0061: stloc.3 + IL_0062: ldloca.s V_3 + IL_0064: call ""System.Runtime.CompilerServices.ValueTaskAwaiter System.Threading.Tasks.ValueTask.GetAwaiter()"" + IL_0069: stloc.2 + IL_006a: ldloca.s V_2 + IL_006c: call ""bool System.Runtime.CompilerServices.ValueTaskAwaiter.IsCompleted.get"" + IL_0071: brtrue.s IL_00b4 + IL_0073: ldarg.0 + IL_0074: ldc.i4.0 + IL_0075: dup + IL_0076: stloc.0 + IL_0077: stfld ""int C.d__0.<>1__state"" + IL_007c: ldarg.0 + IL_007d: ldloc.2 + IL_007e: stfld ""System.Runtime.CompilerServices.ValueTaskAwaiter C.d__0.<>u__1"" + IL_0083: ldarg.0 + IL_0084: stloc.s V_4 + IL_0086: ldarg.0 + IL_0087: ldflda ""System.Runtime.CompilerServices.AsyncTaskMethodBuilder C.d__0.<>t__builder"" + IL_008c: ldloca.s V_2 + IL_008e: ldloca.s V_4 + IL_0090: call ""void System.Runtime.CompilerServices.AsyncTaskMethodBuilder.AwaitUnsafeOnCompleted, C.d__0>(ref System.Runtime.CompilerServices.ValueTaskAwaiter, ref C.d__0)"" + IL_0095: nop + IL_0096: leave.s IL_0109 + IL_0098: ldarg.0 + IL_0099: ldfld ""System.Runtime.CompilerServices.ValueTaskAwaiter C.d__0.<>u__1"" + IL_009e: stloc.2 + IL_009f: ldarg.0 + IL_00a0: ldflda ""System.Runtime.CompilerServices.ValueTaskAwaiter C.d__0.<>u__1"" + IL_00a5: initobj ""System.Runtime.CompilerServices.ValueTaskAwaiter"" + IL_00ab: ldarg.0 + IL_00ac: ldc.i4.m1 + IL_00ad: dup + IL_00ae: stloc.0 + IL_00af: stfld ""int C.d__0.<>1__state"" + IL_00b4: ldarg.0 + IL_00b5: ldloca.s V_2 + IL_00b7: call ""bool System.Runtime.CompilerServices.ValueTaskAwaiter.GetResult()"" + IL_00bc: stfld ""bool C.d__0.<>s__3"" + IL_00c1: ldarg.0 + IL_00c2: ldfld ""bool C.d__0.<>s__3"" + IL_00c7: brtrue IL_003c + IL_00cc: ldarg.0 + IL_00cd: ldnull + IL_00ce: stfld ""System.Collections.Generic.IAsyncEnumerator C.d__0.<>s__1"" + IL_00d3: call ""void C.End()"" + IL_00d8: nop + IL_00d9: leave.s IL_00f5 + } + catch System.Exception + { + IL_00db: stloc.s V_5 + IL_00dd: ldarg.0 + IL_00de: ldc.i4.s -2 + IL_00e0: stfld ""int C.d__0.<>1__state"" + IL_00e5: ldarg.0 + IL_00e6: ldflda ""System.Runtime.CompilerServices.AsyncTaskMethodBuilder C.d__0.<>t__builder"" + IL_00eb: ldloc.s V_5 + IL_00ed: call ""void System.Runtime.CompilerServices.AsyncTaskMethodBuilder.SetException(System.Exception)"" + IL_00f2: nop + IL_00f3: leave.s IL_0109 + } + IL_00f5: ldarg.0 + IL_00f6: ldc.i4.s -2 + IL_00f8: stfld ""int C.d__0.<>1__state"" + IL_00fd: ldarg.0 + IL_00fe: ldflda ""System.Runtime.CompilerServices.AsyncTaskMethodBuilder C.d__0.<>t__builder"" + IL_0103: call ""void System.Runtime.CompilerServices.AsyncTaskMethodBuilder.SetResult()"" + IL_0108: nop + IL_0109: ret +} +"); + } + [Fact] public void HoistedVariables_MultipleGenerations() { diff --git a/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/LocalSlotMappingTests.cs b/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/LocalSlotMappingTests.cs index b8fd5ae36ac46..9924d67ae135e 100644 --- a/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/LocalSlotMappingTests.cs +++ b/src/Compilers/CSharp/Test/Emit/Emit/EditAndContinue/LocalSlotMappingTests.cs @@ -767,9 +767,6 @@ static async Task M() v0.VerifyPdb("C.M", @" - - - @@ -783,14 +780,15 @@ static async Task M() - + -"); +", options: PdbValidationOptions.ExcludeDocuments); + v0.VerifyLocalSignature("C.d__1.System.Runtime.CompilerServices.IAsyncStateMachine.MoveNext", @" .locals init (int V_0, object V_1, @@ -860,9 +858,6 @@ static async Task M() v0.VerifyPdb("C.M", @" - - - @@ -876,14 +871,15 @@ static async Task M() - + -"); +", options: PdbValidationOptions.ExcludeDocuments); + v0.VerifyLocalSignature("C.d__1.System.Runtime.CompilerServices.IAsyncStateMachine.MoveNext", @" .locals init (int V_0, object V_1, diff --git a/src/Compilers/Core/CodeAnalysisTest/Emit/CustomDebugInfoTests.cs b/src/Compilers/Core/CodeAnalysisTest/Emit/CustomDebugInfoTests.cs index 43c4da4123e23..1f5c94b3e7579 100644 --- a/src/Compilers/Core/CodeAnalysisTest/Emit/CustomDebugInfoTests.cs +++ b/src/Compilers/Core/CodeAnalysisTest/Emit/CustomDebugInfoTests.cs @@ -4,19 +4,15 @@ #nullable disable -extern alias PDB; - using System; using System.Collections.Immutable; using System.IO; using System.Linq; -using System.Reflection; using System.Reflection.Metadata; using System.Reflection.Metadata.Ecma335; using Microsoft.CodeAnalysis.CodeGen; using Microsoft.CodeAnalysis.Debugging; using Microsoft.CodeAnalysis.Emit; -using PDB::Microsoft.CodeAnalysis; using Roslyn.Test.Utilities; using Xunit; @@ -294,6 +290,56 @@ public void EditAndContinueLambdaAndClosureMap_NoLambdas() AssertEx.Equal(lambdas, deserialized.Lambdas); } + [Fact] + public void StateMachineStateDebugInfo() + { + var cmw = new BlobBuilder(); + + var info = new EditAndContinueMethodDebugInformation( + methodOrdinal: 1, + localSlots: ImmutableArray.Empty, + closures: ImmutableArray.Empty, + lambdas: ImmutableArray.Empty, + stateMachineStates: ImmutableArray.Create( + new StateMachineStateDebugInfo(syntaxOffset: 0x10, new AwaitDebugId(2), (StateMachineState)0), + new StateMachineStateDebugInfo(syntaxOffset: 0x30, new AwaitDebugId(0), (StateMachineState)5), + new StateMachineStateDebugInfo(syntaxOffset: 0x10, new AwaitDebugId(0), (StateMachineState)1), + new StateMachineStateDebugInfo(syntaxOffset: 0x20, new AwaitDebugId(0), (StateMachineState)3), + new StateMachineStateDebugInfo(syntaxOffset: 0x10, new AwaitDebugId(1), (StateMachineState)2), + new StateMachineStateDebugInfo(syntaxOffset: 0x20, new AwaitDebugId(1), (StateMachineState)4) + )); + + info.SerializeStateMachineStates(cmw); + + var bytes = cmw.ToImmutableArray(); + AssertEx.Equal(new byte[] { 0x06, 0x00, 0x02, 0x10, 0x04, 0x10, 0x00, 0x10, 0x06, 0x20, 0x08, 0x20, 0x0A, 0x30 }, bytes); + + var deserialized = EditAndContinueMethodDebugInformation.Create( + compressedSlotMap: ImmutableArray.Empty, + compressedLambdaMap: ImmutableArray.Empty, + compressedStateMachineStateMap: bytes).StateMachineStates; + + AssertEx.Equal(new[] + { + new StateMachineStateDebugInfo(syntaxOffset: 0x10, new AwaitDebugId(0), (StateMachineState)1), + new StateMachineStateDebugInfo(syntaxOffset: 0x10, new AwaitDebugId(1), (StateMachineState)2), + new StateMachineStateDebugInfo(syntaxOffset: 0x10, new AwaitDebugId(2), (StateMachineState)0), + new StateMachineStateDebugInfo(syntaxOffset: 0x20, new AwaitDebugId(0), (StateMachineState)3), + new StateMachineStateDebugInfo(syntaxOffset: 0x20, new AwaitDebugId(1), (StateMachineState)4), + new StateMachineStateDebugInfo(syntaxOffset: 0x30, new AwaitDebugId(0), (StateMachineState)5), + }, deserialized); + } + + [Fact] + public void StateMachineStateDebugInfo_BadData() + { + // not sorted: + Assert.Throws(() => EditAndContinueMethodDebugInformation.Create( + compressedSlotMap: ImmutableArray.Empty, + compressedLambdaMap: ImmutableArray.Empty, + compressedStateMachineStateMap: ImmutableArray.Create(0x06, 0x00, 0x02, 0x20, 0x04, 0x10, 0x00, 0x10, 0x06, 0x20, 0x08, 0x20, 0x0A, 0x30))); + } + [Fact] public void EncCdiAlignment() { diff --git a/src/Compilers/Core/Portable/CodeGen/AwaitDebugId.cs b/src/Compilers/Core/Portable/CodeGen/AwaitDebugId.cs new file mode 100644 index 0000000000000..faa000da3776e --- /dev/null +++ b/src/Compilers/Core/Portable/CodeGen/AwaitDebugId.cs @@ -0,0 +1,14 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +namespace Microsoft.CodeAnalysis.CodeGen; + +/// +/// Identifies a specific await within a set of awaits generated for a syntax node. +/// +/// If multiple await expressions are produced for the same syntax node EnC needs to know how they map to specific async calls. +/// For example, `await foreach` generates two awaits -- one for MoveNextAsync ( is 0) +/// and the other for DisposeAsync ( is 1). +/// +internal readonly record struct AwaitDebugId(byte RelativeStateOrdinal); diff --git a/src/Compilers/Core/Portable/CodeGen/DebugId.cs b/src/Compilers/Core/Portable/CodeGen/DebugId.cs index 1667637f552ee..2459499f7e787 100644 --- a/src/Compilers/Core/Portable/CodeGen/DebugId.cs +++ b/src/Compilers/Core/Portable/CodeGen/DebugId.cs @@ -16,7 +16,7 @@ namespace Microsoft.CodeAnalysis.CodeGen /// For user defined methods the ordinal is included in Custom Debug Information record attached to the method. /// [DebuggerDisplay("{GetDebuggerDisplay(), nq}")] - internal struct DebugId : IEquatable + internal readonly struct DebugId : IEquatable { public const int UndefinedOrdinal = -1; diff --git a/src/Compilers/Core/Portable/CodeGen/StateMachineStateDebugInfo.cs b/src/Compilers/Core/Portable/CodeGen/StateMachineStateDebugInfo.cs index e4196126666fb..17512e66c9815 100644 --- a/src/Compilers/Core/Portable/CodeGen/StateMachineStateDebugInfo.cs +++ b/src/Compilers/Core/Portable/CodeGen/StateMachineStateDebugInfo.cs @@ -9,16 +9,11 @@ namespace Microsoft.CodeAnalysis.CodeGen; -internal readonly struct StateMachineStateDebugInfo +internal readonly struct StateMachineStateDebugInfo(int syntaxOffset, AwaitDebugId awaitId, StateMachineState stateNumber) { - public readonly int SyntaxOffset; - public readonly StateMachineState StateNumber; - - public StateMachineStateDebugInfo(int syntaxOffset, StateMachineState stateNumber) - { - SyntaxOffset = syntaxOffset; - StateNumber = stateNumber; - } + public readonly int SyntaxOffset = syntaxOffset; + public readonly AwaitDebugId AwaitId = awaitId; + public readonly StateMachineState StateNumber = stateNumber; } /// diff --git a/src/Compilers/Core/Portable/CodeGen/VariableSlotAllocator.cs b/src/Compilers/Core/Portable/CodeGen/VariableSlotAllocator.cs index 557bffc19b857..c7400a206f650 100644 --- a/src/Compilers/Core/Portable/CodeGen/VariableSlotAllocator.cs +++ b/src/Compilers/Core/Portable/CodeGen/VariableSlotAllocator.cs @@ -92,12 +92,10 @@ public abstract bool TryGetPreviousHoistedLocalSlotIndex( /// For a given node associated with entering a state of a state machine in the new compilation, /// returns the ordinal of the corresponding state in the previous version of the state machine. /// + /// Await expression, await foreach statement, yield return statement, or try block syntax node. /// /// True if there is a corresponding node in the previous code version that matches the given . /// - /// - /// is an await expression, yield return statement, or try block syntax node. - /// - public abstract bool TryGetPreviousStateMachineState(SyntaxNode syntax, out StateMachineState state); + public abstract bool TryGetPreviousStateMachineState(SyntaxNode syntax, AwaitDebugId awaitId, out StateMachineState state); } } diff --git a/src/Compilers/Core/Portable/Emit/EditAndContinue/DefinitionMap.cs b/src/Compilers/Core/Portable/Emit/EditAndContinue/DefinitionMap.cs index cee83a6740a59..9353f02b08e2c 100644 --- a/src/Compilers/Core/Portable/Emit/EditAndContinue/DefinitionMap.cs +++ b/src/Compilers/Core/Portable/Emit/EditAndContinue/DefinitionMap.cs @@ -172,7 +172,7 @@ protected abstract void GetStateMachineFieldMapFromMetadata( IReadOnlyDictionary? awaiterMap = null; IReadOnlyDictionary>? lambdaMap = null; IReadOnlyDictionary? closureMap = null; - IReadOnlyDictionary? stateMachineStateMap = null; + IReadOnlyDictionary<(int syntaxOffset, AwaitDebugId debugId), StateMachineState>? stateMachineStateMap = null; StateMachineState? firstUnusedIncreasingStateMachineState = null; StateMachineState? firstUnusedDecreasingStateMachineState = null; @@ -388,11 +388,11 @@ private static void MakeLambdaAndClosureMaps( private static void MakeStateMachineStateMap( ImmutableArray debugInfos, - out IReadOnlyDictionary? map) + out IReadOnlyDictionary<(int syntaxOffset, AwaitDebugId debugId), StateMachineState>? map) { map = debugInfos.IsDefault ? null : - debugInfos.ToDictionary(entry => entry.SyntaxOffset, entry => entry.StateNumber); + debugInfos.ToDictionary(entry => (entry.SyntaxOffset, entry.AwaitId), entry => entry.StateNumber); } private static void GetStateMachineFieldMapFromPreviousCompilation( diff --git a/src/Compilers/Core/Portable/Emit/EditAndContinue/EncVariableSlotAllocator.cs b/src/Compilers/Core/Portable/Emit/EditAndContinue/EncVariableSlotAllocator.cs index d2761d6eb5344..20958f7c1beb8 100644 --- a/src/Compilers/Core/Portable/Emit/EditAndContinue/EncVariableSlotAllocator.cs +++ b/src/Compilers/Core/Portable/Emit/EditAndContinue/EncVariableSlotAllocator.cs @@ -35,7 +35,7 @@ internal sealed class EncVariableSlotAllocator : VariableSlotAllocator private readonly IReadOnlyDictionary? _hoistedLocalSlots; private readonly int _awaiterCount; private readonly IReadOnlyDictionary? _awaiterMap; - private readonly IReadOnlyDictionary? _stateMachineStateMap; // SyntaxOffset -> State Ordinal + private readonly IReadOnlyDictionary<(int syntaxOffset, AwaitDebugId awaitId), StateMachineState>? _stateMachineStateMap; private readonly StateMachineState? _firstUnusedDecreasingStateMachineState; private readonly StateMachineState? _firstUnusedIncreasingStateMachineState; @@ -58,7 +58,7 @@ public EncVariableSlotAllocator( IReadOnlyDictionary? hoistedLocalSlots, int awaiterCount, IReadOnlyDictionary? awaiterMap, - IReadOnlyDictionary? stateMachineStateMap, + IReadOnlyDictionary<(int syntaxOffset, AwaitDebugId awaitId), StateMachineState>? stateMachineStateMap, StateMachineState? firstUnusedIncreasingStateMachineState, StateMachineState? firstUnusedDecreasingStateMachineState, LambdaSyntaxFacts lambdaSyntaxFacts) @@ -332,11 +332,11 @@ public override bool TryGetPreviousLambda(SyntaxNode lambdaOrLambdaBodySyntax, b public override StateMachineState? GetFirstUnusedStateMachineState(bool increasing) => increasing ? _firstUnusedIncreasingStateMachineState : _firstUnusedDecreasingStateMachineState; - public override bool TryGetPreviousStateMachineState(SyntaxNode syntax, out StateMachineState state) + public override bool TryGetPreviousStateMachineState(SyntaxNode syntax, AwaitDebugId awaitId, out StateMachineState state) { if (_stateMachineStateMap != null && TryGetPreviousSyntaxOffset(syntax, out int syntaxOffset) && - _stateMachineStateMap.TryGetValue(syntaxOffset, out state)) + _stateMachineStateMap.TryGetValue((syntaxOffset, awaitId), out state)) { return true; } diff --git a/src/Compilers/Core/Portable/Emit/EditAndContinueMethodDebugInformation.cs b/src/Compilers/Core/Portable/Emit/EditAndContinueMethodDebugInformation.cs index 0e78c4199d00c..5cebf9731db90 100644 --- a/src/Compilers/Core/Portable/Emit/EditAndContinueMethodDebugInformation.cs +++ b/src/Compilers/Core/Portable/Emit/EditAndContinueMethodDebugInformation.cs @@ -325,14 +325,30 @@ private static unsafe ImmutableArray UncompressState if (count > 0) { int syntaxOffsetBaseline = -blobReader.ReadCompressedInteger(); + int lastSyntaxOffset = int.MinValue; + int relativeOrdinal = 0; while (count > 0) { int stateNumber = blobReader.ReadCompressedSignedInteger(); int syntaxOffset = syntaxOffsetBaseline + blobReader.ReadCompressedInteger(); - mapBuilder.Add(new StateMachineStateDebugInfo(syntaxOffset, (StateMachineState)stateNumber)); + // The entries are ordered by syntax offset. + // The relative ordinal is the index of the entry relative to the last entry with a different syntax offset. + if (syntaxOffset < lastSyntaxOffset) + { + throw CreateInvalidDataException(compressedStateMachineStates, blobReader.Offset); + } + + relativeOrdinal = (syntaxOffset == lastSyntaxOffset) ? relativeOrdinal + 1 : 0; + if (relativeOrdinal > byte.MaxValue) + { + throw CreateInvalidDataException(compressedStateMachineStates, blobReader.Offset); + } + + mapBuilder.Add(new StateMachineStateDebugInfo(syntaxOffset, new AwaitDebugId((byte)relativeOrdinal), (StateMachineState)stateNumber)); count--; + lastSyntaxOffset = syntaxOffset; } } } @@ -356,7 +372,7 @@ internal void SerializeStateMachineStates(BlobBuilder writer) int syntaxOffsetBaseline = Math.Min(StateMachineStates.Min(state => state.SyntaxOffset), 0); writer.WriteCompressedInteger(-syntaxOffsetBaseline); - foreach (StateMachineStateDebugInfo state in StateMachineStates) + foreach (StateMachineStateDebugInfo state in StateMachineStates.OrderBy(s => s.SyntaxOffset).ThenBy(s => s.AwaitId.RelativeStateOrdinal)) { writer.WriteCompressedSignedInteger((int)state.StateNumber); writer.WriteCompressedInteger(state.SyntaxOffset - syntaxOffsetBaseline); diff --git a/src/Compilers/Test/Utilities/CSharp/CSharpTestBase.cs b/src/Compilers/Test/Utilities/CSharp/CSharpTestBase.cs index dacf3a99d69a8..05068a6e5c5b4 100644 --- a/src/Compilers/Test/Utilities/CSharp/CSharpTestBase.cs +++ b/src/Compilers/Test/Utilities/CSharp/CSharpTestBase.cs @@ -233,25 +233,42 @@ public interface IAsyncDisposable } "; - protected const string AsyncStreamsTypes = @" + protected const string NonDisposableAsyncEnumeratorDefinition = @" +#nullable disable + namespace System.Collections.Generic { - public interface IAsyncEnumerable + public interface IAsyncEnumerator { - IAsyncEnumerator GetAsyncEnumerator(System.Threading.CancellationToken token = default); + System.Threading.Tasks.ValueTask MoveNextAsync(); + T Current { get; } } +} +"; + + protected const string DisposableAsyncEnumeratorDefinition = @" +#nullable disable +namespace System.Collections.Generic +{ public interface IAsyncEnumerator : System.IAsyncDisposable { System.Threading.Tasks.ValueTask MoveNextAsync(); T Current { get; } } } -namespace System +" + IAsyncDisposableDefinition; + + protected const string AsyncStreamsTypes = DisposableAsyncEnumeratorDefinition + CommonAsyncStreamsTypes; + + protected const string CommonAsyncStreamsTypes = @" +#nullable disable + +namespace System.Collections.Generic { - public interface IAsyncDisposable + public interface IAsyncEnumerable { - System.Threading.Tasks.ValueTask DisposeAsync(); + IAsyncEnumerator GetAsyncEnumerator(System.Threading.CancellationToken token = default); } } @@ -266,8 +283,6 @@ public AsyncIteratorStateMachineAttribute(Type stateMachineType) : base(stateMac } } -#nullable disable - namespace System.Threading.Tasks.Sources { // From https://github.com/dotnet/runtime/blob/f580068aa93fb3c6d5fbc7e33f6cd7d52fa86b24/src/libraries/Microsoft.Bcl.AsyncInterfaces/src/System/Threading/Tasks/Sources/ManualResetValueTaskSourceCore.cs diff --git a/src/Compilers/VisualBasic/Portable/CodeGen/ResumableStateMachineStateAllocator.vb b/src/Compilers/VisualBasic/Portable/CodeGen/ResumableStateMachineStateAllocator.vb index 0b5ea39ab7e2b..7c53690ddc6d9 100644 --- a/src/Compilers/VisualBasic/Portable/CodeGen/ResumableStateMachineStateAllocator.vb +++ b/src/Compilers/VisualBasic/Portable/CodeGen/ResumableStateMachineStateAllocator.vb @@ -47,7 +47,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic Dim direction = If(_increasing, +1, -1) Dim state As StateMachineState - If _slotAllocator?.TryGetPreviousStateMachineState(awaitOrYieldReturnSyntax, state) = True Then + If _slotAllocator?.TryGetPreviousStateMachineState(awaitOrYieldReturnSyntax, awaitId:=Nothing, state) = True Then #If DEBUG Then ' two states of the new state machine should not match the same state of the previous machine Debug.Assert(Not _matchedStates(state * direction)) diff --git a/src/Compilers/VisualBasic/Portable/Lowering/StateMachineRewriter/StateMachineRewriter.StateMachineMethodToClassRewriter.vb b/src/Compilers/VisualBasic/Portable/Lowering/StateMachineRewriter/StateMachineRewriter.StateMachineMethodToClassRewriter.vb index ab4526131a4e1..70816a62cdf42 100644 --- a/src/Compilers/VisualBasic/Portable/Lowering/StateMachineRewriter/StateMachineRewriter.StateMachineMethodToClassRewriter.vb +++ b/src/Compilers/VisualBasic/Portable/Lowering/StateMachineRewriter/StateMachineRewriter.StateMachineMethodToClassRewriter.vb @@ -161,7 +161,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic If _tryBlockSyntaxForNextFinalizerState IsNot Nothing Then If SlotAllocatorOpt Is Nothing OrElse - Not SlotAllocatorOpt.TryGetPreviousStateMachineState(_tryBlockSyntaxForNextFinalizerState, _currentFinalizerState) Then + Not SlotAllocatorOpt.TryGetPreviousStateMachineState(_tryBlockSyntaxForNextFinalizerState, awaitId:=Nothing, _currentFinalizerState) Then _currentFinalizerState = _nextFinalizerState _nextFinalizerState = CType(_nextFinalizerState - 1, StateMachineState) End If @@ -179,7 +179,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic SyntaxBindingUtilities.BindsToTryStatement(node), $"Unexpected syntax: {node.Kind()}") Dim syntaxOffset = CurrentMethod.CalculateLocalSyntaxOffset(node.SpanStart, node.SyntaxTree) - _stateDebugInfoBuilder.Add(New StateMachineStateDebugInfo(syntaxOffset, state)) + _stateDebugInfoBuilder.Add(New StateMachineStateDebugInfo(syntaxOffset, awaitId:=Nothing, state)) End Sub Protected Sub AddState(stateNumber As Integer, ByRef resumeLabel As GeneratedLabelSymbol) diff --git a/src/Compilers/VisualBasic/Test/Emit/PDB/PDBAsyncTests.vb b/src/Compilers/VisualBasic/Test/Emit/PDB/PDBAsyncTests.vb index 604b416ba632f..47e243783f3c6 100644 --- a/src/Compilers/VisualBasic/Test/Emit/PDB/PDBAsyncTests.vb +++ b/src/Compilers/VisualBasic/Test/Emit/PDB/PDBAsyncTests.vb @@ -213,9 +213,6 @@ End Module compilation.VerifyPdb("Module1.F", - - - @@ -226,13 +223,10 @@ End Module -) +, options:=PdbValidationOptions.ExcludeDocuments) compilation.VerifyPdb("Module1+VB$StateMachine_1_F.MoveNext", - - - @@ -246,18 +240,6 @@ End Module - - @@ -267,38 +249,32 @@ End Module -) +, options:=PdbValidationOptions.ExcludeDocuments Or PdbValidationOptions.ExcludeSequencePoints) compilation.VerifyPdb("Module1.Test", - - - + + - - - + + + - - -) +, options:=PdbValidationOptions.ExcludeDocuments) compilation.VerifyPdb("Module1+VB$StateMachine_2_Test.MoveNext", - - - @@ -318,26 +294,6 @@ End Module - - @@ -355,13 +311,10 @@ End Module -) +, options:=PdbValidationOptions.ExcludeDocuments Or PdbValidationOptions.ExcludeSequencePoints) compilation.VerifyPdb("Module1.S", - - - @@ -372,13 +325,10 @@ End Module -) +, options:=PdbValidationOptions.ExcludeDocuments) compilation.VerifyPdb("Module1+VB$StateMachine_3_S.MoveNext", - - - @@ -390,18 +340,6 @@ End Module - - @@ -412,7 +350,7 @@ End Module -) +, options:=PdbValidationOptions.ExcludeDocuments Or PdbValidationOptions.ExcludeSequencePoints) End Sub diff --git a/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/EEAssemblyBuilder.cs b/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/EEAssemblyBuilder.cs index 8f42a8751099a..27ca3362bad6c 100644 --- a/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/EEAssemblyBuilder.cs +++ b/src/ExpressionEvaluator/CSharp/Source/ExpressionCompiler/EEAssemblyBuilder.cs @@ -173,7 +173,7 @@ public override bool TryGetPreviousLambda(SyntaxNode lambdaOrLambdaBodySyntax, b return false; } - public override bool TryGetPreviousStateMachineState(SyntaxNode awaitOrYieldSyntax, out StateMachineState state) + public override bool TryGetPreviousStateMachineState(SyntaxNode syntax, AwaitDebugId awaitId, out StateMachineState state) { state = 0; return false; diff --git a/src/ExpressionEvaluator/VisualBasic/Source/ExpressionCompiler/EEAssemblyBuilder.vb b/src/ExpressionEvaluator/VisualBasic/Source/ExpressionCompiler/EEAssemblyBuilder.vb index 46560586129fa..5db6dd6ce31a9 100644 --- a/src/ExpressionEvaluator/VisualBasic/Source/ExpressionCompiler/EEAssemblyBuilder.vb +++ b/src/ExpressionEvaluator/VisualBasic/Source/ExpressionCompiler/EEAssemblyBuilder.vb @@ -190,7 +190,7 @@ Namespace Microsoft.CodeAnalysis.VisualBasic.ExpressionEvaluator Return False End Function - Public Overrides Function TryGetPreviousStateMachineState(awaitOrYieldSyntax As SyntaxNode, ByRef state As StateMachineState) As Boolean + Public Overrides Function TryGetPreviousStateMachineState(syntax As SyntaxNode, awaitId As AwaitDebugId, ByRef state As StateMachineState) As Boolean state = 0 Return False End Function