
- •Содержание
- •Введение
- •1.3. Множества цепочек
- •1.4. Языки
- •1.5. Алгоритмы
- •1.6. Некоторые понятия теории графов
- •Контрольные вопросы
- •2. Введение в компиляцию
- •2.1. Задание языков программирования
- •2.2. Синтаксис и семантика
- •2.3. Процесс компиляции
- •2.4. Лексический анализ
- •2.5. Работа с таблицами
- •Переменная с плавающей точкой
- •2.6. Синтаксический анализ
- •2.7. Генератор кода
- •2.8. Оптимизация кода
- •2.8. Оптимизация кода
- •2.9. Исправление ошибок
- •2.10. Резюме
- •Контрольные вопросы
- •3. Теория языков
- •3.1. Способы определения языков
- •3.2. Грамматики
- •3.4. Распознаватели
- •3.5. Регулярные множества, их распознавание и порождение
- •5.2. LR(1) - таблица разбора
- •5.3. Построение LR – таблицы разбора
- •5.4. Сравнение LL – и LR – методов разбора
- •Контрольные вопросы
- •6. Оптимизация кода
- •6.1. Оптимизация линейного участка
- •6.1.1. Модель линейного участка
- •6.1.2. Преобразование блока
- •6.1.3. Графическое представление блоков
- •6.1.4. Критерий эквивалентности блоков
- •6.1.5. Оптимизация блоков
- •6.1.6. Алгебраические преобразования
- •6.2. Арифметические выражения
- •6.2.1. Модель машины
- •6.2.2. Разметка дерева
- •6.2.3. Программы с командами STORE
- •6.2.4. Влияние некоторых алгебраических законов
- •6.3. Программы с циклами
- •6.3.1. Модель программы
- •6.3.2. Анализ потока управления
- •Алгоритм вычисления прямого доминирования
- •6.3.3. Примеры преобразования программ
- •Удаления бесполезных операторов
- •Замена сложных операций
- •6.3.4. Оптимизация циклов
- •Перемещение кода
- •Индуктивное перемещение
- •Замена сложных операций
- •6.4. Анализ потоков данных
- •6.4.1. Интервалы
- •6.4.2. Анализ потоков данных с помощью интервалов
- •6.4.3. Несводимые графы управления
- •7. Включение действий в синтаксис
- •7.1. Получение четверок
- •7.2. Работа с таблицей символов
- •Контрольные вопросы
- •8. Проектирование компиляторов
- •8.1. Число проходов
- •8.2. Таблицы символов
- •8.3. Таблица видов
- •Контрольные вопросы
- •9. Распределение памяти
- •9.1. Стек времени прогона
- •9.2. Методы вызова параметров
- •9.3. Обстановка выполнения процедур
- •9.4. «Куча»
- •9.5. Счетчик ссылок
- •9.6. Сборка мусора
- •Контрольные вопросы
- •10. Генерация кода
- •10.1. Генерация промежуточного кода.
- •10.2. Структура данных для генерации кода
- •10.3. Генерация кода для типичных конструкций
- •10.3.1. Присвоение
- •10.3.2. Условные зависимости
- •10.3.3. Описание идентификаторов
- •10.3.4. Циклы
- •10.3.5. Вход и выход из блока
- •10.3.6. Прикладные реализации
- •10.4. Проблемы, связанные с типами
- •10.5. Время компиляции и время прогона
- •Контрольные вопросы
- •11. Исправление и диагностика ошибок
- •11.1. Типы ошибок
- •11.2. Лексические ошибки
- •11.3. Ошибки в употреблении скобок
- •11.4. Синтаксические ошибки
- •11.5. Методы исправления синтаксических ошибок
- •11.6. Предупреждения
- •11.7. Сообщения о синтаксических ошибках
- •11.8. Контекстно-зависимые ошибки
- •11.9. Ошибки, связанные с употреблением типов
- •11.10. Ошибки, допускаемые во время прогона
- •Контрольные вопросы
- •Список литературы
167
I(n1) = {n1} I(n2) = {n2} I(n3) = {n3}
В соответствие с рассмотренным алгоритмом, находим, что граф F несводим.
Заметим, рассмотренный алгоритм выполняется за время, пропорциональное числу дуг графа управления. Поскольку в графе управления, вершины которого являются участками программы, ни из одной вершины не выходит более 2 дуг, это эквивалентно тому, что алгоритм линейно зависит от участков программы.
6.4.2.Анализ потоков данных с помощью интервалов
Проблема, которую мы будем изучать, состоит в том, что для каж-
дого участка и для каждой переменнойA сводимого графа управле-
ния выяснить в каких операторах программы могла определяться переменная A в последний раз, перед тем как управление достигло
участка .
Будем исходить из априорного утверждения, что анализ потоков данных с помощью интервалов связан с трактовкой графов как упакованных векторных битов. Для вычисления пересечения, объединения и дополнения множеств используются логические операцииAND, OR и NOT на векторах битов.
Построим таблицы, дающие для каждого участка программы все позиции l, где определяется данная переменная A и откуда существует
путь в , вдоль которого A не переопределяется. Эта информация необходима для вычисления возможных значенийA при входе в участок
.
Определим четыре функции, отображающие участки во множест-
ва.
Определение. Путем вычислений из оператора s1 в оператор s2 назо-
вем последовательность операторов, начинающуюся в s1 и заканчивающуюся в s2, которая в данном порядке может выполняться в процессе выполнения программы.
Пусть участок программы P. Определим четыре множества, связанные с операторами определения:
1)IN( ) = {d Î P ½ существует такой путь вычисления из опера-
тора определения d в первый оператор в , что никакой оператор на
168
этом пути, кроме может быть первого оператора в , не переопределяет переменную, определенную в d}.
2)OUT( ) = {d Î P ½ существует такой путь вычисления изd в
последний оператор , что никакой оператор на нем не переопределяет переменную, определенную в d}.
3)TRANS( ) = {d Î P ½ переменная, определенная в d, не опре-
деляется никаким оператором в }.
4)GEN( ) = {d Î P ½ переменная, определенная в d, не опреде-
ляется никаким оператором в }.
Таким образом, IN( ) содержит определения, которые могут быть
активными при входе в , OUT( ) содержит определения, которые
могут быть активными при выходе из , TRANS( ) содержит опреде-
ления, передаваемые через без определения в , GEN( ) содержит
определения, создаваемые в , которые остаются активными при вы-
ходе из .
Легко видеть, что
OUT( ) = (IN( ) Ç TRANS( )) È GEN( ).
Пример. Рассмотрим программу
S1: I ¬ 1
S2: J ¬ 0
S3: J ¬ J + 1
S4: read I
S5: if I < 100 goto S8
S6: write J S7: halt
S8: I ¬ I*I S9: goto S3
Граф программы изображен на рис. 11.29.
Определим для 2 множества IN, OUT, TRANS и GEN. Оператор S1 определяет I, а S1, S2, S3 – путь вычислений, на котором I не определяется (кроме как в S1). Поскольку этот путь ведет изS1 в первый
оператор участка 2, ясно, что S1 Î IN( 2). Аналогично можно показать, что
IN( 3) = {S1, S2, S3, S8}

169
Отметим, что S4 не принадлежит IN( 2), поскольку нет пути вычисления из S4 в S3 не переопределяющего I после S4.
OUT( 2) не содержит S1, поскольку все пути вычисления из S1 в S5 переопределяют I. Далее легко проверить, что
OUT( 2) = { S3, S4}
TRANS( 2) = Æ
GEN( 2) = { S3, S4}
S1: I ¬ 1 B1
S2: J ¬ 0
S3: J ¬ J + 1
S4: read I B2
S5: if I < 100 goto S8
B3 |
S8: I ¬ I*I |
|
S6: write J |
B4 |
|
S9: goto S3 |
|
S7: halt |
|
Рис. 11.29. Граф управления.
Предположим, что 1, …., k – все прямые потомки участка в P. Ясно, что
k k
IN( ) = È OUT( i) = È [(IN( i) Ç TRANS( i)) È GEN( i)].
l =1 l =1
Для вычисления IN( ) можно было бы выписать это уравнение для
каждого участка программы вместе с уравнениемIN( 0) = Æ, где 0 - начальный участок, затем попытаться разрешить систему уравнений. Однако существует более удобный метод, учитывающий преимущества представления графов управления в виде интервалов.
Дадим вначале определения понятий «вход» и «выход» интервала. Определение. Пусть P – программа, а F0 ее граф управления. Пусть
F0, F1,…, Fn – производная последовательность от F0. Каждая вершина в Fi, i ³ 1, является интервалом в Fi-1 и называется интервалом порядка i.

170
Входом интервала порядка 1 служит заголовок интервала. Вход интервала порядка i > 1 – это вход в заголовок интервала. Таким образом, вход любого интервала – это линейный участок исходной программы
P.
Выходом интервала I(n) порядка 1 служит такой последний опера-
тор участка в I(n), что имеет прямого потомка, который является либо заголовком интервала n, либо участком вне I(n). Выход интервала
I(n) порядка i ³ 1 – это последний оператор участка , содержащегося
в I(n) и такого, что в F0 есть дуга, ведущая из либо в заголовок интервала n, либо в участок вне I(n).
Отметим, что каждый участок имеет один вход и ноль или более выходов.
Пример. Пусть F0 – граф управления программы рис. 11.29. С помощью алгоритма разбиения графа на непересекающиеся интервалы, построим его разбиение на интервалы
I1 = I( 1) ={ 1}
I2 = I( 2) = { 2, 3, 4}
Из этих интервалов можно построить первый производный граф управления F1, показанный на рис11.30. Из F1 можно построить его интервалы
I3 = I(I1) = { I1, I2} = { 1, 2, 3, 4}
и получить предельный граф управления, также показанный на рис. 11.30.
I1
F1 |
I3 |
F2 |
|
I2
Рис. 11.30. Производная последовательность от графа F0.
Интервалами порядка 1 являются I1 и I2. Вход для I2 – это 2. Вход для I3 – это 1. Единственный выход для I1 – это оператор S2. Единст-

171
венный выход для I2 – это оператор S9. Интервалом порядка 2 является
I3 с входом 1. Интервал I3 не имеет выходов.
Продолжим теперь функции IN, OUT, TRANS и GEN на интервалы. Пусть F0, F1,…, Fn - производная последовательность от F0, где F – граф управления для P, а I – интервал некоторого графа Fi, i ³ 1. Введем следующие основные определения:
ìIN( ), если I имеет порядок 1, а - заголовок для I,
1)IN(I) = í
îIN(I¢), если I имеет порядок i > 1, а I¢ заголовок для I.
В2) – 4) ниже s – вход для I.
2)OUT(I, s) = OUT( ), где участок Î I таков, что s – его последний оператор.
3)(а) TRANS( , s) = TRANS( ), если s – последний оператор в
.
(б) TRANS(I, s) – множество таких операторов d Î P, что существует путь без циклов I1, I2,…, Ik, состоящих исключительно из вершин в I, и такая последовательность выходов s1, s2, …, sk для I1, I2,…, Ik, соответственно, что
(i)I1 – заголовок для I,
(ii) в F0 участок sj является прямым предком входа дляIj+1
при 1 £ j < k,
(iii)d Î TRANS(Ij, sj) при 1 £ j £ k,
(iv)sk = s.
Эти условия иллюстрированы на рис. 11.31.
Интервал I |
s1 |
в Fi |
|
|
S2 |
|
Интервалы в Fi-1 |
|
Sk |
s
Рис. 11.31. TRANS(Ij, sj).
172
4)(а) GEN( , s) = GEN( ), если s - последний оператор в .
(б) GEN(I, s) - множество таких операторов d Î P, что существует путь без циклов I1, I2,…, Ik, состоящих исключительно из вершин в I, и такая последовательность выходов s1, s2, …, sk для I1, I2,…, Ik, соответственно, что
(i)d Î GEN(I, s),
(ii)в F0 участок sj является прямым предком входа для Ij+1 при 1 £
j < k,
(iii)d Î TRANS(Ij, sj) при 2 £ j £ k,
(iv)sk = s.
Таким образом, TRANS(I, sj) – это множество определений, которое можно передать с входа I на выход s без переопределения в I. GEN(I, s)
– это множество определенийI, которые без переопределений могут достичь s.
Пример. Рассмотрим F0 на рис 11.29 и F1 и F2 на рис. 11.30. В F1
интервал I2 это { 2, 3, 4}, и он имеет выход S9. Таким образом,
IN(I2) = IN( 2) = {S1, S2, S3, S8} и OUT(I2, s) = OUT( 2) = {S3, S8}. TRANS(I2, S9) = Æ.
GEN(I2, S9) содержит S8, поскольку существует последователь-
ность участков, состоящая только из 2, в которой S8 Î GEN( 3, S9). Кроме того, S3 Î GEN(I2, S9) , поскольку существует последователь-
ность участков 2, 3 с выходом на S5 и S9. Это означает, что S3 Î GEN( 2, S5), 2 – прямой предок участка 3, S3 Î GEN( 3, S9).
На основании этих определений дадим алгоритм вычисленияIN( ) для всех участков программыP. Он обрабатывает программы только со сводимыми графами управления.
Алгоритм вычисления функции IN.
Вход. Сводимый граф F0 для программы P.
Выход. IN( ) для каждого участка Î P.
Метод.
1)Пусть F0, F1,…, Fk - производная последовательность отF0.
Вычислим TRANS( ) и GEN( ) для всех участков из F0.
2)Для i = 1, 2, …, k последовательно вычисляем TRANS(I, s) и GEN(I, s) для всех интервалов порядка i и выходов s для I. Рекурсивное определение этих функций гарантирует, что это можно сделать.
3)Полагаем IN(I) = Æ, где I – одиночный интервал порядкаk. Устанавливаем i=k.
173
4)Для всех интервалов порядка i выполняем следующее. Пусть I
={I1, …, In} – интервал порядка i (I1, …, In – интервалы порядка i-1 или участки, если i=1). Можно считать, что эти интервалы перечислены в том порядке, в котором из них составлен интервалI в алгоритме опре-
деления непересекающихся интервалов. Иными словами, I1 – это заголовок и для каждогоj > 1 множество {I1, …, Ij-1} содержит все вершины из Fi-1, являющиеся прямыми предками интервала Ij.
(а) Пусть s1, s2, …, sr – выходы интервала I, каждый из которых принадлежит участку в F0, являющемуся прямым предком входа для I. Полагаем
r
IN(I1) = IN(I) È È GEN(I, si)
i=1
(б) Для всех s Î I1 полагаем
OUT(I1, s) = (IN(I1) Ç TRANS(I1, s) È GEN(I, s))
(в) Для j=2, 3, …, n пусть sr1, sr2, …, srkr – выходы интервала Ir, 1 £ r < j, каждый из которых принадлежит участку вF0, являющемуся пря-
мым предком для входа Ir. Полагаем
IN(Ij) = È OUT(Ir, srl)
r,l
OUT(Ij, s) = (IN(Ij) Ç TRANS(Ij, s)) È GEN(Ij, s)
для всех интервалов, входящих в Ij.
5)Если i = 1 остановиться. В противном случае уменьшить i на 1
ивернуться к шагу 4).
Пример. Применим данный алгоритм к графу управления на рис. 11.29. Для четырех участков в F0 GEN и TRANS вычисляются просто. Результаты приведены в табл. 11.6.
Таблица 11.6.
Участок |
GEN |
TRANS |
1 |
{S1, S2} |
Æ |
2 |
{S3, S4} |
Æ |
{S8} |
{S2, S3} |
|
3 |
Æ |
{S1, S2, S3, S4, S8} |
4 |
|
|
Поскольку 3 определяет только переменнуюI 3 «убивает» предыдущие ее определения, но передает определение переменнойJ, а именно S2 и S3. Поскольку никакой из участков не определяет переменную дважды, все операторы внутри участка принадлежат множеству GEN для данного участка.
174
Интервал I1, состоящий из одного участка 1, имеет один выход – оператор S2. Так как пути в I1 тривиальны, то GEN(I1, S2) = {S1, S2} и TRANS(I1, S2) = Æ.
Интервал I2 имеет один выход S9. GEN(I2, S9) = {S3, S8} и TRANS(I2, S9) = Æ.
Теперь можно начать вычисление функцииIN. Первоначально IN(I3) = Æ. Затем к двум подынтервалам в I3 можно применить шаг4) алгоритма. Это можно сделать только в порядкеI1, I2. На шаге 4а) вычисляем IN(I1) = IN(I3) = Æ, на шаге 4в) –
OUT(I1, S2) = (IN(I1) Ç TRANS(I1, S2)) È GEN(I1, S2) = {S1, S2}.
Далее, на шаге 4в)
IN(I2) = OUT(I1, S2) = {S1, S2}.
Проходя по интервалам порядка 1, мы должны рассмотреть состав-
ляющие для I1 и I2. Интервал I2 состоит из участков 2, 3 и 4, которые в таком порядке и можно рассматривать. На шаге 4а)
IN( 2) = IN(I2) È GEN(I1, S9) = {S1, S2, S3, S8}.
На шаге 4б)
OUT( 2, S5) = IN( 2) Ç TRANS( 2, S5)) È GEN( 2, S5) = {S3, S4}.
Поскольку S5 ведет к 3, находим
IN( 3) = OUT( 2, S5) = {S3, S4},
а так как S5 ведет к 4, то
IN( 4) = OUT( 2, S5) = {S3, S4}.
И так,
IN( 1) = Æ
IN( 2) = {S1, S2, S3, S8}. IN( 3) = {S3, S4}
IN( 4) = {S3, S4}.
Индукцией по порядку интервалов можно доказать, что
1)TRANS(I, s) – множество таких операторов определенийd Î P, что существует путь из первого оператора заголовка дляI вплоть до s, вдоль которого ни один из операторов не переопределяет переменную, определенную в d,
2)GEN(I, s) - множество таких операторов определенийd, что существует путь из d в s, вдоль которого ни один из операторов не переопределяет переменную, определенную в d.
Индукцией по числу переменных шага 4) можно показать, что