为什么 rust 使用 rsi 将参数传递给 fn 指针

Why rust use rsi to pass parameter to fn pointer

对于普通函数,rust 的行为与 x86-64 abi 相同,传递参数使用以 rdi 开头的寄存器,但对于 fn 指针 rust 使用 rsi,那么为什么 rustc 选择这样做?

以此为例

fn foo(f: fn(u64)) {
    f(10);
}

fn main() {
    foo(|i| {
        i + 1;
    });
}

闭包编译为

playground::main::{{closure}}:
    sub rsp, 24
    mov qword ptr [rsp + 8], rdi
    mov qword ptr [rsp + 16], rsi
    add rsi, 1
    setb    al
    test    al, 1
    jne .LBB11_2
    add rsp, 24
    ret

它使用 rsi,而普通函数 foo 被编译为

playground::foo:
    sub rsp, 24
    mov qword ptr [rsp + 16], rdi
    mov eax, 10
    mov qword ptr [rsp + 8], rdi
    mov rdi, rax
    mov rax, qword ptr [rsp + 8]
    call    rax
    add rsp, 24
    ret

使用 rdi 并在 main 中调用它的是

    lea     rdi, [rip + core::ops::function::FnOnce::call_once]
    call    playground::foo

所以这是通过调用 core::ops::function::FnOnce::call_once 完成的

core::ops::function::FnOnce::call_once:
    sub rsp, 40
    mov qword ptr [rsp + 16], rdi
    mov rsi, qword ptr [rsp + 16]
    lea rdi, [rsp + 8]
    call    playground::main::{{closure}}
    jmp .LBB4_1

.LBB4_1:
    jmp .LBB4_2

.LBB4_2:
    add rsp, 40
    ret

纯粹的猜测,但可能是闭包总是被编译为具有采用 &self 的固有方法的结构,然后生成包装器以将其转换为函数指针。这将允许固有方法也被称为传递数据指针的特征对象。包装器的参数在 rdi 中传递,然后分配堆栈 space 来存储合成的零大小结构。该包装器然后在 rdi 中传递合成结构,并在下一个参数槽 rsi 中传递参数。在优化的代码中,预期将固有方法内联到包装器中,并且将消除与寄存器合成无用参数的怪异舞蹈。

简而言之,这是正常的 system v 调用约定,但有一个永远不会被读取的额外隐式参数,因此所有实际参数都从正常的列表中进一步移动了一个寄存器。