Skip to content

Commit 3e37084

Browse files
committed
Do not live track variables, instead retroactively workout what is define when a breakpoint is hit
1 parent 40e13de commit 3e37084

File tree

4 files changed

+149
-153
lines changed

4 files changed

+149
-153
lines changed

src/MagneticReadHead.jl

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@ module MagneticReadHead
33
using Base: invokelatest
44
using Cassette
55
using MacroTools
6-
using OrderedCollections
76
using InteractiveUtils
87
using CodeTracking
98
# We don't use Revise, but if it isn't loaded CodeTracking has issues
109
using Revise: Revise
10+
using OrderedCollections
1111

1212
export @iron_debug
1313

@@ -38,7 +38,6 @@ macro iron_debug(body)
3838
# Disable any stepping left-over
3939
ctx.metadata.stepping_mode = StepContinue()
4040
end
41-
4241
end
4342
end
4443

src/break_action.jl

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,11 @@ end
2929
function breadcrumbs(io, file::AbstractString, line_num; nbefore=2, nafter=2)
3030
@assert(nbefore >= 0)
3131
@assert(nafter >= 0)
32-
32+
3333
all_lines = loc_for_file(file)
3434
first_line_num = max(1, line_num - nbefore)
3535
last_line_num = min(length(all_lines), line_num + nafter)
36-
36+
3737
for ln in first_line_num:last_line_num
3838
line = all_lines[ln]
3939
if ln == line_num
@@ -54,34 +54,36 @@ end
5454
# this function exists only for mocking so we can test it.
5555
breakpoint_hit(meth, statement_ind) = nothing
5656

57-
function iron_repl(metadata::HandEvalMeta, meth, statement_ind)
57+
function iron_repl(metadata::HandEvalMeta, meth, statement_ind, variables)
5858
breakpoint_hit(meth, statement_ind)
5959
breadcrumbs(meth, statement_ind)
60-
60+
6161
printstyled("Vars: "; color=:light_yellow)
62-
println(join(keys(metadata.variables), ", "))
62+
println(join(keys(variables), ", "))
6363
print_commands()
64-
65-
run_repl(metadata.variables, metadata.eval_module)
66-
end
6764

65+
run_repl(variables, metadata.eval_module)
66+
end
6867

6968
"""
70-
break_action(metadata, meth, statement_ind)
69+
should_breakon
70+
Determines if we should actualy break at a potential breakpoint
71+
"""
72+
function should_break(ctx, meth, statement_ind)
73+
return ctx.metadata.stepping_mode isa StepNext ||
74+
should_breakon(ctx.metadata.breakpoint_rules, meth, statement_ind)
75+
end
76+
7177

72-
This determines what we should do when we hit a potential point to break at.
73-
We check if we should actually break here,
74-
and if so open up a REPL.
75-
if not, then we continue.
7678
"""
77-
function break_action(metadata, meth, statement_ind)
78-
if !(metadata.stepping_mode isa StepNext
79-
|| should_breakon(metadata.breakpoint_rules, meth, statement_ind)
80-
)
81-
# Only break on StepNext and actual breakpoints
82-
return
83-
end
79+
break_action
8480
85-
code_word = iron_repl(metadata, meth, statement_ind)
81+
What to do when a breakpoint is hit
82+
"""
83+
function break_action(ctx, meth, statement_ind, slotnames, slotvals)
84+
metadata = ctx.metadata
85+
# TODO we probably need to drop the first few slots as they will contain various metadata
86+
variables = LittleDict(slotnames, slotvals)
87+
code_word = iron_repl(metadata, meth, statement_ind, variables)
8688
actions[code_word].act(metadata)
8789
end

src/core_control.jl

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,18 +23,16 @@ parent_stepping_mode(::StepOut) = StepNext() # This is what they want
2323

2424

2525
mutable struct HandEvalMeta
26-
variables::LittleDict{Symbol, Any}
2726
eval_module::Module
2827
stepping_mode::SteppingMode
2928
breakpoint_rules::BreakpointRules
3029
end
3130

32-
# TODO: Workout how we are actually going to do this in a nonglobal way
31+
# TODO: Workout how and if we are actually going to do this in a nonglobal way
3332
const GLOBAL_BREAKPOINT_RULES = BreakpointRules()
3433

3534
function HandEvalMeta(eval_module, stepping_mode)
3635
return HandEvalMeta(
37-
LittleDict{Symbol,Any}(),
3836
eval_module,
3937
stepping_mode,
4038
GLOBAL_BREAKPOINT_RULES
@@ -61,11 +59,13 @@ function Cassette.overdub(ctx::HandEvalCtx, f, @nospecialize(args...))
6159

6260
if should_recurse
6361
if Cassette.canrecurse(ctx, f, args...)
64-
_ctx = HandEvalCtx(ctx.metadata.eval_module, child_stepping_mode(ctx))
62+
# TODO: Workout this logic
63+
#_ctx = HandEvalCtx(ctx.metadata.eval_module, child_stepping_mode(ctx))
6564
try
66-
return Cassette.recurse(_ctx, f, args...)
65+
return Cassette.recurse(ctx, f, args...)
6766
finally
68-
ctx.metadata.stepping_mode = parent_stepping_mode(_ctx)
67+
# TODO: workout this logic
68+
#ctx.metadata.stepping_mode = parent_stepping_mode(ctx)
6969
end
7070
else
7171
@assert f isa Core.Builtin

src/pass.jl

Lines changed: 119 additions & 124 deletions
Original file line numberDiff line numberDiff line change
@@ -1,150 +1,145 @@
11
# For ease of editting we have this here
22
# It should be set to just redistpatch
3-
function handeval_break_action(metadata, meth, stmt_number)
4-
break_action(metadata, meth, stmt_number)
3+
function handeval_break_action(ctx, meth, stmt_number, slotnames, slotvalues)
4+
break_action(ctx, meth, stmt_number, slotnames, slotvalues)
55
end
6-
7-
8-
slotname(ir::Core.CodeInfo, slotnum::Integer) = ir.slotnames[slotnum]
9-
slotname(ir::Core.CodeInfo, slotnum) = slotname(ir, slotnum.id)
10-
11-
# inserts insert `ctx.metadata[:x] = x`
12-
function record_slot_value(ir, variable_record_slot, slotnum)
13-
name = slotname(ir, slotnum)
14-
return Expr(
15-
:call,
16-
Expr(:nooverdub, GlobalRef(Base, :setindex!)),
17-
variable_record_slot,
18-
slotnum,
19-
QuoteNode(name)
20-
)
6+
function handeval_should_break(ctx, meth, stmt_number)
7+
should_break(ctx, meth, stmt_number)
218
end
229

23-
# What we want to do is:
24-
# After every assigment: `x = foo`, insert `ctx.metadata[:x] = x`
25-
function instrument_assignments!(ir, variable_record_slot)
26-
is_assignment(stmt) = Base.Meta.isexpr(stmt, :(=))
27-
stmtcount(stmt, i) = is_assignment(stmt) ? 2 : nothing
28-
function newstmts(stmt, i)
29-
lhs = stmt.args[1]
30-
record = record_slot_value(ir, variable_record_slot, lhs)
31-
return [stmt, record]
32-
end
33-
Cassette.insert_statements!(ir.code, ir.codelocs, stmtcount, newstmts)
34-
end
35-
36-
function instrument_arguments!(ir, method, variable_record_slot)
37-
# start from 2 to skip #self
38-
arg_names = Base.method_argnames(method)[2:end]
39-
arg_slots = 1 .+ (1:length(arg_names))
40-
@assert(
41-
ir.slotnames[arg_slots] == arg_names,
42-
"$(ir.slotnames[arg_slots]) != $(arg_names)"
43-
)
4410

45-
Cassette.insert_statements!(
46-
ir.code, ir.codelocs,
47-
(stmt, i) -> i == 1 ? length(arg_slots) + 1 : nothing,
48-
49-
(stmt, i) -> [
50-
map(arg_slots) do slotnum
51-
slot = Core.SlotNumber(slotnum)
52-
record_slot_value(ir, variable_record_slot, slot)
53-
end;
54-
stmt
55-
]
56-
)
11+
"""
12+
extended_insert_statements!(code, codelocs, stmtcount, newstmts)
13+
14+
Like `Cassette.insert_statements` but the `newstmts` function takes
15+
3 arguments:
16+
- `statement`: the IR statement it will be replacing
17+
- `dest_i`: the first index that this will be inserted into, after renumbering to insert prior replacements.
18+
- in the `newst,ts` function `dest_i` should be used to calculate SSAValues and goto addresses
19+
- `src_i`: the index of the original IR statement that this will be replacing
20+
- You may wish to use this in the `newstmts` function if the code actually depends on which
21+
statment number of original IR is being replaced.
22+
"""
23+
function extended_insert_statements!(code, codelocs, stmtcount, newstmts)
24+
ssachangemap = fill(0, length(code))
25+
labelchangemap = fill(0, length(code))
26+
worklist = Tuple{Int,Int}[]
27+
for i in 1:length(code)
28+
stmt = code[i]
29+
nstmts = stmtcount(stmt, i)
30+
if nstmts !== nothing
31+
addedstmts = nstmts - 1
32+
push!(worklist, (i, addedstmts))
33+
ssachangemap[i] = addedstmts
34+
if i < length(code)
35+
labelchangemap[i + 1] = addedstmts
36+
end
37+
end
38+
end
39+
Core.Compiler.renumber_ir_elements!(code, ssachangemap, labelchangemap)
40+
for (src_i, addedstmts) in worklist
41+
dest_i = src_i + ssachangemap[src_i] - addedstmts # correct the index for accumulated offsets
42+
stmts = newstmts(code[dest_i], dest_i, src_i)
43+
@assert(length(stmts) == (addedstmts + 1), "$(length(stmts)) == $(addedstmts + 1)")
44+
code[dest_i] = stmts[end]
45+
for j in 1:(length(stmts) - 1) # insert in reverse to maintain the provided ordering
46+
insert!(code, dest_i, stmts[end - j])
47+
insert!(codelocs, dest_i, codelocs[dest_i])
48+
end
49+
end
5750
end
5851

5952
"""
60-
create_slot!(ir, namebase="")
61-
Adds a slot to the IR with a name based on `namebase`.
62-
It will be added at the end
63-
returns the new slot number
53+
created_on
54+
Given an `ir` returns a vector the same length as slotnames, with the index corresponds to that on which each was created
6455
"""
65-
function create_slot!(ir, namebase="")
66-
slot_name = gensym(namebase)
67-
push!(ir.slotnames, slot_name)
68-
push!(ir.slotflags, 0x00)
69-
slot = Core.SlotNumber(length(ir.slotnames))
70-
return slot
56+
function created_on(ir)
57+
created_stmt_ind = zeros(length(ir.slotnames)) # default to assuming everything created before start
58+
for (ii,stmt) in enumerate(ir.code)
59+
if stmt isa Core.NewvarNode
60+
@assert created_stmt_ind[stmt.slot.id] == 0
61+
created_stmt_ind[stmt.slot.id] = ii
62+
end
63+
end
64+
return created_stmt_ind
7165
end
7266

73-
"""
74-
setup_metadata_slots!(ir, metadata_slot, variable_record_slot)
67+
call_expr(mod::Module, func::Symbol, args...) = Expr(:call, Expr(:nooverdub, GlobalRef(mod, func)), args...)
7568

76-
Attaches the cassette metadata object and it's variable field to the slot given.
77-
This will be added as the very first statement to the ir.
78-
"""
79-
function setup_metadata_slots!(ir, metadata_slot, variable_record_slot)
69+
function enter_debug_statements(slotnames, slot_created_ons, method::Method, ind::Int, orig_ind::Int)
8070
statements = [
81-
# Get Cassette to fill in the MetaData slot
82-
Expr(:(=), metadata_slot, Expr(
83-
:call,
84-
Expr(:nooverdub, GlobalRef(Core, :getfield)),
85-
Expr(:contextslot),
86-
QuoteNode(:metadata)
87-
)),
88-
89-
# Extract it's variables dict field
90-
Expr(:(=), variable_record_slot, Expr(
91-
:call,
92-
Expr(:nooverdub, GlobalRef(Core, :getfield)),
93-
metadata_slot,
94-
QuoteNode(:variables)
95-
))
71+
call_expr(MagneticReadHead, :handeval_should_break, Expr(:contextslot), method, orig_ind),
72+
Expr(:REPLACE_THIS_WITH_GOTOIFNOT_AT_END),
73+
Expr(:call, Expr(:nooverdub, GlobalRef(Base, :getindex)), GlobalRef(Core, :Symbol)),
74+
Expr(:call, Expr(:nooverdub, GlobalRef(Base, :getindex)), GlobalRef(Core, :Any)),
9675
]
76+
stop_cond_ssa = Core.SSAValue(ind)
77+
# Skip the pplaceholder
78+
names_ssa = Core.SSAValue(ind + 2)
79+
values_ssa = Core.SSAValue(ind + 3)
80+
cur_ind = ind + 4
81+
# Now we store all of the slots that have values assigned to them
82+
for (slotind, (slotname, slot_created_on)) in enumerate(zip(slotnames, slot_created_ons))
83+
orig_ind > slot_created_on || continue
84+
slot = Core.SlotNumber(slotind)
85+
append!(statements, (
86+
Expr(:isdefined, slot), # cur_ind
87+
Expr(:gotoifnot, Core.SSAValue(cur_ind), cur_ind + 4), # cur_ind + 1
88+
call_expr(Base, :push!, names_ssa, QuoteNode(slotname)), # cur_ind + 2
89+
call_expr(Base, :push!, values_ssa, slot) # cur_ind + 3
90+
))
9791

98-
Cassette.insert_statements!(
99-
ir.code, ir.codelocs,
100-
(stmt, i) -> i == 1 ? length(statements) + 1 : nothing,
101-
(stmt, i) -> [statements; stmt]
92+
cur_ind += 4
93+
end
94+
95+
push!(statements, call_expr(
96+
MagneticReadHead, :handeval_break_action,
97+
Expr(:contextslot),
98+
method,
99+
orig_ind,
100+
names_ssa, values_ssa)
102101
)
102+
103+
statements[2] = Expr(:gotoifnot, stop_cond_ssa, ind + length(statements))
104+
return statements
103105
end
104106

105-
"""
106-
insert_break_actions!(ir, metadata_slot)
107107

108-
Add calls to the break action between every statement.
109-
"""
110-
function insert_break_actions!(reflection, metadata_slot)
111-
ir = reflection.code_info
112-
break_state(i) = Expr(:call,
113-
Expr(:nooverdub, GlobalRef(MagneticReadHead, :handeval_break_action)),
114-
metadata_slot,
115-
reflection.method,
116-
i
117-
)
118-
119-
Cassette.insert_statements!(
120-
ir.code, ir.codelocs,
121-
(stmt, i) -> 2,
122-
(stmt, i) -> [break_state(i-1); stmt]
123-
)
108+
function enter_debug_statements_count(slot_created_ons, orig_ind)
109+
# this function intentionally mirrors structure of enter_debug_statements
110+
# for ease of updating to match it
111+
n_statements = 4
112+
113+
for slot_created_on in slot_created_ons
114+
if orig_ind > slot_created_on
115+
n_statements += 4
116+
end
117+
end
118+
n_statements += 1
119+
return n_statements
124120
end
125121

126-
function instrument_handeval!(::Type{<:HandEvalCtx}, reflection::Cassette.Reflection)
122+
123+
function instrument!(::Type{<:HandEvalCtx}, reflection::Cassette.Reflection)
127124
ir = reflection.code_info
128-
# Create slots to store metadata and it's variable record field
129-
# put them a the end.
130-
metadata_slot = create_slot!(ir, "metadata")
131-
variable_record_slot = create_slot!(ir, "variable_record")
132-
133-
insert_break_actions!(reflection, metadata_slot)
134-
135-
# Now the real part where we determine about assigments
136-
instrument_assignments!(ir, variable_record_slot)
137-
138-
# record all the initial values so we get the parameters
139-
instrument_arguments!(ir, reflection.method, variable_record_slot)
140-
141-
# insert the initial metadata and variable record slot
142-
# assignments into the IR.
143-
# Do this last so it doesn't get caught in our assignment catching
144-
setup_metadata_slots!(ir, metadata_slot, variable_record_slot)
145-
125+
126+
slot_created_ons = created_on(ir)
127+
extended_insert_statements!(
128+
ir.code, ir.codelocs,
129+
(stmt, i) -> stmt isa Expr ?
130+
enter_debug_statements_count(slot_created_ons, i) + 1
131+
: nothing,
132+
(stmt, i, orig_i) -> [
133+
enter_debug_statements(
134+
ir.slotnames,
135+
slot_created_ons,
136+
reflection.method,
137+
i, orig_i
138+
);
139+
stmt
140+
]
141+
)
146142
return ir
147143
end
148144

149-
150-
const handeval_pass = Cassette.@pass instrument_handeval!
145+
const handeval_pass = Cassette.@pass instrument!

0 commit comments

Comments
 (0)