Явное кодирование отдельных примитивов

Наш компилятор тщательно избегает ненужных операций со стеком, но с точки зрения перевода вызовов элементарных процедур языка в операции машины он очень слаб. Рассмотрим, например, сколько кода порождается для вычисления (+ a 1) : код порождает список аргументов в argl , помещает элементарную процедуру сложения (которую он находит через поиск символа + в окружении) в proc , затем проверяет, является ли эта процедура элементарной или составной. Компилятор всегда порождает код этой проверки, а также код для ветви элементарной процедуры и ветви составной процедуры (из которых только одна будет выполняться). Мы не показали ту часть контроллера, которая реализует примитивы, но мы предполагаем, что эти команды используют элементарные арифметические операции в путях данных машины. Рассмотрим, насколько меньше кода будет порождаться, если компилятор сможет вставлять примитивы в виде явного кода (open coding) — то есть порождать код, который прямо использует эти машинные операции. Выражение (+ a 1) можно было бы скомпилировать в простую последовательность вроде

(assign val (op lookup-variable-value) (const a) (reg env))
(assign val (op +) (reg val) (const 1))

В этом упражнении мы расширим компилятор так, чтобы он поддерживал явное кодирование отдельных примитивов. При обращениях к этим примитивам будет порождаться специально написанный код, а не общий код для вызова процедуры. Для того, чтобы поддержать такую работу, мы дополним машину специальными регистрами для аргументов arg1 и arg2 . Элементарные арифметические операции машины будут принимать свои аргументы в arg1 и arg2 . Они могут помещать результаты в val , arg1 или arg2 .

Компилятор должен уметь распознавать вызов явно кодируемого примитива в исходной программе. Мы дополним распознаватель в процедуре compile , так, чтобы он узнавал имена этих примитивов в дополнение к зарезервированным словам (особым формам), которые он узнает сейчас. Для каждой особой формы в компиляторе есть свой генератор кода. В этом упражнении мы построим семью генераторов кода для явно кодируемых примитивов.

а. В отличие от особых форм, явно кодируемые примитивы требуют, чтобы их аргументы вычислялись. Напишите генератор кода spread-arguments , который будут использовать генераторы явного кода. Spread-arguments должен принимать список операндов и компилировать данные ему операнды, направляя их в последовательные аргументные регистры. Заметим, что операнд может содержать вызов явно кодируемого примитива, так что во время вычисления операндов придется сохранять аргументные регистры.

б. Для каждой из элементарных процедур = , * , - и + напишите по генератору кода, который принимает комбинацию, содержащую этот оператор вместе с целевым регистром и описателем связи, и порождает код, который раскидывает аргументы по регистрам, а затем проводит операцию с данным целевым регистром и указанным типом связи. Достаточно обрабатывать только выражения с двумя операндами. Заставьте compile передавать управление этим генераторам кода.

в. Опробуйте обновленный компилятор на примере с процедурой factorial . Сравните полученный результат с результатом, который получается без открытого кодирования.

г. Расширьте свои генераторы кода для + и * так, чтобы они могли обрабатывать выражения с произвольным числом операндов. Выражение, в котором операндов больше двух, придется компилировать в последовательность операций, каждая из которых работает с двумя входами.


Комментарии отсутствуют.

Необходима авторизация

Вы должны авторизоваться для создания комментария.

Вход