Задача генератора кода - построение для программы на
входном языке эквивалентной машинной программы. Обычно
в качестве входа для генератора кода служит некоторое
промежуточное представление программы.
Генерация кода включает ряд специфических,
относительно независимых подзадач: распределение
памяти (в частности, распределение регистров), выбор
команд, генерацию объектного (или загрузочного) модуля.
Конечно, независимость этих подзадач относительна:
например, при выборе команд нельзя не учитывать схему
распределения памяти, и, наоборот, схема распределения
памяти (регистров, в частности) ведет к генерации той
или иной последовательности команд. Однако удобно и
практично эти задачи все же разделять, обращая при этом
внимание на их взаимодействие.
В какой-то мере схема генератора кода зависит от формы
промежуточного представления. Ясно, что генерация кода из
дерева отличается от генерации кода из троек, а генерация
кода из префиксной записи отличается от генерации кода из
ориентированного графа. В то же время все генераторы кода
имеют много общего, и основные применяемые алгоритмы
отличаются, как правило, только в деталях, связанных с
используемым промежуточным представлением.
В дальнейшем в качестве промежуточного представления
мы будем использовать префиксную нотацию. А именно,
алгоритмы генерации кода будем излагать в виде атрибутных
схем со входным языком Лидер.
Модель машины
При изложении алгоритмов генерации кода мы будем
следовать некоторой модели машины, в основу которой
положена система команд микропроцессора Motorola
MC68020. В микропроцессоре имеется регистр - счетчик
команд PC, 8 регистров данных и 8 адресных регистров.
В системе команд используются следующие способы
адресации:
ABS - абсолютная: исполнительным адресом является
значение адресного выражения.
IMM - непосредственный операнд: операндом команды
является константа, заданная в адресном выражении.
D - прямая адресация через регистр данных, записывается
как Хn, операнд находится в регистре Хn.
А - прямая адресация через адресный регистр,
записывается как An, операнд находится в регистре An.
INDIRECT - записывается как ( An ), адрес операнда
находится в адресном регистре An.
POST - пост-инкрементная адресация, записывается как (Аn)+, исполнительный адрес есть значение адресного
регистра An и после исполнения команды значение этого
регистра увеличивается на длину операнда.
PRE - преинкрементная адресация, записывается как -(Аn): перед исполнением операции содержимое адресного
регистра An уменьшается на длину операнда, исполнительный
адрес равен новому содержимому адресного регистра.
INDISP - косвенная адресация со смещением,
записывается как (bd,An), исполнительный адрес
вычисляется как (An)+d - содержимое An плюс d.
INDEX - косвенная адресация с индексом, записывается
как (bd,An, Xn*sc), исполнительный адрес вычисляется
как (An)+bd+(Xn)*sc - содержимое адресного регистра + адресное смещение + содержимое индексного регистра,
умноженное на sc.
INDIRPC - косвенная через PC (счетчик команд),
записывается как (bd, PC), исполнительный адрес
определяется выражением (PC)+bd.
INDEXPC - косвенная через PC со смещением,
записывается как (bd,PC, Xn*sc), исполнительный адрес
определяется выражением (PC)+bd+(Xn)*sc.
INDPRE - пре-косвенная через память, записывается как ([bd,An,sc*Xn], od) (схема вычисления адресов для этого и
трех последующих способов адресации приведена ниже).
INDPOST - пост-косвенная через память: ([bd,An],sc*Xn,od).
INDPREPC - прекосвенная через PC: ([bd,PC,sc*Xn],od).
INDPOSTPC - пост-косвенная через PC: ([bd,PC],Xn,od).
Здесь bd - это 16 - или 32 -битная константа, называемая
смещением, od - 16 - или 32 -битная литеральная константа,
называемая внешним смещением. Эти способы адресации
могут использоваться в упрощенных формах без смещений bd и/или od и без регистров An или Xn. Следующие примеры
иллюстрируют косвенную постиндексную адресацию:
MOVE D0, ([A0])
MOVE D0, ([4,A0])
MOVE D0, ([A0],6)
MOVE D0, ([A0],D3)
MOVE D0, ([A0],D4,12)
MOVE D0, ([$12345678,A0],D4,$FF000000)
Индексный регистр Xn может масштабироваться
(умножаться) на 2,4,8, что записывается как sc*Xn.
Например, в исполнительном адресе ([24,A0, 4*D0])
содержимое квадратных скобок вычисляется как [A0] + 4 * [D0] + 24.
Эти способы адресации работают следующим образом.
Каждый исполнительный адрес содержит пару квадратных
скобок [...] внутри пары круглых скобок, то есть ([...], ... ). Сначала вычисляется содержимое
квадратных скобок, в результате чего получается 32- битный
указатель. Например, если используется постиндексная
форма [20,A2], то исполнительный адрес - это 20 + [A2]
Аналогично, для преиндексной формы [12,A4,D5]
исполнительный адрес - это 12 + [A4] + [D5].
Указатель, сформированный содержимым квадратных
скобок, используется для доступа в память, чтобы получить
новый указатель (отсюда термин косвенная адресация через
память). К этому новому указателю добавляется содержимое
внешних круглых скобок и таким образом формируется
исполнительный адрес операнда.
В дальнейшем изложении будут использованы
следующие команды (в частности, рассматриваются только
арифметические команды с целыми операндами, но не с
плавающими):
MOVEA ИА, А - загрузить содержимое по исполнительному
адресу ИА на адресный регистр А.
MOVE ИА1, ИА2 - содержимое по исполнительному адресу ИА1 переписать по исполнительному адресу ИА2.
MOVEM список_регистров, ИА - сохранить указанные
регистры в памяти, начиная с адреса ИА (регистры
указываются маской в самой команде).
MOVEM ИА, список_регистров - восстановить указанные
регистры из памяти, начиная с адреса ИА (регистры
указываются маской в самой команде).
LEA ИА, А - загрузить исполнительный адрес ИА на
адресный регистр А.
MUL ИА, D - умножить содержимое по исполнительному
адресу ИА на содержимое регистра данных D и результат
разместить в D (на самом деле в системе команд имеются
две различные команды MULS и MULU для чисел со знаком и
чисел без знака соответственно; для упрощения мы не будем
принимать во внимание это различие).
DIV ИА, D - разделить содержимое регистра данных D
на содержимое по исполнительному адресу ИА и результат
разместить в D.
ADD ИА, D - сложить содержимое по исполнительному
адресу ИА с содержимым регистра данных D и результат
разместить в D.
SUB ИА, D - вычесть содержимое по исполнительному
адресу ИА из содержимого регистра данных D и результат
разместить в D.
Команды CMP и TST формируют разряды регистра
состояний. Всего имеется 4 разряда: Z - признак нулевого
результата, N - признак отрицательного результата, V -
признак переполнения, C - признак переноса.
CMP ИА, D - из содержимого регистра данных D
вычитается содержимое по исполнительному адресу ИА, при
этом формируется все разряды регистра состояний, но
содержимое регистра D не меняется.
TST ИА - выработать разряд Z регистра состояний по
значению, находящемуся по исполнительному адресу ИА.
BNE ИА - условный переход по признаку Z = 1 (не равно)
по исполнительному адресу ИА.
BEQ ИА - условный переход по признаку Z = 0 (равно) по
исполнительному адресу ИА.
BLE ИА - условный переход по признаку N or Z (меньше
или равно) по исполнительному адресу ИА.
BGT ИА - условный переход по признаку not N (больше)
по исполнительному адресу ИА.
BLT ИА - условный переход по признаку N (меньше) по
исполнительному адресу ИА.
BRA ИА - безусловный переход по адресу ИА.
JMP ИА - безусловный переход по исполнительному
адресу.
RTD размер_локальных - возврат из подпрограммы с
указанием размера локальных.
LINK A, размер_локальных - в стеке сохраняется
значение регистра А, в регистр А заносится указатель на
это место в стеке и указатель стека продвигается на размер
локальных.
UNLK A - стек сокращается на размер локальных и регистр
А восстанавливается из стека.