Function reference

Running the interpreter

@interpret f(args; kwargs...)

Evaluate f on the specified arguments using the interpreter.


julia> a = [1, 7]
2-element Vector{Int64}:

julia> sum(a)

julia> @interpret sum(a)

Frame creation

frame = Frame(mod::Module, ex::Expr)

Construct a Frame to evaluate ex in module mod.

This constructor can error, for example if lowering ex results in an :error or :incomplete expression, or if it otherwise fails to return a :thunk.

ExprSplitter(mod::Module, ex::Expr; lnn=nothing)

Create an iterable that returns individual expressions together with their module of evaluation. Optionally supply an initial LineNumberNode lnn.


julia> expr = quote
           public(x::Integer) = true
           module Private
           private(y::String) = false
           const threshold = 0.1

julia> for (mod, ex) in ExprSplitter(Main, expr)
           @show mod ex
mod = Main
ex = quote
    #= REPL[7]:2 =#
    public(x::Integer) = begin
            #= REPL[7]:2 =#
mod = Main.Private
ex = quote
    #= REPL[7]:4 =#
    private(y::String) = begin
            #= REPL[7]:4 =#
mod = Main
ex = :($(Expr(:toplevel, :(()), :(const threshold = 0.1))))

Note that Main.Private was created for you so that its internal expressions could be evaluated. ExprSplitter will check to see whether the module already exists and if so return it rather than try to create a new module with the same name.

In general each returned expression is a block with two parts: a LineNumberNode followed by a single expression. In some cases the returned expression may be :toplevel, as shown in the const declaration, but otherwise it will be a :block.

World age, frame creation, and evaluation

The primary purpose of ExprSplitter is to allow sequential return to top-level (e.g., the REPL) after evaluation of each expression. Returning to top-level allows the world age to update, and hence allows one to call methods and use types defined in earlier expressions in a block.

For evaluation by JuliaInterpreter, the returned module/expression pairs can be passed directly to the Frame constructor. However, some expressions cannot be converted into Frames and may need special handling:

julia> for (mod, ex) in ExprSplitter(Main, expr)
           if ex.head === :global
               # global declarations can't be lowered to a CodeInfo.
               # In this demo we choose to evaluate them, but you can do something else.
               Core.eval(mod, ex)
           frame = Frame(mod, ex)
           debug_command(frame, :c, true)

julia> threshold

julia> public(3)

If you're parsing package code, ex might be a docstring-expression; you may wish to check for such expressions and take distinct actions.

See Frame(mod::Module, ex::Expr) for more information about frame creation.

frame = enter_call(f, args...; kwargs...)

Build a Frame ready to execute f with the specified positional and keyword arguments.


julia> mymethod(x) = x+1
mymethod (generic function with 1 method)

julia> JuliaInterpreter.enter_call(mymethod, 1)
Frame for mymethod(x) in Main at none:1
  1* 1  1 ─ %1 = x + 1
  2  1  └──      return %1
x = 1

julia> mymethod(x::Vector{T}) where T = 1
mymethod (generic function with 2 methods)

julia> JuliaInterpreter.enter_call(mymethod, [1.0, 2.0])
Frame for mymethod(x::Vector{T}) where T in Main at none:1
  1* 1  1 ─     return 1
x = [1.0, 2.0]
T = Float64

For a @generated function you can use enter_call((f, true), args...; kwargs...) to execute the generator of a @generated function, rather than the code that would be created by the generator.

See enter_call_expr for a similar approach based on expressions.

frame = enter_call_expr(expr; enter_generated=false)

Build a Frame ready to execute the expression expr. Set enter_generated=true if you want to execute the generator of a @generated function, rather than the code that would be created by the generator.


julia> mymethod(x) = x+1
mymethod (generic function with 1 method)

julia> JuliaInterpreter.enter_call_expr(:($mymethod(1)))
Frame for mymethod(x) in Main at none:1
  1* 1  1 ─ %1 = x + 1
  2  1  └──      return %1
x = 1

julia> mymethod(x::Vector{T}) where T = 1
mymethod (generic function with 2 methods)

julia> a = [1.0, 2.0]
2-element Vector{Float64}:

julia> JuliaInterpreter.enter_call_expr(:($mymethod($a)))
Frame for mymethod(x::Vector{T}) where T in Main at none:1
  1* 1  1 ─     return 1
x = [1.0, 2.0]
T = Float64

See enter_call for a similar approach not based on expressions.

frun, allargs = prepare_args(fcall, fargs, kwargs)

Prepare the complete argument sequence for a call to fcall. fargs = [fcall, args...] is a list containing both fcall (the #self# slot in lowered code) and the positional arguments supplied to fcall. kwargs is a list of keyword arguments, supplied either as list of expressions :(kwname=kwval) or pairs :kwname=>kwval.

For non-keyword methods, frun === fcall, but for methods with keywords frun will be the keyword-sorter function for fcall.


julia> mymethod(x) = 1
mymethod (generic function with 1 method)

julia> mymethod(x, y; verbose=false) = nothing
mymethod (generic function with 2 methods)

julia> JuliaInterpreter.prepare_args(mymethod, [mymethod, 15], ())
(mymethod, Any[mymethod, 15])

julia> JuliaInterpreter.prepare_args(mymethod, [mymethod, 1, 2], [:verbose=>true])
(var"#mymethod##kw"(), Any[var"#mymethod##kw"(), (verbose = true,), mymethod, 1, 2])
framecode, frameargs, lenv, argtypes = prepare_call(f, allargs; enter_generated=false)

Prepare all the information needed to execute lowered code for f given arguments allargs. f and allargs are the outputs of prepare_args. For @generated methods, set enter_generated=true if you want to extract the lowered code of the generator itself.

On return framecode is the FrameCode of the method. frameargs contains the actual arguments needed for executing this frame (for generators, this will be the types of allargs); lenv is the "environment", i.e., the static parameters for f given allargs. argtypes is the Tuple-type for this specific call (equivalent to the signature of the MethodInstance).


julia> mymethod(x::Vector{T}) where T = 1
mymethod (generic function with 1 method)

julia> framecode, frameargs, lenv, argtypes = JuliaInterpreter.prepare_call(mymethod, [mymethod, [1.0,2.0]]);

julia> framecode
  1  1  1 ─     return 1

julia> frameargs
2-element Vector{Any}:
 mymethod (generic function with 1 method)
 [1.0, 2.0]

julia> lenv

julia> argtypes
Tuple{typeof(mymethod), Vector{Float64}}
framecode, lenv = get_call_framecode(fargs, parentframe::FrameCode, idx::Int)

Return the framecode and environment for a call specified by fargs = [f, args...] (see prepare_args). parentframecode is the caller, and idx is the program-counter index. If possible, framecode will be looked up from the local method tables of parentframe.

optimize!(code::CodeInfo, mod::Module)

Perform minor optimizations on the lowered AST in code to reduce execution time of the interpreter. Currently it looks up GlobalRefs (for which it needs mod to know the scope in which this will run) and ensures that no statement includes nested :call expressions (splitting them out into multiple SSA-form statements if needed).


Frame traversal

Frame execution


Compiled is a trait indicating that any :call expressions should be evaluated using Julia's normal compiled-code evaluation. The alternative is to pass stack=Frame[], which will cause all calls to be evaluated via the interpreter.

pc = step_expr!(recurse, frame, istoplevel=false)
pc = step_expr!(frame, istoplevel=false)

Execute the next statement in frame. pc is the new program counter, or nothing if execution terminates, or a BreakpointRef if execution hits a breakpoint.

recurse controls call evaluation; recurse = Compiled() evaluates :call expressions by normal dispatch. The default value recurse = finish_and_return! will use recursive interpretation.

If you are evaluating frame at module scope you should pass istoplevel=true.

pc = finish!(recurse, frame, istoplevel=false)
pc = finish!(frame, istoplevel=false)

Run frame until execution terminates. pc is either nothing (if execution terminates when it hits a return statement) or a reference to a breakpoint. In the latter case, leaf(frame) returns the frame in which it hit the breakpoint.

recurse controls call evaluation; recurse = Compiled() evaluates :call expressions by normal dispatch, whereas the default recurse = finish_and_return! uses recursive interpretation.

ret = finish_stack!(recurse, frame, rootistoplevel=false)
ret = finish_stack!(frame, rootistoplevel=false)

Unwind the callees of frame, finishing each before returning to the caller. frame itself is also finished. rootistoplevel should be true if the root frame is top-level.

ret is typically the returned value. If execution hits a breakpoint, ret will be a reference to the breakpoint.

ret = get_return(frame)

Get the return value of frame. Throws an error if frame.pc does not point to a return expression. frame must have already been executed so that the return value has been computed (see, e.g., JuliaInterpreter.finish!).

pc = next_until!(predicate, recurse, frame, istoplevel=false)
pc = next_until!(predicate, frame, istoplevel=false)

Execute the current statement. Then step through statements of frame until the next statement satisfies predicate(frame). pc will be the index of the statement at which evaluation terminates, nothing (if the frame reached a return), or a BreakpointRef.

pc = maybe_next_until!(predicate, recurse, frame, istoplevel=false)
pc = maybe_next_until!(predicate, frame, istoplevel=false)

Like next_until! except checks predicate before executing the current statment.

pc = through_methoddef_or_done!(recurse, frame)
pc = through_methoddef_or_done!(frame)

Runs frame at top level until it either finishes (e.g., hits a return statement) or defines a new method.

ret = evaluate_call!(Compiled(), frame::Frame, call_expr)
ret = evaluate_call!(recurse,    frame::Frame, call_expr)

Evaluate a :call expression call_expr in the context of frame. The first causes it to be executed using Julia's normal dispatch (compiled code), whereas the second recurses in via the interpreter. recurse has a default value of JuliaInterpreter.finish_and_return!.

ret = maybe_evaluate_builtin(frame, call_expr, expand::Bool)

If call_expr is to a builtin function, evaluate it, returning the result inside a Some wrapper. Otherwise, return call_expr.

If expand is true, Core._apply_iterate calls will be resolved as a call to the applied function.

pc = next_call!(recurse, frame, istoplevel=false)
pc = next_call!(frame, istoplevel=false)

Execute the current statement. Continue stepping through frame until the next :return or :call expression.

pc = maybe_next_call!(recurse, frame, istoplevel=false)
pc = maybe_next_call!(frame, istoplevel=false)

Return the current program counter of frame if it is a :return or :call expression. Otherwise, step through the statements of frame until the next :return or :call expression.

pc = next_line!(recurse, frame, istoplevel=false)
pc = next_line!(frame, istoplevel=false)

Execute until reaching the first call of the next line of the source code. Upon return, pc is either the new program counter, nothing if a return is reached, or a BreakpointRef if it encountered a wrapper call. In the latter case, call leaf(frame) to obtain the new execution frame.

pc = until_line!(recurse, frame, line=nothing istoplevel=false)
pc = until_line!(frame, line=nothing, istoplevel=false)

Execute until the current frame reaches a line greater than line. If line == nothing execute until the current frame reaches any line greater than the current line.

ret = maybe_reset_frame!(recurse, frame, pc, rootistoplevel)

Perform a return to the caller, or descend to the level of a breakpoint. pc is the return state from the previous command (e.g., next_call! or similar). rootistoplevel should be true if the root frame is top-level.

ret will be nothing if we have just completed a top-level frame. Otherwise,

cframe, cpc = ret

where cframe is the frame from which execution should continue and cpc is the state of cframe (the program counter, a BreakpointRef, or nothing).

cframe = maybe_step_through_wrapper!(recurse, frame)
cframe = maybe_step_through_wrapper!(frame)

Return the new frame of execution, potentially stepping through "wrapper" methods like those that supply default positional arguments or handle keywords. cframe is the leaf frame from which execution should start.

frame = maybe_step_through_kwprep!(recurse, frame)
frame = maybe_step_through_kwprep!(frame)

If frame.pc points to the beginning of preparatory work for calling a keyword-argument function, advance forward until the actual call.

loc = handle_err(recurse, frame, err)

Deal with an error err that arose while evaluating frame. There are one of three behaviors:

  • if frame catches the error, loc is the program counter at which to resume evaluation of frame;
  • if frame doesn't catch the error, but break_on_error[] is true, loc is a BreakpointRef;
  • otherwise, err gets rethrown.
ret = debug_command(recurse, frame, cmd, rootistoplevel=false; line=nothing)
ret = debug_command(frame, cmd, rootistoplevel=false; line=nothing)

Perform one "debugger" command. The keyword arguments are not used for all debug commands. cmd should be one of:

  • :n: advance to the next line
  • :s: step into the next call
  • :sl step into the last call on the current line (e.g. steps into f if the line is f(g(h(x)))).
  • :until: advance the frame to line line if given, otherwise advance to the line after the current line
  • :c: continue execution until termination or reaching a breakpoint
  • :finish: finish the current frame and return to the parent

or one of the 'advanced' commands

  • :nc: step forward to the next call
  • :se: execute a single statement
  • :si: execute a single statement, stepping in if it's a call
  • :sg: step into the generator of a generated function

rootistoplevel and ret are as described for JuliaInterpreter.maybe_reset_frame!.



@breakpoint f(args...) condition=nothing
@breakpoint f(args...) line condition=nothing

Break upon entry, or at the specified line number, in the method called by f(args...). Optionally supply a condition expressed in terms of the arguments and internal variables of the method. If line is supplied, it must be a literal integer.


Suppose a method mysum is defined as follows, where the numbers to the left are the line number in the file:

12 function mysum(A)
13     s = zero(eltype(A))
14     for a in A
15         s += a
16     end
17     return s
18 end


@breakpoint mysum(A) 15 s>10

would cause execution of the loop to break whenever s>10.

breakpoint(f, [sig], [line], [condition])

Add a breakpoint to f with the specified argument types sig.¨ If sig is not given, the breakpoint will apply to all methods of f. If f is a method, the breakpoint will only apply to that method. Optionally specify an absolute line number line in the source file; the default is to break upon entry at the first line of the body. Without condition, the breakpoint will be triggered every time it is encountered; the second only if condition evaluates to true. condition should be written in terms of the arguments and local variables of f.


function radius2(x, y)
    return x^2 + y^2

breakpoint(radius2, Tuple{Int,Int}, :(y > x))
breakpoint(file, line, [condition])

Set a breakpoint in file at line. The argument file can be a filename, a partial path or absolute path. For example, file = foo.jl will match against all files with the name foo.jl, file = src/foo.jl will match against all paths containing src/foo.jl, e.g. both Foo/src/foo.jl and Bar/src/foo.jl. Absolute paths only matches against the file with that exact absolute path.


Remove (delete) breakpoint bp. Removed breakpoints cannot be re-enabled.


Remove all breakpoints.


Turn on automatic breakpoints when any of the conditions described in states occurs. The supported states are:

  • :error: trigger a breakpoint any time an uncaught exception is thrown
  • :throw : trigger a breakpoint any time a throw is executed (even if it will eventually be caught)
bpref = dummy_breakpoint(recurse, frame::Frame, istoplevel)

Return a fake breakpoint. dummy_breakpoint can be useful as the recurse argument to evaluate_call! (or any of the higher-order commands) to ensure that you return immediately after stepping into a call.




Frame represents the current execution state in a particular call frame. Fields:

  • framecode: the FrameCode for this frame.
  • framedata: the FrameData for this frame.
  • pc: the program counter (integer index of the next statment to be evaluated) for this frame.
  • caller: the parent caller of this frame, or nothing.
  • callee: the frame called by this one, or nothing.

The Base functions show_backtrace and display_error are overloaded such that show_backtrace(io::IO, frame::Frame) and display_error(io::IO, er, frame::Frame) shows a backtrace or error, respectively, in a similar way as to how Base shows them.


FrameCode holds static information about a method or toplevel code. One FrameCode can be shared by many calling Frames.

Important fields:

  • scope: the Method or Module in which this frame is to be evaluated.
  • src: the CodeInfo object storing (optimized) lowered source code.
  • methodtables: a vector, each entry potentially stores a "local method table" for the corresponding :call expression in src (undefined entries correspond to statements that do not contain :call expressions).
  • used: a BitSet storing the list of SSAValues that get referenced by later statements.

FrameData holds the arguments, local variables, and intermediate execution state in a particular call frame.

Important fields:

  • locals: a vector containing the input arguments and named local variables for this frame. The indexing corresponds to the names in the slotnames of the src. Use locals to extract the current value of local variables.
  • ssavalues: a vector containing the Static Single Assignment values produced at the current state of execution.
  • sparams: the static type parameters, e.g., for f(x::Vector{T}) where T this would store the value of T given the particular input x.
  • exception_frames: a list of indexes to catch blocks for handling exceptions within the current frame. The active handler is the last one on the list.
  • last_exception: the exception thrown by this frame or one of its callees.

Represents a case where no exceptions are thrown yet. End users will not see this singleton type, otherwise it usually means there is missing error handling in the interpretation process.

BreakpointState(isactive=true, condition=JuliaInterpreter.truecondition)

BreakpointState represents a breakpoint at a particular statement in a FrameCode. isactive indicates whether the breakpoint is currently enabled or disabled. condition is a function that accepts a single Frame, and condition(frame) must return either true or false. Execution will stop at a breakpoint only if isactive and condition(frame) both evaluate as true. The default condition always returns true.

To create these objects, see breakpoint.

BreakpointRef(framecode, stmtidx)
BreakpointRef(framecode, stmtidx, err)

A reference to a breakpoint at a particular statement index stmtidx in framecode. If the break was due to an error, supply that as well.

Commands that execute complex control-flow (e.g., next_line!) may also return a BreakpointRef to indicate that the execution stack switched frames, even when no breakpoint has been set at the corresponding statement.


AbstractBreakpoint is the abstract type that is the supertype for breakpoints. Currently, the concrete breakpoint types BreakpointSignature and BreakpointFileLocation exist.

Common fields shared by the concrete breakpoints:

  • condition::Union{Nothing,Expr,Tuple{Module,Expr}}: the condition when the breakpoint applies . nothing means unconditionally, otherwise when the Expr (optionally in Module).
  • enabled::Ref{Bool}: If the breakpoint is enabled (should not be directly modified, use enable() or disable()).
  • instances::Vector{BreakpointRef}: All the BreakpointRef that the breakpoint has applied to.
  • line::Int The line of the breakpoint (equal to 0 if unset).

See BreakpointSignature and BreakpointFileLocation for additional fields in the concrete types.


A BreakpointSignature is a breakpoint that is set on methods or functions.


  • f::Union{Method, Function, Type}: A method or function that the breakpoint should apply to.
  • sig::Union{Nothing, Type}: if f is a Method, always equal to nothing. Otherwise, contains the method signature as a tuple type for what methods the breakpoint should apply to.

For common fields shared by all breakpoints, see AbstractBreakpoint.


A BreakpointFileLocation is a breakpoint that is set on a line in a file.


  • path::String: The literal string that was used to create the breakpoint, e.g. "path/file.jl".
  • abspath::String: The absolute path to the file when the breakpoint was created, e.g. "/Users/Someone/path/file.jl".

For common fields shared by all breakpoints, see AbstractBreakpoint.


Internal storage


genframedict[(method,argtypes)] returns the FrameCode for a @generated method method, for the particular argument types argtypes.

The framecodes stored in genframedict are for the code returned by the generator (i.e, what will run when you call the method on particular argument types); for the generator itself, its framecode would be stored in framedict.



eval_code(frame::Frame, code::Union{String, Expr})

Evaluate code in the context of frame, updating any local variables (including type parameters) that are reassigned in code, however, new local variables cannot be introduced.

julia> foo(x, y) = x + y;

julia> frame = JuliaInterpreter.enter_call(foo, 1, 3);

julia> JuliaInterpreter.eval_code(frame, "x + y")

julia> JuliaInterpreter.eval_code(frame, "x = 5");

julia> JuliaInterpreter.finish_and_return!(frame)

When variables are captured in closures (and thus gets wrapped in a Core.Box) they will be automatically unwrapped and rewrapped upon evaluating them:

julia> function capture()
           x = 1
           f = ()->(x = 2) # x captured in closure and is thus a Core.Box

julia> frame = JuliaInterpreter.enter_call(capture);

julia> JuliaInterpreter.step_expr!(frame);

julia> JuliaInterpreter.step_expr!(frame);

julia> JuliaInterpreter.locals(frame)
2-element Vector{JuliaInterpreter.Variable}:
 #self# = capture
 x = Core.Box(1)

julia> JuliaInterpreter.eval_code(frame, "x")

julia> JuliaInterpreter.eval_code(frame, "x = 2")

julia> JuliaInterpreter.locals(frame)
2-element Vector{JuliaInterpreter.Variable}:
 #self# = capture
 x = Core.Box(2)

"Special" values like SSA values and slots (shown in lowered code as e.g. %3 and @_4 respectively) can be evaluated using the syntax var"%3" and var"@_4" respectively.

rhs = @lookup(frame, node)
rhs = @lookup(mod, frame, node)

This macro looks up previously-computed values referenced as SSAValues, SlotNumbers, GlobalRefs, QuoteNode, sparam or exception reference expression. It will also lookup symbols in moduleof(frame); this can be supplied ahead-of-time via the 3-argument version. If none of the above apply, the value of node will be returned.


Determine whether we are calling a function for which the current function is a wrapper (either because of optional arguments or because of keyword arguments).

loc = whereis(frame, pc::Int=frame.pc; macro_caller=false)

Return the file and line number for frame at pc. If this cannot be determined, loc == nothing. Otherwise loc == (filepath, line).

By default, any statements expanded from a macro are attributed to the macro definition, but withmacro_caller=true you can obtain the location within the method that issued the macro.

line = linenumber(framecode, pc)

Return the "static" line number at statement index pc. The static line number is the location at the time the method was most recently defined. See CodeTracking.whereis for dynamic line information.


Variable is a struct representing a variable with an asigned value. By calling the function locals on a Frame a Vector of Variable's is returned.

Important fields:

  • value::Any: the value of the local variable.
  • name::Symbol: the name of the variable as given in the source code.
  • isparam::Bool: if the variable is a type parameter, for example T in f(x::T) where {T} = x.
  • is_captured_closure::Bool: if the variable has been captured by a closure
method = whichtt(tt)

Like which except it operates on the complete tuple-type tt, and doesn't throw when there is no matching method.




Register a one-argument function to be called after any update to the set of all breakpoints. This includes their creation, deletion, enabling and disabling.

The function f should take two inputs:

  • First argument is the function doing to update, this is provided to allow to dispatch on its type. It will be one:
    • ::typeof(breakpoint) for the creation,
    • ::typeof(remove) for the deletion.
    • ::typeof(update_states) for disable/enable/toggleing
  • Second argument is the breakpoint object that was changed.

If only desiring to handle some kinds of update, f should have fallback methods to do nothing in the general case.


This feature is experimental, and may be modified or removed in a minor release.

firehooks(hooked_fun, bp::AbstractBreakpoint)

Trigger all hooks that were registered with on_breakpoints_updated, passing them the hooked_fun and the bp. This should be called whenever the set of breakpoints is updated. hooked_fun is the function doing the update, and bp is the relevent breakpoint being updated after the update is applied.


This feature is experimental, and may be modified or removed in a minor release.