C言語を用いたマシン非依存な

C言語を用いたマシン非依存な
JITコンパイラ作成フレームワーク
栗田洋輔 千葉滋
東京工業大学
1
従来のJITコンパイラの開発と問題点



本格的なJITコンパイラ
 プラットフォーム依存の中間表現を作成し、それを機械語に変換
移植性の問題
 マシンごとにプラットフォーム依存の中間表現を作成
実装の容易性の問題
 高い開発スキルが必要
 バイトコードから中間表現への変換
 中間表現から機械語への変換
バイトコード
機械語
プラットフォーム依存の中間表現
2
本研究の目的

JIT コンパイラ作成を容易にするフレームワーク


移植性が高い
短いコンパイル時間でそこそこの品質のコードを生成
実装の容易性
移植性
実行速度
コンパイル時間
ネイティブコンパイラ
△
×
◎
×
インタープリタ
◎
◎
×
◎
従来のJITコンパイラ
×
×
○
△
本フレームワークを用いて ○
開発するJITコンパイラ
○
△
○
3
そこそこの最適化

本質的なオーバーヘッドを除去することで高速
化を行う


広い範囲のインタープリターに適用できる
汎用的な手法
本質的なオーバーヘッド


バイトコードインタープリター
 次の命令をフェッチしてデコードするコスト
 バイトコードのオペランドをメモリからロードするコスト
抽象構文木のインタープリター
 抽象構文木を巡回してノードをデコードするコスト
4
命令をフェッチ・デコードする
オーバーヘッド (1/2)

命令ポインタ(ip)のインクリメントと
メモリからのフェッチ
インタープリター
バイトコード
ip
ip
ip
ip
ip
PUSH_CONST
2
PUSH_CONST
3
ADD
char *ip = code – 1;
for (;;) {
char* bcode = *++ip;
switch (bcode)
case PUSH_CONST:
*++sp = (int)*++ip;
break;
case ADD:
--sp;
*sp += sp[1];
break;
/* other cases …*/
}
}
トレース
フェッチ bcode = *++ip;
デコード switch (bcode)
本体
*++sp = (int)*++ip;
フェッチ bcode = *++ip;
デコード switch (bcode)
本体
*++sp = (int)*++ip;
フェッチ bcode = *++ip;
デコード switch (bcode)
本体
--sp;
5
*sp += sp[1];
命令をフェッチ・デコードする
オーバーヘッド (2/2)

バイトコードのデコード

switchによる条件分岐で実装
インタープリター
バイトコード
PUSH_CONST
2
PUSH_CONST
3
ADD
char *ip = code – 1;
for (;;) {
char* bcode = *++ip;
switch (bcode)
case PUSH_CONST:
*++sp = (int)*++ip;
break;
case ADD:
--sp;
*sp += sp[1];
break;
/* other cases …*/
}
}
トレース
フェッチ bcode = *++ip;
デコード switch (bcode)
本体
*++sp = (int)*++ip;
フェッチ bcode = *++ip;
デコード switch (bcode)
本体
*++sp = (int)*++ip;
フェッチ bcode = *++ip;
デコード switch (bcode)
本体
--sp;
6
*sp += sp[1];
命令をフェッチ・デコードする
オーバーヘッドの除去

高速化を達成


フェッチ・デコード部分を削除
本体部分のみを並べる
命令ポインタの指す
アドレスが不正
フェッチ bcode = *++ip;
*++ip;
デコード switch (bcode)
本体
*++sp = (int)*++ip;
*++ip;
フェッチ bcode = *++ip;
デコード switch (bcode)
本体
*++sp = (int)*++ip;
*++ip;
フェッチ bcode = *++ip;
*++sp = (int)*++ip;
(int)*++ip;
*++sp = (int)*++ip;
(int)*++ip;
--sp;
*sp += sp[1];
デコード switch (bcode)
本体
--sp;
*sp += sp[1];
7
バイトコードのオペランドをメモリから
ロードするコストの削減

バイトコードのオペランドをメモリからロード

オペランドの値が既知の場合には不必要
フェッチ bcode = *++ip;
デコード switch (bcode)
本体
*++sp = (int)*++ip;
フェッチ bcode = *++ip;
デコード switch (bcode)
本体
*++sp = (int)*++ip;
フェッチ bcode = *++ip;
*++sp = (int)*++ip;
(int)*++ip;
*++sp = 2;
*++sp = (int)*++ip;
(int)*++ip;
*++sp = 3;
--sp;
*sp += sp[1];
--sp;
*sp += sp[1];
デコード switch (bcode)
本体
--sp;
*sp += sp[1];
8
JIT コンパイラ・フレームワーク
の提案

プラットフォーム独立な中間表現


中間表現から機械語への変換はフレームワークが担当
 高い移植性を実現(フレームワーク自体の移植は必要)
テンプレート


中間表現の各ノードに対応する機械語のひな形
中間表現のセマンティクスを与える
JITコンパイラの
作成者が実装
テンプレート
バイトコード
機械語
9
プラットフォーム独立な中間表現
テンプレートの書き方

インタープリターの主ループそのもの
テンプレート
char *ip = code – 1;
 記述は容易
for (;;) {
char* bcode = *++ip;
 一部の命令は、テンプレート用に書き
switch (bcode)
換えが必要
case ADD:
--sp;
 命令ポインタ (IP) を使っている場合
*sp += sp[1];
 生成される機械語に IP は含められない
break;
 IP でオペランド(即値)を取得する
case PUSH_CONST:
*++sp = (int)*++ip;
 HOLEマクロを利用
break;
 gccインラインアセンブリに展開
case TMPL_PUSH_CONST:
 機械語生成時に即値を挿入
HOLE(hole1, int_val);
*++sp = int_val;
 IP で分岐先を決定する
break;
 分岐命令にはテンプレート不要
}
}
10
プラットフォーム独立な中間表現

各ノード


バイトコード命令(あるいは式)に対応
 中間表現への変換は容易
属性
 テンプレートのどの部分を使って機械語を生成するか
 HOLE に挿入する即値の値
中間表現
抽象構文木
second
バイトコード
PLUS
third
NUMBER: 2
NUMBER: 3
PUSH 2
PUSH 3
中間表現
label: PLUS
label: NUMBER
holes: {hole1:=2}
label: NUMBER
holes: {hole1:=3}
label: TMPL_PUSH
holes: {hole1:=2}
label: TMPL_PUSH
holes: {hole1:=3}
ADD
label: TMPL_ADD
holes: NULL
11
中間表現への変換は容易

インタープリタの制御構造を流
用可能

appendCmpnt


BlockCmpntをリストの末尾に追加
addHoleInfo

Holeに埋め込む値を設定
中間表現
label: TMPL_PUSH_CONST
holes: {push_const_hole := 2}
label: TMPL_PUSH_CONST
holes: {push_const_hole := 3}
label: ADD
holes: NULL
中間表現
を作るコード
char *ip = code – 1;
for (;;) {
char* bcode = *++ip;
switch (bcode)
case ADD:
cmpnt =
appendCmpnt(cmpnt, ip
v_ADD);
break;
case PUSH_CONST:
cmpnt =
appendCmpnt(cmpnt, ip,
v_TMPL_PUSH_CONST);
addHoleInfo(cmpnt, ip,
v_push_const_hole,
*((int *)(++ip)) );
break;
}
12
}
フレームワークによるコード生成

入力:中間表現、出力:機械語


コンパイル済みテンプレートの逆アセンブル情報を利用する
機械語生成の流れ
1.中間表現で指定されたコンパイル済みテンプレートを、最後のjmp命令
やret命令を除きコピー
2. 相対アドレスを用いた部分をコードが移動した分だけずらす
3. 中間表現で指定された即値命令に指定された即値を埋め込む
4. バイトコードの分岐命令は機械語の分岐命令に変換
中間表現
label: TMPL_PUSH
holes: {hole1 := 2}
label: TMPL_PUSH
holes: {hole1 := 3}
コンパイル済みテンプレート
参照
TMPL_PUSH
hole1:
movl $1234, %eax
…
TMPL_ADD
label: TMPL_ADD
holes: NULL
…
機械語
コピー
movl $2,
$1234,
%eax
%eax
…
movl $3,
$1234,
%eax
%eax
…
…
13
バイトコードの分岐命令の処理


分岐先バイトコードのアドレスが入った中間表現を作る
機械語の分岐命令に変換

バイトコードのアドレスを機械語のアドレスへフレームワークが変換
バイトコード
unsigned char code[] = {
…, GOTO, -0x100, …};
中間表現
label: …
バイトコード番地: 0x0200
label: …
バイトコード番地: 0x0204
機械語
0x10000000
…
0x10000020
…
中略
label: NULL
バイトコード番地: 0x0300
type: jump
ジャンプオフセット: -0x0100
中略
0x10001000
jmp 0x10000000
14
中間表現での最適化


中間表現のノードの並べ替え
複数ノードを最適化された1ノードへ置換
 例 定数の畳み込み
中間表現 (最適化前)
label: TMPL_PUSH_CONST
holes: {push_const_hole => 2}
中間表現 (最適化後)
label: TMPL_PUSH_CONST
holes: {push_const_hole => 3}
label: TMPL_PUSH_CONST
holes: {push_const_hole => 5}
label: ADD
holes: NULL
15
抽象構文木インタープリターの場合

抽象構文木を直接解釈するインタープリター

switch文がオーバーヘッド


バイトコードインタープリターと同様
eval 関数が再帰呼び出し
インタープリター
int eval(List* expr) {
switch (testElementType(expr)) {
/* ... */
case PLUS :
return eval(getSecond(expr))
+ eval(getThird(expr));
case NUMBER :
return getIntegerElement(expr);
/* ... */
}}
抽象構文木
PLUS
second
third
NUMBER: 2 NUMBER: 3
16
入れ子の関数呼び出しの中間表現

中間表現を木構造に

入れ子で呼ばれる関数を表すノードを
木構造の子ノードに
抽象構文木
PLUS
second
third
NUMBER: 2
中間表現
NUMBER: 3
label: PLUS
label: NUMBER
holes: {hole1 := 2}
label: NUMBER
holes: {hole1 := 3}
17
入れ子の関数呼び出しのテンプレート

引数なしのvoid関数がテンプレート


値の受け渡しはグローバル変数で
LABEL マクロ

テンプレートの内部に他のテンプレートを挿入可能
テンプレート
void code_PLUS() {
LABEL(PLUS_section);
LABEL(PLUS_label1);
sp--;
*sp = sp[1];
}
void code_NUMBER() {
int ret_val;
HOLE(hole1, ret_val);
*++sp = ret_val;
}
中間表現
PLUS
PLUS_label1
NUMBER 2
PLUS_label1
NUMBER 3
機械語
void code_PLUS() {
*++sp = 2;
*++sp = 3;
sp--;
*sp = sp[1];
}
18
フレームワークのマシン非依存性

JITコンパイラの作成者



マシンアーキテクチャを意識しない
中間表現はプラットフォーム独立
フレームワーク自体


マシンアーキテクチャ固有の実装
 コンパイル済みテンプレートの機械語の解析
 インラインアセンブリに展開されるマクロ
 即値命令に定数を埋め込む方法
 機械語の分岐命令
 相対アドレスの調整
gccが必要
 gccインラインアセンブリを使用
19
予備的な実験

ディスパッチにswitchを用いた抽象構文木
インタープリター
(+ x y)の実行結果
120
160
140
100
実行時間(JITなし) [ns]
120
80
100
60
80
60
40
40
20
20
0
0
O0
O1
O2
gcc最適化オプション
O3
実行時間(JITあり) [ns]
JITコンパイル時間 [μs]
Intel Xeon CPU 3.06GHz x 2
memory 2GB
linux 2.6.17
gcc 4.1.1
glibc 2.4
20
関連研究

Cコンパイラが生成した機械語を利用する研究

ErtlらのJITコンパイラの移植に関する研究 [Ertl 04]




命令ポインタを除去して高速化するところが本研究に類似
インタープリタを書き換えないため、バイトコードの即値命令を機
械語の即値命令に書き換えられない場合がある
本研究はフレームワークおよびそれが解釈する中間表現を提供
している
DyC [Grant 00]

伝統的なMultiflow Compilerをベースにしている


移植性に焦点が当てられていない
Tempo [Noel 98]


ランタイムコンパイラをフロントエンドからバックエンドまで提供
ネイティブコードの生成が自動化されているため、バイトコード命
令レベルの最適化を行えない
21
まとめ

JITコンパイラを作成するフレームワーク


高い移植性
 プラットフォーム独立なコードを書くだけ
そこそこの品質のコードを生成
 オーバーヘッドを除去




命令のフェッチ・デコード
オペランドのロード
実装が容易
 インタープリタの実装を流用可能
テンプレートが中間表現にセマンティクスを与える

置き換えで独自の最適化が可能
22
終わり

ご清聴ありがとうございました
23