Premature optimization on Cortex-M4?

I have a bit of embedded development experience, but I am trying to use the Discovery book (https://docs.rust-embedded.org/discovery/index.html) to learn about Rust on the Cortex-4M.

The book has you create a program that flashes an LED on and off at a 1 Hz rate. The code (directly from the book) is as follows:

#![deny(unsafe_code)]
#![no_main]
#![no_std]

use aux5::{entry, prelude::*, Delay, Leds};

#[entry]
fn main() -> ! {
    let (mut delay, mut leds): (Delay, Leds) = aux5::init();

    let half_period = 500_u16;
    
    loop {
        leds[0].on();
        delay.delay_ms(half_period);

        leds[0].off();
        delay.delay_ms(half_period);
    }
}

That worked. Then the book says to use the debugger to set the half_period variable to different values. I tried, and it had no impact. So I disassembled the code to get the following. I won't show all of it, but the relevant pieces, below. It starts out by creating space on the stack for local variables, including space for half_period. then it sets half_period to 500 at 0x8000210. However, when it comes time to call the delay.delay_ms function, the value 500 is loaded into r1, rather than reading the value of the variable half_period, see 0x8000234.

So, it seems to be treating my stack variable as a const. I decided to replace half_period with a static named HALF_PERIOD - but the same thing happened.

Next I called rustc directly from the compile line with the option "-C opt-level=0." Same result.

Now, the resulting compiled code has behavior that is correct, but I can no longer use gdb to set the value. Is this a feature of the compiler, to make things more efficient even though I am compiling in debug mode, with no optimization? Or is this a bug, prematurely optimizing code even though optimization is off?

080001ea led_roulette::__cortex_m_rt_main::hdb91259dda246b32:
; fn main() -> ! {
 80001ea: 80 b5                        	push	{r7, lr}
 80001ec: 6f 46                        	mov	r7, sp
 80001ee: 92 b0                        	sub	sp, #72
 80001f0: 0a a8                        	add	r0, sp, #40
;     let (mut delay, mut leds): (Delay, Leds) = aux5::init();
 80001f2: 00 f0 39 f8                  	bl	#114
 80001f6: ff e7                        	b	#-2 <led_roulette::__cortex_m_rt_main::hdb91259dda246b32+0xe>
 80001f8: 0a a8                        	add	r0, sp, #40
 80001fa: 03 a9                        	add	r1, sp, #12
 80001fc: 90 e8 1c 50                  	ldm.w	r0, {r2, r3, r4, r12, lr}
 8000200: 81 e8 1c 50                  	stm.w	r1, {r2, r3, r4, r12, lr}
 8000204: 0f 98                        	ldr	r0, [sp, #60]
 8000206: 10 99                        	ldr	r1, [sp, #64]
 8000208: 09 91                        	str	r1, [sp, #36]
 800020a: 08 90                        	str	r0, [sp, #32]
 800020c: 4f f4 fa 70                  	mov.w	r0, #500
;     let half_period = 500_u16;
 8000210: 27 f8 02 0c                  	strh	r0, [r7, #-2]   
;     loop {
 8000214: ff e7                        	b	#-2 <led_roulette::__cortex_m_rt_main::hdb91259dda246b32+0x2c>
;         leds[0].on();
 8000216: 43 f6 e0 12                  	movw	r2, #14816
 800021a: c0 f6 00 02                  	movt	r2, #2048
 800021e: 08 a8                        	add	r0, sp, #32
 8000220: 00 21                        	movs	r1, #0
 8000222: 00 f0 57 fa                  	bl	#1198
 8000226: 02 90                        	str	r0, [sp, #8]
 8000228: ff e7                        	b	#-2 <led_roulette::__cortex_m_rt_main::hdb91259dda246b32+0x40>
 800022a: 02 98                        	ldr	r0, [sp, #8]
 800022c: 00 f0 71 fa                  	bl	#1250
 8000230: ff e7                        	b	#-2 <led_roulette::__cortex_m_rt_main::hdb91259dda246b32+0x48>
 8000232: 03 a8                        	add	r0, sp, #12
 8000234: 4f f4 fa 71                  	mov.w	r1, #500
;         delay.delay_ms(half_period);
 8000238: 02 f0 2a fc                  	bl	#10324
 800023c: ff e7                        	b	#-2 <led_roulette::__cortex_m_rt_main::hdb91259dda246b32+0x54>

[remainder deleted]

I believe this is a result of MIR constant propagation, which is done by rustc before it generates LLVM IR and runs LLVM's optimization passes.

In recent versions of rustc, MIR constant propagation is enabled by default in debug mode because it reduces compile times. If it interferes with debugging, you can disable it using the -Zmir-opt-level=0 rustc flag (requires a nightly build of the Rust toolchain).

Edit: There is an issue about this in the Discovery Book repo:

3 Likes

Awesome! Thanks!