Compilation error using asm!("mov...") on aarch64

72 Views Asked by At

I'm running a code sample from a book on a Mac/aarch64, and getting a compilation error.

fn dereference(ptr: *const usize) -> usize {
    let mut res: usize;
    unsafe { 
        asm!("mov {0}, [{1}]", out(reg) res, in(reg) ptr)
    };
    res
}

I get:

error: expected compatible register or logical immediate
  --> src/main.rs:20:15
   |
20 |         asm!("mov {0}, [{1}]", out(reg) res, in(reg) ptr)
   |               ^
   |
note: instantiated into assembly here
  --> <inline asm>:1:10
   |
1  |     mov x8, [x0]
   |             ^

Having searched for the error message, I believe the architecture has something to do with it, but I haven't been able to figure out how to fix it yet. I'm looking at the Rust inline assembly doc, but as the topic is completely new to me, I can't map my example that is failing to compile to what I'm seeing in the doc.

2

There are 2 best solutions below

0
Peter Cordes On BEST ANSWER

mov x8, [x0] looks like x86-64 syntax with AArch64 register names. (From your answer, it looks like you just found an example that maybe failed to mention it was for x86-64, and tried compiling it for AArch64.)

AArch64 is a load/store architecture: special instructions (like ldr and str) are the only one that access memory, e.g. ldr x8, [x0]. Look at compiler output for a Rust function that derefs a pointer, e.g. Godbolt shows a pure-Rust dereference compiling for aarch64-apple-darwin to ldr x0, [x0] and ret.

By contrast, x86-64 uses mov for loads, stores, and register copies: most x86-64 instructions that read and write registers are also available with a memory source or destination. (This is one of the CISC vs. RISC differences in the designs of the two architectures.)


Read a tutorial on AArch64 asm, e.g. https://modexp.wordpress.com/2018/10/30/arm64-assembly looks reasonable; google found it when I searched for inline assembly x86-64 aarch64 ldr. It has a short section comparing x86 vs. AArch64 assembly, but that's just a table of some equivalents for reg, reg instructions. The AArch64 equivalent for movzx eax, byte [rdi] is ldrb w0, [x0], vs. movzx eax, dil being uxtb w0, w0, again with special instructions for zero-extending or sign-extending narrow loads.

Really they're quite different architectures. The way a lot of things are done is quite different, e.g. instructions don't set FLAGS by default, you need an S suffix on the mnemonic. (Except for cmp and tst which exist only to write flags.) And instead of push/pop, there's stp / ldp with post-increment addressing. (Always done as a pair of 64-bit regs to keep the stack aligned by 16.)

1
Tatiana Racheva On

Of course, after spending a bunch of time but not until I posted this question, I found the answer.

The template in Arm64 assembly should be "ldr {0}, [{1}]", not "mov {0}, [{1}]".

What helped:

  1. I decided to start from the beginning and read the Rust By Example chapter on inline assembly. I found that the simplest add example also didn't compile for me.
  2. After googling add on Arm64, I came across the assembly cheat sheet which showed that the syntax for add was simply different on this architecture from x86/x86-64 (3 arguments rather than 2).
  3. I stared more carefully at the syntax for the mov instruction and saw the issue. Then the author pointed out ldr was the correct instruction on aarch64.