インラインアセンブリだけの関数の最適化でハマった

はじめに

Cortex-Mのコンテキストスイッチを書いています。 引数有りで、インラインアセンブリだけを実行する関数を作り、threadのディスパッチを書いていました。

このような場合、適切なconstraintを書いてインラインアセンブリを呼び出さないと、リリースビルドの最適化で意図せぬ挙動になります。

問題を起こしたdispatch関数

まず、デバッグビルドでのみ動作する実装です。

/// A dispatcher of Cortex-M.
/// Parameters:
///   sp: stack pointer
pub extern fn dispatch(sp: *usize) void {
  asm volatile (
    \\ ldr sp, [r0]
    \\ pop {r4-r12,lr}
    \\ mov r0,r12
    \\ bx lr
  );
}

第一引数は、r0に格納されるため、そのままバイナリが出力されれば、問題なく動き、スレッドが起動します。

症状

上記プログラムをリリースビルドすると、HardFaultが発生し、スレッドが正常に起動しません。 dispatchのシンボルを強制的にexportし、break pointを作ってデバッグします。

その結果、dispatchに到達した時点で、r0(引数のsp)に0が格納されていることがわかりました (補足ですが、ここでの期待値は、RAM領域なので、0x200xxxxxです)。 dispatch関数のインライン化を抑制して、明示的に関数呼び出しするようにしても、症状が改善しませんでした。

調査を進めたところ、dispatch内で明示的に引数のspを使用すると、正常に動作することがわかりました。

解決策

input constraintを使って、spを利用することを明示的に示します。

/// A dispatcher of Cortex-M.
/// Parameters:
///   sp: stack pointer
pub extern fn dispatch(sp: *usize) void {
  // **Note** Need explicit constraint to prevent compiler from mis-optimizing.
  asm volatile (
    "ldr sp, [%[sp_i]]":: [sp_i] "r" (sp)
  );

  asm volatile (
    \\ pop {r4-r12,lr}
    \\ mov r0,r12
    \\ bx lr
  );
}

これで、リリースビルドでも、意図通り動きます。

せめて、インライン化を抑制したら、引数くらいはちゃんと渡して欲しいです…。