API
Signatures
LoweredCodeUtils.signature
— Functionsig = signature(sigsv::SimpleVector)
Compute a method signature from a suitable SimpleVector
: sigsv[1]
holds the signature and sigsv[2]
the TypeVar
s.
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)
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
.
LoweredCodeUtils.methoddef!
— Functionret = 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.
LoweredCodeUtils.rename_framemethods!
— Functionmethranges = 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.
LoweredCodeUtils.bodymethod
— Functionmbody = bodymethod(m::Method)
Return the "body method" for a method m
. mbody
contains the code of the function body when m
was defined.
Edges
LoweredCodeUtils.CodeEdges
— Typeedges = CodeEdges(src::CodeInfo)
Analyze src
and determine the chain of dependencies.
edges.preds[i]
lists the preceding statements that statementi
depends on.edges.succs[i]
lists the succeeding statements that depend on statementi
.edges.byname[v]
returns information about the predecessors, successors, and assignment statements for an objectv::GlobalRef
.
LoweredCodeUtils.lines_required
— Functionisrequired = lines_required(obj::GlobalRef, 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 i
th 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!
.
LoweredCodeUtils.lines_required!
— Functionlines_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 Int
s) 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.
LoweredCodeUtils.selective_eval!
— Functionselective_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.
LoweredCodeUtils.selective_eval_fromstart!
— Functionselective_eval_fromstart!([recurse], frame, isrequired, istoplevel=false)
Like selective_eval!
, except it sets frame.pc
to the first true
statement in isrequired
.
Internal utilities
LoweredCodeUtils.print_with_code
— Functionprint_with_code(io, src::CodeInfo, cl::CodeLinks)
Interweave display of code and links.
This function produces dummy output if suitable support is missing in your version of Julia.
print_with_code(io, src::CodeInfo, edges::CodeEdges)
Interweave display of code and edges.
This function produces dummy output if suitable support is missing in your version of Julia.
print_with_code(io, src::CodeInfo, isrequired::AbstractVector{Bool})
Mark each line of code with its requirement status.
This function produces dummy output if suitable support is missing in your version of Julia.
LoweredCodeUtils.next_or_nothing
— Functionnextpc = next_or_nothing([recurse], frame, pc)
nextpc = next_or_nothing!([recurse], frame)
Advance the program counter without executing the corresponding line. If frame
is finished, nextpc
will be nothing
.
LoweredCodeUtils.skip_until
— Functionnextpc = skip_until(predicate, [recurse], frame, pc)
nextpc = skip_until!(predicate, [recurse], frame)
Advance the program counter until predicate(stmt)
return true
.
LoweredCodeUtils.MethodInfo
— TypeMethodInfo(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.
LoweredCodeUtils.identify_framemethod_calls
— Functionmethodinfos, 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.
LoweredCodeUtils.iscallto
— Functioniscallto(stmt, name, src)
Returns true
is stmt
is a call expression to name
.
LoweredCodeUtils.getcallee
— Functiongetcallee(stmt)
Returns the function (or Symbol) being called in a :call expression.
LoweredCodeUtils.find_name_caller_sig
— Functionpctop, 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.
LoweredCodeUtils.replacename!
— Functionreplacename!(stmts, oldname=>newname)
Replace a Symbol oldname
with newname
in stmts
.
LoweredCodeUtils.Variable
— TypeVariable
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 assignedv.preds
is the set of statement numbers upon which this assignment dependsv.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
.