Хорошо, шаблон, который я использовал в эмуляторах, выглядит примерно так:
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();
}
}
Это упрощение, но идея есть. По сути, каждый раз, когда движку предлагается выполнить «базовый блок» (обычно это все до следующей операции управления потоком или возможной целой функции), он проверяет, был ли он уже создан. Если это так, выполните его, а затем создайте, добавьте и выполните.
повтор полоскания:)
Что касается генерации кода, это немного усложняется, но идея состоит в том, чтобы создать правильную "функцию", которая выполняет работу вашего основного блока в контексте вашей виртуальной машины.
РЕДАКТИРОВАТЬ: обратите внимание, что я также не продемонстрировал никаких оптимизаций, но вы попросили "мягкое введение"
РЕДАКТИРОВАТЬ 2: Я забыл упомянуть одно из самых быстрых ускорений, которые вы можете реализовать с помощью этого шаблона. По сути, если вы никогда не удаляете блок из своего дерева (вы можете обходить его, если это так, но это гораздо проще, если вы никогда этого не делаете), тогда вы можете "связать" блоки вместе, чтобы избежать поиска. Вот концепция. Всякий раз, когда вы возвращаетесь из f () и собираетесь выполнить «update_instruction_pointer», если только что выполненный вами блок завершился либо вызовом, либо безусловным переходом, либо не завершил управление потоком вообще, тогда вы можете «исправить» его Команда ret с прямым jmp к следующему блоку, который он выполнит (потому что он всегда будет одним и тем же) , если вы уже испустили его. Это позволяет вам выполнять все чаще и чаще на виртуальной машине, а все реже - выполнять функцию execute_block.