Теория языков программирования методов трансляции
..pdf
n5
n1 |
n2 |
n3 |
n6 |
n7 |
n8 |
n4
Рис. 6.26. Граф управления.
Начальной вершиной служит n1. Вначале список заголовков содержит только n1. Для построения I(n1) включаем n1 в I(n1) как непомеченную вершину. Помечаем вершину n1 ее прямым потомком n2. Для этого уменьшаем счетчик n2 с 2 до 1, полагаем достижимость до нее n1 и добавляем ее к списку заголовков. К этому моменту I(n1) не остается непомеченных вершин, так что список I(n1) = {n1} заполнен.
Список заголовков содержит потомка n2 вершины из I(n1). Для вычисления I(n2) включаем n2 в I(n2) и рассматриваем вершину n3, счетчик для которой равен 2. Уменьшаем счетчик на 1, полагаем достижимость для нее равной n2 и добавляем ее к списку заголовков. Находим таким образом, что I(n2) = {n2}.
Список заголовков содержит теперь потомка n3 из I(n2). Вычисление I(n3) начинаем теперь с занесения n3 в I(n3). Рассматриваем теперь вершины n4 и n5, уменьшая счетчик для них 1 до 0, полагая достижимость для них равной n3 и добавляя их к I(n3) как непомеченные вершины. Помечаем n4, уменьшая счетчик для n6 с 2 до 1, полагая достижимость для n6 равной n3 и добавляя n6 к списку заголовков. Помечая n5 в I(n3), уменьшаем счетчик для n6 с 1 до 0, удаляем ее из списка и добавляем к I(n3).
Чтобы пометить n6 в I(n3) делаем счетчик для n7 равным 0, полагаем достижимость для нее равной n3 и добавляем ее к I(n3). Следующей рассматривается вершина n3, поскольку есть дуга из n6 в n3. Так как достижимость для n3 равна n2, вершина n3 в данный момент не изменяет I(n3) и списка заголовков. Чтобы пометить n7, делаем счетчик n8 равным 0, полагаем достижимость для нее равной n3 и добавляем ее к I(n3). Вершина n2 также является потомком вершины n7, но так как до-
161
стижимость для n2 равна n1, то n2 не добавляется ни к I(n3) ни к списку заголовков.
Наконец чтобы пометить n8, не надо производить никаких операций, поскольку n8 не имеет потомков. К этому моменту в I(n3) не остается непомеченных вершин, так что I(n3) = {n3, n4, n5, n6, n7, n8}.
Список заголовков пуст, так что алгоритм закончился. В результате граф управления оказался разбитым на непересекающиеся интервала
I(n1) = {n1} I(n2) = {n2}
I(n3) = {n3, n4, n5, n6, n7, n8}.
На этих интервалах можно построить последовательность графов управления (рис. 6.27).
{n1} |
|
{n1} |
{n1, … n8} |
{n2} |
|
{n2, n3, … n8} |
|
|
|
||
|
|
{n3, … n8}
Рис. 6.27. Последовательность графов управления.
Пример. Рассмотрим граф рис. 6.28.
n1
n2 n3
Рис. 6.28. Граф управления F.
Интервалы для него таковы:
I(n1) = {n1} I(n2) = {n2} I(n3) = {n3}
162
В соответствие с рассмотренным алгоритмом, находим, что граф F несводим.
Заметим, рассмотренный алгоритм выполняется за время, пропорциональное числу дуг графа управления. Поскольку в графе управления, вершины которого являются участками программы, ни из одной вершины не выходит более 2 дуг, это эквивалентно тому, что алгоритм линейно зависит от участков программы.
6.4.2. Анализ потоков данных с помощью интервалов
Проблема, которую мы будем изучать, состоит в том, что для каж-
дого участка и для каждой переменной A сводимого графа управления выяснить в каких операторах программы могла определяться переменная A в последний раз, перед тем как управление достигло
участка .
Будем исходить из априорного утверждения, что анализ потоков данных с помощью интервалов связан с трактовкой графов как упакованных векторных битов. Для вычисления пересечения, объединения и дополнения множеств используются логические операции AND, OR и NOT на векторах битов.
Построим таблицы, дающие для каждого участка программы все позиции l, где определяется данная переменная A и откуда существует
путь в , вдоль которого A не переопределяется. Эта информация необходима для вычисления возможных значений A при входе в уча-
сток .
Определим четыре функции, отображающие участки во множества.
Определение. Путем вычислений из оператора s1 в оператор s2 назо-
вем последовательность операторов, начинающуюся в s1 и заканчивающуюся в s2, которая в данном порядке может выполняться в процессе выполнения программы.
Пусть участок программы P. Определим четыре множества, связанные с операторами определения:
1)IN( ) = {d P существует такой путь вычисления из опера-
тора определения d в первый оператор в , что никакой оператор на
этом пути, кроме может быть первого оператора в , не переопределяет переменную, определенную в d}.
163
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
Граф программы изображен на рис. 6.29.
Определим для 2 множества IN, OUT, TRANS и GEN. Оператор S1 определяет I, а S1, S2, S3 – путь вычислений, на котором I не определяется (кроме как в S1). Поскольку этот путь ведет из S1 в первый
оператор участка 2, ясно, что S1 IN( 2). Аналогично можно показать, что
IN( 3) = {S1, S2, S3, S8}
Отметим, что S4 не принадлежит IN( 2), поскольку нет пути вычисления из S4 в S3 не переопределяющего I после S4.
164
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 |
|
Рис. 6.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.
Входом интервала порядка 1 служит заголовок интервала. Вход интервала порядка i > 1 – это вход в заголовок интервала. Таким образом,
165
вход любого интервала – это линейный участок исходной программы
P.
Выходом интервала I(n) порядка 1 служит такой последний опера-
тор участка в I(n), что имеет прямого потомка, который является либо заголовком интервала n, либо участком вне I(n). Выход интервала
I(n) порядка i 1 – это последний оператор участка , содержащегося
в I(n) и такого, что в F0 есть дуга, ведущая из либо в заголовок интервала n, либо в участок вне I(n).
Отметим, что каждый участок имеет один вход и ноль или более выходов.
Пример. Пусть F0 – граф управления программы рис. 6.29. С помощью алгоритма разбиения графа на непересекающиеся интервалы, построим его разбиение на интервалы
I1 = I( 1) ={ 1}
I2 = I( 2) = { 2, 3, 4}
Из этих интервалов можно построить первый производный граф управления F1, показанный на рис. 6.30. Из F1 можно построить его интервалы
I3 = I(I1) = { I1, I2} = { 1, 2, 3, 4}
и получить предельный граф управления, также показанный на рис. 6.30.
I1
F1 |
I3 |
F2 |
|
I2
Рис. 6.30. Производная последовательность от графа F0.
Интервалами порядка 1 являются I1 и I2. Вход для I2 – это 2. Вход
для I3 – это 1. Единственный выход для I1 – это оператор S2. Единственный выход для I2 – это оператор S9. Интервалом порядка 2 явля-
ется I3 с входом 1. Интервал I3 не имеет выходов.
Продолжим теперь функции IN, OUT, TRANS и GEN на интервалы. Пусть F0, F1,…, Fn - производная последовательность от F0, где F –
166
граф управления для 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.
Эти условия иллюстрированы на рис. 6.31.
Интервал I |
s1 |
|
|
|
|
в Fi |
|
|
|
|
|
|
S2 |
|
|
|
Интервалы в Fi-1 |
|
Sk |
|
|
|
|
|
|
|
|
s |
|
|
Рис. 6.31. TRANS(Ij, sj). |
|
4)(а) GEN( , s) = GEN( ), если s - последний оператор в .
(б) GEN(I, s) - множество таких операторов d P, что существует путь без циклов I1, I2,…, Ik, состоящих исключительно из вершин в I, и
167
такая последовательность выходов 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 на рис 6.29 и F1 и F2 на рис. 6.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.
4)Для всех интервалов порядка i выполняем следующее. Пусть I
={I1, …, In} – интервал порядка i (I1, …, In – интервалы порядка i-1 или участки, если i=1). Можно считать, что эти интервалы перечислены в том порядке, в котором из них составлен интервал I в алгоритме опре-
168
деления непересекающихся интервалов. Иными словами, 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).
Пример. Применим данный алгоритм к графу управления на рис. 6.29. Для четырех участков в F0 GEN и TRANS вычисляются просто. Результаты приведены в табл. 6.6.
|
|
|
Таблица 6.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 для данного участка.
Интервал I1, состоящий из одного участка 1, имеет один выход – оператор S2. Так как пути в I1 тривиальны, то GEN(I1, S2) = {S1, S2} и TRANS(I1, S2) = .
Интервал I2 имеет один выход S9. GEN(I2, S9) = {S3, S8} и TRANS(I2, S9) = .
169
Теперь можно начать вычисление функции 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) можно показать, что если для вычисления IN(Ij) применяется шаг 4), то IN(Ij) – множе-
ство таких определений d, что существует путь из d во вход Ij, вдоль которого ни один из операторов не переопределяет переменную, определенную в d, а OUT(Ij, s) – множество таких d в s, вдоль которого ни один из операторов не переопределяет переменную, определенную в d.
170
