3 分で読了 #rust #unsafe-rust
このページの目次

インラインアセンブリ

asm! マクロにより、Rust でターゲットマシンのアセンブリ命令を直接埋め込めます。制約(constraints)はレジスタの入出力関係を記述し、clobbers はどのレジスタが破壊されるかを宣言します。各アセンブリテンプレートはアーキテクチャ固有(x86_64 と ARM64 では書き方が全く異なります)であり、本質的には「この行を越えて最適化しないでください」という、コンパイラ最適化に対する壁です。

asm! の基礎

use std::arch::asm;
let x: u64;
unsafe { asm!("rdtsc", out("rax") _, out("rdx") _, lateout("rax") x); }
// TSC (Time Stamp Counter) = EDX:EAX
// out("rax") _  — 上位32ビット、値は破棄 (_)
// out("rdx") _  — rdx の下位32ビット、破棄
// lateout("rax") x — x に結合された値

asm! は Rust 1.59+ で導入された新しいマクロです(古い llvm_asm! に取って代わります)。制約の妥当性をコンパイル時にチェックし、レジスタの競合、無効なオペランド、型の不一致はすべてコンパイル時に報告されます。

制約(Constraints): 入出力仕様

let mut a: u64 = 4;
let b: u64 = 4;
unsafe {
    asm!(
        "add {0}, {1}",              // {0} = a, {1} = b
        inlateout(reg) a,            // a: 入力+出力、入力とレジスタを共有しない
        in(reg) b,                   // b: 純粋な入力
    );
}
  • in(reg): 入力。コンパイラがレジスタを割り当て、値を読み込みます。
  • out(reg): 出力。最終値を読み取ります。
  • inout(reg): 入力+出力。
  • lateout(reg): 出力。入力とレジスタを共有しません(入力レジスタが clobber されるシナリオ用)。
  • inlateout(reg): 入力+出力。入力とレジスタを共有しません。

Clobbers: どのレジスタが変更されるか

outlateout は対応するレジスタを自動的に clobbered としてマークします。しかし、他の関数を呼び出す場合、ABI 全体の clobber セットをマークする必要があります。

unsafe {
    asm!(
        "call my_function",
        clobber_abi("C"),            // C 呼び出し規約の caller-saved レジスタすべてが clobbered としてマークされる
    );
}

アーキテクチャ固有の条件コンパイル

#[cfg(target_arch = "x86_64")]
unsafe { asm!("rdtsc", ...); }

#[cfg(target_arch = "aarch64")]
unsafe { asm!("mrs {}, cntvct_el0", out(reg) x); }  // ARM64 のサイクルカウンター

オプション

asm!("nop", options(nomem, nostack, preserves_flags));
// nomem:    メモリへの読み書きを行わない(コンパイラはキャッシュを更新しなくてもよい)
// nostack:  スタックの push/pop を行わない
// preserves_flags: EFLAGS/RFLAGS を変更しない
// pure:     副作用がない(最適化によって削除可能)
// readonly: メモリのみ読み取り

参考

  • Rust Reference: inline assembly
  • Rust By Example: asm

Keywords: asm!, inline assembly, constraints, clobbers, lateout, clobber_abi, per-arch asm