Skip to content

Commit 0a2428e

Browse files
authored
Merge pull request #81 from JuliaDebug/teh/breakpoints
Add support for breakpoints (!)
2 parents c86c059 + 6322fb8 commit 0a2428e

File tree

6 files changed

+478
-59
lines changed

6 files changed

+478
-59
lines changed

src/JuliaInterpreter.jl

Lines changed: 113 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ using UUIDs
1111
using Random.DSFMT
1212
using InteractiveUtils
1313

14-
export @enter, @make_stack, @interpret, Compiled, JuliaStackFrame
14+
export @enter, @make_stack, @interpret, Compiled, JuliaStackFrame,
15+
Breakpoints, breakpoint, @breakpoint, breakpoints, enable, disable, remove
1516

1617
module CompiledCalls
1718
# This module is for handling intrinsics that must be compiled (llvmcall)
@@ -40,14 +41,39 @@ isless(x::JuliaProgramCounter, y::Integer) = isless(x.next_stmt, y)
4041

4142
Base.show(io::IO, pc::JuliaProgramCounter) = print(io, "JuliaProgramCounter(", pc.next_stmt, ')')
4243

44+
# Breakpoint support
45+
truecondition(frame) = true
46+
falsecondition(frame) = false
47+
const break_on_error = Ref(false)
48+
49+
"""
50+
BreakpointState(isactive=true, condition=JuliaInterpreter.truecondition)
51+
52+
`BreakpointState` represents a breakpoint at a particular statement in
53+
a `JuliaFrameCode`. `isactive` indicates whether the breakpoint is currently
54+
[`enable`](@ref)d or [`disable`](@ref)d. `condition` is a function that accepts
55+
a single `JuliaStackFrame`, and `condition(frame)` must return either
56+
`true` or `false`. Execution will stop at a breakpoint only if `isactive`
57+
and `condition(frame)` both evaluate as `true`. The default `condition` always
58+
returns `true`.
59+
60+
To create these objects, see [`breakpoint`](@ref).
61+
"""
62+
struct BreakpointState
63+
isactive::Bool
64+
condition::Function
65+
end
66+
BreakpointState(isactive::Bool) = BreakpointState(isactive, truecondition)
67+
BreakpointState() = BreakpointState(true)
68+
4369
# A type used transiently in renumbering CodeInfo SSAValues (to distinguish a new SSAValue from an old one)
4470
struct NewSSAValue
4571
id::Int
4672
end
4773

4874
"""
4975
`JuliaFrameCode` holds static information about a method or toplevel code.
50-
One `JuliaFrameCode` can be shared by many `JuliaFrameState` calling frames.
76+
One `JuliaFrameCode` can be shared by many `JuliaStackFrame` calling frames.
5177
5278
Important fields:
5379
- `scope`: the `Method` or `Module` in which this frame is to be evaluated
@@ -61,6 +87,7 @@ struct JuliaFrameCode
6187
scope::Union{Method,Module}
6288
code::CodeInfo
6389
methodtables::Vector{Union{Compiled,TypeMapEntry}} # line-by-line method tables for generic-function :call Exprs
90+
breakpoints::Vector{BreakpointState}
6491
used::BitSet
6592
wrapper::Bool
6693
generator::Bool
@@ -69,7 +96,7 @@ struct JuliaFrameCode
6996
end
7097

7198
function JuliaFrameCode(frame::JuliaFrameCode; wrapper = frame.wrapper, generator=frame.generator, fullpath=frame.fullpath)
72-
JuliaFrameCode(frame.scope, frame.code, frame.methodtables, frame.used,
99+
JuliaFrameCode(frame.scope, frame.code, frame.methodtables, frame.breakpoints, frame.used,
73100
wrapper, generator, fullpath)
74101
end
75102

@@ -80,8 +107,9 @@ function JuliaFrameCode(scope, code::CodeInfo; wrapper=false, generator=false, f
80107
code = copy_codeinfo(code)
81108
methodtables = Vector{Union{Compiled,TypeMapEntry}}(undef, length(code.code))
82109
end
110+
breakpoints = Vector{BreakpointState}(undef, length(code.code))
83111
used = find_used(code)
84-
return JuliaFrameCode(scope, code, methodtables, used, wrapper, generator, fullpath)
112+
return JuliaFrameCode(scope, code, methodtables, breakpoints, used, wrapper, generator, fullpath)
85113
end
86114

87115
"""
@@ -211,6 +239,16 @@ function is_generated(meth)
211239
isdefined(meth, :generator)
212240
end
213241

242+
function sparam_syms(meth::Method)
243+
s = Symbol[]
244+
sig = meth.sig
245+
while sig isa UnionAll
246+
push!(s, Symbol(sig.var.name))
247+
sig = sig.body
248+
end
249+
return s
250+
end
251+
214252
function namedtuple(kwargs)
215253
names, types, vals = Symbol[], [], []
216254
for pr in kwargs
@@ -269,6 +307,58 @@ function prepare_args(@nospecialize(f), allargs, kwargs)
269307
return f, allargs
270308
end
271309

310+
function prepare_framecode(method::Method, argtypes; enter_generated=false)
311+
sig = method.sig
312+
isa(method, TypeMapEntry) && (method = method.func)
313+
if method.module == Core.Compiler || method.module == Base.Threads || method compiled_methods
314+
return Compiled()
315+
end
316+
# Get static parameters
317+
(ti, lenv::SimpleVector) = ccall(:jl_type_intersection_with_env, Any, (Any, Any),
318+
argtypes, sig)::SimpleVector
319+
enter_generated &= is_generated(method)
320+
if is_generated(method) && !enter_generated
321+
framecode = get(genframedict, (method, argtypes), nothing)
322+
else
323+
framecode = get(framedict, method, nothing)
324+
end
325+
if framecode === nothing
326+
if is_generated(method) && !enter_generated
327+
# If we're stepping into a staged function, we need to use
328+
# the specialization, rather than stepping through the
329+
# unspecialized method.
330+
code = Core.Compiler.get_staged(Core.Compiler.code_for_method(method, argtypes, lenv, typemax(UInt), false))
331+
code === nothing && return nothing
332+
generator = false
333+
else
334+
if is_generated(method)
335+
code = get_source(method.generator)
336+
generator = true
337+
else
338+
code = get_source(method)
339+
generator = false
340+
end
341+
end
342+
framecode = JuliaFrameCode(method, code; generator=generator)
343+
if is_generated(method) && !enter_generated
344+
genframedict[(method, argtypes)] = framecode
345+
else
346+
framedict[method] = framecode
347+
end
348+
end
349+
return framecode, lenv
350+
end
351+
352+
function get_framecode(method)
353+
framecode = get(framedict, method, nothing)
354+
if framecode === nothing
355+
code = get_source(method)
356+
framecode = JuliaFrameCode(method, code; generator=false)
357+
framedict[method] = framecode
358+
end
359+
return framecode
360+
end
361+
272362
function whichtt(tt)
273363
m = ccall(:jl_gf_invoke_lookup, Any, (Any, UInt), tt, typemax(UInt))
274364
m === nothing && return nothing
@@ -328,47 +418,17 @@ function prepare_call(@nospecialize(f), allargs; enter_generated = false)
328418
f(allargs[2:end]...)
329419
end
330420
args = allargs
331-
sig = method.sig
332-
isa(method, TypeMapEntry) && (method = method.func)
333-
if method.module == Core.Compiler || method.module == Base.Threads || method compiled_methods
334-
return Compiled()
335-
end
336-
# Get static parameters
337-
(ti, lenv::SimpleVector) = ccall(:jl_type_intersection_with_env, Any, (Any, Any),
338-
argtypes, sig)::SimpleVector
339-
enter_generated &= is_generated(method)
340-
if is_generated(method) && !enter_generated
341-
framecode = get(genframedict, (method, argtypes), nothing)
342-
else
343-
framecode = get(framedict, method, nothing)
421+
ret = prepare_framecode(method, argtypes; enter_generated=enter_generated)
422+
# Exceptional returns
423+
if ret === nothing
424+
# The generator threw an error. Let's generate the same error by calling it.
425+
f(allargs[2:end]...)
344426
end
345-
if framecode === nothing
346-
if is_generated(method) && !enter_generated
347-
# If we're stepping into a staged function, we need to use
348-
# the specialization, rather than stepping through the
349-
# unspecialized method.
350-
code = Core.Compiler.get_staged(Core.Compiler.code_for_method(method, argtypes, lenv, typemax(UInt), false))
351-
generator = false
352-
if code === nothing
353-
# The generator threw an error. Let's generate the same error by calling it.
354-
f(allargs[2:end]...)
355-
end
356-
else
357-
if is_generated(method)
358-
args = Any[_Typeof(a) for a in args]
359-
code = get_source(method.generator)
360-
generator = true
361-
else
362-
code = get_source(method)
363-
generator = false
364-
end
365-
end
366-
framecode = JuliaFrameCode(method, code; generator=generator)
367-
if is_generated(method) && !enter_generated
368-
genframedict[(method, argtypes)] = framecode
369-
else
370-
framedict[method] = framecode
371-
end
427+
isa(ret, Compiled) && return ret
428+
# Typical return
429+
framecode, lenv = ret
430+
if is_generated(method) && enter_generated
431+
args = Any[_Typeof(a) for a in args]
372432
end
373433
return framecode, args, lenv, argtypes
374434
end
@@ -795,6 +855,7 @@ function prepare_locals(framecode, argvals::Vector{Any})
795855
pc = Ref(JuliaProgramCounter(1))
796856
end
797857
for i = 1:meth.nargs
858+
last_reference[framecode.code.slotnames[i]] = i
798859
if meth.isva && i == meth.nargs
799860
locals[i] = nargs < i ? Some{Any}(()) : (let i=i; Some{Any}(ntuple(k->argvals[i+k-1], nargs-i+1)); end)
800861
break
@@ -1060,8 +1121,10 @@ macro interpret(arg)
10601121
if frame === nothing
10611122
return eval(Expr(:call, map(QuoteNode, theargs)...))
10621123
end
1063-
empty!(framedict) # start fresh each time; kind of like bumping the world age at the REPL prompt
1064-
empty!(genframedict)
1124+
if shouldbreak(frame, 1)
1125+
push!(stack, frame)
1126+
return stack, BreakpointRef(frame.code, 1)
1127+
end
10651128
finish_and_return!(stack, frame)
10661129
end
10671130
end
@@ -1085,6 +1148,10 @@ function __init__()
10851148
set_compiled_methods()
10861149
end
10871150

1151+
include("breakpoints.jl")
1152+
using .Breakpoints
1153+
using .Breakpoints: shouldbreak, BreakpointRef
1154+
10881155
include("precompile.jl")
10891156
_precompile_()
10901157

0 commit comments

Comments
 (0)