私がエミュレーターで使用したパターンは次のようになります。
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 命令 (常に同じブロックになるため) 。