【问题标题】:Can I force Rust to not optimize a single function?我可以强制 Rust 不优化单个函数吗?
【发布时间】:2017-10-23 14:07:27
【问题描述】:

我有一个函数,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();
}

【问题讨论】:

  • “我有一个函数,Rust/llvm 的优化失败并导致恐慌”你能给我们看看吗?
  • @E_net4:我已经编辑了这个问题。但是,我会对最初的问题感兴趣,即使有一种巧妙的方法可以避免这个特定功能出现问题。
  • 正如 Matthias 在 this comment 中解释的那样,问题是实现不受支持的调用约定的一部分。

标签: optimization rust llvm-codegen


【解决方案1】:

据我所知,Rust 除了整个 crate 之外,没有任何工具可以指定优化级别。 您唯一的解决方法是在单独的 crate 中编译此函数,编译它,然后将其作为预编译的依赖项包含在内。 (普通的rust-dependencies是在depender的优化级别编译的)

但是:为这个单一函数指定不同的优化级别并不能解决您的问题!当然,它今天可能工作,但每次编译器(或优化标志)更改时都会再次中断。

TL;DR:裸函数是非常不安全的(尊敬的,你是一个比我更勇敢的人!)。 使用它们的唯一可靠方法是只编写一个 asm!() 块作为整个函数体,仅此而已。 像你一样混合asm!、正常的 Rust 和函数调用实际上是未定义的行为(在可怕的 C/Nasal-Demon 术语中)再多的优化调整都不会改变这一点。


在 Rust 作者“做对了”之前,裸函数仍然不稳定。 正如您所发现的,这存在许多微妙的问题。稳定跟踪问题here

naked-fn RFC的“动机”下,我们发现:

因为编译器依赖函数序言和结尾来维护局部变量绑定的存储,所以在裸函数中编写除内联汇编之外的任何内容通常是不安全的。 LLVM 语言参考将这个特性描述为具有“非常系统特定的后果”,程序员必须意识到这一点。

(强调我的)

在 RFC 中稍低一点的 unresolved questions 下,我们了解到这不仅仅是 Rust 的问题。其他语言也会遇到此功能的问题:

.. 大多数支持类似功能的编译器都要求或强烈建议作者仅在裸函数中编写内联汇编,以确保不会生成假定特定堆栈布局的代码。

原因是所有编译器都对函数的调用方式做了很多假设(关键字:“调用者保存的寄存器”、“被调用者保存的寄存器”、“调用约定”、“红色区域”)。裸函数不遵守这些假设,因此编译器生成的任何代码都高度可能是错误的。 “解决方案”是不让编译器生成任何东西,即在汇编中手动编写整个函数。

因此,您在裸函数中混合“正常”代码 (let mut nr: u32 = 0; )、函数调用 (swi_service_routine(nr);) 和原始汇编程序的方式是未指定行为。 (是的,Rust 中存在这样的东西,但仅在 Unstable 中)。

裸函数会导致足够多的问题,它们应该在 Rust 错误跟踪器中使用their own label。 在其中一个 A-naked 问题中,我们找到了知识渊博的用户 Tari(其中包括 llvm-sys 的作者)的this comment。他解释说:

裸函数中非asm代码的实际正确性取决于优化器和代码生成器,通常我们无法保证它会做什么。

还有人谈到需要 unsafe 用于裸函数,因为它们打破了 Rust 的许多正常假设。事实上他们在所有情况下都不需要这个is an open bug


因此,“优化问题”的正确解决方案是完全停止依赖优化。相反,只写一个asm!() 块。

对于您的Cpu::save_context() / Cpu::restore_context_and_return() 对:我可以理解代码重用的愿望。要获得它,请将它们更改为插入相关asm!(...) 的宏。 asm!(...); asm!(...); asm!(...); 的串联应该等效于单个asm!()

【讨论】:

  • 虽然包含有用的信息,但这并不能回答问题,作为对原始问题的评论可能会更好。
  • 我愿意,但我可怜的 41 代表阻止了我这样做.. 感谢您的反馈!
  • 澄清一下,我相信发帖者可能患有XY-problem。他正在将“正常”的 rust (let mut nr: u32 = 0;) 与原始汇编程序 (asm!) 以及看起来像函数调用 (swi_service_routine(nr);) 的东西混合在一起。正如我的链接所解释的,这种混合在 #[naked] 函数中似乎是无效的。 debug-version意外起作用了,release优化暴露了#[naked]的错误用法,那么问题不是“如何不优化单个函数”,而是“如何编写这个函数正确"
  • @JulesKerssemakers:你说得对,#[naked] 函数高度特定于平台且不安全。但是,您认为单个 asm! 会有所帮助是错误的。正如您可以自己尝试的那样,优化器甚至会触及此汇编代码。因此,控制优化器至少会有一点的帮助。最好的办法是完全避免裸函数,但不幸的是,Rust 没有提供所需的调用约定(还没有?)。所以这只是半个 XY 问题,但我已经检查了 X 部分(调用约定)。 :-)
  • 哇,我没有意识到即使是原始 asm 也被优化了。这似乎正在进入真正的实验性生锈角落。您是否尝试过在 users.rustlang.org 上询问?特别是this thread 处理微控制器的低级内容,这可能是人们最有可能了解这些内容的地方。
【解决方案2】:

如果你使用 cargo,你可以告诉它根本不优化任何东西,或者按级别优化

cargo optimize

【讨论】:

  • 谢谢,但我知道这样的全局优化。是否有更细粒度的控制的问题。
  • 抱歉,我误解了这个问题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-03-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-06-17
  • 1970-01-01
相关资源
最近更新 更多