Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions docs/compilers/CSharp/Runtime Async Design.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,9 +135,11 @@ For any `await expr` with where `expr` has type `E`, the compiler will attempt t
2. There is an identity or implicit reference conversion from `E` to the type of `P`.
4. Otherwise, if `Mi` has a generic arity of 1 with type param `Tm`, all of the following must be true, or `Mi` is removed:
1. The return type is `Tm`
2. There is an identity or implicit reference conversion from `E`'s unsubstituted definition to `P`
3. `E`'s type argument, `Te`, is valid to substitute for `Tm`
6. If only one `Mi` remains, that method is used for the following rewrites. Otherwise, we instead move to [await any other type].
2. The generic parameter of `E` is `Te`
3. `Ti` satisfies any constraints on `Tm`
4. `Mie` is `Mi` with `Te` substituted for `Tm`, and `Pe` is the resulting parameter of `Mie`
5. There is an identity or implicit reference conversion from `E` to the type of `Pe`
5. If only one `Mi` remains, that method is used for the following rewrites. Otherwise, we instead move to [await any other type].

We'll generally rewrite `await expr` into `System.Runtime.CompilerServices.AsyncHelpers.Await(expr)`. A number of different example scenarios for this are covered below. The
main interesting deviations are when `struct` rvalues need to be hoisted across an `await`, and exception handling rewriting.
Expand Down
237 changes: 185 additions & 52 deletions src/Compilers/CSharp/Portable/Binder/Binder_Await.cs

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion src/Compilers/CSharp/Portable/Binder/ForEachLoopBinder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -263,7 +263,7 @@ private BoundForEachStatement BindForEachPartsWorker(BindingDiagnosticBag diagno
var placeholder = new BoundAwaitableValuePlaceholder(expr, builder.MoveNextInfo?.Method.ReturnType ?? CreateErrorType());
awaitInfo = BindAwaitInfo(placeholder, expr, diagnostics, ref hasErrors);

if (!hasErrors && (awaitInfo.GetResult ?? awaitInfo.RuntimeAsyncAwaitMethod)?.ReturnType.SpecialType != SpecialType.System_Boolean)
if (!hasErrors && (awaitInfo.GetResult ?? awaitInfo.RuntimeAsyncAwaitCall?.Method)?.ReturnType.SpecialType != SpecialType.System_Boolean)
{
diagnostics.Add(ErrorCode.ERR_BadGetAsyncEnumerator, expr.Location, getEnumeratorMethod.ReturnTypeWithAnnotations, getEnumeratorMethod);
hasErrors = true;
Expand Down
5 changes: 5 additions & 0 deletions src/Compilers/CSharp/Portable/Binder/RefSafetyAnalysis.cs
Original file line number Diff line number Diff line change
Expand Up @@ -958,6 +958,11 @@ private void GetAwaitableInstancePlaceholders(ArrayBuilder<(BoundValuePlaceholde
{
placeholders.Add((placeholder, valEscapeScope));
}

if (awaitableInfo.RuntimeAsyncAwaitCallPlaceholder is { } runtimePlaceholder)
{
placeholders.Add((runtimePlaceholder, valEscapeScope));
}
}

public override BoundNode? VisitImplicitIndexerAccess(BoundImplicitIndexerAccess node)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ internal static BoundStatement BindUsingStatementOrDeclarationFromParts(SyntaxNo

if (awaitableTypeOpt is null)
{
awaitOpt = new BoundAwaitableInfo(syntax, awaitableInstancePlaceholder: null, isDynamic: true, getAwaiter: null, isCompleted: null, getResult: null, runtimeAsyncAwaitMethod: null) { WasCompilerGenerated = true };
awaitOpt = new BoundAwaitableInfo(syntax, awaitableInstancePlaceholder: null, isDynamic: true, getAwaiter: null, isCompleted: null, getResult: null, runtimeAsyncAwaitCall: null, runtimeAsyncAwaitCallPlaceholder: null) { WasCompilerGenerated = true };
}
else
{
Expand Down
11 changes: 6 additions & 5 deletions src/Compilers/CSharp/Portable/BoundTree/BoundAwaitableInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,12 @@ partial class BoundAwaitableInfo
{
private partial void Validate()
{
if (RuntimeAsyncAwaitMethod is not null)
if (RuntimeAsyncAwaitCall is not null)
{
Debug.Assert(RuntimeAsyncAwaitMethod.ContainingType.ExtendedSpecialType == InternalSpecialType.System_Runtime_CompilerServices_AsyncHelpers);
Debug.Assert(RuntimeAsyncAwaitCall.Method.ContainingType.ExtendedSpecialType == InternalSpecialType.System_Runtime_CompilerServices_AsyncHelpers);
Debug.Assert(RuntimeAsyncAwaitCallPlaceholder is not null);

switch (RuntimeAsyncAwaitMethod.Name)
switch (RuntimeAsyncAwaitCall.Method.Name)
{
case "Await":
Debug.Assert(GetAwaiter is null);
Expand All @@ -30,11 +31,11 @@ private partial void Validate()
break;

default:
Debug.Fail($"Unexpected RuntimeAsyncAwaitMethod: {RuntimeAsyncAwaitMethod.Name}");
Debug.Fail($"Unexpected RuntimeAsyncAwaitCall: {RuntimeAsyncAwaitCall.Method.Name}");
break;
}
}

Debug.Assert(GetAwaiter is not null || RuntimeAsyncAwaitMethod is not null || IsDynamic || HasErrors);
Debug.Assert(GetAwaiter is not null || RuntimeAsyncAwaitCall is not null || IsDynamic || HasErrors);
}
}
8 changes: 5 additions & 3 deletions src/Compilers/CSharp/Portable/BoundTree/BoundNodes.xml
Original file line number Diff line number Diff line change
Expand Up @@ -702,11 +702,13 @@
<Field Name="GetAwaiter" Type="BoundExpression?" Null="allow"/>
<Field Name="IsCompleted" Type="PropertySymbol?" Null="allow"/>
<Field Name="GetResult" Type="MethodSymbol?" Null="allow"/>
<!-- Refers to the runtime async helper method we use for awaiting. Either this is an instance of an AsyncHelpers.Await method symbol, and
GetAwaiter, IsCompleted, and GetResult are null, or this is AsyncHelpers.AwaitAwaiter/UnsafeAwaitAwaiter, and the other
<!-- Refers to the runtime async helper call we use for awaiting. Either this is an instance of a BoundCall of an AsyncHelpers.Await method, and
GetAwaiter, IsCompleted, and GetResult are null, or this is an instance of a AsyncHelpers.AwaitAwaiter/UnsafeAwaitAwaiter BoundCall, and the other
fields are not null. -->
<!-- PROTOTYPE: Look at consumers of this API and see if we can assert that this is null when it should be -->
<Field Name="RuntimeAsyncAwaitMethod" Type="MethodSymbol?" Null="allow"/>
<Field Name="RuntimeAsyncAwaitCall" Type="BoundCall?" Null="allow"/>
<!-- Never null when RuntimeAsyncAwaitCall is not null -->
<Field Name="RuntimeAsyncAwaitCallPlaceholder" Type="BoundAwaitableValuePlaceholder?" Null="allow"/>
</Node>

<Node Name="BoundAwaitExpression" Base="BoundExpression">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2249,16 +2249,16 @@ internal bool ReturnsAwaitableToVoidOrInt(MethodSymbol method, BindingDiagnostic
var syntax = method.ExtractReturnTypeSyntax();
var dumbInstance = new BoundLiteral(syntax, ConstantValue.Null, namedType);
var binder = GetBinder(syntax);
var success = binder.GetAwaitableExpressionInfo(dumbInstance, out BoundExpression? result, out MethodSymbol? runtimeAwaitMethod, syntax, diagnostics);
var success = binder.GetAwaitableExpressionInfo(dumbInstance, out BoundExpression? result, out BoundCall? runtimeAwaitCall, syntax, diagnostics);

RoslynDebug.Assert(!namedType.IsDynamic());
if (!success)
{
return false;
}

Debug.Assert(result is { Type: not null } || runtimeAwaitMethod is { ReturnType: not null });
var returnType = result?.Type ?? runtimeAwaitMethod!.ReturnType;
Debug.Assert(result is { Type: not null } || runtimeAwaitCall is { Type: not null });
var returnType = result?.Type ?? runtimeAwaitCall!.Type;
return returnType.IsVoidType() || returnType.SpecialType == SpecialType.System_Int32;
}

Expand Down
31 changes: 19 additions & 12 deletions src/Compilers/CSharp/Portable/Generated/BoundNodes.xml.Generated.cs

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
Expand Up @@ -50,7 +50,10 @@ private RuntimeAsyncRewriter(SyntheticBoundNodeFactory factory)
Debug.Assert(nodeType is not null);

var awaitableInfo = node.AwaitableInfo;
var runtimeAsyncAwaitMethod = awaitableInfo.RuntimeAsyncAwaitMethod;
var runtimeAsyncAwaitCall = awaitableInfo.RuntimeAsyncAwaitCall;
Debug.Assert(runtimeAsyncAwaitCall is not null);
Debug.Assert(awaitableInfo.RuntimeAsyncAwaitCallPlaceholder is not null);
var runtimeAsyncAwaitMethod = runtimeAsyncAwaitCall.Method;
Debug.Assert(runtimeAsyncAwaitMethod is not null);
Debug.Assert(ReferenceEquals(
runtimeAsyncAwaitMethod.ContainingType.OriginalDefinition,
Expand All @@ -61,7 +64,11 @@ private RuntimeAsyncRewriter(SyntheticBoundNodeFactory factory)
{
// This is the direct await case, with no need for the full pattern.
// System.Runtime.CompilerServices.RuntimeHelpers.Await(awaitedExpression)
return _factory.Call(receiver: null, runtimeAsyncAwaitMethod, VisitExpression(node.Expression));
var expr = VisitExpression(node.Expression);
_placeholderMap.Add(awaitableInfo.RuntimeAsyncAwaitCallPlaceholder, expr);
var call = Visit(awaitableInfo.RuntimeAsyncAwaitCall);
_placeholderMap.Remove(awaitableInfo.RuntimeAsyncAwaitCallPlaceholder);
return call;
}
else
{
Expand Down Expand Up @@ -108,11 +115,11 @@ private BoundExpression RewriteCustomAwaiterAwait(BoundAwaitExpression node)
var isCompletedCall = _factory.Call(tmp, isCompletedMethod);

// UnsafeAwaitAwaiter(_tmp) OR AwaitAwaiter(_tmp)
Debug.Assert(awaitableInfo.RuntimeAsyncAwaitMethod is not null);
var awaitCall = _factory.Call(
receiver: null,
awaitableInfo.RuntimeAsyncAwaitMethod,
tmp);
Debug.Assert(awaitableInfo.RuntimeAsyncAwaitCall is not null);
Debug.Assert(awaitableInfo.RuntimeAsyncAwaitCallPlaceholder is not null);
_placeholderMap.Add(awaitableInfo.RuntimeAsyncAwaitCallPlaceholder, tmp);
var awaitCall = (BoundCall)Visit(awaitableInfo.RuntimeAsyncAwaitCall);
_placeholderMap.Remove(awaitableInfo.RuntimeAsyncAwaitCallPlaceholder);

// if (!_tmp.IsCompleted) awaitCall
var ifNotCompleted = _factory.If(_factory.Not(isCompletedCall), _factory.ExpressionStatement(awaitCall));
Expand Down
Loading