Skip to content

[RuntimeAsync] Merge from labs, all except JIT #114675

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

Merged
merged 30 commits into from
Apr 28, 2025
Merged
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
a0e9046
Merge from RTL, all except JIT
VSadov Apr 15, 2025
7e088a9
fix mono build
VSadov Apr 15, 2025
eebd959
Removed public API changes.
VSadov Apr 15, 2025
af7a529
require FEATURE_RUNTIME_ASYNC
VSadov Apr 16, 2025
924cfa1
whitespace changes
VSadov Apr 16, 2025
2eefbea
redundant change
VSadov Apr 16, 2025
43c46cd
removed GetNativeCodeSlot
VSadov Apr 16, 2025
ff126fe
Apply suggestions from code review
VSadov Apr 16, 2025
ff6dc4c
fix the build
VSadov Apr 16, 2025
f305713
add a frameless path in IL_ThrowExact (except for X86)
VSadov Apr 16, 2025
029a53a
keep amTracker untill after CreateAndLinkNewILStubMethodDesc
VSadov Apr 16, 2025
b59344d
no comment numbering
VSadov Apr 16, 2025
1b0f737
undo changes to IsRuntimeSupplied
VSadov Apr 18, 2025
c4d3e2e
move async thunk generation code into separate file
VSadov Apr 18, 2025
bd93053
Moved infrastructure helpers into CompilerServices.AsyncHelpers
VSadov Apr 18, 2025
f1d33d4
Added a section on Async calling convention in ABI doc
VSadov Apr 19, 2025
7b6e9c9
Apply suggestions from code review
VSadov Apr 20, 2025
4cbc09b
undo unnecessary change in stubgen
VSadov Apr 21, 2025
e466e5e
Use the newest API
VSadov Apr 23, 2025
31744c8
Use approved value for MethodImplAttributes.Async
VSadov Apr 23, 2025
42e42e1
added some TODOs
VSadov Apr 26, 2025
89a68b1
Update src/coreclr/vm/prestub.cpp
VSadov Apr 26, 2025
2489290
some simple PR feedback
VSadov Apr 26, 2025
74f4da3
Fail if EnC targets an Async method
VSadov Apr 26, 2025
61c63d3
moved IsEnCAddedMethod bit to Flags4
VSadov Apr 27, 2025
55caeca
Implement IL_ThrowExact for x86/funclets
filipnavara Apr 24, 2025
ad5cb52
Addres PR feedback
filipnavara Apr 24, 2025
6f2c815
couple TODOs for EnC NYI
VSadov Apr 27, 2025
7dbc621
add a simple ldtoken test scenario for Task-returning method
VSadov Apr 27, 2025
49f651f
Put back BypassReadyToRun untill we are sure about R2R support
VSadov Apr 27, 2025
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
30 changes: 29 additions & 1 deletion docs/design/coreclr/botr/clr-abi.md
Original file line number Diff line number Diff line change
@@ -96,6 +96,34 @@ There is no defined/enforced/declared ordering between the generic parameter and
call(["this" pointer] [return buffer pointer] [generics context|varargs cookie] [userargs]*)
```

## Async

Async calling convention is additive to other calling conventions when supported. The set of scenarios is constrained to regular static/virtual calls and does not, for example, support PInvokes or varargs. At the minimum ordinary static calls, calls with `this` parameter or generic hidden parameters are supported.

Async calling convention adds an extra `Continuation` parameter and an extra return, which sematically takes precedence when not `null`. A non-null `Continuation` upon return signals that the computation is not complete and the formal result is not ready. A non-null argument means that the function is resuming and should extract the state from the `Continuation` and continue execution (while ignoring all other arguments).
Copy link
Member

Choose a reason for hiding this comment

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

Are the other arguments expected to have null/default values when Continuation argument is non-null?

Copy link
Member Author

Choose a reason for hiding this comment

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

The arguments need to have some value, so we do set other arguments to default values when calling with a continuation.
I am not sure it is strictly required as they are not supposed to be used if continuation is passed in. Perhaps nothing will break if arguments contain random values. Managed refs might need to be valid though - actual objects or null, for GC reasons.

Maybe it is worth to document that arguments/returns in the presence of continuation are supposed to have default values, although I am not sure we would check/enforce that.

Copy link
Member Author

@VSadov VSadov Apr 19, 2025

Choose a reason for hiding this comment

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

Actually it looks like we can return random values for non-gc returns when returning continuation.

This is in the JIT (so not in this PR):

void CodeGen::genReturnSuspend(GenTreeUnOp* treeNode)
{
    GenTree* op = treeNode->gtGetOp1();
    assert(op->TypeIs(TYP_REF));

    regNumber reg = genConsumeReg(op);
    inst_Mov(TYP_REF, REG_ASYNC_CONTINUATION_RET, reg, /* canSkip */ true);

    ReturnTypeDesc retTypeDesc = compiler->compRetTypeDesc;
    unsigned       numRetRegs  = retTypeDesc.GetReturnRegCount();
    for (unsigned i = 0; i < numRetRegs; i++)
    {
        if (varTypeIsGC(retTypeDesc.GetReturnRegType(i)))     <=====  zeroing only if GC
        {
            regNumber returnReg = retTypeDesc.GetABIReturnReg(i, compiler->info.compCallConv);
            instGen_Set_Reg_To_Zero(EA_PTRSIZE, returnReg);
        }
    }

    genMarkReturnGCInfo();
}

Copy link
Member Author

Choose a reason for hiding this comment

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

The resumption stub does fill arguments with default values, but that might be because it is in IL

Copy link
Member Author

Choose a reason for hiding this comment

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

Maybe it is worth to document that arguments/returns in the presence of continuation are supposed to have default values, although I am not sure we would check/enforce that.

Or perhaps just mentioning that they are unused is good enough. Caller might zero them out or might not - whatever is more convenient.

Copy link
Member

Choose a reason for hiding this comment

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

Have we considered generating the resumption stub in the JIT? It feels like we are leaving some perf and code size on the table here.

Copy link
Member Author

@VSadov VSadov Apr 20, 2025

Choose a reason for hiding this comment

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

It is possible, although resumption is not a hot path. A lot happens between suspension and resumption.

I think every part will be revisited eventually with perf in mind, but resumption may not be the most impactful area.
This is where, I think, getting correct and reasonably fast behavior is good enough for now.

Copy link
Member

Choose a reason for hiding this comment

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

I think eventually we will want to add support for generating multiple entry points for async functions and have the JIT generate an entry point for resumption and an entry point for fresh calls. It will also remove the extra null argument being passed on the synchronous path.

This is the 4th item under https://github.com/dotnet/runtimelab/blob/feature/async2-experiment/docs/design/features/runtime-handled-tasks.md#potential-future-improvements

Copy link
Member

Choose a reason for hiding this comment

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

It's not totally trivial to completely remove the resumption stub, even with multiple entry points -- currently the resumption stub has the important job of setting up space for the arguments. The resumed function uses that space to store parameters from the continuation when parameters are live. Particularly for implicit byrefs that space exists on the stack frame of the resumption stub and cannot easily be allocated by the callee (without deoptimizing e.g. tailcalls).


The `Continuation` is a managed object and needs to be tracked accordingly. The GC info includes the continuation result as live at Async call sites.

### Returning `Continuation`
To return `Continuation` we use a volatile/calee-trash register that cannot be used to return the actual result.

| arch | `REG_ASYNC_CONTINUATION_RET` |
| ------------- | ------------- |
| x86 | ecx |
| x64 | rcx |
| arm | r2 |
| arm64 | x2 |
| risc-v | a2 |

### Passing `Continuation` argument
The `Continuation` parameter is passed at the same position as generic instantiation parameter or immediately after, if both present.

```
call(["this" pointer] [return buffer pointer] [generics context] [continuation] [userargs]) // not x86

call(["this" pointer] [return buffer pointer] [userargs] [generics context] [continuation]) // x86
```

## AMD64-only: by-value value types

Just like native, AMD64 has implicit-byrefs. Any structure (value type in IL parlance) that is not 1, 2, 4, or 8 bytes in size (i.e., 3, 5, 6, 7, or >= 9 bytes in size) that is declared to be passed by value, is instead passed by reference. For JIT generated code, it follows the native ABI where the passed-in reference is a pointer to a compiler generated temp local on the stack. However, there are some cases within remoting or reflection where apparently stackalloc is too hard, and so they pass in pointers within the GC heap, thus the JITed code must report these implicit byref parameters as interior pointers (BYREFs in JIT parlance), in case the callee is one of these reflection paths. Similarly, all writes must use checked write barriers.
@@ -729,4 +757,4 @@ MyStruct Test2()
// We can use memset here
return default;
}
```
```
Original file line number Diff line number Diff line change
@@ -205,6 +205,7 @@
<Compile Include="$(BclSourcesRoot)\System\Runtime\CompilerServices\CastHelpers.cs" />
<Compile Include="$(BclSourcesRoot)\System\Runtime\CompilerServices\GenericsHelpers.cs" />
<Compile Include="$(BclSourcesRoot)\System\Runtime\CompilerServices\InitHelpers.cs" />
<Compile Include="$(BclSourcesRoot)\System\Runtime\CompilerServices\AsyncHelpers.CoreCLR.cs" />
<Compile Include="$(BclSourcesRoot)\System\Runtime\CompilerServices\RuntimeHelpers.CoreCLR.cs" />
<Compile Include="$(BclSourcesRoot)\System\Runtime\CompilerServices\StaticsHelpers.cs" />
<Compile Include="$(BclSourcesRoot)\System\Runtime\CompilerServices\VirtualDispatchHelpers.cs" />
Loading
Oops, something went wrong.
Loading
Oops, something went wrong.