Debugger in Juno for Julia 1.0

juno
debugging

#1

What is the state of the debugger in Juno for Julia >=1.0? The documentation for Juno 0.6 mentions a prototype debugger based on ASTInterpreter2. However, the “latest” documentation for Juno does not mention debugging. What happened?


#2

You can use the ASTInterpreter2 stepping debugger. Despite the name, it works fairly well. That’s all there is, I’m afraid. There are also many discourse threads about people wanting a debugger.


#3

I’ll publish a new Juno release with ASTInterpreter2-based debugging ASAP, but you’ll still need to

pkg> add ASTInterpreter2#master
pkg> add DebuggerFrameworkg#master

for now.


#4

Quick update on this – latest Juno release works with ASTInterpreter2#master and DebuggerFramework#master:

Feel free to try it for a bit, but this is very likely to break very soon due to big changes coming to ASTInterpreter.


State of the Debugger
#5

is very likely to break very soon due to big changes coming to ASTInterpreter.

Yes, sorry about that. Reference is https://github.com/JuliaDebug/ASTInterpreter2.jl/pull/37.


#6

Eh, it’s a two-line diff for this to work with your PR, which is super awesome in and of itself (also, wohoo, documentation :heart:) .


#7

Yeah, but I think it’s going to get worse before it gets better. As you know I think we need to break up packages, but that’s still in the future, so there’s going to be a transition period that is almost guaranteed to be ugly.


#8

I wonder about your post in that pull:

Also something that may help explain a lot: when you’re dealing with Expr s then basically nothing is inferrable. If you want decent performance, you have to change your style of writing Julia code: instead of

foo(a::Typ1) = 1
foo(a::Typ2) = 2
foo(x)

you want only one method for foo (so that Julia’s compiler knows which foo you mean without needing to know anything about types) which is written something like

function foo(x) isa(x, Typ1) && return 1 isa(x, Typ2) && return 2 error("unhandled type ", typeof(x)) end

You get good performance from the latter even if you don’t know the type of x going in.

If you want the best of both worlds, you can combine these and write it like this:

_foo(x::Typ1) = 1 _foo(x::Typ2) = 2 function foo(x) # make sure there is only one foo method isa(x, Typ1) && return _foo(x) # here it knows to dispatch to the Typ1 implementation isa(x, Typ2) && return _foo(x) # here it knows to dispatch to the Typ2 implementation return _foo(x) # fallback implementation (requires dynamic dispatch if typeof(x) is unknown) end

This version will be fast if x is Typ1 or Typ2 , and slow otherwise (assuming the existing of more methods of _foo that can handle other types.

Could you explain what is going on here?
It seems something like manual dispatching. Why is any better than what Julia would do automatically?


#9

It’s because normal dispatch has to “spin up” the whole generic machinery for subtyping, and that’s expensive. But for concrete types there’s a trivial implementation. You can see that with the following demo:

julia> x = Ref{Any}(3)   # create an Int that's "hidden" from inference
Base.RefValue{Any}(3)

julia> isintref(x::Ref) = isa(x[], Int)
isintref (generic function with 1 method)

julia> isintegerref(x::Ref) = isa(x[], Integer)
isintegerref (generic function with 1 method)

Now compare @code_llvm isintref(x) to @code_llvm isintegerref(x); you’ll see that isintref has a line

  %11 = icmp eq %jl_value_t addrspace(10)* %10, addrspacecast (%jl_value_t* inttoptr (i64 140355592194032 to %jl_value_t*) to %jl_value_t addrspace(10)*)

which is just a pointer-comparison, whereas isintegerref has a line

  %11 = call i32 @jl_subtype(%jl_value_t addrspace(10)* nonnull %10, %jl_value_t addrspace(10)* addrspacecast (%jl_value_t* inttoptr (i64 140355676482752 to %jl_value_t*) to %jl_value_t addrspace(10)*))

jl_subtype invokes Julia’s type-analysis machinery (which is awesome, but not trivial).

Now, could normal dispatch do this? Yes—in principle it could check to see if every method of a function is defined solely in terms of concrete types, and if so do dispatch via this simple short-circuit. This is somewhere between an optimization not-yet-implemented (modulo Union-splitting, which is what this really is) and an optimization we don’t want (I worry it would encourage people to specify concrete types on all their function arguments and thus break the lovely polymorphism of most well-written Julia code). The “switch statement” alternative allows people who know what they are doing to achieve the same thing without such downsides.