0

I have the following inline assembly which used to work in Rust, but it seems there has been some changes to the syntax, and it throws error at "={ecx}(features) with the message expected type. How can I rewrite this in new assembly syntax?

use std::arch::asm;

let features: u32;
unsafe {
    asm!("mov eax, 1
          cpuid"
         : "={ecx}"(features)
         :
         : "eax"
         : "intel"
    );
}

let support: u32 = (features >> 25) & 1;
E_net4
  • 27,810
  • 13
  • 101
  • 139
terett
  • 355
  • 1
  • 8
  • 3
    Note that the inline assembly is incorrect because it does not register clobbers on `ebx` and `edx`. – fuz Mar 15 '22 at 09:58
  • @fuz What's the proper way to write this? – terett Mar 15 '22 at 09:59
  • I don't know, I don't program Rust. – fuz Mar 15 '22 at 10:00
  • 1
    Inline assembly has been stabilized in the compiler recently. Consider having a look at the [reference](https://doc.rust-lang.org/nightly/reference/inline-assembly.html). – E_net4 Mar 15 '22 at 10:23
  • Why not use compiler intrinsics: https://doc.rust-lang.org/core/arch/x86_64/fn.__cpuid.html – stepan Mar 15 '22 at 10:39
  • @E_net4standswithUkraine Noticed that, but new syntax seems a bit confusing to me. It's not clear how I translate this into the new syntax. – terett Mar 15 '22 at 11:11

1 Answers1

3

Try this:

use std::arch::asm;

fn main() {
    let features: u32;
    unsafe {
        asm!("mov eax, 1",
             "push rbx",
             "cpuid",
             "pop rbx",
             out("ecx") features,
             out("eax") _,
             out("edx") _,
        );
    }
    println!("features: {:x}", features);
}

Playground

Note that you are not allowed to clobber ebx since it is used internally by LLVM, so you need to save and restore it.

Jmb
  • 18,893
  • 2
  • 28
  • 55
  • 1
    This didn't fix the missing clobbers on EDX and EBX which fuz pointed out in comments. The `cpuid` instruction writes them, so it's essential to tell the compiler about it. (Inline asm is easy to silently get wrong, so IMO it's very important to avoid subtly / dangerously broken examples in Stack Overflow answers.) I assume that would be done here with two more `out("ebx") _, out("edx") _` dummy outputs but I'm just guessing from this example without explanation of the parts. – Peter Cordes Mar 15 '22 at 14:18
  • @PeterCordes Doing that gives error `cannot use register bx: rbx is used internally by LLVM and cannot be used as an operand for inline asm`. – terett Mar 15 '22 at 16:57
  • @terett: Then you can't safely wrap CPUID in Rust inline asm, without extra instructions. e.g. save RBX in R9 or RSI or something, and then swap it after CPUID. So the full asm template looks to the compiler like it only outputs in R9 without affecting RBX. (Also, I'm curious what Rust uses RBX for internally! Whether that's a real thing or some weird hold-over from 32-bit PIC code that traditionally used EBX as a GOT pointer; in very old GCC versions, you couldn't declare a clobber on EBX in 32-bit mode.) – Peter Cordes Mar 15 '22 at 17:01
  • @PeterCordes "This is used internally by LLVM as a "base pointer" for functions with complex stack frames." https://doc.rust-lang.org/nightly/reference/inline-assembly.html#register-names. Also https://github.com/rust-lang/rust/issues/85056. – Chayim Friedman Mar 15 '22 at 20:24
  • 1
    @ChayimFriedman: Thanks for the link. Weird, or at least a different choice from GNU C inline asm, to disallow RBP as well; most functions don't need a frame pointer. GCC has allowed RBP as an operand for quite a while in GNU C inline asm, saving/restoring it around the asm statement if necessary. I guess it makes some sense to make that an error, even if that means disallowing it even when it wouldn't actually be a problem (e.g. with optimization enabled). Also, reported https://github.com/rust-lang/rust/issues/94977 - `k0` isn't hard-wired to 0. – Peter Cordes Mar 15 '22 at 20:54
  • 1
    @PeterCordes I hadn't checked the validity of the assembly, just translated the original snippet to the current syntax. Edited the answer to fix it. – Jmb Mar 16 '22 at 07:21
  • Are you allowed to push/pop in Rust inline asm for x86-64? [In GNU C inline asm](https://stackoverflow.com/questions/34520013/using-base-pointer-register-in-c-inline-asm), there's no way to tell the compiler that you clobber the red-zone (below RSP), so if you want to use stack space you need to `sub rsp, 128` first. (Or better, `add rsp, -128` so it can use an imm8). That's one reason I suggested swapping with R8 or R9, and also so you could actually return the RBX output of `cpuid` if/when it's wanted. – Peter Cordes Mar 16 '22 at 15:00
  • @PeterCordes According to [godbolt](https://godbolt.org/z/v673adPxf), it looks like the compiler assumes that inline asm clobbers the red zone and it adds the appropriate `sub rsp, …` instruction to protect its local variables as required. – Jmb Mar 16 '22 at 16:09
  • 1
    This is in fact [documented](https://doc.rust-lang.org/reference/inline-assembly.html#options): `asm` blocks that don't clobber the red zone may use the `nostack` option to notify the compiler. – Jmb Mar 16 '22 at 16:10
  • Oh wow, nice. Evidently they learned from experience with GNU C inline asm when designing Rust's asm. I still think two `mov` instructions and an R9 clobber or output would be cheaper than push/pop (and most importantly would let you get all 4 outputs from CPUID, which is essential for a general-purpose wrapper, just by changing the 2nd mov to xchg). And that would let you use `nostack`. But upvoted since this is now fully safe. Thanks for checking on that. – Peter Cordes Mar 16 '22 at 16:15