From 4145028c7a06d225341c9b96cdbf4b6dd42b9a0f Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Sun, 14 Jul 2024 06:11:08 -0500 Subject: [PATCH 01/16] Add Cthulhu.ascend integration Closes #511 --- Project.toml | 6 +++++- ext/JETCthuhluExt.jl | 25 +++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) create mode 100644 ext/JETCthuhluExt.jl diff --git a/Project.toml b/Project.toml index f8659c420..9f41712e8 100644 --- a/Project.toml +++ b/Project.toml @@ -15,15 +15,18 @@ Preferences = "21216c6a-2e73-6563-6e65-726566657250" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [weakdeps] +Cthulhu = "f68482b8-f384-11e8-15f7-abe071a5a75f" Revise = "295af30f-e4ad-537b-8983-00126c2a3abe" [extensions] +JETCthulhuExt = "Cthulhu" ReviseExt = "Revise" [compat] Aqua = "0.8.2" BenchmarkTools = "1.3.2" CodeTracking = "1.3.1" +Cthulhu = "2.12.7" Example = "0.5.3" InteractiveUtils = "1.10" JuliaInterpreter = "0.9" @@ -43,6 +46,7 @@ julia = "1.10" [extras] Aqua = "4c88cf16-eb10-579e-8560-4a9242c79595" BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf" +Cthulhu = "f68482b8-f384-11e8-15f7-abe071a5a75f" Example = "7876af07-990d-54b4-ab0e-23690620f79a" Libdl = "8f399da3-3557-5675-b5ff-fb832c97cbdb" Logging = "56ddb016-857b-54e1-b83d-db4d58db5568" @@ -52,4 +56,4 @@ StaticArrays = "90137ffa-7385-5640-81b9-e52037218182" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [targets] -test = ["Aqua", "BenchmarkTools", "Example", "Libdl", "Logging", "Random", "Revise", "StaticArrays", "Test"] +test = ["Aqua", "BenchmarkTools", "Cthulhu", "Example", "Libdl", "Logging", "Random", "Revise", "StaticArrays", "Test"] diff --git a/ext/JETCthuhluExt.jl b/ext/JETCthuhluExt.jl new file mode 100644 index 000000000..f13f6fb71 --- /dev/null +++ b/ext/JETCthuhluExt.jl @@ -0,0 +1,25 @@ +module JETCthulhuExt + +using JET: JET, RuntimeDispatchReport, VirtualFrame +using Cthulhu: Cthulhu, Node, Data, callstring +using Core: MethodInstance + +const _emptybackedges = MethodInstance[] + +struct CallFrames + frames::Vector{VirtualFrame} +end + +function Cthulhu.treelist(r::RuntimeDispatchReport) + io = IOBuffer() + cf = CallFrames(r.vst) + frame = r.vst[end] + str = callstring(io, frame.linfo) + Cthulhu.treelist!(Node(Data(str, frame.linfo)), io, cf, "", Base.IdSet{MethodInstance}()) +end + +Cthulhu.instance(cf::CallFrames) = cf.frames[end].linfo +Cthulhu.backedges(cf::CallFrames) = isempty(cf.frames) ? _emptybackedges : [cf.frames[end].linfo] +Cthulhu.nextnode(cf::CallFrames, ::MethodInstance) = CallFrames(cf.frames[1:end-1]) + +end From fb97d7b0ba755907f9cdb2e1178125dba21c8f8d Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Sun, 14 Jul 2024 06:58:32 -0500 Subject: [PATCH 02/16] Add a smoke test --- test/ext/test_cthulhu.jl | 1 + test/runtests.jl | 4 ++++ 2 files changed, 5 insertions(+) create mode 100644 test/ext/test_cthulhu.jl diff --git a/test/ext/test_cthulhu.jl b/test/ext/test_cthulhu.jl new file mode 100644 index 000000000..65a5b60ec --- /dev/null +++ b/test/ext/test_cthulhu.jl @@ -0,0 +1 @@ +using JET, Cthulhu diff --git a/test/runtests.jl b/test/runtests.jl index 03174418c..c077c4a6d 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -37,4 +37,8 @@ using Test, JET @testset "sanity check" include("sanity_check.jl") @testset "self check" include("self_check.jl") + + @testset "extensions" begin + include("ext/test_cthulhu.jl") + end end From 2b6c717768931743b5460610b3b086945296cd3f Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Sun, 14 Jul 2024 10:04:57 -0500 Subject: [PATCH 03/16] fix filename --- ext/{JETCthuhluExt.jl => JETCthulhuExt.jl} | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) rename ext/{JETCthuhluExt.jl => JETCthulhuExt.jl} (82%) diff --git a/ext/JETCthuhluExt.jl b/ext/JETCthulhuExt.jl similarity index 82% rename from ext/JETCthuhluExt.jl rename to ext/JETCthulhuExt.jl index f13f6fb71..6ef4137ca 100644 --- a/ext/JETCthuhluExt.jl +++ b/ext/JETCthulhuExt.jl @@ -15,10 +15,10 @@ function Cthulhu.treelist(r::RuntimeDispatchReport) cf = CallFrames(r.vst) frame = r.vst[end] str = callstring(io, frame.linfo) - Cthulhu.treelist!(Node(Data(str, frame.linfo)), io, cf, "", Base.IdSet{MethodInstance}()) + Cthulhu.treelist!(Node(Data(str, frame.linfo)), io, cf, "", Base.IdSet{Union{MethodInstance,Nothing}}([nothing])) end -Cthulhu.instance(cf::CallFrames) = cf.frames[end].linfo +Cthulhu.instance(cf::CallFrames) = isempty(cf.frames) ? nothing : cf.frames[end].linfo Cthulhu.backedges(cf::CallFrames) = isempty(cf.frames) ? _emptybackedges : [cf.frames[end].linfo] Cthulhu.nextnode(cf::CallFrames, ::MethodInstance) = CallFrames(cf.frames[1:end-1]) From d974e9092d31a3b2776227cf97dacc40a9530459 Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Sun, 14 Jul 2024 10:15:28 -0500 Subject: [PATCH 04/16] More than just smoke --- test/ext/test_cthulhu.jl | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/test/ext/test_cthulhu.jl b/test/ext/test_cthulhu.jl index 65a5b60ec..f5633ec89 100644 --- a/test/ext/test_cthulhu.jl +++ b/test/ext/test_cthulhu.jl @@ -1 +1,11 @@ using JET, Cthulhu + +@testset begin + getsomething(x::Array) = x[] + computesomething(x) = getsomething(x) + 1 + + rpt = @report_opt computesomething(Any[1]) + r = only(JET.get_reports(rpt)) + parent = Cthulhu.treelist(r) + @test parent.data.nd isa Core.MethodInstance +end From a05b12fa302cc50158f745138def4f6387f216cc Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Tue, 16 Jul 2024 08:27:53 -0500 Subject: [PATCH 05/16] fixes --- ext/JETCthulhuExt.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ext/JETCthulhuExt.jl b/ext/JETCthulhuExt.jl index 6ef4137ca..b8ef2878f 100644 --- a/ext/JETCthulhuExt.jl +++ b/ext/JETCthulhuExt.jl @@ -1,6 +1,6 @@ module JETCthulhuExt -using JET: JET, RuntimeDispatchReport, VirtualFrame +using JET: JET, OptimizationFailureReport, RuntimeDispatchReport, VirtualFrame using Cthulhu: Cthulhu, Node, Data, callstring using Core: MethodInstance @@ -10,9 +10,9 @@ struct CallFrames frames::Vector{VirtualFrame} end -function Cthulhu.treelist(r::RuntimeDispatchReport) +function Cthulhu.treelist(r::Union{OptimizationFailureReport,RuntimeDispatchReport}) io = IOBuffer() - cf = CallFrames(r.vst) + cf = CallFrames(r.vst[1:end-1]) frame = r.vst[end] str = callstring(io, frame.linfo) Cthulhu.treelist!(Node(Data(str, frame.linfo)), io, cf, "", Base.IdSet{Union{MethodInstance,Nothing}}([nothing])) From 27f18f50ed6f9cb372b08a125fba16f8c0a83a2f Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Fri, 26 Jul 2024 08:00:42 -0500 Subject: [PATCH 06/16] Support all InferenceErrorReports --- ext/JETCthulhuExt.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ext/JETCthulhuExt.jl b/ext/JETCthulhuExt.jl index b8ef2878f..9013d63a6 100644 --- a/ext/JETCthulhuExt.jl +++ b/ext/JETCthulhuExt.jl @@ -1,6 +1,6 @@ module JETCthulhuExt -using JET: JET, OptimizationFailureReport, RuntimeDispatchReport, VirtualFrame +using JET: JET, InferenceErrorReport, VirtualFrame using Cthulhu: Cthulhu, Node, Data, callstring using Core: MethodInstance @@ -10,7 +10,7 @@ struct CallFrames frames::Vector{VirtualFrame} end -function Cthulhu.treelist(r::Union{OptimizationFailureReport,RuntimeDispatchReport}) +function Cthulhu.treelist(r::InferenceErrorReport) io = IOBuffer() cf = CallFrames(r.vst[1:end-1]) frame = r.vst[end] From b560cfaee1514f347ebb1df87d6376d8887d27f1 Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Thu, 8 Aug 2024 10:10:42 -0500 Subject: [PATCH 07/16] Add docs --- docs/src/optanalysis.md | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/docs/src/optanalysis.md b/docs/src/optanalysis.md index 8bb8beaff..7f0aeaa7f 100644 --- a/docs/src/optanalysis.md +++ b/docs/src/optanalysis.md @@ -27,7 +27,7 @@ JET implements such an analyzer that investigates the optimized representation o anywhere the compiler failed in optimization. Especially, it can find where Julia creates captured variables, where runtime dispatch will happen, and where Julia gives up the optimization work due to unresolvable recursive function call. -[SnoopCompile also detects inference failures](https://timholy.github.io/SnoopCompile.jl/stable/snoopi_deep_analysis/), but JET and SnoopCompile use different mechanisms: JET performs *static* analysis of a particular call, while SnoopCompile performs *dynamic* analysis of new inference. As a consequence, JET's detection of inference failures is reproducible (you can run the same analysis repeatedly and get the same result) but terminates at any non-inferable node of the call graph: you will miss runtime dispatch in any non-inferable callees. Conversely, SnoopCompile's detection of inference failures can explore the entire callgraph, but only for those portions that have not been previously inferred, and the analysis cannot be repeated in the same session. +[SnoopCompile also detects inference failures](https://timholy.github.io/SnoopCompile.jl/stable/tutorials/snoop_inference_analysis/), but JET and SnoopCompile use different mechanisms: JET performs *static* analysis of a particular call, while SnoopCompile performs *dynamic* analysis of new inference. As a consequence, JET's detection of inference failures is reproducible (you can run the same analysis repeatedly and get the same result) but terminates at any non-inferable node of the call graph: you will miss runtime dispatch in any non-inferable callees. Conversely, SnoopCompile's detection of inference failures can explore the entire callgraph, but only for those portions that have not been previously inferred, and the analysis cannot be repeated in the same session. ## [Quick Start](@id optanalysis-quick-start) @@ -164,6 +164,30 @@ using Test end ``` +## [Integration with Cthulhu](@id cthulhu-integration) + +If you identify inference problems, you may want to fix them. Cthulhu can be a useful tool for gaining more insight, and JET integrates nicely with Cthulhu. + +To exploit Cthulhu, you first need to split the overall report into individual inference failures: + +```@repl quickstart +report = @report_opt sumup(sin); +rpts = JET.get_reports(report) +``` + +Now you can `ascend` individual reports: + +``` +julia> using Cthulhu + +julia> ascend(rpts[1]) +Choose a call for analysis (q to quit): + > sumup(::typeof(sin)) +``` + +`ascend` will show the full call-chain to reach a particlar runtime dispatch; in this case, it was our entry point, but in other cases it may be deeper in the call graph. + +Because Cthulhu is an interactive terminal program, we can't demonstrate it in this page, but you're encouraged to see Cthulhu's documentation which includes a video tutorial. ## [Entry Points](@id optanalysis-entry) From 0ba4d9114917d16c0ecb31700d64a8c28d4b3066 Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Sat, 10 Aug 2024 04:49:16 -0500 Subject: [PATCH 08/16] WIP: incorporate tuple-type into Signature --- ext/JETCthulhuExt.jl | 7 +- src/abstractinterpret/inferenceerrorreport.jl | 67 +++++++++++++------ src/analyzers/jetanalyzer.jl | 4 +- 3 files changed, 53 insertions(+), 25 deletions(-) diff --git a/ext/JETCthulhuExt.jl b/ext/JETCthulhuExt.jl index 9013d63a6..be4150225 100644 --- a/ext/JETCthulhuExt.jl +++ b/ext/JETCthulhuExt.jl @@ -12,10 +12,9 @@ end function Cthulhu.treelist(r::InferenceErrorReport) io = IOBuffer() - cf = CallFrames(r.vst[1:end-1]) - frame = r.vst[end] - str = callstring(io, frame.linfo) - Cthulhu.treelist!(Node(Data(str, frame.linfo)), io, cf, "", Base.IdSet{Union{MethodInstance,Nothing}}([nothing])) + cf = CallFrames(r.vst) + printstyled(IOContext(io, :color=>true), r.sig.tt, color=:red) + Cthulhu.treelist!(Node(Data{Union{MethodInstance,Type}}("runtime call to " * String(take!(io)), r.sig.tt)), io, cf, "", Base.IdSet{Union{MethodInstance,Nothing}}([nothing])) end Cthulhu.instance(cf::CallFrames) = isempty(cf.frames) ? nothing : cf.frames[end].linfo diff --git a/src/abstractinterpret/inferenceerrorreport.jl b/src/abstractinterpret/inferenceerrorreport.jl index 6b6bd95b8..b6cf90b90 100644 --- a/src/abstractinterpret/inferenceerrorreport.jl +++ b/src/abstractinterpret/inferenceerrorreport.jl @@ -12,6 +12,7 @@ Represents an expression signature. """ struct Signature _sig::Vector{Any} + tt::Union{Type,Nothing} end # define equality functions that avoid dynamic dispatches @@ -94,21 +95,21 @@ end # signature # --------- -@inline get_sig(s::StateAtPC, @nospecialize(x=get_stmt(s))) = Signature(get_sig_nowrap(s, x)) +@inline get_sig(s::StateAtPC, @nospecialize(x=get_stmt(s))) = Signature(get_sig_nowrap(s, x)...) get_sig(sv::InferenceState) = get_sig((sv, get_currpc(sv))) -get_sig(mi::MethodInstance) = Signature(Any[mi]) +get_sig(mi::MethodInstance) = Signature(Any[mi], mi.specTypes) get_sig(caller::InferenceResult) = get_sig(get_linfo(caller)) function get_sig_nowrap(@nospecialize args...) sig = Any[] - handle_sig!(sig, args...) - return sig + sig, tt = handle_sig!(sig, args...) + return sig, tt end function handle_sig!(sig::Vector{Any}, s::StateAtPC, expr::Expr) head = expr.head - if head === :call + sig, tt = if head === :call handle_sig_call!(sig, s, expr) elseif head === :invoke handle_sig_invoke!(sig, s, expr) @@ -118,29 +119,36 @@ function handle_sig!(sig::Vector{Any}, s::StateAtPC, expr::Expr) handle_sig_static_parameter!(sig, s, expr) else push!(sig, expr) + sig, nothing end - return sig + @show tt head s[2] + return sig, tt end function handle_sig_call!(sig::Vector{Any}, s::StateAtPC, expr::Expr) + function splitlast!(list) + last = pop!(list) + return list, last + end + f = first(expr.args) args = expr.args[2:end] splat = false if isa(f, GlobalRef) - handle_sig_binop!(sig, s, f, args) && return sig - handle_sig_getproperty!(sig, s, f, args) && return sig - handle_sig_setproperty!!(sig, s, f, args) && return sig - handle_sig_getindex!(sig, s, f, args) && return sig - handle_sig_setindex!!(sig, s, f, args) && return sig - handle_sig_const_apply_type!(sig, s, f, args) && return sig + handle_sig_binop!(sig, s, f, args) && return splitlast!(sig) + handle_sig_getproperty!(sig, s, f, args) && return splitlast!(sig) + handle_sig_setproperty!!(sig, s, f, args) && return splitlast!(sig) + handle_sig_getindex!(sig, s, f, args) && return splitlast!(sig) + handle_sig_setindex!!(sig, s, f, args) && return splitlast!(sig) + handle_sig_const_apply_type!(sig, s, f, args) && return splitlast!(sig) if issplat(f, args) f = args[2] args = args[3:end] splat = true end end - handle_sig_call!(sig, s, f, args, #=splat=#splat) - return sig + sig, tt = handle_sig_call!(sig, s, f, args, #=splat=#splat) + return sig, tt end # create a type-annotated signature for `([sig of ex]::T)` @@ -164,12 +172,15 @@ function handle_sig_binop!(sig::Vector{Any}, s::StateAtPC, f::GlobalRef, args::V (Base.isbinaryoperator(f.name) && length(args) == 2) || return false @annotate_if_active sig begin handle_sig!(sig, s, args[1]) + t1 = sig[end] push!(sig, ' ') handle_sig!(sig, s, f) push!(sig, ' ') handle_sig!(sig, s, args[2]) + t2 = sig[end] end push!(sig, safewidenconst(get_ssavaluetype(s))) + push!(sig, Tuple{typeof_sig_f(s, f), t1, t2}) return true end @@ -184,6 +195,7 @@ function handle_sig_getproperty!(sig::Vector{Any}, s::StateAtPC, f::GlobalRef, a push!(sig, '.') push!(sig, String(val)) push!(sig, safewidenconst(get_ssavaluetype(s))) + push!(sig, nothing) # FIXME return true end @@ -202,6 +214,7 @@ function handle_sig_setproperty!!(sig::Vector{Any}, s::StateAtPC, f::GlobalRef, handle_sig!(sig, s, args[3]) push!(sig, safewidenconst(get_ssavaluetype(s))) end + push!(sig, nothing) # FIXME return true end @@ -217,6 +230,7 @@ function handle_sig_getindex!(sig::Vector{Any}, s::StateAtPC, f::GlobalRef, args end push!(sig, ']') push!(sig, safewidenconst(get_ssavaluetype(s))) + push!(sig, nothing) # FIXME return true end @@ -236,6 +250,7 @@ function handle_sig_setindex!!(sig::Vector{Any}, s::StateAtPC, f::GlobalRef, arg handle_sig!(sig, s, args[2]) push!(sig, safewidenconst(get_ssavaluetype(s))) end + push!(sig, nothing) # FIXME return true end @@ -244,6 +259,7 @@ function handle_sig_const_apply_type!(sig::Vector{Any}, s::StateAtPC, f::GlobalR typ = get_ssavaluetype(s) isa(typ, Const) || return false push!(sig, ApplyTypeResult(typ.val)) + push!(sig, nothing) # FIXME return true end @@ -258,25 +274,30 @@ function handle_sig_call!(sig::Vector{Any}, s::StateAtPC, @nospecialize(f), args splat::Bool = false) handle_sig!(sig, s, f) push!(sig, '(') + @show f typeof(f) + typs = Any[typeof_sig_f(s, f)] + @show typs nargs = length(args) for (i, arg) in enumerate(args) handle_sig!(sig, s, arg) + push!(typs, sig[end]) if i ≠ nargs push!(sig, ", ") else splat && push!(sig, "...") end + end push!(sig, ')') push!(sig, safewidenconst(get_ssavaluetype(s))) - return sig + return sig, Tuple{typs...} end function handle_sig_invoke!(sig::Vector{Any}, s::StateAtPC, expr::Expr) f = expr.args[2] args = expr.args[3:end] - handle_sig_call!(sig, s, f, args) - return sig + sig, tt = handle_sig_call!(sig, s, f, args) + return sig, tt end function handle_sig_assignment!(sig::Vector{Any}, s::StateAtPC, expr::Expr) @@ -293,7 +314,7 @@ function handle_sig_assignment!(sig::Vector{Any}, s::StateAtPC, expr::Expr) end end handle_sig!(sig, s, last(expr.args)) - return sig + return sig, nothing end function handle_sig_static_parameter!(sig::Vector{Any}, s::StateAtPC, expr::Expr) @@ -302,7 +323,7 @@ function handle_sig_static_parameter!(sig::Vector{Any}, s::StateAtPC, expr::Expr name = sparam_name((sv.linfo.def::Method).sig::UnionAll, i) typ = widenconst(sv.sptypes[i].typ) push!(sig, String(name), typ) - return sig + return sig, nothing end function sparam_name(u::UnionAll, i::Int) @@ -396,6 +417,14 @@ handle_sig!(sig::Vector{Any}, ::StateAtPC, x::String) = (push!(sig, Repr(x)); re # fallback: GlobalRef, literals... handle_sig!(sig::Vector{Any}, ::StateAtPC, @nospecialize(x)) = (push!(sig, x); return sig) +function typeof_sig_f(s::State, @nospecialize(f)) + isa(f, GlobalRef) ? Core.Typeof(getglobal(f.mod, f.name)) : + isa(f, Core.Argument) ? safewidenconst(s.slottypes[f.n]) : + isa(f, SSAValue ? safewidenconst(s.ssavaluetypes[f.id]) : + error("f ", f, " with type ", typeof(f), " not supported")) +end +typeof_sig_f(s::StateAtPC, @nospecialize(f)) = typeof_sig_f(first(s), f) + # new report # ---------- diff --git a/src/analyzers/jetanalyzer.jl b/src/analyzers/jetanalyzer.jl index 5ec61d116..0f8217654 100644 --- a/src/analyzers/jetanalyzer.jl +++ b/src/analyzers/jetanalyzer.jl @@ -482,7 +482,7 @@ function UncaughtExceptionReport(sv::InferenceState, throw_calls::Vector{Tuple{I append!(sigs, call_sig) i ≠ ncalls && push!(sigs, ", ") end - sig = Signature(sigs) + sig = Signature(sigs, nothing) single_error = ncalls == 1 return UncaughtExceptionReport(vst, sig, single_error) end @@ -626,7 +626,7 @@ end const REDUCE_EMPTY_REPORT_SIG = let sig = Any["MethodError: reducing over an empty collection is not allowed; consider supplying `init` to the reducer"] - Signature(sig) + Signature(sig, nothing) end # special case `reduce_empty` and `mapreduce_empty`: From 33e63c66f5541beb8f79e485b22f99bca2cdd1f0 Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Thu, 15 Aug 2024 03:45:39 -0500 Subject: [PATCH 09/16] rm debugging Co-authored-by: Shuhei Kadowaki <40514306+aviatesk@users.noreply.github.com> --- src/abstractinterpret/inferenceerrorreport.jl | 1 - 1 file changed, 1 deletion(-) diff --git a/src/abstractinterpret/inferenceerrorreport.jl b/src/abstractinterpret/inferenceerrorreport.jl index b6cf90b90..29b7974a3 100644 --- a/src/abstractinterpret/inferenceerrorreport.jl +++ b/src/abstractinterpret/inferenceerrorreport.jl @@ -121,7 +121,6 @@ function handle_sig!(sig::Vector{Any}, s::StateAtPC, expr::Expr) push!(sig, expr) sig, nothing end - @show tt head s[2] return sig, tt end From f41e82742a488df2dad98c1a44d8aa2b367a0ac5 Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Thu, 15 Aug 2024 04:00:19 -0500 Subject: [PATCH 10/16] fix type --- src/abstractinterpret/inferenceerrorreport.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/abstractinterpret/inferenceerrorreport.jl b/src/abstractinterpret/inferenceerrorreport.jl index 29b7974a3..a66951e59 100644 --- a/src/abstractinterpret/inferenceerrorreport.jl +++ b/src/abstractinterpret/inferenceerrorreport.jl @@ -279,7 +279,7 @@ function handle_sig_call!(sig::Vector{Any}, s::StateAtPC, @nospecialize(f), args nargs = length(args) for (i, arg) in enumerate(args) handle_sig!(sig, s, arg) - push!(typs, sig[end]) + push!(typs, typeof_sig_f(s, arg)) if i ≠ nargs push!(sig, ", ") else From 9a311c4536abc9e363fbf6f5d172bb85c81b3e38 Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Fri, 16 Aug 2024 06:34:39 -0500 Subject: [PATCH 11/16] mostly working --- src/abstractinterpret/inferenceerrorreport.jl | 85 ++++++++++++------- src/analyzers/jetanalyzer.jl | 5 +- test/ext/test_cthulhu.jl | 3 +- 3 files changed, 57 insertions(+), 36 deletions(-) diff --git a/src/abstractinterpret/inferenceerrorreport.jl b/src/abstractinterpret/inferenceerrorreport.jl index a66951e59..e7c7a904b 100644 --- a/src/abstractinterpret/inferenceerrorreport.jl +++ b/src/abstractinterpret/inferenceerrorreport.jl @@ -98,12 +98,12 @@ end @inline get_sig(s::StateAtPC, @nospecialize(x=get_stmt(s))) = Signature(get_sig_nowrap(s, x)...) get_sig(sv::InferenceState) = get_sig((sv, get_currpc(sv))) -get_sig(mi::MethodInstance) = Signature(Any[mi], mi.specTypes) +get_sig(mi::MethodInstance) = Signature(Any[mi], mi.specTypes::DataType) get_sig(caller::InferenceResult) = get_sig(get_linfo(caller)) -function get_sig_nowrap(@nospecialize args...) +function get_sig_nowrap(s::StateAtPC, @nospecialize(stmt)) sig = Any[] - sig, tt = handle_sig!(sig, args...) + _, tt = handle_sig!(sig, s, stmt)::Tuple{Vector{Any}, Union{Type,Nothing}} return sig, tt end @@ -171,15 +171,15 @@ function handle_sig_binop!(sig::Vector{Any}, s::StateAtPC, f::GlobalRef, args::V (Base.isbinaryoperator(f.name) && length(args) == 2) || return false @annotate_if_active sig begin handle_sig!(sig, s, args[1]) - t1 = sig[end] + t1 = typeof_arg(s, args[1]) push!(sig, ' ') handle_sig!(sig, s, f) push!(sig, ' ') handle_sig!(sig, s, args[2]) - t2 = sig[end] + t2 = typeof_arg(s, args[2]) end push!(sig, safewidenconst(get_ssavaluetype(s))) - push!(sig, Tuple{typeof_sig_f(s, f), t1, t2}) + push!(sig, Tuple{typeof_arg(s, f), t1, t2}) return true end @@ -194,7 +194,7 @@ function handle_sig_getproperty!(sig::Vector{Any}, s::StateAtPC, f::GlobalRef, a push!(sig, '.') push!(sig, String(val)) push!(sig, safewidenconst(get_ssavaluetype(s))) - push!(sig, nothing) # FIXME + push!(sig, Tuple{typeof(getglobal(f.mod, f.name)), typeof_arg(s, args[1]), Symbol}) return true end @@ -213,7 +213,7 @@ function handle_sig_setproperty!!(sig::Vector{Any}, s::StateAtPC, f::GlobalRef, handle_sig!(sig, s, args[3]) push!(sig, safewidenconst(get_ssavaluetype(s))) end - push!(sig, nothing) # FIXME + push!(sig, Tuple{typeof(getglobal(f.mod, f.name)), typeof_arg(s, args[1]), Symbol, typeof_arg(s, args[3])}) return true end @@ -229,7 +229,7 @@ function handle_sig_getindex!(sig::Vector{Any}, s::StateAtPC, f::GlobalRef, args end push!(sig, ']') push!(sig, safewidenconst(get_ssavaluetype(s))) - push!(sig, nothing) # FIXME + push!(sig, Tuple{typeof(getglobal(f.mod, f.name)), [typeof_arg(s, arg) for arg in args]...}) return true end @@ -249,7 +249,7 @@ function handle_sig_setindex!!(sig::Vector{Any}, s::StateAtPC, f::GlobalRef, arg handle_sig!(sig, s, args[2]) push!(sig, safewidenconst(get_ssavaluetype(s))) end - push!(sig, nothing) # FIXME + push!(sig, Tuple{typeof(getglobal(f.mod, f.name)), [typeof_arg(s, arg) for arg in args]...}) return true end @@ -258,7 +258,7 @@ function handle_sig_const_apply_type!(sig::Vector{Any}, s::StateAtPC, f::GlobalR typ = get_ssavaluetype(s) isa(typ, Const) || return false push!(sig, ApplyTypeResult(typ.val)) - push!(sig, nothing) # FIXME + push!(sig, Tuple{typeof(getglobal(f.mod, f.name)), [typeof_arg(s, arg) for arg in args]...}) return true end @@ -273,13 +273,11 @@ function handle_sig_call!(sig::Vector{Any}, s::StateAtPC, @nospecialize(f), args splat::Bool = false) handle_sig!(sig, s, f) push!(sig, '(') - @show f typeof(f) - typs = Any[typeof_sig_f(s, f)] - @show typs + typs = Any[typeof_arg(s, f; callable=true)] nargs = length(args) for (i, arg) in enumerate(args) + push!(typs, typeof_arg(s, arg)) handle_sig!(sig, s, arg) - push!(typs, typeof_sig_f(s, arg)) if i ≠ nargs push!(sig, ", ") else @@ -345,7 +343,7 @@ function handle_sig!(sig::Vector{Any}, (sv, _)::StateAtPC, ssa::SSAValue) # XXX the same problem may happen for `InferenceState` too ? handle_sig!(sig, newstate, get_stmt(newstate)) end - return sig + return sig, Union{} end function handle_sig!(sig::Vector{Any}, s::StateAtPC, slot::SlotNumber) @@ -354,7 +352,7 @@ function handle_sig!(sig::Vector{Any}, s::StateAtPC, slot::SlotNumber) if istoplevel(sv) # this is a abstract global variable, form the global reference handle_sig!(sig, s, GlobalRef(sv.linfo.def::Module, name)) - return sig + return sig, Union{} end if name === Symbol("") repr = slot # fallback if no explicit slotname @@ -365,7 +363,7 @@ function handle_sig!(sig::Vector{Any}, s::StateAtPC, slot::SlotNumber) typ = safewidenconst((sv isa InferenceState && CC.is_inferred(sv)) ? get_slottype(sv, slot) : get_slottype(s, slot)) push!(sig, repr, typ) - return sig + return sig, Union{} end # NOTE `Argument` is introduced by optimization, and so we don't need to handle abstract global variable here @@ -378,13 +376,13 @@ function handle_sig!(sig::Vector{Any}, (sv, _)::StateAtPC, arg::Argument) end typ = safewidenconst(get_slottype(sv, arg)) # after optimization we shouldn't use `get_slottype(::StateAtPC, ::Any)` push!(sig, repr, typ) - return sig + return sig, Union{} end function handle_sig!(sig::Vector{Any}, s::StateAtPC, gotoifnot::GotoIfNot) push!(sig, "goto ", SSAValue(gotoifnot.dest), " if not ") handle_sig!(sig, s, gotoifnot.cond) - return sig + return sig, Union{} end function handle_sig!(sig::Vector{Any}, s::StateAtPC, rn::ReturnNode) @@ -394,7 +392,7 @@ function handle_sig!(sig::Vector{Any}, s::StateAtPC, rn::ReturnNode) push!(sig, "return ") handle_sig!(sig, s, rn.val) end - return sig + return sig, Union{} end is_unreachable(@nospecialize(x)) = isa(x, ReturnNode) && !isdefined(x, :val) @@ -402,27 +400,48 @@ function handle_sig!(sig::Vector{Any}, ::StateAtPC, qn::QuoteNode) v = qn.value if isa(v, Symbol) push!(sig, Repr(v)) - return sig + return sig, Union{} end typ = typeof(v) push!(sig, qn, typ) - return sig + return sig, Union{} end # reprs -handle_sig!(sig::Vector{Any}, ::StateAtPC, x::Symbol) = (push!(sig, Repr(x)); return sig) -handle_sig!(sig::Vector{Any}, ::StateAtPC, x::String) = (push!(sig, Repr(x)); return sig) +handle_sig!(sig::Vector{Any}, ::StateAtPC, x::Symbol) = (push!(sig, Repr(x)); return sig, Union{}) +handle_sig!(sig::Vector{Any}, ::StateAtPC, x::String) = (push!(sig, Repr(x)); return sig, Union{}) # fallback: GlobalRef, literals... -handle_sig!(sig::Vector{Any}, ::StateAtPC, @nospecialize(x)) = (push!(sig, x); return sig) - -function typeof_sig_f(s::State, @nospecialize(f)) - isa(f, GlobalRef) ? Core.Typeof(getglobal(f.mod, f.name)) : - isa(f, Core.Argument) ? safewidenconst(s.slottypes[f.n]) : - isa(f, SSAValue ? safewidenconst(s.ssavaluetypes[f.id]) : - error("f ", f, " with type ", typeof(f), " not supported")) +handle_sig!(sig::Vector{Any}, ::StateAtPC, @nospecialize(x)) = (push!(sig, x); return sig, Union{}) + +function typeof_arg(s::State, @nospecialize(f); callable::Bool=false) + isa(f, GlobalRef) && return isdefined(f.mod, f.name) ? Core.Typeof(getglobal(f.mod, f.name)) : Any + isa(f, SSAValue) && return safewidenconst(get_ssavaluetype((s, f.id))) + isa(f, Function) && return Core.Typeof(f) + isa(f, Type) && return Type{f} + isa(f, QuoteNode) && return Core.Typeof(f.value) + isexpr(f, :static_parameter) && return Core.Typeof(s.sptypes[first(f.args)::Int]) + callable && error("f ", f, " with type ", typeof(f), " not supported") # FIXME self check runtime dispatch + return typeof(f) +end +function typeof_arg(s::StateAtPC, @nospecialize(f); kwargs...) + if isa(f, SlotNumber) + ret = safewidenconst(get_slottype(s, f)) + ret === Union{} || return ret + # We have to look at earlier lines and see where this slot is assigned + # FIXME: control-flow? + state, pc = s + for i = pc-1:-1:1 + stmt = get_stmt((state, i)) + if isexpr(stmt, :(=)) && stmt.args[1] === f + return safewidenconst(get_ssavaluetype((state, i))) + end + end + error("unhandled slot ", f) + end + isa(f, Core.Argument) && return safewidenconst(get_slottype(s, f)) + return typeof_arg(first(s), f; kwargs...) end -typeof_sig_f(s::StateAtPC, @nospecialize(f)) = typeof_sig_f(first(s), f) # new report # ---------- diff --git a/src/analyzers/jetanalyzer.jl b/src/analyzers/jetanalyzer.jl index 0f8217654..bc473bdc8 100644 --- a/src/analyzers/jetanalyzer.jl +++ b/src/analyzers/jetanalyzer.jl @@ -476,13 +476,14 @@ function UncaughtExceptionReport(sv::InferenceState, throw_calls::Vector{Tuple{I vf = get_virtual_frame(sv.linfo) vst = VirtualFrame[vf] sigs = Any[] + tt = Union{} ncalls = length(throw_calls) for (i, (pc, call)) in enumerate(throw_calls) - call_sig = get_sig_nowrap((sv, pc), call) + call_sig, tt = get_sig_nowrap((sv, pc), call) append!(sigs, call_sig) i ≠ ncalls && push!(sigs, ", ") end - sig = Signature(sigs, nothing) + sig = Signature(sigs, tt) single_error = ncalls == 1 return UncaughtExceptionReport(vst, sig, single_error) end diff --git a/test/ext/test_cthulhu.jl b/test/ext/test_cthulhu.jl index f5633ec89..9d6462dc1 100644 --- a/test/ext/test_cthulhu.jl +++ b/test/ext/test_cthulhu.jl @@ -7,5 +7,6 @@ using JET, Cthulhu rpt = @report_opt computesomething(Any[1]) r = only(JET.get_reports(rpt)) parent = Cthulhu.treelist(r) - @test parent.data.nd isa Core.MethodInstance + @test parent.data.nd isa DataType + @test only(parent.children).data.nd isa Core.MethodInstance end From 838267f1c93ee83361292118e15cae15fe2b07bc Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Sat, 17 Aug 2024 05:35:15 -0500 Subject: [PATCH 12/16] finalize --- src/JET.jl | 2 + src/abstractinterpret/inferenceerrorreport.jl | 47 +++++++------------ src/analyzers/jetanalyzer.jl | 3 +- 3 files changed, 22 insertions(+), 30 deletions(-) diff --git a/src/JET.jl b/src/JET.jl index ed2852b49..ed823d7b3 100644 --- a/src/JET.jl +++ b/src/JET.jl @@ -294,6 +294,8 @@ get_linfo(linfo::MethodInstance) = linfo is_constant_propagated(frame::InferenceState) = is_constant_propagated(frame.result) is_constant_propagated(result::InferenceResult) = CC.any(result.overridden_by_const) +struct TypeUnassigned end # for when inference doesn't bother assigning a type to a slot (e.g. dead code) + # lattice ignorenotfound(@nospecialize(t)) = t === NOT_FOUND ? Bottom : t diff --git a/src/abstractinterpret/inferenceerrorreport.jl b/src/abstractinterpret/inferenceerrorreport.jl index e7c7a904b..cd0c59771 100644 --- a/src/abstractinterpret/inferenceerrorreport.jl +++ b/src/abstractinterpret/inferenceerrorreport.jl @@ -101,11 +101,8 @@ get_sig(sv::InferenceState) = get_sig((sv, get_currpc(sv))) get_sig(mi::MethodInstance) = Signature(Any[mi], mi.specTypes::DataType) get_sig(caller::InferenceResult) = get_sig(get_linfo(caller)) -function get_sig_nowrap(s::StateAtPC, @nospecialize(stmt)) - sig = Any[] - _, tt = handle_sig!(sig, s, stmt)::Tuple{Vector{Any}, Union{Type,Nothing}} - return sig, tt -end +const HandleSigRT = Tuple{Vector{Any}, Union{Type,Nothing}} # the return type of `handle_sig!` +get_sig_nowrap(s::StateAtPC, @nospecialize(stmt)) = handle_sig!([], s, stmt)::HandleSigRT function handle_sig!(sig::Vector{Any}, s::StateAtPC, expr::Expr) head = expr.head @@ -310,8 +307,7 @@ function handle_sig_assignment!(sig::Vector{Any}, s::StateAtPC, expr::Expr) end end end - handle_sig!(sig, s, last(expr.args)) - return sig, nothing + return handle_sig!(sig, s, last(expr.args))::HandleSigRT end function handle_sig_static_parameter!(sig::Vector{Any}, s::StateAtPC, expr::Expr) @@ -343,7 +339,7 @@ function handle_sig!(sig::Vector{Any}, (sv, _)::StateAtPC, ssa::SSAValue) # XXX the same problem may happen for `InferenceState` too ? handle_sig!(sig, newstate, get_stmt(newstate)) end - return sig, Union{} + return sig, nothing end function handle_sig!(sig::Vector{Any}, s::StateAtPC, slot::SlotNumber) @@ -352,7 +348,7 @@ function handle_sig!(sig::Vector{Any}, s::StateAtPC, slot::SlotNumber) if istoplevel(sv) # this is a abstract global variable, form the global reference handle_sig!(sig, s, GlobalRef(sv.linfo.def::Module, name)) - return sig, Union{} + return sig, nothing end if name === Symbol("") repr = slot # fallback if no explicit slotname @@ -363,7 +359,7 @@ function handle_sig!(sig::Vector{Any}, s::StateAtPC, slot::SlotNumber) typ = safewidenconst((sv isa InferenceState && CC.is_inferred(sv)) ? get_slottype(sv, slot) : get_slottype(s, slot)) push!(sig, repr, typ) - return sig, Union{} + return sig, nothing end # NOTE `Argument` is introduced by optimization, and so we don't need to handle abstract global variable here @@ -376,13 +372,13 @@ function handle_sig!(sig::Vector{Any}, (sv, _)::StateAtPC, arg::Argument) end typ = safewidenconst(get_slottype(sv, arg)) # after optimization we shouldn't use `get_slottype(::StateAtPC, ::Any)` push!(sig, repr, typ) - return sig, Union{} + return sig, nothing end function handle_sig!(sig::Vector{Any}, s::StateAtPC, gotoifnot::GotoIfNot) push!(sig, "goto ", SSAValue(gotoifnot.dest), " if not ") handle_sig!(sig, s, gotoifnot.cond) - return sig, Union{} + return sig, nothing end function handle_sig!(sig::Vector{Any}, s::StateAtPC, rn::ReturnNode) @@ -392,7 +388,7 @@ function handle_sig!(sig::Vector{Any}, s::StateAtPC, rn::ReturnNode) push!(sig, "return ") handle_sig!(sig, s, rn.val) end - return sig, Union{} + return sig, nothing end is_unreachable(@nospecialize(x)) = isa(x, ReturnNode) && !isdefined(x, :val) @@ -400,19 +396,19 @@ function handle_sig!(sig::Vector{Any}, ::StateAtPC, qn::QuoteNode) v = qn.value if isa(v, Symbol) push!(sig, Repr(v)) - return sig, Union{} + return sig, nothing end typ = typeof(v) push!(sig, qn, typ) - return sig, Union{} + return sig, nothing end # reprs -handle_sig!(sig::Vector{Any}, ::StateAtPC, x::Symbol) = (push!(sig, Repr(x)); return sig, Union{}) -handle_sig!(sig::Vector{Any}, ::StateAtPC, x::String) = (push!(sig, Repr(x)); return sig, Union{}) +handle_sig!(sig::Vector{Any}, ::StateAtPC, x::Symbol) = (push!(sig, Repr(x)); return sig, nothing) +handle_sig!(sig::Vector{Any}, ::StateAtPC, x::String) = (push!(sig, Repr(x)); return sig, nothing) # fallback: GlobalRef, literals... -handle_sig!(sig::Vector{Any}, ::StateAtPC, @nospecialize(x)) = (push!(sig, x); return sig, Union{}) +handle_sig!(sig::Vector{Any}, ::StateAtPC, @nospecialize(x)) = (push!(sig, x); return sig, nothing) function typeof_arg(s::State, @nospecialize(f); callable::Bool=false) isa(f, GlobalRef) && return isdefined(f.mod, f.name) ? Core.Typeof(getglobal(f.mod, f.name)) : Any @@ -421,23 +417,16 @@ function typeof_arg(s::State, @nospecialize(f); callable::Bool=false) isa(f, Type) && return Type{f} isa(f, QuoteNode) && return Core.Typeof(f.value) isexpr(f, :static_parameter) && return Core.Typeof(s.sptypes[first(f.args)::Int]) - callable && error("f ", f, " with type ", typeof(f), " not supported") # FIXME self check runtime dispatch + callable && error("f ", string(f)::String, " with type ", string(typeof(f)), " not supported") # FIXME self check runtime dispatch return typeof(f) end function typeof_arg(s::StateAtPC, @nospecialize(f); kwargs...) if isa(f, SlotNumber) ret = safewidenconst(get_slottype(s, f)) ret === Union{} || return ret - # We have to look at earlier lines and see where this slot is assigned - # FIXME: control-flow? - state, pc = s - for i = pc-1:-1:1 - stmt = get_stmt((state, i)) - if isexpr(stmt, :(=)) && stmt.args[1] === f - return safewidenconst(get_ssavaluetype((state, i))) - end - end - error("unhandled slot ", f) + # "broken" calls end up here, e.g., one where an argument (not necessarily this one) is undefined and inference doesn't bother assigning types + # for the other args + return TypeUnassigned # One can't create Tuple{typeof(f), Union{}} so we use a placeholder end isa(f, Core.Argument) && return safewidenconst(get_slottype(s, f)) return typeof_arg(first(s), f; kwargs...) diff --git a/src/analyzers/jetanalyzer.jl b/src/analyzers/jetanalyzer.jl index bc473bdc8..a92991b16 100644 --- a/src/analyzers/jetanalyzer.jl +++ b/src/analyzers/jetanalyzer.jl @@ -479,8 +479,9 @@ function UncaughtExceptionReport(sv::InferenceState, throw_calls::Vector{Tuple{I tt = Union{} ncalls = length(throw_calls) for (i, (pc, call)) in enumerate(throw_calls) - call_sig, tt = get_sig_nowrap((sv, pc), call) + call_sig, call_tt = get_sig_nowrap((sv, pc), call) append!(sigs, call_sig) + tt = Union{tt, call_tt} i ≠ ncalls && push!(sigs, ", ") end sig = Signature(sigs, tt) From 95e20bdfa65e90a936502ae1227c927aab3323d2 Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Sat, 17 Aug 2024 06:26:48 -0500 Subject: [PATCH 13/16] Update docs and print more friendly signature --- docs/src/optanalysis.md | 31 ++++++++++++++++++++++++++++--- ext/JETCthulhuExt.jl | 7 ++++--- 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/docs/src/optanalysis.md b/docs/src/optanalysis.md index 7f0aeaa7f..ae17c6948 100644 --- a/docs/src/optanalysis.md +++ b/docs/src/optanalysis.md @@ -182,12 +182,37 @@ julia> using Cthulhu julia> ascend(rpts[1]) Choose a call for analysis (q to quit): - > sumup(::typeof(sin)) + runtime dispatch to make_vals(%1::Any)::Any + > sumup(::typeof(sin)) + +Open an editor at a possible caller of + Tuple{typeof(make_vals), Any} +or browse typed code: + > "REPL[7]", sumup: lines [4] + Browse typed code ``` -`ascend` will show the full call-chain to reach a particlar runtime dispatch; in this case, it was our entry point, but in other cases it may be deeper in the call graph. +`ascend` will show the full call-chain to reach a particular runtime dispatch; in this case, it was our entry point, but in other cases it may be deeper in the call graph. In this case, we've interactively moved the selector `>` down to the `sumup` call (you cannot descend into the `"runtime dispatch to..."` as there is no known code associated with it) and hit ``, at which point Cthulhu showed us that the call to `make_vals(::Any)` occured only on line 4 of the definition of `sumup` (which we entered at the REPL). Cthulhu is now prompting us to either open the code in an editor (which will fail in this case, since there is no associated file!) or view the type-annoted code. If we select the "Browse typed code" option we see -Because Cthulhu is an interactive terminal program, we can't demonstrate it in this page, but you're encouraged to see Cthulhu's documentation which includes a video tutorial. +``` +sumup(f) @ Main REPL[7]:1 + 1 function sumup(f::Core.Const(sin))::Any + 2 # this function uses the non-constant global variable `n` here + 3 # and it makes every succeeding operations type-unstable + 4 vals::Any = make_vals(n::Any)::Any + 5 s::Any = zero(eltype(vals::Any)::Any)::Any + 6 for v::Any in vals::Any::Any + 7 (s::Any += f::Core.Const(sin)(v::Any)::Any)::Any + 8 end + 9 return s::Any +10 end +Select a call to descend into or ↩ to ascend. [q]uit. [b]ookmark. +⋮ +``` + +with red highlighting to indicate the non-inferable arguments. + +For more information, you're encouraged to read Cthulhu's documentation, which includes a video tutorial better-suited to this interactive tool. ## [Entry Points](@id optanalysis-entry) diff --git a/ext/JETCthulhuExt.jl b/ext/JETCthulhuExt.jl index be4150225..3e0cb9929 100644 --- a/ext/JETCthulhuExt.jl +++ b/ext/JETCthulhuExt.jl @@ -1,6 +1,6 @@ module JETCthulhuExt -using JET: JET, InferenceErrorReport, VirtualFrame +using JET: JET, InferenceErrorReport, VirtualFrame, PrintConfig, print_signature using Cthulhu: Cthulhu, Node, Data, callstring using Core: MethodInstance @@ -13,8 +13,9 @@ end function Cthulhu.treelist(r::InferenceErrorReport) io = IOBuffer() cf = CallFrames(r.vst) - printstyled(IOContext(io, :color=>true), r.sig.tt, color=:red) - Cthulhu.treelist!(Node(Data{Union{MethodInstance,Type}}("runtime call to " * String(take!(io)), r.sig.tt)), io, cf, "", Base.IdSet{Union{MethodInstance,Nothing}}([nothing])) + print_signature(IOContext(io, :color=>true), r.sig, PrintConfig()) + # printstyled(IOContext(io, :color=>true), r.sig.tt, color=:red) + Cthulhu.treelist!(Node(Data{Union{MethodInstance,Type}}("runtime dispatch to " * String(take!(io)), r.sig.tt)), io, cf, "", Base.IdSet{Union{MethodInstance,Nothing}}([nothing])) end Cthulhu.instance(cf::CallFrames) = isempty(cf.frames) ? nothing : cf.frames[end].linfo From 86e673e162e76835f1cce3ac4752ea0d4a588cb4 Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Sat, 17 Aug 2024 07:32:35 -0500 Subject: [PATCH 14/16] Add `reportkey` --- docs/src/optanalysis.md | 4 ++++ src/JET.jl | 2 +- src/abstractinterpret/inferenceerrorreport.jl | 10 ++++++++++ 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/docs/src/optanalysis.md b/docs/src/optanalysis.md index ae17c6948..0ea1c2628 100644 --- a/docs/src/optanalysis.md +++ b/docs/src/optanalysis.md @@ -175,6 +175,10 @@ report = @report_opt sumup(sin); rpts = JET.get_reports(report) ``` +!!! tip + If `rpts` is a long list, consider using `urpts = unique(reportkey, rpts)` to trim it. + See [`reportkey`](@ref). + Now you can `ascend` individual reports: ``` diff --git a/src/JET.jl b/src/JET.jl index ed823d7b3..a97d82274 100644 --- a/src/JET.jl +++ b/src/JET.jl @@ -6,7 +6,7 @@ module JET export # jetanalyzer @report_call, report_call, @test_call, test_call, - report_file, test_file, report_package, test_package, report_text, test_text, + report_file, test_file, report_package, test_package, report_text, reportkey, test_text, watch_file, # optanalyzer @report_opt, report_opt, @test_opt, test_opt, diff --git a/src/abstractinterpret/inferenceerrorreport.jl b/src/abstractinterpret/inferenceerrorreport.jl index cd0c59771..ecec9e5c0 100644 --- a/src/abstractinterpret/inferenceerrorreport.jl +++ b/src/abstractinterpret/inferenceerrorreport.jl @@ -580,6 +580,16 @@ end # utility # ------- +""" + reportkey(report::InferenceErrorReport) + +Returns an identifier for the runtime-dispatched call site of `report`. + +If you have a long list of reports to analyze, `urpts = unique(reportkey, rpts)` may remove "duplicates" +that arrive at the same runtime dispatch from different entry points. +""" +reportkey(report::InferenceErrorReport) = (report.sig.tt, report.vst[end].linfo) + # TODO parametric definition? """ From 05deba4252ff232f97f3995c6fb6a9fc24f4f59b Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Sat, 17 Aug 2024 14:43:09 -0500 Subject: [PATCH 15/16] Update [compat] for Cthulhu --- CHANGELOG.md | 6 ++++++ Project.toml | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index be5a8cf60..d85c33735 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +[0.9.8]: https://github.com/aviatesk/JET.jl/compare/v0.9.7...v0.9.8 [0.9.6]: https://github.com/aviatesk/JET.jl/compare/v0.9.5...v0.9.6 [0.9.5]: https://github.com/aviatesk/JET.jl/compare/v0.9.4...v0.9.5 [0.9.4]: https://github.com/aviatesk/JET.jl/compare/v0.9.3...v0.9.4 @@ -25,6 +26,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 [0.8.0]: https://github.com/aviatesk/JET.jl/compare/v0.7.15...v0.8.0 +## [0.9.8] +### Added +- An extension that integrates `@report_opt` with Cthulhu (aviatesk/JET.jl#648) +- `reportkey` for trimming multiple reports that resolve to the same runtime-dispatch caller/callee pair (aviatesk/JET.jl#648) + ## [0.9.6] ### Fixed - `report_opt` no longer raises reports from callees on `throw` code path when the diff --git a/Project.toml b/Project.toml index 9f41712e8..399bc5875 100644 --- a/Project.toml +++ b/Project.toml @@ -26,7 +26,7 @@ ReviseExt = "Revise" Aqua = "0.8.2" BenchmarkTools = "1.3.2" CodeTracking = "1.3.1" -Cthulhu = "2.12.7" +Cthulhu = "2.14.0" Example = "0.5.3" InteractiveUtils = "1.10" JuliaInterpreter = "0.9" From 359eb52296f0062a31a37d9d45569f200880a210 Mon Sep 17 00:00:00 2001 From: Tim Holy Date: Sat, 17 Aug 2024 15:00:19 -0500 Subject: [PATCH 16/16] v0.9.8 --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 399bc5875..c63aaf468 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "JET" uuid = "c3a54625-cd67-489e-a8e7-0a5a0ff4e31b" authors = ["Shuhei Kadowaki "] -version = "0.9.7" +version = "0.9.8" [deps] CodeTracking = "da1fd8a2-8d9e-5ec2-8556-3022fb5608a2"