我如何真正禁用所有的Rustc优化?

3

我正在尝试通过编译 Rust 来学习汇编语言。我找到了一种将 Rust 代码编译为二进制机器码的方法,并且能够使用 objdump 命令查看生成的汇编代码。然而,如果我写下面这段代码:

#![no_main]

#[link_section = ".text.entry"]
#[no_mangle]
pub extern "C" fn _start() -> ! {
    let a: u64 = 4;
    let b: u64 = 7;
    let c: u64 = a * b;
    
    loop {}
}

我得到的汇编代码如下:
0000000000000000 <.data>:
   0:   1101                    addi    sp,sp,-32
   2:   4511                    li      a0,4
   4:   e42a                    sd      a0,8(sp)
   6:   451d                    li      a0,7
   8:   e82a                    sd      a0,16(sp)
   a:   4571                    li      a0,28
   c:   ec2a                    sd      a0,24(sp)
   e:   a009                    j       0x10
  10:   a001                    j       0x10

看起来rust正在将mul折叠成常数。我使用以下编译选项:

Cargo.toml:

[profile.dev]
opt-level = 0
mir-opt-level = 0

有没有办法阻止 Rust 进行优化?
LLVM 生成的代码如下:
; Function Attrs: noreturn nounwind
define dso_local void @_start() unnamed_addr #0 section ".text.entry" !dbg !22 {
start:
  %c.dbg.spill = alloca i64, align 8
  %b.dbg.spill = alloca i64, align 8
  %a.dbg.spill = alloca i64, align 8
  store i64 4, i64* %a.dbg.spill, align 8, !dbg !36
  call void @llvm.dbg.declare(metadata i64* %a.dbg.spill, metadata !28, metadata !DIExpression()), !dbg !37
  store i64 7, i64* %b.dbg.spill, align 8, !dbg !38
  call void @llvm.dbg.declare(metadata i64* %b.dbg.spill, metadata !31, metadata !DIExpression()), !dbg !39
  store i64 28, i64* %c.dbg.spill, align 8, !dbg !40
  call void @llvm.dbg.declare(metadata i64* %c.dbg.spill, metadata !33, metadata !DIExpression()), !dbg !41

看起来优化是在LLVM pass之前进行的。

$ rustc --version                                                                                                                                           
rustc 1.60.0-nightly (c5c610aad 2022-02-14)

构建命令:

RUSTFLAGS="--emit=llvm-bc" cargo build --target riscv64imac-unknown-none-elf --no-default-features

build.rs

fn main() {
    println!("cargo:rerun-if-changed=build.rs");
    println!("cargo:rustc-link-arg=-Tlink.ld");
}

link.ld

ENTRY(_start)
SECTIONS {
  .text : { *(.text); *(.text.*) }
}

如果你愿意稍微改动一下代码并且不介意使用不安全的方法,你可以使用 volatile 访问来避免这些优化。 - rodrigo
1个回答

10
在生成LLVM-IR之前,有一个编译器步骤需要生成MIR,即Rust的中间表示。如果您使用以下命令为给定代码发出此命令,则会生成它:
cargo rustc -- --emit mir

您将在生成的.mir文件中看到优化已经发生了。
fn _start() -> ! {
    let mut _0: !;                       // return place in scope 0 at src\main.rs:5:31: 5:32
    let _1: u64;                         // in scope 0 at src\main.rs:6:9: 6:10
    scope 1 {
        debug a => _1;                   // in scope 1 at src\main.rs:6:9: 6:10
        let _2: u64;                     // in scope 1 at src\main.rs:7:9: 7:10
        scope 2 {
            debug b => _2;               // in scope 2 at src\main.rs:7:9: 7:10
            let _3: u64;                 // in scope 2 at src\main.rs:8:9: 8:10
            scope 3 {
                debug c => _3;           // in scope 3 at src\main.rs:8:9: 8:10
            }
        }
    }

    bb0: {
        _1 = const 4_u64;                // scope 0 at src\main.rs:6:18: 6:19
        _2 = const 7_u64;                // scope 1 at src\main.rs:7:18: 7:19
        _3 = const 28_u64;               // scope 2 at src\main.rs:8:18: 8:23
        goto -> bb1;                     // scope 3 at src\main.rs:10:5: 10:12
    }

    bb1: {
        goto -> bb1;                     // scope 3 at src\main.rs:10:5: 10:12
    }
}

这是因为mir-opt-level选项目前只作为一个不稳定的编译器选项存在。它在Cargo中不可用作为一个配置属性。需要手动在直接调用编译器时设置它:
cargo rustc -- -Z mir-opt-level=0 --emir mir

而这个优化将会消失:

fn _start() -> ! {
    let mut _0: !;                       // return place in scope 0 at src\main.rs:5:31: 5:32
    let mut _1: !;                       // in scope 0 at src\main.rs:5:33: 11:2
    let _2: u64;                         // in scope 0 at src\main.rs:6:9: 6:10
    let mut _5: u64;                     // in scope 0 at src\main.rs:8:18: 8:19
    let mut _6: u64;                     // in scope 0 at src\main.rs:8:22: 8:23
    let mut _7: (u64, bool);             // in scope 0 at src\main.rs:8:18: 8:23
    let mut _8: !;                       // in scope 0 at src\main.rs:10:5: 10:12
    let mut _9: ();                      // in scope 0 at src\main.rs:5:1: 11:2
    scope 1 {
        debug a => _2;                   // in scope 1 at src\main.rs:6:9: 6:10
        let _3: u64;                     // in scope 1 at src\main.rs:7:9: 7:10
        scope 2 {
            debug b => _3;               // in scope 2 at src\main.rs:7:9: 7:10
            let _4: u64;                 // in scope 2 at src\main.rs:8:9: 8:10
            scope 3 {
                debug c => _4;           // in scope 3 at src\main.rs:8:9: 8:10
            }
        }
    }

    bb0: {
        StorageLive(_1);                 // scope 0 at src\main.rs:5:33: 11:2
        StorageLive(_2);                 // scope 0 at src\main.rs:6:9: 6:10
        _2 = const 4_u64;                // scope 0 at src\main.rs:6:18: 6:19
        StorageLive(_3);                 // scope 1 at src\main.rs:7:9: 7:10
        _3 = const 7_u64;                // scope 1 at src\main.rs:7:18: 7:19
        StorageLive(_4);                 // scope 2 at src\main.rs:8:9: 8:10
        StorageLive(_5);                 // scope 2 at src\main.rs:8:18: 8:19
        _5 = _2;                         // scope 2 at src\main.rs:8:18: 8:19
        StorageLive(_6);                 // scope 2 at src\main.rs:8:22: 8:23
        _6 = _3;                         // scope 2 at src\main.rs:8:22: 8:23
        _7 = CheckedMul(_5, _6);         // scope 2 at src\main.rs:8:18: 8:23
        assert(!move (_7.1: bool), "attempt to compute `{} * {}`, which would overflow", move _5, move _6) -> bb1; // scope 2 at src\main.rs:8:18: 8:23
    }

    bb1: {
        _4 = move (_7.0: u64);           // scope 2 at src\main.rs:8:18: 8:23
        StorageDead(_6);                 // scope 2 at src\main.rs:8:22: 8:23
        StorageDead(_5);                 // scope 2 at src\main.rs:8:22: 8:23
        StorageLive(_8);                 // scope 3 at src\main.rs:10:5: 10:12
        goto -> bb2;                     // scope 3 at src\main.rs:10:5: 10:12
    }

    bb2: {
        _9 = const ();                   // scope 3 at src\main.rs:10:10: 10:12
        goto -> bb2;                     // scope 3 at src\main.rs:10:5: 10:12
    }
}

如果不直接涉及LLVM,这可能是你能到达的最远点了。通过像 black_box 这样的结构也可以防止代码特定部分的一些优化。

另请参见:


太好了,非常感谢!我还没有进行深入的调查,但是我注意到如果我改成:let c = a.wrapping_mul(b); 它似乎又可以优化掉乘法操作 :) 我会看一下 MIR 选项。 - ashleysmithgpu
3
即使MIR不行,LLVM的常量折叠也可能会做同样的事情,除非您修改编译器,但这似乎是学习汇编的冗长绕路。 - arnt
我明白了,我想我已经达到了工具无法优化的极限,因为它并不是为此而设计的。谢谢提供链接! - ashleysmithgpu

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