API

Signatures

LoweredCodeUtils.signatureFunction
sig = signature(sigsv::SimpleVector)

Compute a method signature from a suitable SimpleVector: sigsv[1] holds the signature and sigsv[2] the TypeVars.

Example:

For f(x::AbstractArray{T}) where T, the corresponding sigsv is constructed as

T = TypeVar(:T)
sig1 = Core.svec(typeof(f), AbstractArray{T})
sig2 = Core.svec(T)
sigsv = Core.svec(sig1, sig2)
sig = signature(sigsv)
source
sigt, lastpc = signature(recurse, frame, pc)
sigt, lastpc = signature(frame, pc)

Compute the signature-type sigt of a method whose definition in frame starts at pc. Generally, pc should point to the Expr(:method, methname) statement, in which case lastpc is the final statement number in frame that is part of the signature (i.e, the line above the 3-argument :method expression). Alternatively, pc can point to the 3-argument :method expression, as long as all the relevant SSAValues have been assigned. In this case, lastpc == pc.

If no 3-argument :method expression is found, sigt will be nothing.

source
LoweredCodeUtils.methoddef!Function
ret = methoddef!(recurse, signatures, frame; define=true)
ret = methoddef!(signatures, frame; define=true)

Compute the signature of a method definition. frame.pc should point to a :method expression. Upon exit, the new signature will be added to signatures.

There are several possible return values:

pc, pc3 = ret

is the typical return. pc will point to the next statement to be executed, or be nothing if there are no further statements in frame. pc3 will point to the 3-argument :method expression.

Alternatively,

pc = ret

occurs for "empty method" expressions, e.g., :(function foo end). pc will be nothing.

By default the method will be defined (evaluated). You can prevent this by setting define=false. This is recommended if you are simply extracting signatures from code that has already been evaluated.

source
LoweredCodeUtils.rename_framemethods!Function
methranges = rename_framemethods!(frame)
methranges = rename_framemethods!(recurse, frame)

Rename the gensymmed methods in frame to match those that are currently active. The issues are described in https://github.com/JuliaLang/julia/issues/30908. frame will be modified in-place as needed.

Returns a vector of name=>start:stop pairs specifying the range of lines in frame at which method definitions occur. In some cases there may be more than one method with the same name in the start:stop range.

source
LoweredCodeUtils.bodymethodFunction
mbody = bodymethod(m::Method)

Return the "body method" for a method m. mbody contains the code of the function body when m was defined.

source

Edges

LoweredCodeUtils.CodeEdgesType
edges = CodeEdges(src::CodeInfo)

Analyze src and determine the chain of dependencies.

  • edges.preds[i] lists the preceding statements that statement i depends on.
  • edges.succs[i] lists the succeeding statements that depend on statement i.
  • edges.byname[v] returns information about the predecessors, successors, and assignment statements for an object v::Union{GlobalRef, Symbol}.
source
LoweredCodeUtils.lines_requiredFunction
isrequired = lines_required(obj::Union{GlobalRef, Symbol}, src::CodeInfo, edges::CodeEdges)
isrequired = lines_required(idx::Int,                     src::CodeInfo, edges::CodeEdges)

Determine which lines might need to be executed to evaluate obj or the statement indexed by idx. If isrequired[i] is false, the ith statement is not required. In some circumstances all statements marked true may be needed, in others control-flow will end up skipping a subset of such statements, perhaps while repeating others multiple times.

See also lines_required! and selective_eval!.

source
LoweredCodeUtils.lines_required!Function
lines_required!(isrequired::AbstractVector{Bool}, src::CodeInfo, edges::CodeEdges;
                norequire = ())

Like lines_required, but where isrequired[idx] has already been set to true for all statements that you know you need to evaluate. All other statements should be marked false at entry. On return, the complete set of required statements will be marked true.

norequire keyword argument specifies statements (represented as iterator of Ints) that should not be marked as a requirement. For example, use norequire = LoweredCodeUtils.exclude_named_typedefs(src, edges) if you're extracting method signatures and not evaluating new definitions.

source
LoweredCodeUtils.selective_eval!Function
selective_eval!([recurse], frame::Frame, isrequired::AbstractVector{Bool}, istoplevel=false)

Execute the code in frame in the manner of JuliaInterpreter.finish_and_return!, but skipping all statements that are marked false in isrequired. See lines_required. Upon entry, if needed the caller must ensure that frame.pc is set to the correct statement, typically findfirst(isrequired). See selective_eval_fromstart! to have that performed automatically.

The default value for recurse is JuliaInterpreter.finish_and_return!. isrequired pertains only to frame itself, not any of its callees.

This will return either a BreakpointRef, the value obtained from the last executed statement (if stored to frame.framedata.ssavlues), or nothing. Typically, assignment to a variable binding does not result in an ssa store by JuliaInterpreter.

source

Internal utilities

LoweredCodeUtils.print_with_codeFunction
print_with_code(io, src::CodeInfo, cl::CodeLinks)

Interweave display of code and links.

Julia 1.6

This function produces dummy output if suitable support is missing in your version of Julia.

source
print_with_code(io, src::CodeInfo, edges::CodeEdges)

Interweave display of code and edges.

Julia 1.6

This function produces dummy output if suitable support is missing in your version of Julia.

source
print_with_code(io, src::CodeInfo, isrequired::AbstractVector{Bool})

Mark each line of code with its requirement status.

Julia 1.6

This function produces dummy output if suitable support is missing in your version of Julia.

source
LoweredCodeUtils.next_or_nothingFunction
nextpc = next_or_nothing(frame, pc)
nextpc = next_or_nothing!(frame)

Advance the program counter without executing the corresponding line. If frame is finished, nextpc will be nothing.

source
LoweredCodeUtils.skip_untilFunction
nextpc = skip_until(predicate, frame, pc)
nextpc = skip_until!(predicate, frame)

Advance the program counter until predicate(stmt) return true.

source
LoweredCodeUtils.MethodInfoType
MethodInfo(start, stop, refs)

Given a frame and its CodeInfo, start is the line of the first Expr(:method, name), whereas stop is the line of the last Expr(:method, name, sig, src) expression for name. refs is a vector of line numbers of other references. Some of these will be the location of the "declaration" of a method, the :thunk expression containing a CodeInfo that just returns a 1-argument :method expression. Others may be :global declarations.

In some cases there may be more than one method with the same name in the start:stop range.

source
LoweredCodeUtils.identify_framemethod_callsFunction
methodinfos, selfcalls = identify_framemethod_calls(frame)

Analyze the code in frame to locate method definitions and "self-calls," i.e., calls to methods defined in frame that occur within frame.

methodinfos is a Dict of name=>info pairs, where info is a MethodInfo.

selfcalls is a list of SelfCall(linetop, linebody, callee, caller) that holds the location of calls the methods defined in frame. linetop is the line in frame (top meaning "top level"), which will correspond to a 3-argument :method expression containing a CodeInfo body. linebody is the line within the CodeInfo body from which the call is made. callee is the Symbol of the called method.

source
LoweredCodeUtils.find_name_caller_sigFunction
pctop, isgen = find_name_caller_sig(recurse, frame, pc, name, parentname)

Scans forward from pc in frame until a method is found that calls name. pctop points to the beginning of that method's signature. isgen is true if name corresponds to sa GeneratedFunctionStub.

Alternatively, this returns nothing if pc does not appear to point to either a keyword or generated method.

source
LoweredCodeUtils.VariableType

Variable holds information about named variables. Unlike SSAValues, a single Variable can be assigned from multiple code locations.

If v is a Variable, then

  • v.assigned is a list of statement numbers on which it is assigned
  • v.preds is the set of statement numbers upon which this assignment depends
  • v.succs is the set of statement numbers which make use of this variable

preds and succs are short for "predecessors" and "successors," respectively. These are meant in the sense of execution order, not statement number; depending on control-flow, a variable may have entries in preds that are larger than the smallest entry in assigned.

source