我有一个函数,其中 Rust/LLVM 的优化失败并导致恐慌(在发布版本中),而未优化的代码(调试版本)工作正常。如果我比较生成的汇编代码,我什至无法掌握优化器试图完成什么的想法。 (原因可能是这个函数使用内联汇编器。
有没有办法告诉 Rust 在优化过程中不理会某些函数,或者我必须关闭所有优化?
具体功能如下:
#[naked]
pub extern "C" fn dispatch_svc(){
Cpu::save_context();
let mut nr: u32 = 0;
unsafe {
asm!("ldr r0, [lr, #-4]
bic $0, r0, #0xff000000":"=r"(nr)::"r0":"volatile")
};
swi_service_routine(nr);
Cpu::restore_context_and_return();
}
不,你不能.
Rusts 的编译单元(编译器和优化器操作的最小单元)是整个 crate。
您唯一的解决方法是在单个 crate 中编译此函数,编译它,然后将其作为预编译依赖项包含在内。(正常的 rust 依赖项是在依赖项的优化级别编译的)
但是:为这个单一功能指定不同的优化级别并不能解决您的问题!当然,它今天可能有效,但每次编译器(或优化标志)更改时都可能再次中断。 给定示例中的函数名称(Cpu::save_context
/restore_context_and_return
),您似乎正在解决的潜在问题需要向rustc
添加适当的调用约定。
TL;DR:赤裸裸的功能是非常不安全的(我的尊重,你是一个比我更勇敢的人! 使用它们的唯一可靠方法是只编写一个asm!()
块作为整个函数体,没有别的。
像你一样混合asm!
、正常的 Rust 和函数调用实际上是未定义的行为(在可怕的 C/Nasal-Demon 意义上)再多的优化调整也不会改变这一点。
2022-04 更新:自从最初回答这个问题以来,围绕裸函数发生了很多事情.
裸函数的最小"约束"子集(请参阅 RFC #2972]1)计划在 1.60 中稳定下来 还有编译器错误来"拒绝不受支持的裸函数",这将触发此处提供的示例。
裸函数仍然不稳定,直到 Rust 作者"做对了"。 正如您所发现的,这个.
这里的稳定跟踪问题存在许多微妙的问题,在 2022 年被更有限的"受限"裸功能跟踪器所取代。
在裸fn RFC的"动机"下,我们发现:
由于编译器依赖于函数序言和尾声来维护局部变量绑定的存储,因此在裸函数中编写除内联程序集之外的任何内容通常是不安全的。LLVM语言参考将此功能描述为具有"非常特定于系统的后果",程序员必须意识到这一点。
(强调我的)
在 RFC 的下方,在未解决的问题下,我们知道这不仅仅是 Rust 的问题。其他语言在使用此功能时也会遇到问题:
大多数支持类似功能的编译器要么要求,要么强烈建议作者只在裸函数中编写内联程序集,以确保不会生成假定特定堆栈布局的代码。
原因是所有编译器都对如何调用函数做出了很多假设(关键字:"调用方保存的寄存器"、"被调用方保存的寄存器"、"调用约定"、"红色区域")。裸函数不遵守这些假设,因此编译器生成的任何代码都很可能是错误的。"解决方案"是不让编译器生成任何东西,即在汇编中手动编写整个函数。
因此,在裸函数中混合"普通"代码(let mut nr: u32 = 0;
),函数调用(swi_service_routine(nr);
)和原始汇编器的方式是未指定的行为。(是的,这样的事情存在于 Rust 中,但只存在于 Unstable 中)。
裸函数会导致足够的问题,以至于它们应该在 Rust 错误跟踪器中拥有自己的标签。 在其中一个裸体问题中,我们找到了知识渊博的用户Tari(其中包括llvm-sys
的作者)的此评论。他解释说:
裸函数中非 asm 代码的实际正确性取决于优化器和代码生成器,通常我们无法保证它将做什么。
也有人谈论需要unsafe
的裸函数,因为它们打破了 Rust 的许多正常假设。事实上,他们并非在所有情况下都要求这样做是一个开放的错误
2022 更新:由新的默认拒绝 lints 于 2022-01-21 关闭,以拒绝不受支持的裸函数 (#93153)
因此,解决"优化问题"的正确方法是完全停止依赖优化。相反,只写入一个asm!()
块。
对于您的Cpu::save_context()
/Cpu::restore_context_and_return()
对:我可以理解对代码重用的渴望。要获得它,请将它们更改为插入相关asm!(...)
的宏。asm!(...); asm!(...); asm!(...);
的串联应等效于单个asm!()
。
如果你正在使用货物,你可以告诉它根本不优化任何东西,或者按级别优化
货物优化