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

[khei4さんの功績](https://khei4.github.io/gsoc2023/)によって、LLVM17では部分的に不要なmemcpyを排除することができるようになりました。

具体的なケースでは[Rustのcodegen testを追加するPR](https://github.com/rust-lang/rust/pull/115050)がわかりやすいです。

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

## rustc 1.75.0(LLVM17)で実験

```rust
#![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>()
}
```

```console
$ 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
```

```ll
(...)
; 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より前ではこの最適化が効いてないことがわかります。

```console
$ 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命令が確認できました。

```ll
(...)
; 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な参照がエイリアスになりえない](https://doc.rust-lang.org/nomicon/aliasing.html)という協力な性質を持っており、これによりLLVMではnoalias属性の付与が可能となっています。

実際に試してみます。

```rust
#![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属性がなくなっていることもわかります。

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