В теории компиляторов достигающее определение для данной инструкции — это более ранняя инструкция, целевая переменная которой может достичь (быть назначена) данной без промежуточного назначения. Например, в следующем коде:
d1 : у := 3d2 : х := у
d1
является идущим определением для d2
. В следующем примере, однако:
d1 : у := 3d2 : у := 4d3 : х := у
d1
больше не является определением достижения для d3
, поскольку d2
убивает его достижение: значение, определенное в , d1
больше недоступно и не может достичь d3
.
Аналогично названные определения достижения — это анализ потока данных , который статически определяет, какие определения могут достичь заданной точки в коде. Из-за своей простоты он часто используется как канонический пример анализа потока данных в учебниках. Используемый оператор слияния потока данных — это объединение множеств, а анализ — прямой поток. Определения достижения используются для вычисления цепочек использования-определений .
Уравнения потока данных, используемые для данного базового блока при достижении определений, следующие:
Другими словами, набор достигающих определений, входящих в , — это все достигающие определения из предшественников . состоит из всех базовых блоков, которые идут до этого в графе потока управления . Достигающие определения, выходящие из , — это все достигающие определения его предшественников за вычетом тех достигающих определений, переменная которых уничтожается , плюс любые новые определения, сгенерированные внутри .
Для общей инструкции мы определяем наборы и следующим образом:
где — множество всех определений, которые присваиваются переменной . Здесь — уникальная метка, прикрепленная к инструкции присваивания; таким образом, область значений при достижении определений — это эти метки инструкций.
Достижение определения обычно рассчитывается с использованием итеративного алгоритма рабочего списка.
Вход: граф потока управления CFG = (Узлы, Ребра, Вход, Выход)
// Инициализация для всех узлов CFG n в N , OUT [ n ] = emptyset ; // можно оптимизировать с помощью OUT[n] = GEN[n]; // помещаем все узлы в измененный набор // N — все узлы в графе, Changed = N ; // Итерация while ( Changed != emptyset ) { выбрать узел n в Changed ; // удалить его из измененного набора Changed = Changed - { n } ; // инициализируем IN[n] как пустой IN [ n ] = emptyset ; // вычислить IN[n] из OUT[p] предшественников для всех узлов p в предшественниках ( n ) IN [ n ] = IN [ n ] Union OUT [ p ]; oldout = OUT [ n ]; // сохраняем старый OUT[n] // обновляем OUT[n] с помощью передаточной функции f_n() OUT [ n ] = GEN [ n ] Union ( IN [ n ] - KILL [ n ]); // есть ли изменения в OUT[n] по сравнению с предыдущим значением? if ( OUT [ n ] changed ) // сравнить oldout с OUT[n] { // если да, поместить всех последователей n в измененный набор для всех узлов s в successors ( n ) Changed = Changed U { s }; } }