rust - 为什么 Rust 在 LLVM IR 闭包环境中将闭包捕获的 i64 存储为 i64*s?

标签 rust

在这个简单的例子中

#[inline(never)]
fn apply<F, A, B>(f: F, x: A) -> B
    where F: FnOnce(A) -> B {
    f(x)
}

fn main() {
    let y: i64 = 1;
    let z: i64 = 2;
    let f = |x: i64| x + y + z;
    print!("{}", apply(f, 42));
}

传递给 apply 的闭包作为 LLVM IR {i64*, i64*}* 传递:

%closure = type { i64*, i64* }
define internal fastcc i64 @apply(%closure* noalias nocapture readonly dereferenceable(16)) unnamed_addr #0 personality i32 (i32, i32, i64, %"8.unwind::libunwind::_Unwind_Exception"*, %"8.unwind::libunwind::_Unwind_Context"*)* @rust_eh_personality {
entry-block:
  %1 = getelementptr inbounds %closure, %closure* %0, i64 0, i32 1
  %2 = getelementptr inbounds %closure, %closure* %0, i64 0, i32 0
  %3 = load i64*, i64** %2, align 8
  %4 = load i64*, i64** %1, align 8
  %.idx.val.val.i = load i64, i64* %3, align 8, !noalias !1
  %.idx1.val.val.i = load i64, i64* %4, align 8, !noalias !1
  %5 = add i64 %.idx.val.val.i, 42
  %6 = add i64 %5, %.idx1.val.val.i
  ret i64 %6
}

(apply 在生成的 LLVM 代码中实际上有一个更复杂的名字。)

这会导致两个负载到达每个捕获的变量。为什么 %closure 不只是 {i64, i64} (这将使参数成为 apply {i64, i64}* )?

最佳答案

Closures默认情况下通过引用捕获。您可以通过在参数列表前添加 move 关键字来将该行为更改为按值捕获:

let f = move |x: i64| x + y + z;

这会生成更精简的代码:

define internal fastcc i64 @apply(i64 %.0.0.val, i64 %.0.1.val) unnamed_addr #0 personality i32 (i32, i32, i64, %"8.unwind::libunwind::_Unwind_Exception"*, %"8.unwind::libunwind::_Unwind_Context"*)* @rust_eh_personality {
entry-block:
  %0 = add i64 %.0.0.val, 42
  %1 = add i64 %0, %.0.1.val
  ret i64 %1
}

添加move 关键字意味着闭包使用的任何值都将被移动到闭包的环境中。对于整数,即 Copy,没有太大区别,但对于其他类型,如 String,这意味着你不能使用创建闭包后, String 不再位于外部范围内。这是一个孤注一掷的交易,但您可以在 move 闭包之外手动获取对单个变量的引用,并让闭包使用这些引用而不是原始值来获得手动捕获引用行为.

Can you observe the value vs ref difference somehow in this code?

If you take the address of the captured variable, you can observe the difference.请注意第一条和第二条输出线是如何相同的,而第三条是不同的。

关于rust - 为什么 Rust 在 LLVM IR 闭包环境中将闭包捕获的 i64 存储为 i64*s?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40230796/

相关文章:

rust - 在 Rust 中的线程之间发送特征对象

rust - 如何在不使用构建脚本的情况下仅添加特定 Rust 版本的代码?

rust - 是否有一种紧凑且惯用的方式来打印错误并返回而不返回错误?

winapi - 在 Rust 中使用 winapi 从窗口获取位图

compiler-errors - 为什么临时变量会抑制 "type ascription is experimental"错误?

rust - 如何在 BTreeMap/BTreeSet 中找到下一个较小的键?

ios - iOS 对 Rust 的支持现状如何?

assembly - 为什么 Rust 优化器不删除那些无用的指令(在 Godbolt Compiler Explorer 上测试过)?

rust - rust 中的 "Jump to definition"

rust - 将 Apache Beam 与 Rust 结合使用