2008-05-22

x264のインラインアセンブリをMSVCでコンパイルできるように移植してみた

もちろん、例のx264_median_mvの部分だ。インラインアセンブリにする必要性があるらしい。というわけでMSVCでコンパイルできるようにutil.hを書き直してみた。まあ、単にMSVCのCompiler Intrinsicsを試してみたかっただけだが。

/*****************************************************************************
* mc.h: h264 encoder library
*****************************************************************************
* Copyright (C) 2008 Loren Merritt
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA.
*****************************************************************************/

#ifndef X264_X86_UTIL_H
#define X264_X86_UTIL_H

#ifdef __GNUC__
#define x264_median_mv x264_median_mv_mmxext
static inline void x264_median_mv_mmxext( int16_t *dst, int16_t *a, int16_t *b, int16_t *c )
{
  asm(
    "movd  %1,  %%mm0 \n"
    "movd  %2,  %%mm1 \n"
    "movq  %%mm0, %%mm3 \n"
    "movd  %3,  %%mm2 \n"
    "pmaxsw %%mm1, %%mm0 \n"
    "pminsw %%mm3, %%mm1 \n"
    "pminsw %%mm2, %%mm0 \n"
    "pmaxsw %%mm1, %%mm0 \n"
    "movd  %%mm0, %0  \n"
    :"=m"(*(uint32_t*)dst)
    :"m"(*(uint32_t*)a), "m"(*(uint32_t*)b), "m"(*(uint32_t*)c)
  );
}

#elif defined(_MSC_VER)
#include <emmintrin.h>
#define x264_median_mv x264_median_mv_sse2
static inline void x264_median_mv_sse2( int16_t *dst, int16_t *a, int16_t *b, int16_t *c )
{
  __m128i m0, m1, m2, m3 ;

  m0 = _mm_cvtsi32_si128( *(uint32_t*)a ) ;
  m1 = _mm_cvtsi32_si128( *(uint32_t*)b ) ;
  m3 = _mm_move_epi64( m0 ) ;
  m2 = _mm_cvtsi32_si128( *(uint32_t*)c ) ;
  m0 = _mm_max_epi16( m0, m1 ) ;
  m1 = _mm_min_epi16( m1, m3 ) ;
  m0 = _mm_min_epi16( m0, m2 ) ;
  m0 = _mm_max_epi16( m0, m1 ) ;

  (*(uint32_t *)dst) = _mm_cvtsi128_si32( m0 ) ;
}
#endif

#endif

驚くべきことに、このコードは問題なく動くようだ。

このコードの何が、x264開発者の連中のお気に召さないかというと、奴らはコンパイラを一切信用していないからだ。出力されたコードは、それほど悪いものでもないと思うのだが。

マイクロソフトが、x64からインラインアセンブリを捨てて、このようなCompiler Intrinsicsを豊富に提供しだしたのは、まあ、色々理由があるのだろうけど、ひとつにはレジスタの割り当てというのがあるのではないか。x264もだいぶ苦労していて、いまはマクロアセンブラのマクロ機能を多用して、各プラットフォームごとにレジスタの割り当てを切り替えている。Windows向けのx64コードは誰も気にしていないので、当然動かない。そんなことをするぐらいだったら、やはりこういうCompiler Intrinsicsの方がいいのではないだろうか。

追記
関数名を正しく変更した。このコードはSSE2を要求する。つまり、このコードは、Pentium 4、あるいはAthlon 64 以降でないと動かない。なぜSSE2を使ったかというと、MMXはx64ではサポートされていないからだ。

追記2
int16_t *をint32_t *にキャストするようにした。どうやら、このポインタのキャストは、意図したものらしい。

追記3 dstの型をキャストするのを忘れていた。Cキャストはuglyで嫌いだ。reinterpret_castを使いたい。

2 comments:

Anonymous said...

仕事でintrinsic使いまくっていますが、かなりいいですよ。

昔は頑張って__asmかMASMでやっていましたが、ソースがでかくなるとレジスターアロケーションが手動では無理です。あとx64で動かない。

吐かれるコードに関しても、一昔前まではクセがあってIntelCompilerとかでやったほうが速いとかあったのですが、VS2005以降はほとんど差がないですし。

江添亮 said...

上の例では、既に元になるアセンブラがあるので、レジスタの名前も合わせていて、実際コンパイラもこの通りにレジスタを割り当てるようです。
しかし、レジスタを意味する構造体の変数名に、もっと分かりやすい名前をつけることもできますし、其の場合、必要のなくなった変数はそれ以降読まないなど、C++として、当然のコードを書けば、賢いコンパイラなら、人間に劣らないレジスタの割り当てをしてくれると思うのですがね。

後は、コンパイラを信じられるかどうかですね。