API Reference

This page provides a comprehensive reference for IRTools functionality.

Reflection

IRTools.@code_irMacro
@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.metaFunction
meta(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.@metaMacro
@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.IRType
IR()
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.StatementType
Statement(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.VariableType
Variable(id::Integer)
var(id::Integer)

Represents an SSA variable. Primarily used as an index into IR objects.

IRTools.BranchType
Branch(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.BasicBlockType
BasicBlock(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.argumentsFunction
arguments(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.argtypesFunction
argtypes(bb::BasicBlock)

Return the argument types of the basic block bb. (These are the arguments given by the branch to this block.)

IRTools.branchesFunction
branches(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.isreturnFunction
isreturn(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.returnvalueFunction
returnvalue(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.canbranchFunction
canbranch(b::Block)

Check whether adding a branch to block b will be valid (i.e, the last existing branch is not conditional).

IRTools.explicitbranch!Function
explicitbranch!(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!Function
argument!(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!Function
emptyargs!(b::Block)

Delete all arguments from block b, and automatically remove them from all branches to this block.

IRTools.deletearg!Function
deletearg!(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!Function
block!(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!Function
deleteblock!(ir::IR, i)

Delete the block at position i. Branches in all other blocks are updated to preserve the original behaviour.

IRTools.blockFunction
block(ir, i)

Return the i-th Block of ir.

IRTools.blocksFunction
blocks(ir)

Return the list of blocks Block of ir.

IRTools.branch!Function
branch!(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!Function
return!(block, x)
return!(ir, x)

Add to block or the last block of ir a return branch with argument x, and return it.

IRTools.successorsFunction
successors(b::Block)

Returns all Blocks 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.predecessorsFunction
predecessors(b::Block)

Returns all Blocks 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

Base.keysFunction
keys(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.haskeyFunction
haskey(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!Function
push!(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!Function
pushfirst!(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!Function
insert!(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!Function
insertafter!(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!Function
permute!(ir::IR, perm::AbstractVector)

Permutes block order in-place, keeping track of internal references (like branch targets).

Base.emptyFunction
empty(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.PipeType
Pipe(ir)

In general, it is not efficient to insert statements into IR; only appending is fast, for the same reason as with Vectors.

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).