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
}
(...)