Статическая форма однократного назначения

редактировать

В конструкции компилятора статическая форма одиночного присваивания (часто сокращенно форма SSA или просто SSA ) - это свойство промежуточного представления (IR), которое требует, чтобы каждой переменной назначался ровно один раз, и каждая переменная должна быть определена до ее использования. Существующие переменные в исходном IR разделены на версии, новые переменные обычно обозначаются исходным именем с нижним индексом в учебниках, так что каждое определение получает свою собственную версию. В форме SSA цепочки use-def являются явными, и каждая содержит единственный элемент.

SSA был предложен Марком Н. Вегманом, а в 1988 г. Жанна Ферранте и три предыдущих исследователя из IBM разработали алгоритм, который может эффективно вычислять форму SSA.

Можно ожидать найти SSA в компиляторе для Fortran, C или C ++, тогда как в функциональном языке обычно используются компиляторы для схемы и ML, стиля передачи продолжения (CPS). SSA формально эквивалентен подмножеству CPS с хорошим поведением, за исключением нелокального потока управления, чего не происходит, когда CPS используется в качестве промежуточного представления. Таким образом, оптимизации и преобразования, сформулированные в терминах одного, немедленно применяются к другому.

Содержание
  • 1 Преимущества
  • 2 Преобразование в SSA
    • 2.1 Вычисление минимального SSA с использованием границ доминирования
  • 3 Варианты, уменьшающие количество функций Φ
    • 3.1 Сокращенный SSA
    • 3.2 Полу- сокращенный SSA
  • 4 Преобразование из формы SSA
  • 5 Расширения
  • 6 Компиляторы с использованием формы SSA
  • 7 Ссылки
    • 7.1 Примечания
    • 7.2 Общие ссылки
  • 8 Внешние ссылки
Преимущества

Основная полезность SSA заключается в том, что он одновременно упрощает и улучшает результаты различных оптимизаций компилятора за счет упрощения свойств переменных. Например, рассмотрим этот фрагмент кода:

y: = 1 y: = 2 x: = y

Люди могут видеть, что первое присваивание не требуется и что значение y, используемое в третьей строке, происходит от второго присвоения y. Программа должна будет выполнить анализ определения, чтобы определить это. Но если программа находится в форме SSA, оба из них действуют немедленно:

y1: = 1 y 2 : = 2 x 1 : = y 2

Оптимизация компилятора алгоритмы, которые либо активируются, либо сильно улучшаются за счет использования SSA, включают:

Преобразование в SSA

Преобразование обычного кода в форму SSA - это, в первую очередь, простой вопрос замены цели каждого назначения новой переменной и замены каждого использования переменной «версия» переменной , достигшая этой точки. Например, рассмотрим следующий граф потока управления :

Пример графа потока управления, до преобразования в SSA

. Изменение имени в левой части строки «x ← {\ displaystyle \ leftarrow}\ leftarrow x - 3» и изменение следующего использование xдля этого нового имени оставит программу без изменений. Это можно использовать в SSA, создав две новые переменные: xx2, каждая из которых назначается только один раз. Аналогичным образом, присвоение отличительных индексов всем другим переменным дает:

An пример графа потока управления, частично преобразованный в SSA

Ясно, к какому определению относится каждое использование, за исключением одного случая: оба использования yв нижнем блоке могут относиться к любому y1или y2, в зависимости от того, какой путь выбрал поток управления.

Чтобы решить эту проблему, в последний блок вставляется специальный оператор, называемый функцией Φ (Phi). Этот оператор сгенерирует новое определение y с именем y3путем «выбора» либо y1, либо y2, в зависимости от потока управления в прошлом.

Пример графа потока управления, полностью преобразованный в SSA

Теперь последний блок может просто использовать y3, и правильное значение будет получено в любом случае. Функция Φ для x не требуется: только одна версия x, а именно x2, достигает этого места, поэтому проблем нет (другими словами, Φ (x1,x2)=x2).

Для произвольного графа потока управления может быть трудно сказать, куда вставить Φ-функции и для каких переменных. Этот общий вопрос имеет эффективное решение, которое может быть вычислено с использованием концепции, называемой границами доминирования (см. Ниже).

Функции Φ не реализованы как машинные операции на большинстве машин. Компилятор может реализовать функцию Φ, вставляя операции «перемещения» в конец каждого блока-предшественника. В приведенном выше примере компилятор может вставить перемещение из От y1до y3в конце среднего левого блока и переход от yy3в конце среднего правого блока. Эти операции перемещения могут не закончиться в окончательном коде на основе процедура выделения регистров компилятора. Однако этот подход может не работать, когда одновременные операции предположительно производят входные данные для Φ функция, как это может происходить на машинах с широким выпуском. Как правило, у широко распространенной машины есть инструкция выбора, используемая в таких ситуациях компилятором для реализации функции Φ.

По словам Кенни Задека, функции Φ изначально назывались фальшивыми функциями, в то время как SSA разрабатывалась в IBM Research в 1980-х годах. Формальное название функции Φ было принято только тогда, когда работа была впервые опубликована в академической статье.

Вычисление минимального SSA с использованием границ доминирования

Во-первых, нам нужна концепция доминатора : мы говорим, что узел A строго доминирует над другим узлом B в потоке управления график, если невозможно достичь B, не пройдя сначала через A. Это полезно, потому что, если мы когда-нибудь дойдем до B, мы узнаем, что любой код в A запущен. Мы говорим, что A доминирует над B (B доминирует над A), если либо A строго доминирует над B, либо A = B.

Теперь мы можем определить границу доминирования : узел B находится в граница доминирования узла A, если A не строго доминирует над B, но доминирует над некоторым непосредственным предшественником B, или если узел A является непосредственным предшественником B, и, поскольку любой узел доминирует над самим собой, узел A доминирует над собой, и как Узел результата A находится на границе доминирования узла B. С точки зрения A, это узлы, в которых другие пути управления, которые не проходят через A, появляются раньше всего.

Границы доминирования фиксируют точные места, в которых нам нужны функции Φ: если узел A определяет определенную переменную, тогда это определение и это определение само по себе (или переопределения) достигнут каждого узла A. Только когда мы покидаем эти узлы и выходим на границу доминирования, мы должны учитывать другие потоки, вводя другие определения той же переменной. Более того, никакие другие функции Φ не нужны в графе потока управления, чтобы иметь дело с определениями A, и мы можем обойтись не меньшим.

Существует эффективный алгоритм для поиска границ доминирования каждого узла. Этот алгоритм был первоначально описан в Cytron et al. 1991. Также полезна глава 19 книги Эндрю Аппеля "Современная реализация компилятора на Java" (Cambridge University Press, 2002). См. Статью для более подробной информации.

Кейт Д. Купер, Тимоти Дж. Харви и Кен Кеннеди из Университета Райса описывают алгоритм в своей статье под названием «Простой, быстрый алгоритм доминирования». Алгоритм использует хорошо спроектированные структуры данных для повышения производительности. Он выражается просто следующим образом:

для каждого узла b dominance_frontier (b): = {} для каждого узла b, если количество непосредственных предшественников b ≥ 2 для каждого p inнепосредственного предшественника b runner: = p while runner ≠ idom (b) dominance_frontier (бегун): = dominance_frontier (бегун) ∪ {b} runner: = idom (бегун)

Примечание: в приведенном выше коде непосредственным предшественником узла n является любой узел, от которого управление передается узлу n, а idom (b) - узел, который сразу доминирует над узлом b (одноэлементный набор).

Варианты, которые уменьшают количество функций Φ

«Минимальный» SSA вставляет минимальное количество функций Φ, необходимых для обеспечения того, чтобы каждому имени было присвоено значение ровно один раз и что каждая ссылка (использование) имени в исходной программе все еще может относиться к уникальному имени. (Последнее требование необходимо для обеспечения того, чтобы компилятор мог записывать имя для каждого операнда в каждой операции.)

Однако некоторые из этих функций Φ могут быть мертвыми. По этой причине минимальный SSA не обязательно дает наименьшее количество Φ функций, необходимых для конкретной процедуры. Для некоторых типов анализа эти функции Φ излишни и могут привести к снижению эффективности анализа.

Сокращенная форма SSA

Сокращенная форма SSA основана на простом наблюдении: функции Φ необходимы только для переменных, которые «живы» после функции Φ. (Здесь «живое» означает, что значение используется на некотором пути, который начинается с рассматриваемой функции Φ.) Если переменная не является действующей, результат функции Φ не может быть использован, и присвоение функцией Φ не выполняется..

Построение сокращенной формы SSA использует информацию о текущих переменных на этапе вставки функции Φ, чтобы решить, нужна ли данная функция Φ. Если исходное имя переменной отсутствует в точке вставки функции Φ, функция Φ не вставляется.

Другая возможность - рассматривать сокращение как проблему устранения мертвого кода. Тогда функция Φ является активной только в том случае, если какое-либо использование во входной программе будет переписано в нее, или если она будет использоваться в качестве аргумента в другой функции Φ. При вводе формы SSA каждое использование переписывается до ближайшего определения, которое в нем доминирует. Тогда функция Φ будет считаться действующей, если это ближайшее определение, которое доминирует по крайней мере одно использование или по крайней мере один аргумент живого Φ.

Полусрезанная форма SSA

Полусрезанная форма SSA - это попытка уменьшить количество функций Φ без относительно высоких затрат на вычисление информации динамических переменных. Он основан на следующем наблюдении: если переменная никогда не становится активной при входе в базовый блок, ей никогда не нужна функция Φ. При построении SSA функции Φ для любых «блочно-локальных» переменных опускаются.

Вычисление набора локальных переменных блока является более простой и быстрой процедурой, чем полный анализ динамических переменных, что делает полу-сокращенную форму SSA более эффективной для вычислений, чем сокращенную форму SSA. С другой стороны, полуобрезанная форма SSA будет содержать больше функций Φ.

Преобразование из формы SSA

Форма SSA обычно не используется для прямого выполнения (хотя SSA можно интерпретировать), и она часто используется «поверх» другого IR, с которым остается в прямой переписке. Это может быть достигнуто путем «построения» SSA как набора функций, которые отображают части существующего IR (базовые блоки, инструкции, операнды и т. Д.) И его аналог SSA. Когда форма SSA больше не нужна, эти функции сопоставления могут быть отброшены, оставив только теперь оптимизированный IR.

Оптимизация формы SSA обычно приводит к запутанным SSA-сетям, что означает наличие инструкций Φ, не все операнды которых имеют одинаковый корневой операнд. В таких случаях для выхода из SSA используются алгоритмы выделения цвета. Наивные алгоритмы вводят копию по каждому пути предшественника, что заставляет источник другого корневого символа быть помещенным в Φ, чем место назначения Φ. Существует несколько алгоритмов выхода из SSA с меньшим количеством копий, большинство из них используют графики интерференции или некоторое их приближение для объединения копий.

Расширения

Расширения формы SSA можно разделить на две категории.

Расширения схемы переименования изменяют критерий переименования. Напомним, что форма SSA переименовывает каждую переменную, когда ей присваивается значение. Альтернативные схемы включают статическую форму одноразового использования (которая переименовывает каждую переменную в каждом операторе при ее использовании) и статическую форму единой информации (которая переименовывает каждую переменную, когда ей присваивается значение, и на границе пост-доминирования).

Расширения для конкретных функций сохраняют единственное свойство присваивания для переменных, но включают новую семантику для моделирования дополнительных функций. Некоторые специализированные расширения моделируют такие функции языка программирования высокого уровня, как массивы, объекты и указатели с псевдонимами. Другие специфичные для функций расширения моделируют низкоуровневые архитектурные особенности, такие как предположения и предсказания.

Компиляторы, использующие форму SSA

Форма SSA - относительно недавняя разработка сообщества компиляторов. Таким образом, многие старые компиляторы используют форму SSA только для некоторой части процесса компиляции или оптимизации, но большинство из них не полагаются на нее. Примеры компиляторов, которые в значительной степени полагаются на форму SSA, включают:

  • Компилятор ETH Oberon-2 был одним из первых общедоступных проектов, включающих «GSA», вариант SSA.
  • Инфраструктура компилятора LLVM использует форму SSA для всех значений скалярных регистров (всего, кроме памяти) в своем представлении первичного кода. Форма SSA удаляется только тогда, когда происходит выделение регистров, на поздних этапах процесса компиляции (часто во время компоновки).
  • Компилятор Open64 использует форму SSA в своем глобальном скалярном оптимизаторе, хотя код внесены в форму SSA раньше и изъяты из формы SSA после. Open64 использует расширения формы SSA для представления памяти в форме SSA, а также скалярных значений.
  • Начиная с версии 4 (выпущенной в апреле 2005 г.) GCC, Коллекция компиляторов GNU, широко используется SSA. Интерфейсы генерируют код «GENERIC », который затем преобразуется в код «GIMPLE » с помощью «gimplifier». Затем к форме SSA «GIMPLE» применяются высокоуровневые оптимизации. Полученный в результате оптимизированный промежуточный код затем транслируется в RTL, к которому применяются низкоуровневые оптимизации. Архитектурно-зависимые бэкенды, наконец, превращают RTL в язык ассемблера.
  • IBM с открытым исходным кодом адаптивную виртуальную машину Java, Jikes RVM, использует расширенный Array SSA, расширение SSA, которое позволяет анализировать скаляры, массивы и поля объектов в единой структуре. Анализ расширенного массива SSA включен только на максимальном уровне оптимизации, который применяется к наиболее часто выполняемым частям кода.
  • В 2002 году исследователи модифицировали IBM JikesRVM (в то время называвшуюся Jalapeño) для запуска как стандартного байт-кода Java , так и файлов классов байт-кода безопасного типа SSA (SafeTSA ) и продемонстрировали значительные преимущества в производительности при использовании байт-кода SSA.
  • Oracle 's Виртуальная машина Java HotSpot использует промежуточный язык на основе SSA в своем JIT-компиляторе.
  • Серверная часть компилятора Microsoft Visual C ++ доступна в Microsoft Visual Studio Обновление 3 2015 года использует SSA
  • Mono использует SSA в своем JIT-компиляторе под названием Mini.
  • jackcc - компилятор с открытым исходным кодом для академического набора инструкций Jackal 3.0. Он использует простой код с тремя операндами и SSA для его промежуточного представления. В качестве интересного варианта он заменяет функции Φ так называемой инструкцией SAME, которая инструктирует распределителю регистров поместить два активных диапазона в один и тот же физический регистр.
  • Хотя это и не компилятор, Boomerang декомпилятор использует форму SSA во внутреннем представлении. SSA используется для упрощения распространения выражений, идентификации параметров и возвращаемых значений, анализа сохранения и т. Д.
  • Portable.NET использует SSA в своем JIT-компиляторе.
  • libFirm промежуточное представление SSA на основе графа для компиляторы. libFirm использует форму SSA для всех значений скалярных регистров до генерации кода с помощью распределителя регистров с поддержкой SSA.
  • Компилятор Illinois Concert около 1994 года использовал вариант SSA, называемый SSU (Static Single Use), который переименовывает каждую переменную когда ему присваивается значение и в каждом условном контексте, в котором эта переменная используется; по сути, статическая единая информационная форма, упомянутая выше. Форма SSU задокументирована в докторской диссертации Джона Плевяка.
  • Компилятор COINS использует оптимизацию формы SSA, как описано здесь: http://www.is.titech.ac.jp/~sassa/coins -www-ssa / english /
  • Mozilla Firefox SpiderMonkey JavaScript-движок использует IR на основе SSA.
  • Chromium JavaScript-движок V8 реализует SSA в своей инфраструктуре компилятора Crankshaft, поскольку объявлено в декабре 2010 г.
  • PyPy использует линейное представление SSA для трассировок в своем JIT-компиляторе.
  • Виртуальная машина Dalvik Android использует SSA в своем JIT-компиляторе.
  • Новый оптимизирующий компилятор Android для Android Runtime использует SSA для своего IR.
  • Стандартный компилятор ML MLton использует SSA в одном из своих промежуточных языков.
  • LuaJIT интенсивно использует оптимизацию на основе SSA.
  • The PHP и Hack компилятор HHVM использует SSA в своем IR.
  • Компилятор R-Stream от Reservoir Labs поддерживает не-SSA (q uad list), формы SSA и SSI (Static Single Information).
  • Go (1.7: только для архитектуры x86-64; 1.8: для всех поддерживаемых архитектур).
  • SPIR-V, стандарт языка затенения для Vulkan graphics API и язык ядра для OpenCL compute API, является представлением SSA.
  • Различные драйверы Mesa через NIR, представление SSA для языков затенения.
  • WebKit использует SSA в своих JIT-компиляторах.
  • Swift определяет свою собственную форму SSA над LLVM IR, которая называется SIL (Swift Intermediate Language).
  • Erlang переписал свой компилятор в OTP 22.0, чтобы «внутренне использовать промежуточное представление, основанное на статическом одиночном назначении (SSA). " С планами по дальнейшей оптимизации, основанной на SSA в будущих выпусках.
Ссылки

Примечания

Общие ссылки

Внешние ссылки
Последняя правка сделана 2021-06-09 10:04:06
Содержание доступно по лицензии CC BY-SA 3.0 (если не указано иное).
Обратная связь: support@alphapedia.ru
Соглашение
О проекте