Читайте также:
|
|
Желательно иметь компилятор с минимальным числом проходов, особенно если учесть время, необходимое для чтения и записи промежуточных файлов. Однако при группировании нескольких фаз в одном проходе может потребоваться размещение программы в памяти целиком — поскольку одной фазе может потребоваться информация в порядке, отличном от того, в котором ее выдает предыдущая фаза. Программа во внутреннем представлении может быть значительно больше исходной программы или программы, получаемой в результате работы компилятора, поэтому вопрос о пространстве далеко не тривиален.
Группировка некоторых фаз в одном проходе не представляет проблем. Как упоминалось выше, с одной стороны, интерфейс между лексическим и синтаксическим анализами зачастую ограничивается единственным токеном. С другой стороны, часто очень сложно выполнить генерацию кода до завершения создания промежуточного представления. Например, языки типа PL/I и Algol 68 позволяют использование переменных до их объявления. Невозможно сгенерировать целевой код для языковой конструкции, если не известны типы используемых в ней переменных. Подобная же ситуация возникает и в языках, которые позволяют использовать оператор безусловного перехода вперед по коду (таких языков программирования подавляющее большинство). Определить целевой адрес такого оператора безусловного перехода невозможно без генерации кода для инструкций между оператором безусловного перехода и его местом назначения.
Иногда удается оставить необходимое для отсутствующей информации пустое место и заполнить его позже, когда информация станет доступной. В частности, генерация промежуточного и целевого кодов часто может быть объединена в один проход с использованием технологии "обратных поправок" (backpatching). До тех пор, пока в главе 8, "Генерация промежуточного кода", не будут рассмотрены вопросы, связанные с генерацией промежуточного кода, невозможно пояснить все детали этой технологии. Однако мы попытаемся проиллюстрировать технологию обратных поправок на примере ассемблера. В предыдущем разделе рассматривался двухпроходный ассемблер, когда в первом проходе производился поиск всех идентификаторов, представляющих ячейки памяти, и происходило назначение им адресов, а во втором — идентификаторы заменялись адресами.
Можно скомбинировать эти действия следующим образом. При использовании ассемблерной инструкции со ссылкой вперед
GOTO target
мы генерируем инструкцию с машинным кодом операции GOTOи пустым местом вместо адреса. Все инструкции с пустыми местами вместо адреса target хранятся в списке, связанном с записью для идентификатора target в таблице символов. Эти пустые места заполняются, как только появляется инструкция типа
target: MOV foobar, R1
и определяется значение идентификатора target, которое является адресом текущей инструкции. Затем производим "обратную поправку", проходя по списку, связанному с идентификатором target, и внося реальное значение адреса в пустые поля адресов. Такой подход прост в реализации, если инструкции хранятся в памяти до определения всех целевых адресов.
Такой подход вполне допустим в ассемблере, который может хранить весь свой вывод в памяти. Поскольку промежуточное и окончательное представления кода в случае ассемблера практически одинаковы и имеют близкие размеры, применение технологии обратных поправок не сталкивается с неразрешимыми проблемами. Однако в компиляторах с большим промежуточным кодом, возможно, придется ограничить использование метода обратных поправок некоторым диапазоном, определяемым доступной памятью.
Дата добавления: 2015-11-30; просмотров: 32 | Нарушение авторских прав