首页 > 解决方案 > Why rust use rsi to pass parameter to fn pointer

问题描述

For normal functions, rust behaves the same as x86-64 abi which pass parameter use register starting with rdi, but for fn pointer rust use rsi, so why do rustc choose to do so?

Take this for an example

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

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

the closure is compiled to

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

and it's using rsi while the normal function foo is compiled to

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

which use rdi and calling it in main is

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

so this is done with calling core::ops::function::FnOnce::call_once which does

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

标签: rustabi

解决方案


Pure speculation, but it may be that closures are always compiled as structs with an inherent method which takes &self, and then a wrapper is generated to convert that to function pointer. This would allow the inherent method to also be called as a trait object where a data pointer is passed. The argument to the wrapper is passed in rdi, which then allocates stack space to store a synthesized zero size struct. That wrapper then passes the synthesized struct in rdi, and the parameter in the next parameter slot rsi. In optimized code it would be expected for the inherent method to be inlined into the wrapper, and the weird dance with the registers to synthesize a useless parameter would be eliminated.

In short it is the normal system v calling convention, but there is an extra implicit parameter that is never read, so all the actual parameters get shifted one register further along the list from normal.


推荐阅读