MORE INLINE ASSEMBLY – Creating Our Own Fibers

We need to explain the new concepts we introduced here. The assembly calls the function switch (the function is tagged with #[no_mangle] so we can call it by name). The in(“rdi”) old and in(“rsi”) new arguments place the value of old and new to the rdi and rsi registers, respectively. The System V ABI for x86-64 states that the rdi register holds the first argument to a function and rsi holds the second argument.

The clobber_abi(“C”) argument tells the compiler that it may not assume any that any general-purpose registers are preserved across the asm! block. The compiler will emit instructions to push the registers it uses to the stack and restore them when resuming after the asm! block.

If you take one more look at the list in Figure 5.1, we already know that we need to take special care with registers that are marked as callee saved. When calling a normal function, the compiler will insert code* to save/restore all the non-callee-saved, or caller saved, registers before calling a function so it can resume with the correct state when the function returns. Since we marked the function we’re calling as #[naked], we explicitly told the compiler to not insert this code, so the safest thing is to make sure the compiler doesn’t assume that it can rely on any register being untouched when it resumes after the call we make in our asm! block.

*In some instances, the compiler will know that a register is untouched by the function call since it controls the register usage in both the caller and the callee and it will not emit any special instructions to save/restore registers they know will be untouched when the function returns

The self.threads.len() > 0 line at the end is just a way for us to prevent the compiler from optimizing our code away. This happens to me on Windows but not on Linux, and it is a common problem when running benchmarks, for example. There are other ways of preventing the compiler from optimizing this code, but I chose the simplest way I could find. As long as it’s commented, it should be OK to do. The code never reaches this point anyway.

Next up is our spawn function. I’ll present the function first and guide you through it after:
pub fn spawn(&mut self, f: fn()) {
    let available = self
        .threads
        .iter_mut()
        .find(|t| t.state == State::Available)
        .expect(“no available thread.”);
    let size = available.stack.len();
    unsafe {
        let s_ptr = available.stack.as_mut_ptr().offset(size as isize);
        let s_ptr = (s_ptr as usize & !15) as *mut u8;
        std::ptr::write(s_ptr.offset(-16) as *mut u64, guard as u64);
        std::ptr::write(s_ptr.offset(-24) as *mut u64, skip as u64);
        std::ptr::write(s_ptr.offset(-32) as *mut u64, f as u64);
        available.ctx.rsp = s_ptr.offset(-32) as u64;
    }
    available.state = State::Ready;
}
} // We close the `impl Runtime` block here

Leave a Reply

Your email address will not be published. Required fields are marked *

Related Post