/home/travis/build/MoarVM/MoarVM/src/instrument/crossthreadwrite.c
Line | Count | Source (jump to first uncovered line) |
1 | | #include "moar.h" |
2 | | |
3 | | /* Walk graph and insert write check instructions. */ |
4 | | static void prepend_ctw_check(MVMThreadContext *tc, MVMSpeshGraph *g, MVMSpeshBB *bb, |
5 | | MVMSpeshIns *before_ins, MVMSpeshOperand check_reg, |
6 | 0 | MVMint16 guilty) { |
7 | 0 | MVMSpeshIns *ctw_ins = MVM_spesh_alloc(tc, g, sizeof(MVMSpeshIns)); |
8 | 0 | ctw_ins->info = MVM_op_get_op(MVM_OP_ctw_check); |
9 | 0 | ctw_ins->operands = MVM_spesh_alloc(tc, g, 2 * sizeof(MVMSpeshOperand)); |
10 | 0 | ctw_ins->operands[0] = check_reg; |
11 | 0 | ctw_ins->operands[1].lit_i16 = guilty; |
12 | 0 | MVM_spesh_manipulate_insert_ins(tc, bb, before_ins->prev, ctw_ins); |
13 | 0 | } |
14 | 0 | static void instrument_graph(MVMThreadContext *tc, MVMSpeshGraph *g) { |
15 | 0 | MVMSpeshBB *bb = g->entry->linear_next; |
16 | 0 | while (bb) { |
17 | 0 | MVMSpeshIns *ins = bb->first_ins; |
18 | 0 | while (ins) { |
19 | 0 | switch (ins->info->opcode) { |
20 | 0 | case MVM_OP_rebless: |
21 | 0 | prepend_ctw_check(tc, g, bb, ins, ins->operands[0], MVM_CTW_REBLESS); |
22 | 0 | case MVM_OP_bindattr_i: |
23 | 0 | case MVM_OP_bindattr_n: |
24 | 0 | case MVM_OP_bindattr_s: |
25 | 0 | case MVM_OP_bindattr_o: |
26 | 0 | case MVM_OP_bindattrs_i: |
27 | 0 | case MVM_OP_bindattrs_n: |
28 | 0 | case MVM_OP_bindattrs_s: |
29 | 0 | case MVM_OP_bindattrs_o: |
30 | 0 | prepend_ctw_check(tc, g, bb, ins, ins->operands[0], MVM_CTW_BIND_ATTR); |
31 | 0 | break; |
32 | 0 | case MVM_OP_bindpos_i: |
33 | 0 | case MVM_OP_bindpos_n: |
34 | 0 | case MVM_OP_bindpos_s: |
35 | 0 | case MVM_OP_bindpos_o: |
36 | 0 | prepend_ctw_check(tc, g, bb, ins, ins->operands[0], MVM_CTW_BIND_POS); |
37 | 0 | break; |
38 | 0 | case MVM_OP_push_i: |
39 | 0 | case MVM_OP_push_n: |
40 | 0 | case MVM_OP_push_s: |
41 | 0 | case MVM_OP_push_o: |
42 | 0 | prepend_ctw_check(tc, g, bb, ins, ins->operands[0], MVM_CTW_PUSH); |
43 | 0 | break; |
44 | 0 | case MVM_OP_pop_i: |
45 | 0 | case MVM_OP_pop_n: |
46 | 0 | case MVM_OP_pop_s: |
47 | 0 | case MVM_OP_pop_o: |
48 | 0 | prepend_ctw_check(tc, g, bb, ins, ins->operands[1], MVM_CTW_POP); |
49 | 0 | break; |
50 | 0 | case MVM_OP_shift_i: |
51 | 0 | case MVM_OP_shift_n: |
52 | 0 | case MVM_OP_shift_s: |
53 | 0 | case MVM_OP_shift_o: |
54 | 0 | prepend_ctw_check(tc, g, bb, ins, ins->operands[1], MVM_CTW_SHIFT); |
55 | 0 | break; |
56 | 0 | case MVM_OP_unshift_i: |
57 | 0 | case MVM_OP_unshift_n: |
58 | 0 | case MVM_OP_unshift_s: |
59 | 0 | case MVM_OP_unshift_o: |
60 | 0 | prepend_ctw_check(tc, g, bb, ins, ins->operands[0], MVM_CTW_UNSHIFT); |
61 | 0 | break; |
62 | 0 | case MVM_OP_slice: |
63 | 0 | prepend_ctw_check(tc, g, bb, ins, ins->operands[0], MVM_CTW_SLICE); |
64 | 0 | break; |
65 | 0 | case MVM_OP_splice: |
66 | 0 | prepend_ctw_check(tc, g, bb, ins, ins->operands[0], MVM_CTW_SPLICE); |
67 | 0 | break; |
68 | 0 | case MVM_OP_bindkey_i: |
69 | 0 | case MVM_OP_bindkey_n: |
70 | 0 | case MVM_OP_bindkey_s: |
71 | 0 | case MVM_OP_bindkey_o: |
72 | 0 | prepend_ctw_check(tc, g, bb, ins, ins->operands[0], MVM_CTW_BIND_KEY); |
73 | 0 | break; |
74 | 0 | case MVM_OP_deletekey: |
75 | 0 | prepend_ctw_check(tc, g, bb, ins, ins->operands[0], MVM_CTW_DELETE_KEY); |
76 | 0 | break; |
77 | 0 | case MVM_OP_assign: |
78 | 0 | case MVM_OP_assignunchecked: |
79 | 0 | case MVM_OP_assign_i: |
80 | 0 | case MVM_OP_assign_n: |
81 | 0 | case MVM_OP_assign_s: |
82 | 0 | prepend_ctw_check(tc, g, bb, ins, ins->operands[0], MVM_CTW_ASSIGN); |
83 | 0 | break; |
84 | 0 | case MVM_OP_bindpos2d_i: |
85 | 0 | case MVM_OP_bindpos2d_n: |
86 | 0 | case MVM_OP_bindpos2d_s: |
87 | 0 | case MVM_OP_bindpos2d_o: |
88 | 0 | case MVM_OP_bindpos3d_i: |
89 | 0 | case MVM_OP_bindpos3d_n: |
90 | 0 | case MVM_OP_bindpos3d_s: |
91 | 0 | case MVM_OP_bindpos3d_o: |
92 | 0 | case MVM_OP_bindposnd_i: |
93 | 0 | case MVM_OP_bindposnd_n: |
94 | 0 | case MVM_OP_bindposnd_s: |
95 | 0 | case MVM_OP_bindposnd_o: |
96 | 0 | prepend_ctw_check(tc, g, bb, ins, ins->operands[0], MVM_CTW_BIND_POS); |
97 | 0 | break; |
98 | 0 | } |
99 | 0 | ins = ins->next; |
100 | 0 | } |
101 | 0 | bb = bb->linear_next; |
102 | 0 | } |
103 | 0 | } |
104 | | |
105 | | /* Adds instrumented version of the unspecialized bytecode. */ |
106 | 0 | static void add_instrumentation(MVMThreadContext *tc, MVMStaticFrame *sf) { |
107 | 0 | MVMSpeshCode *sc; |
108 | 0 | MVMStaticFrameInstrumentation *ins; |
109 | 0 | MVMSpeshGraph *sg = MVM_spesh_graph_create(tc, sf, 1, 0); |
110 | 0 | instrument_graph(tc, sg); |
111 | 0 | sc = MVM_spesh_codegen(tc, sg); |
112 | 0 | ins = MVM_calloc(1, sizeof(MVMStaticFrameInstrumentation)); |
113 | 0 | ins->instrumented_bytecode = sc->bytecode; |
114 | 0 | ins->instrumented_handlers = sc->handlers; |
115 | 0 | ins->instrumented_bytecode_size = sc->bytecode_size; |
116 | 0 | ins->uninstrumented_bytecode = sf->body.bytecode; |
117 | 0 | ins->uninstrumented_handlers = sf->body.handlers; |
118 | 0 | ins->uninstrumented_bytecode_size = sf->body.bytecode_size; |
119 | 0 | sf->body.instrumentation = ins; |
120 | 0 | MVM_spesh_graph_destroy(tc, sg); |
121 | 0 | MVM_free(sc); |
122 | 0 | } |
123 | | |
124 | | /* Instruments code with detection and reporting of cross-thread writes. */ |
125 | 0 | void MVM_cross_thread_write_instrument(MVMThreadContext *tc, MVMStaticFrame *sf) { |
126 | 0 | if (!sf->body.instrumentation || sf->body.bytecode != sf->body.instrumentation->instrumented_bytecode) { |
127 | 0 | /* Handle main, non-specialized, bytecode. */ |
128 | 0 | if (!sf->body.instrumentation) |
129 | 0 | add_instrumentation(tc, sf); |
130 | 0 | sf->body.bytecode = sf->body.instrumentation->instrumented_bytecode; |
131 | 0 | sf->body.handlers = sf->body.instrumentation->instrumented_handlers; |
132 | 0 | sf->body.bytecode_size = sf->body.instrumentation->instrumented_bytecode_size; |
133 | 0 |
|
134 | 0 | /* Throw away any argument guard so we'll never resolve prior |
135 | 0 | * specializations again. */ |
136 | 0 | MVM_spesh_arg_guard_discard(tc, sf); |
137 | 0 | } |
138 | 0 | } |
139 | | |
140 | | /* Filter out some special cases to reduce noise. */ |
141 | 0 | static MVMint64 filtered_out(MVMThreadContext *tc, MVMObject *written) { |
142 | 0 | /* If we're holding locks, exclude by default (unless we were asked to |
143 | 0 | * also include these). */ |
144 | 0 | if (tc->num_locks && !tc->instance->cross_thread_write_logging_include_locked) |
145 | 0 | return 1; |
146 | 0 |
|
147 | 0 | /* Operations on a concurrent queue are fine 'cus it's concurrent. */ |
148 | 0 | if (REPR(written)->ID == MVM_REPR_ID_ConcBlockingQueue) |
149 | 0 | return 1; |
150 | 0 |
|
151 | 0 | /* Write on object from event loop thread is usually shift of invokable. */ |
152 | 0 | if (tc->instance->event_loop_thread) |
153 | 0 | if (written->header.owner == tc->instance->event_loop_thread->thread_id) |
154 | 0 | return 1; |
155 | 0 |
|
156 | 0 | /* Filter out writes to Sub and Method, since these are almost always just |
157 | 0 | * multi-dispatch caches. */ |
158 | 0 | if (strncmp( MVM_6model_get_stable_debug_name(tc, written->st), "Method", 6) == 0) |
159 | 0 | return 1; |
160 | 0 | if (strncmp( MVM_6model_get_stable_debug_name(tc, written->st), "Sub", 3) == 0) |
161 | 0 | return 1; |
162 | 0 |
|
163 | 0 | /* Otherwise, may be relevant. */ |
164 | 0 | return 0; |
165 | 0 | } |
166 | | |
167 | | /* Squeal if the target of the write wasn't allocated by us. */ |
168 | 0 | void MVM_cross_thread_write_check(MVMThreadContext *tc, MVMObject *written, MVMint16 guilty) { |
169 | 0 | if (written->header.owner != tc->thread_id && !filtered_out(tc, written)) { |
170 | 0 | char *guilty_desc = "did something to"; |
171 | 0 | switch (guilty) { |
172 | 0 | case MVM_CTW_BIND_ATTR: |
173 | 0 | guilty_desc = "bound to an attribute of"; |
174 | 0 | break; |
175 | 0 | case MVM_CTW_BIND_POS: |
176 | 0 | guilty_desc = "bound to an array slot of"; |
177 | 0 | break; |
178 | 0 | case MVM_CTW_PUSH: |
179 | 0 | guilty_desc = "pushed to"; |
180 | 0 | break; |
181 | 0 | case MVM_CTW_POP: |
182 | 0 | guilty_desc = "popped"; |
183 | 0 | break; |
184 | 0 | case MVM_CTW_SHIFT: |
185 | 0 | guilty_desc = "shifted"; |
186 | 0 | break; |
187 | 0 | case MVM_CTW_UNSHIFT: |
188 | 0 | guilty_desc = "unshifted to"; |
189 | 0 | break; |
190 | 0 | case MVM_CTW_SLICE: |
191 | 0 | guilty_desc = "sliced"; |
192 | 0 | break; |
193 | 0 | case MVM_CTW_SPLICE: |
194 | 0 | guilty_desc = "spliced"; |
195 | 0 | break; |
196 | 0 | case MVM_CTW_BIND_KEY: |
197 | 0 | guilty_desc = "bound to a hash key of"; |
198 | 0 | break; |
199 | 0 | case MVM_CTW_DELETE_KEY: |
200 | 0 | guilty_desc = "deleted a hash key of"; |
201 | 0 | break; |
202 | 0 | case MVM_CTW_ASSIGN: |
203 | 0 | guilty_desc = "assigned to"; |
204 | 0 | break; |
205 | 0 | case MVM_CTW_REBLESS: |
206 | 0 | guilty_desc = "reblessed"; |
207 | 0 | break; |
208 | 0 | } |
209 | 0 | uv_mutex_lock(&(tc->instance->mutex_cross_thread_write_logging)); |
210 | 0 | fprintf(stderr, "Thread %d %s an object (%s) allocated by thread %d\n", |
211 | 0 | tc->thread_id, guilty_desc, MVM_6model_get_debug_name(tc, written), written->header.owner); |
212 | 0 | MVM_dump_backtrace(tc); |
213 | 0 | fprintf(stderr, "\n"); |
214 | 0 | uv_mutex_unlock(&(tc->instance->mutex_cross_thread_write_logging)); |
215 | 0 | } |
216 | 0 | } |