You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何Rust中函数指针用rsi传参而非普通函数的rdi?

Why does Rust use RSI for function pointer arguments instead of RDI (like regular functions)?

Great question! Let's break down the register usage difference you're observing between regular functions and the closure called via a function pointer.

Key Background: Two Calling Conventions

First, we need to distinguish between two critical calling conventions at play here:

  • x86-64 SysV ABI: This is the standard convention Rust (and C) uses for regular function calls. Under this rule, the first function argument is passed in the rdi register, the second in rsi, and so on.
  • Rust's rust-call Convention: This is Rust's internal convention for trait methods like FnOnce::call_once, designed specifically to handle closures and function objects. For a method like call_once(self, args), the self parameter (the closure instance) goes into rdi, and the args tuple (your function's parameters) goes into rsi.

Breaking Down Your Code & Assembly

Let's walk through what's happening in your example, using the assembly you provided.

1. Regular Function foo

Your foo function accepts a fn(u64)—a "bare" function pointer that follows the C ABI. When foo calls f(10), it strictly adheres to the x86-64 SysV ABI, as shown in its assembly:

playground::foo:
 sub rsp, 24
 mov qword ptr [rsp + 16], rdi
 mov eax, 10
 mov qword ptr [rsp + 8], rdi
 mov rdi, rax  ; Move the parameter 10 into `rdi` (SysV first argument register)
 mov rax, qword ptr [rsp + 8]
 call rax      ; Call the function pointer stored in rax
 add rsp, 24
 ret

This is exactly why you see rdi used here—it's following the standard system ABI.

2. Closure & Indirect call_once Call

Your closure |i| { i + 1; } has no captured environment, so it could be directly converted to a fn(u64) pointer that follows the C ABI. However, in debug mode, Rust prioritizes debuggability over optimization, so it preserves the trait-based closure machinery:

  • In main, the value passed to foo is actually the address of core::ops::function::FnOnce::call_once (as seen in the lea rdi, [rip + core::ops::function::FnOnce::call_once] line).
  • call_once uses the rust-call convention. Its assembly shows it preparing the closure instance and parameter:
    core::ops::function::FnOnce::call_once:
     sub rsp, 40
     mov qword ptr [rsp + 16], rdi  ; Store the closure instance (self)
     mov rsi, qword ptr [rsp + 16]
     lea rdi, [rsp + 8]
     call playground::main::{{closure}}  ; Invoke the closure
     ; ... cleanup ...
    
  • When call_once calls your closure, the parameter 10 is in rsi (per rust-call rules, since rdi is reserved for the closure instance). Your closure's assembly confirms it uses rsi for the parameter:
    playground::main::{{closure}}:
     sub rsp, 24
     mov qword ptr [rsp + 8], rdi
     mov qword ptr [rsp + 16], rsi
     add rsi, 1  ; Operate on the parameter in `rsi`
     setb al
     test al, 1
     jne .LBB11_2
     add rsp, 24
     ret
    

What Happens in Release Mode?

If you compile your code in release mode (cargo build --release), Rust's optimizer will eliminate this indirect trait call layer. It will generate a bare function for your closure that strictly follows the x86-64 SysV ABI, so calling the function pointer will use rdi for the parameter—just like a regular function. You can verify this yourself by checking the assembly in release mode on the Rust Playground.

Summary

The difference boils down to debug vs. release compilation choices and Rust's internal trait handling:

  • Regular functions and optimized function pointers follow the standard x86-64 SysV ABI (using rdi for the first argument).
  • Debug-mode closures use Rust's rust-call convention via FnOnce::call_once, which passes the function parameter in rsi (since rdi is reserved for the closure instance itself).

内容的提问来源于stack exchange,提问作者Austaras

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.08 14:52:26