Xbyakで簡単な関数を実行時に生成する

2013-09-11

iPhoneやAndroidなどで動くゲームエンジンであるCocos2dxではマルチプラットフォームを実現するためにゲームロジック部をC++で書くんだけど、スクリプト言語として軽量組み込み言語であるLuaやJavaScriptを使うこともできる。それも単なるインタプリタじゃなくてLuaJITやSpiderMonkeyを組み込んでいる。なのでスクリプト言語を使ったからといって速度的に大きなペナルティはない模様、すげえ!iPhoneやAndroidでCPU違っても動くんだろうか。

てことで、また瞬間的にJIT熱にうかされた。JITってどうやって作るのか全く知らないので、まずXbyakというものを触ってみた。そもそもx86のアーキテクチャもほとんど知らないので辛いんだけど、簡単なサンプルとして、定数を返す関数を作るクラスを生成する例:

#include "xbyak/xbyak.h"
#include <iostream>
using namespace std;

class ConstFunc : public Xbyak::CodeGenerator {
public:
ConstFunc(int x) {
mov(eax, x); // eax = 即値
ret();
}
int (*get() const)() { return getCode<int(*)()>(); }
};

int main() {
#ifdef XBYAK64_GCC
cout << "64bit mode(gcc)" << endl;
#elif defined(XBYAK64_WIN)
cout << "64bit mode(win)" << endl;
#else
cout << "32bit" << endl;
#endif

cout << ConstFunc(1234).get()() << endl;
// => 1234

return 0;
}

コンパイルするにはg++で-fno-operator-namesオプションをつける必要がある(Xbyakで始めるx86(IA-32)入門(2-1) (mitsunari@cybozu labs))。

Xbyakでのコードの生成は、Xbyak::CodeGeneratorクラスを継承して、x86のニーモニックに似たメソッドでコードを組み立てて、getCode<関数の型>()で取得して使うってのが流れ。本当はx86の32ビットか64ビットかなどで使う命令を変える必要があるみたいなんだけど、自分の環境でしか試してないため64bit GCC用のコードとなっている。戻り値はeaxレジスタに入れてやればよい。mov命令でレジスタに即値、または他のレジスタの値を代入する。

次に引数を受け取る関数のテストとして、ある値を加算する関数を生成する:

class AdderFunc : public Xbyak::CodeGenerator {
public:
AdderFunc(int x) {
mov(eax, edi);
add(eax, x);
ret();
}
int (*get() const)(int) { return getCode<int(*)(int)>(); }
};

int main() {
cout << AdderFunc(123).get()(456) << endl;
// => 579
}

64bitのx86でのgccの呼出規約で、関数への整数の引数は1番目がedi、以降esi, edx, ecs, er8, er9…に渡されるらしい(gccでのレジスタ - x64 Assembly Language Programming)。

次は条件分岐を使う例として、階乗を求める関数を生成する:

class FactorialFunc : public Xbyak::CodeGenerator {
public:
FactorialFunc() {
inLocalLabel(); // use local label for multiple instance
mov(eax, 1);
L(".lp");
mul(edi);
dec(edi);
cmp(edi, 0);
jg(".lp");
ret();
}
int (*get() const)(int) { return getCode<int(*)(int)>(); }
};

int main() {
cout << FactorialFunc().get()(10) << endl;
// => 3628800
}

ジャンプ命令は条件によっていろいろあって、ある値以上である場合はjgとなる(アセンブリ言語とx86機械語の対応表みたいなのってどこにありますか? 確かIntelの… - Yahoo!知恵袋より、intelの資料のIA-32 インテル® アーキテクチャー・ソフトウェア・デベロッパーズ・マニュアル、中巻 A: 命令セット・リファレンス A-MおよびN-Z)。

C言語で書けば

int factorial(int edi) {
int eax = 1;
do {
eax *= edi;
--edi;
} while (edi > 0);
return eax;
}

次記事