Теория языков 8

advertisement
Теория языков
программирования и
методы трансляции
Тема №8
Генерация кода
Вопросы
В этой теме будет рассмотрена фаза генерации кода при
компиляции. В частности, будут рассмотрены следующие вопросы.
• Различные типы промежуточного кода, создаваемого
компилятором, и то, как они генерируются.
• Оптимизации кода и ее осуществление в различных фазах
процесса компиляции.
• Некоторые моменты, связанные с генераторами генераторов
кода.
В то же время вместо всестороннего рассмотрения всех
аспектов генерации кода основное внимание будет уделено вопросу
создания кода. Детальное рассмотрение того, как может выполняться
код, созданный для какой-то конкретной машины, скорее запутает, чем
прояснит ситуацию.
Создание промежуточного кода
Существует
ряд
причин
для
создания
компиляторами
промежуточного кода как первого шага к созданию кода для реальных машин.
Перечислим эти причины.
• Обеспечение четкого разделения меду машинно-независимой и
машинно-зависимой частями компилятора.
• Минимизация усилий для переноса компилятора в новую среду.
• Минимизация усилий для реализации m языков на n машинах.
• Простота оптимизации.
Промежуточный код может выглядеть по-разному. Он может
связываться с реализуемым языком, например Р-код для языка Pascal, байткод для языка Java. В качестве альтернативы он может также связываться с
машинами, на которых осуществляется реализация.
Создание промежуточного кода
Промежуточный язык может быть близким к реализуемым языкам
или к машинам, на которых осуществляется реализация. В любом случае
он представляет собой линеаризацию синтаксического дерева, созданного в
процессе синтаксического и семантического анализа, и формируется
посредством разбиения древовидной структуры на последовательность
инструкций, каждая из которых эквивалентна одной или нескольким
машинным командам. Машинный код может генерироваться, исходя только
из промежуточного кода, хотя при этом может потребоваться доступ к
таблице символов или другая информация, относящаяся к процессу
компиляции.
Создание промежуточного кода
Существует три наиболее известных промежуточных кода.
1. Трехадресный код.
2. Р-код – ориентированный на конкретный язык промежуточный
код, на котором основано большинство реализаций Pascal.
3. Байт-код, используемый Java Virtual Machine.
Трехадресный код
Примером трехадресного кода (three-address code) является строка
a = b op c
Здесь op – арифметический (или другой) оператор, b и c – его
операнды (или их адреса), a – адрес результата применения оператора к
операндам. Арифметическое выражение
(а + b)*(с + d)
можно представить в виде последовательности таких инструкций трехадресного кода
t1 = a + b
t2 = c + a
t3 = t1 * t2
Здесь t – создаваемые компилятором временные имена. Можно также
применять унарные операторы. Например, оператор
-m создаст инструкцию
t1 = -m
(хотя, безусловно, в этом случае трехадресный код будет содержать всего лишь два адреса!)
Трехадресный код
Преобразование выражений в последовательность инструкций
трехадресного кода легко осуществляется с помощью анализатора на основе
YACC, подобного использованному при создании постфиксной записи.
Грамматика YACC с действиями выглядит следующим образом.
S : EXP;
EXP : TERM
|
EXP + {A1();} TERM {A2();}
| EXP – {A1();} TERM {A2();};
TERM : FACT
| TERM* {A1();} FACT {A2();}
| TERM/ {A1();} FACT {A2();};
FACT : – {A1();} FACT {A4();}
| VAR {A3();}
| ( EXP );
VAR : a | b | c | d | e;
Трехадресный код
Необходимо, чтобы многоцелевой стек мог хранить операторы и
операнды. Действиями являются:
A1 – занести оператор в стек;
A2 – следующим образом напечатать инструкции трехадресного кода:
напечатать имя следующей распределяемой временной величины
напечатать "="
напечатать три верхних элемента стека снизу вверх
занести в стек только что распределенное имя временной
величины;
A3 – занести в стек операнд;
A4 – следующим образом напечатать инструкции трехадресного кода:
напечатать имя следующей распределяемой временной величины
напечатать "="
напечатать два верхних элемента стека снизу вверх
занести в стек только что распределенное имя временной
величины.
Трехадресный код
Трехадресный код может также применяться для представления
других аспектов типичных языков программирования, например,
присваиваний, обращений к массивам, условных и безусловных переходов.
Все следующие выражения являются примерами трехадресного кода.
a := tj
ti = c[i]
goto L
if ti goto L
Каждый оператор трехадресного кода имеет максимум три адреса,
также существуют формы инструкций для присваивания, включающего
адреса и указатели, вызовы процедур, вычисление параметров и т.д.
Высокоуровневые управляющие структуры, такие как циклы, условные
операторы и операторы выбора для создания трехадресного кода, сводятся
к проверкам условий и переходам.
Трехадресный код
Приведем примеры того, как управляющие структуры
компилируются в трехадресный код
if (выражение) оператор1 else олвратор2
Приведенный выше оператор if можно реализовать путем добавления к
грамматике следующих действий.
if (выражение) <l1> оператор1 <I2> else оператор2 <l3>
Трехадресный код
Здесь действиями являются:
l1 увеличить номер метки
образовать код для перехода к метке, если выражение ложно
поместить номер метки в стек
l2 увеличить номер метки
образовать код для перехода к метке
извлечь из стека метку, допустим, Lk
установить Lk в коде
поместить в стек метку, которая выше применялась в
безусловном переходе
l3 извлечь из стека метку, скажем, Lj
установить Lj в коде
При использовании данной грамматики будет создан следующий
код.
Трехадресный код
код для вычисления выражения
t1 = not выражение
if t1 goto L1
код для оператора1
goto L2
L1 код для оператора2
L2
Трехадресный код
while (выражение) оператор
Приведенный выше оператор while можно реализовать путем добавления к
грамматике следующих действий.
while <W1> (выражение) <W2> оператор <W3>
Здесь действиями являются:
W1 увеличить номер метки
установить метку в коде
поместить метку в стек
W2 увеличить номер метки
образовать код для перехода к метке, если выражение ложно
поместить метку в стек
W3 извлечь из стека метку, скажем, Lj
извлечь из стека метку, скажем, Lk
образовать код для безусловного перехода к Lk
установить Lj в коде
Трехадресный код
В результате будет создан следующий код:
L1: код для вычисления выражения
t1 = not выражение
if t1 goto L2
код для оператора
goto L1
L2:
Процесс назначения меток нетривиален и требует
использования стека времени компиляции. Он включает в себя
переходы вперед и назад по коду, а применимое и определяющее
вхождения меток обычно вкладываются "обычным образом". В то же
время следует быть осторожными в тех случаях (например, внутри
действия W3), когда порядок появления меток в стеке не совсем
соответствует порядку, в котором они требуются. Стек меток может
быть отдельным стеком времени компиляции или может объединяться
с другими стеками времени компиляции.
Download