PacktPublishing / Asynchronous-Programming-in-Rust

Asynchronous Programming in Rust, published by Packt

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Can you help to explain why lateout is needed in raw syscall in ch03?

npuichigo opened this issue · comments

I check the later chapter and documents of asm and still cannot understand why lateout is used for rsi and rdx here.

Besides, the description We do that by telling the compiler that there will be some unspecified data (indicated by the underscore) written to these registers. is not very clear since out means allocate an undefined value at the start of the asm code while underscore means value is disgarded at the end of the asm code, while the description confuse me.

#[cfg(target_os = "linux")]
#[inline(never)]
fn syscall(message: String) {
    let msg_ptr = message.as_ptr();
    let len = message.len();

    unsafe {
        asm!(
            "mov rax, 1",      // system call 1 is write on Linux
            "mov rdi, 1",      // file handle 1 is stdout
            "syscall",         // call kernel, software interrupt
            in("rsi") msg_ptr, // address of string to output
            in("rdx") len,     // number of bytes
            out("rax") _, out("rdi") _, lateout("rsi") _, lateout("rdx") _
        );
    }
}

Hi, and good question!

So the phrase you referred to above was aimed at both the out and lateout arguments. It's a long time since I wrote this example now, and I'm currently travelling, so I'll have to take this out of memory:

The motive for out("rax") _, out("rdi") _, lateout("rsi") _, lateout("rdx") _ is to tell the compiler that we clobber these registers. We clobber rax and rdi since we write a literal value into those registers and the compiler doesn't know that we do that. To avoid that causing problems, we have to tell the compiler to add instructions to store any state (and later restore) that it wants to preserve in these registers, since we will overwrite what's there.

The reason for adding the clobbers for rsi and rdx is a more subtle. When we call syscall, the OS might assume that it can write whatever it wants to callee saved registers (when performing a syscall we yield to the OS), so we can't be sure that they have the same value when exiting the asm! block.

If you read the rules for inline assembly, you'll see the following statement:

Any registers not specified as outputs must have the same value upon exiting the asm block as they had on entry, otherwise behavior is undefined.

  • This only applies to registers which can be specified as an input or output. Other registers follow target-specific rules.
  • Note that a lateout may be allocated to the same register as an in, in which case this rule does not apply. Code should not rely on this however since it depends on the results of register allocation.

Since we can't promise that the OS doesn't write something to the rsi and rdx registers when calling syscall, we can't really uphold that promise. As suggested in the documentation we allocate lateout to that register to basically let the compiler know that they might be clobbered before we exit the asm! block and that they might not have the same value as it had on entering it (you'll also see this mentioned in the documentation for the in operand).

The better solution is probably to use the abi_clobber operand, but I think the current solution it's good enough for our example.

I hope this helps answering your question.

is not very clear since out means allocate an undefined value at the start of the asm code while underscore means value is disgarded at the end of the asm code, while the description confuse me.

And just to be sure we're on the same page here. We don't really tell the compiler to allocate an undefined value. It means that we have to treat the value as undefined (we can't make any assumptions about what's in the register when entering the asm! block), but that's not the real point here.

What we really want to do is to make sure the compiler knows that it can't store any data in that register and assume that it will have the same value after the asm! block. The way to do that is to allocate it as an out or a lateout.

Thank you for the detailed reply. Hope you have a good trip.