Implementing our own fibers 121 When we spawn a new fiber (or userland thread), we first check if there are any available userland threads (threads in Available state). If we run out of threads, we panic in this scenario, but there are several (better) ways to handle that. We'll keep things simple for now. When we find an available thread, we get the stack length and a pointer to our u8 byte array. In the next segment, we have to use some unsafe functions. We'll explain the functions we refer to here later, but this is where we set them up in our new stack so that they're called in the right order for our runtime to work. First, we make sure that the memory segment we'll use is 16-byte-aligned. Then, we write the address to our guard function that will be called when the task we provide finishes and the function returns. Second, we'll write the address to a skip function, which is there just to handle the gap when we return from f, so that guard will get called on a 16-byte boundary. The next value we write to the stack is the address to f. Why do we need the skip function? Remember how we explained how the stack works? We want the f function to be the first to run, so we set the base pointer to f and make sure it's 16-byte aligned. We then push the address to the skip function and lastly the guard function. Since, skip is simply one instruction, ret, doing this makes sure that our call to guard is 16-byte aligned so that we adhere to the ABI requirements. After we've written our function pointers to the stack, we set the value of rsp, which is the stack pointer to the address of our provided function, so we start executing that first when we are scheduled to run. Lastly, we set the state to Ready, which means we have work to do and that we are ready to do it. Remember, it's up to our scheduler to actually start up this thread. We're now finished implementing our Runtime, if you got all this, you basically understand how fibers/green threads work. However, there are still a few details needed to make it all work. Guard, skip, and switch functions There are a few functions we've referred to that are really important for our Runtime to actually work. Fortunately, all but one of them are extremely simple to understand. We'll start with the guard function: fn guard() { unsafe { let rt_ptr = RUNTIME as *mut Runtime; (*rt_ptr). t_return(); }; }