I am learning about rust and asm, and using godbolt for this.
I have a program that looks like:
pub fn test() -> i32 {
let a = 1;
let b = 2;
let c = 3;
a + b + c
}
And I would expect the output to look something like
example::test:
subq $16, %rsp
movl $1, (%rsp)
movl $2, 4(%rsp)
movl $3, 8(%rsp)
movl (%rsp), %eax
addl 4(%rsp), %eax
addl 8(%rsp), %eax
addq $16, %rsp
retq
But I actually get:
example::test:
mov eax, 6
ret
This is useless when trying to demonstrate stack allocation, addition etc.
I am using the compiler flags: -Z mir-opt-level=0 -C opt-level=0 -C overflow-checks=off
So the MIR isn't optimising away the additions. The MIR output is:
// WARNING: This output format is intended for human consumers only
// and is subject to change without notice. Knock yourself out.
fn test() -> i32 {
let mut _0: i32; // return place in scope 0 at /app/example.rs:2:18: 2:21
let _1: i32; // in scope 0 at /app/example.rs:3:9: 3:10
let mut _4: i32; // in scope 0 at /app/example.rs:6:5: 6:10
let mut _5: i32; // in scope 0 at /app/example.rs:6:5: 6:6
let mut _6: i32; // in scope 0 at /app/example.rs:6:9: 6:10
let mut _7: i32; // in scope 0 at /app/example.rs:6:13: 6:14
scope 1 {
debug a => _1; // in scope 1 at /app/example.rs:3:9: 3:10
let _2: i32; // in scope 1 at /app/example.rs:4:9: 4:10
scope 2 {
debug b => _2; // in scope 2 at /app/example.rs:4:9: 4:10
let _3: i32; // in scope 2 at /app/example.rs:5:9: 5:10
scope 3 {
debug c => _3; // in scope 3 at /app/example.rs:5:9: 5:10
}
}
}
bb0: {
StorageLive(_1); // scope 0 at /app/example.rs:3:9: 3:10
_1 = const 1_i32; // scope 0 at /app/example.rs:3:13: 3:14
StorageLive(_2); // scope 1 at /app/example.rs:4:9: 4:10
_2 = const 2_i32; // scope 1 at /app/example.rs:4:13: 4:14
StorageLive(_3); // scope 2 at /app/example.rs:5:9: 5:10
_3 = const 3_i32; // scope 2 at /app/example.rs:5:13: 5:14
StorageLive(_4); // scope 3 at /app/example.rs:6:5: 6:10
StorageLive(_5); // scope 3 at /app/example.rs:6:5: 6:6
_5 = _1; // scope 3 at /app/example.rs:6:5: 6:6
StorageLive(_6); // scope 3 at /app/example.rs:6:9: 6:10
_6 = _2; // scope 3 at /app/example.rs:6:9: 6:10
_4 = Add(move _5, move _6); // scope 3 at /app/example.rs:6:5: 6:10
StorageDead(_6); // scope 3 at /app/example.rs:6:9: 6:10
StorageDead(_5); // scope 3 at /app/example.rs:6:9: 6:10
StorageLive(_7); // scope 3 at /app/example.rs:6:13: 6:14
_7 = _3; // scope 3 at /app/example.rs:6:13: 6:14
_0 = Add(move _4, move _7); // scope 3 at /app/example.rs:6:5: 6:14
StorageDead(_7); // scope 3 at /app/example.rs:6:13: 6:14
StorageDead(_4); // scope 3 at /app/example.rs:6:13: 6:14
StorageDead(_3); // scope 2 at /app/example.rs:7:1: 7:2
StorageDead(_2); // scope 1 at /app/example.rs:7:1: 7:2
StorageDead(_1); // scope 0 at /app/example.rs:7:1: 7:2
return; // scope 0 at /app/example.rs:7:2: 7:2
}
}
And the LLVM IR output is:
define i32 @_ZN7example4test17h2e9277ab15e59fbdE() unnamed_addr #0 !dbg !5 {
start:
ret i32 6, !dbg !10
}
attributes #0 = { nonlazybind uwtable "probe-stack"="__rust_probestack" "target-cpu"="x86-64" }
So it is at the MIR->LLVM level when the additions are optimised out.
How can I prevent this?
Thanks!
Note
If I use a tuple, the optimisation doesn't happen. e.g
pub fn test() -> i32 {
let a = (1,2,3);
a.0 + a.1 + a.2
}
becomes:
example::test:
subq $16, %rsp
movl $1, (%rsp)
movl $2, 4(%rsp)
movl $3, 8(%rsp)
movl (%rsp), %eax
addl 4(%rsp), %eax
addl 8(%rsp), %eax
addq $16, %rsp
retq