LLVM coroutines are functions that have one or more suspend points. When a suspend point is reached, the execution of a coroutine is suspended and control is returned back to its caller. A suspended coroutine can be resumed to continue execution from the last suspend point or it can be destroyed.

In the following example, we call function f (which may or may not be a coroutine itself) that returns a handle to a suspended coroutine (coroutine handle) that is used by main to resume the coroutine twice and then destroy it:

define i32 @main () { entry: %hdl = call i8 * @f ( i32 4 ) call void @llvm.coro.resume ( i8 * %hdl ) call void @llvm.coro.resume ( i8 * %hdl ) call void @llvm.coro.destroy ( i8 * %hdl ) ret i32 0 }

In addition to the function stack frame which exists when a coroutine is executing, there is an additional region of storage that contains objects that keep the coroutine state when a coroutine is suspended. This region of storage is called the coroutine frame. It is created when a coroutine is called and destroyed when a coroutine either runs to completion or is destroyed while suspended.

LLVM currently supports two styles of coroutine lowering. These styles support substantially different sets of features, have substantially different ABIs, and expect substantially different patterns of frontend code generation. However, the styles also have a great deal in common.

In all cases, an LLVM coroutine is initially represented as an ordinary LLVM function that has calls to coroutine intrinsics defining the structure of the coroutine. The coroutine function is then, in the most general case, rewritten by the coroutine lowering passes to become the “ramp function”, the initial entrypoint of the coroutine, which executes until a suspend point is first reached. The remainder of the original coroutine function is split out into some number of “resume functions”. Any state which must persist across suspensions is stored in the coroutine frame. The resume functions must somehow be able to handle either a “normal” resumption, which continues the normal execution of the coroutine, or an “abnormal” resumption, which must unwind the coroutine without attempting to suspend it.

Switched-Resume Lowering¶ In LLVM’s standard switched-resume lowering, signaled by the use of llvm.coro.id , the coroutine frame is stored as part of a “coroutine object” which represents a handle to a particular invocation of the coroutine. All coroutine objects support a common ABI allowing certain features to be used without knowing anything about the coroutine’s implementation: A coroutine object can be queried to see if it has reached completion with llvm.coro.done .

. A coroutine object can be resumed normally if it has not already reached completion with llvm.coro.resume .

. A coroutine object can be destroyed, invalidating the coroutine object, with llvm.coro.destroy . This must be done separately even if the coroutine has reached completion normally.

. This must be done separately even if the coroutine has reached completion normally. “Promise” storage, which is known to have a certain size and alignment, can be projected out of the coroutine object with llvm.coro.promise . The coroutine implementation must have been compiled to define a promise of the same size and alignment. In general, interacting with a coroutine object in any of these ways while it is running has undefined behavior. The coroutine function is split into three functions, representing three different ways that control can enter the coroutine: the ramp function that is initially invoked, which takes arbitrary arguments and returns a pointer to the coroutine object; a coroutine resume function that is invoked when the coroutine is resumed, which takes a pointer to the coroutine object and returns void ; a coroutine destroy function that is invoked when the coroutine is destroyed, which takes a pointer to the coroutine object and returns void . Because the resume and destroy functions are shared across all suspend points, suspend points must store the index of the active suspend in the coroutine object, and the resume/destroy functions must switch over that index to get back to the correct point. Hence the name of this lowering. Pointers to the resume and destroy functions are stored in the coroutine object at known offsets which are fixed for all coroutines. A completed coroutine is represented with a null resume function. There is a somewhat complex protocol of intrinsics for allocating and deallocating the coroutine object. It is complex in order to allow the allocation to be elided due to inlining. This protocol is discussed in further detail below. The frontend may generate code to call the coroutine function directly; this will become a call to the ramp function and will return a pointer to the coroutine object. The frontend should always resume or destroy the coroutine using the corresponding intrinsics.