ポータブルで使いやすい JITコンパイラ・バックエンド 数理・計算科学専攻 千葉研究室 栗田 洋輔 指導教員: 千葉 滋 JITコンパイラの開発の難しさ 移植性の高いポータブルな JIT コンパイラ・フレーム ワークがほしい 現状の技術 新しいJITコンパイラを開発 実装が難しい 他言語のJIT コンパイラつきVMを利用 Java VM、CLI など 対象言語のソース・プログラムを 他言語(or 独自中間言語)のVMのバイトコードへ変換して実行 2つの言語の仕様のミスマッチが実装を面倒にする ポータブルな JITコンパイラ・バックエンドの提案 既に存在して いることを仮定 インタプリタ 開発者が作成 バイトコード or AST JITコンパイラ 開発者が作成 フロントエンド コード生成 テンプレート gccでコンパイル 中間表現 本システム バックエンド 機械語 機械語 コンパイル済み テンプレート 本システムの特徴 移植性が高い JITコンパイラ作成者はマシン依存のコードを書かない C言語とマシン非依存のマクロを使用 開発が容易 フロントエンド ソース言語に合わせて中間表現を定義可能 コード生成テンプレート 元のインタープリタのコードを流用できる 最適化の手法 命令ポインタ(IP)に関わるオーバーヘッドを除去 バイトコードのフェッチ バイトコードのデコード オペランドのロード フェッチ bcode = *++ip; デコード switch (bcode) 本体 バイトコード ip PUSH_CONST 2 PUSH_CONST 3 ADD *++sp = (int)*++ip; オペランドのロード フェッチ bcode = *++ip; デコード switch (bcode) 本体 *++sp = (int)*++ip; フェッチ bcode = *++ip; デコード switch (bcode) 本体 --sp; *sp += sp[1]; *++sp = (int)*++ip; *++sp = 2; *++sp = (int)*++ip; *++sp = 3; --sp; *sp += sp[1]; --sp; *sp += sp[1]; 開発者が自由に定義できる中間表現 中間表現の各ノード 各々のバイトコード命令に対応 コード生成テンプレートで開発者が定義 テンプレート TMPL_PUSH 中間表現 バイトコード PUSH 2 PUSH 3 label: TMPL_PUSH holes: {hole1:=2} 機械語 2をPUSHする label: TMPL_PUSH holes: {hole1:=3} ADD label: TMPL_ADD holes: NULL 3をPUSHする スタックのトップにある 2つの値を加算 テンプレート TMPL_ADD テンプレートの定義 char *ip = code – 1; for (;;) { char* bcode = *++ip; switch (bcode) case ADD: LABEL(ADD_b); --sp; *sp += sp[1]; LABEL(ADD_e); break; case PUSH_CONST: *++sp = (int)*++ip; break; case TMPL_PUSH_CONST: LABEL(TMPL_PUSH_CONST_b) LABEL(TMPL_PUSH_CONST_b); HOLE(hole1, int_val); *++sp = int_val; LABEL(TMPL_PUSH_CONST_e) break; } } インタープリター のソースコード を流用できる 機械語 --sp; *sp += sp[1]; 命令ポインタを含む 実装はテンプレート として利用しない 機械語 HOLE(hole1, int_val); *++sp = int_val; 定数値のコード中への埋め込み HOLEマクロ ラベルと即値命令に変換 即値命令の即値部分には中間表現で指定された定 数値が埋め込まれる HOLEマクロ HOLE(hole1, int_val); 中間表現 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 … … 分岐命令の変換 全ての中間表現はバイトコードの番地を属性に持つ 分岐命令用中間表現 システムが機械語の分岐命令に変換する テンプレートを用いない特殊な中間表現 中間表現 機械語 label: … バイトコード番地: 0x0200 label: … バイトコード番地: 0x0204 0x10000000 … 0x10000020 … 中略 中略 label: NULL バイトコード番地: 0x0300 type: jump ジャンプオフセット: -0x0100 0x10001000 分岐命令用 中間表現 jmp 0x10000000 フロントエンドによる最適化 融合命令の利用 融合命令を開発者がテンプレートで定義しておく 定数の畳み込み 中間表現 (最適化前) label: SUB 中間表現 (最適化後) 融合命令 に変換 label: TMPL_SUB_MUL label: MUL label: TMPL_PUSH_CONST holes: {hole1 => 2} 定数の畳み込み label: TMPL_PUSH_CONST holes: {hole1 => 3} label: ADD label: TMPL_PUSH_CONST holes: {hole1 => 5} 抽象構文木インタープリタの場合 中間表現を木構造にする 抽象構文木では式が入れ子になる 各式の実行中にオペランド式の実行を行う 抽象構文木 PLUS second third NUMBER: 2 中間表現 PLUS_label1 NUMBER 2 NUMBER: 3 PLUS PLUS_label1 NUMBER 3 PLUSの実行中に NUMBERの実行を行う 抽象構文木の巡回順 PLUS NUMBER 2 PLUS NUMBER 3 PLUS 抽象構文木インタープリタのテンプレート テンプレートはvoid関数内に記述 変数はグローバル変数を利用 テンプレート 中間表現 PLUS void code_PLUS() { LABEL(PLUS_section); LABEL(PLUS_label1); sp--; *sp = sp[1]; } PLUS_label1 void code_NUMBER() { int ret_val; HOLE(hole1, ret_val); *++sp = ret_val; } void code_PLUS() { *++sp = 2; *++sp = 3; sp--; *sp = sp[1]; } NUMBER 2 機械語 PLUS_label1 NUMBER 3 予備的な実験 ディスパッチにswitchを用いた抽象構文木インタープリタ (defun factorial (x) (if x (* x (factorial (- x 1))) 1)) (factorial 16) の実行結果 30 54 25 実行時間 [μs] 実行時間(JITなし) 53 52 20 51 50 15 49 10 48 47 5 46 0 45 O0 O1 O2 gcc最適化オプション O3 JITコンパイル時間 [μs] 実行時間(JITあり) JITコンパイル時間 Intel Xeon CPU 3.06GHz x 2 memory 2GB linux 2.6.17 gcc 4.1.1 glibc 2.4 Gforthへの適用 バイトコードインタープリタ コア部分はDirect threaded code ルーチン(Word)はDictionary に保存される JITコンパイルの開発 Dictionaryに最適化コードを 保存する Direct threaded codeの延 長にテンプレートを記述 void *code[] = { ..., &&opcode_push3, &&opcode_push4, &&opcode_add, ... }; /* 次の命令をディスパッチ */ #define NEXT() goto **++instructionPointer; void **instructionPointer = code - 1; /* 最初のオペコードをディスパッチ */ NEXT(); /* オペコードの実装 */ opcode_push3: *++stackPointer = 3; NEXT(); opcode_push4: *++stackPointer = 4; NEXT(); opcode_add: --stackPointer; *stackPointer += stackPointer[1]; NEXT(); /* ... */ Gforth用JITコンパイラ開発のコスト プリミティブ命令の数431 インタープリタのプリミティブ命令の行数17019 テンプレートのためにプリミティブ命令の実装からコピーした 行数4201 テンプレートのために新たに書き加えた行数629 フロントエンドのためにインタープリタからコピーした行数 2355 フロントエンドのために新たに書き加えた行数1348 インタープリタ本体に新たに書き加えた行数306 JITコンパイラ開発のために新たに書き加えた総行数 629+1348+306=2283 Gforthでの実験 Gforthベンチマークの実行結果 900 25 実行時間(JITなし) 20 700 600 15 500 400 10 300 200 5 100 0 0 sieve bubble matrix Gforthベンチマーク fib JITコンパイル時間 [ms] 実行時間 [ms] 800 実行時間(JITあり) JITコンパイル時間 Intel Xeon CPU 3.06GHz x 2 memory 2GB linux 2.6.17 gcc 4.1.1 glibc 2.4 関連研究 Cコンパイラが生成した機械語を利用する研究 ErtlらのJITコンパイラの移植に関する研究 [Ertl 04] 命令ポインタを除去して高速化するところが本研究に類似 インタープリタを書き換えないため、バイトコードの即値命令を機械語の 即値命令に書き換えられない場合がある 本研究はフレームワークおよびそれが解釈する中間表現を提供してい る DyC [Grant 00] C言語用のRuntime Specializer Tempo [Noel 98] 独自にコンパイラを拡張しているので移植性が低い ランタイムコンパイラをフロントエンドからバックエンドまで提供 ネイティブコードの生成が半自動化されているため、フロントエンドでの 最適化を柔軟に行えない VMの仕様記述からバイトコードインタプリタを自動生成する研究 Virtual Machine Builder [内山 03] 実装の命令合成よりも強力な仕様記述上の命令合成が可能 静的コンパイルにしか対応していない まとめ ポータブルなJITコンパイラバックエンドの提案 移植性が高いアーキテクチャ フロントエンドの開発者はマシン依存のコードを書かない 開発が容易 ソース言語に合わせて中間表現を定義可能 元のインタープリタのコードを流用できる 実装・実験 抽象構文木インタープリタ(簡易Lisp)用JITコンパイラを作成 バイトコードインタープリタ(Gforth)用JITコンパイラを作成 それぞれにおいて最適化の効果を確認 JITコンパイラの開発にかかるコストが小さいことを確認
© Copyright 2024 ExpyDoc