How to call assembly functions inside Rust?

Viewed 4839

I know that one way to call C functions inside Rust is to use something like this:

extern "C" {
    fn abs(input: i32) -> i32;
}

fn main() {
    unsafe {
        println!("abs(-3) = {}", abs(-3));
    }
}

But how does one call some assembly functions (located inside .s file in the same directory as other Rust files) inside a Rust code?

2 Answers

Example

This is an assembly function using the C x86_64 calling convention on macOS. It doesn't segfault, so it must be right :-)

Cargo.toml

[build-dependencies]
cc = "1.0"

build.rs

fn main() {
    cc::Build::new()
        .file("add.S")
        .compile("my-asm-lib");
}

add.S

.text
.globl _my_adder
_my_adder:
        addq %rdi, %rsi
        movq %rsi, %rax
        ret

src/main.rs

extern "C" {
    fn my_adder(a: u64, b: u64) -> u64;
}

fn main() {
    let sum = unsafe { my_adder(1, 2) };
    println!("{}", sum);
}

Explanation

To make use of an assembly function, there are two main pieces:

  1. Compiling and linking the function
  2. Calling the function

Compiling and Linking

Rust does not inherently provide a way to compile assembly files. Instead, you have to compile them using a C compiler which already exists on the machine. The most common way of doing is is to use the cc crate in a build script.

The build script will compile your assembly files into a static library and instruct Cargo to link to the library.

You have to make sure that your assembly syntax is valid for the C compiler you are using. For example, MSVC and GCC use different assembly styles. You are likely going to need to have multiple copies of the same assembly function to handle different platforms.

Calling

Calling the function is highly dependent on the calling convention. The primary calling convention is the C calling convention, denoted with either extern "C" or just plain extern. You may want to use the #[link_name = "..."] or #[no_mangle] attributes as well.

You can also create your own custom calling convention using a #[naked] function (see RFC 1201). You would then use assembly code on the caller side before/after the call according to its calling convention. This is not available in stable Rust.

There are many other calling conventions supported by Rust 1.21.1:

cdecl, stdcall, fastcall, vectorcall, thiscall, aapcs, win64, sysv64, ptx-kernel, msp430-interrupt, x86-interrupt, Rust, C, system, rust-intrinsic, rust-call, platform-intrinsic, unadjusted

Completing the other answer :

If you use nightly rust, you can avoid using a custom build script by using the global_asm! macro.

A simple usage looks like this:

#[feature(global_asm)]

...

global_asm!(include_str!("something_neato.s"));
Related