Способы внутреннего представления программ

 

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

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

Известны следующие формы внутреннего представления программ:

· связочные списочные структуры, представляющие синтаксические деревья;

· многоадресный код с явно именуемым результатом (тетрады);

· многоадресный код с неявно именуемым результатом (триады);

· обратная (постфиксная) польская запись операций;

· ассемблерный код или машинные команды.

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

Синтаксические деревья.Это структура, представ­ляющая собой результат работы синтаксического анализатора. Она отражает синтаксис конструкций входного языка и явно содержит в себе полную взаимо­связь операций. Очевидно также, что синтаксические деревья – это машинно-независимая форма внутреннего представления программы.

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

Синтаксические деревья могут быть преобразованы в другие формы внутренне­го представления программы, представляющие собой линейные списки, с учетом семантики входного языка. Эти преобразования выполняются на основе принципов СУ-компиляции.

Многоадресный код с явно именуемым результатом (тетрады).Тетрады представляют собой запись операций в форме из четырех составляю­щих: операция, два операнда и результат операции. Например, тетрады могут выглядеть так: <операция> (<операнд1>, <операнд2>, <результат>).

Тетрады представляют собой линейную последовательность команд. При вычис­лении выражения, записанного в форме тетрад, они вычисляются одна за другой последовательно. Каждая тетрада в последовательности вычисляется так: опера­ция, заданная тетрадой, выполняется над операндами и результат ее выполнения помещается в переменную, заданную результатом тетрады. Если какой-то из операндов (или оба операнда) в тетраде отсутствует (например, если тетрада представляет собой унарную операцию), то он может быть опущен или заменен пустым операндом (в зависимости от принятой формы записи и ее реализации).

Результат вычисления тетрады никогда опущен быть не может, иначе тетрада полностью теряет смысл. Порядок вычисления тетрад может быть изменен, но только если допустить наличие тетрад, целенаправленно изменяющих этот поря­док (например, тетрады, вызывающие переход на несколько шагов вперед или назад при каком-то условии).

Тетрады представляют собой линейную последовательность, а потому для них несложно написать тривиальный алгоритм, который будет преобразовывать последовательность тетрад в последовательность команд результирующей про­граммы (либо последовательность команд ассемблера). В этом их преимущество перед синтаксическими деревьями. А в отличие от команд ассемблера тетрады не зависят от архитектуры вычислительной системы, на которую ориентирована результирующая программа. Поэтому они представляют собой машинно-незави­симую форму внутреннего представления программы.

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

Например, выражение A:=B*C+D-B*10, записанное в виде тетрад, будет иметь вид:

1. * (В, С, Т1)

2. + (Tl, D, T2)

3. * (В, 10, ТЗ)

4. – (Т2, ТЗ, Т4)

5. := (Т4, 0, А)

Здесь все операции обозначены соответствующими знаками (при этом присвое­ние также является операцией). Идентификаторы Т1, ...,Т4 обозначают времен­ные переменные, используемые для хранения результатов вычисления тетрад. Следует обратить внимание, что в последней тетраде (присвоение), которая тре­бует только одного операнда, в качестве второго операнда выступает незначащий операнд “0”.

Многоадресный код с неявно именуемым результатом (триады).Триады представляют собой запись операций в форме из трех составляющих: операция и два операнда. Например, триады могут иметь вид: <операция>(<операнд1>, <операнд2>). Особенностью триад является то, что один или оба операнда могут быть ссылками на другую триаду в том случае, если в качестве операнда данной триады выступает результат выполнения другой триады. Поэтому триады при записи последовательно нумеруют для удобства указания ссылок одних триад на другие (в реализации компилятора в качестве ссылок можно использовать не но­мера триад, а непосредственно ссылки в виде указателей – тогда при изменении нумерации и порядка следования триад менять ссылки не требуется).

Триады представляют собой линейную последовательность команд. При вычис­лении выражения, записанного в форме триад, они вычисляются одна за другой последовательно. Каждая триада в последовательности вычисляется так: опера­ция, заданная триадой, выполняется над операндами, а если в качестве одного из операндов (или обоих операндов) выступает ссылка на другую триаду, то берет­ся результат вычисления той триады. Результат вычисления триады нужно со­хранять во временной памяти, так как он может быть затребован последующими триадами. Если какой-то из операндов в триаде отсутствует (например, если триада представляет собой унарную операцию), то он может быть опущен или заменен пустым операндом (в зависимости от принятой формы записи и ее реа­лизации). Порядок вычисления триад, как и для тетрад, может быть изменен, но только если допустить наличие триад, целенаправленно изменяющих этот поря­док (например, триады, вызывающие переход на несколько шагов вперед или на­зад при каком-то условии).

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

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

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

Например, выражение A:=B*C+D-B*10, записанное в виде триад, будет иметь вид:

1. * (В, С)

2. + (^1, D)

3. * (В, 10)

4. – (^2, ^3)

5. := (А, ^4)

Здесь операции обозначены соответствующим знаком (при этом присвоение так­же является операцией), а знак ^ означает ссылку операнда одной триады на ре­зультат другой.

Ассемблерный код и машинные команды.Машинные команды удобны тем, что при их использовании внутреннее представление программы полностью соответствует объектному коду и сложные пре­образования не требуются. Команды ассемблера представляют собой лишь форму записи машинных команд, а потому в качестве формы внутренне­го представления программы практически ничем не отличаются от них.

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

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