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: ...
source
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
source
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
source

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

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

source
Statement(stmt::Statement; expr, type, line)

Copy of stmt with optionally updated fields.

source
IRTools.VariableType
Variable(id::Integer)
var(id::Integer)

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

source
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

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

source
IRTools.argumentsFunction
arguments(br::Branch)

Return the argument vector of the branch br. (These are the arguments passed to a jumped-to block.)

source
arguments(bb::BasicBlock)

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

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

source
IRTools.branchesFunction
branches(bb::BasicBlock)

Return the vector of branching instructions in block bb.

source
branches(b::Block, c::Block)

Return the vector of all branches from block b to block c.

source
IRTools.isreturnFunction
isreturn(br::Branch)

Check whether br has the form of a return branch (see Branch).

source
isreturn(b::Block)

Check whether the block b has a return branch.

source
IRTools.returnvalueFunction
returnvalue(b::Branch)

Get the return value of b (the first argument of the branch).

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

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

source
IRTools.emptyargs!Function
emptyargs!(b::Block)

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

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

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

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

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

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

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

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

source
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

source
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

source
IRTools.inlineFunction
inline(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
source
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
source
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
source
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
source
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
source
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
source
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
source
Base.permute!Function
permute!(ir::IR, perm::AbstractVector)

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

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

source