Опубликован: 23.07.2006 | Доступ: свободный | Студентов: 2213 / 889 | Оценка: 4.28 / 4.17 | Длительность: 21:37:00
Специальности: Системный архитектор
Лекция 9:

Семантический анализ. Внутреннее представление

Преобразование типов

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

Описания языков определяют, какие преобразования возможны и необходимы. Если целое значение присваивается вещественной переменной или наоборот, выполняется преобразование к типу получателя присваивания. Хотя преобразование вещественного значения в целое, вообще говоря, некорректно. В формуле обычно выполняется преобразование целого операнда к вещественному типу. Фаза контроля типов вставляет эти операции преобразования в промежуточное представление исходной программы. Например, для формулы x+i после фазы контроля типов будет получено следующее дерево:


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

Преобразование называется явным (explicit conversion) , если программист должен написать что-нибудь для того, чтобы это преобразование было выполнено. Явные преобразования подобны вызовам функций, определенных над типами. В языке Pascal встроенная функция ord отображает литеру в целое, а функция chr выполняет обратное преобразование. Язык C приводит, т.е. преобразует неявно, ASCII литеры в целое в арифметических формулах.

Роль промежуточных языков в компиляторе

Большинство современных компиляторов - многопросмотровые; даже те языки программирования, которые теоретически могли бы быть скомпилированы за один проход (например, Pascal) чаще всего анализируются в несколько просмотров для того, чтобы улучшить качество генерируемого кода или упростить написание самого компилятора. Для связи между различными просмотрами компилятора используется некоторый промежуточный язык (ПЯ) , который иногда также называют внутренним представлением программы в компиляторе. По ходу компиляции внутреннее представление нагружается различной дополнительной информацией, полученной во время выполнения просмотров.

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

Идея массового применения промежуточного языка

Задача проектирования ПЯ особенно сложна при проектировании многоязыковых систем трансляции, позволяющих генерировать код сразу для нескольких целевых платформ. В этом случае становится выгодно спроектировать единый промежуточный язык для всего семейства трансляторов. Таким образом, можно свести задачу написания m*n компиляторов к реализации разбора m входных языков с построением в процессе анализа единого внутреннего представления и последующему написанию n просмотров, синтезирующих объектный код для n целевых платформ.

Эта идея была известна уже более 30 лет назад (UNCOL, Warren Abstract Machine, Виртовский p-code, АЛЬФА и т.д.), но лишь недавно такой подход стал широко применяться на практике, причем в основном совместно с идеей динамической компиляции:

  • Java bytecode представляет собой подход, в котором один входной язык проецируется сразу на множество целевых платформ с помощью единого промежуточного языка. Собственно синтез объектного кода возлагается на реализаторов Java Virtual Machine для данной платформы. Интересно, что несмотря на тот факт, что Java bytecode создавался только для одного входного языка, впоследствии появилось множество компиляторов, генерирующих Java bytecode для других исходных языков (например, Кобол).
  • MSIL представляет собой более общий случай той же идеи, с множеством входных языков и множеством целевых платформ. При этом синтез машинного кода выполняется .NET runtime во время выполнения программы.
  • Возможно, наиболее "чистой" реализацией идеи единого промежуточного языка является семейство компиляторов GNU, в котором единое внутреннее представление GNU RTL (RTL расшифровывается как Register Transfer Language) может порождаться из целого ряда языков (С/С++, Fortran, Ада, CHILL) и впоследствии может быть использовано для генерации объектного кода сразу для целого ряда платформ. GNU RTL также позволяет писать независимые просмотры оптимизации, причем наличие информации об относительной стоимости выполнения различных операций позволяет задавать как машинно-независимые, так и машинно-зависимые оптимизации.