Skip to content

JIT: Fix edge cases around async call definitions with exceptional flow#121583

Merged
jakobbotsch merged 5 commits into
dotnet:mainfrom
jakobbotsch:fix-121564
Nov 14, 2025
Merged

JIT: Fix edge cases around async call definitions with exceptional flow#121583
jakobbotsch merged 5 commits into
dotnet:mainfrom
jakobbotsch:fix-121564

Conversation

@jakobbotsch

@jakobbotsch jakobbotsch commented Nov 13, 2025

Copy link
Copy Markdown
Member

We had special logic to optimize the case where an async call defines a local, avoiding capturing the local as part of the live state since its value would be overridden on resumption anyway.

This does not handle the case where the callee throws an exception and we continue internally in the function. In that case we may still have needed to capture the local state. Consider the following example:

int x = GetValue();
try
{
    x = await IntThrows();
}
catch
{
}

return x;

Here we need to capture the value of x in the continuation as we need it if IntThrows throws. The previous logic optimized away this capture.

For cases where we have liveness for the defined local we can use liveness for precision when we have exceptional flow. Liveness will tell us that x is live on the IntThrows call here, precisely because of the exceptional flow. If there was no exceptional flow, x would not be live. We can still use the previous optimization for address exposed or tier0 cases.

Also fix when we update the live set around the async calls we are transforming. Before this change we were updating it before the transformation. This handling was imprecise for return buffer definitions, but it would not usually cause problems beyond overestimating the live set.

Fix #121564

We had special logic to optimize the case where an async call defines a
local, avoiding capturing the local as part of the live state since its
value would be overridden on resumption anyway.

This does not handle the case where the callee throws an exception and
we continue internally in the function. In that case we may still have
needed to capture the local state. Consider the following example:

```csharp
int x = GetValue();
try
{
    x = await IntThrows();
}
catch
{
}

return x;
```

Here we need to capture the value of `x` in the continuation as we need
it if `IntThrows` throws. The previous logic optimized away this
capture.

For cases where we have liveness for the defined local we can use
liveness for precision when we have exceptional flow. Liveness will tell
us that `x` is live on the `IntThrows` call here, precisely because of
the exceptional flow. If there was no exceptional flow, `x` would not be
live. We can still use the previous optimization for address exposed or
tier0 cases.

Also fix when we update the live set around the async calls we are
transforming. Before this change we were updating it before the
transformation. This handling was imprecise for return buffer
definitions, but it would not usually cause problems beyond
overestimating the live set.
@github-actions github-actions Bot added the area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI label Nov 13, 2025
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @JulieLeeMSFT, @jakobbotsch
See info in area-owners.md if you want to be subscribed.

This reverts commit e6fa1b5.
Comment thread src/tests/async/simple-eh/simple-eh.csproj
Removed nested try-finally and simplified exception handling.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This PR fixes edge cases in the JIT's async call handling where exceptional flow (try/catch/finally blocks) could lead to incorrect optimization of local variable capture. Previously, locals fully defined by an async call were optimized away from the continuation state, but this was incorrect when the callee threw an exception that was caught internally, as the original value of the local was still needed.

Key changes:

  • Introduced liveness-aware handling of exceptional flow for async call definitions
  • Added new helper method HasNonContextRestoreExceptionalFlow to detect when exceptional flow requires preserving local state
  • Moved liveness updates to occur at the correct time during async call transformation
  • Enabled and added comprehensive test cases covering both value types and struct types with exceptional flow

Reviewed Changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
src/tests/async/simple-eh/simple-eh.csproj Removes DisableProjectBuild flag to enable the test execution
src/tests/async/simple-eh/simple-eh.cs Adds comprehensive test cases for async calls that define locals but throw exceptions, covering both primitive int types and struct types
src/coreclr/jit/importercalls.cpp Renames function from impSetupAndSpillForAsyncCall to impSetupAsyncCall to reflect the simplified functionality
src/coreclr/jit/compiler.h Updates function declaration to match the renamed function
src/coreclr/jit/async.h Adds declaration for new helper method HasNonContextRestoreExceptionalFlow
src/coreclr/jit/async.cpp Implements the core fix: adds exceptional flow detection, updates liveness tracking timing, and improves canonicalization logic to preserve locals when needed for exception handling

Comment thread src/coreclr/jit/async.cpp Outdated
Comment thread src/coreclr/jit/async.cpp Outdated
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@jakobbotsch

Copy link
Copy Markdown
Member Author

cc @dotnet/jit-contrib PTAL @AndyAyersMS

@jakobbotsch jakobbotsch merged commit d00f7be into dotnet:main Nov 14, 2025
108 of 118 checks passed
@jakobbotsch jakobbotsch deleted the fix-121564 branch November 14, 2025 22:48
@github-actions github-actions Bot locked and limited conversation to collaborators Dec 15, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

area-CodeGen-coreclr CLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMI runtime-async

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[RuntimeAsync] NullReferenceException crash in some scenarios in System.Text.Json.Serialization.Tests.PipeTests

4 participants