API reference

The API is not exported; use qualified names (JuliaIR.register!).

Passes

JuliaIR.register! — Function
JuliaIR.register!(pb::PassBuilder; fp16=:auto, bf16=:auto, gc_barrier=:stock,
                  options=Dict())

Register all ports and pipeline marker passes with pb for use by name (see JuliaIR.build_pipeline!). The policy keywords supply defaults for passes that do not specify them; see JuliaIR.Dialect.

options maps pass names to default constructor keywords, e.g. Dict("jl-cpu-features" => (; target_cpu="haswell")).

source
JuliaIR.passes — Function
JuliaIR.passes()

The names of the passes of this package, as used in pipelines.

source
JuliaIR.RemoveNIPass — Function
JuliaIR.RemoveNIPass()

Remove the non-integral address spaces from the data layout of a module (jl-remove-ni in a pipeline). Needed before code generation by back-ends that don't support non-integral pointers.

source
JuliaIR.AllocOptPass — Function
JuliaIR.AllocOptPass()

Remove Julia GC allocations whose object does not escape, or move them to the stack, whole or split into their fields (jl-alloc-opt in a pipeline). From Julia 1.15 on, these include the allocations of Memory (julia.gc_alloc_memory) whose size is a constant that fits a GC pool; on the stack, their header is initialized like the allocation would, and loads of their data pointer are forwarded when nothing else may write it.

source
JuliaIR.PropagateAddrspacesPass — Function
JuliaIR.PropagateAddrspacesPass()

Make loads, stores, atomics and memory intrinsics that access memory through one of Julia's special address spaces use the untracked pointer the address was derived from, when that pointer can be recovered through address space casts, GEPs, phis and selects (jl-propagate-addrspaces in a pipeline). Run dce or another cleanup afterwards to remove the instructions that became unused. Like Julia's C++ pass, it is required, so it also runs on optnone functions.

source
JuliaIR.DemoteFloat16Pass — Function
JuliaIR.DemoteFloat16Pass(; fp16=:auto, bf16=:auto, jit_features=true)

Perform floating-point arithmetic on half (fp16) and bfloat (bf16) values in float, truncating every result, so that the IEEE semantics of the 16-bit types are preserved on targets that don't support them natively (jl-demote-float16 in a pipeline). Whether a type is demoted is decided by the policies: :demote and :keep apply to every function, :auto follows the rule of the Julia version, which reads the function's target-features (Julia 1.10, 1.11) or julia.hasfp16/julia.hasbf16 attributes (1.12 and later; without them, half is demoted only on PowerPC and bfloat is kept). Operations with all fast-math flags are left alone.

For a function without target-features, Julia 1.10 and 1.11 use the features of Julia's JIT, as the C++ pass does in Julia's pipeline: half is kept if the JIT compiles Float16 arithmetic natively and the module is for its architecture. That only applies to IR of the running Julia version; jit_features=false (no-jit-features in a pipeline) ignores the JIT, like the C++ pass in opt.

Meant to run late in the pipeline, not followed by instcombine (which would fold the conversions away); gvn can clean up redundant conversions. Like Julia's C++ pass, it is required, so it also runs on optnone functions, whose semantics it affects.

source
JuliaIR.GCInvariantVerifierPass — Function
JuliaIR.GCInvariantVerifierPass(; strong=false)

Check that functions obey the invariants of Julia's GC address spaces (jl-gc-invariant-verifier in a pipeline, jl-gc-invariant-verifier<strong> for strong=true), and throw a JuliaIR.GCInvariantError listing every violation otherwise. strong also rejects GEPs of tracked pointers, which Julia's code generator never emits but the optimizer may introduce.

source
JuliaIR.GCInvariantError — Type
JuliaIR.GCInvariantError

The error thrown by GCInvariantVerifier (jl-gc-invariant-verifier) when a function violates GC invariants. violations lists every violation found in the function, in the order of its instructions, as JuliaIR.GCInvariantViolations.

source
JuliaIR.GCInvariantViolation — Type
JuliaIR.GCInvariantViolation

One violation of a GC invariant found by GCInvariantVerifier: the message that says which invariant, and the offending instruction, as text.

source
JuliaIR.LateLowerGCFramePass — Function
JuliaIR.LateLowerGCFramePass(; gc_barrier=:stock, task_gcstack=nothing,
                             task_ptls=nothing, tls_safepoint=nothing,
                             tls_gc_state=nothing)

Julia's GC root placement (jl-late-lower-gc-frame in a pipeline, LateLowerGCFrame in Julia's pipelines): roots the values in Julia's tracked address spaces that are live at a safepoint in a GC frame, and lowers Julia's GC intrinsics, allocations, write barriers and operand bundles for its runtime. See the documentation for what it does on each Julia version.

Lowering allocations and GC state transitions needs the layout of the runtime's task and thread-local state, which is taken from the running Julia (see JuliaIR.runtime_layout) only for functions that need it. The other keyword arguments give these offsets explicitly.

source
JuliaIR.FinalLowerGCPass — Function
JuliaIR.FinalLowerGCPass(; kind=:auto)

Julia's final GC lowering (jl-final-lower-gc in a pipeline, FinalLowerGC in Julia's pipelines): lowers the GC frame intrinsics that LateLowerGCFrame emits, allocations, root queuing and safepoints to code for Julia's runtime, and from Julia 1.14 on also the write barriers. See the documentation for what it does on each Julia version.

The pass is both a module pass and a function pass; kind selects one (:module, :function), by default the one of the running Julia's pipeline (a module pass on Julia 1.10, a function pass later).

Allocations of a constant size are lowered to the pool of their size class in the running Julia's GC (see JuliaIR.gc_pool_class).

source
JuliaIR.DeclarationError — Type
JuliaIR.DeclarationError

The error thrown by a pass that needs to call a function of Julia's runtime (or one of Julia's intrinsics) when the module already has a function of that name with another type: calling it would create invalid IR. The module is not changed by the pass before the error.

source
JuliaIR.CombineMulAddPass — Function
JuliaIR.CombineMulAddPass()

Allow the back-end to fuse a multiplication and the addition or subtraction it feeds into a multiply-add (jl-combine-muladd in a pipeline): when an fadd or fsub has the contract fast-math flag and one of its operands is an fmul without other uses, that fmul is marked contract too (if both operands qualify, only the first). Julia 1.12 removed the pass, so it does nothing for the IR of Julia 1.12 and later. Like Julia's C++ pass, it is skipped on optnone functions.

source
JuliaIR.UnsupportedInputError — Type
JuliaIR.UnsupportedInputError

The error thrown by LateGCCleanup (jl-late-gc-cleanup) when a function contains constructs that need Julia's runtime or a GC frame (see the input contract in the documentation). constructs lists all of them, as JuliaIR.UnsupportedConstructs, in the order of the function's instructions. The function is not changed.

source
JuliaIR.UnsupportedConstruct — Type
JuliaIR.UnsupportedConstruct

A construct that LateGCCleanup (jl-late-gc-cleanup) does not support: the message that names it, the offending instruction (or argument) as text, and its source location (file:line, or empty without debug information).

source
JuliaIR.LowerExcHandlersPass — Function
JuliaIR.LowerExcHandlersPass(; handler_size=nothing, handler_align=nothing,
                             setjmp=nothing, setjmp_savemask=nothing)

Lower Julia's exception handlers (julia.except_enter and the matching jl_pop_handler calls) to calls of the runtime's jl_enter_handler and setjmp on stack buffers (jl-lower-exc-handlers in a pipeline). Only Julia 1.10 to 1.12 emit these; for later dialects the pass does nothing.

The lowering depends on the Julia runtime the code will run with (the size of its exception handler structure and the name of its setjmp), which is taken from the running Julia (see JuliaIR.runtime_layout), only for functions that have handlers; handler_size, handler_align and setjmp give these facts explicitly. Like Julia's pass, setjmp is called with a savemask argument unless the module's triple is for Windows; setjmp_savemask (true or false, setjmp-savemask=yes or no in a pipeline) overrides that.

source
JuliaIR.HandlerNestingError — Type
JuliaIR.HandlerNestingError

The error thrown by LowerExcHandlers (jl-lower-exc-handlers) when a function leaves more exception handlers than it entered, or leaves a number of them that isn't a constant. The function is not changed.

source
JuliaIR.LowerSIMDLoopPass — Function
JuliaIR.LowerSIMDLoopPass()

Lower the loop markers of Julia's @simd (jl-lower-simdloop in a pipeline; a loop pass, for loop(...) and loop-mssa(...) pipelines). Julia marks the loop ID of a loop with julia.simdloop and, for @simd ivdep, julia.ivdep. The pass removes these markers, tags the memory accesses of ivdep loops with !llvm.mem.parallel_loop_access, and allows reassociating and contracting the floating-point reductions of simd loops. For the IR of Julia 1.12 and later, it also marks the multiplications that only feed an addition of a reduction as contract. Like Julia's C++ pass, it is skipped on optnone functions.

source
JuliaIR.JuliaLICMPass — Function
JuliaIR.JuliaLICMPass(; gc_barrier=:stock)

Hoist Julia's GC operations out of loops (jl-licm in a pipeline). This loop pass supports loop(...) and loop-mssa(...) pipelines and updates MemorySSA. It moves the llvm.julia.gc_preserve_begin calls whose operands are defined before the loop (with their llvm.julia.gc_preserve_end calls sunk to the exits of the loop), write barriers whose operands are or can be made loop invariant, and the GC allocations that don't escape the loop. Hoisted allocations are cleared in the preheader. For Julia 1.14 and later, a field write barrier whose slots vary in the loop is hoisted as an object barrier. The gc_barrier option is the GC's barrier configuration from Julia 1.14 on: no barrier is hoisted with snapshot barriers, and no field barrier demoted with field-precise ones. Like Julia's C++ pass, it is skipped on optnone functions.

source
JuliaIR.ExpandAtomicModifyPass — Function
JuliaIR.ExpandAtomicModifyPass()

Lower the julia.atomicmodify.* calls that Julia emits for @atomic modify operations (jl-expand-atomic-modify in a pipeline). Such a call atomically replaces the value at a pointer by the result of a function of the old value, and returns the old and the new value. When that function is a single operation that atomicrmw supports (integer and floating-point addition and subtraction, bitwise operations, minimum and maximum, saturating subtraction, or exchange when it ignores the old value), it is inlined and turned into that atomicrmw, also when it reaches the operation through up to three calls that pass the old value on; otherwise the call to the function is wrapped in a compare-exchange loop.

Julia 1.13 introduced these calls, so the pass does nothing for the IR of earlier versions. Like Julia's C++ pass, it is skipped on optnone functions.

source
JuliaIR.AtomicModifyError — Type
JuliaIR.AtomicModifyError

The error thrown by ExpandAtomicModify (jl-expand-atomic-modify) for a julia.atomicmodify call it can't lower (where Julia's C++ pass crashes or emits invalid IR): its operation is not a function, its values are not integers, floating-point numbers or pointers, its ordering or synchronization scope is not a constant or the ordering is invalid, it needs a compare-exchange loop but its pointer has no alignment, its operation returns the old value unchanged for a value that is not an integer, or the inlined operation no longer corresponds to the atomicrmw it was matched to (e.g., because inlining it into a strictfp function made it a constrained floating-point intrinsic). The function is left unchanged by the errors of the first three kinds, which are checked before any call is lowered.

source
JuliaIR.CancellationLoweringPass — Function
JuliaIR.CancellationLoweringPass(; task_gcstack=nothing, task_ptls=nothing,
                                 task_eh=nothing, task_reset_ctx=nothing,
                                 tls_safepoint=nothing, reset_ctx_size=nothing,
                                 reset_ctx_align=nothing, reset_ctx_gcstack=nothing,
                                 reset_ctx_eh=nothing, reset_ctx_mctx=nothing,
                                 setjmp=nothing)

Lower the cancellation points (julia.cancellation_point() calls, from Base.@cancel_check) that Julia 1.14 and later emit into reset regions (jl-cancellation-lowering in a pipeline, CancellationLowering in Julia's pipeline): at a point, the function publishes a stack buffer in its task, filled by setjmp, to which the runtime can jump back to deliver a cancellation; the pass closes the region again before every operation such a jump could tear, and before returning. See the documentation for the details.

Julia releases up to 1.13 don't have cancellation points, so for their IR the pass does nothing. Like Julia's pass, it also runs on optnone functions.

The lowering needs the layout of the runtime's task and reset context and the name of its setjmp, which are taken from the running Julia (see JuliaIR.runtime_layout) only for functions that need them; the keyword arguments give these facts explicitly.

source
JuliaIR.CancellationLoweringError — Type
JuliaIR.CancellationLoweringError

The error thrown by CancellationLowering (jl-cancellation-lowering) for a cancellation point it can't lower: one in a function without a GC stack (a julia.get_pgcstack call in the entry block, or an argument with the "gcstack" attribute), or one that is invoked. The function is not changed.

source
JuliaIR.RemoveJuliaAddrspacesPass — Function
JuliaIR.RemoveJuliaAddrspacesPass()

Remove Julia's address spaces from a module (jl-remove-addrspaces in a pipeline): pointers in address spaces 10 to 13 (tracked, derived, callee-rooted and loaded pointers) become pointers in address space 0, while other address spaces are kept. Address space casts that become casts to the same address space are removed, and intrinsics are renamed after their new types (llvm.memcpy.p10.p10.i64 becomes llvm.memcpy.p0.p0.i64).

The pass recreates every global variable, alias and function of the module, and every identified struct type that has a body; the old struct types are renamed <name>.bad, also for other modules of the same context. References to the global values of the module taken before the pass are invalid afterwards: look them up by name again.

source
JuliaIR.RemoveAddrspacesPass — Function
JuliaIR.RemoveAddrspacesPass([asmap])

Map the address spaces of the pointer types in a module (jl-remove-all-addrspaces in a pipeline, which maps every address space to 0). asmap is a function that takes an address space (an Int) and returns the address space to use instead; without it, every address space becomes 0. Otherwise the pass works like JuliaIR.RemoveJuliaAddrspacesPass.

source
JuliaIR.LowerPTLSPass — Function
JuliaIR.LowerPTLSPass(; imaging=false, tls_getters=false, facts...)

Julia's lowering of the GC stack getters (jl-lower-ptls in a pipeline, LowerPTLSPass in Julia's pipelines): calls of julia.get_pgcstack and julia.get_pgcstack_or_new become the code that finds the current task's GC stack, and C-callable functions (those that call julia.get_pgcstack_or_new) adopt foreign threads and switch the GC state (on entry, and back at the returns unless the function returns tracked pointers).

In JIT mode (the default), the code reads the GC stack through the thread pointer, at the running process's jl_tls_offset, or calls the runtime's getter (at its address in the process up to Julia 1.13, through the symbol jl_get_pgcstack_resolved from 1.14 on, where tls_getters=true always calls it). The result only works in the process that compiled it. With imaging=true, it loads the getter, and the TLS offset where the running Julia uses ELF TLS, from variables of the system image that its loader fills in.

The other keyword arguments give the runtime facts explicitly (see JuliaIR.runtime_layout): the task and thread-local state offsets, tls_elf_support, tls_offset (-1 for "can't be read from the thread pointer"), pgcstack_getter and pgcstack_key. By default, they are read from the running Julia when a module needs them.

source
JuliaIR.LowerPTLSError — Type
JuliaIR.LowerPTLSError(message)

LowerPTLS can't lower a module: a getter is used other than as the called function of a call (e.g. by an invoke or as a value), a symbol it needs to declare exists with an incompatible definition, a julia.get_pgcstack_or_new call doesn't dominate the returns whose GC state it has to restore, or the thread pointer of the target can't be read. The module is left unchanged.

source
JuliaIR.CPUFeaturesPass — Function
JuliaIR.CPUFeaturesPass(; target_cpu=nothing, target_features=nothing,
                        target_machine=nothing)

Fold the intrinsics that ask about the CPU the code will run on to constants (jl-cpu-features in a pipeline): julia.cpu.have_fma.<type> (Core.Intrinsics.have_fma) and, on Julia 1.14 and later, julia.cpu.supports.<features> (Base.@cpu_supports). The answer is decided per calling function, by the target it is compiled for. How depends on the Julia version, like Julia's C++ pass:

  • Up to Julia 1.13, the function's target-features attribute is searched for features that provide FMA (+fma or +fma4 on x86, +vfp4 or +vfp4sp on 32-bit ARM; AArch64 always has FMA, other architectures never). The first one found decides, and implied features don't count. Without the attribute, target_features is searched, or the feature string of target_machine; without either, the answer is the one Julia gives for its own JIT. The CPU is never consulted, so target_cpu has no effect.
  • On Julia 1.14 and later, LLVM's tables for the target are asked whether the function's CPU and features enable the features (also through implications, and the last setting of a feature wins). They are the function's target-cpu and target-features attributes, each where present, and otherwise target_cpu and target_features, or those of target_machine, which Julia's pipeline gives the C++ pass. Without any of these, the generic CPU is used, as when the C++ pass runs by name (e.g. in opt): to use the JIT's target like Julia's pipeline does, pass the cpu and features of JuliaIR.jit_target. The answer is false for triples whose target isn't registered with LLVM. A module without a triple is taken to be for the triple of target_machine, or for the JIT's when target_cpu or target_features is given. Names of features are LLVM's, so the answers can depend on the version of LLVM.

The options can't be given in a pipeline's text (feature strings contain commas); with jlopt, they are its -mcpu and -mattr flags. target_machine can't be combined with the other two.

Intrinsics that aren't called, are passed as arguments or are called with invoke make the pass throw a JuliaIR.CPUFeaturesError before it changes the module. Like Julia's C++ pass, it is required, so it also runs on optnone functions.

source
JuliaIR.CPUFeaturesError — Type
JuliaIR.CPUFeaturesError(msg)

The intrinsics that CPUFeatures folds are used in a way that it doesn't support: not called (e.g. stored in a global), passed as an argument, called with invoke, or declared with a result that isn't an integer. Julia's C++ pass crashes or produces invalid IR on such IR. Thrown before the module is changed.

source
JuliaIR.always_have_fma — Function
JuliaIR.always_have_fma(name::AbstractString, triple::LLVM.Triple)

Whether code for triple has FMA for the type of the intrinsic name (julia.cpu.have_fma.<type>) whatever its CPU (true or false), or nothing when that depends on the CPU. AArch64 always has FMA for f32 and f64. Julia's multiversioning uses this to skip clones that would only differ in the answer.

source
JuliaIR.jit_target — Function
JuliaIR.jit_target()

The target that the running Julia's JIT compiles for, as a named tuple of its triple, cpu and features (a feature string like "+avx2,-fma"). Julia's own pipeline runs its CPUFeatures pass with the JIT's target machine; to do the same with JuliaIR.CPUFeaturesPass, pass it the cpu and features.

Up to Julia 1.13, the JIT's CPU and features aren't exported (they are nothing), and the pass doesn't need them: it uses the JIT's answers by default, see JuliaIR.CPUFeaturesPass.

source
JuliaIR.MultiVersioningPass — Function
JuliaIR.MultiVersioningPass(; external=false, targets=nothing)

Clone the functions of a module for several CPU targets, and emit the tables with which Julia's image loader picks a version of each function for the CPU it runs on (jl-multiversioning in a pipeline). This is how Julia builds system and package images that run well on several CPUs. The pass only acts on modules with a julia.mv.enable module flag.

The targets come from the module flag julia.mv.specs (see JuliaIR.target_specs), which Julia sets when it builds an image, or for a module without it from targets (a non-empty vector of JuliaIR.MultiVersioningTargets, which the pass then records in the flag); targets that differ from the module's are an error.

Julia annotates the functions of its modules with the targets to clone them for before it runs the pass; for modules without the julia.mv.annotated flag, the pass annotates them itself, cloning for each target the functions that benefit from it (with loops, vectors, fast or fused floating-point math, CPU feature queries, half or bfloat values, as the target says) and the functions between them in the call graph.

external (jl-multiversioning<external>, Julia's JuliaMultiVersioning<external>) tolerates a missing or malformed jl_fvars table, as bugpoint produces: without a table, the module is only annotated, and entries of the table that aren't function definitions are left out. Input the image loader couldn't handle raises a JuliaIR.MultiVersioningError before the module is changed. Like Julia's C++ pass, this pass is required (it also runs on optnone functions' modules).

source
JuliaIR.MultiVersioningTarget — Type
JuliaIR.MultiVersioningTarget(cpu, features; base=0, clone_all=false, opt_size=false,
                              min_size=false, clone=())

A target that JuliaIR.MultiVersioningPass clones functions for: the name of a CPU and its features (as in the target-cpu and target-features function attributes), and how functions are cloned for it. The first target of a list is the default one, for which the original functions are kept.

  • clone_all: clone every function for this target. Such a target (and the first one) is the base of a group of targets.
  • base: for a target that doesn't clone every function, the index (from 0) of the target whose version of a function it uses for the functions it doesn't clone; the first target or one that clones every function.
  • clone: for a target that doesn't clone every function, the categories of functions it clones, those that contain: :loop (loops), :simd (vectors), :math (fused or fast floating-point math), :cpu (queries of CPU features), :float16 (half values), :bfloat16 (bfloat values, Julia 1.11 and later). Julia 1.14 always clones loops and CPU feature queries.
  • opt_size, min_size: optimize the functions cloned for this target for size, or for minimal size.

The targets are stored in the module flag julia.mv.specs in the encoding of the running Julia version, see JuliaIR.target_specs.

source
JuliaIR.target_specs — Function
JuliaIR.target_specs(mod::LLVM.Module) -> Union{Nothing,Vector{MultiVersioningTarget}}

The targets that JuliaIR.MultiVersioningPass clones the functions of mod for, from its julia.mv.specs module flag, or nothing if it has none. The flag is a tuple of !{!"cpu", !"features", i32 base, i32 flags} tuples, whose flags are encoded like the running Julia version does: the JL_TARGET_* bits of Julia's processor.h up to Julia 1.13, or Julia 1.14's TargetSpec::packed_flags.

source

Julia's pipeline

JuliaIR.JuliaPipeline — Type
JuliaIR.JuliaPipeline(; level=2, ports=true, options...)

The optimization pipeline of the running Julia (what its julia<level=...;...> pipeline element builds), at optimization level level (0 to 3; other values are clamped, like Julia does), with Julia's passes replaced by this package's ports (ports=true), or named by their C++ names (ports=false, which only works on Julia's own LLVM).

The other keyword arguments are the options of Julia's pipeline (OptimizationOptions in Julia's src/jitlayers.h), which Julia 1.10 has four of and later versions more:

  • lower_intrinsics (true): lower Julia's intrinsics (GC, exception handling, ...);
  • dump_native (false): optimize for a system image or package image, which multiversions functions for several targets and accesses thread-local state through the image;
  • external_use (false): with dump_native, the image is used externally;
  • llvm_only (false): leave out Julia's passes;
  • from Julia 1.11 on, always_inline, enable_early_simplifications, enable_early_optimizations, enable_scalar_optimizations, enable_loop_optimizations, enable_vector_pipeline, remove_ni, cleanup (true): run the corresponding stage (the stage's start and end markers always run), and warn_missed_transformations (false);
  • from Julia 1.14 on, sanitize_memory, sanitize_thread, sanitize_address (false), which must be false: only stock Julia builds are supported.

Options that the running Julia's pipeline doesn't have are rejected, as is turning off remove_ni while lowering intrinsics (an assertion in Julia's C++).

See JuliaIR.build_pipeline! to build the pipeline, and JuliaIR.optimize! to run it like Julia does.

source
JuliaIR.build_pipeline! — Function
JuliaIR.build_pipeline!(pm, p::JuliaPipeline)
JuliaIR.build_pipeline!(pm; level=2, ports=true, options...)

Add the passes of Julia's optimization pipeline p (see JuliaIR.JuliaPipeline for the keyword arguments) to a module pass manager or a pass builder pm, which needs this package's passes to be registered (see JuliaIR.register!). Some passes' options depend on the LLVM version: the pipeline is built for the current library, so select the library that it will run with (see LLVM.library!).

The pipeline is built in stages, which are available on their own (an experimental API): early_simplification!, early_optimization!, intrinsic_lowering! and cleanup! add to a module pass manager, loop_optimization!, scalar_optimization! and vectorization! to a function pass manager, each (pm, p::JuliaPipeline).

source
JuliaIR.pipeline — Function
JuliaIR.pipeline(p::JuliaPipeline) -> String
JuliaIR.pipeline(; level=2, ports=true, options...) -> String

The text of Julia's optimization pipeline p (see JuliaIR.JuliaPipeline for the keyword arguments), for a pass builder with this package's passes registered.

The text doesn't say which alias analyses the passes use, which Julia chooses by the optimization level (see JuliaIR.alias_analyses!).

source
JuliaIR.optimize! — Function
JuliaIR.optimize!(mod::LLVM.Module, [tm::TargetMachine]; level=2, ports=true, options...)

Optimize mod like Julia does, with its optimization pipeline (see JuliaIR.JuliaPipeline for the keyword arguments) and its choice of alias analyses, for the target machine tm. Like Julia's JIT from Julia 1.14 on, CPUFeatures answers for tm where a function doesn't say its target; with ports=false, Julia's C++ CPUFeatures runs by name, which can't be given a target machine (as in opt).

source
JuliaIR.alias_analyses! — Function
JuliaIR.alias_analyses!(pb::PassBuilder, level)

Make the passes of pb use the alias analyses that Julia uses at optimization level level: none of LLVM's below level 2, and LLVM's basic, scoped no-alias and type-based ones from level 2 on, followed by those of the target machine in both cases.

source
JuliaIR.parse_pipeline_options — Function
JuliaIR.parse_pipeline_options(str) -> NamedTuple

Parse the options of Julia's julia<...> pipeline element (the text between < and >, e.g. level=3;no_always_inline) into the keyword arguments of JuliaIR.JuliaPipeline. Options are separated by ; and turned off with no_, like Julia does. Unlike Julia (whose release builds ignore them), unknown options and a level that isn't an integer are rejected.

source
JuliaIR.expand_julia_pipeline — Function
JuliaIR.expand_julia_pipeline(text; ports=true) -> String

Replace the julia and julia<...> elements of the module pipeline text (also in nested module(...) managers) by the text of the pipelines they stand for (see JuliaIR.JuliaPipeline).

source

Dialects and options

JuliaIR.Dialect — Type
JuliaIR.Dialect(julia::VersionNumber; fp16=:auto, bf16=:auto, gc_barrier=:stock)

The IR dialect emitted by Julia version julia. The passes process the IR of the running Julia, so they use Dialect(VERSION), with the policies of their options; constructing the dialect of another version is useful to inspect how the versions differ.

Every Julia version from 1.10 up to the newest version this package knows about has a dialect; a prerelease like v"1.13.0-DEV.123" has the dialect of the release it precedes. Newer versions, including the development version (Julia master), get the newest known version's dialect: a new minor version starts out emitting the same IR as its predecessor, and only diverges as changes land on master.

The dialect only describes Julia's IR. What depends on the version of LLVM (e.g., the signature of the lifetime intrinsics) is determined by the library the passes run on, see JuliaIR.library_facts.

The keyword arguments are policies, which are not determined by the Julia version:

  • fp16: whether DemoteFloat16 demotes half arithmetic to float. :auto follows the rule of the Julia version (fp16_rule), :keep and :demote override it, including the function attributes that rule reads.
  • bf16: the same for bfloat arithmetic.
  • gc_barrier: the GC write-barrier configuration Julia was built with (:stock, :snapshot, :field_precise or :snapshot_field_precise).
source
JuliaIR.PassOptions — Type
JuliaIR.PassOptions

Options for one occurrence of a pass in a pipeline. dialect describes the running Julia's IR and the selected policies; extra is a NamedTuple of pass-specific options.

source
JuliaIR.option — Function
JuliaIR.option(T, default; text=true, valid=nothing, requirement="")

Declare a pass-specific option of type T with a default value. If T includes Nothing, the value nothing leaves the choice to the pass, for example by querying the running Julia. The type determines the pipeline syntax:

typein a pipeline
Boolname or no-name
Union{Nothing,Bool}name=yes or name=no
Symbolname=value (a - in the value stands for _)
Intname=-8: a decimal integer
UIntname=0x4011c0: an address, hexadecimal or decimal
Stringname=text

Options of other types, and options declared with text=false, are keyword-only: they can only be given as keyword arguments of the pass's constructor (e.g. feature lists, which contain ,, LLVM's separator of pipeline elements, or objects like a target machine). A keyword argument is converted to T where that loses nothing: an integer in range, a Ptr for an address, any AbstractString for a String, and any vector of the element type for a Vector.

valid(value) validates values other than nothing. requirement describes the constraint in errors, e.g. "a non-negative integer"; it defaults to a description of T. Do not include nothing in this description: keyword errors add it when allowed, and pipeline syntax cannot express it.

source
JuliaIR.parse_options — Function
JuliaIR.parse_options(str; fp16=:auto, bf16=:auto, gc_barrier=:stock, options=(;),
                      values)
JuliaIR.parse_options(def::PassDefinition, str; kwargs...)

Parse the text between a pass's < and > into JuliaIR.PassOptions, using the running Julia's dialect. Separate options with ;, e.g. fp16=demote;strong.

The policy keywords supply defaults. options declares pass-specific options using JuliaIR.option or plain default values; values can override those defaults. Unknown, repeated and keyword-only options are rejected. The PassDefinition overload also checks whether the selected options are valid together.

source

Runtime facts

JuliaIR.runtime_layout — Function
JuliaIR.runtime_layout(dialect::Dialect, mod::LLVM.Module; facts...)
JuliaIR.runtime_layout(opts::PassOptions, mod::LLVM.Module)

The facts about the runtime of Julia dialect that passes processing mod need, as a JuliaIR.RuntimeFacts whose properties are the facts:

  • the layout of a task (task_gcstack, task_ptls, task_eh and, from Julia 1.14 on, task_reset_ctx) and of the thread-local state (tls_safepoint, tls_gc_state), as byte offsets;
  • exception handlers: handler_size and handler_align (of jl_handler_t) and the name of the runtime's setjmp; from Julia 1.14 on, the reset context (reset_ctx_size, reset_ctx_align and the offsets reset_ctx_gcstack, reset_ctx_eh, reset_ctx_mctx);
  • thread-local storage: tls_elf_support, the offset tls_offset of the GC stack from the thread pointer, and the getter function of the GC stack and its key (pgcstack_getter and pgcstack_key, process addresses);
  • small type tags, which objects of the runtime's builtin types carry in their header instead of the address of their type (smalltag << 4): where a DataType's flags hold its tag (smalltag_shift), and how many tags there are (max_small_tag);
  • the world age of a task (task_world_age, a byte offset), which a C-callable wrapper (@cfunction) sets to the latest world while it runs;
  • from Julia 1.12 on, the data at the call site of a @cfunction that the runtime's jl_get_abi_converter resolves its entry point with: its size (cfuncdata_size, of cfuncdata_t), and where the entry point and the world it is valid in are cached (cfunction_cache): in two separate words passed with it (:arguments, Julia 1.12), or in its first two fields (:cfuncdata).

A fact is resolved when it is read, in order: from the keyword argument (pass option) of the same name, which gives it explicitly; from the running Julia, if dialect is its own; otherwise a JuliaIR.RuntimeError that names the fact and the option. Like Julia's own passes, the running Julia's facts are used whatever target mod is for (IR for targets without Julia's runtime, like GPUs, doesn't need them).

With PassOptions, the facts given explicitly come from the pass's options (see JuliaIR.runtime_options).

source
JuliaIR.runtime_options — Function
JuliaIR.runtime_options(facts::Symbol...)

The pass options of a pass that needs the runtime facts facts (see JuliaIR.runtime_layout), for define_pass's options: one option per fact that gives it explicitly (task_ptls is task-ptls=N in a pipeline).

source
JuliaIR.gc_pool_class — Function
JuliaIR.gc_pool_class(dialect::Dialect, sz::Integer)

How the running Julia's GC allocates an object of sz bytes: the offset of its pool in the thread-local state and the size of the pool's objects, (offset, osize), or nothing when the object is too big for a pool. This is what Julia's FinalLowerGC asks the runtime (jl_gc_classify_pools), which compiles the allocation into a call of the pool's allocation function, and depends on the build (e.g. the page size).

Only the running Julia's GC can be asked: for another dialect, or a running Julia that was built with another GC than the stock one, this throws a JuliaIR.RuntimeError. The answers are cached for the process.

source
JuliaIR.RuntimeError — Type
JuliaIR.RuntimeError(msg)

A fact about the Julia runtime that a pass needs is not known: the IR is of another Julia version than the running one, the fact doesn't exist in the running Julia, or it could not be read from it. Facts can be given explicitly with pass options.

source

Type lowering

JuliaIR.julia_type — Function
JuliaIR.julia_type(T; allow_boxed=false, no_boxing=false)::LLVMType

The LLVM type that Julia's code generator uses for values of the Julia type T, in the current context (which must use opaque pointers): a port of Julia's julia_type_to_llvm, giving the same types as convert(LLVMType, T) does in a context of Julia's own LLVM, but in any context, e.g. of another LLVM library.

The result depends on the running Julia: it is built from the layout of T that the running Julia computed, never from a data layout. Values of types that Julia boxes are lowered to ptr addrspace(10), which is an error unless allow_boxed is set.

With no_boxing=true, T is lowered like an immutable type even if it is mutable or abstract, as by Julia's jl_struct_to_llvm (which Enzyme uses); the result can still be boxed (for Memory, for example). Types for which Julia's C++ would abort throw an ArgumentError instead: types with atomic fields, types without a layout and types without a fixed layout.

See also JuliaIR.isboxed and JuliaIR.julia_struct_type.

source
JuliaIR.isboxed — Function
JuliaIR.isboxed(T)::Bool

Whether JuliaIR.julia_type lowers values of type T to a pointer to a boxed object. This is not the calling convention: whether a function receives an argument of type T as a box is JuliaIR.deserves_argbox (a struct with an undefined reference field is lowered unboxed but passed boxed, for example).

source
JuliaIR.julia_struct_type — Function
JuliaIR.julia_struct_type(T; llvmcall=false, allow_boxed=false)::LLVMType

The structural representation of a value of type T that Julia's code generator uses when calling C (ccall, @cfunction) and for intrinsics: a port of _julia_struct_to_llvm. Unlike JuliaIR.julia_type, T is not checked to be a concrete immutable type first, and with llvmcall=true the result is the variant that ccall(..., llvmcall, ...) declares: Bool is i1, and structs (but not tuples) whose fields have the same LLVM type are LLVM structs rather than arrays. This is no ABI classification for C calls, which Julia's code generator does separately.

source
JuliaIR.deserves_stack — Function
JuliaIR.deserves_stack(T)::Bool

Whether Julia's code generator keeps values of type T unboxed, passing arguments and returning results of that type by value (or by reference to a stack copy) rather than as boxed objects: concrete immutable types that are singletons or can be stored inline. A port of deserves_stack in Julia's src/codegen.cpp.

source
JuliaIR.deserves_argbox — Function
JuliaIR.deserves_argbox(T)::Bool

Whether functions that Julia compiles receive arguments of type T boxed (as ptr addrspace(10)): !deserves_stack(T).

source
JuliaIR.deserves_retbox — Function
JuliaIR.deserves_retbox(T)::Bool

Whether functions that Julia compiles return values of type T boxed: !deserves_stack(T).

source
JuliaIR.deserves_sret — Function
JuliaIR.deserves_sret(T, llvmT::LLVMType)::Bool

Whether a function that returns an unboxed T, lowered to llvmT, returns it through a pointer argument (sret): values larger than a pointer that are neither floating-point nor vector values.

source
JuliaIR.check_layout — Function
JuliaIR.check_layout(T, dl::LLVM.DataLayout)::Vector{JuliaIR.LayoutDivergence}

Compare the layout the running Julia uses for values of type T with the layout that the data layout dl gives the LLVM type JuliaIR.julia_type lowers T to, and return the differences: the size, allocation size and alignment of T, and the offset of every field that occupies memory, with the size and alignment of the field's value (recursively for inline fields). An empty result means that code generated for dl addresses values of type T like Julia does.

Julia's code generator maps field byte offsets onto LLVM type elements. A divergence can therefore make field accesses or array strides incorrect unless the consumer adapts the representation. The lowering itself never consults a data layout; this is a diagnostic. The tests check agreement with the running Julia's data layout and known differences for GPU targets.

source
JuliaIR.LayoutDivergence — Type
JuliaIR.LayoutDivergence

A difference between the layout of a Julia type and the layout of the LLVM type it is lowered to, under some data layout, as found by JuliaIR.check_layout.

  • path: where in the type, e.g. "I128.b";
  • property: :size (the bytes a value occupies, sizeof, which must cover the bytes LLVM stores and fit in what it allocates; the value given for LLVM is the bound that is violated), :allocation_size (the distance between consecutive values in an array: sizeof rounded up to the alignment vs. LLVM's allocation size), :alignment, :offset (of a field, or of the selector byte of an inline union), or :pointer_size (of a reference);
  • julia and llvm: the two values, in bytes.
source

Libraries

JuliaIR.require_opaque — Function
JuliaIR.require_opaque(ctx::LLVM.Context)

Throw an error if ctx uses typed pointers. The passes only support opaque pointers; IR with typed pointers (as emitted by Julia 1.10) needs to be parsed into a context that uses opaque pointers, e.g. Context(; opaque_pointers=true), which upgrades it.

source
JuliaIR.library_facts — Function
JuliaIR.library_facts(ctx::LLVM.Context)

What the passes need to know about the LLVM library that ctx belongs to, as opposed to the Julia dialect of the IR:

  • llvm: the version of LLVM;
  • lifetime_size: whether the lifetime intrinsics take a size argument (before LLVM 22);
  • lifetime_on_alloca: whether the lifetime intrinsics must be given an alloca itself, not a pointer derived from one such as an addrspacecast (LLVM 22 and later);
  • masked_load_alignment: whether llvm.masked.load and llvm.masked.gather take an alignment argument (before LLVM 22);
  • null_pointer_alignment: whether LLVM's Value::getPointerAlignment takes a null pointer to have the maximum alignment (before LLVM 22; later, only integer constants cast to pointers have a known alignment).
source

The jlopt driver

JuliaIR.jlopt — Function
JuliaIR.jlopt(args::Vector{String}; stdin=stdin, stdout=stdout)

Run the jlopt driver in this process: parse the module from the input named in args (or read from stdin), run the pipeline, and write the result to stdout or the file named by -o. Throws a JuliaIR.JloptError for usage errors, and the exception of a failing pass otherwise. See bin/jlopt --help for the options.

source