昨日の修正をするまでの思考過程をまとめてみた

また FFmpeg の make が通らない - EAGLE 雑記 の続きみたいな。
昨日はちょっと時間が遅かったので書けなかったけど、忘れないうちにメモ。
間違っている箇所があったら指摘してくれるとありがたいです。
今回使用したのは gcc 4.3.2、CPU は Intel(R) Core(TM)2 Duo CPU T8300 @ 2.40GHz


まずはコンパイルできない例から。

static void f(uint32_t *a, uint32_t *b)
{
  asm volatile(
      "movd %4, %%mm0\n\t"
      "movd %5, %%mm1\n\t"
      "movd %6, %%mm2\n\t"
      "movd %7, %%mm3\n\t"
      "movd %%mm0, %0\n\t"
      "movd %%mm1, %1\n\t"
      "movd %%mm2, %2\n\t"
      "movd %%mm3, %3\n\t"
      : "=m"(b[0]), "=m"(b[1]), "=m"(b[2]), "=m"(b[3])
      : "m"(a[0]), "m"(a[1]), "m"(a[2]), "m"(a[3])
  );
}

これは MMXレジスタを使ってコピーしているだけのコードだけど、例のエラーによってコンパイルできない。

% gcc movd.c
movd.c: In function 'f':
movd.c:19: error: can't find a register in class 'GENERAL_REGS' while reloading 'asm'
movd.c:19: error: 'asm' operand has impossible constraints

しかし、これを 64bit コンパイルしてみると通る。

% gcc -m64 movd.c
% 

そして、正しい意図した通りの結果を出す。


そこで、この関数はどのようなアセンブリコードになっているか見てみた。

_f:
LFB4:
  pushq %rbp
LCFI3:
  movq  %rsp, %rbp
LCFI4:
  movq  %rdi, -8(%rbp)
  movq  %rsi, -16(%rbp)
  movq  -16(%rbp), %rax
  leaq  4(%rax), %r9 
  movq  -16(%rbp), %rax
  leaq  8(%rax), %r10
  movq  -16(%rbp), %rax
  leaq  12(%rax), %rdi
  movq  -8(%rbp), %rax
  leaq  4(%rax), %r8 
  movq  -8(%rbp), %rax
  leaq  8(%rax), %rsi
  movq  -8(%rbp), %rax
  leaq  12(%rax), %rcx
  movq  -16(%rbp), %rdx
  movq  -8(%rbp), %rax
# 19 "movd.c" 1
  movd (%rax), %mm0
  movd (%r8), %mm1
  movd (%rsi), %mm2
  movd (%rcx), %mm3
  movd %mm0, (%rdx)
  movd %mm1, (%r9)
  movd %mm2, (%r10)
  movd %mm3, (%rdi)

# 0 "" 2
  leave
  ret

これを見る限り、最初に汎用レジスタに a[0] - a[3] および b[0] - b[3] のアドレスをすべて用意しておいてから、俺が書いたインラインアセンブリのコードを実行するようになっている。


一方、32bit コンパイルでも通るコードにしようとしてみると、以下のコードなら通ることがわかった。

static void f(uint32_t *a, uint32_t *b) 
{
  asm volatile(
      "movd %2, %%mm0\n\t"
      "movd %3, %%mm1\n\t"
      "movd %4, %%mm2\n\t"
      "movd %%mm0, %0\n\t"
      "movd %%mm1, %1\n\t"
      : "=m"(b[0]), "=m"(b[1])
      : "m"(a[0]), "m"(a[1]), "m"(a[2])
  );  
}

さっきのコードと変わっているのは、インラインアセンブリで用いている変数の数である。
5個がギリギリのようで、6個にすると再び例のエラーで通らない。
この関数はこうコンパイルされていた。

_f:
  pushl %ebp
  movl  %esp, %ebp
  pushl %edi
  pushl %esi
  movl  12(%ebp), %eax
  leal  4(%eax), %edi
  movl  8(%ebp), %eax
  leal  4(%eax), %esi
  movl  8(%ebp), %eax
  leal  8(%eax), %ecx
  movl  12(%ebp), %edx
  movl  8(%ebp), %eax
# 19 "movd.c" 1
  movd (%eax), %mm0
  movd (%esi), %mm1
  movd (%ecx), %mm2
  movd %mm0, (%edx)
  movd %mm1, (%edi)
  
# 0 "" 2
  popl  %esi
  popl  %edi
  popl  %ebp
  ret

これまた、あらかじめすべての変数のアドレスを汎用レジスタに移してから、インラインアセンブラのコードを実行している。


これでだいたい予想がついた。
つまり、すべての変数のアドレスを最初に汎用レジスタに移してしまうため、32bit環境だと eax, ecx, edx, esi, edi の5つの汎用レジスタすべてを使い切ってしまい、6個以上の変数を扱えないらしい。
ebx は PLT のために GOT ポインタとして使われるため、保存しておかないと使えない。*1
一方、64bit環境ならば汎用レジスタは上記5個の他に r8 - r15 があるため、数が足りなくなってエラーということにはならない。
実際、14個(rax - rdx, r8 - r15)までの変数なら通るが、15個以上になると 64bit コンパイルも通らなくなる。*2


というわけで、単純な解決方法として思いついたのが「適当なところでインラインアセンブリを区切ればいいんじゃね?」ということでした。
volatile 指定してあるから、たぶん分割したインラインアセンブリの間にへんなコードが入ることもないはず。
libswscale/rgb2rgb_template.c のほうはインラインアセンブリ中で使われていない変数があったから、それを削った。
とはいっても、まだほとんどテストしていないので実際にどうかは不明。

追記

ダメでしたorz
ffplay で見る分には問題ないんだけど、ffmpeg で変換すると色がおかしくなる。
音のほうは問題なさそうなんだけど。

*1:自動的にスタックへ保存してくれてもいいと思うんだけど。。。

*2:このときは rbx, r12 - r15 が自動的にスタックに保存されている。なぜこれと同様のことを32bitでやってくれないんだ!