@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.