3

C / C ++フレームワーク内での動的コード生成の一見単純な基盤は、別の質問ですでに取り上げられています。コード例を使用したトピックの穏やかな紹介はありますか?

私のニーズがはるかに控えめなとき、私の目は非常に複雑なオープンソースのJITコンパイラを見つめ始めています。

コンピュータサイエンスの博士号を取得していない主題に関する優れたテキストはありますか?使い古されたパターン、注意すべき点、パフォーマンスの考慮事項などを探しています。電子リソースまたはツリーベースのリソースも同様に価値があります。(x86だけでなく)アセンブリ言語の実用的な知識があると想定できます。

4

3 に答える 3

4

私がエミュレーターで使用したパターンは次のようになります。

typedef void (*code_ptr)();
unsigned long instruction_pointer = entry_point;
std::map<unsigned long, code_ptr> code_map;


void execute_block() {
    code_ptr f;
    std::map<unsigned long, void *>::iterator it = code_map.find(instruction_pointer);
    if(it != code_map.end()) {
        f = it->second
    } else {
        f = generate_code_block();
        code_map[instruction_pointer] = f;
    }
    f();
    instruction_pointer = update_instruction_pointer();
}

void execute() {
    while(true) {
        execute_block();
    }
}

これは単純化されていますが、アイデアはそこにあります。基本的に、エンジンは「基本ブロック」(通常は次のフロー制御 op までのすべて、または可能であれば関数全体) の実行を要求されるたびに、それが既に作成されているかどうかを確認します。ある場合は実行し、そうでない場合は作成して追加し、実行します。

すすぎを繰り返します:)

コード生成に関しては、少し複雑になりますが、VM のコンテキストで基本ブロックの作業を行う適切な「関数」を発行するという考え方です。

EDIT:私は最適化を示していないことに注意してください。しかし、あなたは「穏やかな紹介」を求めました

編集 2: このパターンで実装できる最もすぐに生産的なスピードアップの 1 つについて言及するのを忘れていました。基本的に、ツリーからブロックを削除しない場合(削除する場合は回避できますが、削除しない場合ははるかに簡単です)、ブロックを「チェーン」してルックアップを回避できます。これがコンセプトです。f() から戻り、「update_instruction_pointer」を実行しようとしているときはいつでも、実行したばかりのブロックが呼び出し、無条件ジャンプで終了した場合、またはフロー制御でまったく終了しなかった場合は、そのブロックを「修正」できます。すでに発行している場合は、実行する次のブロックへの直接の jmp を含む ret 命令 (常に同じブロックになるため) 。

于 2008-10-28T01:47:06.100 に答える
2

特に JIT に関連する情報源は知りませんが、通常のコンパイラとほとんど同じで、パフォーマンスを気にしなければもっと簡単だと思います。

最も簡単な方法は、VM インタープリターから始めることです。次に、VM 命令ごとに、インタープリターが実行するアセンブリ コードを生成します。

それを超えるには、VM バイト コードを解析し、それらを何らかの適切な中間形式 (3 つのアドレス コード? SSA?) に変換してから、他のコンパイラと同様に最適化してコードを生成することを想像します。

スタック ベースの VM の場合、バイト コードを中間形式に変換する際に「現在の」スタックの深さを追跡し、各スタックの場所を変数として扱うと役立つ場合があります。たとえば、現在のスタックの深さが 4 で、"push" 命令が表示されていると思われる場合、"stack_variable_5" への割り当てを生成し、コンパイル時のスタック カウンターをインクリメントするか、またはそのようなものにすることができます。スタックの深さが 5 の場合の「追加」は、コード「stack_variable_4 = stack_variable_4+stack_variable_5」を生成し、コンパイル時のスタック カウンターをデクリメントする可能性があります。

スタック ベースのコードを構文ツリーに変換することもできます。コンパイル時のスタックを維持します。すべての「プッシュ」命令により、プッシュされるものの表現がスタックに格納されます。演算子は、オペランドを含む構文ツリー ノードを作成します。たとえば、"XY +" は、スタックに "var(X)"、次に "var(X) var(Y)" を格納し、プラスは両方の var 参照をポップして "plus(var(X),変化))"。

于 2008-10-28T01:27:30.100 に答える
0

Rotor に関する Joel Pobar の本のコピーを入手し (出版されたら)、ソースからSSCLIまで掘り下げてください。注意してください、狂気は内にあります:)

于 2008-10-28T03:44:33.773 に答える