[Pre-RFC]: Inline assembly

For a real-world example, here is some assembly code (ARM64) which I am using in my current project:

// Common code for interruptible syscalls
macro_rules! asm_interruptible_syscall {
    () => {
        r#"
            # If a signal interrupts us between 0 and 1, the signal handler
            # will rewind the PC back to 0 so that the interrupt flag check is
            # atomic.
            0:
                ldrb ${0:w}, $2
                cbnz ${0:w}, 2f
            1:
               svc #0
            2:

            # Record the range of instructions which should be atomic.
            .section interrupt_restart_list, "aw"
            .quad 0b
            .quad 1b
            .previous
        "#
    };
}

// There are other versions of this function with different numbers of
// arguments, however they all share the same asm code above.
#[inline]
pub unsafe fn interruptible_syscall3(
    interrupt_flag: &AtomicBool,
    nr: usize,
    arg0: usize,
    arg1: usize,
    arg2: usize,
) -> Interruptible<usize> {
    let result;
    let interrupted: u64;
    asm!(
        asm_interruptible_syscall!()
        : "=&r" (interrupted)
          "={x0}" (result)
        : "*m" (interrupt_flag)
          "{x8}" (nr as u64)
          "{x0}" (arg0 as u64)
          "{x1}" (arg1 as u64)
          "{x2}" (arg2 as u64)
        : "x8", "memory"
        : "volatile"
    );
    if interrupted == 0 {
        Ok(result)
    } else {
        Err(Interrupted)
    }
}

This is what it would look like under the original proposed syntax (I made one minor change, I inverted in the volatile flag and renamed it to pure).

// Common code for interruptible syscalls
macro_rules! asm_interruptible_syscall {
    () => {
        r#"
            # If a signal interrupts us between 0 and 1, the signal handler
            # will rewind the PC back to 0 so that the interrupt flag check is
            # atomic.
            0:
                ldrb {interrupted:w}, {interrupt_flag}
                cbnz {interrupted:w}, 2f
            1:
               svc #0
            2:

            # Record the range of instructions which should be atomic.
            .section interrupt_restart_list, "aw"
            .quad 0b
            .quad 1b
            .previous
        "#
    };
}

// There are other versions of this function with different numbers of
// arguments, however they all share the same asm code above.
#[inline]
pub unsafe fn interruptible_syscall3(
    interrupt_flag: &AtomicBool,
    nr: usize,
    arg0: usize,
    arg1: usize,
    arg2: usize,
) -> Interruptible<usize> {
    let result;
    let interrupted: u64;
    asm!(
        asm_interruptible_syscall!(),
        interrupted = out (reg) interrupted,
        interrupt_flag = in (mem) interrupt_flag, // Does (mem) take an address or an lvalue?
        lateout ("x0") result,
        in ("x8") nr as u64,
        in ("x0") arg0 as u64,
        in ("x1") arg1 as u64,
        in ("x2") arg2 as u64,
        clobber("x8", "memory"),
        // volatile is implied by the absence of the "pure" flag
    );
    if interrupted == 0 {
        Ok(result)
    } else {
        Err(Interrupted)
    }
}

I feel that the new syntax is a lot nicer to use, especially since it supports named parameters and doesn’t require outputs to come before inputs. To make it easier to use in macros, I would suggest make the ordering fully flexible: clobbers and flags can occur at any position and multiple clobber lists are allowed (a union of them is used as the actual clobber list).

Having to explicitly say lateout instead of out is great since it makes you double-check that you aren’t reading any inputs after writing to the output. Making out a safe default will greatly help people who are new to inline asm.

2 Likes