Опубликован: 23.07.2006 | Доступ: свободный | Студентов: 2214 / 889 | Оценка: 4.28 / 4.17 | Длительность: 21:37:00
Специальности: Системный архитектор
Лекция 3:

Основы компиляторов

< Лекция 2 || Лекция 3: 123456 || Лекция 4 >

Генерация кода


Наконец, по оптимизированной версии промежуточного представления генерируется объектная программа. Эту задачу решает фаза генерации кода (code generator) . В "Генерация MSIL" мы изучим основные свойства целевой платформы .NET и рассмотрим процесс генерации кода для этой платформы. В "Выбор инструкций при генерации кода" мы рассмотрим более сложные алгоритмы генерации, использующие методику восходящего переписывания деревьев (BURS). Во многих случаях такой подход может значительно улучшить качество порождаемого кода.

Помимо собственно генерации кода, на этом этапе необходимо решить множество сопутствующих проблем, например:

  • распределение памяти, т.е. отображение имен исходной программы в адреса памяти
  • распределение регистров, т.е. определение для каждой точки программы множества переменных, которые должны быть размещены в регистрах
  • выбор такой последовательности записи значений в регистры, которая избавила бы от необходимости частой выгрузки значений из регистров, а затем повторной загрузки

Практически все эти задачи решаются окружением времени исполнения .NET и потому остались за пределами данного курса. Тем не менее, для полноты картины мы рассмотрим проблемы управления памятью в "Анализ потока управления" .

Внешний и внутренний интерфейсы

Нетрудно заметить, что одни фазы компиляции в большей степени зависят от входного языка и в меньшей степени от целевого. Другие фазы, наоборот, в меньшей степени зависят от входного языка и в большей степени от целевого. Например, лексический, синтаксический, видозависимый анализ и некоторые оптимизации не слишком значительно зависят от целевого языка. Эти фазы иногда объединяют вместе под названием внешний интерфейс или front-end. Front-end подразумевает также обработку ошибок, которые могут встретиться на перечисленных фазах (вопросы обработки ошибок обсуждаются в "Семантический анализ. Внутреннее представление" ). Остальные фазы, несущие на себе отпечаток целевого языка, называются внутренним интерфейсом (back-end) .

Таким образом, если мы хотим иметь компиляторы некоторого языка на разных платформах, то наша задача сводится к написанию нескольких back-end'ов без изменения front-end'а. С другой стороны, для создания многоязыкового компилятора достаточно написать несколько front-end'ов.

Просмотры

Обсудим еще один важный термин, а именно, просмотр (passes) . Под просмотром (или проходом) компилятора понимается процесс обработки всего, возможно, уже преобразованного, текста исходной программы.

Одна или несколько фаз компиляции могут выполняться на одном просмотре. Например, лексический анализ и синтаксический анализ часто выполняются на одном просмотре, т.е. синтаксический анализатор может обращаться к лексическому анализатору за очередной лексемой лишь по мере необходимости. С другой стороны, некоторые оптимизации могут выполняться на нескольких просмотрах.

Передача информации между просмотрами происходит в терминах так называемых промежуточных языков. Таким образом, если компилятор состоит из N просмотров, то должно быть определено N-1 промежуточных языков. Каков этот промежуточный язык, зависит от разработчиков компилятора. Обычно программа на промежуточном языке представляет собой синтаксическое дерево и, возможно, какие-то внутренние таблицы компилятора.

Желательно иметь относительно мало просмотров, т.к. для чтения программы на одном промежуточном языке и записи ее на другом промежуточном языке может занимать довольно много времени. С другой стороны, объединяя несколько фаз в один просмотр, мы должны помнить о том, что иногда мы не можем выполнить некоторую фазу, не получив информацию из предыдущих фаз. Например, C# позволяет использовать имя метода до того, как он был описан. Понятно, что мы не можем выполнить видозависимый анализ до тех пор, пока мы не будем знать имена и типы всех методов объектов. Таким образом, эти задачи должны решаться на разных просмотрах.

Техника "заплат"


Аналогично, если в языке есть оператор go to, то мы можем использовать его не только для перехода назад, но и для перехода вперед. Таким образом, мы не всегда можем определить операнд команды перехода, по крайней мере до тех пор, пока не доберемся до конца программы. Однако в простых случаях мы можем использовать технику "заплат" (backpatching) . Рассмотрим фрагмент программы на языке ассемблера:

...
               goto target;
               ...
               goto target;
               ...
target:        mov foobar, r1

Транслятор может при генерации кода сгенерировать вначале скелет команды перехода, запомнив ее адрес в строке некоторой таблицы, соответствующей идентификатору target. Когда адрес этого идентификатора будет определен, мы сможем завершить формирование команд перехода, пробежав по имеющемуся у нас списку.


Литература к лекции

  • А. Ахо, Р. Сети, Дж. Ульман "Компиляторы: принципы, технологии и инструменты", М.: "Вильямс", 2001, 768 стр.
< Лекция 2 || Лекция 3: 123456 || Лекция 4 >