-
Notifications
You must be signed in to change notification settings - Fork 4.2k
Runtime-async ref local hoisting #80093
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
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jakobbotsch @agocke, I'd appreciate if you could look through the expected IL changes here and make sure that they match your expectations.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I spot checked a few of them and they looked good (and valid). I can do some more validation once we start fuzzing with this new change merged into Roslyn.
| IL_0013: ldloc.0 | ||
| IL_0014: ldc.i4.3 | ||
| IL_0015: ldelem.i4 | ||
| IL_0016: pop | ||
| IL_0017: ldloc.0 | ||
| IL_0018: ldc.i4.3 | ||
| IL_0019: ldelem.i4 | ||
| IL_001a: stloc.2 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems to load array[3] twice, discarding the first value. Unexpected?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I expect we will load array[3] once before the await (to read the old value) and once after (to assigned the updated value). If we're dropping the first value, then the result should be incorrect. @333fred Could we add verification of the resulting values (in this test or separate)?
Similarly, if we do M()[3] += await ..., I expect we'll have a side-effect spilling the result of M() once, but we should still do the indexing with [3] twice. Do we have test showing side-effects and order of evaluation directly? (this IL is large enough that it's not trivial to tell...)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The IL here loads the element twice and then also stores the element once. So there's three indexing operations happening: two loads done by the IL right above my comment, before awaiting, and one store done by the IL below, after awaiting.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks. FWIW, it looks like the state machine IL above also had this problem (IL_003d: pop)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks. FWIW, it looks like the state machine IL above also had this problem (
IL_003d: pop)
Thanks for looking at that Julien. Given that, I plan to leave this as is for now, unless it is fairly obvious from a quick investigation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the extra/discarded read is the sacrificial evaluation that we're discussing elsewhere.
Is the following correct:
When doing an assignment arr[index] = await M();, we cache parts with side-effects, and do a sacrificial evaluation of the hoisted/stripped indexing operation before the await. The index-out-of-bounds exception is thrown before M() is evaluated.
Then I think what we're seeing here is that in a compound assignment arr[index] += await M(); we're doing a sacrificial evaluation and an indexing/read before the await, then an indexing/write after the await.
If that understanding is correct, consider filing an issue to optimize the IL (remove sacrificial evaluation) in compound scenarios.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Filed #80147.
src/Compilers/CSharp/Portable/Lowering/StateMachineRewriter/RefInitializationHoister.cs
Show resolved
Hide resolved
src/Compilers/CSharp/Portable/Lowering/StateMachineRewriter/RefInitializationHoister.cs
Outdated
Show resolved
Hide resolved
src/Compilers/CSharp/Portable/Lowering/StateMachineRewriter/RefInitializationHoister.cs
Show resolved
Hide resolved
src/Compilers/CSharp/Portable/Lowering/StateMachineRewriter/MethodToStateMachineRewriter.cs
Show resolved
Hide resolved
src/Compilers/CSharp/Portable/Lowering/StateMachineRewriter/MethodToStateMachineRewriter.cs
Outdated
Show resolved
Hide resolved
src/Compilers/CSharp/Portable/Lowering/AsyncRewriter/RuntimeAsyncRewriter.cs
Outdated
Show resolved
Hide resolved
nit: consider adding Refers to: src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAsyncSpillTests.cs:17 in 8c4a58b. [](commit_id = 8c4a58b, deletion_comment = False) |
src/Compilers/CSharp/Portable/Lowering/StateMachineRewriter/CapturedSymbol.cs
Outdated
Show resolved
Hide resolved
Not related to this PR: I don't expect we'll need this kind of base method wrapper for runtime-async scenario, but it would be good to confirm and check that we have coverage. For example, Refers to: src/Compilers/CSharp/Portable/Lowering/MethodToClassRewriter.cs:168 in 8c4a58b. [](commit_id = 8c4a58b, deletion_comment = False) |
src/Compilers/CSharp/Portable/Lowering/AsyncRewriter/RuntimeAsyncRewriter.cs
Show resolved
Hide resolved
src/Compilers/CSharp/Portable/Lowering/AsyncRewriter/RuntimeAsyncRewriter.cs
Outdated
Show resolved
Hide resolved
src/Compilers/CSharp/Portable/Lowering/AsyncRewriter/RuntimeAsyncRewriter.cs
Outdated
Show resolved
Hide resolved
src/Compilers/CSharp/Portable/Lowering/AsyncRewriter/RuntimeAsyncRewriter.cs
Show resolved
Hide resolved
src/Compilers/CSharp/Portable/Lowering/AsyncRewriter/RuntimeAsyncRewriter.cs
Outdated
Show resolved
Hide resolved
src/Compilers/CSharp/Portable/Lowering/StateMachineRewriter/RefInitializationHoister.cs
Show resolved
Hide resolved
src/Compilers/CSharp/Portable/Lowering/StateMachineRewriter/RefInitializationHoister.cs
Show resolved
Hide resolved
src/Compilers/CSharp/Portable/Lowering/StateMachineRewriter/RefInitializationHoister.cs
Outdated
Show resolved
Hide resolved
src/Compilers/CSharp/Portable/Lowering/StateMachineRewriter/RefInitializationHoister.cs
Outdated
Show resolved
Hide resolved
Regarding Refers to: src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAsyncSpillTests.cs:7295 in 8c4a58b. [](commit_id = 8c4a58b, deletion_comment = False) |
jcouv
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done with review pass (commit 2). I'll look at the tests tomorrow
Not blocking this PR: I failed to notice this in previous PRs, why is the expected type on stack Refers to: src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAsyncSpillTests.cs:1476 in 8c4a58b. [](commit_id = 8c4a58b, deletion_comment = False) |
I updated the tests that have baselines with verification already, but I am not going to update the ones that do not have existing baseline running today in this pr at this point (and this particular test is one such example). In reply to: 3253071615 Refers to: src/Compilers/CSharp/Test/Emit/CodeGen/CodeGenAsyncSpillTests.cs:1259 in 83e3611. [](commit_id = 83e3611, deletion_comment = False) |
I'll add verification to In reply to: 3252991712 Refers to: src/Compilers/CSharp/Portable/Lowering/MethodToClassRewriter.cs:168 in 8c4a58b. [](commit_id = 8c4a58b, deletion_comment = False) |
jcouv
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM Thanks (commit 4) modulo PEVerify errors hit in CI
|
@RikkiGibson @dotnet/roslyn for a second review |
|
Will review tomorrow |
RikkiGibson
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM overall, had various comments and questions though.
src/Compilers/CSharp/Portable/Lowering/AsyncRewriter/RuntimeAsyncRewriter.cs
Outdated
Show resolved
Hide resolved
src/Compilers/CSharp/Portable/Lowering/StateMachineRewriter/RefInitializationHoister.cs
Outdated
Show resolved
Hide resolved
src/Compilers/CSharp/Portable/Lowering/AsyncRewriter/RuntimeAsyncRewriter.cs
Outdated
Show resolved
Hide resolved
| IL_001e: call "void System.Runtime.CompilerServices.AsyncHelpers.UnsafeAwaitAwaiter<System.Runtime.CompilerServices.YieldAwaitable.YieldAwaiter>(System.Runtime.CompilerServices.YieldAwaitable.YieldAwaiter)" | ||
| IL_0023: ldloca.s V_0 | ||
| IL_0025: call "void System.Runtime.CompilerServices.YieldAwaitable.YieldAwaiter.GetResult()" | ||
| IL_002a: ldfld "int S.i" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
huh, so this is operating on the "S" pushed to stack by IL_0001: ldobj? i.e. we can actually hoist 'this' by-value to the stack, an explicit local is not needed? Neat.
Maybe a similar thing as the other test which eliminated the temp for 'this'.
jcouv
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM Thanks (commit 7)
e1e7564 to
74f635b
Compare
jjonescz
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
last commit LGTM
d2b22f9 to
3679665
Compare
|
Rebased to make the merge clean; I want to keep these two as separate commits in history for future reference, so squash wouldn't have worked. |
jcouv
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM Thanks (commit 2)
Implements hoisting of ref locals in runtime async. Closes #79763. Commit 1 is a refactoring to extract the direct hoisting logic into a helper type that can be used by runtime async as well, and it has no semantic impact beyond that. Commit 2 actually implements the changes in runtime async, so I recommend reviewing this commit-by-commit for ease of reading. @jcouv @RikkiGibson @dotnet/roslyn-compiler for review.
Relates to test plan #75960