To implement coroutines and generates we are going to use multiple call stacks. If you consider the ABI, there is nothing restricting you to using rsp exactly as intended (incrementing/decrementing as a pointer to the top of a single stack), it just always needs to point to some memory with sufficient allocated space "above" it (at lower addresses) so that it doesn't overflow into anything bad. In particular it's possible to "switch" stacks by changing rsp from pointing to the top of one stack to the top of another.

In it's simplest form this could look like

mov rsp , <new stack> ; set the stack pointer to point at the new stack jmp <some function> ; and start executing some function

At this point some function will execute just fine on a new stack, as long as it doesn't return. As it turns out it's easy to specify that it should never return in rust, we just give it a return type of ! . We also want some way of actually calling this code (as well as a consistent environment for the next step), so we are going to wrap it in a function. Passing in the address of the bottom of the stack as the first parameter ( rdi ), and the address of the function to call as the second parameter ( rsi ).

mov rsp , rdi jmp rsi

To be actually useful we are going to want some way of continuing on the original stack, otherwise this just turns into a fancy way to get a bigger stack . To do this we will store the callee save registers onto the stack, and pass the pointer to the current stack to the called function.

sub rsp , 64 ; Allocate space on the stack for the callee save registers mov [ rsp ] , rbx ; copy all the callee save registers into it mov [ rsp + 8 ] , rbp mov [ rsp + 16 ] , r12 mov [ rsp + 24 ] , r13 mov [ rsp + 32 ] , r14 mov [ rsp + 40 ] , r15 stmxcsr [ rsp + 48 ] ; We need to use special instructions for the control word registers fnstcw [ rsp + 52 ] ; but these are both effectively just mov instructions. mov rax , rsp ; swap the address of the current stack (rsp) and new stack (rdi, the first argument) mov rsp , rdi mov rdi , rax jmp rsi ; jump to the new function

This is now almost the start function I actually want, the only addition is it turns out to be very useful to forward an argument to the function (a pointer to some data that we can use to pass arguments and move objects into the new stack).

sub rsp , 64 mov [ rsp ] , rbx mov [ rsp + 8 ] , rbp mov [ rsp + 16 ] , r12 mov [ rsp + 24 ] , r13 mov [ rsp + 32 ] , r14 mov [ rsp + 40 ] , r15 stmxcsr [ rsp + 48 ] fnstcw [ rsp + 52 ] mov rax , rsp mov rsp , rdi mov rdi , rax mov rax , rsi ; save the address we want to jump to mov rsi , rdx ; move the pointer we are forwarding from rdx (arg 3) to rsi (arg 2). jmp rax ; jump to the new function, which we saved in rax

To switch back from the new stack we can switch rsp back to the stack pointer we were passed, restore the callee save registers, and return. I'm going to wrap this code in a function, which expects that pointer as an argument (in rdi ). Note that the return statement will pop the return pointer from the stack we just switched to and start executing there, not the stack where this " return " function (which I have called exit_to ) is called from.

mov rsp , rdi ; Switch stack pointer back mov rbx , [ rsp ] ; copy all the callee save registers back from the stack mov rbp , [ rsp + 8 ] mov r12 , [ rsp + 16 ] mov r13 , [ rsp + 24 ] mov r14 , [ rsp + 32 ] mov r15 , [ rsp + 40 ] ldmxcsr [ rsp + 48 ] ; Again we need to use special instructions for the control word fldcw [ rsp + 52 ] ; registers but these are both effectively just mov instructions. add rsp , 64 ; free the space we allocated on the stack ret ; pop and jump to the return address

Like our initial implementation of switching to a new stack, this gives no way to switch back, though in this case it still ends up being a useful function. We can combine the two functions to make a switch function. The only question is what to do with the original stack pointer, since we aren't calling a function we can pass it to, and returning it ends up being fairly inconvenient. Instead we'll take a second argument as the address at which we should store the stack pointer (the first argument being the stack pointer to switch to).