Явное кодирование отдельных примитивов
Наш компилятор тщательно избегает ненужных операций со стеком, но с точки зрения перевода вызовов элементарных процедур языка в операции машины он очень слаб. Рассмотрим, например, сколько кода порождается для вычисления
(+ 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
. Сравните полученный результат с результатом, который получается без открытого кодирования.
г. Расширьте свои генераторы кода для
+
и
*
так, чтобы они могли обрабатывать выражения с произвольным числом операндов. Выражение, в котором операндов больше двух, придется компилировать в последовательность операций, каждая из которых работает с двумя входами.
Комментарии отсутствуют.