Генерация кода
Назначение адресов
Назначение адресов переменным, параметрам и полям записей происходит при обработке соответствующих объявлений. В однопроходном трансляторе это может производиться вместе с построением основной таблицы символов и соответствующие адреса (или смещения) могут храниться в этой же таблице. В промежуточном представлении Лидер объявления сохранены, что делает это промежуточное представление машинно-независимым. Напомним, что в Лидер-представлении каждому описанию соответствует некоторый номер. В процессе работы генератора кодов поддерживается таблица Table, в которой по этому номеру (входу) содержится следующая информация:
- для типа: его размер;
- для переменной: смещение в области процедуры (или глобальной области);
- для поля записи: смещение внутри записи;
- для процедуры: размер локальных параметров;
- для массива: размер массива, размер элемента, значение левой и правой границы.
Для вычисления адресов определим для каждого объявления два синтезируемых атрибута: DISP будет обозначать смещение внутри области процедуры (или единицы компиляции), а SIZE - размер. Тогда семантика правила для списка объявлений принимает вид
RULE DeclPart ::= ( Decl ) SEMANTICS Disp<1>=0; 1A: Disp<1>=Disp<1>+Size<1>; Size<0>=Disp<1>.
Все объявления, кроме объявлений переменных, имеют нулевой размер. Размер объявления переменной определяется следующим правилом:
RULE Decl ::= 'VAR' TypeDes SEMANTICS Tablentry Entry; 0: Entry=IncTab; Size<0>=((Table[VAL<2>]+1) / 2)*2; // Выравнивание на границу слова Table[Entry]=Disp<0>+Size<0>.
В качестве примера трансляции определения типа рассмотрим обработку описания записи:
RULE TypeDes ::= 'REC' ( TypeDes ) 'END' SEMANTICS int Disp; Tablentry Temp; 0: Entry<0>=IncTab; Disp=0; 2A: {Temp=IncTab; Table[Temp]=Disp; Disp=Disp+Table[Entry<2>]+1) / 2)*2; // Выравнивание на границу слова } Table[Entry<0>]=Disp.
Трансляция переменных
Переменные отражают все многообразие механизмов доступа в языке. Переменная имеет синтезированный атрибут ADDRESS - это запись, описывающая адрес в команде МС68020. Этот атрибут сопоставляется всем нетерминалам, представляющим значения. В системе команд МС68020 много способов адресации, и они отражены в структуре значения атрибута ADDRESS, имеющего следующий тип:
enum Register {D0,D1,D2,D3,D4,D5,D6,D7, A0,A1,A2,A3,A4,A5,A6,SP,NO}; enum AddrMode {D,A,Post,Pre,Indirect,IndPre,IndPost,IndirPC, IndPrePC,IndPostPC,InDisp,Index,IndexPC,Abs,Imm}; struct AddrType{ Register AddrReg,IndexReg; int IndexDisp,AddrDisp; short Scale; };
Значение регистра NO означает, что соответствующий регистр в адресации не используется.
Доступ к переменным осуществляется в зависимости от их уровня: глобальные переменные адресуются с помощью абсолютной адресации; переменные в процедуре текущего уровня адресуются через регистр базы А6.
Если стек организован с помощью статической цепочки, то переменные предыдущего статического уровня адресуются через регистр статической цепочки А5 ; переменные остальных уровней адресуются "пробеганием" по статической цепочке с использованием вспомогательного регистра. Адрес переменной формируется при обработке структуры переменной слева направо и передается сначала сверху вниз как наследуемый атрибут нетерминала VarTail, а затем передается снизу-вверх как глобальный атрибут нетерминала Variable. Таким образом, правило для обращения к переменной имеет вид (первое вхождение Number в правую часть - это уровень переменной, второе - ее Лидер-номер):
RULE Variable ::= VarMode Number Number VarTail SEMANTICS int Temp; struct AddrType AddrTmp1, AddrTmp2; 3: if (Val<2>==0) // Глобальная переменная {Address<4>.AddrMode=Abs; Address<4>.AddrDisp=0; } else // Локальная переменная {Address<4>.AddrMode=Index; if (Val<2>==Level<Block>) // Переменная // текущего уровня Address<4>.AddrReg=A6; else if (Val<2>==Level<Block>-1) // Переменная предыдущего уровня Address<4>.AddrReg=A5; else {Address<4>.Addreg= GetFree(RegSet<Block>); AddrTmp1.AddrMode=Indirect; AddrTmp1.AddrReg=A5; Emit2(MOVEA,AddrTmp1, Address<4>.AddrReg); AddrTmp1.AddrReg=Address<4>.AddrReg; AddrTmp2.AddrMode=A; AddrTmp2.AddrReg=Address<4>.AddrReg; for (Temp=Level<Block>-Val<2>; Temp>=2;Temp--) Emit2(MOVEA,AddrTmp1,AddrTmp2); } if (Val<2>==Level<Block>) Address<4>.AddrDisp=Table[Val<3>]; else Address<4>.AddrDisp= Table[Val<3>]+Table[LevelTab[Val<2>]]; }.
Функция GetFree выбирает очередной свободный регистр (либо регистр данных, либо адресный регистр) и отмечает его как использованный в атрибуте RegSet нетерминала Block. Процедура Emit2 генерирует двухадресную команду. Первый параметр этой процедуры - код команды, второй и третий параметры имеют тип AddrType и служат операндами команды. Смещение переменной текущего уровня отсчитывается от базы ( А6 ), а других уровней - от указателя статической цепочки, поэтому оно определяется как алгебраическая сумма размера локальных параметров и величины смещения переменной. Таблица LevelTab - это таблица уровней процедур, содержащая указатели на последовательно вложенные процедуры.
Если стек организован с помощью дисплея, то трансляция для доступа к переменным может быть осуществлена следующим образом:
RULE Variable ::= VarMode Number Number VarTail SEMANTICS int Temp; 3: if (Val<2>==0) // Глобальная переменная {Address<4>.AddrMode=Abs; Address<4>.AddrDisp=0; } else // Локальная переменная {Address<4>.AddrMode=Index; if (Val<2>=Level<Block>) // Переменная // текущего уровня {Address<4>.AddrReg=A6; Address<4>.AddrDisp=Table[Val<3>]; } else {Address<4>.AddrMode=IndPost; Address<4>.AddrReg=NO; Address<4>.IndexReg=NO; Address<4>.AddrDisp=Display[Val<2>]; Address<4>.IndexDisp=Table[Val<3>]; } }.
Рассмотрим трансляцию доступа к полям записи. Она описывается следующим правилом ( Number - это Лидер- номер описания поля):
RULE VarTail ::= 'FIL' Number VarTail SEMANTICS if (Address<0>.AddrMode==Abs) {Address<3>.AddrMode=Abs; Address<3>.AddrDisp= Address<0>.AddrDisp+Table[Val<2>]; } else {Address<3>=Address<0>; if (Address<0>.AddrMode==Index) Address<3>.AddrDisp= Address<0>.AddrDisp+Table[Val<2>]; else Address<3>.IndexDisp= Address<0>.IndexDisp+Table[Val<2>]; }.