Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I translate x86 GCC-style C inline assembly to Rust inline assembly?

Tags:

assembly

rust

I have the following inline assembly in C:

unsigned long long result;
asm volatile(".byte 15;.byte 49;shlq $32,%%rdx;orq %%rdx,%%rax"
    : "=a" (result) ::  "%rdx");
return result;

I tried to rewrite it in Rust:

let result: u64;
unsafe {
    asm!(".byte 15\n\t
          .byte 49\n\t
          shlq 32, rdx\n\t
          orq  rdx, rax"
         : "=a"(result)
         :
         : "rdx"
         : "volatile"
         );
}
result

It doesn't recognize the =a constraint an it gives me an invalid operand error for rdx and rax at shlq and orq instructions. What is the proper way to rewrite the above C inline assembly in Rust?

like image 959
tinker Avatar asked Jan 04 '18 15:01

tinker


People also ask

Does rust support inline assembly?

Rust provides support for inline assembly via the asm! macro. It can be used to embed handwritten assembly in the assembly output generated by the compiler. Generally this should not be necessary, but might be where the required performance or timing cannot be otherwise achieved.

Does GCC support inline assembly?

There are, in general, two types of inline assembly supported by C/C++ compilers: asm (or __asm__) in GCC. GCC uses a direct extension of the ISO rules: assembly code template is written in strings, with inputs, outputs, and clobbered registers specified after the strings in colons.

What is __ asm __ in C?

The __asm keyword invokes the inline assembler and can appear wherever a C or C++ statement is legal. It cannot appear by itself. It must be followed by an assembly instruction, a group of instructions enclosed in braces, or, at the very least, an empty pair of braces.

What is GCC in assembly?

The well-known GNU C/C++ Compiler (GCC), an optimizing 32-bit compiler at the heart of the GNU project, supports the x86 architecture quite well, and includes the ability to insert assembly code in C programs, in such a way that register allocation can be either specified or left to GCC.


1 Answers

Rust is built on top of LLVM, so a lot of low-level detail like this can be gleaned from what LLVM or Clang do.

  1. If you want to specify a specific register, you use the register name as the constraint: "={rax}"(result). Based on the GCC documentation, the a constraint is the "a" register.

  2. Literals must be prefaced with $$

  3. Registers must be prefaced with %

let result: u64;
unsafe {
    asm!(".byte 15
          .byte 49
          shlq $$32, %rdx
          orq  %rdx, %rax"
         : "={rax}"(result)
         :
         : "rdx"
         : "volatile"
    );
}
result

If I'm understanding the discussion about rdtsc correctly, you can also do:

let upper: u64;
let lower: u64;
unsafe {
    asm!("rdtsc"
         : "={rax}"(lower), 
           "={rdx}"(upper)
         :
         :
         : "volatile"
    );
}
upper << 32 | lower

I advise getting out of inline assembly as soon as it's practical.


The assembly of each function:

playground::thing1:
    #APP
    .byte   15
    .byte   49
    shlq    $32, %rdx
    orq %rdx, %rax
    #NO_APP
    retq

playground::thing2:
    #APP
    rdtsc
    #NO_APP
    shlq    $32, %rdx
    orq %rdx, %rax
    retq

For completeness, here is the same code using the LLVM intrinsic. This requires a different unstable attribute:

#![feature(link_llvm_intrinsics)]

extern "C" {
    #[link_name = "llvm.x86.rdtsc"]
    fn rdtsc() -> u64;
}

fn main() {
    println!("{}", unsafe { rdtsc() })
}

Sources:

  • The unstable book chapter on asm.
  • The LLVM inline assembly reference.
  • My libraries jetscii and cupid.
like image 50
Shepmaster Avatar answered Oct 03 '22 19:10

Shepmaster