Лекции по конструированию компиляторов - Часть 10
Автор: В.А.Серебряков
Глава 8. Генерация кода
Задача генератора кода - построение эквивалентной машинной
программы по программе на входном языке. Обычно в качестве
входного для генератора кода служит некоторое промежуточное
представление программы. В свою очередь, генерация кода
состоит из ряда специфических, относительно независимых
подзадач: распределение памяти (в частности, распределение
регистров), выбор команд, генерация объектного (или
загрузочного) модуля. Конечно, независимость этих подзадач
относительна: например, при выборе команд нельзя не учитывать
схему распределения памяти, и, наоборот, схема распределения
памяти (регистров, в частности) необходимо ведет к генерации
той или иной последовательности команд. Однако удобно и
практично эти задачи все же разделять и при этом особо
обращать внимание на их взаимодействие.
В какой-то мере схема генератора кода зависит от формы
промежуточного представления. Ясно, что генерация кода из
дерева отличается от генерации кода из троек, а генерация кода
из префиксной записи отличается от генерации кода из
ориентированного графа. В то же время все генераторы кода
имеют и много общего и основные применяемые алгоритмы
отличаются, как правило, только в деталях, связанных с
используемым промежуточным представлением.
В дальнейшем в качестве промежуточного представления мы
будем использовать префиксную нотацию и алгоритмы генерации
кода будем излагать в виде атрибутных схем со входным языком
Лидер.
8.1. Модель машины
При изложении алгоритмов генерации кода мы будем следовать
некоторой модели машины, в основу которой положена система
команд микропроцессора Motorola 68020.
В системе команд используются следующие способы адресации:
D - значение находится в регистре данных;
А - значение находится в адресном регистре;
POST - пост-инкрементная адресация (А)+: исполнительный
адрес есть значение адресного регистра и после исполнения
команды значение этого регистра увеличивается на длину
операнда;
PRE - пре-инкрементная адресация -(А): перед исполнением
операции содержимое адресного регистра уменьшается на
длину операнда, исполнительный адрес равен новому
содержимому адресного регистра;
DIRECT - прямая адресация через регистры: исполнительный
адрес равен значению выражения ADDREG+INDEXREG*SCALE
+ADDRDISP - содержимое адресного регистра + содержимое
индексного регистра, умноженное на SCALE+адресное
смещение;
INDPPRE - пре-косвенная через память: (ADDREG+INDEXREG*
SCALE+ADDRDISP)+INDEXDISP - выражение в скобках дает
адрес ячейки, содержимое которой + индексное смещение
дает исполнительный адрес;
INDPOST - пост-косвенная через память: (ADDREG+ADDRDISP)
+INDEXREG*SCALE+INDEXDISP - выражение в скобках дает
адрес ячейки, содержимое которой + содержимое индексного
регистра, умноженное на SCALE + индексное смещение, дает
исполнительный адрес;
DIRPC - прямая через PC (счетчик команд): исполнительный
адрес определяется выражением PC+INDEXREG*SCALE+ADDRDISP;
INDPREPC - пре-косвенная через PC: (PC+INDEXREG* SCALE+
ADDRDISP)+INDEXDISP - то же, что INDPRE, но в качестве
адресного регистра исползуется PC;
INDPOSTPC - пост-косвенная через PC: (PC+ADDRDISP)+INDEXREG
*SCALE+INDEXDISP то же, что и INDPOST, но в качестве
адресного регистра используется PC;
ABS - абсолютная;
IMM - непосредственный операнд.
В дальнейшем изложении будут использованы следующие команды:
MOVEA ИА,А - загрузить содержимое по исполнительному адресу
ИА на адресный регистр А;
MOVE ИА1,ИА2 - содержимое по исполнительному адресу ИА1
переписать по исполнительному адресу ИА2;
MOVEM список регистров,ИА - сохранить указанные регистры в
памяти по адресу ИА;
MOVEM ИА,список регистров - восстановить указанные регистры
из памяти по адресу ИА;
LEA ИА,А - загрузить исполнительный адрес ИА на адресный
регистр А;
MUL ИА,D - умножить содержимое по исполнительному адресу ИА
на содержимое регистра данных D и результат разместить в
D;
ADD ИА,D - сложить содержимое по исполнительному адресу ИА с
содержимым регистра данных D и результат разместить в D;
SUB ИА,D - вычесть содержимое по исполнительному адресу ИА
из содержимого регистра данных D и результат разместить в
D;
CMP ИА,D - содержимое регистра данных D вычитается из
содержимого по исполнительному адресу ИА, при этом
формируется признак результата, но содержимое регистра D
не меняется;
TST ИА - выработать код условия по значению, находящемуся по
исполнительному адресу ИА;
BNE ИА - условный переход по признаку NE (не равно) по
исполнительному адресу ИА;
BEQ ИА - условный переход по признаку EQ (равно) по
исполнительному адресу ИА;
BLE ИА - условный переход по признаку LE (меньше или равно)
по исполнительному адресу ИА;
BGT ИА - условный переход по признаку GT (больше) по
исполнительному адресу ИА;
BLE ИА - условный переход по признаку KE (меньше или равно)
по исполнительному адресу ИА;
BLT ИА - условный переход по признаку LT (меньше) по
исполнительному адресу ИА;
BR ИА - безусловный переход по адресу ИА;
JSR ИА - переход с возвратом на подпрограмму по адресу ИА;
JMP ИА - безусловный переход по исполнительному адресу;
RTD размер локальных - возврат из подпрограммы с указанием
размера локальных;
LINK A,размер локальных - в стеке сохраняется значение
регистра А, в регистр А заносится указатель на это место
в стеке и указатель стека продвигается на размер
локальных;
UNLK A - стек сокращается на размер локальных и регистр А
восстанавливается из стека.
8.2. Динамическая организация памяти
Рассмотрим схему реализации магазина периода выполнения для
простейшего случая (как, например, в языке Паскаль), когда все
переменные в магазине (фактические параметры и локальные
переменные) имеют известные при трансляции смещения. Магазин
служит для хранения локальных переменных (и параметров) и
обращения к ним в языках, допускающих рекурсивные определения
процедур. Еще одной задачей, которую необходимо решать при
трансляции языков с блочной структурой - обеспечение
реализации механизмов статической вложенности. Рассмотрим
рекурсивную программу рис. 8.1.
PROCEDURE P1; +----+
VAR V1; P2 | V2 |
PROCEDURE P2; |----|
VAR V2; |....|
BEGIN P2; |----|
V1:=... P2 | V2 |
V2:=... |----|
END P2; P1 | V1 |
BEGIN P2; +----+
END P1;
Рис. 8.1 Рис. 8.2
В процессе выполнения этой программы в магазине может
сложиться ситуация, изображенная на рис. 8.2. Находясь в
процедуре P2, мы должны иметь доступ к последнему экземпляру
значений переменных процедуры P2 и к экземляру значений
переменных процедуры P1, из которой была вызвана P2. Кроме
того, необходимо обеспечить восстановление состояния программы
при завершении выполнения процедуры.
Мы рассмотрим две возможные схемы динамической организации
памяти: схему со статической цепочкой и с дисплеем в памяти. В
первом случае все статические контексты связаны в список,
который называется статической цепочкой; в каждой записи для
процедуры в магазине хранится указатель на запись статически
охватывающей процедуры (помимо, конечно, указателя
динамической цепочки - указателя на "базу" динамически
предыдущей процедуры). Использование той или иной схемы
определяется, помимо прочих условий, прежде всего числом
адресных регистров.
8.2.1. Организация магазина со статической цепочкой
Минимальный адрес
+-----------------+<---------+
| Сохраненные | |
| регистры | |
|-----------------| |
Текущий | Локальные |<--+ |Область
статический |-----------------| |Disp |последней
уровень +--| Предыдущий BP |<--+- BP |вызванной
| |-----------------| | |процедуры
| | Точка возврата | |Disp |
| |-----------------| | |
| | Факт. параметры |<--+ |
||-+-----------------+-|<-------+
| | Сохраненные |
| | регистры |
| |-----------------| LP
+-+--| Предыдущий LP |<----+D
| | |-----------------| |e
Предыдущий | | | Локальные | |l
статический| | | | |t
уровень | | |-----------------| |a
| +->| Предыдущий BP |-----+
v +-----------------+
................. Максимальный адрес
Рис. 8.3
Итак, в случае статической цепочки магазин организован, как
это изображено на рис. 8.3.
Таким образом, на запись текущей процедуры в магазине
указывает регистр BP (Base pointer), с которого начинается
динамическая цепочка. На статическую цепочку указывает регистр
LP (Link Pointer). В качестве регистров BP и LP в различных
системах команд могут использоваться универсальные, адресные
или специальные регистры. Локальные переменные отсчитываются
от регистра BP вверх, фактические параметры - вниз с учетом
памяти, занятой точкой возврата и самим сохраненным регистром
BP.
Вызов подпрограмм различного уровня производится несколько
по-разному. При вызове подпрограммы того же статического
уровня, что и вызывающая подпрограмма, выполняются следующие
команды:
Занесение фактических параметров в магазин
JSR A
Команда JSR A продвигает указатель SP, заносит PC на верхушку
магазина и осуществляет переход по адресу A. После выполнения
этих команд состояние магазина становится таким, как это
изображено на рис. 8.4. Занесение BP, отведение локальных,
сохранение регистров делает вызываемая подпрограмма.
+---------------+
|Точка возврата |<---SP
|---------------|
|Факт. параметры|
|+---------------+--|
|Сохраненные |
|регистры |
|---------------| LP
+--|Предыдущий LP |<-------
| |---------------|
Предыдущий | |Локальные |
статический| | |
уровень | |---------------| BP
| |Предыдущий BP |<----
v |---------------|
|...............|
Рис. 8.4
При вызове локальной подпрограммы необходимо установить
указатель статического уровня на текущую подпрограмму, а при
выходе - восстановить его на старое значение (охватывающей
текущую). Для этого исполняются следующие команды:
Занесение фактических в магазин
MOVE BP,LP
SUB Delta,LP
JSR A
Здесь Delta - размер локальных вызываемой подпрограммы плюс
двойная длина слова. Магазин после этого принимает состояние,
изображенное на рис. 8.5.
+------------------+
| Точка возврата |
|------------------|
| Факт. параметры |
|--+------------------+--|
| Сохраненные |
| регистры |
|------------------| LP
+---| Предыдущий LP |<---+
| |------------------| D |
Предыдущий | | | e |
статический | | Локальные | l |
уровень | | | t |
| |------------------| a |
| | Предыдущий BP |<---+
v |------------------|
| ................ |
Рис. 8.5
После выхода из подпрограммы в вызывающей подпрограмме
выполняется команда
MOVE -Delta(BP),LP
которая восстанавливает старое значение статической цепочки.
Если выход осуществлялся из подпрограммы 1-го уровня, эту
команду выполнять не надо, поскольку для 1-го уровня нет
статической цепочки.
При вызове подпрограммы меньшего, чем вызывающая, уровня
выполняются следующие команды:
Занесение фактических параметров в магазин
MOVE (LP),LP /*столько раз, какова разность
уровней вызывающей и вызываемой ПП*/
JSR A
Тем самым устанавливается статический уровень вызываемой
подпрограммы. После выхода из подпрограммы выполняется команда
MOVE -Delta(BP),LP
восстанавиливающая статический уровень вызывающей
подпрограммы.
Тело подпрограммы начинается со следующих команд:
LINK BP,-размер локальных
MOVEM -(SP)
Команда LINK BP,размер локальных эквивалентна трем командам:
MOVE BP,-(SP)
MOVE SP,BP
ADD -размер локальных,SP
Команда MOVEM сохраняет в магазине регистры.
В результате выполнения этих команд магазин приобретает вид,
изображенный на рис. 8.3.
Выход из подпрограммы осуществляется следующей
последовательностью команд:
MOVEM (SP)+,D0-D7,A0-A7
UNLK BP
RTD размер фактических
Команда MOVEM восстанавливает регистры из магазина. После ее
выполнения магазин выглядит, как это изображено на рис. 8.6.
+-----------------+<--SP
Текущий | Локальные |
уровень |-----------------|
| Предыдущий BP |<--BP
|-----------------| +------------------+
| Точка возврата | | Точка возврата |<---SP
|-----------------| |------------------|
| Факт. параметры | | Факт. параметры |
|-----------------------| +------------------+
................. ..................
Рис. 8.6 Рис. 8.7
После выполнения команды UNLK BP, которая эквивалентна такой
последовательности команд:
MOVE BP,SP
MOVE (SP),BP
ADD #4,BP /*4 - размер слова*/
магазин имеет содержимое, изображенное на рис. 8.7.
Наконец, после выполнения команды RTD размер_фактических,
которая эквивалентна последовательности
ADD размер_фактических, SP
JMP -размер_фактических(SP)
магазин восстанавливается до состояния, которое было до
вызова.
В зависимости от наличия локальных переменных, фактических
параметров и необходимости упрятывания регистров каждая из
этих команд может отсутствовать.
8.2.2. Организация магазина с дисплеем
Рассмотрим теперь организацию магазина с дисплеем. Дисплей -
это массив (DISPLAY) фиксированного размера, определяющего
ограничение на число статических уровней вложенности процедур.
i-й элемент этого массива представляет собой указатель на
область активации процедуры i-го статического уровня. Доступ к
переменным самой внутренней процедуры осуществляется через
регистр BP. При вызове процедуры меньшего статического уровня
изменений в дисплее не производится. При вызове локальной
процедуры в дисплее отводится элемент для текущей (вызывающей)
процедуры. При вызове процедуры того же уровня (i) i-й элемент
замещается на указатель области активации текущей процедуры и
при выходе восстанавливается. Тем самым DISPLAY[i] всегда
указывает на область активации последней вызванной процедуры
i-го статического уровня.
Минимальный адрес
<------+
| Сохраненные | | Область
| регистры | | последней
|------------------| | вызванной
Текущий | Локальные | | процедуры
статический |------------------| |
уровень +--| Предыдущий BP |<-- BP <-+
| |------------------| | |
| | Точка возврата | | |
| |------------------| | |
| | Факт. параметры | | |
| |------------------|<------+ | DISPLAY[i]
| | Сохраненные | | ---------+
| | регистры | +------* |
| |------------------| ---------+
| | предыдущий |
| | DISPLAY[i] |
| |------------------|
Предыдущий | | Локальные |
статический | | |
уровень | |------------------|
+->| Предыдущий BP |
|------------------|
|..................|
Рис. 8.8
Запоминание и восстановление DISPLAY[i] можно не выполнять,
если процедура не имеет локальных переменных и параметров или
если из процедуры нет вызовов описанных в ней процедур.
Структура магазина при использовании дисплея изображена на
рис. 8.8. Дисплей может быть реализован либо через регистры
(если их достаточно), либо через массив в памяти.
|----------------------|<-------------------------+
| Локальные | Область активации |
|----------------| текущей процедуры |
| Регистры | |
|----------------| BP |
+-----| Предыдущий BP |<----- |
| |----------------| BP предыдущего статического |
| | x---+---------> уровня |
| | Дисплей .......| BP 1-го статического уровня |
| | x---+---------> |
| |----------------| |
| | Точка возврата | |
| |----------------| |
| | Фактические | |
| |--+----------------+--|<-------------------------+
| | Локальные |
| |----------------|
| | Регистры |
| |----------------|
+---->| Предыдущий BP |
|----------------|
| Дисплей |
|----------------|
| Точка возврата |
|----------------|
|--+ Фактические +--|
Рис. 8.9.
Иногда используется комбинированная схема - дисплей в магазине
(рис 8.9). Дисплей хранится в магазине. При вызове процедуры
текущего статического уровня он просто копируется в новую
область активации. При вызове процедуры более глубокого
статического уровня в дисплей добавляется указатель BP
вызывающей процедуры.
Отдельного рассмотрения требует вопрос о технике передачи
фактических параметров. Конечно, в случае простых параметров
(например, чисел) проблем не возникает. Однако передача
массивов по значению - операция довольно дорогая, поэтому с
точки зрения экономии памяти целесообразнее сначала в
подпрограмму передать адрес массива, а затем уже из
подпрограммы по адресу передать в магазин сам массив. В связи
с передачей параметров следует упомянуть еще одно
обстоятельство.
Рассмотренная схема организации магазина допустима только
для языков со статически известными размерами фактических
параметров. Однако, например, в языке Модула-2 по значению
может быть передан гибкий массив, и в этом случае нельзя
статически распределить память для параметров. Обычно в таких
случаях заводят так называемый "паспорт" массива, в котором
хранится вся необходимая информация, а сам массив размещается
в магазине в рабочей области выше сохраненных регистров.
8.3. Назначение адресов
Назначение адресов переменным, параметрам и полям записей
происходит при обработке соответствующих объявлений. В
однопроходном трансляторе это может производиться вместе с
построением основной таблицы символов и соответствующие адреса
(или смещения) могут храниться в этой же таблице. В
промежуточном представлении Лидер объявления сохранены, что
делает это промежуточное представление машинно-независимым.
Напомним, что в Лидер-представлении каждому описанию
соответствует некоторый номер. В процессе работы генератора
кодов поддерживается таблица Table, в которой по этому номеру
(входу) содержится следующая информация:
- для типа - его размер;
- для переменной - адрес;
- для поля записи - смещение внутри записи;
- для процедуры - размер локальных;
- для диапазона индексов массива - значение левой границы.
Функция IncTab вырабатывает указатель (вход) на новый элемент
таблицы, проверяя при этом наличие свободной памяти.
Таблица LevelTab - это таблица уровней процедур, содержащая
указатели на последовательно вложенные процедуры (см. рис.
4.9).
Для вычисления адресов определим для каждого объявления два
синтезируемых атрибута: DISP будет обозначать смещение внутри
области процедуры (или единицы компиляции), а SIZE - размер.
Тогда семантика правила для списка объявлений принимает вид
RULE
DeclPart ::= ( Decl )
SEMANTICS
Disp<1>:=0;
1A: Disp<1>:=Disp<1>+Size<1>;
Size<0>:=Disp<1>.
Это можно следующим образом изобразить на дереве объявлений
(рис. 8.10).
DeclPart | Size <----------+
/|\ |
/ | \ |
/ | \ |
/ | \ |
/ | \ |
Decl Decl Decl |
Disp----->Disp--------->Disp
+ Size + Size + Size
^ ^ ^
| | |
Рис. 8.10
Все объявления, кроме объявлений переменных, имеют нулевой
размер. Размер объявления переменной определяется следующим
правилом:
RULE
Decl ::= 'VAR' TypeDes
SEMANTICS
var Entry:Tablentry;
0: Entry:=IncTab;
Size<0>:=((Table[VAL<2>]+1) DIV 2)*2;
{ Выравнивание на границу слова }
Table[Entry]:=Disp<0>+Size<0>.
В качестве примера трансляции определения типа рассмотрим
обработку описания записи:
RULE
TypeDes ::= 'REC' ( TypeDes ) 'END'
SEMANTICS
var Disp:word;
Temp:Tablentry;
0: Entry<0>:=IncTab;
Disp:=0;
2A: begin Temp:=IncTab;
Table[Temp]:=Disp;
Disp:=Disp+Table[Entry<2>]+1) div 2)*2;
{ Выравнивание на границу слова }
end;
Table[Entry<0>]:=Disp.
8.4. Трансляция переменных.
Переменные отражают все многообразие механизмов доступа в
языке. Переменная имеет синтезированный атрибут ADDRESS - это
запись, описывающая адрес в команде МС68020. Этот атрибут
сопоставляется всем нетерминалам, представляющим значения. В
системе команд МС68020 много способов адресации, и они
отражены в структуре значения атрибута ADDRESS, имеющего
следующий тип:
Register=
(D0,D1,D2,D3,D4,D5,D6,D7,A0,A1,A2,A3,A4,A5,A6,SP,NO);
AddrType=record
AddrMode:(D,A,Post,Pre,Direct,IndPre,IndPost,
DirPC,IndPrePC,IndPostPC,Abs,Imm);
Addreg,IndexREG:Register;
IndexDisp,AddrDisp:cardinal;
Scale:1..8;
end;
Значение регистра NO означает, что соответствующий регистр в
адресации не используется.
Доступ к переменным осуществляется в зависимости от их
уровня: глобальные переменные адресуются с помощью абсолютной
адресации; переменные процедуры текущего уровня адресуются
через регистр базы А6.
Если стек организован с помощью статической цепочки, то
переменные предыдущего статического уровня адресуются через
регистр статической цепочки А5; переменные остальных уровней
адресуются "пробеганием" по статической цепочке с
использованием вспомогательного регистра. Адрес переменной
формируется при обработке структуры переменной слева направо и
передается сначала сверху вниз как наследуемый атрибут
нетерминала VarTail, а затем передается снизу-вверх как
глобальный атрибут нетерминала Variable. Таким образом,
правило для обращения к переменной имеет вид (первое вхождение
Number в правую часть - это уровень переменной, второе - ее
Лидер-номер):
RULE
Variable ::= VarMode Number Number VarTail
SEMANTICS
var Temp:integer;
AddrTmp1,AddrTmp2:AddrType;
3: if (Val<2>=0) then { Глобальная переменная }
Address<4>.AddrMode:=Abs;
Address<4>.AddrDisp:=0;
else { Локальная переменная }
Address<4>.AddrMode:=Direct;
if (Val<2>=Level) then
{ Переменная текущего уровня }
Address<4>.Addreg:=A6
elsif (Val<2>=Level-1) then
{ Переменная предыдущего уровня }
Address<4>.Addreg:=A5;
else Address<4>.Addreg
:=GetFree(RegSet);
with AddrTmp1 do
AddrMode=Direct;
Addreg=A5;
IndexReg=No;
AddrDisp=0;
end;
Emit2(MOVEA,AddrTmp1,Address<4>.Addreg);
AddrTmp1.Addreg;=Address<4>.Addreg;
AddrTmp2.AddrMode=A;
AddrTmp2.Addreg=Address<4>.Addreg;
for Temp:=Level-Val<2> do
Emit2(MOVEA,AddrTmp1,AddrTmp2)
end end;
if (Val<2>=Level) then
Address<4>.AddrDisp:=Table[Val<3>]
else Address<4>.AddrDisp:=Table[Val<3>]
+Table[LevelTAB[Val<2>]].
end end.
Функция GetFree выбирает очередной свободный регистр (либо
регистр данных, либо адресный регистр) и отмечает его как
использованный в атрибуте RegSet нетерминала Block. Процедура
Emit2 генерирует двухадресную команду. Первый параметр этой
процедуры - код команды, второй и третий параметры имеют тип
Address и служат операндами команды (здесь для упрощения
изложения в качестве параметров этой процедуры используются
контрукции типа '(А)'; имеется в виду косвенная адресация
через адресный регистр). Смещение переменной текущего уровня
отсчитывается от базы (А6), а других уровней - от указателя
статической цепочки, поэтому оно определяется как
алгебраическая сумма LOCALSize и величины смещения переменной
(отрицательного!).
Если стек организован с помощью дисплея, то трансляция для
доступа к переменным может быть осуществлена следующим
образом:
RULE
Variable ::= VarMode Number Number VarTail
SEMANTICS
var Temp:integer;
3: if (Val<2>=0) then { Глобальная переменная }
Address<4>.AddrMode:=Abs;
Address<4>.AddrDisp:=0;
else { Локальная переменная }
Address<4>.AddrMode:=Direct;
if (Val<2>=Level) then
{ Переменная текущего уровня }
Address<4>.Addreg:=A6;
Address<4>.AddrDisp:=Table[Val<3>]
else with Address<4> do
AddrMode=IndPost; Addreg:=NO; IndexREG:=NO;
AddrDisp:=DispLAY[Val<2>];
IndexDisp:=Table[Val<3>]
end end end.
Рассмотрим трансляцию доступа к полям записи. Она описывается
следующим правилом (Number - это Лидер-номер описания поля):
RULE
VarTail ::= 'FIL' Number VarTail
SEMANTICS
if (Address<0>.AddrMode=Abs) then
Address<3>.AddrMode:=Abs;
Address<3>.AddrDisp
:=Address<0>.AddrDisp+Table[Val<2>];
else Address<3>:=Address<0>;
if (Address<0>.AddrMode=Direct)
then Address<3>.AddrDisp
:=Address<0>.AddrDisp+Table[Val<2>]
else Address<3>.IndexDisp
:=Address<0>.IndexDisp+Table[Val<2>]
end end.
Смещение в случае абсолютной и прямой адресации определяется
полем AddrDisp, а в остальных случаях - полем IndexDisp. Таким
образом, видно, что при обработке полей записей идет только
накопление смещения поля без генерации каких-либо команд.
Атрибутные зависимости для этого правила могут быть
изображены следующим образом (рис. 8.11):
VarTail | Disp---------------+
| |
/ \ |
/ \ |
/ \ |
+--------Number VarTail | Disp <--+
| ^
| Table |
| |-------| |
+----------->| Disp |----------------+
|-------|
Рис. 8.11
Рассмотрим теперь выборку элемента массива. Лидер-синтаксис
обращения к элементу массива следующий:
VarTail ::= 'ARR' Number Typexpr VarTail
Здесь Number - Лидер номер описания диапазона индексов;
Typexpr - индексное выражение. По адресу левой части правила,
представляющей переменную-массив, от которого берется индекс,
и по индексному выражению, представленному нетерминалом
Typexpr, мы должны сформировать адрес элемента массива. В
приведенной ниже таблице решений функция GetAddr выбирает
очередной свободный адресный регистр.
Тип адресации VarTail левой части:
IndPre or Direct (IndPost or Abs) or
IndexReg=NO ((Direct or IndPre)
& (IndexReg<>NO))
---------------------------------------------------------
ElSize<3>= | AddrDisp<4>:= |AddrDisp<4>:=
1,2,4,8 | AddrDisp<0> |-Left<2>*ElSize<3>
AddrMode<3>=D | -Left<2>*ElSize<3> |AddrMode<4>:=IndPost
| Addreg<4>:=Addreg<0>|Addreg<4>:=
| IndexReg<4>:= |if Addreg<0> рабочий
| Addreg<3> |then Addreg<0>
| AddrMode<4>:=IndPost|else GetAddr
| Scale<4>:=ElSize<3> |IndexReg<4>:=
| | Addreg<3>
| |Scale<4>:=ElSize<3>
| |-------------------
| |LEA Address<0>,
| | Address<4>
---------------------------------------------------------
---------------------------------------------------------
ElSize<3> | AddrDisp<4>:= | AddrDisp<4>:=
<>1,2,4,8 | AddrDIP<0>- | -Left<2>*ElSize<3>
AddrMode<3>=D| Left<2>*ElSize<3> | AddrMode<4>:=IndPost
| Addreg<4>:=Addreg<0>| Addreg<4>:=
| IndexReg<4>:= | if Addreg<0> рабочий
| Addreg<3> | then Addreg<0>
| Scale<4>:=1 | else GetAddr
| AddrMode<4>:=IndPost| IndexReg<4>:=
|---------------------| Addreg<3>
| MUL ElSize<3>, | Scale:=1
| Addreg<3> |-------------------
| | LEA Address<0>,
| | Address<4>
| | MUL ElSize<4>,
| | IndexReg<4>
---------------------------------------------------------
---------------------------------------------------------
ElSize<3>= |AddrDisp<4>:= | AddrDisp<4>:=
1,2,4,8 |AddrDisp<0>- | -Left<2>*ElSize<3>
AddrMode<3><>D|Left<2>*ElSize<3> | AddrMode<4>:=IndPost
|Addreg<4>:=Addreg<0>| Addreg<4>:=
|IndexReg<4>:=GetFree| if Addreg<0> рабочий
|AddrMode<4>:=IndPost| then Addreg<0>
|Scale<4>:=ElSize<3> | else GetAddr
|--------------------| IndexReg<4>:=GetFree
|MOVE Address<3>, | Scale<4>:=ElSize<3>
| IndexReg<4> |-------------------
| | LEA Address<0>,
| | Address<4>
| | MOVE Address<3>,
| | IndexReg<4>
--------------------------------------------------------
---------------------------------------------------------
ElSize<3> |AddrDisp<4>:= | AddrDisp<4>:=
<>1,2,4,8 |AddrDisp<0>- | -Left<2>*ElSize
AddrMode<3><>D|Left<2>*ElSize<3> | AddrMode<4>:=IndPre
|Addreg<4>:=Addreg<0>| Addreg<4>:=
|IndexReg<4>:=GetFree| if Addreg<0> рабочий
| AddrMode<4>:=IndPre| then Addreg<0>
| Scale<4>:=ElSize<3>| else GetAddr
|--------------------| IndexReg<4>:=GetFree
|MOVE Address<3>, | Scale<4>:=1
| IndexReg<4> |----------------
| MUL ElSize<3>, | LEA Address<0>,
| IndexReg<4> | Address<4>
| | MOVE Address<3>,
| | IndexReg<4>
| | MUL ElSize<3>,
| | IndexReg<4>
--------------------------------------------------------
Под "рабочим" регистром в таблице решений имеется в виду
регистр, значение которого может быть испорчено (примером "не
рабочего" регистра может служить регистр А6 или регистр А5,
если он используется как указатель процедуры предыдущего
статического уровня).
MaxReg - максимальный номер адресного регистра, которым
можно пользоваться как рабочим. Все адресные регистры
разделены на два множества: имеющие номера, большие MaxReg,
заняты предварительным распределением, остальные - рабочие.
Функция GetAddr выдает очередной свободный рабочий адресный
регистр. Приведенная таблица реализуется следующим правилом:
RULE
VarTail ::= 'ARR' Number Typexpr VarTail
SEMANTICS
Size:integer;
Flag:boolean;
AddrTmp1,AddrTmp2:AddrType;
Flag:= ((Address<0>.AddrMode=IndPre)
or (Address<0>.AddrMode=Direct))
and (Address<0>.IndexReg=NO);
Size:=Table[Val<2>];
if (Address<3>.AddrMode=D)
then IndexReg<4>:=Addreg<3>
else IndexReg<4>:=GetFree;
end;
AddrMode<4>:=IndPre;
if Flag then
Address<4>.AddrDisp
:=Address<0>.AddrDisp
-Table[Val<2>]*Size;
Addreg<4>:=Addreg<0>;
else Address<4>.AddrDisp:=
-Table[Val<2>]*Size;
if (Address<0>.Addreg<=MaxReg)
then Addreg<4>:=Addreg<0>
else Addreg<4>:=GetAddr;
end end;
if (Size in [2,4,8])
then Address<4>.Scale:=Size)
else Address<4>.Scale:=1;
end;
AddrTmp1.AddrMode:=D; AddrTmp1.Addreg:=IndexReg<4>;
if Flag then Emit2(LEA,Address<0>,Address<4>) end;
if (Address<3>.AddrMode<>D)
then Emit2(MOVE,Address<3>,AddrTmp1);
end;
AddrTmp2.AddrMode:=IMM; AddrTmp2.AddrDisp:=ElSize<3>;
if not (Size IN [1,2,4,8])
then Emit2(MUL,AddrTmp2,AddrTmp1);
end.
|