API Reference
This page provides a comprehensive reference for IRTools functionality.
Reflection
IRTools.@code_ir
— Macro@code_ir f(args...)
Convenience macro similar to @code_lowered
or @code_typed
. Retrieves the IR for the given function call.
julia> @code_ir gcd(10, 5)
1: (%1, %2, %3)
%4 = %2 == 0
br 4 unless %4
2: ...
IRTools.meta
— Functionmeta(Tuple{...})
Construct metadata for a given method signature. Metadata can then be used to construct IR
or used to perform other reflection on the method.
See also @meta
.
julia> IRTools.meta(Tuple{typeof(gcd),Int,Int})
Metadata for gcd(a::T, b::T) where T<:Union{Int128, Int16, Int32, Int64, Int8, UInt128, UInt16, UInt32, UInt64, UInt8} in Base at intfuncs.jl:31
IRTools.@meta
— Macro@meta f(args...)
Convenience macro for retrieving metadata without writing a full type signature.
julia> IRTools.@meta gcd(10, 5)
Metadata for gcd(a::T, b::T) where T<:Union{Int128, Int16, Int32, Int64, Int8, UInt128, UInt16, UInt32, UInt64, UInt8} in Base at intfuncs.jl:31
IR Manipulation
IRTools.IR
— TypeIR()
IR(metadata; slots = false)
Represents a fragment of SSA-form code.
IR can be constructed from scratch, but more usually an existing Julia method is used as a starting point (see meta
for how to get metadata for a method). The slots
argument determines whether the IR preserves mutable variable slots; by default, these are converted to SSA-style variables.
As a shortcut, IR can be constructed directly from a type signature, e.g.
julia> IR(typeof(gcd), Int, Int)
1: (%1, %2, %3)
%4 = %2 == 0
br 4 unless %4
2: ...
See also: code_ir
IRTools.Statement
— TypeStatement(expr; type, line)
stmt(expr; type, line)
Represents a single statement in the IR. The expr
is a non-nested Julia expression (Expr
). type
represents the return type of the statement; in most cases this can be ignored and defaults to Any
. line
represents the source location of the statement; it is an integer index into the IR's line table.
As a convenience, if expr
is already a statement, the new statement will inherit its type and line number.
Statement(stmt::Statement; expr, type, line)
Copy of stmt
with optionally updated fields.
IRTools.Variable
— TypeVariable(id::Integer)
var(id::Integer)
Represents an SSA variable. Primarily used as an index into IR
objects.
IRTools.Branch
— TypeBranch(condition::Any, block::Int, args::Vector{Any})
Represents a generalized branching instruction, consisting of a condition
(usually a Variable
of boolean type), a target block
, and a vector of args
passed to the jump target. There are three types of branches that exist by the following convention:
- A return branch is represented by
Branch(nothing, 0, [<return value>])
. - An unconditional branch is represented by
Branch(nothing, <target>, [<optional arguments ...>])
- A conditional branch is represented by
Branch(<condition>, <target>, [<optional arguments ...>])
See also: branch!
, return!
, isreturn
, isconditional
IRTools.BasicBlock
— TypeBasicBlock(stmts::Vector{Statement}, args::Vector{Any},
argtypes::Vector{Any}, branches::Vector{Branch})
BasicBlock([stmts])
Represents a basic block of code in SSA form. A block consists of a list of statements, followed by optional branching instructions and arguments, with optional types.
IRTools.arguments
— Functionarguments(br::Branch)
Return the argument vector of the branch br
. (These are the arguments passed to a jumped-to block.)
arguments(bb::BasicBlock)
Return the argument vector of the basic block bb
. (These are the arguments given by the branch to this block.)
IRTools.argtypes
— Functionargtypes(bb::BasicBlock)
Return the argument types of the basic block bb
. (These are the arguments given by the branch to this block.)
IRTools.branches
— Functionbranches(bb::BasicBlock)
Return the vector of branching instructions in block bb
.
branches(b::Block, c::Block)
Return the vector of all branches from block b
to block c
.
IRTools.isconditional
— Functionisconditional(br::Branch)
Check whether br
has the form of a conditional branch (see Branch
).
IRTools.isreturn
— Functionisreturn(br::Branch)
Check whether br
has the form of a return branch (see Branch
).
isreturn(b::Block)
Check whether the block b
has a return branch.
IRTools.returnvalue
— Functionreturnvalue(b::Branch)
Get the return value of b
(the first argument of the branch).
returnvalue(b::Block)
Retreive the return value of a block.
julia> f(x) = 3x + 2;
julia> IRTools.block(@code_ir(f(1)), 1)
1: (%1, %2)
%3 = 3 * %2
%4 = %3 + 2
return %4
julia> IRTools.returnvalue(ans)
%4
IRTools.returntype
— Functionreturnvalue(b::Block)
Retreive the return type of a block.
IRTools.canbranch
— Functioncanbranch(b::Block)
Check whether adding a branch to block b
will be valid (i.e, the last existing branch is not conditional).
IRTools.explicitbranch!
— Functionexplicitbranch!(b::Block)
explicitbranch!(ir::IR)
Convert implicit fallthroughs to explicit branches to the next block (these occur when the last branch in a block is conditional):
1: (%1, %2)
%3 = %2 < 0
br 3 unless %3
2:
return 0
3:
return %2
will become
1: (%1, %2)
%3 = %2 < 0
br 3 unless %3
br 2
2:
return 0
3:
return %2
IRTools.argument!
— Functionargument!(block, [value, type]; at, insert)
argument!(ir, [value, type]; at, insert)
Create a new argument for the given block / IR fragment, and return the variable representing the argument.
julia> ir = IR();
julia> argument!(ir)
%1
julia> ir
1: (%1)
The at
keyword argument can be used to specify where the new argument should go; by default it is appended to the end of the argument list.
Unless insert = false
, if there are branches to this block, they will be updated to pass value
(nothing
by default) as an argument (by default, insert = true
).
IRTools.emptyargs!
— Functionemptyargs!(b::Block)
Delete all arguments from block b
, and automatically remove them from all branches to this block.
IRTools.deletearg!
— Functiondeletearg!(b::Block, i::Integer)
Delete the i
-th argument from block b
, and automatically remove it from all branches to this block.
deletearg!(ir::IR, i)
Delete the i
-th argument from the first block of ir
, and automatically remove it from all branches to this block.
IRTools.block!
— Functionblock!(ir::IR)
block!(ir::IR, i)
Insert a new block in ir
. If i
is given, the block is inserted at this position, otherwise it is appended at the end. Branches in all other blocks are updated to preserve the original behaviour.
IRTools.deleteblock!
— Functiondeleteblock!(ir::IR, i)
Delete the block at position i
. Branches in all other blocks are updated to preserve the original behaviour.
IRTools.block
— Functionblock(ir, i)
Return the i
-th Block
of ir
.
IRTools.blocks
— Functionblocks(ir)
Return the list of blocks Block
of ir
.
IRTools.branch!
— Functionbranch!(b::Block, block, args...; unless = nothing)
Add to block b
a new branch to block
, with arguments args
and condition unless
, and return it.
branch!(ir::IR, block, args...; unless = nothing)
Add to the last block of ir
a new branch to block
, with arguments args
and condition unless
, and return it.
IRTools.return!
— Functionreturn!(block, x)
return!(ir, x)
Add to block
or the last block of ir
a return branch with argument x
, and return it.
IRTools.successors
— Functionsuccessors(b::Block)
Returns all Block
s from which you can reach b
in one jump; basically, all x such that branches(x, b)
is non-empty. Implicit jumps by fall-through are noticed as well.
See: predecessors
IRTools.predecessors
— Functionpredecessors(b::Block)
Returns all Block
s of which b
is a successor; basically, all x such that branches(x, b)
is non-empty. Implicit jumps by fall-through are noticed as well.
See: successors
IRTools.inline
— Functioninline(ir, location, source)
Replace the function call at ir[location]
with the IR source
. The inlined IR will use the function arguments at ir[location]
as its input.
julia> foo(x, y) = max(x, y)+1
julia> ir = @code_ir foo(1, 1)
1: (%1, %2, %3)
%4 = Main.max(%2, %3)
%5 = %4 + 1
return %5
julia> inline(ir, var(4), @code_ir(max(1,1)))
1: (%1, %2, %3)
%4 = %3 < %2
%5 = Base.ifelse(%4, %2, %3)
%6 = %5 + 1
return %6
Base.keys
— Functionkeys(ir)
Return the variable keys for all statements defined in ir
.
julia> f(x) = 3x + 2;
julia> ir = @code_ir f(1)
1: (%1, %2)
%3 = 3 * %2
%4 = %3 + 2
return %4
julia> keys(ir)
2-element Array{IRTools.Variable,1}:
%3
%4
Base.haskey
— Functionhaskey(ir, var)
Check whether the variable var
was defined in ir
.
julia> f(x) = 3x + 2;
julia> ir = @code_ir f(1)
1: (%1, %2)
%3 = 3 * %2
%4 = %3 + 2
return %4
julia> haskey(ir, var(3))
true
julia> haskey(ir, var(7))
false
Base.push!
— Functionpush!(ir, x)
Append the statement or expression x
to the IR or block ir
, returning the new variable. See also pushfirst!
, insert!
.
julia> ir = IR();
julia> x = argument!(ir)
%1
julia> push!(ir, xcall(:*, x, x))
%2
julia> ir
1: (%1)
%2 = %1 * %1
Base.pushfirst!
— Functionpushfirst!(ir, x)
Insert the expression or statement x
into the given IR or block at the beginning, returning the new variable. See also push!
, insert!
.
julia> f(x) = 3x + 2
f (generic function with 1 method)
julia> ir = @code_ir f(1)
1: (%1, %2)
%3 = 3 * %2
%4 = %3 + 2
return %4
julia> pushfirst!(ir, :(println("hello, world")))
%5
julia> ir
1: (%1, %2)
%5 = println("hello, world")
%3 = 3 * %2
%4 = %3 + 2
return %4
Base.insert!
— Functioninsert!(ir, v, x)
Insert the expression or statement x
into the given IR, just before the variable v
is defined, returning the new variable for x
. See also insertafter!
.
julia> f(x) = 3x + 2
f (generic function with 1 method)
julia> ir = @code_ir f(1)
1: (%1, %2)
%3 = 3 * %2
%4 = %3 + 2
return %4
julia> insert!(ir, IRTools.var(4), :(println("hello, world")))
%5
julia> ir
1: (%1, %2)
%3 = 3 * %2
%5 = println("hello, world")
%4 = %3 + 2
return %4
IRTools.insertafter!
— Functioninsertafter!(ir, v, x)
Insert the expression or statement x
into the given IR, just before the variable v
is defined, returning the new variable for x
. See also insert!
.
julia> f(x) = 3x + 2
f (generic function with 1 method)
julia> ir = @code_ir f(1)
1: (%1, %2)
%3 = 3 * %2
%4 = %3 + 2
return %4
julia> IRTools.insertafter!(ir, IRTools.var(4), :(println("hello, world")))
%5
julia> ir
1: (%1, %2)
%3 = 3 * %2
%4 = %3 + 2
%5 = println("hello, world")
return %4
Base.permute!
— Functionpermute!(ir::IR, perm::AbstractVector)
Permutes block order in-place, keeping track of internal references (like branch targets).
Base.empty
— Functionempty(ir)
Create an empty IR fragment based on the given IR. The line number table and any metadata are preserved from the original IR.
julia> ir = empty(@code_ir gcd(10, 5))
1:
julia> ir.meta
Metadata for gcd(a::T, b::T) where T<:Union{Int128, Int16, Int32, Int64, Int8, UInt128, UInt16, UInt32, UInt64, UInt8} in Base at intfuncs.jl:31
IRTools.Pipe
— TypePipe(ir)
In general, it is not efficient to insert statements into IR; only appending is fast, for the same reason as with Vector
s.
For this reason, the Pipe
construct makes it convenient to incrementally build an new IR fragment from an old one, making efficient modifications as you go.
The general pattern looks like:
pr = IRTools.Pipe(ir)
for (v, st) in pr
# do stuff
end
ir = IRTools.finish(pr)
Iterating over pr
is just like iterating over ir
, except that within the loop, inserting and deleting statements in pr
around v
is efficient. Later, finish(pr)
converts it back to a normal IR fragment (in this case just a plain copy of the original).