为什么裸Rust函数中有额外的ASM指令?

5

我正在用Rust封装一个低级ABI,利用naked函数特性。以下是我的代码和相关反汇编:

#![feature(asm)]
#![feature(naked_functions)]

struct MyStruct {
    someVar: i64, // not important
                  // ...
}

impl MyStruct {
    #[naked]
    extern "C" fn wrap(&self) {
        unsafe {
            asm!("NOP" :::: "volatile");
            // not sure if the volatile option is needed, but I
            // figured it wouldn't hurt
        }
    }
}

使用LLDB逐步分解:

ABIWrap`ABIWrap::{{impl}}::wrap:
  * 0x100001310 <+0>:  movq   %rdi, -0x10(%rbp)
  * 0x100001314 <+4>:  movq   %rsi, -0x8(%rbp)
  * 0x100001318 <+8>:  movq   -0x10(%rbp), %rax
  * 0x10000131c <+12>: movq   -0x8(%rbp), %rcx
  * 0x100001320 <+16>: movq   %rax, -0x20(%rbp)
  * 0x100001324 <+20>: movq   %rcx, -0x18(%rbp)
    0x100001328 <+24>: nop    
    0x100001329 <+25>: retq   
    0x10000132a <+26>: nopw   (%rax,%rax)

我对NOP之前的6行代码感到困惑(我已用*标记)。按理说,naked指令应该只留下一个裸函数,对吗?
我试图通过此函数将参数直接传递给ABI,因为它遵循与Rust大致相同的调用约定,只需要交换一个或两个寄存器,因此需要内联汇编。
有没有办法摆脱这6行先前的指令呢?我经常调用ABI,以前的调用方式会造成相当大的开销。我希望确保包含任何重要值的寄存器不会被覆盖。
顺便提一下:是否需要使用“volatile”选项?我不确定,但无论如何都添加了它。

我不知道 - 但只是为了确保一下:你反编译的是一个_release_版本,对吗? - Simon Whitehead
不,我正在反编译调试版本。 - Chase Walden
出于我的好奇心,这是哪个平台的ABI? - Shepmaster
看起来有点像它跳回到某个影子空间获取一些数据,这是64位Windows ABI规范所指定的..不过很难说。@ChaseWalden出于我的好奇心(我还没有深入研究Rust中裸函数的内部工作原理),如果你不将函数嵌入到结构体的实现中,会发生什么?如果它只是一个独立的纯函数呢?我想知道那个"prelude"是否从某个地方获取了self - Simon Whitehead
1
@ChaseWalden:我认为你已经找到了答案,裸函数接受参数是没有意义的,因为使用这些参数需要对它们的传递方式(ABI)达成一致,而这与“naked”属性相矛盾,该属性指定不要假定任何ABI。 - Matthieu M.
显示剩余5条评论
1个回答

3
在进一步尝试后(并且找到了如何有效地解开我的发布构建),我发现额外的指令只在调试构建时添加(或至少是当 -O0 时)。使用 -O2 编译代码后,我发现所有汇编都被内联了,但是这很容易通过一个 #[inline(never)] 指令来修复。现在参数正确传递而不会破坏我的寄存器 :) 现在我只需要让代码在这些函数上运行 -O2,而不是在整个调试构建上运行...

这可能值得为此开一个问题 :) - Matthieu M.
@MatthieuM:可能吧……我还在试图弄清楚将参数移动到堆栈上的目的是什么。我几乎开始认为,SimonWhitehead在上面的评论线程中建议的那样,被调用者正在将值存储在“影子空间”中,这很奇怪,因为我在使用Mac。 - Chase Walden

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接