Custom LLVM Passes

Юлия имеет ряд пользовательских проходов LLVM. В общем, их можно классифицировать на проходы, которые необходимо выполнять для поддержания семантики Julia, и проходы, которые используют семантику Julia для оптимизации LLVM IR.

Semantic Passes

Эти проходы используются для преобразования LLVM IR в код, который может быть выполнен на ЦП. Их основная цель - позволить генерации кода выдавать более простой IR, что затем позволяет другим проходам LLVM оптимизировать общие шаблоны.

CPUFeatures

  • Имя файла: llvm-cpufeatures.cpp
  • Имя класса: CPUFeaturesPass
  • Имя опции: module(CPUFeatures)

Этот проход понижает встроенную функцию julia.cpu.have_fma.(f32|f64) до истинного или ложного значения, в зависимости от целевой архитектуры и целевых функций, присутствующих в функции. Эта встроенная функция часто используется для определения, лучше ли использовать алгоритмы, зависящие от быстрых fused multiply-add операций, чем использовать стандартные алгоритмы, не зависящие от таких инструкций.

DemoteFloat16

  • Имя файла: llvm-demote-float16.cpp
  • ClassName: DemoteFloat16Pass
  • Опция Имя function(DemoteFloat16)

Этот проход заменяет float16 операции на операции float32 на архитектурах, которые не поддерживают операции float16 на аппаратном уровне. Это делается путем вставки инструкций fpext и fptrunc вокруг любой операции float16. На архитектурах, которые поддерживают нативные операции float16, этот проход является no-op.

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. Затем этот проход ищет цепочку операций с плавающей запятой, которые формируют редукцию, и добавляет флаги быстрого математического вычисления contract и reassoc, чтобы разрешить реассоциацию (а значит, и векторизацию). Этот проход не сохраняет информацию о циклах и не гарантирует корректность вывода, поэтому он может нарушать семантику Julia неожиданными способами. Если цикл также был аннотирован ivdep, то проход помечает цикл как не имеющий зависимостей, переносимых по циклу (результирующее поведение неопределенно, если аннотация пользователя была неверной или применена к неправильному циклу).

LowerPTLS

  • Имя файла: llvm-ptls.cpp
  • Имя класса: LowerPTLSPass
  • Имя опции: module(LowerPTLSPass)

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

Если кодогенерация создает функцию с аргументом и соглашением о вызове swiftself, этот проход предполагает, что аргумент swiftself является pgcstack и заменит встроенные функции на этот аргумент. Это обеспечивает увеличение скорости на архитектурах, где медленные обращения к локальному хранилищу потоков.

RemoveAddrspaces

  • Имя файла: llvm-remove-addrspaces.cpp
  • Имя класса: RemoveAddrspacesPass
  • Имя опции: module(RemoveAddrspaces)

Этот проход переименовывает указатели из одного адресного пространства в другое адресное пространство. Это используется для удаления специфичных для Julia адресных пространств из LLVM IR.

RemoveJuliaAddrspaces

  • Имя файла: llvm-remove-addrspaces.cpp
  • Имя класса: RemoveJuliaAddrspacesPass
  • Имя опции: module(RemoveJuliaAddrspaces)

Этот проход удаляет адресные пространства, специфичные для Julia, из LLVM IR. Он в основном используется для отображения LLVM IR в менее загроможденном формате. Внутри он реализован на основе прохода RemoveAddrspaces.

Multiversioning

  • Имя файла: llvm-multiversioning.cpp
  • Имя класса: MultiVersioningPass
  • Имя модуля: module(JuliaMultiVersioning)

Этот проход выполняет модификации модуля для создания функций, оптимизированных для работы на различных архитектурах (см. sysimg.md и pkgimg.md для получения дополнительных сведений). С точки зрения реализации, он клонирует функции и применяет к ним различные специфические для целевой платформы атрибуты, чтобы позволить оптимизатору использовать такие продвинутые функции, как векторизация и планирование инструкций для этой платформы. Он также создает некоторую инфраструктуру, чтобы загрузчик изображений Julia мог выбрать соответствующую версию функции для вызова в зависимости от архитектуры, на которой работает загрузчик. Специфические для целевой платформы атрибуты контролируются флагом модуля julia.mv.specs, который во время компиляции выводится из переменной окружения JULIA_CPU_TARGET. Проход также должен быть включен, предоставив флаг модуля julia.mv.enable со значением 1.

Warning

Использование llvmcall с многоверсионностью опасно. llvmcall предоставляет доступ к функциям, которые обычно не доступны через API Julia, и поэтому обычно недоступны на всех архитектурах. Если многоверсионность включена и запрашивается генерация кода для целевой архитектуры, которая не поддерживает функцию, необходимую для выражения llvmcall, LLVM, вероятно, выдаст ошибку, скорее всего, с завершением работы и сообщением LLVM ERROR: Do not know how to split the result of this operator!.

GCInvariantVerifier

  • Имя файла: llvm-gc-invariant-verifier.cpp
  • Имя класса: GCInvariantVerifierPass
  • Имя опции: module(GCInvariantVerifier)

Этот проход используется для проверки инвариантов Julia относительно LLVM IR. Это включает в себя такие вещи, как отсутствие ptrtoint в Julia's non-integral address spaces [nislides] и существование только благословленных инструкций addrspacecast (Tracked -> Derived, 0 -> Tracked и т.д.). Он не выполняет никаких преобразований IR.

Optimization Passes

Эти проходы используются для выполнения преобразований в LLVM IR, которые LLVM не выполнит самостоятельно, например, распространение флага быстрого математического вычисления, анализ утечек и оптимизации внутренних функций, специфичных для Julia. Они используют знания о семантике Julia для выполнения этих оптимизаций.

CombineMulAdd

  • Имя файла: llvm-muladd.cpp
  • Имя класса: CombineMulAddPass
  • Имя опции: function(CombineMulAdd)

Этот проход служит для оптимизации конкретной комбинации обычного fmul с быстрым fadd в контрактный fmul с быстрым fadd. Это позже оптимизируется бэкендом в инструкцию fused multiply-add, которая может обеспечить значительно более быстрые операции за счет большего unpredictable semantics.

Note

Эта оптимизация происходит только тогда, когда fmul имеет единственное использование, которое является быстрым fadd.

AllocOpt

  • Имя файла: llvm-alloc-opt.cpp
  • Имя класса: AllocOptPass
  • Имя опции: function(AllocOpt)

Julia не имеет концепции стека программы как места для выделения изменяемых объектов. Однако выделение объектов в стеке снижает нагрузку на сборщик мусора и критически важно для компиляции под GPU. Таким образом, AllocOpt выполняет преобразование объектов из кучи в стек, которые он может доказать, что не escape текущей функции. Он также выполняет ряд других оптимизаций при выделении, таких как удаление выделений, которые никогда не используются, оптимизация вызовов typeof для только что выделенных объектов и удаление записей в выделения, которые немедленно перезаписываются. Реализация анализа побегов находится в llvm-alloc-helpers.cpp. В настоящее время этот проход не использует информацию из EscapeAnalysis.jl, хотя это может измениться в будущем.

PropagateJuliaAddrspaces

  • Имя файла: llvm-propagate-addrspaces.cpp
  • Имя класса: PropagateJuliaAddrspacesPass
  • Имя опции: function(PropagateJuliaAddrspaces)

Этот проход используется для распространения специфичных для Julia адресных пространств через операции с указателями. LLVM не разрешает вводить или удалять инструкции addrspacecast в результате оптимизаций, поэтому этот проход действует для устранения избыточных приведения адресных пространств, заменяя операции на их эквиваленты в адресном пространстве Julia. Для получения дополнительной информации об адресных пространствах Julia см. (TODO link to llvm.md).

JuliaLICM

  • Имя файла: llvm-julia-licm.cpp
  • Имя класса: JuliaLICMPass
  • Имя оптимизации: loop(JuliaLICM)

Этот проход используется для выноса специфичных для Julia встроенных функций из циклов. В частности, он выполняет следующие преобразования:

  1. Поднимите gc_preserve_begin и опустите gc_preserve_end из циклов, когда сохраняемые объекты не зависят от цикла.

    1. Поскольку объекты, сохраненные в цикле, вероятно, сохраняются на протяжении всего цикла, эта трансформация может уменьшить количество пар gc_preserve_begin/gc_preserve_end в IR. Это упрощает задачу LateLowerGCPass по определению местоположения конкретных объектов, которые сохраняются.
  2. Поднимите записи о барьерах с инвариантными объектами

    1. Здесь мы предполагаем, что объект может принадлежать только двум поколениям. Учитывая это, барьер записи должен выполняться только один раз для любой пары одного и того же объекта. Таким образом, мы можем выносить барьеры записи за пределы циклов, когда объект, к которому происходит запись, является инвариантом цикла.
  3. Поднимите выделения из циклов, когда они не покидают цикл

    1. Мы используем очень консервативное определение выхода здесь, такое же, как в AllocOptPass. Эта трансформация может уменьшить количество аллокаций в IR, даже когда аллокация полностью выходит за пределы функции.
Note

Этот пропуск необходим для сохранения MemorySSA (Short Video, Longer Video) и ScalarEvolution (Newer Slides Older Slides) анализов.

  • nislideshttps://llvm.org/devmtg/2015-02/slides/chisnall-pointers-not-int.pdf