LoginSignup
1
0

LLVMのstack move optimizationの効果をRustで確認する

Posted at

khei4さんの功績によって、LLVM17では部分的に不要なmemcpyを排除することができるようになりました。

具体的なケースではRustのcodegen testを追加するPRがわかりやすいです。

実際にこのコードの生成するLLVM IRをみてみましょう。

rustc 1.75.0(LLVM17)で実験

#![crate_type = "lib"]

#[repr(C)]
pub struct ThreeSlices<'a>(&'a [u32], &'a [u32], &'a [u32]);

#[no_mangle]
pub fn sum_slices(val: ThreeSlices) -> u32 {
    let val = val;
    sum(&val)
}

#[no_mangle]
#[inline(never)]
pub fn sum(val: &ThreeSlices) -> u32 {
    val.0.iter().sum::<u32>() + val.1.iter().sum::<u32>() + val.2.iter().sum::<u32>()
}
$ rustc -Vv
rustc 1.75.0 (82e1608df 2023-12-21)
binary: rustc
commit-hash: 82e1608dfa6e0b5569232559e3d385fea5a93112
commit-date: 2023-12-21
host: x86_64-unknown-linux-gnu
release: 1.75.0
LLVM version: 17.0.6
$ rustc -O --emit=llvm-ir gh-pr115050.rs
(...)
; Function Attrs: nofree norecurse nosync nounwind nonlazybind memory(read, inaccessiblemem: none) uwtable
define noundef i32 @sum_slices(ptr noalias nocapture noundef readonly align 8 dereferenceable(48) %val) unnamed_addr #0 {
start:
  %_0 = tail call noundef i32 @sum(ptr noalias noundef nonnull readonly align 8 dereferenceable(48) %val)
  ret i32 %_0
}
(...)

rustc 1.72.1(LLVM16)で実験

LLVM17より前ではこの最適化が効いてないことがわかります。

$ rustup run 1.72.1 rustc -Vv
rustc 1.72.1 (d5c2e9c34 2023-09-13)
binary: rustc
commit-hash: d5c2e9c342b358556da91d61ed4133f6f50fc0c3
commit-date: 2023-09-13
host: x86_64-unknown-linux-gnu
release: 1.72.1
LLVM version: 16.0.5
$ rustup run 1.72.1 rustc -O --emit=llvm-ir gh-pr115050.rs

LLVM IRのmemcpy命令が確認できました。

(...)
; Function Attrs: nofree nosync nounwind nonlazybind memory(read, inaccessiblemem: none) uwtable
define noundef i32 @sum_slices(ptr noalias nocapture noundef readonly dereferenceable(48) %val) unnamed_addr #0 {
start:
  %val1 = alloca %"ThreeSlices<'_>", align 8
  call void @llvm.lifetime.start.p0(i64 48, ptr nonnull %val1)
  call void @llvm.memcpy.p0.p0.i64(ptr noundef nonnull align 8 dereferenceable(48) %val1, ptr noundef nonnull align 8 dereferenceable(48) %val, i64 48, i1 false)
  %0 = call noundef i32 @sum(ptr noalias noundef nonnull readonly align 8 dereferenceable(48) %val1)
  call void @llvm.lifetime.end.p0(i64 48, ptr nonnull %val1)
  ret i32 %0
}
(...)

再度rustc 1.75.0で実験・今度はmutをつける

Rustはmutableな参照がエイリアスになりえないという協力な性質を持っており、これによりLLVMではnoalias属性の付与が可能となっています。

実際に試してみます。

#![crate_type = "lib"]

#[repr(C)]
pub struct ThreeSlices<'a>(&'a [u32], &'a [u32], &'a [u32]);

#[no_mangle]
pub fn sum_slices(val: ThreeSlices) -> u32 {
    let mut val = val;
    sum(&mut val)
}

#[no_mangle]
#[inline(never)]
pub fn sum(val: &mut ThreeSlices) -> u32 {
    val.0.iter().sum::<u32>() + val.1.iter().sum::<u32>() + val.2.iter().sum::<u32>()
}

同様のコンパイルオプションを与えると、memcpyがないことを確認できました。
immutable版との違いとしてsumの引数からreadonly属性がなくなっていることもわかります。

(...)
; Function Attrs: nofree norecurse nosync nounwind nonlazybind memory(read, inaccessiblemem: none) uwtable
define noundef i32 @sum_slices(ptr noalias nocapture noundef readonly align 8 dereferenceable(48) %val) unnamed_addr #0 {
start:
  %_0 = tail call noundef i32 @sum(ptr noalias noundef nonnull align 8 dereferenceable(48) %val)
  ret i32 %_0
}
(...)
1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0