Function reference

Function reference

Running the interpreter

@interpret f(args; kwargs...)

Evaluate f on the specified arguments using the interpreter.

Example

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

julia> sum(a)
8

julia> @interpret sum(a)
8
source

Frame creation

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

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

Example

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::Array{T,1}) 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.

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

Example

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 Array{Float64,1}:
 1.0
 2.0

julia> JuliaInterpreter.enter_call_expr(:($mymethod($a)))
Frame for mymethod(x::Array{T,1}) 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.

source
frame = prepare_frame(framecode::FrameCode, frameargs, lenv)

Construct a new Frame for framecode, given lowered-code arguments frameargs and static parameters lenv. See JuliaInterpreter.prepare_call for information about how to prepare the inputs.

source
framecode, frameargs, lenv, argtypes = determine_method_for_expr(expr; enter_generated = false)

Prepare all the information needed to execute a particular :call expression expr. For example, try JuliaInterpreter.determine_method_for_expr(:($sum([1,2]))). See JuliaInterpreter.prepare_call for information about the outputs.

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

Example

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])
(getfield( Symbol("#kw##mymethod"))(), Any[#kw##mymethod(), (verbose = true,), mymethod, 1, 2])
source
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).

Example

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 Array{Any,1}:
 mymethod
 [1.0, 2.0]

julia> lenv
svec(Float64)

julia> argtypes
Tuple{typeof(mymethod),Array{Float64,1}}
source
frame = prepare_thunk(mod::Module, expr::Expr)

Prepare expr for evaluation in mod. expr should be a "straightforward" expression, one that does not require special top-level handling (see JuliaInterpreter.split_expressions).

source
modexs, docexprs = split_expressions(mod::Module, expr::Expr; extract_docexprs=false)

Break expr into a list modexs of sequential blocks. This is often needed when expr needs to be evaluated at top level.

modexs[i] is a (mod::Module, ex::Expr) tuple, where ex is to be evaluated in mod.

Toplevel evaluation

For code that defines new structs, new methods, or new macros, it can be important to evaluate these expressions carefully:

stack = Frame[]
for modex in modexs    # or use `for (mod, ex) in modexs` to split the tuple
    frame = JuliaInterpreter.prepare_thunk(modex)
    while true
        JuliaInterpreter.through_methoddef_or_done!(stack, frame) === nothing && break
    end
end

The while loop here deserves some explanation. Occasionally, a frame may define new methods (e.g., anonymous or local functions) and then call those methods. In such cases, running the entire frame as a single block (e.g., with JuliaInterpreter.finish_and_return! can trigger "method is too new..." errors. Instead, the approach above runs each frame, but returns to the caller after any new method is defined. When this loop is running at top level (e.g., in the REPL), this allows the world age to update and thus avoid "method is too new..." errors.

Putting the above nested loop inside a function defeats its purpose, because inside a compiled function the world age will not update. If necessary, use the following strategy:

Core.eval(somemodule, Expr(:toplevel, quote
    body
))

where body contains the nested loop, plus any preparatory statements required to make the necessary variables available at top level in somemodule.

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

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

source

Frame traversal

JuliaInterpreter.rootFunction.
rframe = root(frame)

Return the initial frame in the call stack.

source
JuliaInterpreter.leafFunction.
lframe = leaf(frame)

Return the deepest callee in the call stack.

source

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.

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

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

source
ret = finish_and_return!(recurse, frame, istoplevel::Bool=false)
ret = finish_and_return!(frame, istoplevel::Bool=false)

Call JuliaInterpreter.finish! and pass back the return value ret. If execution pauses at a breakpoint, ret is the reference to the breakpoint.

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

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

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

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

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

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

source
ret = evaluate_foreigncall(frame::Frame, call_expr)

Evaluate a :foreigncall (from a ccall) statement callexpr in the context of frame.

source
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 calls will be resolved as a call to the applied function.

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

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

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

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

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

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

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

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

source

Breakpoints

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

Example

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

Then

@breakpoint mysum(A) 15 s>10

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

source
@bp

Insert a breakpoint at a location in the source code.

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

Example

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

breakpoint(radius2, Tuple{Int,Int}, :(y > x))
source
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.

source
enable(bp::AbstractBreakpoint)

Enable breakpoint bp.

source
enable()

Enable all breakpoints.

source
disable(bp::AbstractBreakpoint)

Disable breakpoint bp. Disabled breakpoints can be re-enabled with enable.

source
disable()

Disable all breakpoints.

source
remove(bp::AbstractBreakpoint)

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

source
remove()

Remove all breakpoints.

source
toggle(bp::AbstractBreakpoint)

Toggle breakpoint bp.

source
break_on(states...)

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)
source
break_off(states...)

Turn off automatic breakpoints when any of the conditions described in states occurs. See break_on for a description of valid states.

source
breakpoints()::Vector{AbstractBreakpoint}

Return an array with all breakpoints.

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

source

Types

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.

source

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

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

FrameInstance represents a method specialized for particular argument types.

Fields:

  • framecode: the FrameCode for the method.
  • sparam_vals: the static parameter values for the method.
source
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.

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

source

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.

source

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

Fields:

  • f::Union{Method, Function}: 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.

source

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

Fields:

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

source

Internal storage

framedict[method] returns the FrameCode for method. For @generated methods, see genframedict.

source

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.

source

meth ∈ compiled_methods indicates that meth should be run using Compiled rather than recursed into via the interpreter.

source

mod ∈ compiled_modules indicates that any method in mod should be run using Compiled rather than recursed into via the interpreter.

source

Utilities

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")
4

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

julia> JuliaInterpreter.finish_and_return!(frame)
8

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
           f()
           x
       end;

julia> frame = JuliaInterpreter.enter_call(capture);

julia> JuliaInterpreter.step_expr!(frame);

julia> JuliaInterpreter.step_expr!(frame);

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

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

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

julia> JuliaInterpreter.locals(frame)
2-element Array{JuliaInterpreter.Variable,1}:
 #self# = capture
 x = Core.Box(2)
source
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.

source

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

source
is_doc_expr(ex)

Test whether expression ex is a @doc expression.

source
is_global_ref(g, mod, name)

Tests whether g is equal to GlobalRef(mod, name).

source
CodeTracking.whereisFunction.
loc = whereis(frame, pc=frame.pc)

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

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

source
stmtidx = statementnumber(frame, line)

Return the index of the first statement in frame's CodeInfo that corresponds to static line number line.

source
framecode, stmtidx = statementnumber(method, line)

Return the index of the first statement in framecode that corresponds to the given static line number line in method.

source

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.
source
local_variables = locals(frame::Frame)::Vector{Variable}

Return the local variables as a vector of Variable.

source
method = whichtt(tt)

Like which except it operates on the complete tuple-type tt.

source