Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Компиляторы.doc
Скачиваний:
99
Добавлен:
04.11.2018
Размер:
5.13 Mб
Скачать

8.2. Генерация кода и сборка

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

На рис. 8.1 представлены примеры кодовых продукций в виде ассемблерных фрагментов для ряда операций из матрицы тетрад.

В учебных компиляторах имеет смысл для каждой определяемой Вами операции подготовить макроопределения и представлять каждую тетраду макрокомандой, возложив формирование кода на стандартный транслятор с языка ассемблера. Пример макроопределения для операции сложения, где операнды могут быть словами или байтами, а результат всегда – слово представлен на рис. 8.2.

В реальных компиляторах формирование кода зачастую также осуществляется с помощью специализированных макропроцессоров. При этом директивы условной трансляции в макроопределениях операций могут обеспечить не только преобразование типов данных, но и машинно-зависимую оптимизацию, связанную с сохранением значений регистров во временной памяти и их загрузку, эффективном использовании всей совокупности регистров процессора и формировании более коротких и быстродействующих регистровых команд [6].

Фаза сборки зависит от того, что является результатом фазы генерации кода. В простейшем случае фаза сборки должна обработать все метки объектной программы, сформировать объектный модуль и информацию для загрузчика (таблицу переместимых и внешних имен). Функционально фаза сборки в этом случае похожа на второй просмотр ассемблера. В другом случае, если фаза генерации оставляет коды и метки в символическом виде, фаза сборки должна осуществлять, по сути дела, полную трансляцию с языка ассемблера и решать следующие задачи:

 разрешить все символьные ссылки;

 вычислить адреса;

 сгенерировать двоичные машинные команды;

 подготовить информацию для загрузчика.

8.3. Трансляция с языка ассемблера

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

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

Цель первого прохода – получить всю информацию о местоположении идентификаторов (имен, меток), а второго, – непосредственно генерировать код. Для локализации имен в ассемблере предусмотрен счетчик адреса (ячеек). Идентификатор, обнаруженный в поле метки анализируемого оператора заносится в таблицу имен и ему ставится в соответствие текущее значение счетчика адреса. При просмотре программы происходит увеличение счетчика адреса на число байт, занимаемых операторами. При переходе от одного сегмента программы к другому счетчик адреса обнуляется. Таким образом, счетчик адреса – это указатель, динамически фиксирующий относительные позиции (смещения) операторов (команд или директив) внутри одного сегмента. Итак, на первом проходе строится таблица имен (см. таблицу 7.1), а на втором проходе эта таблица используется для формирования адресов операндов.

Таблица 8.1.

Имя

Смещение

Сегмент, в котором определено имя

Тип

Размер и прочее

@Data

00H

сегмент

Vari

00H

@Data

переменная

Array

0AH

@Data

переменная

_Text

00H

сегмент

Start

00H

_Text

метка

Repeat

09H

_Text

метка

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

В отличие от рассмотренного, однопроходный ассемблер работает эффективнее. Он может легко генерировать команды, где имена операндов уже известны, определяются в программе до их использования. Иное дело, когда команда ссылается на имя пока неизвестное и, определяемое, например, как имя переменной или метка команды идущей за анализируемой строкой. В этом случае в таблице имен (идентификаторов) появляется дополнительная информация – признак определения метки. Если какое-либо неопределенное ранее имя встречается в поле операнда команды или директивы, то это имя помещается в таблицу с отметкой о том, что адрес (смещение) для этого имени еще не известно. Вместо смещения в таблицу помещается указатель головы списка, хранящего адреса команд ссылающихся на данную, пока неопределенную метку. После того, как данное имя появится в программе в поле метки и для него будет определено смещение, то ассемблеру достаточно “пробежаться по списку” ссылавшихся на данное имя команд и сформировать для них адреса операндов.