Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Теория языков программирования методов трансляции

..pdf
Скачиваний:
19
Добавлен:
05.02.2023
Размер:
1.62 Mб
Скачать

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