Skip to content

Commit 0b8bb4e

Browse files
committed
Simplify and clean up
1 parent 22e8f43 commit 0b8bb4e

9 files changed

Lines changed: 204 additions & 221 deletions

File tree

src/core/IronPython/Compiler/Ast/AwaitExpression.cs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,8 @@ namespace IronPython.Compiler.Ast {
1414
using Ast = MSAst.Expression;
1515

1616
/// <summary>
17-
/// Represents <c>await expr</c>. Under <c>FEATURE_NET_ASYNC</c> this compiles
18-
/// directly to a DLR runtime-async suspension point. Otherwise it is
19-
/// desugared into <c>yield from expr.__await__()</c> against the enclosing
20-
/// generator-shaped coroutine state machine.
17+
/// Represents <c>await expr</c>. Under <c>FEATURE_NET_ASYNC</c> this compiles directly to a DLR async suspension point.
18+
/// Otherwise it is desugared into <c>yield from expr.__await__()</c> against the enclosing generator-shaped coroutine state machine.
2119
/// </summary>
2220
public class AwaitExpression : Expression {
2321
#if FEATURE_NET_ASYNC

src/core/IronPython/Compiler/Ast/FunctionDefinition.cs

Lines changed: 39 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using System.Runtime.CompilerServices;
99
using System.Text;
1010
using System.Threading;
11+
using System.Threading.Tasks;
1112

1213
using IronPython.Runtime;
1314
using IronPython.Runtime.Operations;
@@ -118,24 +119,21 @@ internal override int KwOnlyArgCount {
118119
public Expression ReturnAnnotation { get; internal set; }
119120

120121
#if FEATURE_NET_ASYNC
121-
// Under runtime-async, async functions are compiled directly to a
122-
// Task<object?> via the DLR's AsyncExpression rather than reused
123-
// through the generator state machine, so IsAsync no longer implies
124-
// generator-shaped emission.
122+
// Under .NET-async, async functions are compiled directly to a Task<object?> via the DLR's AsyncExpression
123+
// rather than reused through the generator state machine, so IsAsync does not imply generator-shaped emission.
125124
internal override bool IsGeneratorMethod => IsGenerator;
126125

127-
// Async-generator (PEP 525) channels, one StrongBox per async-generator instance (per stack frame
128-
// at runtime — not static). Created when lowering the async generator; declared/assigned in the
129-
// function body, captured by the generator (the body's yields read them through Parent), and handed
130-
// to the PythonAsyncGenerator wrapper, which writes them before each resume:
126+
// Async-generator (PEP 525) channels. The StrongBox *values* are per-async-generator instance,
127+
// allocated per stack frame at runtime in the function body.
128+
// Declared/assigned in the body, captured by the generator (its yields read them through Parent),
129+
// and handed to the PythonAsyncGenerator wrapper, which writes them before each resume:
131130
// AsyncSendSlot — the value of `x = yield z` (asend(v); None for __anext__/async for).
132131
// AsyncThrowSlot — an exception to rethrow at the yield resume point (athrow/aclose).
133-
private MSAst.ParameterExpression _asyncSendSlot;
134-
private MSAst.ParameterExpression _asyncThrowSlot;
135-
internal MSAst.ParameterExpression AsyncSendSlot
136-
=> _asyncSendSlot ??= MSAst.Expression.Variable(typeof(System.Runtime.CompilerServices.StrongBox<object>), "$asyncSend");
137-
internal MSAst.ParameterExpression AsyncThrowSlot
138-
=> _asyncThrowSlot ??= MSAst.Expression.Variable(typeof(System.Runtime.CompilerServices.StrongBox<Exception>), "$asyncThrow");
132+
private readonly MSAst.ParameterExpression _asyncSendSlot = MSAst.Expression.Variable(typeof(StrongBox<object>), "$asyncSend");
133+
private readonly MSAst.ParameterExpression _asyncThrowSlot = MSAst.Expression.Variable(typeof(StrongBox<Exception>), "$asyncThrow");
134+
135+
internal MSAst.ParameterExpression AsyncSendSlot => _asyncSendSlot;
136+
internal MSAst.ParameterExpression AsyncThrowSlot => _asyncThrowSlot;
139137
#else
140138
internal override bool IsGeneratorMethod => IsGenerator || IsAsync;
141139
#endif
@@ -385,16 +383,14 @@ internal MSAst.Expression MakeFunctionExpression() {
385383
)
386384
),
387385
#if FEATURE_NET_ASYNC
388-
// Async generators are lowered via AsyncEnumerableExpression in the body, so they must not
389-
// be wrapped as a PythonGenerator here — only plain (non-async) generators are.
386+
// Async generators are lowered via AsyncEnumerableExpression in the body,
387+
// so they must not be wrapped as a PythonGenerator here — only plain (non-async) generators are.
390388
(IsGenerator && !IsAsync) ?
391-
(MSAst.Expression)new PythonGeneratorExpression(code, GlobalParent.PyContext.Options.CompilationThreshold, IsAsync) :
392-
(MSAst.Expression)code
393389
#else
394390
(IsGenerator || IsAsync) ?
391+
#endif
395392
(MSAst.Expression)new PythonGeneratorExpression(code, GlobalParent.PyContext.Options.CompilationThreshold, IsAsync) :
396393
(MSAst.Expression)code
397-
#endif
398394
);
399395
} else {
400396
ret = Ast.Call(
@@ -695,18 +691,15 @@ private LightLambdaExpression CreateFunctionLambda() {
695691
// The exception traceback needs to come from the generator's method body, and so we must do the check and throw
696692
// from inside the generator.
697693
#if FEATURE_NET_ASYNC
698-
// Async generators have no backing PythonGenerator (they lower to IAsyncEnumerable via
699-
// AsyncEnumerableExpression), so skip the $generator.CheckThrowable() prologue for them.
694+
// Async generators have no backing PythonGenerator (they lower to IAsyncEnumerable via AsyncEnumerableExpression),
695+
// so skip the $generator.CheckThrowable() prologue for them.
700696
if (IsGenerator && !IsAsync) {
701-
MSAst.Expression s1 = YieldExpression.CreateCheckThrowExpression(SourceSpan.None);
702-
statements.Add(s1);
703-
}
704697
#else
705698
if (IsGenerator || IsAsync) {
699+
#endif
706700
MSAst.Expression s1 = YieldExpression.CreateCheckThrowExpression(SourceSpan.None);
707701
statements.Add(s1);
708702
}
709-
#endif
710703

711704
if (Body.CanThrow && !(Body is SuiteStatement) && Body.StartIndex != -1) {
712705
statements.Add(UpdateLineNumber(GlobalParent.IndexToLocation(Body.StartIndex).Line));
@@ -726,16 +719,15 @@ private LightLambdaExpression CreateFunctionLambda() {
726719
body = AddReturnTarget(body);
727720

728721
#if FEATURE_NET_ASYNC
729-
// Under runtime-async, an `async def` body returns a PythonCoroutine wrapping a
730-
// Task<object?>. We pre-allocate a CancellationTokenSource and a StrongBox<Exception?>
731-
// here so the same instances are shared with both AsyncExpression (which threads them into
732-
// AsyncHelpers.DriveAsync) and PythonCoroutine (which uses them to implement
733-
// coro.throw(exc) on a running coroutine: write the exception to the box, cancel the CTS,
734-
// and DriveAsync surfaces that exception in place of OperationCanceledException).
722+
// Under .NET-async, an `async def` body returns a PythonCoroutine wrapping a Task<object?>.
723+
// We pre-allocate a CancellationTokenSource and a StrongBox<Exception?> here
724+
// so the same instances are shared with both AsyncExpression, which threads them into AsyncHelpers.DriveAsync
725+
// and PythonCoroutine, which uses them to implement coro.throw(exc) on a running coroutine:
726+
// write the exception to the box, cancel the CTS, and DriveAsync surfaces that exception in place of OperationCanceledException.
735727
if (IsAsync) {
736-
var cts = MSAst.Expression.Variable(typeof(System.Threading.CancellationTokenSource), "$cts");
737-
var excBox = MSAst.Expression.Variable(typeof(System.Runtime.CompilerServices.StrongBox<Exception>), "$cancelExc");
738-
var ctToken = MSAst.Expression.Property(cts, nameof(System.Threading.CancellationTokenSource.Token));
728+
var cts = MSAst.Expression.Variable(typeof(CancellationTokenSource), "$cts");
729+
var excBox = MSAst.Expression.Variable(typeof(StrongBox<Exception>), "$cancelExc");
730+
var ctToken = MSAst.Expression.Property(cts, nameof(CancellationTokenSource.Token));
739731
if (IsGenerator) {
740732
// Async generator: the body has both `await` and `yield`. Lower it to an
741733
// IAsyncEnumerable<object?> via AsyncEnumerableExpression, sharing the generator label so
@@ -746,11 +738,11 @@ private LightLambdaExpression CreateFunctionLambda() {
746738
var sendSlot = AsyncSendSlot;
747739
var throwSlot = AsyncThrowSlot;
748740
body = MSAst.Expression.Block(
749-
new[] { cts, excBox, sendSlot, throwSlot },
750-
MSAst.Expression.Assign(cts, MSAst.Expression.New(typeof(System.Threading.CancellationTokenSource))),
751-
MSAst.Expression.Assign(excBox, MSAst.Expression.New(typeof(System.Runtime.CompilerServices.StrongBox<Exception>))),
752-
MSAst.Expression.Assign(sendSlot, MSAst.Expression.New(typeof(System.Runtime.CompilerServices.StrongBox<object>))),
753-
MSAst.Expression.Assign(throwSlot, MSAst.Expression.New(typeof(System.Runtime.CompilerServices.StrongBox<Exception>))),
741+
[cts, excBox, sendSlot, throwSlot],
742+
MSAst.Expression.Assign(cts, MSAst.Expression.New(typeof(CancellationTokenSource))),
743+
MSAst.Expression.Assign(excBox, MSAst.Expression.New(typeof(StrongBox<Exception>))),
744+
MSAst.Expression.Assign(sendSlot, MSAst.Expression.New(typeof(StrongBox<object>))),
745+
MSAst.Expression.Assign(throwSlot, MSAst.Expression.New(typeof(StrongBox<Exception>))),
754746
Ast.Call(
755747
AstMethods.MakeAsyncGenerator,
756748
_functionParam,
@@ -760,19 +752,18 @@ private LightLambdaExpression CreateFunctionLambda() {
760752
cts));
761753
} else {
762754
// Plain async def: the body returns a PythonCoroutine wrapping a Task<object?>.
763-
// Lazy start: hand MakeAsyncCoroutine a thunk (Func<Task<object?>>) instead of an
764-
// already-running Task, so the body doesn't execute until the coroutine is first driven
765-
// (send/AsTask). This makes calling an async def side-effect-free (PEP 492) and lets the
766-
// body's first await capture the driver's SynchronizationContext rather than whatever
767-
// context happened to be current at construction.
755+
// Lazy start: hand MakeAsyncCoroutine a thunk (Func<Task<object?>>) instead of an already-running Task,
756+
// so the body doesn't execute until the coroutine is first driven (send/AsTask).
757+
// This makes calling an async def side-effect-free (PEP 492) and lets the body's first await capture the driver's SynchronizationContext
758+
// rather than whatever context happened to be current at construction.
768759
body = MSAst.Expression.Block(
769-
new[] { cts, excBox },
770-
MSAst.Expression.Assign(cts, MSAst.Expression.New(typeof(System.Threading.CancellationTokenSource))),
771-
MSAst.Expression.Assign(excBox, MSAst.Expression.New(typeof(System.Runtime.CompilerServices.StrongBox<Exception>))),
760+
[cts, excBox],
761+
MSAst.Expression.Assign(cts, MSAst.Expression.New(typeof(CancellationTokenSource))),
762+
MSAst.Expression.Assign(excBox, MSAst.Expression.New(typeof(StrongBox<Exception>))),
772763
Ast.Call(
773764
AstMethods.MakeAsyncCoroutine,
774765
_functionParam,
775-
MSAst.Expression.Lambda<Func<System.Threading.Tasks.Task<object>>>(
766+
MSAst.Expression.Lambda<Func<Task<object>>>(
776767
AstUtils.Async(Name, body, ctToken, excBox)),
777768
cts,
778769
excBox));

src/core/IronPython/Compiler/Ast/ReturnStatement.cs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
// The .NET Foundation licenses this file to you under the Apache 2.0 License.
33
// See the LICENSE file in the project root for more information.
44

5+
using System.Diagnostics;
6+
57
using MSAst = System.Linq.Expressions;
68

79
namespace IronPython.Compiler.Ast {
@@ -18,11 +20,12 @@ public ReturnStatement(Expression expression) {
1820
public override MSAst.Expression Reduce() {
1921
if (Parent.IsGeneratorMethod) {
2022
#if FEATURE_NET_ASYNC
21-
// An async generator (`async def` with `yield`) lowers through the DLR generator via
22-
// AsyncEnumerableExpression, which doesn't understand IronPython's -2 "generator-return"
23-
// marker. `return` there is always bare (`return value` is a SyntaxError in async
24-
// generators) and simply ends the async iteration — map it to a YieldBreak.
23+
// An async generator (`async def` with `yield`) lowers through the DLR generator via AsyncEnumerableExpression,
24+
// which doesn't understand IronPython's -2 "generator-return" marker.
25+
// `return` there is always bare (`return value` is a SyntaxError in async generators)
26+
// and simply ends the async iteration — map it to a YieldBreak without a value.
2527
if (Parent is FunctionDefinition { IsAsync: true }) {
28+
Debug.Assert(Expression == null, "async generators should not have a return value");
2629
return GlobalParent.AddDebugInfo(AstUtils.YieldBreak(GeneratorLabel), Span);
2730
}
2831
#endif

src/core/IronPython/Compiler/Ast/YieldExpression.cs

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
using MSAst = System.Linq.Expressions;
88

99
using System;
10+
using System.Runtime.CompilerServices;
11+
using System.Runtime.ExceptionServices;
1012
using System.Diagnostics;
1113
using Microsoft.Scripting;
1214
using Microsoft.Scripting.Runtime;
@@ -21,9 +23,9 @@ namespace IronPython.Compiler.Ast {
2123
public class YieldExpression : Expression {
2224
#if FEATURE_NET_ASYNC
2325
private static readonly System.Reflection.MethodInfo s_captureMethod
24-
= typeof(System.Runtime.ExceptionServices.ExceptionDispatchInfo).GetMethod(nameof(System.Runtime.ExceptionServices.ExceptionDispatchInfo.Capture))!;
26+
= typeof(ExceptionDispatchInfo).GetMethod(nameof(ExceptionDispatchInfo.Capture))!;
2527
private static readonly System.Reflection.MethodInfo s_throwMethod
26-
= typeof(System.Runtime.ExceptionServices.ExceptionDispatchInfo).GetMethod(nameof(System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw), Type.EmptyTypes)!;
28+
= typeof(ExceptionDispatchInfo).GetMethod(nameof(ExceptionDispatchInfo.Throw), Type.EmptyTypes)!;
2729
#endif
2830

2931
public YieldExpression(Expression? expression) {
@@ -53,26 +55,26 @@ public override MSAst.Expression Reduce() {
5355
MSAst.Expression yieldValue = Expression == null ? AstUtils.Constant(null) : AstUtils.Convert(Expression, typeof(object));
5456

5557
#if FEATURE_NET_ASYNC
56-
// An async generator (`async def` with `yield`) is lowered via AsyncEnumerableExpression and has no
57-
// backing PythonGenerator, so there is no `$generator` to call CheckThrowable() on. Instead the
58-
// resume reads two per-generator cells that PythonAsyncGenerator writes before advancing:
59-
// AsyncThrowSlot — if set (athrow/aclose), rethrow it here (preserving stack); cleared first so a
60-
// body that catches it and yields again doesn't re-throw on the next resume.
58+
// An async generator (`async def` with `yield`) is lowered via AsyncEnumerableExpression
59+
// and has no backing PythonGenerator, so there is no `$generator` to call CheckThrowable() on.
60+
// Instead the resume reads two per-generator cells that PythonAsyncGenerator writes before advancing:
61+
// AsyncThrowSlot — if set (athrow/aclose), rethrow it here (preserving stack);
62+
// cleared first so a body that catches it and yields again doesn't re-throw on the next resume.
6163
// AsyncSendSlot — the value of the yield expression: the asend(v) value, or None.
6264
if (Parent is FunctionDefinition { IsAsync: true } fd) {
6365
MSAst.ParameterExpression sendSlot = fd.AsyncSendSlot;
6466
MSAst.ParameterExpression throwSlot = fd.AsyncThrowSlot;
6567
MSAst.ParameterExpression pending = Ast.Variable(typeof(Exception), "$athrow");
6668
return Ast.Block(
6769
typeof(object),
68-
new[] { pending },
70+
[pending],
6971
AstUtils.YieldReturn(GeneratorLabel, yieldValue),
70-
Ast.Assign(pending, Ast.Field(throwSlot, nameof(System.Runtime.CompilerServices.StrongBox<Exception>.Value))),
71-
Ast.Assign(Ast.Field(throwSlot, nameof(System.Runtime.CompilerServices.StrongBox<Exception>.Value)), Ast.Constant(null, typeof(Exception))),
72+
Ast.Assign(pending, Ast.Field(throwSlot, nameof(StrongBox<Exception>.Value))),
73+
Ast.Assign(Ast.Field(throwSlot, nameof(StrongBox<Exception>.Value)), Ast.Constant(null, typeof(Exception))),
7274
Ast.IfThen(
7375
Ast.ReferenceNotEqual(pending, Ast.Constant(null, typeof(Exception))),
7476
Ast.Call(Ast.Call(s_captureMethod, pending), s_throwMethod)),
75-
Ast.Field(sendSlot, nameof(System.Runtime.CompilerServices.StrongBox<object>.Value))
77+
Ast.Field(sendSlot, nameof(StrongBox<object>.Value))
7678
);
7779
}
7880
#endif

src/core/IronPython/Compiler/Parser.cs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2000,11 +2000,10 @@ private Expression ParseAtomExpr() {
20002000
ReportSyntaxError("'await' outside async function");
20012001
}
20022002
#if !FEATURE_NET_ASYNC
2003-
// Under the async-generator path, `await` desugars to `yield from`, so the
2004-
// enclosing function must be marked a generator. Under FEATURE_NET_ASYNC
2005-
// the body is lowered through AsyncExpression and is *not*
2006-
// a Python generator — leaving IsGenerator false here is what keeps
2007-
// ReturnStatement out of the yield branch.
2003+
// Under the generator-based async path, `await` desugars to `yield from`,
2004+
// so the enclosing function must be marked a generator.
2005+
// Under FEATURE_NET_ASYNC, the body is lowered through AsyncExpression and is *not* a Python generator
2006+
// so this mark assignment is being skipped here.
20082007
if (current is not null) {
20092008
current.IsGenerator = true;
20102009
current.GeneratorStop = GeneratorStop;

0 commit comments

Comments
 (0)