Problem using inline assembly

I'm trying to read the stack pointer using inline assembly code. The code I have is:

#![feature(asm)]

#[cfg(target_arch = "x86_64")]
fn read_sp() -> i64  {
    let sp: i64;
    unsafe {
        asm!("movl %rsp, $0" : "=0"(sp) );
    }
    sp
}

fn main() {
    println!("SP: {}",read_sp());
}

But when I try to compile I'm triggering an assertion:

$ rustc test.rs
rustc: /home/gabriel/projs/rust/src/llvm/lib/IR/InlineAsm.cpp:47: llvm::InlineAsm::InlineAsm(llvm::PointerType*, const string&, const string&, bool, bool, llvm::InlineAsm::AsmDialect): Assertion `Verify(getFunctionType(), constraints) && "Function type not legal for constraints!"' failed.
Aborted (core dumped)

From what I can tell from looking at the assertion my constraint isn't written correctly. I've tried a few different variations but I'm not sure what I need to fix. I'm compiling rust in debug mode now, but while I'm waiting I thought I'd ask on the forum to see if anyone has suggestions that might help solve the problem.

Try asm!("movq %rsp, $0" : "=r"(sp) );

That fixes the crashing on the assertion. I still need to test to see if it's actually reading the stack pointer correctly.

As a conceptual question how does the compiler know to associate $0 with the variable sp if I don't explicitly list that as a constraint?

Another version is the following:

#![feature(asm)]

#[cfg(target_arch = "x86_64")]
fn read_sp() -> i64  {
    let sp: i64;
    unsafe {
        asm!("" : "={sp}"(sp) );
    }
    sp
}

fn main() {
    let x = 1;
    println!("SP: 0x{:x}\n&x: {:p}",read_sp(), &x);
}

The x variable is to give a pointer into the stack as a comparison/validation of read_sp, e.g. playpen prints out something like:

SP: 0x7fff03079330
&x: 0x7fff03079394

This is using LLVM's support for naming specific registers in constraints, so LLVM is reading the value of sp from the sp register (automatically extended to the appropriate rsp/esp/... version). That said, the explicit mov version gives essentially the same behaviour.

The number in $0 variables refers to the position in the parameter list, so "=r"(sp) is the 0th parameter and hence the compiler will replace $0 with whatever register it chooses to use for sp.

2 Likes

Thanks this is a very helpful explanation. I saw syntax in gcc inline asm documentation for how to declare a variable that takes it's value from a register, but I didn't know how to translate to convert it to work for rust. It looks like your code shows how to do this and it would avoid the extra mov instruction.

As a related question I'm curious as to my my original asm statement caused the compiler to crash. Since the mov instruction can work with memory or a register would it ever be valid to let the compiler pick whatever it wants (and is there syntax for that). Or does the constraint need to explicitly specify that the variable in a register or memory (depending on the choice).

It doesn't actually avoid the mov instruction in general. Both versions of read_sp compile to mov %rsp, %rax (without inlining).

It crashed because =0 just isn't a legal constraint for LLVM inline asm, apparently. It's not the same as gcc/clang's asm.

I don't know whether LLVM supports handling memory locations and registers uniformly, but they recently landed some documentation, which may clarify.

1 Like

Thanks again. I got the code that I need working now and learned something in the process too.