Межпроцедурные анализ и оптимизации

Download Report

Transcript Межпроцедурные анализ и оптимизации

Межпроцедурные анализ и
оптимизации
Андрей Неволин
План
• Основные понятия
• Абстрактная интерпретация программ
• Межпроцедурное распространение
информации
• Анализ указателей и проблемы
межпроцедурного анализа
• Частичные трансферные функции (на
примере анализа указателей)
Основные понятия
Анализ vs. оптимизация
• Анализ выполняет работу по сбору сведений о
программе.
• Оптимизации осуществляют трансформацию
программы на основе данных, предоставленных
анализами.
• Некоторые анализы могут основываться частично
или полностью на сведениях, собранных другими
анализами.
Межпроцедурные анализ и
оптимизации
• Определение Мучника межпроцедурных
оптимизаций: “Interprocedural optimizations are
ones that use the calling relationships among a set of
procedures to drive optimizations in one or more of
them or in how they relate to each other”.
• Более просто: межпроцедурные анализ и
оптимизации – те, которые используют в своей
работе более одной процедуры.
Граф вызовов (1 из 3)
• Для описания потока передачи управления в
оптимизируемой программе используется граф
вызовов.
• Для программы P, состоящей из процедур p1, …,
pn, граф вызовов Gp есть набор <N, S, E, r>, где
N={p1, …, pn} – множество вершин, S –
множество меток мест передачи управления
между функциями, E – множество помеченных
ребер, r – точка входа в программу.
Граф вызовов (2 из 3)
• Ребро (элемент множества E) выглядит
следующим образом: e=<pi, sk, pj>, т.е. обозначает
вызов процедуры pj из процедуры pi, причем
место вызова определяется меткой sk
• Из определения видно, что граф вызовов в
действительности является мультиграфом (т.е. от
одного узла к другому может существовать
множество направленных ребер)
Граф вызовов (3 из 3)
пример
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
procedure f()
call g()
call g()
call h()
end
procedure g()
call h()
call i()
end
procedure h()
end
procedure i()
procedure j()
end
//j
call g()
call j()
end
f
8
h
15
i
//i
7
g
//g
//h
4
2, 3
//f
16
j
Граф активаций
Граф активаций – развернутый в дерево
граф вызовов
Граф вызовов
f
f
2
3
4
2, 3
g
7
g
8
4
h
g
h
8
7
8
7
i
h
i
h
15
16
i
16
j
15
j
16
g
15
j
g
Абстрактная интерпретация
программ
Решетка (неформально)
• Для абстрактного описания анализа потока
данных используется понятие «решетки»
• Решетка – алгебраическая структура, элементы
которой представляют абстрактные свойства
переменных, выражений и других программных
конструкций. Эти свойства не должны зависеть
от конкретных условий исполнения процедуры
Определение решетки
Решетка L состоит из множества значений и двух операций
meet и join, причем:
1.
2.
3.
4.
Для всех x, y из L существуют единственные z, w из L
такие, что x(meet)y = z и x(join)y = w
Для всех x, y из L x(meet)y = y(meet)x и x(join)y = y(join)x
(коммутативность)
Для всех x, y, z из L [x(meet)y](meet)z = x(meet)[y(meet)z]
и [x(join)y](join)z = x(join)[y(join)z] (ассоциативность).
Существуют два особых элемента L bottom и top такие,
что для любого x из L: x(meet)bottom = bottom и
x(join)top = top
Потоковая функция
•
•
•
Значения решетки используются для абстрактного
описания данных и могут представлять константы,
диапазоны, выравнивания и т.д.
Для моделирования обработки данных программой
используется потоковая функция
Таким образом, абстрактная интерпретация программы
состоит в следующем:
1.
2.
3.
В узел графа потока данных по входным дугам приходят
некоторые значения решетки;
К этим значениям применяется потоковая функция;
Результирующие значения (также принадлежащие решетке)
отправляются на выходные дуги узла.
Пример решетки
Решетка для распространения целочисленных и
булевых констант:
top соответствует неинициализированному
значению, bottom означает, что значение может
быть любым
TOP
FALSE
…
-2
-1
0
1
BOTTOM
2
…
TRUE
Пример интерпретации 1
Интерпретация выражений при
распространении констант (1 из 2)
u = v = w = top
u = v = w = top
u=1
v=2
u = 1, v = w = top
v = 2, u = w = top
meet: u = 1, v = 2, w = top
w=u+v
u = 1, v = 2, w = 3
Пример интерпретации 2
Интерпретация выражений при
распространении констант (2 из 2)
u = top
u = top
u=1
u=2
u=1
u=2
meet: u = bottom
w = 2*u
u = w = bottom
Упорядоченность решетки
• Можно показать, что операции meet и join задают
частичный порядок на решетке. Следовательно, можно
считать, что в решетке существует направление
• В предыдущих примерах значения переменных
«понижались» от top’а к bottom’y
• Это соответствует общему правилу: во время
интерпретации значения решетки могут только
понижаться (не подниматься). Иначе говоря, в процессе
интерпретации количество уже полученной информации
не должно уменьшаться.
Межпроцедурное
распространение информации
Общие замечания
• Одни и те же схемы межпроцедурного
распространения могут быть использованы
для различной информации
• Специфика информации будет отражена в
решетке и потоковых функциях
Простой способ межпроцедурного
распространения – jump-функции
READ(x)
y=3
CALL f(x, y, 2)
READ(x, y)
CALL f(x, y, 2)
Jump:
a = bottom
b = bottom
c=2
Jump:
a = bottom
b=3
c=2
meet: a = bottom, b = bottom, c =2
f (a, b, c) {
…….
}
Анализ указателей и проблемы
межпроцедурного анализа
Анализ указателей
общие сведения
• Целью анализа указателей является
определение областей памяти, к которым
можно обратиться двумя или более
способами
• Например, мы можем взять адрес некоторой
переменной в программе на C (или С++) и
обращаться к ней как по имени, так и через
указатель на нее
Анализ указателей
пример
int main() {
p
int *p;
int n;
p = &n;
n = 4;
printf(“%d\n”, *p);
}
n
4
Мотивация для анализа
указателей (1 из 2)
void func() {
int a, k;
extern int *q;
………
k = a + 5;
f(a, &k);
*q = 13;
k = a + 5;
……...
}
Здесь второе присваивание k
= a + 5 избыточно в том и
только том случае, если
присваивание через
указатель q и обращение к
функции f() не изменяют
ни k, ни a.
Мотивация для анализа
указателей (1 из 2)
Информация, предоставляемая анализом указателей
может быть использована для:
• Удаления общих подвыражений
• Частичного устранения избыточности
• Эффективного распределения регистров
• Улучшения скалярного планирования
• Раскрытия параллелизма
• Многого другого
Чувствительность к потоку
управления
Если анализ учитывает поток управления, он называется
чувствительным к потоку управления; в противном случае – не
чувствительным
Анализ, чувствительный
к потоку
Здесь p→{x}.
(p указывает на x)
p→{y}
p→{x,y}
φ-функция для p
p→{z}
...
if(cond){
p = &x;
foo(p);
} else {
p = &y;
bar(p);
}
foo1(p);
p = &z;
bar1(p);
...
Нечувствительный
к потоку
p→{x,y,z}
в каждой точке процедуры
May и must информация
May информация
показывает
какие значения
указатели могут
иметь
Must информация
содержит
значения,
которые
указатели
обязательно
должны иметь
p = &x;
q = &y;
if (test) p = q;
В конце примера q
обязательно указывает
на y, а p может
указывать на x или на y
Аппроксимация структур данных
next
next
list
list
right
tree
right
tree
left
left
array
array
Нарушения стандартов
Здесь значение указателя q копируется в
указатель p
char *p = 0;
while (p < q) {
p += 1;
}
Чувствительность к месту вызова
(контексту), нереализуемые пути
•Анализ, учитывающий
место вызова
процедуры, называется
чувствительным к
контексту. В противном
xp
случае, анализ
нечувствителен к
Call
контексту.
xp, q
Entry
xq
Call
xp, q
•Проблема последнего
– нереализуемые пути
xp, q
Exit
xp, q
Разделение контекстов в случае
чувствительности к месту вызова
xp
Entry
xq
Entry
xp
xq
Call
Call
xp
xq
Exit
xp
Exit
xq
Различение контекстов
Существует два способа различения
контекстов:
1. Аппроксимация путей вызовов
2. Использование данных, посчитанных анализом
Аппроксимация путей вызовов
В этом случае
контекст
определяется
списком процедур,
которые были
вызваны на пути к
анализируемой
main
f()
g()
h()
i()
j()
k()
Один из контекстов вызова
процедуры j(): main  f()  h() j()
Использование данных,
посчитанных анализом
• В случае анализа указателей, эффект процедуры может
значительно изменяться в зависимости от того, указывают
ли различные формальные параметры на одни и те же
области памяти
• Таким образом, для анализа указателей имеет смысл
разделять контексты вызова одной и той же процедуры не
по пути в графе вызовов, а по наличию перекрытий между
областями памяти, адресуемыми формальными
параметрами
• Метод использования данных для разделения контекстов
может использоваться и в других анализах. Например, в
случае распространения констант можно разделять
контексты по тому, какие именно параметры являются
константами (1-ый и 5-ый или 2-ой и 3-ий и т.д.)
Пример разделения контекстов на основе
данных, посчитанных анализом
f(int **p, int **q, int **r){
*p = *q;
*q = *r;
}
int x, y, z;
int *x0, *y0, *z0;
main( ){
x0 = &x; y0 = &y; z0 = &z;
if(cond1)
f(&x0, &y0, &z0); //C.1
else if (cond2)
f(&z0, &x0, &y0); //C.2
else
f(&x0, &y0, &x0); //C.3
}
• Если r и p не указывают на одну
и ту же область памяти, то q в
результате работы процедуры
будет указывать туда же, куда и r
• Если r и p могут указывать на
одну и ту же область памяти, то q
может сохранить свое значение в
результате работы f()
• Можно выделить два контекста:
вызовы C.1 и C.2 принадлежат
одному из них, а вызов C.3 –
другому
Частичные трансферные
функции (на примере анализа
указателей)
Общие сведения
• ЧТФ описывают эффект процедуры по
отношению к ее формальным параметрам (можно
считать, что глобалы передаются явно в виде
формалов) и не используют их конкретные
значения
• Эти значения скрыты с помощью абстракции
символьных параметров
• Способ разделения контекстов может быть
любым, но далее в примерах используется метод
на основе данных, полученных анализом
Пример использования
абстрактных параметров
f(int **p, int **q, int **r){
*p = *q;
*q = *r;
}
bx – абстрактные блоки, которые в различных контекстах могут
соответствовать различным областям памяти. ЧТФ устанавливает
соотношения между этими блоками. Далее результат может быть
применен к конкретному контексту. Ниже приведены начальные ЧТФ
для двух разных контекстов.
p
b0
(x0)
int x, y, z;
int *x0, *y0, *z0;
main( ){
x0 = &x; y0 = &y; z0 = &z;
if(cond1)
f(&x0, &y0, &z0); //C.1
else if (cond2)
f(&z0, &x0, &y0); //C.2
else
f(&x0, &y0, &x0); //C.3
}
q
b1
(y0)
r
b3
(z0)
b2
(y)
b4
(z)
p
b0
r
q
(x0)
b2
(y0)
b1
(x)
b3
(y)
ЧТФ, применимая
для вызовов C.1 и
C.2. Конкретные
значения в скобках
указаны для вызова
C.1
ЧТФ, применимая
для вызова C3. p и q
могут указывать на
одну и ту же область
памяти, которой
соответствует блок
b0
Концептуальная схема
проведения анализа на базе ЧТФ
1. Анализ рекурсивно спускается по
3.
мультиграфу вызовов, подсчитывая
необходимую информацию для
каждого контекста
2. Как только встречается вызов
процедуры, мы применяем ЧТФ,
4.
чтобы определить эффект этого
вызова
proc1
call proc2
proc2
call proc3
При этом, если ЧТФ уже была
подсчитана для вызываемой
процедуры и рассматриваемого
контекста, мы применяем
существующую ЧТФ
В противном случае, начинаем
анализировать вызываемую
процедуру, чтобы получить
новую ЧТФ
proc3
Построение points-to функции
p
b0
p
(x0)
q
b1
(y0)
r
b3
(z0)
(x0)
b2
b4
b1
(y0)
r
(z)
b3
(z0)
b2
(y)
b4
Для вызова C.1:
x0{y}, y0{z}, z0{z}
Для вызова С.2:
z0{x}, x0{y}, y0{y}
(z)
p
b0
q
q
(y)
p
r
b0
(x0)
b2
(y0)
b1
b0
(x)
r
b3
q
(x0)
b2
(y0)
(y)
x0
b1
(x)
Для вызова C.3:
x0{x,y}, y0{x,y}
b3
(y)
x
y0
y
z0
z
Объединение
результатов в функции
main()
Литература
1. Muchnick S. Compiler design and implementation.
San Francisco, California: Morgan Kaufmann
Publishers, 1997.
2. Wilson R.P. Efficient, Context-sensitive pointer
analysis for C programs. A dissertation for the
degree of doctor of philosophy. 1997.
3. Andersen O. Program Analysis and Specialization
for the C Programming Language. PhD. thesis,
DIKU report 94/19, 1994.
La finis