From e7ab8b60248f52223818d2baa0c63e613d1a008a Mon Sep 17 00:00:00 2001 From: "Documenter.jl" Date: Wed, 11 Sep 2024 04:34:17 +0000 Subject: [PATCH] build based on 969e7c9 --- dev/.documenter-siteinfo.json | 2 +- dev/api/index.html | 16 ++++++++-------- dev/edges/index.html | 2 +- dev/index.html | 2 +- dev/signatures/index.html | 2 +- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/dev/.documenter-siteinfo.json b/dev/.documenter-siteinfo.json index fe6543b..6355ac0 100644 --- a/dev/.documenter-siteinfo.json +++ b/dev/.documenter-siteinfo.json @@ -1 +1 @@ -{"documenter":{"julia_version":"1.10.5","generation_timestamp":"2024-09-10T13:56:43","documenter_version":"1.7.0"}} \ No newline at end of file +{"documenter":{"julia_version":"1.10.5","generation_timestamp":"2024-09-11T04:34:12","documenter_version":"1.7.0"}} \ No newline at end of file diff --git a/dev/api/index.html b/dev/api/index.html index 6506d1a..bcf735d 100644 --- a/dev/api/index.html +++ b/dev/api/index.html @@ -3,11 +3,11 @@ 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::GlobalRef.
source
LoweredCodeUtils.lines_requiredFunction
isrequired = 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 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
LoweredCodeUtils.selective_eval_fromstart!Function
selective_eval_fromstart!([recurse], frame, isrequired, istoplevel=false)

Like selective_eval!, except it sets frame.pc to the first true statement in isrequired.

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([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.

source
LoweredCodeUtils.skip_untilFunction
nextpc = skip_until(predicate, [recurse], frame, pc)
-nextpc = skip_until!(predicate, [recurse], 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.iscalltoFunction
iscallto(stmt, name, src)

Returns true is stmt is a call expression to name.

source
LoweredCodeUtils.getcalleeFunction
getcallee(stmt)

Returns the function (or Symbol) being called in a :call expression.

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.replacename!Function
replacename!(stmts, oldname=>newname)

Replace a Symbol oldname with newname in stmts.

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
+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::GlobalRef.
source
LoweredCodeUtils.lines_requiredFunction
isrequired = 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 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
LoweredCodeUtils.selective_eval_fromstart!Function
selective_eval_fromstart!([recurse], frame, isrequired, istoplevel=false)

Like selective_eval!, except it sets frame.pc to the first true statement in isrequired.

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([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.

source
LoweredCodeUtils.skip_untilFunction
nextpc = skip_until(predicate, [recurse], frame, pc)
+nextpc = skip_until!(predicate, [recurse], 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.iscalltoFunction
iscallto(stmt, name, src)

Returns true is stmt is a call expression to name.

source
LoweredCodeUtils.getcalleeFunction
getcallee(stmt)

Returns the function (or Symbol) being called in a :call expression.

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.replacename!Function
replacename!(stmts, oldname=>newname)

Replace a Symbol oldname with newname in stmts.

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
diff --git a/dev/edges/index.html b/dev/edges/index.html index 1d896c9..99d53fc 100644 --- a/dev/edges/index.html +++ b/dev/edges/index.html @@ -219,4 +219,4 @@ 0 julia> s -9 # random

You can see that k was not reset to its value of 11 when we ran this code selectively, but that s was updated (to a random value) each time.

+9 # random

You can see that k was not reset to its value of 11 when we ran this code selectively, but that s was updated (to a random value) each time.

diff --git a/dev/index.html b/dev/index.html index cc75967..7fb9ce1 100644 --- a/dev/index.html +++ b/dev/index.html @@ -1,2 +1,2 @@ -Home · LoweredCodeUtils

LoweredCodeUtils.jl

This package performs operations on Julia's lowered AST. An introduction to this representation can be found at JuliaInterpreter.

Lowered AST (like other ASTs, type-inferred AST and SSA IR form) is generally more amenable to analysis than "surface" Julia expressions. However, sophisticated analyses can nevertheless require a fair bit of infrastructure. The purpose of this package is to standardize a few operations that are important in some applications.

Currently there are two major domains of this package: the "signatures" domain and the "edges" domain.

Signatures

A major role of this package is to support extraction of method signatures, in particular to provide strong support for relating keyword-method "bodies" to their parent methods. The central challenge this addresses is the lowering of keyword-argument functions and the fact that the "gensymmed" names are different each time you lower the code, and therefore you don't recover the actual (running) keyword-body method. The technical details are described in this Julia issue and on the next page. This package provides a workaround to rename gensymmed variables in newly-lowered code to match the name of the running keyword-body method, and provides a convenience function, bodymethod, to obtain that otherwise difficult-to-discover method.

Edges

Sometimes you want to run only a selected subset of code. For instance, Revise tracks methods by their signatures, and therefore needs to compute signatures from the lowered representation of code. Doing this robustly (including for @evaled methods, etc.) requires running module top-level code through the interpreter. For reasons of performance and safety, it is important to minimize the amount of code that gets executed when extracting the signature.

This package provides a general framework for computing dependencies in code, through the CodeEdges constructor. It allows you to determine the lines on which any given statement depends, the lines which "consume" the result of the current line, and any "named" dependencies (Symbol and GlobalRef dependencies). In particular, this resolves the line-dependencies of all SlotNumber variables so that their own dependencies will be handled via the code-line dependencies.

+Home · LoweredCodeUtils

LoweredCodeUtils.jl

This package performs operations on Julia's lowered AST. An introduction to this representation can be found at JuliaInterpreter.

Lowered AST (like other ASTs, type-inferred AST and SSA IR form) is generally more amenable to analysis than "surface" Julia expressions. However, sophisticated analyses can nevertheless require a fair bit of infrastructure. The purpose of this package is to standardize a few operations that are important in some applications.

Currently there are two major domains of this package: the "signatures" domain and the "edges" domain.

Signatures

A major role of this package is to support extraction of method signatures, in particular to provide strong support for relating keyword-method "bodies" to their parent methods. The central challenge this addresses is the lowering of keyword-argument functions and the fact that the "gensymmed" names are different each time you lower the code, and therefore you don't recover the actual (running) keyword-body method. The technical details are described in this Julia issue and on the next page. This package provides a workaround to rename gensymmed variables in newly-lowered code to match the name of the running keyword-body method, and provides a convenience function, bodymethod, to obtain that otherwise difficult-to-discover method.

Edges

Sometimes you want to run only a selected subset of code. For instance, Revise tracks methods by their signatures, and therefore needs to compute signatures from the lowered representation of code. Doing this robustly (including for @evaled methods, etc.) requires running module top-level code through the interpreter. For reasons of performance and safety, it is important to minimize the amount of code that gets executed when extracting the signature.

This package provides a general framework for computing dependencies in code, through the CodeEdges constructor. It allows you to determine the lines on which any given statement depends, the lines which "consume" the result of the current line, and any "named" dependencies (Symbol and GlobalRef dependencies). In particular, this resolves the line-dependencies of all SlotNumber variables so that their own dependencies will be handled via the code-line dependencies.

diff --git a/dev/signatures/index.html b/dev/signatures/index.html index fb69540..85b2ed9 100644 --- a/dev/signatures/index.html +++ b/dev/signatures/index.html @@ -177,4 +177,4 @@ │ f │ ($(QuoteNode(ifelse)))(false, false, %J25) └── return %J26 -)

While there are a few differences in representation stemming from converting it to a frame, you can see that the #f#2s have been changed to #f#1s to match the currently-running names.

+)

While there are a few differences in representation stemming from converting it to a frame, you can see that the #f#2s have been changed to #f#1s to match the currently-running names.