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:
- Compiling and linking the function
- 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