我可以翻译为:我能强制 Rust 不优化一个单独的函数吗?

14

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

5
我有一个函数,其中Rust/llvm的优化失败并导致恐慌。 - E net4
@E_net4:我已经编辑了这个问题。不过,我还是对原始问题很感兴趣,即使有一种聪明的方法可以避免与这个特定函数相关的问题。 - Matthias
正如Matthias在此评论中所解释的那样,该问题是实现不支持的调用约定的一部分。 - Jules Kerssemakers
2个回答

9
不,你不能。
Rust的编译单元(编译器和优化器操作的最小单元)是整个crate。
你唯一的解决方法是在单独的crate中编译此函数,将其编译,然后将其作为预编译依赖项包含进来。(普通的rust依赖项在依赖者的优化级别下编译)
然而:为这个单独的函数指定不同的优化级别并不能解决你的问题!当然,它可能今天可以工作,但每次编译器(或优化标志)更改时都可能会再次出现问题。鉴于你示例中的函数名称(Cpu::save_context/restore_context_and_return),你似乎正在解决的根本问题需要向rustc添加适当的调用约定。
简而言之:裸函数非常不安全(我很尊敬你,你比我勇敢!)。唯一可靠的使用方式是编写一个完整的函数体,只有一个asm!()块,没有其他内容。
混合使用asm!、普通的Rust和函数调用,实际上是未定义行为(在可怕的C/Nasal-Demon意义上)。无论如何优化调整都不会改变这一点。 2022年4月更新:自原来回答此问题以来,裸函数方面发生了很多事情。
裸函数的最小“约束”子集(参见RFC#2972)1即将稳定下来。 还有编译器错误会"拒绝不支持的裸函数",这些错误会触发此处提供的示例。1.60版本
赤裸函数在 Rust 作者 "搞定" 它之前仍然不稳定。正如您所发现的,这存在许多微妙的问题。
稳定问题追踪 在这里,已于2022年被更为有限的"受限制"赤裸函数追踪器所取代。

naked-fn RFC中,“动机”部分中,我们可以找到:

由于编译器依赖于函数序言和尾声来维护本地变量绑定的存储,因此除内联汇编外,写入任何内容都是不安全的。 LLVM 语言参考将此功能描述为具有“非常系统特定的后果”,程序员必须意识到这一点。

(重点是我的)

在RFC的稍低部分,未解决的问题下面,我们得知这不仅是Rust的问题。其他编程语言也会遇到此功能的问题:
“..大多数支持类似功能的编译器要求或强烈建议作者只在裸函数内写入内联汇编,以确保生成的代码不会假定特定的堆栈布局。”
原因在于所有编译器都对函数调用有很多假设(关键词:“Caller-Saved Registers”,“Callee-saved registers”,“Calling convention”,“Red zone”)。裸函数不遵守这些假设,因此编译器生成的任何代码都极有可能是错误的。 “解决方案”是不让编译器生成任何东西,即手动用汇编语言编写整个函数。
作为这样的,你将'正常'代码(let mut nr: u32 = 0;)、函数调用(swi_service_routine(nr);)和裸汇编混合在一个裸函数中是未指定的行为。(是的,在Rust中存在这样的事情,但只在不稳定版本中存在)。
裸函数引起了足够多的问题,以至于它们在Rust bugtracker中应该有自己的标签。在其中一个A-naked问题中,我们发现这个评论,由知识渊博的用户Tari(作者之一llvm-sys)解释道:

非汇编代码在裸函数中的实际正确性取决于优化器和代码生成器,一般来说我们无法保证它会做什么。

有关裸函数,也有讨论要求使用unsafe,因为它们破坏了Rust的许多正常假设。 事实上,在所有情况下它们尚未要求此是一个未解决的错误
2022更新: 通过新的默认拒绝lints于2022-01-21关闭,以拒绝不支持的裸函数(#93153)


所以,你的“优化问题”的正确解决方案是停止依赖优化。相反,只编写一个单一的asm!()块。
对于你的Cpu::save_context()/Cpu::restore_context_and_return()配对:我可以理解对代码重用的渴望。为了实现它,将其更改为插入相关asm!(...)的宏即可。 asm!(...); asm!(...); asm!(...); 的连接应该等同于单个asm!()

1
虽然包含有用的信息,但这并没有回答问题,最好将其作为原始问题的评论。 - Shepmaster
我想这么做,但我的可怜的41声望值阻止了我这样做...不过还是谢谢你的反馈! - Jules Kerssemakers
澄清一下,我认为发帖者可能正在遭受XY问题的困扰。他正在将“正常”的rust (let mut nr: u32 = 0;)与原始汇编(asm!)以及类似函数调用的东西(swi_service_routine(nr);)混合使用。这种混合在#[naked]函数中似乎是无效的,正如我的链接所解释的那样。调试版本“意外地”工作了,而发布优化则暴露了#[naked]的不正确使用。在这种情况下,问题不是“如何不优化单个函数”,而是“如何正确编写此函数”。 - Jules Kerssemakers
1
@JulesKerssemakers:你是对的,#[naked]函数高度依赖于平台且不安全。但是,你错了,单个asm!也无济于事。正如你可以自己尝试的那样,优化器甚至会触及这段汇编代码。因此,控制优化器至少会有所帮助。最好的方法是尽可能避免裸函数,但不幸的是,Rust没有提供所需的调用约定(还没有?)。因此,这只是一个半个XY问题,但我已经检查过X部分(调用约定)了。 :-) - Matthias
哇,我没想到即使是原始汇编也被优化了...这似乎已经进入了 Rust 的实验性领域。你有试过在 users.rustlang.org 上问吗?特别是这个帖子,它涉及微控制器的低级问题,这可能是 Rust 中人们了解这些问题的最有可能的领域。 - Jules Kerssemakers

-5
如果您正在使用 Cargo,您可以告诉它不要优化任何内容,或按级别进行优化。 Cargo 优化

5
谢谢,但我已经了解这样的全局优化。问题是是否有更细粒度的控制。 - Matthias
抱歉,我误解了问题。 - jesusbv - user3085938

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