Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

(誤)最適化

レジスタの読み書きはかなり特殊です。副作用を体現したものだと言ってしまってもよいくらいです。前の例では、同じレジスタに 4 つの異なる値を書き込みました。そのアドレスがレジスタだと知らなければ、ロジックを単純化して最後の値 1 << (11 + 16) だけを書き込むようにしてしまうかもしれません。

実際には、コンパイラのバックエンド / オプティマイザである LLVM は、こちらがレジスタを扱っていることを知りません。そのため、これらの書き込みをまとめてしまい、結果としてプログラムの動作を変えてしまいます。手早く確認してみましょう。

$ cargo run --release
(..)
Breakpoint 1, registers::__cortex_m_rt_main_trampoline () at src/07-registers/src/main.rs:7
7       #[entry]

(gdb) step
registers::__cortex_m_rt_main () at src/07-registers/src/main.rs:9
9           aux7::init();

(gdb) next
25              *(GPIOE_BSRR as *mut u32) = 1 << (11 + 16);

(gdb) disassemble /m
Dump of assembler code for function _ZN9registers18__cortex_m_rt_main17h45b1ef53e18aa8d0E:
8       fn main() -> ! {
   0x08000248 <+0>:     push    {r7, lr}
   0x0800024a <+2>:     mov     r7, sp

9           aux7::init();
   0x0800024c <+4>:     bl      0x8000260 <aux7::init>
   0x08000250 <+8>:     movw    r0, #4120       ; 0x1018
   0x08000254 <+12>:    mov.w   r1, #134217728  ; 0x8000000
   0x08000258 <+16>:    movt    r0, #18432      ; 0x4800

10
11          unsafe {
12              // マジックアドレス!
13              const GPIOE_BSRR: u32 = 0x48001018;
14
15              // "North" LED(赤)を点灯
16              *(GPIOE_BSRR as *mut u32) = 1 << 9;
17
18              // "East" LED(緑)を点灯
19              *(GPIOE_BSRR as *mut u32) = 1 << 11;
20
21              // "North" LED を消灯
22              *(GPIOE_BSRR as *mut u32) = 1 << (9 + 16);
23
24              // "East" LED を消灯
25              *(GPIOE_BSRR as *mut u32) = 1 << (11 + 16);
=> 0x0800025c <+20>:    str     r1, [r0, #0]
   0x0800025e <+22>:    b.n     0x800025e <registers::__cortex_m_rt_main+22>

End of assembler dump.

今回は LED の状態が変化しませんでした! str 命令は、レジスタに値を書き込む命令です。debug(非最適化)プログラムにはそれが 4 つあり、レジスタへの各書き込みごとに 1 つずつ対応していましたが、release(最適化)プログラムには 1 つしかありません。

これは objdump を使って確認でき、その出力を out.asm に保存できます。

# cargo objdump -- -d --no-show-raw-insn --print-imm-hex --source target/thumbv7em-none-eabihf/debug/registers と同じ
cargo objdump --bin registers -- -d --no-show-raw-insn --print-imm-hex --source > debug.txt

次に、debug.txt の中で main を探すと、4 つの str 命令があることが分かります。

080001ec <main>:
; #[entry]
 80001ec:       push    {r7, lr}
 80001ee:       mov     r7, sp
 80001f0:       bl      #0x2
 80001f4:       trap

080001f6 <registers::__cortex_m_rt_main::hc2e3436fa38cd6f2>:
; fn main() -> ! {
 80001f6:       push    {r7, lr}
 80001f8:       mov     r7, sp
;     aux7::init();
 80001fa:       bl      #0x3e
 80001fe:       b       #-0x2 <registers::__cortex_m_rt_main::hc2e3436fa38cd6f2+0xa>
;         *(GPIOE_BSRR as *mut u32) = 1 << 9;
 8000200:       movw    r0, #0x2640
 8000204:       movt    r0, #0x800
 8000208:       ldr     r0, [r0]
 800020a:       movw    r1, #0x1018
 800020e:       movt    r1, #0x4800
 8000212:       str     r0, [r1]
;         *(GPIOE_BSRR as *mut u32) = 1 << 11;
 8000214:       movw    r0, #0x2648
 8000218:       movt    r0, #0x800
 800021c:       ldr     r0, [r0]
 800021e:       str     r0, [r1]
;         *(GPIOE_BSRR as *mut u32) = 1 << (9 + 16);
 8000220:       movw    r0, #0x2650
 8000224:       movt    r0, #0x800
 8000228:       ldr     r0, [r0]
 800022a:       str     r0, [r1]
;         *(GPIOE_BSRR as *mut u32) = 1 << (11 + 16);
 800022c:       movw    r0, #0x2638
 8000230:       movt    r0, #0x800
 8000234:       ldr     r0, [r0]
 8000236:       str     r0, [r1]
;     loop {}
 8000238:       b       #-0x2 <registers::__cortex_m_rt_main::hc2e3436fa38cd6f2+0x44>
 800023a:       b       #-0x4 <registers::__cortex_m_rt_main::hc2e3436fa38cd6f2+0x44>
 (..)

LLVM がプログラムを誤って最適化しないようにするにはどうすればよいのでしょうか? 通常の読み書きの代わりに volatile 操作を使います。

#![no_main]
#![no_std]

use core::ptr;

#[allow(unused_imports)]
use aux7::entry;

#[entry]
fn main() -> ! {
    aux7::init();

    unsafe {
        // マジックアドレス!
        const GPIOE_BSRR: u32 = 0x48001018;

        // "North" LED(赤)を点灯
        ptr::write_volatile(GPIOE_BSRR as *mut u32, 1 << 9);

        // "East" LED(緑)を点灯
        ptr::write_volatile(GPIOE_BSRR as *mut u32, 1 << 11);

        // "North" LED を消灯
        ptr::write_volatile(GPIOE_BSRR as *mut u32, 1 << (9 + 16));

        // "East" LED を消灯
        ptr::write_volatile(GPIOE_BSRR as *mut u32, 1 << (11 + 16));
    }

    loop {}
}

--release モードで release.txt を生成します。

cargo objdump --release --bin registers -- -d --no-show-raw-insn --print-imm-hex --source > release.txt

次に release.txt の中で main ルーチンを探すと、4 つの str 命令があることが分かります。

0800023e <main>:
; #[entry]
 800023e:       push    {r7, lr}
 8000240:       mov     r7, sp
 8000242:       bl      #0x2
 8000246:       trap

08000248 <registers::__cortex_m_rt_main::h45b1ef53e18aa8d0>:
; fn main() -> ! {
 8000248:       push    {r7, lr}
 800024a:       mov     r7, sp
;     aux7::init();
 800024c:       bl      #0x22
 8000250:       movw    r0, #0x1018
 8000254:       mov.w   r1, #0x200
 8000258:       movt    r0, #0x4800
;         intrinsics::volatile_store(dst, src);
 800025c:       str     r1, [r0]
 800025e:       mov.w   r1, #0x800
 8000262:       str     r1, [r0]
 8000264:       mov.w   r1, #0x2000000
 8000268:       str     r1, [r0]
 800026a:       mov.w   r1, #0x8000000
 800026e:       str     r1, [r0]
 8000270:       b       #-0x4 <registers::__cortex_m_rt_main::h45b1ef53e18aa8d0+0x28>
 (..)

4 回の書き込み(str 命令)が保持されていることが分かります。gdb を使って実行すると、期待どおりの動作になることも確認できます。

注: 最後の nextloop {} を延々と実行するので、Ctrl-c を使って (gdb) プロンプトに戻ってください。

$ cargo run --release
(..)

Breakpoint 1, registers::__cortex_m_rt_main_trampoline () at src/07-registers/src/main.rs:9
9       #[entry]

(gdb) step
registers::__cortex_m_rt_main () at src/07-registers/src/main.rs:11
11          aux7::init();

(gdb) next
18              ptr::write_volatile(GPIOE_BSRR as *mut u32, 1 << 9);

(gdb) next
21              ptr::write_volatile(GPIOE_BSRR as *mut u32, 1 << 11);

(gdb) next
24              ptr::write_volatile(GPIOE_BSRR as *mut u32, 1 << (9 + 16));

(gdb) next
27              ptr::write_volatile(GPIOE_BSRR as *mut u32, 1 << (11 + 16));

(gdb) next
^C
Program received signal SIGINT, Interrupt.
0x08000270 in registers::__cortex_m_rt_main ()
    at ~/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ptr/mod.rs:1124
1124            intrinsics::volatile_store(dst, src);
(gdb)