|
1 | 1 | # For ease of editting we have this here |
2 | 2 | # 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) |
5 | 5 | 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) |
21 | 8 | end |
22 | 9 |
|
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 | | - ) |
44 | 10 |
|
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 |
57 | 50 | end |
58 | 51 |
|
59 | 52 | """ |
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 |
64 | 55 | """ |
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 |
71 | 65 | end |
72 | 66 |
|
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...) |
75 | 68 |
|
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) |
80 | 70 | 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)), |
96 | 75 | ] |
| 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 | + )) |
97 | 91 |
|
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) |
102 | 101 | ) |
| 102 | + |
| 103 | + statements[2] = Expr(:gotoifnot, stop_cond_ssa, ind + length(statements)) |
| 104 | + return statements |
103 | 105 | end |
104 | 106 |
|
105 | | -""" |
106 | | - insert_break_actions!(ir, metadata_slot) |
107 | 107 |
|
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 |
124 | 120 | end |
125 | 121 |
|
126 | | -function instrument_handeval!(::Type{<:HandEvalCtx}, reflection::Cassette.Reflection) |
| 122 | + |
| 123 | +function instrument!(::Type{<:HandEvalCtx}, reflection::Cassette.Reflection) |
127 | 124 | 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 | + ) |
146 | 142 | return ir |
147 | 143 | end |
148 | 144 |
|
149 | | - |
150 | | -const handeval_pass = Cassette.@pass instrument_handeval! |
| 145 | +const handeval_pass = Cassette.@pass instrument! |
0 commit comments