Компиляция "на лету"
Итак, вспомним, в каком виде мы оставили нашу виртуальную машину, – структура ее выглядела примерно так:
Для того чтобы адаптировать этот код для JIT-компиляции, в него надо внести некоторые изменения. Во-первых, массив code будет теперь массивом указателей на функции, а массивом исполняемых команд.
Будут ли эти команды иметь тип char, int или long – зависит только от того процессора, под который мы компилируем; предположим, что это будет int. После того как код будет сгенерирован, мы вызываем его как функцию. Никакого виртуального счетчика команд программы в новом коде не будет, поскольку обход кода за нас теперь будет выполнять собственно исполнительный цикл процессора; по окончании вычисления результат будет возвращаться – совсем как в обычной функции. Далее, мы можем выбрать – поддерживать ли нам отдельный стек операндов для нашей машины или воспользоваться стеком самого процессора. У каждого из этих вариантов есть свои преимущества; мы решили остаться верными отдельному стеку и сконцентрироваться на деталях самого кода. Теперь реализация выглядит таким образом:
После того как generate завершит работу, gen return вставит команды, которые обусловят передачу управления от сгенерированного кода к eval.
Функция flushcaches отвечает за шаги, необходимые для подготовки процессора к запуску свежесозданного кода. Современные машины работают быстро, в частности благодаря наличию кэшей для команд и данных, а также конвейеров (pipeline), которые отвечают за выполнение сразу нескольких подряд идущих команд. Эти кэши и конвейеры исходят из предположения, что код не изменяется; если же мы генерируем этот код непосредственно перед запуском, то процессор может оказаться в затруднении: ему нужно обязательно очистить свой конвейер и кэши для исполнения новых команд. Эти операции очень сильно зависят от конкретного компьютера, и, соответственно, реализация flushcaches будет в каждом случае совершенно уникальной.
Замечательное выражение (void (*)(void)) code преобразует адрес массива, содержащего сгенерированные команды, в указатель на функцию, который можно было бы использовать для вызова нашего кода.