Skip to content

Commit 6b0dc29

Browse files
committed
Export enable, disable, and remove, and rework some types
This makes a clearer distinction between a "breakpoint reference" and the actual information needed to test the breakpoint.
1 parent 4446267 commit 6b0dc29

File tree

4 files changed

+117
-93
lines changed

4 files changed

+117
-93
lines changed

src/JuliaInterpreter.jl

Lines changed: 38 additions & 4 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, Breakpoints, breakpoint
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,6 +41,29 @@ 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+
truecondition(frame) = true
45+
falsecondition(frame) = false
46+
47+
"""
48+
BreakpointState(isactive=true, condition=JuliaInterpreter.truecondition)
49+
50+
`BreakpointState` represents a breakpoint at a particular statement in
51+
a `JuliaFrameCode`. `isactive` indicates whether the breakpoint is currently
52+
[`enable`](@ref)d or [`disable`](@ref)d. `condition` is a function that accepts
53+
a single `JuliaStackFrame`, and `condition(frame)` must return either
54+
`true` or `false`. Execution will stop at a breakpoint only if `isactive`
55+
and `condition(frame)` both evaluate as `true`. The default `condition` always
56+
returns `true`.
57+
58+
To create these objects, see [`breakpoint`](@ref).
59+
"""
60+
struct BreakpointState
61+
isactive::Bool
62+
condition::Function
63+
end
64+
BreakpointState(isactive::Bool) = BreakpointState(isactive, truecondition)
65+
BreakpointState() = BreakpointState(true)
66+
4367
# A type used transiently in renumbering CodeInfo SSAValues (to distinguish a new SSAValue from an old one)
4468
struct NewSSAValue
4569
id::Int
@@ -61,7 +85,7 @@ struct JuliaFrameCode
6185
scope::Union{Method,Module}
6286
code::CodeInfo
6387
methodtables::Vector{Union{Compiled,TypeMapEntry}} # line-by-line method tables for generic-function :call Exprs
64-
breakpoints::Vector{Tuple{Bool,Function}} # (isactive, condition)
88+
breakpoints::Vector{BreakpointState}
6589
used::BitSet
6690
wrapper::Bool
6791
generator::Bool
@@ -81,7 +105,7 @@ function JuliaFrameCode(scope, code::CodeInfo; wrapper=false, generator=false, f
81105
code = copy_codeinfo(code)
82106
methodtables = Vector{Union{Compiled,TypeMapEntry}}(undef, length(code.code))
83107
end
84-
breakpoints = Vector{Tuple{Bool,Function}}(undef, length(code.code))
108+
breakpoints = Vector{BreakpointState}(undef, length(code.code))
85109
used = find_used(code)
86110
return JuliaFrameCode(scope, code, methodtables, breakpoints, used, wrapper, generator, fullpath)
87111
end
@@ -323,6 +347,16 @@ function prepare_framecode(method::Method, argtypes; enter_generated=false)
323347
return framecode, lenv
324348
end
325349

350+
function get_framecode(method)
351+
framecode = get(framedict, method, nothing)
352+
if framecode === nothing
353+
code = get_source(method)
354+
framecode = JuliaFrameCode(method, code; generator=false)
355+
framedict[method] = framecode
356+
end
357+
return framecode
358+
end
359+
326360
function whichtt(tt)
327361
m = ccall(:jl_gf_invoke_lookup, Any, (Any, UInt), tt, typemax(UInt))
328362
m === nothing && return nothing
@@ -1110,7 +1144,7 @@ end
11101144

11111145
include("breakpoints.jl")
11121146
using .Breakpoints
1113-
using .Breakpoints: shouldbreak, Breakpoint
1147+
using .Breakpoints: shouldbreak, BreakpointRef
11141148

11151149
include("precompile.jl")
11161150
_precompile_()

src/breakpoints.jl

Lines changed: 48 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,61 @@
11
module Breakpoints
22

33
using ..JuliaInterpreter
4-
using JuliaInterpreter: JuliaFrameCode, JuliaStackFrame,
5-
prepare_framecode, framedict, get_source, sparam_syms,
6-
linenumber
4+
using JuliaInterpreter: JuliaFrameCode, JuliaStackFrame, BreakpointState,
5+
truecondition, falsecondition, prepare_framecode, get_framecode,
6+
sparam_syms, linenumber
7+
using Base.Meta: isexpr
8+
using InteractiveUtils
79

8-
export breakpoint
10+
export @breakpoint, breakpoint, enable, disable, remove
911

1012
# A type that is unique to this package for which there are no valid operations
1113
struct Unassigned end
1214

1315
"""
14-
Breakpoint(framecode, stmtidx)
16+
BreakpointRef(framecode, stmtidx)
1517
1618
A reference to a breakpoint at a particular statement index `stmtidx` in `framecode`.
1719
"""
18-
struct Breakpoint
20+
struct BreakpointRef
1921
framecode::JuliaFrameCode
2022
stmtidx::Int
2123
end
2224

23-
function Base.show(io::IO, bp::Breakpoint)
25+
function Base.show(io::IO, bp::BreakpointRef)
2426
lineno = linenumber(bp.framecode, bp.stmtidx)
2527
print(io, "breakpoint(", bp.framecode.scope, ", ", lineno, ')')
2628
end
2729

28-
const _breakpoints = Set{Breakpoint}()
30+
const _breakpoints = BreakpointRef[]
31+
breakpoints() = copy(_breakpoints)
32+
33+
Base.getindex(bp::BreakpointRef) = bp.framecode.breakpoints[bp.stmtidx]
34+
function Base.setindex!(bp::BreakpointRef, isactive::Bool)
35+
bp.framecode.breakpoints[bp.stmtidx] = BreakpointState(isactive, bp[].condition)
36+
end
37+
function toggle!(bp::BreakpointRef)
38+
state = bp[]
39+
bp.framecode.breakpoints[bp.stmtidx] = BreakpointState(!state.isactive, state.condition)
40+
end
41+
42+
function add_breakpoint(framecode, stmtidx)
43+
bp = BreakpointRef(framecode, stmtidx)
44+
# Since there can be only one BreakpointState for a given framecode/stmtidx,
45+
# check whether _breakpoints is already storing a reference to that location
46+
idx = findfirst(isequal(bp), _breakpoints)
47+
if idx === nothing
48+
push!(_breakpoints, bp)
49+
end
50+
return bp
51+
end
2952

3053
function shouldbreak(frame, pc=frame.pc[])
3154
idx = convert(Int, pc)
3255
isassigned(frame.code.breakpoints, idx) || return false
3356
bp = frame.code.breakpoints[idx]
34-
bp[1] || return false
35-
slotfunction = bp[2]
36-
return slotfunction(frame)::Bool
57+
bp.isactive || return false
58+
return bp.condition(frame)::Bool
3759
end
3860

3961
function prepare_slotfunction(framecode::JuliaFrameCode, body::Union{Symbol,Expr})
@@ -65,63 +87,35 @@ function prepare_slotfunction(framecode::JuliaFrameCode, body::Union{Symbol,Expr
6587
return Expr(:function, Expr(:call, funcname, framename), Expr(:block, assignments..., body))
6688
end
6789

68-
truecondition(frame) = true
69-
falsecondition(frame) = false
7090

7191
## The fundamental implementations of breakpoint-setting
7292
function breakpoint!(framecode::JuliaFrameCode, pc, condition::Union{Bool,Expr}=true)
7393
stmtidx = convert(Int, pc)
7494
if isa(condition, Bool)
75-
framecode.breakpoints[stmtidx] = condition ? (true, truecondition) : (false, falsecondition)
95+
framecode.breakpoints[stmtidx] = BreakpointState(condition)
7696
else
7797
fex = prepare_slotfunction(framecode, condition)
78-
framecode.breakpoints[stmtidx] = (true, eval(fex))
98+
framecode.breakpoints[stmtidx] = BreakpointState(true, eval(fex))
7999
end
80-
bp = Breakpoint(framecode, stmtidx)
81-
push!(_breakpoints, bp)
82-
return bp
100+
return add_breakpoint(framecode, stmtidx)
83101
end
84102
breakpoint!(frame::JuliaStackFrame, pc=frame.pc[], condition::Union{Bool,Expr}=true) =
85103
breakpoint!(frame.code, pc, condition)
86104

87-
function set_breakpoint_active!(framecode::JuliaFrameCode, active, pc)
88-
stmtidx = convert(Int, pc)
89-
_, condition = framecode.breakpoints[stmtidx]
90-
framecode.breakpoints[stmtidx] = (active, condition)
91-
active
92-
end
93-
set_breakpoint_active!(frame::JuliaStackFrame, active, pc) =
94-
set_breakpoint_active!(frame.code, active, pc)
95-
96-
function get_breakpoint_active(framecode::JuliaFrameCode, pc)
97-
stmtidx = convert(Int, pc)
98-
return framecode.breakpoints[stmtidx][1]
99-
end
100-
get_breakpoint_active(frame::JuliaStackFrame, pc) =
101-
get_breakpoint_active(frame.code, pc)
102-
103-
function toggle_breakpoint_active!(framecode::JuliaFrameCode, pc)
104-
stmtidx = convert(Int, pc)
105-
active, condition = framecode.breakpoints[stmtidx]
106-
framecode.breakpoints[stmtidx] = (!active, condition)
107-
!active
108-
end
109-
toggle_breakpoint_active!(frame::JuliaStackFrame, active, pc) =
110-
toggle_breakpoint_active!(frame.code, active, pc)
111-
112-
enable(bp::Breakpoint) = set_breakpoint_active!(bp.framecode, true, bp.stmtidx)
113-
disable(bp::Breakpoint) = set_breakpoint_active!(bp.framecode, false, bp.stmtidx)
114-
function remove(bp::Breakpoint)
115-
bp.framecode.breakpoints[bp.stmtidx] = (false, falsecondition)
116-
delete!(_breakpoints, bp)
105+
enable(bp::BreakpointRef) = bp[] = true
106+
disable(bp::BreakpointRef) = bp[] = false
107+
function remove(bp::BreakpointRef)
108+
idx = findfirst(isequal(bp), _breakpoints)
109+
deleteat!(_breakpoints, idx)
110+
bp.framecode.breakpoints[bp.stmtidx] = BreakpointState(false, falsecondition)
117111
return nothing
118112
end
119113

120114
enable() = for bp in _breakpoints enable(bp) end
121115
disable() = for bp in _breakpoints disable(bp) end
122116
function remove()
123117
for bp in _breakpoints
124-
bp.framecode.breakpoints[bp.stmtidx] = (false, falsecondition)
118+
bp.framecode.breakpoints[bp.stmtidx] = BreakpointState(false, falsecondition)
125119
end
126120
empty!(_breakpoints)
127121
return nothing
@@ -155,18 +149,10 @@ end
155149
breakpoint(method::Method)
156150
breakpoint(method::Method, condition::Expr)
157151
158-
Add a breakpoint upon entry to `method`. The first will break unconditionally, the second
159-
only if `condition` evaluates to `true`. `condition` should be written in terms of the
160-
arguments and local variables of `method`.
152+
Add a breakpoint upon entry to `method`.
161153
"""
162154
function breakpoint(method::Method, condition::Union{Bool,Expr}=true)
163-
# FIXME: this duplicates stuff in prepare_call
164-
framecode = get(framedict, method, nothing)
165-
if framecode === nothing
166-
code = get_source(method)
167-
framecode = JuliaFrameCode(method, code; generator=false)
168-
framedict[method] = framecode
169-
end
155+
framecode = get_framecode(method)
170156
breakpoint!(framecode, 1, condition)
171157
end
172158

@@ -177,10 +163,11 @@ end
177163
Break-on-entry to all methods of `f`.
178164
"""
179165
function breakpoint(f, condition::Union{Bool,Expr}=true)
166+
bps = BreakpointRef[]
180167
for method in methods(f)
181-
breakpoint(method, condition)
168+
push!(bps, breakpoint(method, condition))
182169
end
183-
nothing
170+
return bps
184171
end
185172

186173
end

src/interpret.jl

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,7 @@ function evaluate_call!(stack, frame::JuliaStackFrame, call_expr::Expr, pc; exec
208208
newframe = build_frame(framecode, fargs, lenv)
209209
if shouldbreak(newframe)
210210
push!(stack, newframe)
211-
return Breakpoint(newframe.code, newframe.pc[])
211+
return BreakpointRef(newframe.code, newframe.pc[])
212212
end
213213
ret = exec!(stack, newframe)
214214
pop!(stack)
@@ -362,7 +362,7 @@ function _step_expr!(stack, frame, @nospecialize(node), pc::JuliaProgramCounter,
362362
else
363363
rhs = istoplevel ? @lookup(moduleof(frame), frame, rhs) : @lookup(frame, rhs)
364364
end
365-
isa(rhs, Breakpoint) && return rhs
365+
isa(rhs, BreakpointRef) && return rhs
366366
do_assignment!(frame, lhs, rhs)
367367
elseif node.head == :gotoifnot
368368
arg = @lookup(frame, node.args[1])
@@ -456,7 +456,7 @@ function _step_expr!(stack, frame, @nospecialize(node), pc::JuliaProgramCounter,
456456
catch err
457457
return handle_err(frame, err)
458458
end
459-
@isdefined(rhs) && isa(rhs, Breakpoint) && return rhs
459+
@isdefined(rhs) && isa(rhs, BreakpointRef) && return rhs
460460
if isassign(frame, pc)
461461
if !@isdefined(rhs)
462462
@show frame node pc
@@ -480,7 +480,7 @@ by normal dispatch, whereas a vector of `JuliaStackFrame`s will use recursive in
480480
"""
481481
function step_expr!(stack, frame, istoplevel::Bool=false)
482482
pc = _step_expr!(stack, frame, frame.pc[], istoplevel)
483-
(pc === nothing || isa(pc, Breakpoint)) && return pc
483+
(pc === nothing || isa(pc, BreakpointRef)) && return pc
484484
frame.pc[] = pc
485485
end
486486

@@ -515,16 +515,16 @@ function finish!(stack, frame, pc::JuliaProgramCounter=frame.pc[], istoplevel::B
515515
local new_pc
516516
while true
517517
new_pc = _step_expr!(stack, frame, pc, istoplevel)
518-
(new_pc == nothing || isa(new_pc, Breakpoint)) && break
518+
(new_pc == nothing || isa(new_pc, BreakpointRef)) && break
519519
pc = new_pc
520520
if shouldbreak(frame, pc)
521521
push!(stack, frame)
522-
new_pc = Breakpoint(frame.code, pc)
522+
new_pc = BreakpointRef(frame.code, pc)
523523
break
524524
end
525525
end
526526
frame.pc[] = pc
527-
return isa(new_pc, Breakpoint) ? new_pc : pc
527+
return isa(new_pc, BreakpointRef) ? new_pc : pc
528528
end
529529
finish!(stack, frame, istoplevel::Bool) = finish!(stack, frame, frame.pc[], istoplevel)
530530

@@ -543,7 +543,7 @@ Optionally supply the starting `pc`, if you don't want to start at the current l
543543
"""
544544
function finish_and_return!(stack, frame, pc::JuliaProgramCounter=frame.pc[], istoplevel::Bool=false)
545545
pc = finish!(stack, frame, pc, istoplevel)
546-
isa(pc, Breakpoint) && return pc
546+
isa(pc, BreakpointRef) && return pc
547547
return get_return(frame, pc)
548548
end
549549
finish_and_return!(stack, frame, istoplevel::Bool) = finish_and_return!(stack, frame, frame.pc[], istoplevel)
@@ -572,7 +572,7 @@ function finish_stack!(stack)
572572
while !isempty(stack)
573573
frame = pop!(stack)
574574
ret = finish_and_return!(stack, frame)
575-
isa(ret, Breakpoint) && return ret
575+
isa(ret, BreakpointRef) && return ret
576576
isempty(stack) && return ret
577577
parentframe = stack[end]
578578
pc = parentframe.pc[]
@@ -582,7 +582,7 @@ function finish_stack!(stack)
582582
end
583583
pc += 1
584584
parentframe.pc[] = pc
585-
shouldbreak(parentframe) && return Breakpoint(parentframe.code, pc)
585+
shouldbreak(parentframe) && return BreakpointRef(parentframe.code, pc)
586586
end
587587
error("stack is empty")
588588
end
@@ -710,7 +710,7 @@ function next_line!(stack, frame, dbstack = nothing)
710710
# If this is a goto node, step it and reevaluate
711711
if isgotonode(expr)
712712
pc = _step_expr!(stack, frame, pc)
713-
(pc == nothing || isa(pc, Breakpoint)) && return pc
713+
(pc == nothing || isa(pc, BreakpointRef)) && return pc
714714
elseif dbstack !== nothing && iswrappercall(expr)
715715
# With splatting it can happen that we do something like ssa = tuple(#self#), _apply(ssa), which
716716
# confuses the logic here, just step into the first call that's not a builtin
@@ -727,15 +727,15 @@ function next_line!(stack, frame, dbstack = nothing)
727727
break
728728
else
729729
pc = _step_expr!(stack, frame, pc)
730-
(pc == nothing || isa(pc, Breakpoint)) && return pc
730+
(pc == nothing || isa(pc, BreakpointRef)) && return pc
731731
end
732732
end
733733
else
734734
pc = _step_expr!(stack, frame, pc)
735-
(pc == nothing || isa(pc, Breakpoint)) && return pc
735+
(pc == nothing || isa(pc, BreakpointRef)) && return pc
736736
end
737737
frame.pc[] = pc
738-
shouldbreak(frame, pc) && return Breakpoint(frame.code, pc)
738+
shouldbreak(frame, pc) && return BreakpointRef(frame.code, pc)
739739
end
740740
maybe_next_call!(stack, frame, pc)
741741
end

0 commit comments

Comments
 (0)