Пользовательские проходы LLVM
Семантические проходы
Эти проходы используются для преобразования IR LLVM в код, который можно запускать на ЦП. Их основное назначение — создание при генерации кода более простых представлений IR, за счет чего в других проходах LLVM будет выполняться оптимизация распространенных шаблонов.
CPUFeatures
-
Имя файла:
llvm-cpufeatures.cpp
-
Имя класса:
CPUFeaturesPass
-
Имя оптимизации:
module(CPUFeatures)
Этот проход снижает значение внутренней функции julia.cpu.have_fma.(f32|f64)
до true или false в зависимости от целевой архитектуры и целевых особенностей, присутствующих в функции. Эта внутренняя функция часто используется для определения того, насколько использование алгоритмов, зависящих от быстрых операций объединенного умножения-сложения, лучше, чем использование стандартных алгоритмов, не зависящих от таких инструкций.
DemoteFloat16
-
Имя файла:
llvm-demote-float16.cpp
-
Имя класса:
DemoteFloat16Pass
-
Имя оптимизации:
function(DemoteFloat16)
Этот проход заменяет операции float16 операциями float32 в архитектурах, изначально не поддерживающих операции float16. Для этого нужно вставить инструкции fpext
и fptrunc
вокруг любой операции float16. В архитектурах, поддерживающих собственные операции float16, этот проход не работает.
LateGCLowering
-
Имя файла:
llvm-late-gc-lowering.cpp
-
Имя класса:
LateLowerGCPass
-
Имя оптимизации:
function(LateLowerGCFrame)
Этот проход выполняет большую часть работы с корнями сборки мусора, необходимой для отслеживания указателей между точками безопасности сборки мусора. Он также снижает уровень нескольких внутренних функций до соответствующего преобразования инструкции и может нарушать установленные ранее нецелочисленные инварианты (pointer_from_objref
здесь понижается до инструкции ptrtoint
). Этот проход обычно длится дольше всех пользовательских проходов Julia из-за алгоритма потока данных, минимизирующего количество объектов, находящихся в любой безопасной точке.
FinalGCLowering
-
Имя файла:
llvm-final-gc-lowering.cpp
-
Имя класса:
FinalLowerGCPass
-
Имя оптимизации:
module(FinalLowerGC)
Этот проход снижает уровень нескольких последних внутренних функций до их окончательного варианта с нацеливанием на функции в библиотеке libjulia
. Отделение этого прохода от LateGCLowering
позволяет другим бэкендам (компиляция GPU) предоставлять собственные понижения для этих внутренних функций, чтобы конвейер Julia можно было использовать и на этих бэкендах.
LowerHandlers
-
Имя файла:
llvm-lower-handlers.cpp
-
Имя класса:
LowerExcHandlersPass
-
Имя оптимизации:
function(LowerExcHandlers)
Этот проход снижает уровень внутренних функций обработки исключений до вызовов функций среды выполнения, которые действительно вызываются при обработке исключений.
RemoveNI
-
Имя файла:
llvm-remove-ni.cpp
-
Имя класса:
RemoveNIPass
-
Имя оптимизации:
module(RemoveNI)
Эта передача удаляет нецелочисленные адресные пространства из строки datalayout модуля. В этом случае бэкенд может снижать уровень пользовательских адресных пространств Julia непосредственно в машинный код без дорогостоящего переписывания каждой операции с указателем на адресное пространство 0.
SIMDLoop
-
Имя файла:
llvm-simdloop.cpp
-
Имя класса:
LowerSIMDLoopPass
-
Имя оптимизации:
loop(LowerSIMDLoop)
Этот проход выступает в качестве основного движущего фактора аннотации @simd
. Генерация кода вставляет маркер !llvm.loopid
в заднюю ветвь цикла, который в этом проходе используется для идентификации циклов, которые изначально были помечены @simd
. Затем этот проход ищет цепочку операций с плавающей запятой, которые образуют reduce, и добавляет флаги contract
и reassoc
быстрой математики, чтобы разрешить выполнение повторной связи (и, следовательно, векторизации). Этот проход не сохраняет ни информацию о цикле, ни корректность выводов, поэтому может неожиданным образом нарушать семантику Julia. Если цикл также был аннотирован ivdep
, то проход помечает его как не имеющий циклически порожденных зависимостей (результирующее поведение будет неопределенным, если пользовательская аннотация была неверной или применена не к тому циклу).
LowerPTLS
-
Имя файла:
llvm-ptls.cpp
-
Имя класса:
LowerPTLSPass
-
Имя оптимизации:
module(LowerPTLSPass)
Этот проход снижает уровень локальных для потока внутренних функций Julia до инструкций сборки. При сборке мусора и планирования многопоточных задач Julia опирается на локальное для потока хранилище. При компиляции кода для образов системы и образов пакетов этот проход заменяет вызовы внутренних функций с загрузками из глобальных переменных, которые инициализируются во время загрузки.
Если при генерации кода создается функция с аргументом swiftself
и соглашением о вызове, этот проход предполагает, что аргумент swiftself
является pgcstack, и заменит внутреннюю функцию с этим аргументом. Это позволяет ускорить работу в архитектурах с медленным доступом к локальному для потоков хранилищу.
RemoveAddrspaces
-
Имя файла:
llvm-remove-addrspaces.cpp
-
Имя класса:
RemoveAddrspacesPass
-
Имя оптимизации:
module(RemoveAddrspaces)
Этот проход переименовывает указатели в одном адресном пространстве в другое адресное пространство. Это используется для удаления из IR LLVM относящихся к Julia адресных пространств.
RemoveJuliaAddrspaces
-
Имя файла:
llvm-remove-addrspaces.cpp
-
Имя класса:
RemoveJuliaAddrspacesPass
-
Имя оптимизации:
module(RemoveJuliaAddrspaces)
Этот проход удаляет адресные пространства, специфичные для Julia, из IR LLVM. В основном он используется для отображения IR LLVM в более простом формате. Внутренним образом этот проход реализован на основе прохода RemoveAddrspaces.
Множественное управление версиями
-
Имя файла:
llvm-multiversioning.cpp
-
Имя класса:
MultiVersioningPass
-
Имя оптимизации:
module(JuliaMultiVersioning)
Этот проход вносит изменения в модуль для создания функций, оптимизированных для работы в разных архитектурах (подробнее см. в sysimg.md и pkgimg.md). С точки зрения реализации он клонирует функции и применяет к ним различные атрибуты, специфичные для конкретной платформы, чтобы оптимизатор мог использовать расширенные возможности, такие как векторизация и планирование инструкций для данной платформы. Он также создает определенную инфраструктуру, позволяющую загрузчику образов Julia выбирать подходящую версию функции для вызова в зависимости от архитектуры, на которой работает этот загрузчик. Атрибутами, специфичными для целевого объекта, управляет флаг модуля julia.mv.specs
, который при компиляции извлекается из переменной среды JULIA_CPU_TARGET
. Для активации прохода необходимо установить флаг модуля julia.mv.enable
со значением 1.
Использовать |
GCInvariantVerifier
-
Имя файла:
llvm-gc-invariant-verifier.cpp
-
Имя класса:
GCInvariantVerifierPass
-
Имя оптимизации:
module(GCInvariantVerifier)
Этот проход используется для проверки инвариантов Julia относительно IR LLVM. Сюда входят, например несуществование ptrtoint
в нецелочисленных адресных пространствах Julia [1] и существование только одобренных инструкций nislides (Tracked -> Derived, 0 -> Tracked и т. д.). Он не выполняет никаких преобразований в IR.
Проходы оптимизаций
Эти проходы используются для выполнения преобразований в IR LLVM, которые LLVM не будет осуществлять самостоятельно, например для быстрого распространения математических флагов, escape-анализа и оптимизации внутренних функций, специфичных для Julia. Для выполнения этих оптимизаций они используют знания о семантике Julia.
CombineMulAdd
-
Имя файла:
llvm-muladd.cpp
-
Имя класса:
CombineMulAddPass
-
Имя оптимизации:
function(CombineMulAdd)
Этот проход служит для оптимизации конкретного сочетания обычной функции fmul
с быстрой функцией fadd
в сжатую функцию fmul
с быстрой функцией fadd
. Впоследствии бэкенд оптимизирует этот вариант в инструкцию объединенного умножения-сложения для значительного ускорения операций ценой более непредсказуемой семантики.
Эта оптимизация происходит только в том случае, если |
AllocOpt
-
Имя файла:
llvm-alloc-opt.cpp
-
Имя класса:
AllocOptPass
-
Имя оптимизации:
function(AllocOpt)
В Julia нет понятия программного стека как места для выделения изменяемых объектов. Однако выделение объектов в стеке снижает нагрузку на сборку мусора и очень важно для компиляции на графических процессорах. Таким образом, AllocOpt
выполняет преобразование из кучи в стек для объектов, которые, как он может доказать, не выходят из текущей функции. Он также выполняет ряд других оптимизаций выделений, например удаляет выделения, которые никогда не используются, оптимизирует вызовы typeof к только что выделенным объектам и удаляет сохранения в выделениях, которые сразу же перезаписываются. Реализация escape-анализа находится в файле llvm-alloc-helpers.cpp
. Сейчас этот проход не использует информацию из EscapeAnalysis.jl
, но эта ситуация может измениться в будущем.
PropagateJuliaAddrspaces
-
Имя файла:
llvm-propagate-addrspaces.cpp
-
Имя класса:
PropagateJuliaAddrspacesPass
-
Имя оптимизации:
function(PropagateJuliaAddrspaces)
Этот проход используется для распространения относящихся к Julia адресных пространств в ходе операций с указателями. LLVM не может вводить или удалять инструкции addrspacecast с помощью оптимизаций, поэтому этот проход служит для устранения избыточных инструкций addrspacecast, заменяя операции их аналогом в адресном пространстве Julia. Дополнительные сведения об адресных пространствах Julia см. (ссылка TODO на llvm.md).
JuliaLICM
-
Имя файла:
llvm-julia-licm.cpp
-
Имя класса:
JuliaLICMPass
-
Имя оптимизации:
loop(JuliaLICM)
Этот проход используется для вывода относящихся к Julia внутренних функций из циклов. В частности, он выполняет следующие преобразования.
-
Вывод
gc_preserve_begin
иgc_preserve_end
из циклов, если сохраняемые объекты инвариантны к циклам.-
Поскольку объекты, сохраняемые в цикле, скорее всего, сохраняются на протяжении всего цикла, это преобразование может уменьшить количество пар
gc_preserve_begin
/gc_preserve_end
в IR. ТакLateLowerGCPass
может легко определять места хранения конкретных объектов.
-
-
Вывод барьеров записи с инвариантными объектами.
-
Здесь мы предполагаем, что существует только два поколения, которым может принадлежать объект. Учитывая этот момент, барьер записи должен выполняться только один раз для любой пары одинаковых объектов. Таким образом, мы можем вывести барьеры записи из циклов, если объект, в который производится запись, является инвариантным к циклу.
-
-
Вывод распределений из циклов, если они не выходят из цикла.
-
Здесь мы используем очень консервативное определение выхода, такое же, как в
AllocOptPass
. Это преобразование позволяет сократить количество выделений в IR, даже если выделение вообще выходит за пределы функции.
-
Этот проход необходим для сохранения анализов LLVM MemorySSA (короткий видеоролик, длинный видеоролик) и ScalarEvolution (новые слайды старые слайды). |