@nsc and I have been looking at the assembly the compiler generates for an async function call and trying to understand it. Take this example (Godbolt link):
func caller() async {
await asyncFunc()
}
@inline(never) func asyncFunc() async {
}
The assembly for the first partial func of caller (i.e. up to the await call) looks like this (commented by me).
output.caller() async -> ():
push rax
; Load size of AsyncContext required for calling asyncFunc()
mov edi, dword ptr [rip + (async function pointer to output.asyncFunc() async -> ())+4]
; Allocate AsyncContext for async function call.
call swift_task_alloc@PLT
; Store new AsyncContext at [current AsyncContext + 16]
; ??? Why???
mov qword ptr [r14 + 16], rax
; Set current AsyncContext as parent of new AsyncContext (parent field is at offset 0)
mov qword ptr [rax], r14
; Set continuation in new AsyncContext (field at offset 8)
lea rcx, [rip + ((1) await resume partial function for output.caller() async -> ())]
mov qword ptr [rax + 8], rcx
; Set new AsyncContext as the current AsyncContext (passed in r14)
mov r14, rax
pop rax
; Perform async function call (tail call)
jmp (output.asyncFunc() async -> ())
I think I understand every line of this except this one:
mov qword ptr [r14 + 16], rax
What's the purpose of this? This seems to be storing the newly allocated AsyncContext (in rax) in the third field of the current AsyncContext (in r14). Looking at the class definition for AsyncContext, I don't see a third field, and I couldn't find an obvious match in any of its subclasses, either.