Giter VIP home page Giter VIP logo

cthulhu.jl's Introduction

Cthulhu.jl

The slow descent into madness

⚠️ The latest stable version is only compatible with Julia v1.8.5 and higher.

Cthulhu can help you debug type inference issues by recursively showing the type-inferred code until you find the exact point where inference gave up, messed up, or did something unexpected. Using the Cthulhu interface, you can debug type inference problems faster.

Cthulhu's main tool, descend, can be invoked like this:

descend(f, tt)     # function `f` and Tuple `tt` of argument types
@descend f(args)   # normal call

descend allows you to interactively explore the type-annotated source code by descending into the callees of f. Press enter to select a call to descend into, select ↩ to ascend, and press q or control-c to quit. You can also toggle various aspect of the view, for example to suppress "type-stable" (concretely inferred) annotations or view non-concrete types in red. Currently-active options are highlighted with color; press the corresponding key to toggle these options. Below we walk through a simple example of these interactive features; you can also see Cthulhu v2.8 in action in this video.

Usage: descend

function foo()
    T = rand() > 0.5 ? Int64 : Float64
    sum(rand(T, 100))
end

descend(foo, Tuple{})     # option 1: specify by function name and argument types
@descend foo()            # option 2: apply `@descend` to a working execution of the function

If you do this, you'll see quite a bit of text output. Let's break it down and see it section-by-section. At the top, you may see something like this:

source-section-all

This shows your original source code (together with line numbers, which here were in the REPL). The cyan annotations are the types of the variables: Union{Float64, Int64} means "either a Float64 or an Int64". Small concrete unions (where all the possibilities are known exactly) are generally are not a problem for type inference, unless there are so many that Julia stops trying to work out all the different combinations (see this blog post for more information).

Note: if the function has default positional or keyword arguments, you may see only the signature of the function. Internally, Julia creates additional methods to fill in default arguments, which in turn call the "body" method that appears in the source text. If you're descending into one of these "default-filling" functions, you won't be able to see types on variables that appear in the body method, so to reduce confusing the entire body is eliminated. You'll have an opportunity to descend further into the "body" method in the "call menu" described below.

In the next section you may see something like

toggles

This section shows you some interactive options you have for controlling the display. Normal text inside [] generally indicates "off", and color is used for "on" or specific options. For example, if you hit w to turn on warnings, now you should see something like this:

warn

Note that the w in the [w]arn toggle is now shown in cyan, indicating that it is "on." Now you can see small concrete unions in yellow, and concretely inferred code in cyan. Serious forms of poor inferrability are colored in red (of which there are none in this example); these generally hurt runtime performance and may make compiled code more vulnerable to being invalidated.

In the final section, you see:

calls

This is a menu of calls that you can further descend into. Move the dot with the up and down arrow keys, and hit Enter to descend into a particular call. Note that the naming of calls can sometimes vary from what you see in the source-text; for example, if you're descending into kwarg-function foo, then the "body" function might be called something like #foo#123.

Any calls that are made at runtime (dynamic dispatch) cannot be descended into; if you select one, you'll see

[ Info: This is a runtime call. You cannot descend into it.

and the call menu will be printed again.

Calls that start with %nn = ... are in Julia's internal Abstract Syntax Tree (AST) form; for these calls, Cthulhu and/or TypedSyntax (a sub-package living inside the Cthulhu repository) failed to "map" the call back to the original source code.

Caveats

As a word of warning, mapping type inference results back to the source is hard, and there may be errors or omissions in this mapping. See the TypedSyntax README for further details about the challenges. When you think there are reasons to doubt what you're seeing, a reliable but harder-to-interpret strategy is to directly view the [T]yped code rather than the [S]ource code.

For problems you encounter, please consider filing issues for (and/or making pull requests to fix) any failures you observe. See CONTRIBUTING.md for tips on filing effective bug reports.

Methods: descend

  • @descend_code_typed
  • descend_code_typed
  • @descend_code_warntype
  • descend_code_warntype
  • @descend: Shortcut for @descend_code_typed
  • descend: Shortcut for descend_code_typed

Usage: ascend

Cthulhu also provides the "upwards-looking" ascend. While descend allows you to explore a call tree starting from the outermost caller, ascend allows you to explore a call chain or tree starting from the innermost callee. Its primary purpose is to support analysis of invalidation and inference triggers in conjunction with SnoopCompile, but you can use it as a standalone tool. There is a video using ascend to fix invalidations, where the part on ascend starts at minute 4:55.

For example, you can use it to examine all the inferred callers of a method instance:

julia> m = which(length, (Set{Symbol},))
length(s::Set) in Base at set.jl:55

julia> mi = m.specializations[1]      # or `mi = first(Base.specializations(m))` on Julia 1.10+
MethodInstance for length(::Set{Symbol})

julia> ascend(mi)
Choose a call for analysis (q to quit):
 >   length(::Set{Symbol})
       union!(::Set{Symbol}, ::Vector{Symbol})
         Set{Symbol}(::Vector{Symbol})
         intersect!(::Set{Union{Int64, Symbol}}, ::Vector{Symbol})
           _shrink(::typeof(intersect!), ::Vector{Union{Int64, Symbol}}, ::Tuple{Vector{Symbol}})
             intersect(::Vector{Union{Int64, Symbol}}, ::Vector{Symbol})
       union!(::Set{Symbol}, ::Set{Symbol})
         union!(::Set{Symbol}, ::Set{Symbol}, ::Set{Symbol})
           union(::Set{Symbol}, ::Set{Symbol})

You use the up/down arrows to navigate this menu, enter to select a call to descend into, and your space bar to toggle branch-folding.

It also works on stacktraces. If your version of Julia stores the most recent error in the global err variable, you can use

julia> using Cthulhu

julia> sqrt(-1)
ERROR: DomainError with -1.0:
sqrt will only return a complex result if called with a complex argument. Try sqrt(Complex(x)).
Stacktrace:
 [1] throw_complex_domainerror(f::Symbol, x::Float64)
   @ Base.Math ./math.jl:33
 [2] sqrt
   @ ./math.jl:677 [inlined]
 [3] sqrt(x::Int64)
   @ Base.Math ./math.jl:1491
 [4] top-level scope
   @ REPL[1]:1

julia> ascend(err)
Choose a call for analysis (q to quit):
 >   throw_complex_domainerror(::Symbol, ::Float64) at ./math.jl:33
       sqrt(::Int64) at ./math.jl:1491

If this isn't available to you, a more "manual" approach is:

julia> bt = try
           [sqrt(x) for x in [1, -1]]
       catch
           catch_backtrace()
       end;

julia> ascend(bt)
Choose a call for analysis (q to quit):
 >   throw_complex_domainerror(::Symbol, ::Float64) at ./math.jl:33
       sqrt at ./math.jl:582 => sqrt at ./math.jl:608 => iterate at ./generator.jl:47 => collect_to! at ./array.jl:710 => collect_to_with_first!(::Vector{Float64}, ::Float64, ::Base.Generator{Vector{Int64}, typeof(sqrt)}, ::Int64) at ./array.jl:688
         collect(::Base.Generator{Vector{Int64}, typeof(sqrt)}) at ./array.jl:669
           eval(::Module, ::Any) at ./boot.jl:360
             eval_user_input(::Any, ::REPL.REPLBackend) at /home/tim/src/julia-master/usr/share/julia/stdlib/v1.6/REPL/src/REPL.jl:139
...

The calls that appear on the same line separated by => represent inlined methods; when you select such a line, you enter at the final (topmost) call on that line.

Using Cthulhu may be particularly useful for MethodErrors, since those exist purely in the type-domain.

By default,

  • descend views non-optimized code without "warn" coloration of types
  • ascend views non-optimized code with "warn" coloration

You can toggle between these with o and w.

Combine static and runtime information

Cthulhu has access only to "static" type information, the same information available to the Julia compiler and type inference. In some situations, this will lead to incomplete or misleading information about type instabilities.

Take for example:

using Infiltrator: @infiltrate
using Cthulhu: @descend
using Base: @noinline # already exported, but be explcit

function foo(n)
    x = n < 2 ? 2 * n : 2.5 * n
    y = n < 4 ? 3 * n : 3.5 * n
    z = n < 5 ? 4 * n : 4.5 * n
    # on Julia v1.6, there is no union splitting for this number of cases.
    bar(x, y, z)
end

@noinline function bar(x, y, z)
    string(x + y + z)
end

Then invoke:

Cthulhu.@descend foo(5)

Now, descend into bar: move the cursor down (or wrap around by hitting the up arrow) until the dot is next to the bar call:

 ⋮
   4  (4.5 * n::Int64)::Float64
 • 6 bar(x, y, z)
   ↩

and then hit Enter. Then you will see the code for bar with its type annotations.

Notice that many variables are annotated as Union. To give Cthulhu more complete type information, we have to actually run some Julia code. There are many ways to do this. In this example, we use Infiltrator.jl.

Add an @infiltrate:

function foo(n)
    x = n < 2 ? 2 * n : 2.5 * n
    y = n < 4 ? 3 * n : 3.5 * n
    z = n < 5 ? 4 * n : 4.5 * n
    # on Julia v1.6, there is no union splitting for this number of cases.
    @infiltrate
    bar(x, y, z)
end

@noinline function bar(x, y, z)
    string(x + y + z)
end

Now invoke foo to get REPL in the scope just before bar gets called:

julia> foo(4)
Infiltrating foo(n::Int64) at ex.jl:10:

infil>

Enter @descend bar(x, y, z) you can see that, for foo(4), the types within bar are fully inferred.

Viewing the internal representation of Julia code

Anyone using Cthulhu to investigate the behavior of Julia's compiler will prefer to examine the While Cthulhu tries to place type-annotations on the source code, this obscures detail and can occassionally go awry (see details here). For anyone who needs more direct insight, it can be better to look directly at Julia's internal representations of type-inferred code. Looking at type-inferred code can be a bit daunting initially, but you grow more comfortable with practice. Consider starting with a tutorial on "lowered" representation, which introduces most of the new concepts. Type-inferrred code differs from lowered representation by having additional type annotation. Moreover, call statements that can be inferred are converted to invokes (these correspond to static dispatch), whereas dynamic dispatch is indicated by the remaining call statements. Depending on whether you're looking at optimized or non-optimized code, it may also incorporate inlining and other fairly significant transformations of the original code as written by the programmer.

This video demonstrates Cthulhu for viewing "raw" type-inferred code: Watch on YouTube Click to watch video

The version of Cthulhu in the demo is a little outdated, without the newest features, but may still be relevant for users who want to view code at this level of detail.

Customization

The default configuration of toggles in the @descend menu can be customized with Cthulhu.CONFIG and persistently saved (via Preferences.jl) using Cthulhu.save_config!():

julia> Cthulhu.CONFIG.enable_highlighter = true # Change default
true

julia> Cthulhu.save_config!(Cthulhu.CONFIG) # Will be automatically read next time you `using Cthulhu`

cthulhu.jl's People

Contributors

aviatesk avatar chriselrod avatar chrisrackauckas avatar dilumaluthge avatar fingolfin avatar gaurav-arya avatar goretkin avatar hwjsnc avatar ianatol avatar janebert avatar jeffbezanson avatar jishnub avatar jpfairbanks avatar juliatagbot avatar keno avatar kristofferc avatar maleadt avatar moelf avatar oscardssmith avatar oxinabox avatar simeonschaub avatar simsurace avatar staticfloat avatar timholy avatar tkf avatar vchuravy avatar vtjnash avatar willow-ahrens avatar yingboma avatar zentrik avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

cthulhu.jl's Issues

Print type instability colors in descent menu

I love the type instability colors while descending; often times I am simply chasing the red down into the endless abyss; it would be great if we could accelerate my fall by printing the red text style in the descent multiple-choice menu as well, so I don't need to look for ::Any at the end of the call; I can simply let my primitive hind-brain steer me down.

Fails to descend into Type constructors

For example:

julia> @descend Array{Int}(undef, 2)
ERROR: MethodError: no method matching _descend(::Nothing; params=Core.Compiler.Params(Core.Compiler.InferenceResult[], 0x0000000000006741, true, true, true, false, 100, 1000, 400, 4, 4, 8, 3, 32), iswarn=false)
Closest candidates are:
  _descend(::Any, ::Any; params, kwargs...) at /home/mbauman/.julia/packages/Cthulhu/c2nCD/src/Cthulhu.jl:202
  _descend(::Core.MethodInstance; iswarn, params, optimize, kwargs...) at /home/mbauman/.julia/packages/Cthulhu/c2nCD/src/Cthulhu.jl:112
Stacktrace:
 [1] #_descend#37(::Core.Compiler.Params, ::Base.Iterators.Pairs{Symbol,Bool,Tuple{Symbol},NamedTuple{(:iswarn,),Tuple{Bool}}}, ::typeof(Cthulhu._descend), ::Any, ::Any) at /home/mbauman/.julia/packages/Cthulhu/c2nCD/src/Cthulhu.jl:203
 [2] #_descend at ./none:0 [inlined]
 [3] #_descend_with_error_handling#31(::Base.Iterators.Pairs{Symbol,Bool,Tuple{Symbol},NamedTuple{(:iswarn,),Tuple{Bool}}}, ::typeof(Cthulhu._descend_with_error_handling), ::Type, ::Any) at /home/mbauman/.julia/packages/Cthulhu/c2nCD/src/Cthulhu.jl:88
 [4] #_descend_with_error_handling at ./none:0 [inlined]
 [5] #descend_code_typed#30 at /home/mbauman/.julia/packages/Cthulhu/c2nCD/src/Cthulhu.jl:62 [inlined]
 [6] descend_code_typed(::Type, ::Any) at /home/mbauman/.julia/packages/Cthulhu/c2nCD/src/Cthulhu.jl:62
 [7] top-level scope at REPL[32]:1

Of course this example wouldn't be terribly interesting, but this came from a different example where the constructor was actually doing something I wanted to look at.

Missing verbose_stmt_info on master

I've switched to master. On a complex code base I got an error; I managed to find a MWE:

julia> using Cthulhu
[ Info: Precompiling Cthulhu [f68482b8-f384-11e8-15f7-abe071a5a75f]

julia> f(w, dim) = [i == dim ? w[i]/2 : w[i]/1 for i in eachindex(w)]
f (generic function with 1 method)

julia> f([1,2,3], 3)
3-element Vector{Float64}:
 1.0
 2.0
 1.5

julia> @descend f([1,2,3], 3)
ERROR: MethodError: no method matching verbose_stmt_info(::Cthulhu.CthulhuInterpreter)
Closest candidates are:
  verbose_stmt_info(::Core.Compiler.NativeInterpreter) at compiler/types.jl:213
Stacktrace:
  [1] return_type_tfunc(interp::Core.Compiler.AbstractInterpreter, argtypes::Vector{Any}, sv::Core.Compiler.InferenceState)
    @ Core.Compiler ./compiler/tfuncs.jl:1615
  [2] abstract_call_known(interp::Cthulhu.CthulhuInterpreter, f::Any, fargs::Vector{Any}, argtypes::Vector{Any}, sv::Core.Compiler.InferenceState, max_methods::Int64)
    @ Core.Compiler ./compiler/abstractinterpretation.jl:1164
  [3] abstract_call(interp::Cthulhu.CthulhuInterpreter, fargs::Vector{Any}, argtypes::Vector{Any}, sv::Core.Compiler.InferenceState, max_methods::Int64)
    @ Core.Compiler ./compiler/abstractinterpretation.jl:1267
  [4] abstract_call(interp::Cthulhu.CthulhuInterpreter, fargs::Vector{Any}, argtypes::Vector{Any}, sv::Core.Compiler.InferenceState)
    @ Core.Compiler ./compiler/abstractinterpretation.jl:1247
...

PR coming.

assertion failure for `optimize=false`

julia> using Cassette, Cthulhu

julia> Cassette.@context NoOp
Cassette.Context{nametype(NoOp),M,P,T,B} where B<:Union{Nothing, IdDict{Module,Dict{Symbol,BindingMeta}}} where P<:Cassette.AbstractPass where T<:Union{Nothing, Tag} where M

julia> f(x) = sin(x)
f (generic function with 1 method)

julia> f2(x) = Cassette.overdub(NoOp(), f::typeof(f), x)
f2 (generic function with 1 method)

julia> f3(x) = Cassette.overdub(NoOp(), f2::typeof(f2), x)
f3 (generic function with 1 method)

julia> @descend optimize=false f3(1)

│ ─ %-1 = invoke f3(::Int64)::Any
CodeInfo(
1%1 = Cassette.overdub::Const(overdub, false)
│   %2 = (Main.NoOp)()::Const(Context{nametype(NoOp),Nothing,NoPass,Nothing,Nothing}(nametype(NoOp)(), nothing, NoPass(), nothing, nothing), false)
│   %3 = (Main.typeof)(Main.f2)::Const(typeof(f2), false)
│   %4 = (Core.typeassert)(Main.f2, %3)::Const(f2, false)
│   %5 = (%1)(%2, %4, x)::Any
└──      return %5
) => Any

In `f3` select a call to descend into or  to ascend. [q] to quit.
   %2 = invoke Cassette.Context{nametype(NoOp),M,P,T,B} where B<:Union{Nothing, IdDict{Module,Dict{Symbol,BindingMeta}}} where P<:Cassette.AbstractPass where T<:Union{Nothing, Tag} where M()::Core.Compiler.Const(Cassette.Context{namety..%5 = invoke Cassette.overdub(::Cassette.Context{nametype(NoOp),Nothing,Cassette.NoPass,Nothing,Nothing},::typeof(f2),::Int64)::Any
   
ERROR: AssertionError: arg.head === :static_parameter
Stacktrace:
 [1] find_type(::Core.CodeInfo, ::Type, ::Expr) at /Users/jarrettrevels/.julia/dev/Cthulhu/src/Cthulhu.jl:121
 [2] find_type(::Core.CodeInfo, ::DataType, ::Core.SlotNumber) at /Users/jarrettrevels/.julia/dev/Cthulhu/src/Cthulhu.jl:143
 [3] (::getfield(Cthulhu, Symbol("##4#6")){Core.CodeInfo,DataType})(::Core.SlotNumber) at /Users/jarrettrevels/.julia/dev/Cthulhu/src/Cthulhu.jl:187
 [4] collect_to!(::Array{DataType,1}, ::Base.Generator{Array{Any,1},getfield(Cthulhu, Symbol("##4#6")){Core.CodeInfo,DataType}}, ::Int64, ::Int64) at ./generator.jl:47
 [5] collect_to_with_first!(::Array{DataType,1}, ::Type, ::Base.Generator{Array{Any,1},getfield(Cthulhu, Symbol("##4#6")){Core.CodeInfo,DataType}}, ::Int64) at ./array.jl:627
 [6] _collect(::Array{Any,1}, ::Base.Generator{Array{Any,1},getfield(Cthulhu, Symbol("##4#6")){Core.CodeInfo,DataType}}, ::Base.EltypeUnknown, ::Base.HasShape{1}) at ./array.jl:621
 [7] collect_similar(::Array{Any,1}, ::Base.Generator{Array{Any,1},getfield(Cthulhu, Symbol("##4#6")){Core.CodeInfo,DataType}}) at ./array.jl:545
 [8] map(::Function, ::Array{Any,1}) at ./abstractarray.jl:2014
 [9] #find_callsites#3(::Base.Iterators.Pairs{Symbol,Bool,Tuple{Symbol},NamedTuple{(:optimize,),Tuple{Bool}}}, ::Function, ::Core.CodeInfo, ::Type) at /Users/jarrettrevels/.julia/dev/Cthulhu/src/Cthulhu.jl:187
 [10] (::getfield(Cthulhu, Symbol("#kw##find_callsites")))(::NamedTuple{(:optimize,),Tuple{Bool}}, ::typeof(Cthulhu.find_callsites), ::Core.CodeInfo, ::Type) at ./none:0
 [11] #_descend#8(::Bool, ::Base.Iterators.Pairs{Symbol,Bool,Tuple{Symbol},NamedTuple{(:optimize,),Tuple{Bool}}}, ::Function, ::Any, ::Any) at /Users/jarrettrevels/.julia/dev/Cthulhu/src/Cthulhu.jl:229
 [12] #_descend at ./none:0 [inlined]
 [13] #_descend#8(::Bool, ::Base.Iterators.Pairs{Symbol,Bool,Tuple{Symbol},NamedTuple{(:optimize,),Tuple{Bool}}}, ::Function, ::Any, ::Any) at /Users/jarrettrevels/.julia/dev/Cthulhu/src/Cthulhu.jl:246
 [14] #_descend_with_error_handling#2(::Base.Iterators.Pairs{Symbol,Bool,Tuple{Symbol,Symbol},NamedTuple{(:iswarn, :optimize),Tuple{Bool,Bool}}}, ::Function, ::Function, ::Any) at ./none:0
 [15] #_descend_with_error_handling at ./none:0 [inlined]
 [16] #descend_code_typed#1 at /Users/jarrettrevels/.julia/dev/Cthulhu/src/Cthulhu.jl:80 [inlined]
 [17] (::getfield(Cthulhu, Symbol("#kw##descend_code_typed")))(::NamedTuple{(:optimize,),Tuple{Bool}}, ::typeof(descend_code_typed), ::Function, ::Type) at ./none:0
 [18] top-level scope at none:0

Note also that I had to put ::typeof(f2) etc. in there; if I leave those annotations out, Cthulhu thinks that those arguments are ::GlobalRef in the invoke signature (which is obviously bonkers)

julia> versioninfo()
Julia Version 1.1.0-DEV.772
Commit 3dd678f646 (2018-11-30 22:20 UTC)
Platform Info:
  OS: macOS (x86_64-apple-darwin17.5.0)
  CPU: Intel(R) Core(TM) i7-7920HQ CPU @ 3.10GHz
  WORD_SIZE: 64
  LIBM: libopenlibm
  LLVM: libLLVM-6.0.1 (ORCJIT, skylake)

holy moly this package is amazing for debugging the inference cache

type UnionAll has no field name

%3  = invoke #write#54(…,…,…,…,…,…,…,…)::Base.GenericIOBuffer{Array{UInt8,1}}
   
ERROR: type UnionAll has no field name
Stacktrace:
 [1] getproperty(::Type, ::Symbol) at ./Base.jl:15
 [2] #find_callsites#9(::Core.Compiler.Params, ::Base.Iterators.Pairs{Symbol,Symbol,Tuple{Symbol},NamedTuple{(:debuginfo,),Tuple{Symbol}}}, ::typeof(Cthulhu.find_callsites), ::Core.CodeInfo, ::Core.MethodInstance, ::Array{Any,1}) at /Users/jacobquinn/.julia/dev/Cthulhu/src/reflection.jl:128
 [3] (::Cthulhu.var"#kw##find_callsites")(::NamedTuple{(:params, :debuginfo),Tuple{Core.Compiler.Params,Symbol}}, ::typeof(Cthulhu.find_callsites), ::Core.CodeInfo, ::Core.MethodInstance, ::Array{Any,1}) at /Users/jacobquinn/.julia/dev/Cthulhu/src/reflection.jl:42
 [4] #_descend#29(::Bool, ::Core.Compiler.Params, ::Bool, ::Base.Iterators.Pairs{Symbol,Symbol,Tuple{Symbol},NamedTuple{(:debuginfo,),Tuple{Symbol}}}, ::typeof(Cthulhu._descend), ::Core.MethodInstance) at /Users/jacobquinn/.julia/dev/Cthulhu/src/Cthulhu.jl:151
 [5] (::Cthulhu.var"#kw##_descend")(::NamedTuple{(:params, :optimize, :iswarn, :debuginfo),Tuple{Core.Compiler.Params,Bool,Bool,Symbol}}, ::typeof(Cthulhu._descend), ::Core.MethodInstance) at /Users/jacobquinn/.julia/dev/Cthulhu/src/Cthulhu.jl:139
 [6] #_descend#29(::Bool, ::Core.Compiler.Params, ::Bool, ::Base.Iterators.Pairs{Symbol,Symbol,Tuple{Symbol},NamedTuple{(:debuginfo,),Tuple{Symbol}}}, ::typeof(Cthulhu._descend), ::Core.MethodInstance) at /Users/jacobquinn/.julia/dev/Cthulhu/src/Cthulhu.jl:193
 [7] (::Cthulhu.var"#kw##_descend")(::NamedTuple{(:params, :optimize, :iswarn, :debuginfo),Tuple{Core.Compiler.Params,Bool,Bool,Symbol}}, ::typeof(Cthulhu._descend), ::Core.MethodInstance) at /Users/jacobquinn/.julia/dev/Cthulhu/src/Cthulhu.jl:139
 [8] #_descend#29(::Bool, ::Core.Compiler.Params, ::Bool, ::Base.Iterators.Pairs{Symbol,Symbol,Tuple{Symbol},NamedTuple{(:debuginfo,),Tuple{Symbol}}}, ::typeof(Cthulhu._descend), ::Core.MethodInstance) at /Users/jacobquinn/.julia/dev/Cthulhu/src/Cthulhu.jl:193
 [9] (::Cthulhu.var"#kw##_descend")(::NamedTuple{(:params, :optimize, :iswarn, :debuginfo),Tuple{Core.Compiler.Params,Bool,Bool,Symbol}}, ::typeof(Cthulhu._descend), ::Core.MethodInstance) at /Users/jacobquinn/.julia/dev/Cthulhu/src/Cthulhu.jl:139
 [10] #_descend#29(::Bool, ::Core.Compiler.Params, ::Bool, ::Base.Iterators.Pairs{Union{},Union{},Tuple{},NamedTuple{(),Tuple{}}}, ::typeof(Cthulhu._descend), ::Core.MethodInstance) at /Users/jacobquinn/.julia/dev/Cthulhu/src/Cthulhu.jl:193
 [11] #_descend at ./none:0 [inlined]
 [12] #_descend#34(::Core.Compiler.Params, ::Base.Iterators.Pairs{Symbol,Bool,Tuple{Symbol},NamedTuple{(:iswarn,),Tuple{Bool}}}, ::typeof(Cthulhu._descend), ::Any, ::Any) at /Users/jacobquinn/.julia/dev/Cthulhu/src/Cthulhu.jl:229
 [13] #_descend at ./none:0 [inlined]
 [14] #_descend_with_error_handling#28(::Base.Iterators.Pairs{Symbol,Bool,Tuple{Symbol},NamedTuple{(:iswarn,),Tuple{Bool}}}, ::typeof(Cthulhu._descend_with_error_handling), ::Function, ::Any) at /Users/jacobquinn/.julia/dev/Cthulhu/src/Cthulhu.jl:114
 [15] #_descend_with_error_handling at ./none:0 [inlined]
 [16] #descend_code_typed#26 at /Users/jacobquinn/.julia/packages/Cthulhu/8utSI/src/Cthulhu.jl:89 [inlined]
 [17] descend_code_typed(::Function, ::Any) at /Users/jacobquinn/.julia/dev/Cthulhu/src/Cthulhu.jl:89
 [18] top-level scope at REPL[15]:1

I can provide a full repro if needed. The issue seems to be here where we need another elseif branch to handle UnionAll. I'm not quite sure what the code is doing there, but if someone gives me some guidance, I could try to take a stab at fixing.

Varargs invoke is inconsistently inferred

While investigating JuliaLang/julia#39175 with Cthulhu 1.6 on JuliaLang/julia@ffa966ee22, the following puzzled me:

Consider

julia> @noinline function f(x...)
       return x
       end;

julia> g(x) = f(x, 1);

julia> @code_warntype optimize=:true g(1)
Variables
  #self#::Core.Const(g)
  x::Int64

Body::Tuple{Int64, Int64}
1%1 = invoke Main.f(_2::Int64, 1::Vararg{Int64})::Tuple{Int64, Int64}
└──      return %1

This suggests that the invoked signature of f is (Int64, Vararg{Int64}).
However, doing @descend g(1) and pressing o to get the optimized version yields

CodeInfo(
    @ REPL[2]:1 within `g'
1 ─ %1 = Main.f(x, 1)::Tuple{Int64, Int64}
└──      return %1
)
Select a call to descend into or ↩ to ascend. [q]uit. [b]ookmark.
Toggles: [o]ptimize, [w]arn, [v]erbose printing for warntype code, [d]ebuginfo, [s]yntax highlight for Source/LLVM/Native.
Show: [S]ource code, [A]ST, [L]LVM IR, [N]ative code
Actions: [E]dit source code, [R]evise and redisplay
Advanced: dump [P]arams cache.
 • %1  = f(::Int64,::Int64)::Tuple{Int64, Int64}

which suggests that the invoked signature is (Int64, Int64).

So which one is correct?

We don't handle TypeTuple inputs like `code_typed` does

julia> code_typed(Tuple{typeof(Base.zero), Type{Tuple{Int64}}})
3-element Array{Any,1}:
                                                                                                                                                                      CodeInfo(
1 ─ %1 = Base.convert($(Expr(:static_parameter, 1)), x)::Tuple{typeof(zero),DataType}
└──      return %1
) => Tuple{typeof(zero),DataType}
 CodeInfo(
1 ─ %1 = $(Expr(:static_parameter, 1))::Core.Compiler.Const(Tuple{typeof(zero),Type{Tuple{Int64}}}, false)
│   %2 = Core._apply(Core.tuple, nt)::Tuple
│   %3 = Base.convert(%1, %2)::Tuple{typeof(zero),DataType}
└──      return %3
) => Tuple{typeof(zero),DataType}
                                                                                                                                               CodeInfo(
1 ─ %1 = Base._totuple($(Expr(:static_parameter, 1)), itr)::Core.Compiler.Const((zero, Tuple{Int64}), false)
└──      return %1
) => Tuple{typeof(zero),DataType}

But sadly:

julia> using Cthulhu

julia> descend_code_typed(Tuple{typeof(Base.zero), Type{Tuple{Int64}}})
ERROR: MethodError: no method matching descend_code_typed(::Type{Tuple{typeof(zero),Type{Tuple{Int64}}}})
Closest candidates are:
  descend_code_typed(::Any, ::Any; kwargs...) at /Users/nathan.daly/work/raicode-testing/.julia/packages/Cthulhu/cytTX/src/Cthulhu.jl:62
Stacktrace:
 [1] top-level scope at REPL[35]:1

Broken on master

ERROR: unimplemented
Stacktrace:
 [1] error(::String) at ./error.jl:33
 [2] writeLine(::Base.GenericIOBuffer{Array{UInt8,1}}, ::Cthulhu.CthulhuMenu, ::Int64, ::Bool) at /home/keno/julia-tkf/usr/share/julia/stdlib/v1.5/REPL/src/TerminalMenus/AbstractMenu.jl:87
 [3] printMenu(::Base.TTY, ::Cthulhu.CthulhuMenu, ::Int64; init::Bool) at /home/keno/julia-tkf/usr/share/julia/stdlib/v1.5/REPL/src/TerminalMenus/AbstractMenu.jl:258
 [4] request(::REPL.Terminals.TTYTerminal, ::Cthulhu.CthulhuMenu) at /home/keno/julia-tkf/usr/share/julia/stdlib/v1.5/REPL/src/TerminalMenus/AbstractMenu.jl:127
 [5] request(::Cthulhu.CthulhuMenu) at /home/keno/julia-tkf/usr/share/julia/stdlib/v1.5/REPL/src/TerminalMenus/AbstractMenu.jl:119
 [6] _descend(::Core.MethodInstance; iswarn::Bool, params::Core.Compiler.Params, optimize::Bool, kwargs::Base.Iterators.Pairs{Union{},Union{},Tuple{},NamedTuple{(),Tuple{}}}) at /home/keno/.julia/dev/Cthulhu/src/Cthulhu.jl:158
 [7] _descend(::Any, ::Any; params::Core.Compiler.Params, kwargs::Base.Iterators.Pairs{Symbol,Bool,Tuple{Symbol},NamedTuple{(:iswarn,),Tuple{Bool}}}) at /home/keno/.julia/dev/Cthulhu/src/Cthulhu.jl:234
 [8] _descend_with_error_handling(::Function, ::Any; kwargs::Base.Iterators.Pairs{Symbol,Bool,Tuple{Symbol},NamedTuple{(:iswarn,),Tuple{Bool}}}) at /home/keno/.julia/dev/Cthulhu/src/Cthulhu.jl:115
 [9] #descend_code_typed#26 at /home/keno/.julia/dev/Cthulhu/src/Cthulhu.jl:89 [inlined]
 [10] descend_code_typed(::Function, ::Any) at /home/keno/.julia/dev/Cthulhu/src/Cthulhu.jl:89
 [11] top-level scope at REPL[8]:1

Descending invokes `UndefVarError` from `show`?

MWE, temp.jl:

using TensorKit  # This is crucial for the bug the appear, even though the import isn't used
using Cthulhu

struct Inner{A, B}
    a::A
    b::B
end

Base.eltype(i::Inner) = reduce(promote_type, (eltype(x) for x in i))
Base.iterate(i::Inner) = (i.a, 1)
Base.iterate(i::Inner, state) = state == 1 ? (i.b, 2) : nothing
Base.length(i::Inner) = 2

struct Outer{N, T}
    inners::NTuple{N, T}
end

Base.eltype(o::Outer) = reduce(promote_type, (eltype(x) for x in o.inners))

f(o::Outer) = one(eltype(o))

a = randn(2)
b = randn(2)
i = Inner(a, b)
o = Outer((i, i))

f(o)
@descend_code_warntype f(o)

Running this:

julia> include("temp.jl")

│ ─ %-1  = invoke f(::Outer{2,Inner{Array{Float64,1},Array{Float64,1}}})::Any
Variables
  #self#::Core.Compiler.Const(f, false)
  o::Outer{2,Inner{Array{Float64,1},Array{Float64,1}}}

Body::Any
    @ /tmp/MERA/temp.jl:20 within `f'
   ┌ @ /tmp/MERA/temp.jl:18 within `eltype'
   │┌ @ Base.jl:33 within `getproperty'
1 ─││ %1 = Base.getfield(o, :inners)::Tuple{Inner{Array{Float64,1},Array{Float64,1}},Inner{Array{Float64,1},Array{Float64,1}}}
│  │└
│  │┌ @ generator.jl:32 within `Generator' @ generator.jl:32
│  ││ %2 = %new(Base.Generator{Tuple{Inner{Array{Float64,1},Array{Float64,1}},Inner{Array{Float64,1},Array{Float64,1}}},typeof(eltype)}, eltype, %1)::Base.Generator{Tuple{Inner{Array{Float64,1},Array{Float64,1}},Inner{Array{Float64,1},Array{Float64,1}}},typeof(eltype)}
│  │└
│  │ %3 = Main.reduce::typeof(reduce)
│  │ %4 = Main.promote_type::typeof(promote_type)
│  │┌ @ reduce.jl:456 within `reduce'
│  ││ %5 = invoke Base.:(var"#reduce#210")($(QuoteNode(Base.Iterators.Pairs{Union{},Union{},Tuple{},NamedTuple{(),Tuple{}}}()))::Base.Iterators.Pairs{Union{},Union{},Tuple{},NamedTuple{(),Tuple{}}}, %3::typeof(reduce), %4::Function, %2::Base.Generator{Tuple{Inner{Array{Float64,1},Array{Float64,1}},Inner{Array{Float64,1},Array{Float64,1}}},typeof(eltype)})::Any
│  └└
│   %6 = Main.one(%5)::Any
└──      return %6

Select a call to descend into or ↩ to ascend. [q]uit. [b]ookmark.
Toggles: [o]ptimize, [w]arn, [d]ebuginfo, [s]yntax highlight for Source/LLVM/Native.
Show: [S]ource code, [A]ST, [L]LVM IR, [N]ative code
Advanced: dump [P]arams cache.

   %5  = invoke #reduce#210(::Base.Iterators.Pairs{…},::typeof(reduce){…},::Function{…},::Base.Generator{…})::Any
 • %6  = call #one(::Any)::Any
   ↩
ERROR: LoadError: UndefVarError: T not defined
Stacktrace:
 [1] show(::Base.GenericIOBuffer{Array{UInt8,1}}, ::Type{
SYSTEM (REPL): showing an error caused an error
ERROR: UndefVarError: T not defined
Stacktrace:
 [1] show(::IOContext{REPL.Terminals.TTYTerminal}, ::Type{
SYSTEM (REPL): caught exception of type UndefVarError while trying to handle a nested exception; giving up

At the last step I've navigated to %6 and descended into it.

This on Cthulhu 1.2.0 and Julia versions 1.4.0, 1.5.0-rc1, and current master at the time of writing.

I have no idea what this means, where this originates from, and whether this is even Cthulhu's fault, but maybe you can help me figure it out. Most interestingly, the import of TensorKit is crucial for this to appear, even though I don't actually use anything from TensorKit. TensorKit does implement new methods for eltype and one though, which might be the source.

Line numbers for functions

I just tried Cthulhu for exploring how the sum implementation works, and I must say it's pretty great, thank you :-)

One thing I noticed is that it seems hard to connect the current function to the source where it's defined, as there's no line numbers or file names. Would it be easy to put those in? (Related idea - provide an option to list the source of the current function, or otherwise open it in an editor like @edit? Though this might be straying from the use case you had in mind with Cthulhu?)

Change I pretty rendering of AST to Meta.show_sexpr?

Thinking about this more.
One of the main reasonto want to look at the unpretty ast is to know about things that get reordered during parsing.
Or added during parsing.
Both of which are invisible on the pretty ast
(Most of the thing I am aware of for this relate to kwargs)

Anyway with that in mind what is better,
The giant dump,
Or the pseudolisp sexpr

BoundsError when using @descend on a function using @profile

               _
   _       _ _(_)_     |  Documentation: https://docs.julialang.org
  (_)     | (_) (_)    |
   _ _   _| |_  __ _   |  Type "?" for help, "]?" for Pkg help.
  | | | | | | |/ _` |  |
  | | |_| | | | (_| |  |  Version 1.1.1 (2019-05-16)
 _/ |\__'_|_|_|\__'_|  |  Official https://julialang.org/ release
|__/                   |

julia> using Cthulhu

julia> using Profile

julia> function run()
           @profile println()
       end
run (generic function with 1 method)

julia> @descend run()
ERROR: BoundsError: attempt to access 10-element Array{Int64,1} at index [0]
Stacktrace:
 [1] getindex(::Array{Int64,1}, ::Int64) at ./array.jl:729
 [2] Core.Compiler.IncrementalCompact(::Core.Compiler.IRCode, ::Bool) at ./compiler/ssair/ir.jl:510
 [3] dce!(::Core.CodeInfo, ::Core.MethodInstance) at /Users/green/.julia/packages/Cthulhu/F3tIT/src/reflection.jl:161
 [4] preprocess_ci!(::Core.CodeInfo, ::Core.MethodInstance, ::Bool) at /Users/green/.julia/packages/Cthulhu/F3tIT/src/reflection.jl:191
 [5] #_descend#37(::Bool, ::Core.Compiler.Params, ::Bool, ::Base.Iterators.Pairs{Union{},Union{},Tuple{},NamedTuple{(),Tuple{}}}, ::Function, ::Core.MethodInstance) at /Users/green/.julia/packages/Cthulhu/F3tIT/src/Cthulhu.jl:122
 [6] #_descend at ./none:0 [inlined]
 [7] #_descend#42(::Core.Compiler.Params, ::Base.Iterators.Pairs{Symbol,Bool,Tuple{Symbol},NamedTuple{(:iswarn,),Tuple{Bool}}}, ::Function, ::Any, ::Any) at /Users/green/.julia/packages/Cthulhu/F3tIT/src/Cthulhu.jl:211
 [8] #_descend at ./none:0 [inlined]
 [9] #_descend_with_error_handling#36(::Base.Iterators.Pairs{Symbol,Bool,Tuple{Symbol},NamedTuple{(:iswarn,),Tuple{Bool}}}, ::Function, ::Function, ::Any) at /Users/green/.julia/packages/Cthulhu/F3tIT/src/Cthulhu.jl:88
 [10] #_descend_with_error_handling at ./none:0 [inlined]
 [11] #descend_code_typed#35 at /Users/green/.julia/packages/Cthulhu/F3tIT/src/Cthulhu.jl:62 [inlined]
 [12] descend_code_typed(::Function, ::Any) at /Users/green/.julia/packages/Cthulhu/F3tIT/src/Cthulhu.jl:62
 [13] top-level scope at none:0

julia> versioninfo()
Julia Version 1.1.1
Commit 55e36cc (2019-05-16 04:10 UTC)
Platform Info:
  OS: macOS (x86_64-apple-darwin15.6.0)
  CPU: Intel(R) Core(TM) i7-7820HQ CPU @ 2.90GHz
  WORD_SIZE: 64
  LIBM: libopenlibm
  LLVM: libLLVM-6.0.1 (ORCJIT, skylake)
Environment:
  JULIA_EDITOR = vim

(v1.1) pkg> status
    Status `~/.julia/environments/v1.1/Project.toml`
  [c52e3926] Atom v0.8.8
  [6e4b80f9] BenchmarkTools v0.4.2
  [f68482b8] Cthulhu v0.1.0 #master (https://github.com/JuliaDebug/Cthulhu.jl)
  [864edb3b] DataStructures v0.17.0
  [e5e0dc1b] Juno v0.7.0
  [c46f51b8] ProfileView v0.4.1
  [295af30f] Revise v2.1.6
  [a8a75453] StatProfilerHTML v0.4.0
  [0796e94c] Tokenize v0.5.4

Bounds error during dce

julia> Base.VERSION
v"1.2.0-DEV.357"
julia> using Cthulhu
julia> g(t)=Dict(t);

julia> @descend g([1=>2])
│ ─ %-1  = invoke g(::Array{Pair{Int64,Int64},1})::Dict{Int64,Int64}
CodeInfo(
    @ REPL[3]:1 within `g'
1 ─ %1 = invoke Main.Dict(_2::Array{Pair{Int64,Int64},1})::Dict{Int64,Int64}
└──      return %1
)
Select a call to descend into or ↩ to ascend. [q]uit.
Toggles: [o]ptimize, [w]arn, [d]ebuginfo.
Show: [L]LVM IR, [N]ative code
Advanced: dump [P]arams cache.

 • %1  = invoke Type(::Array{Pair{Int64,Int64},1})::Dict{Int64,Int64}
   ↩
ERROR: BoundsError: attempt to access 23-element Array{Int64,1} at index [0]
Stacktrace:
 [1] getindex(::Array{Int64,1}, ::Int64) at ./array.jl:728
 [2] Core.Compiler.IncrementalCompact(::Core.Compiler.IRCode, ::Bool) at ./compiler/ssair/ir.jl:510
 [3] dce!(::Core.CodeInfo, ::Core.MethodInstance) at /home/bruhns/.julia/packages/Cthulhu/eUNdq/src/reflection.jl:113
 [4] preprocess_ci!(::Core.CodeInfo, ::Core.MethodInstance, ::Bool) at ~/.julia/packages/Cthulhu/eUNdq/src/reflection.jl:143
...

julia> using Pkg; Pkg.status("Cthulhu")
    Status `~/.julia/environments/v1.2/Project.toml`
  [f68482b8] Cthulhu v0.1.0 #master (https://github.com/vchuravy/Cthulhu.jl)

BoundsError in `dce!`

MWE:

using BandedMatrices, BlockArrays, BlockBandedMatrices, Cthulhu
A = BandedBlockBandedMatrix(Ones(10000, 10000), (fill(100, 100), fill(100, 100)), (1, 1), (1, 1))
λ,μ = subblockbandwidths(A)
J=Block.(1:nblocks(A,2))[1]; K=BlockBandedMatrices.blockcolrange(A,J)[1]
V = view(A,K,J)
data = BlockBandedMatrices.bandeddata(V)
@descend data[1]
julia> @descend data[1]
ERROR: BoundsError: attempt to access 0-element Array{Core.Compiler.NewNode,1} at index [7]
Stacktrace:
 [1] getindex at ./array.jl:742 [inlined]
 [2] getindex at ./compiler/ssair/ir.jl:738 [inlined]
 [3] abstract_eval_ssavalue at ./compiler/ssair/queries.jl:68 [inlined]
 [4] argextype(::Any, ::Core.Compiler.IncrementalCompact, ::Array{Any,1}, ::Array{Any,1}) at ./compiler/utilities.jl:173
 [5] argextype at ./compiler/utilities.jl:159 [inlined]
 [6] stmt_effect_free(::Any, ::Any, ::Core.Compiler.IncrementalCompact, ::Array{Any,1}) at ./compiler/ssair/queries.jl:37
 [7] maybe_erase_unused!(::Array{Int64,1}, ::Core.Compiler.IncrementalCompact, ::Int64, ::getfield(Core.Compiler, Symbol("##224#225"))) at ./compiler/ssair/ir.jl:1175
 [8] maybe_erase_unused! at ./compiler/ssair/ir.jl:1170 [inlined]
 [9] simple_dce!(::Core.Compiler.IncrementalCompact) at ./compiler/ssair/ir.jl:1262
 [10] finish at ./compiler/ssair/ir.jl:1285 [inlined]
 [11] dce!(::Core.CodeInfo, ::Core.MethodInstance) at /home/scheme/.julia/dev/Cthulhu/src/reflection.jl:164
 [12] preprocess_ci!(::Core.CodeInfo, ::Core.MethodInstance, ::Bool) at /home/scheme/.julia/dev/Cthulhu/src/reflection.jl:192
 [13] #_descend#38(::Bool, ::Core.Compiler.Params, ::Bool, ::Base.Iterators.Pairs{Union{},Union{},Tuple{},NamedTuple{(),Tuple{}}}, ::typeof(Cthulhu._descend), ::Core.MethodInstance) at /home/scheme/.julia/dev/Cthulhu/src/Cthulhu.jl:122
 [14] #_descend at ./none:0 [inlined]
 [15] #_descend#43(::Core.Compiler.Params, ::Base.Iterators.Pairs{Symbol,Bool,Tuple{Symbol},NamedTuple{(:iswarn,),Tuple{Bool}}}, ::typeof(Cthulhu._descend), ::Any, ::Any) at /home/scheme/.julia/dev/Cthulhu/src/Cthulhu.jl:211
 [16] #_descend at ./none:0 [inlined]
 [17] #_descend_with_error_handling#37(::Base.Iterators.Pairs{Symbol,Bool,Tuple{Symbol},NamedTuple{(:iswarn,),Tuple{Bool}}}, ::typeof(Cthulhu._descend_with_error_handling), ::Function, ::Any) at /home/scheme/.julia/dev/Cthulhu/src/Cthulhu.jl:88
 [18] #_descend_with_error_handling at ./none:0 [inlined]
 [19] #descend_code_typed#35 at /home/scheme/.julia/dev/Cthulhu/src/Cthulhu.jl:62 [inlined]
 [20] descend_code_typed(::Function, ::Any) at /home/scheme/.julia/dev/Cthulhu/src/Cthulhu.jl:62
 [21] top-level scope at REPL[21]:1

Split into Cthulhu and TypedCodeUtils

Some of Cthulhu and Traceur.jl functionality is shared as an example https://github.com/JuliaDebug/Cthulhu.jl/blob/master/src/reflection.jl and have very similar jobs https://github.com/JunoLab/Traceur.jl/blob/master/src/trace_static.jl

CUDAnative wants to validate the Julia IR (see JuliaGPU/CUDAnative.jl#389 (comment)) in order to provider users more informative error messages. Both Traceur and Cthulhu do enough other things that seem not quite the right place to have the basic functionality. I would propose extracting all the shared functionality into a common packages ala https://github.com/JuliaDebug/LoweredCodeUtils.jl with the focus being to provide tools to extract callsites (static, dynamic, generated, ccall) and if possible extract the lowered code for that callsite so that a consumers can recurse.

It get's a bit tricky since Cthulhu would like to define non-traditional callsites, e.g. calls into return_type (changing to the inference version), calls to GPU/threads/tasks. Which most other consumers won't worry about.

CC: @maleadt @Keno @pfitzseb @MikeInnes @jamii

Broken again on nightly

julia> @descend weighted_color_mean(0.5, RGB(0.9, 0.6, 0.1), RGB(0.9, 0.7, 0.2))
ERROR: MethodError: no method matching optimize(::Core.Compiler.OptimizationState, ::Core.Compiler.OptimizationParams, ::Type{Any})
Stacktrace:
 [1] do_typeinf_slottypes
   @ ~/.julia/packages/Cthulhu/31ncW/src/reflection.jl:253 [inlined]
 [2] _descend(mi::Core.MethodInstance; iswarn::Bool, params::Core.Compiler.NativeInterpreter, optimize::Bool, interruptexc::Bool, kwargs::Base.Iterators.Pairs{Union{}, Union{}, Tuple{}, NamedTuple{(), Tuple{}}})
   @ Cthulhu ~/.julia/packages/Cthulhu/31ncW/src/Cthulhu.jl:161

Cthulhu is missing a function call

While trying to use Cthulhu to descend into my NNlib.∇maxpool() call, it appears that it "can't see" one of the calls:

│ ─ %-1  = invoke #∇maxpool#185(::Base.Iterators.Pairs{Union{},Union{},Tuple{},NamedTuple{(),Tuple{}}},::Function,::Array{Float64,4},::Array{Float64,4},::PoolDims{2,(2, 2),(2, 2),(0, 0, 0, 0),(1, 1)})::AbstractArray{Float64,4}
Body::AbstractArray{Float64,4}
    @ /Users/sabae/.julia/dev/TimerOutputs/src/TimerOutput.jl:198 within `#∇maxpool#185'
   ┌ @ boot.jl:337 within `Type'
1 ─│ %1  = %new(Core.Box)::Core.Box
│  └
│   @ /Users/sabae/.julia/dev/TimerOutputs/src/TimerOutput.jl:187 within `#∇maxpool#185'
└──       goto #2
    @ /Users/sabae/.julia/dev/TimerOutputs/src/TimerOutput.jl:190 within `#∇maxpool#185'
   ┌ @ /Users/sabae/.julia/dev/NNlib/src/pooling.jl:119 within `macro expansion'
   │┌ @ /Users/sabae/.julia/dev/NNlib/src/dim_helpers/PoolDims.jl:17 within `input_size'
   ││┌ @ sysimg.jl:18 within `getproperty'
2 ─│││ %3  = (Base.getfield)(pdims, :I)::Tuple{Int64,Int64}
│  │└└
│  │┌ @ /Users/sabae/.julia/dev/NNlib/src/dim_helpers/PoolDims.jl:18 within `channels_in'
│  ││┌ @ sysimg.jl:18 within `getproperty'
│  │││ %4  = (Base.getfield)(pdims, :C_in)::Int64
│  │└└
│  │ %5  = $(Expr(:static_parameter, 2))::Core.Compiler.Const(4, false)
│  │┌ @ array.jl:154 within `size'
│  ││ %6  = (Base.arraysize)(dy, %5)::Int64
│  │└
│  │ %7  = (getfield)(%3, 1)::Int64
│  │ %8  = (getfield)(%3, 2)::Int64
│  │┌ @ array.jl:449 within `zeros' @ array.jl:452
│  ││┌ @ tuple.jl:168 within `map'
│  │││ %9  = (Core.tuple)(%7, %8, %4, %6)::NTuple{4,Int64}
│  ││└
│  ││┌ @ boot.jl:414 within `Type'
│  │││ %10 = $(Expr(:foreigncall, :(:jl_new_array), Array{Float64,4}, svec(Any, Any), :(:ccall), 2, Array{Float64,4}, :(%9)))::Array{Float64,4}
│  ││└
│  ││ %11 = invoke Base.fill!(%10::Array{Float64,4}, 0.0::Float64)::Array{Float64,4}
│  │└
│  │       (Core.setfield!)(%1, :contents, %11)
│  │ @ /Users/sabae/.julia/dev/NNlib/src/pooling.jl:120 within `macro expansion'
│  │ %13 = (Core.isdefined)(%1, :contents)::Bool
└──│       goto #4 if not %13
3 ─│       goto #5
4 ─│       $(Expr(:throw_undef_if_not, :dx, false))
5 ┄│ %17 = (Core.getfield)(%1, :contents)::Any
│  │ %18 = (NNlib.∇maxpool!)(%17, dy, x, pdims)::AbstractArray{Float64,4}
└──│       return %18
   └

Select a call to descend into or ↩ to ascend. [q]uit.
Toggles: [o]ptimize, [w]arn, [d]ebuginfo.
Show: [L]LVM IR, [N]ative code
Advanced: dump [P]arams cache.

 • %11  = invoke fill!(::Array{Float64,4},::Float64)::Array{Float64,4}
   ↩

Notice how it does not allow me to descend into the method that would set %18. This is the function I'm actually interested in, which is too bad. Switching to unoptimized IR does not help; it reveals many other methods that I could descend into, but the key method is still unavailable to me. Unfortunately this is using a dev'ed out version of NNlib, so it's not easy for me to share the code leading to this just yet; but when the NNlib hacking is done and if this issue still persists, I will do so.

`descend_code_warntype` is broken

I just noticed that @descend_code_warntype was broken by #10
Probably easy to fix, by just pulling in the code_warntype function form InteractiveUtils and making it work on MethodInstances.

Update to use AbstractInterpreter framework

Cthulhu should ideally be updated to use an AbstractInterpreter to do a full inference from the call site and then just be an interactive utility to explore what inference actually did. At the moment if inference does things like limiting call cycles, Cthulhu will re-do inference on descend and give a misleading view of the quality of type information. An additional benefit of using the AbstractInterpreter framework would be that Cthulhu could expose inference remarks. Perhaps @aviatesk would be interested in looking at this, since JET.jl is somewhat similar.

Bug: `@descend_code_warntype` does not accept keyword arguments (but `@descend` and `@descend_code_typed` correctly do)

Minimum working example:

julia> using Cthulhu

julia> foo(x) = x
foo (generic function with 1 method)

julia> @descend debuginfo=:source foo(1)

│ ─ %-1  = invoke foo(::Int64)::Int64
CodeInfo(
    @ REPL[2]:1 within `foo'
1 ─     return x
)
Select a call to descend into or ↩ to ascend. [q]uit.
Toggles: [o]ptimize, [w]arn, [d]ebuginfo, [s]yntax highlight for LLVM/Native.
Show: [L]LVM IR, [N]ative code
Advanced: dump [P]arams cache.

 • ↩


julia> @descend_code_typed debuginfo=:source foo(1)

│ ─ %-1  = invoke foo(::Int64)::Int64
CodeInfo(
    @ REPL[2]:1 within `foo'
1 ─     return x
)
Select a call to descend into or ↩ to ascend. [q]uit.
Toggles: [o]ptimize, [w]arn, [d]ebuginfo, [s]yntax highlight for LLVM/Native.
Show: [L]LVM IR, [N]ative code
Advanced: dump [P]arams cache.

 • ↩


julia> @descend_code_warntype debuginfo=:source foo(1)
ERROR: function descend_code_warntype does not accept keyword arguments
Stacktrace:
 [1] kwfunc(::Any) at ./boot.jl:330
 [2] top-level scope at none:0

could not spawn `pygmentize -l llvm`

ERROR: IOError: could not spawn `pygmentize -l llvm`: no such file or directory (ENOENT)
Stacktrace:
 [1] _spawn_primitive(::String, ::Cmd, ::Array{Any,1}) at ./process.jl:400
 [2] setup_stdios(::getfield(Base, Symbol("##505#506")){Cmd}, ::Array{Any,1}) at ./process.jl:413
 [3] _spawn at ./process.jl:412 [inlined]
 [4] _spawn(::Base.CmdRedirect, ::Array{Any,1}) at ./process.jl:440 (repeats 2 times)
 [5] #open#514(::Bool, ::Bool, ::Function, ::Base.CmdRedirect, ::Base.DevNull) at ./process.jl:661
 [6] #open at ./none:0 [inlined]
 [7] open(::Base.CmdRedirect, ::String, ::Base.DevNull) at ./process.jl:630
 [8] open at ./process.jl:625 [inlined]
 [9] open(::getfield(Cthulhu, Symbol("##33#34")){String}, ::Base.CmdRedirect, ::String) at ./process.jl:678
 [10] highlight(::Base.TTY, ::String, ::String, ::Cthulhu.CthulhuConfig) at /Users/mbauman/.julia/packages/Cthulhu/PyZJN/src/codeview.jl:34
 [11] cthulhu_llvm(::Base.TTY, ::Core.MethodInstance, ::Bool, ::Bool, ::Core.Compiler.Params, ::Cthulhu.CthulhuConfig) at /Users/mbauman/.julia/packages/Cthulhu/PyZJN/src/codeview.jl:45
 [12] #_descend#37(::Bool, ::Core.Compiler.Params, ::Bool, ::Base.Iterators.Pairs{Union{},Union{},Tuple{},NamedTuple{(),Tuple{}}}, ::Function, ::Core.MethodInstance) at /Users/mbauman/.julia/packages/Cthulhu/PyZJN/src/Cthulhu.jl:186
 [13] #_descend at ./none:0 [inlined]
 [14] #_descend#42(::Core.Compiler.Params, ::Base.Iterators.Pairs{Symbol,Bool,Tuple{Symbol},NamedTuple{(:iswarn,),Tuple{Bool}}}, ::Function, ::Any, ::Any) at /Users/mbauman/.julia/packages/Cthulhu/PyZJN/src/Cthulhu.jl:211
 [15] #_descend at ./none:0 [inlined]
 [16] #_descend_with_error_handling#36(::Base.Iterators.Pairs{Symbol,Bool,Tuple{Symbol},NamedTuple{(:iswarn,),Tuple{Bool}}}, ::Function, ::Function, ::Any) at /Users/mbauman/.julia/packages/Cthulhu/PyZJN/src/Cthulhu.jl:88
 [17] #_descend_with_error_handling at ./none:0 [inlined]
 [18] #descend_code_typed#35 at /Users/mbauman/.julia/packages/Cthulhu/PyZJN/src/Cthulhu.jl:62 [inlined]
 [19] descend_code_typed(::Function, ::Any) at /Users/mbauman/.julia/packages/Cthulhu/PyZJN/src/Cthulhu.jl:62
 [20] top-level scope at none:0

I guess I don't have pygmentize installed? Does it need a BB?

Tests are failing on Julia 1.3 and nightly

The test failure is on line 27 of runtests.jl.

The issue seems to be that in Julia 1.2 and earlier, test has 4 callsites (regardless of the value of optimize). However, in Julia 1.3, test has 5 callsites if optimize is true and 4 callsites if optimize is false.

The extra call is to Random.default_rng.

julia> callsites = find_callsites_by_ftt(test, Tuple{})
4-element Array{Cthulhu.Callsite,1}:
 %12  = invoke dsfmt_fill_array_close1_open2!(,,)::Nothing
 %35  = invoke rand(::Union{Type{Float64}, Type{Int64}},::Int64)
 %70  = invoke mapreduce_impl(,,,,,)::Float64
 %115  = invoke mapreduce_impl(,,,,,)::Int64

julia> callsites = find_callsites_by_ftt(test, Tuple{}; optimize=false)
4-element Array{Cthulhu.Callsite,1}:
 %2  = invoke rand()::Float64
 %3  = invoke >(::Float64,::Float64)::Bool
 %9  = invoke rand(::Union{Type{Float64}, Type{Int64}},::Int64)
 %10  = invoke sum(::Union{Array{Float64,1}, Array{Int64,1}})

On Julia 1.3

5-element Array{Cthulhu.Callsite,1}:
 %4  = invoke default_rng(::Int64)::Random.MersenneTwister
 %15  = invoke dsfmt_fill_array_close1_open2!(,,)::Nothing
 %38  = invoke rand(::Union{Type{Float64}, Type{Int64}},::Int64)
 %73  = invoke mapreduce_impl(,,,,,)::Float64
 %118  = invoke mapreduce_impl(,,,,,)::Int64

julia> callsites = find_callsites_by_ftt(test, Tuple{}; optimize=false)
4-element Array{Cthulhu.Callsite,1}:
 %2  = invoke rand()::Float64
 %3  = invoke >(::Float64,::Float64)::Bool
 %9  = invoke rand(::Union{Type{Float64}, Type{Int64}},::Int64)
 %10  = invoke sum(::Union{Array{Float64,1}, Array{Int64,1}})

Cthulhu hates that TypeofVararg has no field name?

julia> descend(max, (Int64, Int64, Int64, Int64, Vararg{Int64}))

ERROR: type TypeofVararg has no field name
Stacktrace:
  [1] getproperty(x::Core.TypeofVararg, f::Symbol)
    @ Base ./Base.jl:33
  [2] headstring(T::Any)
    @ Cthulhu ~/.julia/packages/Cthulhu/dG9TN/src/callsite.jl:107
  [3] map(f::typeof(Cthulhu.headstring), v::Core.SimpleVector)
    @ Base ./essentials.jl:616
  [4] __show_limited(limiter::Cthulhu.TextWidthLimiter, name::Symbol, tt::Core.SimpleVector, rt::Type)
    @ Cthulhu ~/.julia/packages/Cthulhu/dG9TN/src/callsite.jl:119
  [5] show_callinfo(limiter::Cthulhu.TextWidthLimiter, mici::Cthulhu.MICallInfo)
    @ Cthulhu ~/.julia/packages/Cthulhu/dG9TN/src/callsite.jl:149
  [6] show(io::IOBuffer, c::Cthulhu.Callsite)
    @ Cthulhu ~/.julia/packages/Cthulhu/dG9TN/src/callsite.jl:172
  [7] print(io::IOBuffer, x::Cthulhu.Callsite)
    @ Base ./strings/io.jl:35
  [8] print_to_string(xs::Cthulhu.Callsite)
    @ Base ./strings/io.jl:135
  [9] string
    @ ./strings/io.jl:174 [inlined]
 [10] cthulu_typed(io::Base.TTY, debuginfo_key::Symbol, CI::Core.CodeInfo, rettype::Type, mi::Core.MethodInstance, iswarn::Bool, stable_code::Bool)
    @ Cthulhu ~/.julia/packages/Cthulhu/dG9TN/src/codeview.jl:106
 [11] _descend(mi::Core.MethodInstance; iswarn::Bool, params::Core.Compiler.NativeInterpreter, optimize::Bool, interruptexc::Bool, verbose::Bool, kwargs::Base.Iterators.Pairs{Union{}, Union{}, Tuple{}, NamedTuple{(), Tuple{}}})
    @ Cthulhu ~/.julia/packages/Cthulhu/dG9TN/src/Cthulhu.jl:167
 [12] _descend(F::Any, TT::Any; params::Core.Compiler.NativeInterpreter, kwargs::Base.Iterators.Pairs{Symbol, Bool, Tuple{Symbol}, NamedTuple{(:iswarn,), Tuple{Bool}}})
    @ Cthulhu ~/.julia/packages/Cthulhu/dG9TN/src/Cthulhu.jl:272
 [13] _descend_with_error_handling(::Any, ::Vararg{Any}; kwargs::Any)
    @ Cthulhu ~/.julia/packages/Cthulhu/dG9TN/src/Cthulhu.jl:127
 [14] #descend_code_typed#39
    @ ~/.julia/packages/Cthulhu/dG9TN/src/Cthulhu.jl:99 [inlined]
 [15] descend_code_typed(f::Function, tt::Any)
    @ Cthulhu ~/.julia/packages/Cthulhu/dG9TN/src/Cthulhu.jl:99
 [16] top-level scope
    @ REPL[6]:1

This recording has been archived

This is from the Readme:

image

From asciicinema.org:

This recording has been archived

All unclaimed recordings (the ones not linked to any user account) are automatically archived 7 days after upload.

Source code is missing final line feed without syntax highlighting

In Cthulhu.jl version 1.3.0, when syntax highlighting is off, code is displayed without a trailing line feed when shown with [S]. An example session:

julia> using Cthulhu

julia> f(x) = 3
f (generic function with 1 method)

julia> @descend f(1)

│ ─ %-1  = invoke f(::Int64)::Core.Compiler.Const(3, false)
CodeInfo(
    @ REPL[2]:1 within `f'
1 ─     return 3
)
Select a call to descend into or ↩ to ascend. [q]uit. [b]ookmark.
Toggles: [o]ptimize, [w]arn, [d]ebuginfo, [s]yntax highlight for Source/LLVM/Native.
Show: [S]ource code, [A]ST, [L]LVM IR, [N]ative code
Actions: [E]dit source code, [R]evise and redisplay
Advanced: dump [P]arams cache.

  ↩

After pressing s to turn off syntax highlighting:

[ Info: Turned off syntax highlighter for LLVM and native code.
Select a call to descend into or ↩ to ascend. [q]uit. [b]ookmark.
Toggles: [o]ptimize, [w]arn, [d]ebuginfo, [s]yntax highlight for Source/LLVM/Native.
Show: [S]ource code, [A]ST, [L]LVM IR, [N]ative code
Actions: [E]dit source code, [R]evise and redisplay
Advanced: dump [P]arams cache.

  ↩

After pressing S to display the source code, note that the prompt starts on the same line as the code:

f(x) = 3Select a call to descend into or ↩ to ascend. [q]uit. [b]ookmark.
Toggles: [o]ptimize, [w]arn, [d]ebuginfo, [s]yntax highlight for Source/LLVM/Native.
Show: [S]ource code, [A]ST, [L]LVM IR, [N]ative code
Actions: [E]dit source code, [R]evise and redisplay
Advanced: dump [P]arams cache.

  ↩

The issue also occurs for function definitions spanning multiple lines.

The following patch fixes the issue:

diff --git a/src/codeview.jl b/src/codeview.jl
index 4f27046..6eced43 100644
--- a/src/codeview.jl
+++ b/src/codeview.jl
@@ -4,7 +4,7 @@ highlighter_exists(config::CthulhuConfig) =
 __init__() = CONFIG.enable_highlighter = highlighter_exists(CONFIG)
 
 function highlight(io, x, lexer, config::CthulhuConfig)
-    config.enable_highlighter || return print(io, x)
+    config.enable_highlighter || return println(io, x)
     if !highlighter_exists(config)
         @warn "Highlighter command $(config.highlighter.exec[1]) does not exist."
         return print(io, x)

If it is more convenient for the maintainers, I can submit a pull request as well.

Cleanup IR printing

We have three copies of the IR printer, two in Base (one for IRCode, one for CodeInfo) and one here. Since we're making a hard break, we should consolidate everything and have one uniform printing code in base that has all the hooks that we need so we can delete the copy we keep here.

Crashes while traversing Flux ADs

As mentioned here: FluxML/Flux.jl#828. Cthulhu is crashing while trying to traverse through Flux's ADs (both Tracker and Zygote). Here is an example with Tracker. The same error occurs for Zygote if you use Flux's Zygote branch. The interface for Tracker and Zygote are pretty similar, so the crash is probably triggered somewhere in there.

julia> using Flux, Cthulhu

julia> function test_tracker_big()
              t = ones(Float32, 200, 200, 1, 1)
              f = Conv((1, 1), 1=>1)
              ps = params(f)
              Flux.Tracker.gradient(() -> sum(f(t)), ps)
              end
test_tracker_big (generic function with 1 method)

julia> @descend test_tracker_big()

│ ─ %-1  = invoke test_tracker_big()::Tracker.Grads
CodeInfo(
    @ REPL[1]:2 within `test_tracker_big'
   ┌ @ array.jl:449 within `ones' @ array.jl:452
   │┌ @ boot.jl:414 within `Type'
1 ─││ %1  = $(Expr(:foreigncall, :(:jl_new_array), Array{Float32,4}, svec(Any, Any), :(:ccall), 2, Array{Float32,4}, (200, 200, 1, 1)))::Array{Float32,4}
│  │└
│  │ %2  = invoke Base.fill!(%1::Array{Float32,4}, 1.0f0::Float32)::Array{Float32,4}
│  └
│   @ REPL[1]:3 within `test_tracker_big'
│   %3  = Main.Conv::Core.Compiler.Const(Conv, false)
│  ┌ @ /Users/johnmulcahy/.julia/packages/Flux/qXNjB/src/layers/conv.jl:44 within `Type'
│  │ %4  = Flux.identity::typeof(identity)
│  │ @ /Users/johnmulcahy/.julia/packages/Flux/qXNjB/src/layers/conv.jl:44 within `Type' @ /Users/johnmulcahy/.julia/packages/Flux/qXNjB/src/layers/conv.jl:44
│  │ %5  = invoke Flux.:(#Conv#80)(Flux.glorot_uniform::Function, 1::Int64, 0::Int64, 1::Int64, %3::Type, (1, 1)::Tuple{Int64,Int64}, $(QuoteNode(1 => 1))::Pair{Int64,Int64}, %4::Function)::Conv{_1,_2,typeof(identity),_3,TrackedArray{…,Array{Float32,1}}} where _3 where _2 where _1
│  └
│   @ REPL[1]:4 within `test_tracker_big'
│   %6  = (Main.params)(%5)::Tracker.Params
│   @ REPL[1]:5 within `test_tracker_big'
│   %7  = Main.:(##7#8)::Core.Compiler.Const(getfield(Main, Symbol("##7#8")), false)
│   %8  = (Core.typeof)(%5)::Type{#s70} where #s70<:Conv{_1,_2,typeof(identity),_3,TrackedArray{…,Array{Float32,1}}} where _3 where _2 where _1
│   %9  = (Core.apply_type)(%7, Array{Float32,4}, %8)::Type{getfield(Main, Symbol("##7#8")){Array{Float32,4},_1}} where _1
│   %10 = %new(%9, %2, %5)::getfield(Main, Symbol("##7#8")){Array{Float32,4},_1} where _1
│  ┌ @ /Users/johnmulcahy/.julia/packages/Tracker/RRYy6/src/back.jl:164 within `gradient'
│  │ %11 = invoke Tracker.:(#gradient#24)(false::Bool, Tracker.gradient::Function, %10::Function, %6::Tracker.Params)::Tracker.Grads
│  └
└──       return %11
)
Select a call to descend into or ↩ to ascend. [q]uit.
Toggles: [o]ptimize, [w]arn, [d]ebuginfo, [s]yntax highlight for LLVM/Native.
Show: [L]LVM IR, [N]ative code
Advanced: dump [P]arams cache.

   %2  = invoke fill!(::Array{Float32,4},::Float32)::Array{Float32,4}
   %5  = invoke #Conv#80(::Function,::Int64,::Int64,::Int64,::Type,::Tuple{Int64,Int64},::Pair{Int64,Int64},::Function)
   %6  = invoke params(::Conv{_1,_2,typeof(identity),_3,TrackedArray{…,Array{Float32,1}}} where _3 where _2 where _1)::Tracker.Params
 • %11  = invoke #gradient#24(::Bool,::Function,::Function,::Tracker.Params)::Tracker.Grads
   ↩

│ ─ %-1  = invoke #gradient#24(::Bool,::Function,::Function,::Tracker.Params)::Tracker.Grads
CodeInfo(
    @ /Users/johnmulcahy/.julia/packages/Tracker/RRYy6/src/back.jl:164 within `#gradient#24'
1 ─      goto #3 if not nest
2 ─ %2 = (getfield)(xs, 1)::Tracker.Params
│   %3 = invoke Tracker.gradient_nested(_4::Function, %2::Tracker.Params)::Tracker.Grads
└──      return %3
3 ─ %5 = (getfield)(xs, 1)::Tracker.Params
│   %6 = (Tracker.gradient_)(f, %5)::Tracker.Grads
└──      return %6
)
Select a call to descend into or ↩ to ascend. [q]uit.
Toggles: [o]ptimize, [w]arn, [d]ebuginfo, [s]yntax highlight for LLVM/Native.
Show: [L]LVM IR, [N]ative code
Advanced: dump [P]arams cache.

   %3  = invoke gradient_nested(::Function,::Tracker.Params)::Tracker.Grads
 • %6  = invoke gradient_(::Function,::Tracker.Params)::Tracker.Grads
   ↩
ERROR: BoundsError: attempt to access 22-element Array{Int64,1} at index [0]
Stacktrace:
 [1] getindex(::Array{Int64,1}, ::Int64) at ./array.jl:729
 [2] Core.Compiler.IncrementalCompact(::Core.Compiler.IRCode, ::Bool) at ./compiler/ssair/ir.jl:510
 [3] dce!(::Core.CodeInfo, ::Core.MethodInstance) at /Users/johnmulcahy/.julia/packages/Cthulhu/FuE2s/src/reflection.jl:161
 [4] preprocess_ci!(::Core.CodeInfo, ::Core.MethodInstance, ::Bool) at /Users/johnmulcahy/.julia/packages/Cthulhu/FuE2s/src/reflection.jl:191
 [5] #_descend#38(::Bool, ::Core.Compiler.Params, ::Bool, ::Base.Iterators.Pairs{Symbol,Symbol,Tuple{Symbol},NamedTuple{(:debuginfo,),Tuple{Symbol}}}, ::Function, ::Core.MethodInstance) at /Users/johnmulcahy/.julia/packages/Cthulhu/FuE2s/src/Cthulhu.jl:122
 [6] #_descend#38(::Bool, ::Core.Compiler.Params, ::Bool, ::Base.Iterators.Pairs{Symbol,Symbol,Tuple{Symbol},NamedTuple{(:debuginfo,),Tuple{Symbol}}}, ::Function, ::Core.MethodInstance) at ./none:0
 [7] #_descend at ./none:0 [inlined]
 [8] #_descend#38(::Bool, ::Core.Compiler.Params, ::Bool, ::Base.Iterators.Pairs{Union{},Union{},Tuple{},NamedTuple{(),Tuple{}}}, ::Function, ::Core.MethodInstance) at /Users/johnmulcahy/.julia/packages/Cthulhu/FuE2s/src/Cthulhu.jl:177
 [9] #_descend at ./none:0 [inlined]
 [10] #_descend#43(::Core.Compiler.Params, ::Base.Iterators.Pairs{Symbol,Bool,Tuple{Symbol},NamedTuple{(:iswarn,),Tuple{Bool}}}, ::Function, ::Any, ::Any) at /Users/johnmulcahy/.julia/packages/Cthulhu/FuE2s/src/Cthulhu.jl:211
 [11] #_descend at ./none:0 [inlined]
 [12] #_descend_with_error_handling#37(::Base.Iterators.Pairs{Symbol,Bool,Tuple{Symbol},NamedTuple{(:iswarn,),Tuple{Bool}}}, ::Function, ::Function, ::Any) at /Users/johnmulcahy/.julia/packages/Cthulhu/FuE2s/src/Cthulhu.jl:88
 [13] #_descend_with_error_handling at ./none:0 [inlined]
 [14] #descend_code_typed#35 at /Users/johnmulcahy/.julia/packages/Cthulhu/FuE2s/src/Cthulhu.jl:62 [inlined]
 [15] descend_code_typed(::Function, ::Any) at /Users/johnmulcahy/.julia/packages/Cthulhu/FuE2s/src/Cthulhu.jl:62
 [16] top-level scope at none:0

I Need A Reason!

This is a feature request for reasoning to be given when functions fail to inline. It would probably require a little bit of hacking to base, but something like the vectorization summaries that llvm provides about "This function didn't inline and why" would be really helpful. For example, "this function hit the recursion limiter," "this function has an unassigned type parameter", or "this function contains an error" would all be great things to know about why a function didn't inline, so that I can go and fix it.

ERROR: TypeError: in <:, expected Type, got Vararg{Pair{String, String}}

I as trying to use SnoopCompile on CxxWrap (see JuliaInterop/CxxWrap.jl#278 for my tiny progress so far; any feedback there from experts is of course welcome ;-) ) and run into the following issue. I am reporting it here since it seems to be in Cthulhu.jl code only, but perhaps that wrong, sorry in that case.

Here is what I did and the error:

$ julia-master
               _
   _       _ _(_)_     |  Documentation: https://docs.julialang.org
  (_)     | (_) (_)    |
   _ _   _| |_  __ _   |  Type "?" for help, "]?" for Pkg help.
  | | | | | | |/ _` |  |
  | | |_| | | | (_| |  |  Version 1.7.0-DEV.219 (2021-01-05)
 _/ |\__'_|_|_|\__'_|  |  Commit 399f8ba175* (0 days old master)
|__/                   |

(@v1.7) pkg> status
Status `~/.julia/environments/v1.7/Project.toml`
  [1f15a43c] CxxWrap v0.11.0
  [aa65fe97] SnoopCompile v2.2.1
  [e2b509da] SnoopCompileCore v2.2.1

julia> using SnoopCompileCore

julia> invalidations = @snoopr using CxxWrap;

julia> length(invalidations)
8226

julia> using SnoopCompile

julia> trees = invalidation_trees(invalidations);

julia> method_invalidations = trees[end];

julia> root = method_invalidations.backedges[end]
MethodInstance for String(::AbstractString) at depth 0 with 509 children

julia> ascend(root)
ERROR: TypeError: in <:, expected Type, got Vararg{Pair{String, String}}
Stacktrace:
  [1] stripType(typ::Any)
    @ Cthulhu ~/.julia/packages/Cthulhu/dG9TN/src/backedges.jl:58
  [2] nonconcrete_red(typ::Any)
    @ Cthulhu ~/.julia/packages/Cthulhu/dG9TN/src/backedges.jl:60
  [3] show_tuple_as_call(highlighter::Any, io::IOContext{IOBuffer}, name::Symbol, sig::Type, demangle::Bool, kwargs::Nothing)
    @ Cthulhu ~/.julia/packages/Cthulhu/dG9TN/src/backedges.jl:35
  [4] show_tuple_as_call(highlighter::Any, io::IOContext{IOBuffer}, name::Symbol, sig::Type)
    @ Cthulhu ~/.julia/packages/Cthulhu/dG9TN/src/backedges.jl:4
  [5] callstring(io::IOBuffer, mi::SnoopCompile.InstanceNode)
    @ Cthulhu ~/.julia/packages/Cthulhu/dG9TN/src/backedges.jl:95
  [6] treelist!(parent::FoldingTrees.Node{Cthulhu.Data{Core.MethodInstance}}, io::IOBuffer, mi::SnoopCompile.InstanceNode, indent::String, visited::Base.IdSet{Core.MethodInstance})
    @ Cthulhu ~/.julia/packages/Cthulhu/dG9TN/src/backedges.jl:124
  [7] treelist!(parent::FoldingTrees.Node{Cthulhu.Data{Core.MethodInstance}}, io::IOBuffer, mi::SnoopCompile.InstanceNode, indent::String, visited::Base.IdSet{Core.MethodInstance})
    @ Cthulhu ~/.julia/packages/Cthulhu/dG9TN/src/backedges.jl:126
  [8] treelist(mi::SnoopCompile.InstanceNode)
    @ Cthulhu ~/.julia/packages/Cthulhu/dG9TN/src/backedges.jl:117
  [9] ascend(mi::SnoopCompile.InstanceNode)
    @ Cthulhu ~/.julia/packages/Cthulhu/dG9TN/src/Cthulhu.jl:285
 [10] top-level scope
    @ REPL[11]:1

Losing concrete type information on Julia 1.5?

Doing a simple @descend mean(rand(10)) ends up pessimistically typing a : as its super type (Function):

julia> @descend mean(rand(10))

│ ─ %-1  = invoke mean(::Array{Float64,1})::Float64
CodeInfo(
    @ /Users/mbauman/Julia/master/usr/share/julia/stdlib/v1.5/Statistics/src/Statistics.jl:164 within `mean'
1 ─ %1 = Statistics.:(:)::Core.Compiler.Const(Colon(), false)
│  ┌ @ /Users/mbauman/Julia/master/usr/share/julia/stdlib/v1.5/Statistics/src/Statistics.jl:164 within `#mean#2'
│  │ %2 = invoke Statistics._mean(Statistics.identity::typeof(identity), _2::Array{Float64,1}, %1::Function)::Float64
│  └
└──      return %2
)
Select a call to descend into or  to ascend. [q]uit.
Toggles: [o]ptimize, [w]arn, [d]ebuginfo, [s]yntax highlight for LLVM/Native.
Show: [L]LVM IR, [N]ative code
Advanced: dump [P]arams cache.

 • %2  = invoke _mean(::typeof(identity),::Array{Float64,1},::Function)::Float64
   

Descending into _mean then shows me the code for (_, _, ::Function) instead of (_, _, ::Colon) (which is what's really happening). Not sure what the root cause is here, but thinking it might have to do with the Const?

Happens on Julia 1.5 but not 1.4.

Display of CFGs and DomTrees

Would be fabolous to add some pretty printing for the CFG and dominator trees.

Keno apparently uses this snippet for DomTrees. We could use dot for the CFG like opt does it.

using AbstractTrees
using Core.Compiler: DomTree

AbstractTrees.treekind(d::DomTree) = AbstractTrees.IndexedTree()
AbstractTrees.childindices(d::DomTree, i::Int) = d[i].children
AbstractTrees.childindices(::DomTree, ::DomTree) = (1,)
AbstractTrees.parentlinks(::DomTree) = AbstractTrees.StoredParents()
AbstractTrees.printnode(io::IO, i::Int, d::DomTree) = print(io, i)
Base.getindex(d::DomTree, i) = d.nodes[i]
Base.show(io::IO, d::DomTree) = print_tree(io, 1; roottree = d)

`Error: MethodInstance extraction failed` when passing multiple `Val`s with symbols

using Cthulhu
const X = 3

@generated function foo(::Val{A}, x) where {A}
    t1 = Expr(:tuple, A...);
    Expr(:block, Expr(:meta,:noinline), :($t1.+ x))
end
bar(x) = foo(Val((:X,:X,:X)), x)
bar(2)
@descend bar(2)

I get:

julia> @descend bar(2)
┌ Error: MethodInstance extraction failed
│   ci.sig = Tuple{typeof(foo), QuoteNode, Int64}
│   ci.rt = Tuple{Int64, Int64, Int64}
└ @ Cthulhu ~/.julia/packages/Cthulhu/dG9TN/src/callsite.jl:19

│ ─ %-1  = invoke bar(::Int64)::Tuple{Int64, Int64, Int64}
CodeInfo(
    @ REPL[4]:1 within `bar'
1 ─ %1 = invoke Main.foo($(QuoteNode(Val{(:X, :X, :X)}()))::Val{(:X, :X, :X)}, _2::Int64)::Tuple{Int64, Int64, Int64}
└──      return %1
)
Select a call to descend into or ↩ to ascend. [q]uit. [b]ookmark.
Toggles: [o]ptimize, [w]arn, [v]erbose printing for warntype code, [d]ebuginfo, [s]yntax highlight for Source/LLVM/Native.
Show: [S]ource code, [A]ST, [L]LVM IR, [N]ative code
Actions: [E]dit source code, [R]evise and redisplay
Advanced: dump [P]arams cache.
 • %1  = deoptimized #foo(::QuoteNode,::Int64)::Tuple{Int64, Int64, Int64}

Everything still seems to work, e.g. I can still descnd into foo -> invoke foo -> :

│ ─ %-1  = invoke foo(::Val{(:X, :X, :X)},::Int64)::Tuple{Int64, Int64, Int64}
CodeInfo(
    @ REPL[3]:1 within `foo'
   ┌ @ REPL[3]:1 within `macro expansion'
   │┌ @ broadcast.jl:882 within `materialize'
   ││┌ @ broadcast.jl:1095 within `copy'
   │││┌ @ ntuple.jl:50 within `ntuple'
   ││││┌ @ broadcast.jl:1095 within `#19'
   │││││┌ @ broadcast.jl:621 within `_broadcast_getindex'
   ││││││┌ @ broadcast.jl:648 within `_broadcast_getindex_evalf'
   │││││││┌ @ int.jl:87 within `+'
1 ─││││││││ %1 = Base.add_int(3, x)::Int64
│  │││││└└└
│  │││││┌ @ broadcast.jl:621 within `_broadcast_getindex'
│  ││││││┌ @ broadcast.jl:648 within `_broadcast_getindex_evalf'
│  │││││││┌ @ int.jl:87 within `+'
│  ││││││││ %2 = Base.add_int(3, x)::Int64
│  │││││└└└
│  │││││┌ @ broadcast.jl:621 within `_broadcast_getindex'
│  ││││││┌ @ broadcast.jl:648 within `_broadcast_getindex_evalf'
│  │││││││┌ @ int.jl:87 within `+'
│  ││││││││ %3 = Base.add_int(3, x)::Int64
│  ││││└└└└
│  ││││ %4 = Core.tuple(%1, %2, %3)::Tuple{Int64, Int64, Int64}
│  │└└└
└──│      return %4

)
Select a call to descend into or ↩ to ascend. [q]uit. [b]ookmark.
Toggles: [o]ptimize, [w]arn, [v]erbose printing for warntype code, [d]ebuginfo, [s]yntax highlight for Source/LLVM/Native.
Show: [S]ource code, [A]ST, [L]LVM IR, [N]ative code
Actions: [E]dit source code, [R]evise and redisplay
Advanced: dump [P]arams cache.

But it's not entirely clear what the implications of deoptimized are, for example, the function call and foo itself look normal, and the call vs invoke page doesn't appear to have any code associated with it, so it doesn't look like a dynamic dispatch as far as I can tell, and it benchmarks identically to a version without the Val:

julia> @noinline function nofoo(x)
           (3,3,3) .+ x
       end
nofoo (generic function with 1 method)

julia> baz(x) = nofoo(x)
baz (generic function with 1 method)

julia> @btime baz($(Ref(2))[])
  6.233 ns (0 allocations: 0 bytes)
(5, 5, 5)

julia> @btime bar($(Ref(2))[])
  6.234 ns (0 allocations: 0 bytes)
(5, 5, 5)

Display slot types

When printing code chunks such as

Body::Any
1 ─       Core.NewvarNode(:(#1070))
│   %2  = Zygote.size(x)::Tuple{Int64,Int64}
│   %3  = Zygote.size(x̄)::Any
│   %4  = (%2 == %3)::Any
└──       goto #3 if not %4
2 ─       return x̄

It would be very helpful to be able to display the type of , so as to be able to see the difference between the inferred types of %2 and %3.

Grand Master Valentin says:

@code_warntype can do that, so we could probably add that printing

Cthulu does not use limited printing when displaying types.

Try

@descend display([0x0001//0x0002])

if you have a big REPL history / slow terminal you might have to wait for a while because it tries to display the full REPL history which is stored inside the repl object.

Now compare with

julia> @code_typed display([0x0001//0x0002])

There, the type printing of the long vectors is limited.

Problem precompiling master branch

I wanted to test #60 so I tried installing the master version of Cthulhu and it is having problems precompiling. I have tried Julia 1.2.0 and 1.3.0-rc1 and they both seem to have the issue.

❯ julia --project=.
               _
   _       _ _(_)_     |  Documentation: https://docs.julialang.org
  (_)     | (_) (_)    |
   _ _   _| |_  __ _   |  Type "?" for help, "]?" for Pkg help.
  | | | | | | |/ _` |  |
  | | |_| | | | (_| |  |  Version 1.2.0 (2019-08-20)
 _/ |\__'_|_|_|\__'_|  |  Official https://julialang.org/ release
|__/                   |

(try128) pkg> add Cthulhu#master
   Cloning default registries into `~/.julia`
   Cloning registry from "https://github.com/JuliaRegistries/General.git"
     Added registry `General` to `~/.julia/registries/General`
   Cloning git-repo `https://github.com/JuliaDebug/Cthulhu.jl.git`
  Updating git-repo `https://github.com/JuliaDebug/Cthulhu.jl.git`
 Resolving package versions...
 Installed CodeTracking ── v0.5.8
 Installed Compat ──────── v2.1.0
 Installed Crayons ─────── v4.0.0
 Installed TerminalMenus ─ v0.1.0
  Updating `~/scratch/try128/Project.toml`
 [no changes]
  Updating `~/scratch/try128/Manifest.toml`
 [no changes]

julia> using Cthulhu
[ Info: Precompiling Cthulhu [f68482b8-f384-11e8-15f7-abe071a5a75f]
ERROR: LoadError: LoadError: LoadError: UndefVarError: @init not defined
Stacktrace:
 [1] top-level scope
 [2] include at ./boot.jl:328 [inlined]
 [3] include_relative(::Module, ::String) at ./loading.jl:1094
 [4] include at ./Base.jl:31 [inlined]
 [5] include(::String) at /home/lucas/.julia/packages/Cthulhu/KueME/src/Cthulhu.jl:1
 [6] top-level scope at /home/lucas/.julia/packages/Cthulhu/KueME/src/Cthulhu.jl:38
 [7] include at ./boot.jl:328 [inlined]
 [8] include_relative(::Module, ::String) at ./loading.jl:1094
 [9] include(::Module, ::String) at ./Base.jl:31
 [10] top-level scope at none:2
 [11] eval at ./boot.jl:330 [inlined]
 [12] eval(::Expr) at ./client.jl:432
 [13] top-level scope at ./none:3
in expression starting at /home/lucas/.julia/packages/Cthulhu/KueME/src/codeview.jl:4
in expression starting at /home/lucas/.julia/packages/Cthulhu/KueME/src/codeview.jl:4
in expression starting at /home/lucas/.julia/packages/Cthulhu/KueME/src/Cthulhu.jl:38
ERROR: Failed to precompile Cthulhu [f68482b8-f384-11e8-15f7-abe071a5a75f] to /home/lucas/.julia/compiled/v1.2/Cthulhu/Dqimq.ji.
Stacktrace:
 [1] error(::String) at ./error.jl:33
 [2] compilecache(::Base.PkgId, ::String) at ./loading.jl:1253
 [3] _require(::Base.PkgId) at ./loading.jl:1013
 [4] require(::Base.PkgId) at ./loading.jl:911
 [5] require(::Module, ::Symbol) at ./loading.jl:906

Julia>

I am not sure what is going on. I tried updating all of the dependencies to the master branch (see the following) but that didn't help.

❯ julia --project=.
               _
   _       _ _(_)_     |  Documentation: https://docs.julialang.org
  (_)     | (_) (_)    |
   _ _   _| |_  __ _   |  Type "?" for help, "]?" for Pkg help.
  | | | | | | |/ _` |  |
  | | |_| | | | (_| |  |  Version 1.2.0 (2019-08-20)
 _/ |\__'_|_|_|\__'_|  |  Official https://julialang.org/ release
|__/                   |

(try128) pkg> st -m
    Status `~/scratch/try128/Manifest.toml`
  [da1fd8a2] CodeTracking v0.5.8 #master (https://github.com/timholy/CodeTracking.jl.git)
  [34da2185] Compat v2.1.0 #master (https://github.com/JuliaLang/Compat.jl.git)
  [a8cc5b0e] Crayons v4.0.0 #master (https://github.com/KristofferC/Crayons.jl.git)
  [f68482b8] Cthulhu v0.1.2 #master (https://github.com/JuliaDebug/Cthulhu.jl.git)
  [dc548174] TerminalMenus v0.1.0+ #master (https://github.com/nick-paul/TerminalMenus.jl.git)
  [2a0f44e3] Base64
  [ade2ca70] Dates
  [8bb1440f] DelimitedFiles
  [8ba89e20] Distributed
  [b77e0a4c] InteractiveUtils
  [76f85450] LibGit2
  [8f399da3] Libdl
  [37e2e46d] LinearAlgebra
  [56ddb016] Logging
  [d6f4376e] Markdown
  [a63ad114] Mmap
  [44cfe95a] Pkg
  [de0858da] Printf
  [3fa0cd96] REPL
  [9a3f8284] Random
  [ea8e919c] SHA
  [9e88b42a] Serialization
  [1a1011a3] SharedArrays
  [6462fe0b] Sockets
  [2f01184e] SparseArrays
  [10745b16] Statistics
  [8dfed614] Test
  [cf7118a7] UUIDs
  [4ec0a83e] Unicode

julia> using Cthulhu
[ Info: Precompiling Cthulhu [f68482b8-f384-11e8-15f7-abe071a5a75f]
ERROR: LoadError: LoadError: LoadError: UndefVarError: @init not defined
Stacktrace:
 [1] top-level scope
 [2] include at ./boot.jl:328 [inlined]
 [3] include_relative(::Module, ::String) at ./loading.jl:1094
 [4] include at ./Base.jl:31 [inlined]
 [5] include(::String) at /home/lucas/.julia/packages/Cthulhu/KueME/src/Cthulhu.jl:1
 [6] top-level scope at /home/lucas/.julia/packages/Cthulhu/KueME/src/Cthulhu.jl:38
 [7] include at ./boot.jl:328 [inlined]
 [8] include_relative(::Module, ::String) at ./loading.jl:1094
 [9] include(::Module, ::String) at ./Base.jl:31
 [10] top-level scope at none:2
 [11] eval at ./boot.jl:330 [inlined]
 [12] eval(::Expr) at ./client.jl:432
 [13] top-level scope at ./none:3
in expression starting at /home/lucas/.julia/packages/Cthulhu/KueME/src/codeview.jl:4
in expression starting at /home/lucas/.julia/packages/Cthulhu/KueME/src/codeview.jl:4
in expression starting at /home/lucas/.julia/packages/Cthulhu/KueME/src/Cthulhu.jl:38
ERROR: Failed to precompile Cthulhu [f68482b8-f384-11e8-15f7-abe071a5a75f] to /home/lucas/.julia/compiled/v1.2/Cthulhu/Dqimq.ji.
Stacktrace:
 [1] error(::String) at ./error.jl:33
 [2] compilecache(::Base.PkgId, ::String) at ./loading.jl:1253
 [3] _require(::Base.PkgId) at ./loading.jl:1013
 [4] require(::Base.PkgId) at ./loading.jl:911
 [5] require(::Module, ::Symbol) at ./loading.jl:906

julia>

Is there something else I should try?

cc: @mwarusz

Possible bugfix (?)

I have encountered a bug in Cthulhu in a relatively convoluted code. Changing this line:

if !(t <: Tuple) || t isa Union
to
if !(t <: Tuple) || t isa Union || t isa UnionAll seems to fix it. Does this make sense? I don't think I can find a simple way to reproduce my problem but it complained about UnionAll not having parameters field a few lines below.

Some convenience hotkeys

I regularly find myself wanting some convenience hotkeys for

  1. Switching between optimized and unoptimized views
  2. Somehow saving or printing the current view, so I can come back later after fixing some things.

TagBot trigger issue

This issue is used to trigger TagBot; feel free to unsubscribe.

If you haven't already, you should update your TagBot.yml to include issue comment triggers.
Please see this post on Discourse for instructions and more details.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.