5.5.7 · Связь скомпилированного кода и вычислителя
Единая граница apply для двух представлений
Вычислитель вызывает скомпилированную процедуру, а скомпилированный код интерпретируемую, если у них общая модель окружения и соглашение об аргументах.
Вопрос для размышления
Какие соглашения времени выполнения позволяют интерпретируемым и скомпилированным процедурам вызывать друг друга через границу представлений, не считая себя одним и тем же объектом?
Сохранение интерпретируемых и скомпилированных процедур в виде раздельных тегированных представлений
Совместное использование единого лексического окружения и соглашения о порядке списка аргументов
Направление примитивных, интерпретируемых и скомпилированных процедур через единую границу apply
Вызов скомпилированного кода из вычислителя
Вызов интерпретируемой процедуры из скомпилированного кода
Фиксация фактического порядка диспетчеризации представлений для обоих направлений
Описание общей границы apply через контракты окружения, аргументов и диспетчеризации
Общее глобальное окружение хранит обернутые примитивы, одну интерпретируемую процедуру с именем double и одну скомпилированную процедуру с именем add-three. Процедура evaluate создает интерпретируемые замыкания, чьи тела остаются данными выражений. Процедура compile-expression создает код для стековой машины и инструкции замыканий, чьи тела являются списками инструкций. Два представления остаются заметно различными, но оба содержат параметры и лексическое окружение, а также принимают одинаковый упорядоченный список аргументов.
Интерфейсом служит процедура apply-any. Применение в вычислителе достигает этой границы после вычисления оператора и операндов. Скомпилированная инструкция call достигает той же границы после извлечения процедуры и аргументов из стека VM. Запуск от вычислителя к скомпилированному коду записывает (compiled primitive), так как add-three входит в скомпилированный код, чье сложение вызывает примитив. Запуск от скомпилированного кода к вычислителю записывает (interpreted primitive), так как скомпилированный код вызывает double, чье интерпретируемое тело умножает через примитив. Модель урока фиксирует точный контракт взаимных вызовов для обоих направлений.
Программа возвращает ((evaluator-to-compiled 8 (compiled primitive)) (compiled-to-evaluator 14 (interpreted primitive) #t) (representations #t #t)). Списки диспетчеризации фиксируют фактические представления процедур, в которые входит каждый выбранный запуск.
На что обратить внимание в трассе
Сначала проследите поиск add-three в вычислителе с переходом в ветку apply-any для скомпилированного кода, а затем поиск замыкания в VM и вызов примитива. Затем проследите скомпилированную инструкцию call для double с переходом в интерпретируемую ветку, расширение окружения, вычисление тела выражения и умножение через примитив. Убедитесь, что оба пути используют общее глобальное окружение и соглашение об упорядоченных аргументах, сохраняя при этом раздельные теги интерпретируемых и скомпилированных процедур.
Попробуйте сами
Измените программу и сравните результат.
Добавьте интерпретируемую процедуру, вызывающую add-three, и скомпилируйте процедуру, вызывающую double. Вызовите обе обертки и предскажите вложенные журналы диспетчеризации перед их запуском.
Показать подсказку
Каждая обертка добавляет собственное представление в начало хронологического пути. Сначала проследите внешний вызов, а затем представление, в которое входит его тело.