diff --git a/Project.toml b/Project.toml index 75a2ed1..264285e 100644 --- a/Project.toml +++ b/Project.toml @@ -1,13 +1,13 @@ name = "LoweredCodeUtils" uuid = "6f1432cf-f94c-5a45-995e-cdbf5db27b0b" -version = "3.2.2" +version = "3.3.0" authors = ["Tim Holy "] [deps] JuliaInterpreter = "aa1ae85d-cabe-5617-a682-6adf51b2e16a" [compat] -JuliaInterpreter = "0.9.46" +JuliaInterpreter = "0.10" julia = "1.10" [extras] diff --git a/docs/.gitignore b/docs/.gitignore index a303fff..f76f6fb 100644 --- a/docs/.gitignore +++ b/docs/.gitignore @@ -1,2 +1,3 @@ build/ site/ +!Manifest.toml diff --git a/docs/Manifest.toml b/docs/Manifest.toml new file mode 100644 index 0000000..e99feee --- /dev/null +++ b/docs/Manifest.toml @@ -0,0 +1,307 @@ +# This file is machine-generated - editing it directly is not advised + +julia_version = "1.12.0-beta1" +manifest_format = "2.0" +project_hash = "9333156eea8af2fffc3b9e8bb1bfc75d6bb99ef7" + +[[deps.ANSIColoredPrinters]] +git-tree-sha1 = "574baf8110975760d391c710b6341da1afa48d8c" +uuid = "a4c015fc-c6ff-483c-b24f-f7ea428134e9" +version = "0.0.1" + +[[deps.AbstractTrees]] +git-tree-sha1 = "2d9c9a55f9c93e8887ad391fbae72f8ef55e1177" +uuid = "1520ce14-60c1-5f80-bbc7-55ef81b5835c" +version = "0.4.5" + +[[deps.ArgTools]] +uuid = "0dad84c5-d112-42e6-8d28-ef12dabb789f" +version = "1.1.2" + +[[deps.Artifacts]] +uuid = "56f22d72-fd6d-98f1-02f0-08ddc0907c33" +version = "1.11.0" + +[[deps.Base64]] +uuid = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f" +version = "1.11.0" + +[[deps.CodeTracking]] +deps = ["InteractiveUtils", "UUIDs"] +git-tree-sha1 = "062c5e1a5bf6ada13db96a4ae4749a4c2234f521" +uuid = "da1fd8a2-8d9e-5ec2-8556-3022fb5608a2" +version = "1.3.9" + +[[deps.CodecZlib]] +deps = ["TranscodingStreams", "Zlib_jll"] +git-tree-sha1 = "962834c22b66e32aa10f7611c08c8ca4e20749a9" +uuid = "944b1d66-785c-5afd-91f1-9de20f533193" +version = "0.7.8" + +[[deps.Dates]] +deps = ["Printf"] +uuid = "ade2ca70-3891-5945-98fb-dc099432e06a" +version = "1.11.0" + +[[deps.DocStringExtensions]] +git-tree-sha1 = "e7b7e6f178525d17c720ab9c081e4ef04429f860" +uuid = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae" +version = "0.9.4" + +[[deps.Documenter]] +deps = ["ANSIColoredPrinters", "AbstractTrees", "Base64", "CodecZlib", "Dates", "DocStringExtensions", "Downloads", "Git", "IOCapture", "InteractiveUtils", "JSON", "LibGit2", "Logging", "Markdown", "MarkdownAST", "Pkg", "PrecompileTools", "REPL", "RegistryInstances", "SHA", "TOML", "Test", "Unicode"] +git-tree-sha1 = "9d733459cea04dcf1c41522ec25c31576387be8a" +uuid = "e30172f5-a6a5-5a46-863b-614d45cd2de4" +version = "1.10.1" + +[[deps.Downloads]] +deps = ["ArgTools", "FileWatching", "LibCURL", "NetworkOptions"] +uuid = "f43a241f-c20a-4ad4-852c-f6b1247861c6" +version = "1.6.0" + +[[deps.Expat_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "d55dffd9ae73ff72f1c0482454dcf2ec6c6c4a63" +uuid = "2e619515-83b5-522b-bb60-26c02a35a201" +version = "2.6.5+0" + +[[deps.FileWatching]] +uuid = "7b1f6079-737a-58dc-b8bc-7a2ca5c1b5ee" +version = "1.11.0" + +[[deps.Git]] +deps = ["Git_jll"] +git-tree-sha1 = "04eff47b1354d702c3a85e8ab23d539bb7d5957e" +uuid = "d7ba0133-e1db-5d97-8f8c-041e4b3a1eb2" +version = "1.3.1" + +[[deps.Git_jll]] +deps = ["Artifacts", "Expat_jll", "JLLWrappers", "LibCURL_jll", "Libdl", "Libiconv_jll", "OpenSSL_jll", "PCRE2_jll", "Zlib_jll"] +git-tree-sha1 = "2f6d6f7e6d6de361865d4394b802c02fc944fc7c" +uuid = "f8c6e375-362e-5223-8a59-34ff63f689eb" +version = "2.49.0+0" + +[[deps.IOCapture]] +deps = ["Logging", "Random"] +git-tree-sha1 = "b6d6bfdd7ce25b0f9b2f6b3dd56b2673a66c8770" +uuid = "b5f81e59-6552-4d32-b1f0-c071b021bf89" +version = "0.2.5" + +[[deps.InteractiveUtils]] +deps = ["Markdown"] +uuid = "b77e0a4c-d291-57a0-90e8-8db25a27a240" +version = "1.11.0" + +[[deps.JLLWrappers]] +deps = ["Artifacts", "Preferences"] +git-tree-sha1 = "a007feb38b422fbdab534406aeca1b86823cb4d6" +uuid = "692b3bcd-3c85-4b1f-b108-f13ce0eb3210" +version = "1.7.0" + +[[deps.JSON]] +deps = ["Dates", "Mmap", "Parsers", "Unicode"] +git-tree-sha1 = "31e996f0a15c7b280ba9f76636b3ff9e2ae58c9a" +uuid = "682c06a0-de6a-54ab-a142-c8b1cf79cde6" +version = "0.21.4" + +[[deps.JuliaInterpreter]] +deps = ["CodeTracking", "InteractiveUtils", "Random", "UUIDs"] +git-tree-sha1 = "40237fa9a763c6e9e2465191bb09f7dab4b17593" +uuid = "aa1ae85d-cabe-5617-a682-6adf51b2e16a" +version = "0.10.0" + +[[deps.JuliaSyntaxHighlighting]] +deps = ["StyledStrings"] +uuid = "ac6e5ff7-fb65-4e79-a425-ec3bc9c03011" +version = "1.12.0" + +[[deps.LazilyInitializedFields]] +git-tree-sha1 = "0f2da712350b020bc3957f269c9caad516383ee0" +uuid = "0e77f7df-68c5-4e49-93ce-4cd80f5598bf" +version = "1.3.0" + +[[deps.LibCURL]] +deps = ["LibCURL_jll", "MozillaCACerts_jll"] +uuid = "b27032c2-a3e7-50c8-80cd-2d36dbcbfd21" +version = "0.6.4" + +[[deps.LibCURL_jll]] +deps = ["Artifacts", "LibSSH2_jll", "Libdl", "OpenSSL_jll", "Zlib_jll", "nghttp2_jll"] +uuid = "deac9b47-8bc7-5906-a0fe-35ac56dc84c0" +version = "8.11.1+1" + +[[deps.LibGit2]] +deps = ["LibGit2_jll", "NetworkOptions", "Printf", "SHA"] +uuid = "76f85450-5226-5b5a-8eaa-529ad045b433" +version = "1.11.0" + +[[deps.LibGit2_jll]] +deps = ["Artifacts", "LibSSH2_jll", "Libdl", "OpenSSL_jll"] +uuid = "e37daf67-58a4-590a-8e99-b0245dd2ffc5" +version = "1.9.0+0" + +[[deps.LibSSH2_jll]] +deps = ["Artifacts", "Libdl", "OpenSSL_jll"] +uuid = "29816b5a-b9ab-546f-933c-edad1886dfa8" +version = "1.11.3+1" + +[[deps.Libdl]] +uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb" +version = "1.11.0" + +[[deps.Libiconv_jll]] +deps = ["Artifacts", "JLLWrappers", "Libdl"] +git-tree-sha1 = "be484f5c92fad0bd8acfef35fe017900b0b73809" +uuid = "94ce4f54-9a6c-5748-9c1c-f9c7231a4531" +version = "1.18.0+0" + +[[deps.Logging]] +uuid = "56ddb016-857b-54e1-b83d-db4d58db5568" +version = "1.11.0" + +[[deps.LoweredCodeUtils]] +deps = ["JuliaInterpreter"] +path = ".." +uuid = "6f1432cf-f94c-5a45-995e-cdbf5db27b0b" +version = "3.3.0" + +[[deps.Markdown]] +deps = ["Base64", "JuliaSyntaxHighlighting", "StyledStrings"] +uuid = "d6f4376e-aef5-505a-96c1-9c027394607a" +version = "1.11.0" + +[[deps.MarkdownAST]] +deps = ["AbstractTrees", "Markdown"] +git-tree-sha1 = "465a70f0fc7d443a00dcdc3267a497397b8a3899" +uuid = "d0879d2d-cac2-40c8-9cee-1863dc0c7391" +version = "0.1.2" + +[[deps.Mmap]] +uuid = "a63ad114-7e13-5084-954f-fe012c677804" +version = "1.11.0" + +[[deps.MozillaCACerts_jll]] +uuid = "14a3606d-f60d-562e-9121-12d972cd8159" +version = "2024.12.31" + +[[deps.NetworkOptions]] +uuid = "ca575930-c2e3-43a9-ace4-1e988b2c1908" +version = "1.3.0" + +[[deps.OpenSSL_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "458c3c95-2e84-50aa-8efc-19380b2a3a95" +version = "3.0.16+0" + +[[deps.PCRE2_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "efcefdf7-47ab-520b-bdef-62a2eaa19f15" +version = "10.44.0+1" + +[[deps.Parsers]] +deps = ["Dates", "PrecompileTools", "UUIDs"] +git-tree-sha1 = "44f6c1f38f77cafef9450ff93946c53bd9ca16ff" +uuid = "69de0a69-1ddd-5017-9359-2bf0b02dc9f0" +version = "2.8.2" + +[[deps.Pkg]] +deps = ["Artifacts", "Dates", "Downloads", "FileWatching", "LibGit2", "Libdl", "Logging", "Markdown", "Printf", "Random", "SHA", "TOML", "Tar", "UUIDs", "p7zip_jll"] +uuid = "44cfe95a-1eb2-52ea-b672-e2afdf69b78f" +version = "1.12.0" +weakdeps = ["REPL"] + + [deps.Pkg.extensions] + REPLExt = "REPL" + +[[deps.PrecompileTools]] +deps = ["Preferences"] +git-tree-sha1 = "516f18f048a195409d6e072acf879a9f017d3900" +uuid = "aea7be01-6a6a-4083-8856-8a6e6704d82a" +version = "1.3.2" + +[[deps.Preferences]] +deps = ["TOML"] +git-tree-sha1 = "9306f6085165d270f7e3db02af26a400d580f5c6" +uuid = "21216c6a-2e73-6563-6e65-726566657250" +version = "1.4.3" + +[[deps.Printf]] +deps = ["Unicode"] +uuid = "de0858da-6303-5e67-8744-51eddeeeb8d7" +version = "1.11.0" + +[[deps.REPL]] +deps = ["InteractiveUtils", "JuliaSyntaxHighlighting", "Markdown", "Sockets", "StyledStrings", "Unicode"] +uuid = "3fa0cd96-eef1-5676-8a61-b3b8758bbffb" +version = "1.11.0" + +[[deps.Random]] +deps = ["SHA"] +uuid = "9a3f8284-a2c9-5f02-9a11-845980a1fd5c" +version = "1.11.0" + +[[deps.RegistryInstances]] +deps = ["LazilyInitializedFields", "Pkg", "TOML", "Tar"] +git-tree-sha1 = "ffd19052caf598b8653b99404058fce14828be51" +uuid = "2792f1a3-b283-48e8-9a74-f99dce5104f3" +version = "0.1.0" + +[[deps.SHA]] +uuid = "ea8e919c-243c-51af-8825-aaa63cd721ce" +version = "0.7.0" + +[[deps.Serialization]] +uuid = "9e88b42a-f829-5b0c-bbe9-9e923198166b" +version = "1.11.0" + +[[deps.Sockets]] +uuid = "6462fe0b-24de-5631-8697-dd941f90decc" +version = "1.11.0" + +[[deps.StyledStrings]] +uuid = "f489334b-da3d-4c2e-b8f0-e476e12c162b" +version = "1.11.0" + +[[deps.TOML]] +deps = ["Dates"] +uuid = "fa267f1f-6049-4f14-aa54-33bafae1ed76" +version = "1.0.3" + +[[deps.Tar]] +deps = ["ArgTools", "SHA"] +uuid = "a4e569a6-e804-4fa4-b0f3-eef7a1d5b13e" +version = "1.10.0" + +[[deps.Test]] +deps = ["InteractiveUtils", "Logging", "Random", "Serialization"] +uuid = "8dfed614-e22c-5e08-85e1-65c5234f0b40" +version = "1.11.0" + +[[deps.TranscodingStreams]] +git-tree-sha1 = "0c45878dcfdcfa8480052b6ab162cdd138781742" +uuid = "3bb67fe8-82b1-5028-8e26-92a6c54297fa" +version = "0.11.3" + +[[deps.UUIDs]] +deps = ["Random", "SHA"] +uuid = "cf7118a7-6976-5b1a-9a39-7adc72f591a4" +version = "1.11.0" + +[[deps.Unicode]] +uuid = "4ec0a83e-493e-50e2-b9ac-8f72acf5a8f5" +version = "1.11.0" + +[[deps.Zlib_jll]] +deps = ["Libdl"] +uuid = "83775a58-1f1d-513f-b197-d71354ab007a" +version = "1.3.1+2" + +[[deps.nghttp2_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "8e850ede-7688-5339-a07c-302acd2aaf8d" +version = "1.64.0+1" + +[[deps.p7zip_jll]] +deps = ["Artifacts", "Libdl"] +uuid = "3f19e933-33d8-53b3-aaab-bd5110c3b7a0" +version = "17.5.0+2" diff --git a/docs/src/api.md b/docs/src/api.md index 1d434f3..a4bdbb6 100644 --- a/docs/src/api.md +++ b/docs/src/api.md @@ -19,6 +19,12 @@ selective_eval! selective_eval_fromstart! ``` +## Interpreter + +```@docs +LoweredCodeUtils.SelectiveInterpreter +``` + ## Internal utilities ```@docs diff --git a/src/LoweredCodeUtils.jl b/src/LoweredCodeUtils.jl index 7ce009f..ef33ec5 100644 --- a/src/LoweredCodeUtils.jl +++ b/src/LoweredCodeUtils.jl @@ -10,10 +10,10 @@ module LoweredCodeUtils # the VS Code extension integration. using JuliaInterpreter -using JuliaInterpreter: SSAValue, SlotNumber, Frame +using JuliaInterpreter: SSAValue, SlotNumber, Frame, Interpreter, RecursiveInterpreter using JuliaInterpreter: codelocation, is_global_ref, is_global_ref_egal, is_quotenode_egal, is_return, lookup, lookup_return, linetable, moduleof, next_until!, nstatements, pc_expr, - step_expr!, whichtt, finish_and_return! + step_expr!, whichtt include("packagedef.jl") diff --git a/src/codeedges.jl b/src/codeedges.jl index 647eae8..5e63b29 100644 --- a/src/codeedges.jl +++ b/src/codeedges.jl @@ -1022,7 +1022,46 @@ function add_inplace!(isrequired, src, edges, norequire) end """ - selective_eval!([recurse], frame::Frame, isrequired::AbstractVector{Bool}, istoplevel=false) + struct SelectiveInterpreter{S<:Interpreter,T<:AbstractVector{Bool}} <: Interpreter + inner::S + isrequired::T + end + +An `JuliaInterpreter.Interpreter` that executes only the statements marked `true` in `isrequired`. +Note that this interpreter does not recurse into callee frames. +That is, when `JuliaInterpreter.finish!(interp::SelectiveInterpreter, frame, ...)` is +performed, the `frame` will be executed selectively according to `interp.isrequired`, but +any callee frames within it will be executed by `interp.inner::Interpreter`, not by `interp`. +""" +struct SelectiveInterpreter{S<:Interpreter,T<:AbstractVector{Bool}} <: Interpreter + inner::S + isrequired::T +end +function JuliaInterpreter.step_expr!(interp::SelectiveInterpreter, frame::Frame, istoplevel::Bool) + pc = frame.pc + if interp.isrequired[pc] + step_expr!(interp.inner, frame::Frame, istoplevel::Bool) + else + next_or_nothing!(interp, frame) + end +end +function JuliaInterpreter.get_return(interp::SelectiveInterpreter, frame::Frame) + pc = frame.pc + node = pc_expr(frame, pc) + if is_return(node) + if interp.isrequired[pc] + return lookup_return(frame, node) + end + else + if isassigned(frame.framedata.ssavalues, pc) + return frame.framedata.ssavalues[pcexec] + end + end + return nothing +end + +""" + selective_eval!([interp::Interpreter=RecursiveInterpreter()], 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`. @@ -1030,51 +1069,28 @@ See [`lines_required`](@ref). Upon entry, if needed the caller must ensure that set to the correct statement, typically `findfirst(isrequired)`. See [`selective_eval_fromstart!`](@ref) 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. """ -function selective_eval!(@nospecialize(recurse), frame::Frame, isrequired::AbstractVector{Bool}, istoplevel::Bool=false) - pc = pcexec = pclast = frame.pc - while isa(pc, Int) - frame.pc = pc - te = isrequired[pc] - pclast = pcexec::Int - if te - pcexec = pc = step_expr!(recurse, frame, istoplevel) - else - pc = next_or_nothing!(recurse, frame) - end - end - isa(pc, BreakpointRef) && return pc - pcexec = (pcexec === nothing ? pclast : pcexec)::Int - frame.pc = pcexec - node = pc_expr(frame) - is_return(node) && return isrequired[pcexec] ? lookup_return(frame, node) : nothing - isassigned(frame.framedata.ssavalues, pcexec) && return frame.framedata.ssavalues[pcexec] - return nothing -end -function selective_eval!(frame::Frame, isrequired::AbstractVector{Bool}, istoplevel::Bool=false) - selective_eval!(finish_and_return!, frame, isrequired, istoplevel) -end +selective_eval!(interp::Interpreter, frame::Frame, isrequired::AbstractVector{Bool}, istoplevel::Bool=false) = + JuliaInterpreter.finish_and_return!(SelectiveInterpreter(interp, isrequired), frame, istoplevel) +selective_eval!(args...) = selective_eval!(RecursiveInterpreter(), args...) """ - selective_eval_fromstart!([recurse], frame, isrequired, istoplevel=false) + selective_eval_fromstart!([interp::Interpreter=RecursiveInterpreter()], frame, isrequired, istoplevel=false) Like [`selective_eval!`](@ref), except it sets `frame.pc` to the first `true` statement in `isrequired`. """ -function selective_eval_fromstart!(@nospecialize(recurse), frame, isrequired, istoplevel::Bool=false) +function selective_eval_fromstart!(interp::Interpreter, frame, isrequired, istoplevel::Bool=false) pc = findfirst(isrequired) pc === nothing && return nothing frame.pc = pc - return selective_eval!(recurse, frame, isrequired, istoplevel) -end -function selective_eval_fromstart!(frame::Frame, isrequired::AbstractVector{Bool}, istoplevel::Bool=false) - selective_eval_fromstart!(finish_and_return!, frame, isrequired, istoplevel) + return selective_eval!(interp, frame, isrequired, istoplevel) end +selective_eval_fromstart!(args...) = selective_eval_fromstart!(RecursiveInterpreter(), args...) """ print_with_code(io, src::CodeInfo, isrequired::AbstractVector{Bool}) diff --git a/src/signatures.jl b/src/signatures.jl index 625fa2b..e221633 100644 --- a/src/signatures.jl +++ b/src/signatures.jl @@ -24,8 +24,7 @@ function signature(sigsv::SimpleVector) end """ - sigt, lastpc = signature(recurse, frame, pc) - sigt, lastpc = signature(frame, pc) + sigt, lastpc = signature([interp::Interpreter=RecursiveInterpreter()], frame::Frame, pc::Int) 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 @@ -37,28 +36,28 @@ In this case, `lastpc == pc`. If no 3-argument `:method` expression is found, `sigt` will be `nothing`. """ -function signature(@nospecialize(recurse), frame::Frame, @nospecialize(stmt), pc) +function signature(interp::Interpreter, frame::Frame, @nospecialize(stmt), pc::Int) mod = moduleof(frame) lastpc = frame.pc = pc while !isexpr(stmt, :method, 3) # wait for the 3-arg version if isanonymous_typedef(stmt) - lastpc = pc = step_through_methoddef(recurse, frame, stmt) # define an anonymous function + lastpc = pc = step_through_methoddef(interp, frame, stmt) # define an anonymous function elseif is_Typeof_for_anonymous_methoddef(stmt, frame.framecode.src.code, mod) return nothing, pc else lastpc = pc - pc = step_expr!(recurse, frame, stmt, true) + pc = step_expr!(interp, frame, stmt, true) pc === nothing && return nothing, lastpc end stmt = pc_expr(frame, pc) end isa(stmt, Expr) || return nothing, pc - sigsv = lookup(frame, stmt.args[2])::SimpleVector + sigsv = lookup(interp, frame, stmt.args[2])::SimpleVector sigt = signature(sigsv) return sigt, lastpc end -signature(@nospecialize(recurse), frame::Frame, pc) = signature(recurse, frame, pc_expr(frame, pc), pc) -signature(frame::Frame, pc) = signature(finish_and_return!, frame, pc) +signature(interp::Interpreter, frame::Frame, pc::Int) = signature(interp, frame, pc_expr(frame, pc), pc) +signature(frame::Frame, pc::Int) = signature(RecursiveInterpreter(), frame, pc) function is_Typeof_for_anonymous_methoddef(@nospecialize(stmt), code::Vector{Any}, mod::Module) isexpr(stmt, :call) || return false @@ -91,12 +90,12 @@ function signature_top(frame, stmt::Expr, pc) return minid(stmt.args[2], frame.framecode.src.code, pc) end -function step_through_methoddef(@nospecialize(recurse), frame, @nospecialize(stmt)) +function step_through_methoddef(interp::Interpreter, frame::Frame, @nospecialize(stmt)) while !isexpr(stmt, :method) - pc = step_expr!(recurse, frame, stmt, true) + pc = step_expr!(interp, frame, stmt, true) stmt = pc_expr(frame, pc) end - return step_expr!(recurse, frame, stmt, true) # also define the method + return step_expr!(interp, frame, stmt, true) # also define the method end """ @@ -126,7 +125,7 @@ struct SelfCall end """ - methodinfos, selfcalls = identify_framemethod_calls(frame) + methodinfos::Dict{GlobalRef,MethodInfo}, selfcalls::Vector{SelfCall} = identify_framemethod_calls(frame::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`. @@ -139,7 +138,7 @@ which will correspond to a 3-argument `:method` expression containing a `CodeInf `linebody` is the line within the `CodeInfo` body from which the call is made. `callee` is the Symbol of the called method. """ -function identify_framemethod_calls(frame) +function identify_framemethod_calls(frame::Frame) refs = Pair{GlobalRef,Int}[] methodinfos = Dict{GlobalRef,MethodInfo}() selfcalls = SelfCall[] @@ -249,8 +248,10 @@ function normalize_defsig(@nospecialize(def), mod::Module) end normalize_defsig(@nospecialize(def), frame::Frame) = normalize_defsig(def, moduleof(frame)) +const CalledBy = Dict{GlobalRef,Union{GlobalRef,Bool,Nothing}} + function callchain(selfcalls) - calledby = Dict{GlobalRef,Union{GlobalRef,Bool,Nothing}}() + calledby = CalledBy() for sc in selfcalls startswith(String(sc.callee.name), '#') || continue caller = get(calledby, sc.callee, nothing) @@ -264,13 +265,15 @@ function callchain(selfcalls) return calledby end -function set_to_running_name!(@nospecialize(recurse), replacements, frame, methodinfos, selfcall, calledby, callee, caller) +function set_to_running_name!(interp::Interpreter, replacements::Dict{GlobalRef,GlobalRef}, frame::Frame, + methodinfos::Dict{GlobalRef,MethodInfo}, selfcall::SelfCall, + calledby::CalledBy, callee::GlobalRef, @nospecialize caller#=::Union{GlobalRef,Bool,Nothing}=#) if isa(caller, GlobalRef) && startswith(String(caller.name), '#') rep = get(replacements, caller, nothing) if rep === nothing parentcaller = get(calledby, caller, nothing) if parentcaller !== nothing - set_to_running_name!(recurse, replacements, frame, methodinfos, selfcall, calledby, caller, parentcaller) + set_to_running_name!(interp, replacements, frame, methodinfos, selfcall, calledby, caller, parentcaller) end else caller = rep @@ -285,7 +288,7 @@ function set_to_running_name!(@nospecialize(recurse), replacements, frame, metho end @assert ismethod1(stmt) cname, _pc, _ = try - get_running_name(recurse, frame, pc+1, callee, get(replacements, caller, caller)) + get_running_name(interp, frame, pc+1, callee) catch err if isa(err, UndefVarError) # The function may not be defined, in which case there is nothing to replace @@ -304,8 +307,7 @@ function set_to_running_name!(@nospecialize(recurse), replacements, frame, metho end """ - methranges = rename_framemethods!(frame) - methranges = rename_framemethods!(recurse, frame) + methranges = rename_framemethods!([interp::Interpreter=RecursiveInterpreter()], frame::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. @@ -315,7 +317,10 @@ Returns a vector of `name=>start:stop` pairs specifying the range of lines in `f at which method definitions occur. In some cases there may be more than one method with the same name in the `start:stop` range. """ -function rename_framemethods!(@nospecialize(recurse), frame::Frame, methodinfos, selfcalls, calledby) +function rename_framemethods! end + +function _rename_framemethods!(interp::Interpreter, frame::Frame, + methodinfos::Dict{GlobalRef,MethodInfo}, selfcalls::Vector{SelfCall}, calledby::CalledBy) src = frame.framecode.src replacements = Dict{GlobalRef,GlobalRef}() for (callee, caller) in calledby @@ -323,7 +328,7 @@ function rename_framemethods!(@nospecialize(recurse), frame::Frame, methodinfos, idx = findfirst(sc->sc.callee === callee && sc.caller === caller, selfcalls) idx === nothing && continue try - set_to_running_name!(recurse, replacements, frame, methodinfos, selfcalls[idx], calledby, callee, caller) + set_to_running_name!(interp, replacements, frame, methodinfos, selfcalls[idx], calledby, callee, caller) catch err @warn "skipping callee $callee (called by $caller) due to $err" end @@ -338,18 +343,18 @@ function rename_framemethods!(@nospecialize(recurse), frame::Frame, methodinfos, return methodinfos end -function rename_framemethods!(@nospecialize(recurse), frame::Frame) +function rename_framemethods!(interp::Interpreter, frame::Frame) pc0 = frame.pc methodinfos, selfcalls = identify_framemethod_calls(frame) calledby = callchain(selfcalls) - rename_framemethods!(recurse, frame, methodinfos, selfcalls, calledby) + _rename_framemethods!(interp, frame, methodinfos, selfcalls, calledby) frame.pc = pc0 return methodinfos end -rename_framemethods!(frame::Frame) = rename_framemethods!(finish_and_return!, frame) +rename_framemethods!(frame::Frame) = rename_framemethods!(RecursiveInterpreter(), frame) """ - pctop, isgen = find_name_caller_sig(recurse, frame, pc, name, parentname) + pctop, isgen = find_name_caller_sig(interp::Interpreter, frame::Frame, pc::Int, name::GlobalRef) Scans forward from `pc` in `frame` until a method is found that calls `name`. `pctop` points to the beginning of that method's signature. @@ -358,12 +363,12 @@ Scans forward from `pc` in `frame` until a method is found that calls `name`. Alternatively, this returns `nothing` if `pc` does not appear to point to either a keyword or generated method. """ -function find_name_caller_sig(@nospecialize(recurse), frame, pc, name, parentname) +function find_name_caller_sig(interp::Interpreter, frame::Frame, pc::Int, name::GlobalRef) stmt = pc_expr(frame, pc) while true pc0 = pc while !ismethod3(stmt) - pc = next_or_nothing(recurse, frame, pc) + pc = next_or_nothing(interp, frame, pc) pc === nothing && return nothing stmt = pc_expr(frame, pc) end @@ -381,7 +386,7 @@ function find_name_caller_sig(@nospecialize(recurse), frame, pc, name, parentnam iscallto(bodystmt, moduleof(frame), name, body) && return signature_top(frame, stmt, pc), false end end - pc = next_or_nothing(recurse, frame, pc) + pc = next_or_nothing(interp, frame, pc) pc === nothing && return nothing stmt = pc_expr(frame, pc) end @@ -423,11 +428,11 @@ function replacename!(args::AbstractVector, pr) return args end -function get_running_name(@nospecialize(recurse), frame, pc, name, parentname) - nameinfo = find_name_caller_sig(recurse, frame, pc, name, parentname) +function get_running_name(interp::Interpreter, frame::Frame, pc::Int, name::GlobalRef) + nameinfo = find_name_caller_sig(interp, frame, pc, name) if nameinfo === nothing pc = skip_until(@nospecialize(stmt)->isexpr(stmt, :method, 3), frame, pc) - pc = next_or_nothing(recurse, frame, pc) + pc = next_or_nothing(interp, frame, pc) return name, pc, nothing end pctop, isgen = nameinfo @@ -439,7 +444,7 @@ function get_running_name(@nospecialize(recurse), frame, pc, name, parentname) pctop -= 1 stmt = pc_expr(frame, pctop) end # end fix - sigtparent, lastpcparent = signature(recurse, frame, pctop) + sigtparent, lastpcparent = signature(interp, frame, pctop) sigtparent === nothing && return name, pc, lastpcparent methparent = whichtt(sigtparent) methparent === nothing && return name, pc, lastpcparent # caller isn't defined, no correction is needed @@ -462,16 +467,16 @@ function get_running_name(@nospecialize(recurse), frame, pc, name, parentname) end """ - nextpc = next_or_nothing([recurse], frame, pc) - nextpc = next_or_nothing!([recurse], frame) + nextpc = next_or_nothing([interp::Interpreter=RecursiveInterpreter()], frame::Frame, pc::Int) + nextpc = next_or_nothing!([interp::Interpreter=RecursiveInterpreter()], frame::Frame) Advance the program counter without executing the corresponding line. If `frame` is finished, `nextpc` will be `nothing`. """ -next_or_nothing(frame, pc) = next_or_nothing(finish_and_return!, frame, pc) -next_or_nothing(@nospecialize(recurse), frame, pc) = pc < nstatements(frame.framecode) ? pc+1 : nothing -next_or_nothing!(frame) = next_or_nothing!(finish_and_return!, frame) -function next_or_nothing!(@nospecialize(recurse), frame) +next_or_nothing(frame::Frame, pc::Int) = next_or_nothing(RecursiveInterpreter(), frame, pc) +next_or_nothing(::Interpreter, frame::Frame, pc::Int) = pc < nstatements(frame.framecode) ? pc+1 : nothing +next_or_nothing!(frame::Frame) = next_or_nothing!(RecursiveInterpreter(), frame) +function next_or_nothing!(::Interpreter, frame::Frame) pc = frame.pc if pc < nstatements(frame.framecode) return frame.pc = pc + 1 @@ -480,27 +485,27 @@ function next_or_nothing!(@nospecialize(recurse), frame) end """ - nextpc = skip_until(predicate, [recurse], frame, pc) - nextpc = skip_until!(predicate, [recurse], frame) + nextpc = skip_until(predicate, [interp::Interpreter=RecursiveInterpreter()], frame, pc) + nextpc = skip_until!(predicate, [interp::Interpreter=RecursiveInterpreter()], frame) Advance the program counter until `predicate(stmt)` return `true`. """ -skip_until(predicate, frame, pc) = skip_until(predicate, finish_and_return!, frame, pc) -function skip_until(predicate, @nospecialize(recurse), frame, pc) +skip_until(predicate, frame::Frame, pc::Int) = skip_until(predicate, RecursiveInterpreter(), frame, pc) +function skip_until(predicate::F, interp::Interpreter, frame::Frame, pc::Int) where F stmt = pc_expr(frame, pc) while !predicate(stmt) - pc = next_or_nothing(recurse, frame, pc) + pc = next_or_nothing(interp, frame, pc) pc === nothing && return nothing stmt = pc_expr(frame, pc) end return pc end -skip_until!(predicate, frame) = skip_until!(predicate, finish_and_return!, frame) -function skip_until!(predicate, @nospecialize(recurse), frame) +skip_until!(predicate, frame::Frame) = skip_until!(predicate, RecursiveInterpreter(), frame) +function skip_until!(predicate::F, interp::Interpreter, frame::Frame) where F pc = frame.pc stmt = pc_expr(frame, pc) while !predicate(stmt) - pc = next_or_nothing!(recurse, frame) + pc = next_or_nothing!(interp, frame) pc === nothing && return nothing stmt = pc_expr(frame, pc) end @@ -508,8 +513,7 @@ function skip_until!(predicate, @nospecialize(recurse), frame) end """ - ret = methoddef!(recurse, signatures, frame; define=true) - ret = methoddef!(signatures, frame; define=true) + ret = methoddef!([interp::Interpreter=RecursiveInterpreter()], 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`. @@ -531,17 +535,17 @@ occurs for "empty method" expressions, e.g., `:(function foo end)`. `pc` will be 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. """ -function methoddef!(@nospecialize(recurse), signatures, frame::Frame, @nospecialize(stmt), pc::Int; define::Bool=true) +function methoddef!(interp::Interpreter, signatures, frame::Frame, @nospecialize(stmt), pc::Int; define::Bool=true) framecode, pcin = frame.framecode, pc if ismethod3(stmt) pc3 = pc arg1 = stmt.args[1] - sigt, pc = signature(recurse, frame, stmt, pc) + sigt, pc = signature(interp, frame, stmt, pc) meth = whichtt(sigt) if isa(meth, Method) && (meth.sig <: sigt && sigt <: meth.sig) - pc = define ? step_expr!(recurse, frame, stmt, true) : next_or_nothing!(recurse, frame) + pc = define ? step_expr!(interp, frame, stmt, true) : next_or_nothing!(interp, frame) elseif define - pc = step_expr!(recurse, frame, stmt, true) + pc = step_expr!(interp, frame, stmt, true) meth = whichtt(sigt) end if isa(meth, Method) && (meth.sig <: sigt && sigt <: meth.sig) @@ -549,7 +553,7 @@ function methoddef!(@nospecialize(recurse), signatures, frame::Frame, @nospecial else if arg1 === false || arg1 === nothing # If it's anonymous and not defined, define it - pc = step_expr!(recurse, frame, stmt, true) + pc = step_expr!(interp, frame, stmt, true) meth = whichtt(sigt) isa(meth, Method) && push!(signatures, meth.sig) return pc, pc3 @@ -563,7 +567,7 @@ function methoddef!(@nospecialize(recurse), signatures, frame::Frame, @nospecial @warn "file $(loc.file), line $(loc.line): no method found for $sigt" end if pc == pc3 - pc = next_or_nothing!(recurse, frame) + pc = next_or_nothing!(interp, frame) end end end @@ -592,62 +596,66 @@ function methoddef!(@nospecialize(recurse), signatures, frame::Frame, @nospecial end found || return nothing while true # methods containing inner methods may need multiple trips through this loop - sigt, pc = signature(recurse, frame, stmt, pc) + sigt, pc = signature(interp, frame, stmt, pc) stmt = pc_expr(frame, pc) while !isexpr(stmt, :method, 3) - pc = next_or_nothing(recurse, frame, pc) # this should not check define, we've probably already done this once + pc = next_or_nothing(interp, frame, pc) # this should not check define, we've probably already done this once pc === nothing && return nothing # this was just `function foo end`, signal "no def" stmt = pc_expr(frame, pc) end pc3 = pc stmt = stmt::Expr name3 = normalize_defsig(stmt.args[1], frame) - sigt === nothing && (error("expected a signature"); return next_or_nothing(recurse, frame, pc)), pc3 + sigt === nothing && (error("expected a signature"); return next_or_nothing(interp, frame, pc)), pc3 # Methods like f(x::Ref{<:Real}) that use gensymmed typevars will not have the *exact* # signature of the active method. So let's get the active signature. frame.pc = pc - pc = define ? step_expr!(recurse, frame, stmt, true) : next_or_nothing!(recurse, frame) + pc = define ? step_expr!(interp, frame, stmt, true) : next_or_nothing!(interp, frame) meth = whichtt(sigt) isa(meth, Method) && push!(signatures, meth.sig) # inner methods are not visible name === name3 && return pc, pc3 # if this was an inner method we should keep going stmt = pc_expr(frame, pc) # there *should* be more statements in this frame end end -methoddef!(@nospecialize(recurse), signatures, frame::Frame, pc::Int; define=true) = - methoddef!(recurse, signatures, frame, pc_expr(frame, pc), pc; define=define) -function methoddef!(@nospecialize(recurse), signatures, frame::Frame; define=true) +methoddef!(interp::Interpreter, signatures, frame::Frame, pc::Int; define::Bool=true) = + methoddef!(interp, signatures, frame, pc_expr(frame, pc), pc; define) +function methoddef!(interp::Interpreter, signatures, frame::Frame; define::Bool=true) pc = frame.pc stmt = pc_expr(frame, pc) if !ismethod(stmt) - pc = next_until!(ismethod, recurse, frame, true) + pc = next_until!(ismethod, interp, frame, true) end pc === nothing && error("pc at end of frame without finding a method") - methoddef!(recurse, signatures, frame, pc; define=define) + methoddef!(interp, signatures, frame, pc; define) end -methoddef!(signatures, frame::Frame; define=true) = - methoddef!(finish_and_return!, signatures, frame; define=define) +methoddef!(signatures, frame::Frame, pc::Int; define::Bool=true) = + methoddef!(RecursiveInterpreter(), signatures, frame, pc_expr(frame, pc), pc; define) +methoddef!(signatures, frame::Frame; define::Bool=true) = + methoddef!(RecursiveInterpreter(), signatures, frame; define) -function methoddefs!(@nospecialize(recurse), signatures, frame::Frame, pc; define=true) - ret = methoddef!(recurse, signatures, frame, pc; define=define) +function methoddefs!(interp::Interpreter, signatures, frame::Frame, pc::Int; define::Bool=true) + ret = methoddef!(interp, signatures, frame, pc; define) pc = ret === nothing ? ret : ret[1] - return _methoddefs!(recurse, signatures, frame, pc; define=define) + return _methoddefs!(interp, signatures, frame, pc; define) end -function methoddefs!(@nospecialize(recurse), signatures, frame::Frame; define=true) - ret = methoddef!(recurse, signatures, frame; define=define) +function methoddefs!(interp::Interpreter, signatures, frame::Frame; define::Bool=true) + ret = methoddef!(interp, signatures, frame; define) pc = ret === nothing ? ret : ret[1] - return _methoddefs!(recurse, signatures, frame, pc; define=define) + return _methoddefs!(interp, signatures, frame, pc; define) end -methoddefs!(signatures, frame::Frame; define=true) = - methoddefs!(finish_and_return!, signatures, frame; define=define) +methoddefs!(signatures, frame::Frame, pc::Int; define::Bool=true) = + methoddefs!(RecursiveInterpreter(), signatures, frame, pc; define) +methoddefs!(signatures, frame::Frame; define::Bool=true) = + methoddefs!(RecursiveInterpreter(), signatures, frame; define) -function _methoddefs!(@nospecialize(recurse), signatures, frame::Frame, pc; define=define) +function _methoddefs!(interp::Interpreter, signatures, frame::Frame, pc::Int; define::Bool=define) while pc !== nothing stmt = pc_expr(frame, pc) if !ismethod(stmt) - pc = next_until!(ismethod, recurse, frame, true) + pc = next_until!(ismethod, interp, frame, true) end pc === nothing && break - ret = methoddef!(recurse, signatures, frame, pc; define=define) + ret = methoddef!(interp, signatures, frame, pc; define) pc = ret === nothing ? ret : ret[1] end return pc diff --git a/test/signatures.jl b/test/signatures.jl index ad8d7d9..259c0ae 100644 --- a/test/signatures.jl +++ b/test/signatures.jl @@ -3,7 +3,6 @@ module Signatures using LoweredCodeUtils using InteractiveUtils using JuliaInterpreter -using JuliaInterpreter: finish_and_return! using Core: CodeInfo using Base.Meta: isexpr using Test @@ -382,9 +381,9 @@ bodymethtest5(x, y=Dict(1=>2)) = 5 pcstop = findfirst(LoweredCodeUtils.ismethod3, frame.framecode.src.code) pc = 1 while pc < pcstop - pc = JuliaInterpreter.step_expr!(finish_and_return!, frame, true) + pc = JuliaInterpreter.step_expr!(frame, true) end - pc = methoddef!(finish_and_return!, signatures, frame, pc; define=true) + pc = methoddef!(signatures, frame, pc; define=true) @test Tuple{typeof(Lowering.f568)} ∈ signatures @test Lowering.f568() == -2