如何真正禁用所有 rustc 优化?

发布于 2025-01-13 05:35:22 字数 2446 浏览 1 评论 0原文

我正在尝试通过编译 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 传递之前进行的。

$ 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.*) }
}

I'm trying to learn assembly through compiling Rust. I have found a way to compile Rust code to binary machine code and be able to objdump it to view the assembly. However if I write the following:

#![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 {}
}

The assembly I get is:

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

So it looks like rust is collapsing the mul to a constant. I'm using the following compile options:

Cargo.toml:

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

Is there a way to stop Rust from optimizing this?

The LLVM emitted looks like this:

; 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

So it looks like the optimization is before the LLVM pass.

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

Command to build:

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.*) }
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

囚我心虐我身 2025-01-20 05:35:22

在生成 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

另请参阅:

There is one compiler pass before the generation of LLVM-IR, which is the generation of MIR, the Rust intermediate representation. If you emit this for the given code with a command such as this one:

cargo rustc -- --emit mir

You will see in the .mir file generated that the optimization already took place there.

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
    }
}

This is happening because the mir-opt-level option currently only exists as an unstable compiler option. It is not available as a profile property in Cargo. Set it manually on a direct call to the compiler:

cargo rustc -- -Z mir-opt-level=0 --emir mir

And this optimization will disappear:

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
    }
}

And this is probably as far as you can go without touching LLVM directly. Some optimisations in specific parts of the code can also be prevented through constructs such as black_box.

See also:

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文