昨日の修正をするまでの思考過程をまとめてみた
また 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 で変換すると色がおかしくなる。
音のほうは問題なさそうなんだけど。