Skip to content
Go back

C++はアセンブリをどこまで書ける?

· Updated:
  • nocashのログ出力を実装したい
    • 指定の構造のプログラムを実行すると、そこにあるデータを文字列としてログに出力してくれる
    • 素直に使う分には、実行時コストがほぼかからない
mov r12, r12
b exit
.hword 0x6464
.hword 0
.byte 'message'
exit:
bx lr
  • まずは、IWRAMにプログラムを定義して使い回す方法を試した
    • アセンブリ実装と呼び出し用関数を用意するだけなので簡単
    • 文字列部分を都度書き換えるので、コピー分の実行時コストがかかる
  • 次に、インラインアセンブリを試したが、実現できなかった
    • データ・ディレクティブを記述できないので、文字列部分を表現できない
  • 結果、コンパイル時にプログラム相当のバイト列を作って、そこを関数として呼び出すようにした
    • 汚いやり方だけど、すべてを静的に定義できる
    • ジャンプだけインラインアセンブリで書こうとしてもできなかった
      • なぜか、r3が未定義と言われてコンパイルできなかった
    • 最終的に、関数ポインタに無理やりキャストして呼び出すことにした
  • おそらく、盛大にUBを踏んでいるので、コンパイラがこちらの意図を汲んでくれるか、きちんと確認すること
    • コンパイル時配列のアドレスを取って良いのか、とか
    • 配列のポインタを関数のポインタにキャストして良いのか、とか
struct FormatString {
  template <size_t N>
  constexpr FormatString(const char (&msg)[N]) noexcept {
    if (N > 120) std::terminate();
    bin[0] = 0xe4;  // mov r12, r12
    bin[1] = 0x46;
    bin[2] = 56;    // b exit
    bin[3] = 0xe0;
    bin[4] = 0x64;
    bin[5] = 0x64;  // .hword 0x6464
    bin[6] = 0x00;
    bin[7] = 0x00;  // .hword 0
    for (size_t i = 0; i < N; ++i) {
      bin[8 + i] = msg[i];  // .byte 'msg'
    }
    // exit:
    bin[118] = 0x70;  // bx lr
    bin[119] = 0x47;
  }
  alignas(2) std::array<char, 120> bin{};
};

template <FormatString S>
inline void trace() noexcept {
  // thumbステートで呼び出す
  const auto* volatile ptr = S.bin.data() + 1;
  auto* volatile fn = ((void (*)())ptr);
  fn();
}