Skip to content

Commit

Permalink
Do not live track variables, instead retroactively workout what is de…
Browse files Browse the repository at this point in the history
…fine when a breakpoint is hit
  • Loading branch information
oxinabox committed Apr 27, 2019
1 parent 40e13de commit 3e37084
Show file tree
Hide file tree
Showing 4 changed files with 149 additions and 153 deletions.
3 changes: 1 addition & 2 deletions src/MagneticReadHead.jl
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ module MagneticReadHead
using Base: invokelatest
using Cassette
using MacroTools
using OrderedCollections
using InteractiveUtils
using CodeTracking
# We don't use Revise, but if it isn't loaded CodeTracking has issues
using Revise: Revise
using OrderedCollections

export @iron_debug

Expand Down Expand Up @@ -38,7 +38,6 @@ macro iron_debug(body)
# Disable any stepping left-over
ctx.metadata.stepping_mode = StepContinue()
end

end
end

Expand Down
44 changes: 23 additions & 21 deletions src/break_action.jl
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,11 @@ end
function breadcrumbs(io, file::AbstractString, line_num; nbefore=2, nafter=2)
@assert(nbefore >= 0)
@assert(nafter >= 0)

all_lines = loc_for_file(file)
first_line_num = max(1, line_num - nbefore)
last_line_num = min(length(all_lines), line_num + nafter)

for ln in first_line_num:last_line_num
line = all_lines[ln]
if ln == line_num
Expand All @@ -54,34 +54,36 @@ end
# this function exists only for mocking so we can test it.
breakpoint_hit(meth, statement_ind) = nothing

function iron_repl(metadata::HandEvalMeta, meth, statement_ind)
function iron_repl(metadata::HandEvalMeta, meth, statement_ind, variables)
breakpoint_hit(meth, statement_ind)
breadcrumbs(meth, statement_ind)

printstyled("Vars: "; color=:light_yellow)
println(join(keys(metadata.variables), ", "))
println(join(keys(variables), ", "))
print_commands()

run_repl(metadata.variables, metadata.eval_module)
end

run_repl(variables, metadata.eval_module)
end

"""
break_action(metadata, meth, statement_ind)
should_breakon
Determines if we should actualy break at a potential breakpoint
"""
function should_break(ctx, meth, statement_ind)
return ctx.metadata.stepping_mode isa StepNext ||
should_breakon(ctx.metadata.breakpoint_rules, meth, statement_ind)
end


This determines what we should do when we hit a potential point to break at.
We check if we should actually break here,
and if so open up a REPL.
if not, then we continue.
"""
function break_action(metadata, meth, statement_ind)
if !(metadata.stepping_mode isa StepNext
|| should_breakon(metadata.breakpoint_rules, meth, statement_ind)
)
# Only break on StepNext and actual breakpoints
return
end
break_action
code_word = iron_repl(metadata, meth, statement_ind)
What to do when a breakpoint is hit
"""
function break_action(ctx, meth, statement_ind, slotnames, slotvals)
metadata = ctx.metadata
# TODO we probably need to drop the first few slots as they will contain various metadata
variables = LittleDict(slotnames, slotvals)
code_word = iron_repl(metadata, meth, statement_ind, variables)
actions[code_word].act(metadata)
end
12 changes: 6 additions & 6 deletions src/core_control.jl
Original file line number Diff line number Diff line change
Expand Up @@ -23,18 +23,16 @@ parent_stepping_mode(::StepOut) = StepNext() # This is what they want


mutable struct HandEvalMeta
variables::LittleDict{Symbol, Any}
eval_module::Module
stepping_mode::SteppingMode
breakpoint_rules::BreakpointRules
end

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

function HandEvalMeta(eval_module, stepping_mode)
return HandEvalMeta(
LittleDict{Symbol,Any}(),
eval_module,
stepping_mode,
GLOBAL_BREAKPOINT_RULES
Expand All @@ -61,11 +59,13 @@ function Cassette.overdub(ctx::HandEvalCtx, f, @nospecialize(args...))

if should_recurse
if Cassette.canrecurse(ctx, f, args...)
_ctx = HandEvalCtx(ctx.metadata.eval_module, child_stepping_mode(ctx))
# TODO: Workout this logic
#_ctx = HandEvalCtx(ctx.metadata.eval_module, child_stepping_mode(ctx))
try
return Cassette.recurse(_ctx, f, args...)
return Cassette.recurse(ctx, f, args...)
finally
ctx.metadata.stepping_mode = parent_stepping_mode(_ctx)
# TODO: workout this logic
#ctx.metadata.stepping_mode = parent_stepping_mode(ctx)
end
else
@assert f isa Core.Builtin
Expand Down
243 changes: 119 additions & 124 deletions src/pass.jl
Original file line number Diff line number Diff line change
@@ -1,150 +1,145 @@
# For ease of editting we have this here
# It should be set to just redistpatch
function handeval_break_action(metadata, meth, stmt_number)
break_action(metadata, meth, stmt_number)
function handeval_break_action(ctx, meth, stmt_number, slotnames, slotvalues)
break_action(ctx, meth, stmt_number, slotnames, slotvalues)
end


slotname(ir::Core.CodeInfo, slotnum::Integer) = ir.slotnames[slotnum]
slotname(ir::Core.CodeInfo, slotnum) = slotname(ir, slotnum.id)

# inserts insert `ctx.metadata[:x] = x`
function record_slot_value(ir, variable_record_slot, slotnum)
name = slotname(ir, slotnum)
return Expr(
:call,
Expr(:nooverdub, GlobalRef(Base, :setindex!)),
variable_record_slot,
slotnum,
QuoteNode(name)
)
function handeval_should_break(ctx, meth, stmt_number)
should_break(ctx, meth, stmt_number)
end

# What we want to do is:
# After every assigment: `x = foo`, insert `ctx.metadata[:x] = x`
function instrument_assignments!(ir, variable_record_slot)
is_assignment(stmt) = Base.Meta.isexpr(stmt, :(=))
stmtcount(stmt, i) = is_assignment(stmt) ? 2 : nothing
function newstmts(stmt, i)
lhs = stmt.args[1]
record = record_slot_value(ir, variable_record_slot, lhs)
return [stmt, record]
end
Cassette.insert_statements!(ir.code, ir.codelocs, stmtcount, newstmts)
end

function instrument_arguments!(ir, method, variable_record_slot)
# start from 2 to skip #self
arg_names = Base.method_argnames(method)[2:end]
arg_slots = 1 .+ (1:length(arg_names))
@assert(
ir.slotnames[arg_slots] == arg_names,
"$(ir.slotnames[arg_slots]) != $(arg_names)"
)

Cassette.insert_statements!(
ir.code, ir.codelocs,
(stmt, i) -> i == 1 ? length(arg_slots) + 1 : nothing,

(stmt, i) -> [
map(arg_slots) do slotnum
slot = Core.SlotNumber(slotnum)
record_slot_value(ir, variable_record_slot, slot)
end;
stmt
]
)
"""
extended_insert_statements!(code, codelocs, stmtcount, newstmts)
Like `Cassette.insert_statements` but the `newstmts` function takes
3 arguments:
- `statement`: the IR statement it will be replacing
- `dest_i`: the first index that this will be inserted into, after renumbering to insert prior replacements.
- in the `newst,ts` function `dest_i` should be used to calculate SSAValues and goto addresses
- `src_i`: the index of the original IR statement that this will be replacing
- You may wish to use this in the `newstmts` function if the code actually depends on which
statment number of original IR is being replaced.
"""
function extended_insert_statements!(code, codelocs, stmtcount, newstmts)
ssachangemap = fill(0, length(code))
labelchangemap = fill(0, length(code))
worklist = Tuple{Int,Int}[]
for i in 1:length(code)
stmt = code[i]
nstmts = stmtcount(stmt, i)
if nstmts !== nothing
addedstmts = nstmts - 1
push!(worklist, (i, addedstmts))
ssachangemap[i] = addedstmts
if i < length(code)
labelchangemap[i + 1] = addedstmts
end
end
end
Core.Compiler.renumber_ir_elements!(code, ssachangemap, labelchangemap)
for (src_i, addedstmts) in worklist
dest_i = src_i + ssachangemap[src_i] - addedstmts # correct the index for accumulated offsets
stmts = newstmts(code[dest_i], dest_i, src_i)
@assert(length(stmts) == (addedstmts + 1), "$(length(stmts)) == $(addedstmts + 1)")
code[dest_i] = stmts[end]
for j in 1:(length(stmts) - 1) # insert in reverse to maintain the provided ordering
insert!(code, dest_i, stmts[end - j])
insert!(codelocs, dest_i, codelocs[dest_i])
end
end
end

"""
create_slot!(ir, namebase="")
Adds a slot to the IR with a name based on `namebase`.
It will be added at the end
returns the new slot number
created_on
Given an `ir` returns a vector the same length as slotnames, with the index corresponds to that on which each was created
"""
function create_slot!(ir, namebase="")
slot_name = gensym(namebase)
push!(ir.slotnames, slot_name)
push!(ir.slotflags, 0x00)
slot = Core.SlotNumber(length(ir.slotnames))
return slot
function created_on(ir)
created_stmt_ind = zeros(length(ir.slotnames)) # default to assuming everything created before start
for (ii,stmt) in enumerate(ir.code)
if stmt isa Core.NewvarNode
@assert created_stmt_ind[stmt.slot.id] == 0
created_stmt_ind[stmt.slot.id] = ii
end
end
return created_stmt_ind
end

"""
setup_metadata_slots!(ir, metadata_slot, variable_record_slot)
call_expr(mod::Module, func::Symbol, args...) = Expr(:call, Expr(:nooverdub, GlobalRef(mod, func)), args...)

Attaches the cassette metadata object and it's variable field to the slot given.
This will be added as the very first statement to the ir.
"""
function setup_metadata_slots!(ir, metadata_slot, variable_record_slot)
function enter_debug_statements(slotnames, slot_created_ons, method::Method, ind::Int, orig_ind::Int)
statements = [
# Get Cassette to fill in the MetaData slot
Expr(:(=), metadata_slot, Expr(
:call,
Expr(:nooverdub, GlobalRef(Core, :getfield)),
Expr(:contextslot),
QuoteNode(:metadata)
)),

# Extract it's variables dict field
Expr(:(=), variable_record_slot, Expr(
:call,
Expr(:nooverdub, GlobalRef(Core, :getfield)),
metadata_slot,
QuoteNode(:variables)
))
call_expr(MagneticReadHead, :handeval_should_break, Expr(:contextslot), method, orig_ind),
Expr(:REPLACE_THIS_WITH_GOTOIFNOT_AT_END),
Expr(:call, Expr(:nooverdub, GlobalRef(Base, :getindex)), GlobalRef(Core, :Symbol)),
Expr(:call, Expr(:nooverdub, GlobalRef(Base, :getindex)), GlobalRef(Core, :Any)),
]
stop_cond_ssa = Core.SSAValue(ind)
# Skip the pplaceholder
names_ssa = Core.SSAValue(ind + 2)
values_ssa = Core.SSAValue(ind + 3)
cur_ind = ind + 4
# Now we store all of the slots that have values assigned to them
for (slotind, (slotname, slot_created_on)) in enumerate(zip(slotnames, slot_created_ons))
orig_ind > slot_created_on || continue
slot = Core.SlotNumber(slotind)
append!(statements, (
Expr(:isdefined, slot), # cur_ind
Expr(:gotoifnot, Core.SSAValue(cur_ind), cur_ind + 4), # cur_ind + 1
call_expr(Base, :push!, names_ssa, QuoteNode(slotname)), # cur_ind + 2
call_expr(Base, :push!, values_ssa, slot) # cur_ind + 3
))

Cassette.insert_statements!(
ir.code, ir.codelocs,
(stmt, i) -> i == 1 ? length(statements) + 1 : nothing,
(stmt, i) -> [statements; stmt]
cur_ind += 4
end

push!(statements, call_expr(
MagneticReadHead, :handeval_break_action,
Expr(:contextslot),
method,
orig_ind,
names_ssa, values_ssa)
)

statements[2] = Expr(:gotoifnot, stop_cond_ssa, ind + length(statements))
return statements
end

"""
insert_break_actions!(ir, metadata_slot)

Add calls to the break action between every statement.
"""
function insert_break_actions!(reflection, metadata_slot)
ir = reflection.code_info
break_state(i) = Expr(:call,
Expr(:nooverdub, GlobalRef(MagneticReadHead, :handeval_break_action)),
metadata_slot,
reflection.method,
i
)

Cassette.insert_statements!(
ir.code, ir.codelocs,
(stmt, i) -> 2,
(stmt, i) -> [break_state(i-1); stmt]
)
function enter_debug_statements_count(slot_created_ons, orig_ind)
# this function intentionally mirrors structure of enter_debug_statements
# for ease of updating to match it
n_statements = 4

for slot_created_on in slot_created_ons
if orig_ind > slot_created_on
n_statements += 4
end
end
n_statements += 1
return n_statements
end

function instrument_handeval!(::Type{<:HandEvalCtx}, reflection::Cassette.Reflection)

function instrument!(::Type{<:HandEvalCtx}, reflection::Cassette.Reflection)
ir = reflection.code_info
# Create slots to store metadata and it's variable record field
# put them a the end.
metadata_slot = create_slot!(ir, "metadata")
variable_record_slot = create_slot!(ir, "variable_record")

insert_break_actions!(reflection, metadata_slot)

# Now the real part where we determine about assigments
instrument_assignments!(ir, variable_record_slot)

# record all the initial values so we get the parameters
instrument_arguments!(ir, reflection.method, variable_record_slot)

# insert the initial metadata and variable record slot
# assignments into the IR.
# Do this last so it doesn't get caught in our assignment catching
setup_metadata_slots!(ir, metadata_slot, variable_record_slot)


slot_created_ons = created_on(ir)
extended_insert_statements!(
ir.code, ir.codelocs,
(stmt, i) -> stmt isa Expr ?
enter_debug_statements_count(slot_created_ons, i) + 1
: nothing,
(stmt, i, orig_i) -> [
enter_debug_statements(
ir.slotnames,
slot_created_ons,
reflection.method,
i, orig_i
);
stmt
]
)
return ir
end


const handeval_pass = Cassette.@pass instrument_handeval!
const handeval_pass = Cassette.@pass instrument!

0 comments on commit 3e37084

Please sign in to comment.