Нежное введение в JIT и динамическую компиляцию / генерацию кода - PullRequest
3 голосов
/ 28 октября 2008

Обманчиво простая основа генерации динамического кода в рамках C / C ++ уже была рассмотрена в еще один вопрос . Есть ли какие-нибудь нежные введения в тему с примерами кода?

Мои глаза начинают кровоточить, глядя на очень сложные JIT-компиляторы с открытым исходным кодом, когда мои потребности намного скромнее.

Есть ли хорошие тексты по этому предмету, которые не предполагают докторскую степень в области компьютерных наук? Я ищу хорошо изношенные шаблоны, на которые следует обратить внимание, соображения производительности и т. Д. Электронные или древовидные ресурсы могут быть одинаково ценными. Вы можете иметь рабочие знания (не только x86) ассемблера.

Ответы [ 3 ]

4 голосов
/ 28 октября 2008

Хорошо, шаблон, который я использовал в эмуляторах, выглядит примерно так:

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.

2 голосов
/ 28 октября 2008

Я не знаю ни одного источника, специально связанного с JIT, но я представляю, что он в значительной степени похож на обычный компилятор, только проще, если вы не беспокоитесь о производительности.

Самый простой способ - начать с интерпретатора VM. Затем для каждой инструкции VM сгенерируйте код сборки, который выполнил бы интерпретатор.

Чтобы пойти дальше этого, я представляю, что вы бы проанализировали байтовые коды VM и преобразовали их в какую-то подходящую промежуточную форму (трехадресный код? SSA?), А затем оптимизировали и сгенерировали код, как в любом другом компиляторе.

Для виртуальной машины на основе стека может помочь отслеживать «текущую» глубину стека при переводе байтовых кодов в промежуточную форму и обрабатывать каждое местоположение стека как переменную. Например, если вы думаете, что текущая глубина стека равна 4, и вы видите команду «push», вы можете сгенерировать присваивание «stack_variable_5» и увеличить счетчик стека времени компиляции или что-то в этом роде. «Add», когда глубина стека равна 5, может генерировать код «stack_variable_4 = stack_variable_4 + stack_variable_5» и уменьшать счетчик стека времени компиляции.

Также возможно преобразовать основанный на стеке код в синтаксические деревья. Поддерживать стек во время компиляции. Каждая команда «push» приводит к тому, что представление помещаемой вещи сохраняется в стеке. Операторы создают узлы синтаксического дерева, которые включают их операнды. Например, «XY +» может привести к тому, что в стеке будет содержаться «var (X)», затем «var (X) var (Y)», а затем «плюс» отключит обе ссылки на var и вытолкнет «plus (var (X), вар (Y))».

0 голосов
/ 28 октября 2008

Получите копию книги Джоэла Побара о Роторе (когда она выйдет) и изучите источник до SSCLI . Осторожно, безумие лежит внутри:)

...