Transforms
Pass builders
LLVM.PassBuilder — Type
PassBuilder(; verify_each=false, debug_logging=false, pipeline_tuning_kwargs...)
PassBuilder(f; kwargs...)Create a new pass builder. The pass builder is the main object used to construct and run pass pipelines. The verify_each keyword argument enables module verification after each pass, while debug_logging can be used to enable more output. Pass builder objects need to be disposed of after use, e.g., using @dispose or the do-block form.
Several other keyword arguments can override LLVM's version- and configuration-dependent pipeline defaults. This only has an effect when using one of LLVM's default pipelines, like default<O3>:
loop_interleaving::Bool: Enable loop interleaving.loop_vectorization::Bool: Enable loop vectorization.slp_vectorization::Bool: Enable SLP vectorization.loop_unrolling::Bool: Enable loop unrolling.forget_all_scev_in_loop_unroll::Bool: Forget all SCEV information in loop unrolling.licm_mssa_opt_cap::Int: LICM MSSA optimization cap.licm_mssa_no_acc_for_promotion_cap::Int: LICM MSSA no access for promotion cap.call_graph_profile::Bool: Enable call graph profiling.merge_functions::Bool: Enable function merging.
To collect the optimization remarks that passes emit, e.g., using remark!, pass a function as the remarks keyword argument. It is called with each Remark while the passes run, instead of the remark being logged as a debug message. LLVM's own passes only emit remarks when they are enabled using LLVM's -pass-remarks, -pass-remarks-missed or -pass-remarks-analysis command-line options. Exceptions thrown by the function are rethrown as PassException by run!. This requires a context created by LLVM.jl.
After a pass builder is constructed, custom passes can be registered with register!, passes or nested pass managers can be added with add!, and finally the passes can be run with run!:
@dispose pb = PassBuilder(verify_each=true) begin
register!(pb, SomeCustomPass())
add!(pb, SomeModulePass())
add!(pb, FunctionPassManager()) do fpm
add!(fpm, SomeFunctionPass())
end
run!(pb, mod, tm)
endFor quickly running a simple pass or pipeline, a shorthand run! method is provided that obviates the construction of a PassBuilder:
run!("some-pass", mod, tm; verify_each=true)LLVM.run! — Function
run!(pb::PassBuilder, mod::Module, [tm::TargetMachine])
run!(pipeline::AbstractString, mod::Module, [tm::TargetMachine])Run passes on a module. The passes are specified by a pass builder or a string that represents a pass pipeline. The target machine is used to optimize the passes.
The target's context and library are active while the passes run, including in custom pass callbacks. The caller's context and library are restored afterwards. For a function target, select its library before calling run!, since function wrappers do not remember their library.
LLVM.PassException — Type
PassExceptionThe exception thrown by run! when a custom pass (or a custom target transform info) threw an exception while LLVM ran it. The original exception is available as its ex field, and the backtrace of where it was thrown as processed_bt.
Pass managers
LLVM.PassManager — Type
ModulePassManager()
CGSCCPassManager()
FunctionPassManager(; eager_invalidate=false)
LoopPassManager(; use_memory_ssa=false)
AAManager()Create a new pass manager of the specified type. These objects can be used to construct pass pipelines, by add!ing passes to them, and finally add!ing them to a parent pass manager or pass builder.
Creating a pass manager and adding it to a parent manager or builder can be shortened using a single add!:
add!(parent, ModulePassManager()) do mpm
add!(mpm, SomeModulePass())
endThis form does not add pass managers that are left empty.
With eager_invalidate=true, the analyses of each function are invalidated as soon as the passes of a function pass manager have run on it, rather than kept for the passes that come later (function<eager-inv>), which LLVM's pipelines do to save memory. With use_memory_ssa=true, a loop pass manager maintains MemorySSA for passes that require it (loop-mssa).
An AAManager selects the alias analyses that passes use (in order), instead of LLVM's default ones, and can only be added to a pass builder. Unlike other pass managers, it is meaningful when empty: the passes then use no alias analysis. LLVM's default alias analyses include those of the target machine, which an AAManager only includes where it lists TargetAA().
See also: add!, PassBuilder
LLVM.add! — Method
add!(pm::AbstractPassManager, pass)Adds a pass or pipeline to a pass builder or pass manager, and returns the pass builder or pass manager.
The pass or pipeline should be a string or string-convertible object known by LLVM. These can be constructed by using pass constructors, e.g., InternalizePass(), or by manually specifying names like default<O3>.
When using custom passes, remember that they need to be registered with the pass builder before they can be used.
See also: register!
Passes and pipelines
The functions that return the names of LLVM's passes are listed on the Passes page.
LLVM.DefaultPipeline — Function
DefaultPipeline(; opt_level=0, options...) -> StringLLVM's default optimization pipeline for the optimization level opt_level (0 to 3, or "s" and "z" to optimize for size on LLVM versions before 23), as a string for use with add! or run!, e.g., "default<O3>". Other keyword arguments become options of the pipeline, but LLVM's default pipeline takes few: it is tuned using the keyword arguments of PassBuilder instead.
LLVM.print_pipeline — Function
print_pipeline([io::IO], pb::PassBuilder, [tm::TargetMachine])Print the pipeline of a pass builder as LLVM parses it, like LLVM's opt -print-pipeline-passes does: with the pass managers that LLVM inserts, every pass under the name it is known by, and the options of passes that take them. Pipelines like "default<O2>" are printed as the passes that they consist of. The pipeline is parsed at the module level, with the custom passes that were registered with the pass builder, and with the passes of the target machine tm, but it is not run.
The printed pipeline can be added to a pass builder again. LLVM prints passes in this way on a best-effort basis, though: some passes are printed without some of their options, or under a name that cannot be parsed.
julia> @dispose pb=PassBuilder() begin
add!(pb, "globaldce,instsimplify")
print_pipeline(pb)
end
globaldce,function(instsimplify)Custom passes
LLVM.CustomPass — Type
ModulePass(name, callback; required=false, analyses=false, parse_options=nothing)
FunctionPass(name, callback; required=false, analyses=false, parse_options=nothing)Create a new custom pass. The name is a string that will be used to identify the pass in the pass manager. The callback is a function that will be called when the pass is run. The function should take a single argument, the module or function to be processed, and return a boolean indicating whether the pass made any changes. Set required=true for a pass needed for correctness; LLVM then does not skip it on optnone functions or under -opt-bisect-limit. LLVM's instrumentation also knows the pass by its name, e.g., when listing the passes that run (with debug_logging=true), or when selecting passes to print the IR of (with LLVM's -print-before=name option). That does not work for a name that is also the class name of one of LLVM's passes, like InstCombinePass, which LLVM already knows by its own name.
With analyses=true, a pass can use LLVM's analyses: the callback is then called with two arguments, the function and its FunctionAnalysisManager (for a function pass), or the module and its ModuleAnalysisManager (for a module pass). Instead of a boolean, it may return a PreservedAnalyses value describing the analyses that remain valid after the pass:
function my_pass!(f::LLVM.Function, am::LLVM.FunctionAnalysisManager)
domtree = am[DomTree]
changed = ...
# this pass does not modify the CFG
return changed ? PreservedAnalyses(CFGAnalyses) : PreservedAnalyses(AllAnalyses)
end
FunctionPass("my-pass", my_pass!; analyses=true)A module pass gets the analyses of each of its functions by indexing its manager with the function:
function my_module_pass!(mod::LLVM.Module, am::LLVM.ModuleAnalysisManager)
for f in mod.functions
isdeclaration(f) && continue
domtree = am[f][DomTree]
...
end
return false
end
ModulePass("my-module-pass", my_module_pass!; analyses=true)A pass can take options, which are written in a pipeline like those of LLVM's passes: name<options>. To support them, pass a function parse_options that takes the string between the angle brackets (an empty string if the pass is written as name or name<>), and returns the options. The callback is then called with those options as an extra, last argument. For example, a pass that is configured using my-pass<limit=N>:
function parse_my_options(str)
limit = 10
for opt in split(str, ';'; keepempty=false)
key, value = split(opt, '=')
key == "limit" || throw(ArgumentError("unknown option $key"))
limit = parse(Int, value)
end
return (; limit)
end
my_pass!(f::LLVM.Function, am, options) = ...
FunctionPass("my-pass", my_pass!; analyses=true, parse_options=parse_my_options)Each occurrence of the pass in a pipeline has its own options, which are used for every function or module that occurrence runs on, so they should not be mutated by the pass. The parser may be called more than once for an occurrence, so it should not have side effects. Options cannot contain the characters ,, ( and ), which separate passes in a pipeline. Exceptions thrown by the parser are rethrown as PassException by run!.
Before using a custom pass, it must be registered with a pass builder using register!. LLVM.jl catches exceptions from these callbacks and rethrows them as PassException after control has returned from LLVM. The optional passes that remain to run in the pipeline are skipped then, as are custom passes even if they are required, since the IR may be inconsistent. This is not a guarantee that nothing uses it: LLVM's required passes (like the pass managers and adaptors, which skip the passes they contain) still run, and with verify_each, LLVM verifies the IR after the failing pass and aborts if it is invalid. Callbacks registered directly through the LLVM.API.LLVMPassBuilderExtensionsRegister*Pass APIs must provide an equivalent exception barrier: Julia exceptions must not escape a callback, because they bypass C++ destructors in LLVM's pass runner.
See also: register!
LLVM.LoopPass — Function
LoopPass(name, callback; required=false, analyses=true, parse_options=nothing)Create a custom loop pass, which runs on each loop of a function (innermost loops first) in a loop pass manager, i.e., in a loop(...) or loop-mssa(...) pipeline. Like LLVM's loop passes, it can also be used on its own (without MemorySSA) if its name is the first of a pipeline, e.g., add!(pb, "my-loop-pass").
The callback is called with the Loop and the LoopAnalysisResults of the loop pass manager, which provide the analyses that loop passes use (e.g., ar[ScalarEvolution]). It returns the PreservedAnalyses describing the analyses that remain valid, typically PreservedAnalyses(AllAnalyses) if the pass did not change anything, and PreservedAnalyses(LoopAnalyses) (plus CFGAnalyses and MemorySSA as appropriate) if it did. With analyses=false, the callback is only called with the loop, and returns a boolean indicating whether it changed anything, like other custom passes.
function hoist!(loop::Loop, ar::LoopAnalysisResults)
se = ar[ScalarEvolution]
changed = ...
changed || return PreservedAnalyses(AllAnalyses)
return PreservedAnalyses(LoopAnalyses, CFGAnalyses, MemorySSA)
end
LoopPass("hoist", hoist!)Loop passes need to respect the contract of LLVM's loop pass manager, which shares its analyses between all passes of the pipeline:
- the dominator tree, loop info and scalar evolution of the function must be kept valid by the pass (for scalar evolution, e.g., using
forget!), as the loop pass manager treats them as preserved, whatever the pass returns; - in a
loop-mssapipeline, MemorySSA must be kept valid as well, and the pass must preserve it (MemorySSAorAllAnalyses); returning anything else is an error; - the pass should only change the loop it is called with (and the loops nested in it), and not delete loops, as it cannot notify the loop pass manager of that;
- the loop pass manager runs LLVM's
loop-simplifyandlcssapasses before the loop passes, so loops are normally in their canonical form, which a pass should keep. That can fail (e.g., for loops entered through anindirectbr), so a pass that needs a preheader should check thatloop.preheader !== nothing, like Julia's loop passes do.
required and parse_options work like for FunctionPass. If the callback throws an exception, the remaining passes of the pipeline are skipped as described for FunctionPass, since the analyses that the loop pass manager shares may be stale, and run! throws a PassException. To keep the loop pass manager from aborting the process, a failing pass in a loop-mssa pipeline is reported to preserve MemorySSA. This is best-effort containment, not recovery from an arbitrary partial change of the IR: LLVM's required passes, like the pass managers and adaptors, still run and use analyses.
See also: register!, LoopAnalysisResults
LLVM.register! — Function
register!(pb, custom_pass)Register a custom pass with the pass builder. This is necessary before the pass can be used in a pass pipeline.
See also: ModulePass, FunctionPass
LLVM.run! — Method
run!(pass::CustomPass, target::Union{Module,Function}, [tm::TargetMachine]; kwargs...)Run a single custom pass on a module or a function, using a temporary pass builder that is created with the given keyword arguments (see PassBuilder). A function pass that runs on a module runs on each of its functions, while a module pass cannot run on a function. A loop pass runs on each loop of the function(s) in a loop(...) pipeline, i.e., without MemorySSA. Like for passes that run in a pipeline, exceptions thrown by the pass are rethrown as a PassException.
LLVM.register_callbacks! — Function
register_callbacks!(pb::PassBuilder, callback::Ptr{Cvoid})Register a native callback that is called with LLVM's C++ PassBuilder (as a void *) when the pass builder is used to run passes. This makes it possible to use passes that are implemented in C++, by calling the PassBuilder's register*Callback methods, like pass plugins do. For example, for a library that provides a registerCallbacks function:
register_callbacks!(pb, cglobal((:registerCallbacks, libfoo)))The callback needs to have the signature void (*)(void *), and the library that provides it needs to be built against the same version of LLVM as the one that Julia uses, and remain loaded while the pass builder is used. Callbacks are called in the order they were registered, after LLVM.jl registers Julia's passes, and must not throw Julia exceptions. To implement a pass in Julia instead, use register!.
Analyses in custom passes
LLVM.FunctionAnalysisManager — Type
FunctionAnalysisManagerThe analysis manager of a pass pipeline, for the function that a custom pass (created using FunctionPass(...; analyses=true)) runs on. It provides the results of LLVM's function analyses, which it owns and caches:
am[T]: get the result of the analysisT(e.g., aDomTree), computing it if needed;get(am, T, nothing): get the result ofTonly if it has been computed already;invalidate!(am, preserved): invalidate the results that are not preserved;empty!(am): remove all of the function's results, including the ones that survive invalidation. This is required before a module pass deletes the function.
The supported analyses are DomTree, PostDomTree, AssumptionCache, LazyValueInfo, ScalarEvolution, LoopInfo and MemorySSA.
Analysis results are borrowed from the manager, which owns them:
- they must not be disposed of, and must not be used after the pass returns;
- they must not be used after they were invalidated, using
invalidate!orempty!; - they are not updated when the pass changes the IR, so they become stale when it changes what they describe. A pass that changes the IR and then queries an analysis again needs to update that analysis itself (e.g., register new assumptions with the
AssumptionCache), or invalidate it first.
What the pass returns only determines which analyses remain valid after the pass.
See also: PreservedAnalyses, ModuleAnalysisManager
LLVM.ModuleAnalysisManager — Type
ModuleAnalysisManagerThe analysis manager of a pass pipeline, for the module that a custom pass (created using ModulePass(...; analyses=true)) runs on. Indexing it with a function of the module, am[f], returns the FunctionAnalysisManager for that function, which provides LLVM's function analyses for it:
domtree = am[f][DomTree]
lvi = am[f][LazyValueInfo]Analyses can only be computed for functions that have a body, not for declarations. The function analyses of all functions are cached by the pipeline, and remain valid until they are invalidated: when a module pass changes a function and then queries its analyses again, it needs to invalidate them first using invalidate!(am[f]), and before a module pass deletes a function, it needs to remove its results using empty!(am[f]). The rules for borrowed results are the same as for function passes (see FunctionAnalysisManager).
What the pass returns determines which analyses remain valid afterwards, on all functions of the module (see PreservedAnalyses). A pass that only changed some functions can invalidate their analyses itself, and return PreservedAnalyses(FunctionAnalyses) to keep the cached results of the other functions.
LLVM.run! — Method
run!(pb::PassBuilder, f::Function, am::FunctionAnalysisManager, [tm::TargetMachine])
run!(pipeline::AbstractString, f, am, [tm])
run!(pass::CustomPass, f, am, [tm])
-> PreservedAnalysesRun the passes of a pass builder on a function, from a custom function pass that runs on it with the function analysis manager am (see FunctionPass). The passes run as part of the enclosing pipeline: like the passes of a nested function pass manager, they use and update the analyses of that pipeline, and its instrumentation reports them. The pipeline is parsed as a function pipeline, with the passes that were registered with the pass builder and those of the target machine tm.
Returns the analyses that the passes preserved, which LLVM computes like for a nested function pass manager: the analyses that a pass did not preserve have been invalidated after it ran, so when the passes changed the function, this preserves FunctionAnalyses. This can be returned by the custom pass, or intersected with what the rest of the pass preserves (see PreservedAnalyses). PreservedAnalyses only describes the analyses that LLVM.jl knows, so analyses that the passes preserve individually, but that are not among them, are not preserved by what is returned. As a result, LLVM may recompute more analyses after the custom pass than after the passes themselves.
The alias analyses, the target transform info and the instrumentation are those of the enclosing pipeline: the pass builder's options for them (like verify_each or debug_logging) have no effect, and it cannot have an AAManager, a custom target transform info or collect remarks. If one of the passes fails, the passes that remain are skipped, and run! throws a PassException, which fails the calling pass unless it catches it.
For example, a pass that unrolls loops, and cleans up after that if it changed anything:
function unroll_and_clean_up!(f::LLVM.Function, am::LLVM.FunctionAnalysisManager)
preserved = run!(LoopUnrollPass(), f, am)
preserved == PreservedAnalyses(AllAnalyses) && return preserved
@dispose pb=PassBuilder() begin
add!(pb, SimplifyCFGPass())
add!(pb, InstCombinePass())
return intersect(preserved, run!(pb, f, am))
end
end
FunctionPass("unroll-and-clean-up", unroll_and_clean_up!; analyses=true)LLVM.LoopAnalysisResults — Type
LoopAnalysisResultsThe analyses that the loop pass manager of a pipeline provides to a custom loop pass (created using LoopPass), for the function containing the loop. These are the results of function analyses that the loop pass manager computes before running the loop passes, and keeps valid while they run, with the help of the passes:
ar[T]: the result of the analysisT, which is one ofDomTree,LoopInfo,ScalarEvolution,AssumptionCacheandMemorySSA. MemorySSA is only available in aloop-mssa(...)pipeline;get(ar, T, nothing): the same, ornothingif the result is not available (i.e., for MemorySSA outside of aloop-mssa(...)pipeline);ar.updater: theLPMUpdaterof the loop pass manager.
The results, and the object itself, are borrowed from the loop pass manager: they must not be used after the pass returns. Unlike the results that a function pass obtains, they are shared by all passes of the loop pipeline, so the pass must keep them up to date when it changes the IR (see LoopPass).
LLVM.LPMUpdater — Type
LPMUpdaterThe updater of a loop pass manager, through which a loop pass notifies the loop pass manager of changes to the loop nest. It is available as ar.updater from the LoopAnalysisResults of a custom loop pass, and like those, it must not be used after the pass returns. No operations are supported yet.
LLVM.PreservedAnalyses — Type
PreservedAnalyses(analyses...)The set of analyses that remain valid after a custom pass, returned by a pass created using FunctionPass(...; analyses=true), ModulePass(...; analyses=true) or LoopPass. Each argument is either an analysis (e.g. DomTree), or a marker for a set of analyses (AllAnalyses, CFGAnalyses, FunctionAnalyses or LoopAnalyses):
PreservedAnalyses(): no analysis is preserved (like returningtrue);PreservedAnalyses(AllAnalyses): every analysis is preserved (like returningfalse);PreservedAnalyses(CFGAnalyses): the pass did not change the control-flow graph;PreservedAnalyses(DomTree): the pass kept the dominator tree up to date.
Some analyses are invalidated with the analyses they depend on, even when they are preserved: e.g., MemorySSA depends on the dominator tree, so a pass that kept it up to date returns PreservedAnalyses(MemorySSA, CFGAnalyses) (or PreservedAnalyses(MemorySSA, DomTree) if it changed the control-flow graph and updated the dominator tree).
For a module pass, the function analyses that are listed are preserved for every function of the module, while all other analyses of the module are invalidated. A module pass that invalidated the analyses of the functions it changed itself (using invalidate!(am[f])) can return PreservedAnalyses(FunctionAnalyses), which keeps the cached function analyses of all other functions.
A function pass that changed the function, but invalidated the analyses that it did not preserve itself, can similarly return PreservedAnalyses(FunctionAnalyses): the function's analyses are then kept, while LLVM still knows that the function changed. This is what running passes from a function pass with run!(pb, f, am) returns when they changed the function.
The analyses that are preserved by two passes that run one after the other are intersect(a, b), like for LLVM's PreservedAnalyses::intersect: an analysis or set of analyses is preserved when both preserve it, or when one of them preserves all analyses.
A loop pass can only preserve sets of analyses and MemorySSA: PreservedAnalyses(AllAnalyses) if it did not change anything, or PreservedAnalyses(LoopAnalyses) if it did, together with CFGAnalyses if it did not change the control-flow graph, and MemorySSA if it kept MemorySSA up to date (which is required in a loop-mssa(...) pipeline). See LoopPass.
See also: FunctionAnalysisManager, invalidate!
LLVM.AllAnalyses — Type
AllAnalyses
CFGAnalyses
FunctionAnalyses
LoopAnalysesMarkers for sets of analyses, used with PreservedAnalyses: AllAnalyses for every analysis, CFGAnalyses for the analyses that only depend on the control-flow graph of a function (like DomTree and PostDomTree), i.e., on its blocks and their terminators, FunctionAnalyses for the function analyses of the function (for a function pass), or of every function of the module (for a module pass), and LoopAnalyses (only for loop passes) for the analyses that a loop pass keeps valid when it changes a loop: the dominator tree, loop info and scalar evolution of the function.
LLVM.LoopAnalyses — Type
AllAnalyses
CFGAnalyses
FunctionAnalyses
LoopAnalysesMarkers for sets of analyses, used with PreservedAnalyses: AllAnalyses for every analysis, CFGAnalyses for the analyses that only depend on the control-flow graph of a function (like DomTree and PostDomTree), i.e., on its blocks and their terminators, FunctionAnalyses for the function analyses of the function (for a function pass), or of every function of the module (for a module pass), and LoopAnalyses (only for loop passes) for the analyses that a loop pass keeps valid when it changes a loop: the dominator tree, loop info and scalar evolution of the function.
LLVM.invalidate! — Function
invalidate!(am::FunctionAnalysisManager, preserved=PreservedAnalyses())Invalidate the analysis results of the function that are not in the set of preserved analyses (by default, all of them), so that they are recomputed when they are queried next. Any result that was obtained before must not be used anymore. This is useful for a pass that changes the IR and then queries analyses that depend on it again.
Some analyses, like the assumption cache, are designed to survive invalidation, and need to be updated by the pass instead.
LLVM.remark! — Function
remark!(f, am::FunctionAnalysisManager, kind::Symbol, pass_name, remark_name,
inst::Instruction)
remark!(f, ar::LoopAnalysisResults, kind::Symbol, pass_name, remark_name,
inst::Instruction)
remark!(am_or_ar, kind::Symbol, pass_name, remark_name, inst::Instruction, message...)Emit an optimization remark about the instruction inst of the function of am (or of a loop pass, given its LoopAnalysisResults), like LLVM's passes do using an OptimizationRemarkEmitter: with kind=:passed for an optimization that the pass performed, kind=:missed for one that it could not perform, or kind=:analysis for a fact that explains a decision. The pass_name and the remark_name identify the remark.
The message of the remark is made of parts, each of which is either a string, or an argument key => value whose value is a value, a type, a string or an integer, like the ore::NV(key, value) arguments of LLVM's remarks. LLVM renders these arguments, so that they read like those of its own passes: a value as its name if it is an argument or a global, a constant as an operand, an intrinsic call as call and the name of the intrinsic (on LLVM 21 and later), and any other instruction as its opcode. The remark also carries the arguments separately, with the source location of a value that has one, as can be seen in a RemarkFile.
In the preferred do-block form, f() returns the message, as a single part or as a collection of parts, and is only called when remarks of this kind are wanted for the pass (see remark_enabled), so passes don't pay for building remarks that are discarded:
remark!(am, :passed, "my-pass", "Hoisted", inst) do
"hoisting ", "Inst" => inst, " out of the loop"
endOtherwise, the remark is always emitted, with the parts of the message passed as the final arguments.
Remarks are passed to the diagnostic handler of the context, where they can be collected using the remarks keyword argument of PassBuilder (also accepted by run!), or written to a file using a RemarkFile. Otherwise, they are logged as debug messages.
LLVM.remark_enabled — Function
remark_enabled(ctx::Context, kind::Symbol, pass_name) -> BoolWhether remarks of the given kind (:passed, :missed or :analysis) emitted by the pass pass_name are wanted in the context ctx: because they are collected using the remarks keyword argument of PassBuilder or run!, enabled for that pass using LLVM's -pass-remarks, -pass-remarks-missed or -pass-remarks-analysis options (see LLVM.clopts), written to a RemarkFile that accepts the pass, or logged as debug messages because the current logger accepts those of LLVM.jl (e.g., with JULIA_DEBUG=LLVM). A remarks callback or a debug logger wants all remarks of every pass, while LLVM's own passes only build theirs when they are enabled using these options or a remark file.
The do-block form of remark! uses this to only build remarks that are wanted. A pass can also use it to skip work whose only purpose is to explain a decision in a remark, like LLVM's passes do using OptimizationRemarkEmitter::allowExtraAnalysis.
LLVM.Remark — Type
RemarkAn optimization remark, as collected by a pass builder with a remarks callback (see PassBuilder) and emitted by a custom pass using remark! or by one of LLVM's passes. Its fields are:
kind::passed,:missedor:analysis;pass_name,name: the names of the pass and of the remark;function_name: the name of the function the remark is about;message: the message of the remark;file,line,column: the source location of the remark, from debug information, or an empty file name if it is unknown. LLVM joins the directory and the name of the file with the platform's path separator;arguments: the parts that make up the message, askey => valuepairs of strings, with the key"String"for plain text, and LLVM's rendering of other values.
LLVM.RemarkFile — Type
RemarkFile(path; passes="", format="yaml")
RemarkFile(f, path; kwargs...)Write the optimization remarks emitted in the current context to the file at path, like the -pass-remarks-output option of LLVM's tools, until the remark file is disposed of. The format is any that LLVM supports for remark files: "yaml", or, on LLVM 22 and later, "bitstream" (older versions of LLVM write bitstream remarks for a string table that is stored elsewhere, in an object file). passes is a regular expression that limits the remarks to those of the passes whose name it matches. Remarks are written regardless of whether they are enabled using -pass-remarks options, also by LLVM's own passes, and still passed to the diagnostic handler of the context (e.g., the remarks callback of a PassBuilder).
A context can only write remarks to one file at a time, and the remark file needs to be disposed of before the context, e.g., using the do-block form.
Custom target info
LLVM.AbstractTargetTransformInfo — Type
abstract type AbstractTargetTransformInfoSubtype this to supply a custom TargetTransformInfo to pass pipelines that would otherwise see only LLVM's conservative baseline.
Most LLVM back-ends (host CPU, NVPTX, AMDGPU) carry their own TTI through a TargetMachine; callers that already have one and pass it to run! don't need this. The feature is aimed at out-of-tree back-ends not linked into libLLVM (e.g. Metal) and at tooling that runs passes without a TargetMachine at all. In those pipelines, the baseline TTI reports no flat address space, no branch divergence, etc., and TTI-sensitive passes such as InferAddressSpacesPass or UniformityAnalysis silently no-op.
Subtypes override only the queries they care about; any query left un-overridden is handled by LLVM's TargetTransformInfoImplBase. For a query receiving Value, provide a method accepting Value whenever adding specialized methods: an installed callback can receive any value kind.
Attach an instance with target_transform_info!; attaching nothing reverts to LLVM's native TTI. When a TargetMachine is also supplied to run!, a custom TTI takes precedence.
Exceptions from overridden queries are captured, LLVM receives a conservative answer, and the exception is rethrown as a PassException after the pass pipeline returns.
The queries receive address spaces and intrinsic IDs as UInts, and return address spaces as integers, or nothing for none.
Overridable queries:
- Target-level knobs:
flat_address_space,has_branch_divergence,is_single_threaded. - Address-space predicates:
is_noop_addr_space_cast,is_valid_addr_space_cast,addrspaces_may_alias,can_have_non_undef_global_initializer_in_address_space. - Per-
Valuequeries:is_source_of_divergence,is_always_uniform,get_assumed_addr_space,get_predicated_addr_space. - Intrinsic hooks:
rewrite_intrinsic_with_address_space,collect_flat_address_operands.
LLVM.target_transform_info! — Function
target_transform_info!(pb::PassBuilder, tti::AbstractTargetTransformInfo)
target_transform_info!(pb::PassBuilder, ::Nothing)Attach an AbstractTargetTransformInfo subtype instance to the pass builder, replacing any previously-attached custom TTI. Pass nothing to revert to LLVM's native TTI (derived from the TargetMachine, if any; otherwise TargetTransformInfoImplBase with full DataLayout/Module-aware defaults).
LLVM.flat_address_space — Function
flat_address_space(tti::AbstractTargetTransformInfo) -> Union{Integer,Nothing}Address space the target treats as "flat" / generic, or nothing if there is none. Required — alongside is_noop_addr_space_cast — for InferAddressSpacesPass to fold addrspacecasts. If not defined, LLVM reports no flat AS.
LLVM.has_branch_divergence — Function
has_branch_divergence(tti::AbstractTargetTransformInfo) -> BoolWhether the target can produce divergent control flow. Enables divergence-aware passes (SimplifyCFG, loop analyses) when true. If not defined, falls back to LLVM's baseline.
LLVM.is_single_threaded — Function
is_single_threaded(tti::AbstractTargetTransformInfo) -> BoolWhether the target is single-threaded. A few passes skip concurrency-related transformations when true. If not defined, falls back to LLVM's baseline (which consults the module's "single-thread" flag).
LLVM.is_noop_addr_space_cast — Function
is_noop_addr_space_cast(tti::AbstractTargetTransformInfo,
from::Unsigned, to::Unsigned) -> BoolWhether an addrspacecast from from to to is a noop at runtime. If not defined, falls back to LLVM's baseline.
LLVM.is_valid_addr_space_cast — Function
is_valid_addr_space_cast(tti::AbstractTargetTransformInfo,
from::Unsigned, to::Unsigned) -> BoolWhether an addrspacecast from from to to is permitted. If not defined, falls back to LLVM's baseline.
LLVM.addrspaces_may_alias — Function
addrspaces_may_alias(tti::AbstractTargetTransformInfo,
as0::Unsigned, as1::Unsigned) -> BoolWhether pointers in address spaces as0 and as1 may alias. If not defined, falls back to LLVM's (conservative) baseline.
LLVM.can_have_non_undef_global_initializer_in_address_space — Function
can_have_non_undef_global_initializer_in_address_space(
tti::AbstractTargetTransformInfo, as::Unsigned) -> BoolWhether globals in address space as may have non-undef initializers. If not defined, falls back to LLVM's baseline (which consults DataLayout::isNonIntegralAddressSpace).
LLVM.is_source_of_divergence — Function
is_source_of_divergence(tti::AbstractTargetTransformInfo, v::Value) -> BoolWhether v is a source of divergence. Consulted by UniformityAnalysis. If not defined, falls back to LLVM's baseline.
LLVM.is_always_uniform — Function
is_always_uniform(tti::AbstractTargetTransformInfo, v::Value) -> BoolWhether v is known to hold the same value across all threads. Consulted by UniformityAnalysis. If not defined, falls back to LLVM's baseline.
LLVM.get_assumed_addr_space — Function
get_assumed_addr_space(tti::AbstractTargetTransformInfo, v::Value)
-> Union{Integer,Nothing}Address space statically known to hold for v, or nothing for "no assumption". If not defined, falls back to LLVM's baseline.
LLVM.get_predicated_addr_space — Function
get_predicated_addr_space(tti::AbstractTargetTransformInfo, cond::Value)
-> Union{Tuple{Value,Integer},Nothing}Given the condition cond of an llvm.assume, return a (ptr, as) tuple if the condition implies that the pointer ptr is in address space as (e.g., when cond is a call to an intrinsic that checks the address space of ptr), or nothing otherwise. If not defined, falls back to LLVM's baseline.
LLVM.rewrite_intrinsic_with_address_space — Function
rewrite_intrinsic_with_address_space(tti::AbstractTargetTransformInfo,
ii::Value, old::Value, new::Value) -> Union{Value,Nothing}Rewrite an intrinsic call ii after its pointer operand old has been replaced by new (in a different address space). Return the rewritten value, or nothing to keep the existing call. If not defined, falls back to LLVM's baseline.
LLVM.collect_flat_address_operands — Function
collect_flat_address_operands(tti::AbstractTargetTransformInfo, iid::Unsigned)
-> AbstractVector{<:Integer}Positions of the arguments of calls to intrinsic iid that are flat-address-space pointers, numbered from 1 like call.arguments. The underlying C API supports at most 32 entries; returning more throws an error. If not defined, falls back to LLVM's baseline.
IR cloning
LLVM.ValueMap — Type
ValueMap()Create a map from values (and metadata) to the values (and metadata) that replace them, like LLVM's ValueToValueMapTy. Value maps are used by map_value, map_metadata and clone_into!, which look up the replacements of values in it, and extend it with the replacements they create. Using a single map for several operations, e.g., when rebuilding the global values of a module and then cloning the bodies of its functions, makes them use each other's replacements.
A value map is a dictionary from Value to Value. Its entries track their key and value: when the key is replaced using replace_uses!, the entry moves to the replacement, and it is removed when the key is deleted; when the value is replaced, the entry refers to the replacement, and it is ignored when the value is deleted. The entries for metadata are available as a separate dictionary, the metadata property.
This object needs to be disposed of using dispose, or by using the do-block form ValueMap() do vmap ... end.
Properties
vmap.metadataThe part of the map for metadata, as a mutable dictionary from Metadata to Metadata, e.g., vmap.metadata[md] = md to have metadata mapped to itself instead of being cloned.
LLVM.dispose — Method
dispose(vmap::ValueMap)Dispose of the given value map.
LLVM.map_value — Function
map_value(val::Value, vmap::ValueMap; [type_mapper::Function],
[materializer::Function], [no_module_level_changes::Bool],
[ignore_missing_locals::Bool], [reuse_and_mutate_distinct_mds::Bool],
[null_map_missing_globals::Bool])Map a value using the value map vmap, like LLVM's MapValue, and return the result, or nothing if the value cannot be mapped. Values that are in the map are mapped to their entry. Other constants are rebuilt with their operands mapped (and their type mapped by the type mapper), and metadata is mapped like map_metadata does. The mappings that are made are added to vmap. Global values that aren't in the map are mapped to themselves, while other values that aren't in the map can't be mapped.
The type_mapper callback maps the types of the values that are rebuilt: it is called with a type, and returns the type to use instead. The materializer callback is called with every value that isn't in the map yet, and returns the value to map it to, or nothing to have it mapped as usual. A materializer can call map_value itself, e.g., to map the operands of a value that it rebuilds, which uses and extends the same map. The type mapper is not called for the types that a type contains, so it should map those itself (e.g., the element type of an array type).
The mappings must leave the constant expressions that LLVM rebuilds valid: LLVM keeps their opcode. For example, when the type mapper or the map makes both sides of an addrspacecast the same address space, the materializer should replace the cast with its mapped operand, or LLVM creates an invalid cast (which a build of LLVM with assertions aborts on).
As LLVM cannot be stopped once it maps a value, the type mapper is called before LLVM maps anything, with the types of the constants (including global values) that are reachable from val through the operands of constants and metadata, stopping at entries of the map. Its results are cached, and if it throws, nothing is mapped. This may call it for types that LLVM doesn't end up asking for, e.g., below a value that the materializer replaces. When the materializer throws, LLVM goes on mapping without the callbacks: that value and the remaining constants whose type maps to another one are mapped to poison of that type, so that the constants that are rebuilt are well-formed, but the map and anything mapped with it should be discarded. (A blockaddress, dso_local_equivalent or no_cfi, whose global value LLVM requires to map to a global value, is mapped to poison as a whole instead.) Exceptions thrown by either callback are rethrown as a CallbackException after LLVM returns to Julia.
The flags correspond to LLVM's RemapFlags:
no_module_level_changes: assume that global values and metadata don't change, so that only function-local values and metadata need to be mapped;ignore_missing_locals: keep references to function-local values that aren't in the map (e.g., in metadata) instead of failing to map them;reuse_and_mutate_distinct_mds: map distinct metadata to itself (updating its operands) instead of duplicating it;null_map_missing_globals: don't map global values that aren't in the map to themselves.
LLVM.map_metadata — Function
map_metadata(md::Metadata, vmap::ValueMap; [type_mapper::Function],
[materializer::Function], [flags...])Map metadata using the value map vmap, like LLVM's MapMetadata, and return the result, or nothing if it cannot be mapped. Uniqued nodes are rebuilt if any of their operands change, while distinct nodes are duplicated (unless reuse_and_mutate_distinct_mds is set), so to keep a distinct node, like a compile unit, map it to itself first: vmap.metadata[cu] = cu. The values that metadata refers to are mapped like map_value does, and the keyword arguments are the same as those of map_value.
LLVM.clone_into! — Function
clone_into!(new::LLVM.Function, old::LLVM.Function; [suffix::AbstractString],
[value_map::Union{ValueMap,AbstractDict{<:Value,<:Value}}],
[changes::LLVM.CloneFunctionChangeType.T],
[type_mapper::Function],
[materializer::Function])Clone the contents of a function old into a new function new. The value_map maps values of the old function to values to use in the new function, and must map the parameters of old, while suffix is appended to the names of all values cloned. The type_mapper and materializer functions can be used to respectively map types and materialize values on demand, as with map_value.
If value_map is a ValueMap, it is extended with the mappings that are made (e.g., of the instructions of old to those of new), and the functions and global values it maps are used when cloning, which makes it possible to rebuild several functions and global values that refer to each other. Other dictionaries are not modified.
Exceptions thrown by either callback are captured and rethrown as a CallbackException after LLVM returns to Julia. As with map_value, the type mapper is called before anything is cloned, with the types that LLVM maps: those of the constants and metadata that old refers to (including its personality function and prefix and prologue data), and those of its instructions, including the allocated types of allocas, the source and result element types of GEPs, the parameter types of calls, the function types of inline assembly, and the types of call attributes like byval. If it throws, nothing is cloned. A materializer that throws leaves new and the map with constants that are mapped to poison, so they should be discarded.
The changes argument determines how this function behaves; refer to the LLVM documentation of CloneFunctionInto for more details.
LLVM.clone — Function
clone(f::Function; [value_map::AbstractDict{Value,Value}])Simpler version of clone_into! that clones a function f into a new function, optionally mapping values according to the value_map dictionary.
clone(bb::BasicBlock]; dest=parent(bb), [suffix::AbstractString],
[value_map::AbstractDict{Value,Value}])Clone a basic block bb by copying all instructions. The new block is inserted at the end of the parent function; this can be altered by setting dest to a different function, or to nothing to create a detached block. The suffix is appended to the name of the cloned basic block.
This function only remaps values that are defined in the cloned basic block. Values defined outside the basic block (e.g. function arguments) are not remapped by default. This means that the cloned basic block can generally only be used within the same function that it was cloned from, unless you manually remap other values. This can be done passing a value_map dictionary.
Control flow
LLVM.split_block! — Function
split_block!(pos::InsertionPoint{Instruction}, name::AbstractString="")
split_block!(inst::Instruction, name::AbstractString="")Split a basic block in two at an insertion point, or before an instruction: the instructions from there on move to a new block with the given name, which is inserted after the original one, and which the original block branches to unconditionally:
head: ; the original block, up to the split point
br label %name
name: ; the returned block, with the rest of the original one
...Returns the new block. The PHI nodes in the successors of the original block are updated to refer to the new block. The block must have a terminator, and the split point must not be at the end of the block or before an exception-handling pad. It can be before a PHI node, as in LLVM, but the PHI nodes that move to the new block are not updated: their only predecessor is the original block now, so the caller has to fix them. Debug records at the split point move along when the insertion point is at their head (e.g., LLVM.after(prev)), and stay in the original block otherwise (e.g., LLVM.before(inst)).
Analyses, like the dominator tree, are not updated.
This uses LLVM's BasicBlock::splitBasicBlock.
LLVM.split_block_and_insert_if_then! — Function
split_block_and_insert_if_then!(cond::Value, at::Instruction; unreachable=false,
weights=nothing, domtree=nothing)Split the block containing the instruction at before it, and make the first part branch on the i1 value cond to a new block, which branches to the second part, the tail (or, with unreachable=true, ends with an unreachable instruction):
head: ; the original block, up to `at`
br i1 %cond, label %then, label %tail
then:
br label %tail ; the returned terminator
tail: ; `at` and the rest of the original block
...Returns the terminator of the new block, before which instructions can be inserted. The instruction at must not be an exception-handling pad. It can be a PHI node, as in LLVM, but the PHI nodes in the tail are not updated for its new predecessors, so the caller has to fix them (e.g., by creating a PHI node first, to split before it and add its incoming values afterwards). The new branches get the debug location of at.
The weights of the conditional branch, a pair of integers for when cond is true and when it is false (e.g., (1, 9) when the new block is rarely executed), are attached to it as !prof branch-weight metadata.
If a dominator tree of the function containing at is passed as domtree, it is updated for the new control flow, so that a pass can preserve it (e.g., with PreservedAnalyses(DomTree)). Other analyses, like LoopInfo, are not updated, and the control-flow graph has changed, so CFGAnalyses are not preserved.
This uses LLVM's SplitBlockAndInsertIfThen.
See also: split_block_and_insert_if_then_else!.
LLVM.split_block_and_insert_if_then_else! — Function
split_block_and_insert_if_then_else!(cond::Value, at::Instruction; weights=nothing,
domtree=nothing)Split the block containing the instruction at before it, and make the first part branch on the i1 value cond to one of two new blocks, which both branch to the second part, the tail:
head: ; the original block, up to `at`
br i1 %cond, label %then, label %else
then:
br label %tail ; the first returned terminator
else:
br label %tail ; the second returned terminator
tail: ; `at` and the rest of the original block
...Returns the terminators of the new blocks, (then, else), before which instructions can be inserted. The weights and domtree arguments, and the requirements on at, are as for split_block_and_insert_if_then!.
This uses LLVM's SplitBlockAndInsertIfThenElse.
Promoting memory to registers
LLVM.is_promotable — Function
is_promotable(alloca::AllocaInst)Check whether promote_mem_to_reg! can replace the alloca by SSA values: it is only used by non-volatile loads and stores of the allocated value, and by lifetime markers and debug information.
LLVM.promote_mem_to_reg! — Function
promote_mem_to_reg!(alloca::AllocaInst, domtree::DomTree; [assumptions::AssumptionCache])
promote_mem_to_reg!(allocas::AbstractVector{<:AllocaInst}, domtree::DomTree;
[assumptions::AssumptionCache])Promote allocas to SSA values, inserting phi nodes where needed, like the mem2reg pass does for every promotable alloca of a function. The allocas must be promotable (see is_promotable) and part of the same function, and domtree must be an up-to-date dominator tree of that function, e.g., am[DomTree] in a custom pass. If given, assumptions must be the assumption cache of that function. Throws an ArgumentError, without changing anything, if these requirements are not met, except for whether the tree is up to date and whether assumptions is one of that function, which are not checked.
The CFG is not changed, so domtree remains valid. The allocas are erased, together with the instructions that use them (loads, stores, lifetime markers and the casts and GEPs they use, debug intrinsics): their wrappers must not be used anymore.
Inlining
LLVM.inline! — Function
inline!(call::CallBase; merge_attributes=false, insert_lifetime=true)Inline the function that is called by a call or invoke instruction at this call site: the body of the function is copied into the calling function, replacing the call. Returns nothing if the call was inlined, after which the call instruction is erased, or a message that describes why it could not be inlined, in which case the IR is unchanged:
reason = inline!(call)
reason === nothing || @debug "Could not inline $call" reasonThis is LLVM's InlineFunction, which only refuses call sites that it cannot transform: indirect calls and calls of declarations, callbr instructions, calls with operand bundles it doesn't support, and callers whose garbage collector or exception-handling personality is incompatible with that of the called function. It does not check whether the body of the called function can be inlined at all: e.g., inlining a function that calls llvm.va_start into a function without variadic arguments results in invalid IR. Check that first with inline_viability, unless the functions are known to be fine. Whether inlining is worth it (its cost, noinline attributes, or a definition that may be replaced at link or load time) is up to the caller as well.
With merge_attributes=true, the attributes of the calling function are updated for the inlined code (e.g., to combine their target features or stack protection). With insert_lifetime=true, the static allocas of the inlined function are marked with lifetime intrinsics. No call graph is updated, and no alias analysis is used.
LLVM.inline_viability — Function
inline_viability(f::LLVM.Function)Check whether the body of a function can be inlined into other functions: returns nothing if it can, or a message that describes why not, e.g., because it calls llvm.va_start, uses indirectbr or blockaddress, calls itself, or calls a returns_twice function like setjmp. This is what inline! expects the caller to check. The check is conservative: a rejected function can sometimes still be inlined correctly (e.g., one level of a recursive function).
This uses LLVM's isInlineViable.