-
Notifications
You must be signed in to change notification settings - Fork 4.3k
Implement custom awaitable support #78071
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 3 commits
c265a64
e37bcc2
a4ee4f9
420bfba
db6292a
63886a7
ed51c77
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,7 +2,9 @@ | |
| // The .NET Foundation licenses this file to you under the MIT license. | ||
| // See the LICENSE file in the project root for more information. | ||
|
|
||
| using System.Collections.Generic; | ||
| using System.Diagnostics; | ||
| using System.Diagnostics.CodeAnalysis; | ||
| using Microsoft.CodeAnalysis.CSharp.Symbols; | ||
|
|
||
| namespace Microsoft.CodeAnalysis.CSharp; | ||
|
|
@@ -26,11 +28,13 @@ public static BoundStatement Rewrite( | |
|
|
||
| private readonly CSharpCompilation _compilation; | ||
| private readonly SyntheticBoundNodeFactory _factory; | ||
| private readonly Dictionary<BoundAwaitableValuePlaceholder, BoundExpression> _placeholderMap; | ||
|
|
||
| private RuntimeAsyncRewriter(CSharpCompilation compilation, SyntheticBoundNodeFactory factory) | ||
| { | ||
| _compilation = compilation; | ||
| _factory = factory; | ||
| _placeholderMap = []; | ||
| } | ||
|
|
||
| private NamedTypeSymbol Task | ||
|
|
@@ -53,10 +57,11 @@ private NamedTypeSymbol ValueTaskT | |
| get => field ??= _compilation.GetWellKnownType(WellKnownType.System_Threading_Tasks_ValueTask_T); | ||
| } = null!; | ||
|
|
||
| public BoundExpression VisitExpression(BoundExpression node) | ||
| [return: NotNullIfNotNull(nameof(node))] | ||
| public BoundExpression? VisitExpression(BoundExpression? node) | ||
| { | ||
| var result = Visit(node); | ||
| return (BoundExpression)result; | ||
| return (BoundExpression?)result; | ||
| } | ||
|
|
||
| public override BoundNode? VisitAwaitExpression(BoundAwaitExpression node) | ||
|
|
@@ -88,8 +93,7 @@ public BoundExpression VisitExpression(BoundExpression node) | |
| } | ||
| else | ||
| { | ||
| // PROTOTYPE: when it's not a method with Task/TaskT/ValueTask/ValueTaskT returns, use the helpers | ||
| return base.VisitAwaitExpression(node); | ||
| return RewriteCustomAwaiterAwait(node); | ||
| } | ||
|
|
||
| // PROTOTYPE: Make sure that we report an error in initial binding if these are missing | ||
|
|
@@ -112,4 +116,82 @@ public BoundExpression VisitExpression(BoundExpression node) | |
| // System.Runtime.CompilerServices.RuntimeHelpers.Await(awaitedExpression) | ||
| return _factory.Call(receiver: null, awaitMethod, VisitExpression(node.Expression)); | ||
| } | ||
|
|
||
| private BoundExpression RewriteCustomAwaiterAwait(BoundAwaitExpression node) | ||
| { | ||
| // await expr | ||
| // becomes | ||
| // var _tmp = expr.GetAwaiter(); | ||
| // if (!_tmp.IsCompleted) | ||
| // UnsafeAwaitAwaiterFromRuntimeAsync(_tmp) OR AwaitAwaiterFromRuntimeAsync(_tmp); | ||
| // _tmp.GetResult(); | ||
|
|
||
| // PROTOTYPE: await dynamic will need runtime checks, see AsyncMethodToStateMachine.GenerateAwaitOnCompletedDynamic | ||
|
|
||
| var expr = VisitExpression(node.Expression); | ||
|
|
||
| var awaitablePlaceholder = node.AwaitableInfo.AwaitableInstancePlaceholder; | ||
|
333fred marked this conversation as resolved.
Outdated
|
||
| if (awaitablePlaceholder is not null) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When is
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was basing it off the similar handling in the existing async rewriter, but I believe you're correct, this should never be null.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually, it can be |
||
| { | ||
| _placeholderMap.Add(awaitablePlaceholder, expr); | ||
| } | ||
|
|
||
| // expr.GetAwaiter() | ||
| var getAwaiter = VisitExpression(node.AwaitableInfo.GetAwaiter); | ||
| Debug.Assert(getAwaiter is not null); | ||
|
|
||
| if (awaitablePlaceholder is not null) | ||
| { | ||
| _placeholderMap.Remove(awaitablePlaceholder); | ||
| } | ||
|
|
||
| // var _tmp = expr.GetAwaiter(); | ||
| var tmp = _factory.StoreToTemp(getAwaiter, out BoundAssignmentOperator store, kind: SynthesizedLocalKind.Awaiter); | ||
|
|
||
| // _tmp.IsCompleted | ||
| var isCompletedMethod = node.AwaitableInfo.IsCompleted!.GetMethod; | ||
|
333fred marked this conversation as resolved.
Outdated
|
||
| Debug.Assert(isCompletedMethod is not null); | ||
| var isCompletedCall = _factory.Call(tmp, isCompletedMethod); | ||
|
|
||
| // UnsafeAwaitAwaiterFromRuntimeAsync(_tmp) OR AwaitAwaiterFromRuntimeAsync(_tmp) | ||
| var discardedUseSiteInfo = CompoundUseSiteInfo<AssemblySymbol>.Discarded; | ||
| var useUnsafeAwait = _factory.Compilation.Conversions.ClassifyImplicitConversionFromType( | ||
| tmp.Type, | ||
| _factory.Compilation.GetWellKnownType(WellKnownType.System_Runtime_CompilerServices_ICriticalNotifyCompletion), | ||
| ref discardedUseSiteInfo).IsImplicit; | ||
|
333fred marked this conversation as resolved.
|
||
|
|
||
| // PROTOTYPE: Make sure that we report an error in initial binding if these are missing | ||
| var awaitMethod = (MethodSymbol?)_compilation.GetWellKnownTypeMember(useUnsafeAwait | ||
| ? WellKnownMember.System_Runtime_CompilerServices_RuntimeHelpers__UnsafeAwaitAwaiterFromRuntimeAsync_TAwaiter | ||
| : WellKnownMember.System_Runtime_CompilerServices_RuntimeHelpers__AwaitAwaiterFromRuntimeAsync_TAwaiter); | ||
|
|
||
| Debug.Assert(awaitMethod is { Arity: 1 }); | ||
|
333fred marked this conversation as resolved.
|
||
|
|
||
| var awaitCall = _factory.Call( | ||
| receiver: null, | ||
| awaitMethod.Construct(tmp.Type), | ||
| tmp); | ||
|
|
||
| // if (!_tmp.IsCompleted) awaitCall | ||
| var ifNotCompleted = new BoundLoweredConditionalSideEffect( | ||
|
333fred marked this conversation as resolved.
Outdated
|
||
| node.Syntax, | ||
| condition: _factory.Not(isCompletedCall), | ||
| sideEffect: awaitCall); | ||
|
|
||
| // _tmp.GetResult() | ||
| var getResultMethod = node.AwaitableInfo.GetResult; | ||
| Debug.Assert(getResultMethod is not null); | ||
| var getResultCall = _factory.Call(tmp, getResultMethod); | ||
|
|
||
| // final sequence | ||
| return _factory.Sequence( | ||
| locals: [tmp.LocalSymbol], | ||
| sideEffects: [store, ifNotCompleted], | ||
| result: getResultCall); | ||
| } | ||
|
|
||
| public override BoundNode VisitAwaitableValuePlaceholder(BoundAwaitableValuePlaceholder node) | ||
| { | ||
| return _placeholderMap[node]; | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.