ARM register dual loads/stores with asm! instead of llvm_asm!

I just tried to switch from llvm_asm to asm, but found that for ARM, register dual loads/stores are apparently no longer supported. These work on two 32-bit registers at once (e.g., ldrd can load a 64-bit value into r2 and r3). More specifically, with llvm_asm the following code compiles just fine:

let res: u64;
llvm_asm!(
    "ldrd $0, [$1]"
    : "=r"(res)
    : "r"(addr)
    : : "volatile"
);

But trying the equivalent with asm:

let res: u64;
asm!(
    "ldrd {0}, [{1}]",
    out(reg) res,
    in(reg) addr,
);

fails with:

error: type `u64` cannot be used with this register class
   |
24 |         out(reg) res,
   |                  ^^^
   |
   = note: register class `reg` supports these types: i8, i16, i32, f32

In the documentation I cannot find anything about that. Is this indeed no longer supported (why?) or am I missing something?

I’ll caveat with I have never touched assembly for anything, but have you tried using dreg instead of reg. Based on one of the tables in that link, it looks like reg on ARM only supports up to 32 bits and dreg supports 64 bit types.

I saw that as well, but that refers to the floating point registers provided by the vfp2 extension. I need two consecutive 32-bit general-purpose registers, starting with an even one (e.g., r4 and r5). Additionally, the compiler should choose them for optimal performance.

Ah, that makes sense. I would ask on the inline asm zulip channel where some of the implementors of asm! occassionally are, since I don't think they regularly come to this site.

1 Like

The other complicating factor I ran into when playing around with it, was that even if you used two u32's to get the output which did compile down to something similar to the llvm_asm! version was that I didn't seem possible to use out(reg) to specify that the first register must be an even numbered register which seems to be a requirement of ldrd. So it turned into a coin flip whether it would compile (unless you specified the specific registers you wanted to use.)

EDIT:
And in case anyone is interested, if you do specify registers in the asm! version, these two compile to the same assembly
godbolt

fn double_read_llvm_asm(inp: &u64) -> u64 {
    let out: u64;
    unsafe {
        llvm_asm! {
            "ldrd $0, [$1]"
            : "=r"(out)
            : "r"(inp)
            : : "volatile"
        }
    }
    out
}

fn double_read_asm(inp: &u64) -> u64 {
    let lo: u32;
    let hi: u32;
    unsafe {
        asm! {
            "ldrd r0, r1, [{0}]",
            in(reg) inp,
            lateout("r0") lo,
            out("r1") hi,
        }
    }
    ((hi as u64) << 32) | (lo as u64)
}

Thanks, I'll try that :slight_smile:

Exactly, that's one workaround, but of course suboptimal, because the registers are hardcoded.

I also have the feeling that this is all handled by LLVM (which also knows the constraints like two consecutive registers etc.) and the Rust compiler should just forward it without complaining (like llvm_asm probably does). But I might be wrong with that.

In case someone else has the same problem: I asked on the zulip channel and Amanieu d'Antras, the author of the asm! macro, told me that this is indeed currently not supported, but that it could be added (not sure if that will happen though).

1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.