/home/travis/build/MoarVM/MoarVM/src/instrument/line_coverage.c
Line | Count | Source (jump to first uncovered line) |
1 | | #include "moar.h" |
2 | | |
3 | | #ifdef _WIN32 |
4 | | #define snprintf _snprintf |
5 | | #endif |
6 | | |
7 | 0 | static void instrument_graph_with_breakpoints(MVMThreadContext *tc, MVMSpeshGraph *g) { |
8 | 0 | MVMSpeshBB *bb = g->entry->linear_next; |
9 | 0 | MVMuint16 array_slot = 0; |
10 | 0 |
|
11 | 0 | MVMint32 last_line_number = -2; |
12 | 0 | MVMint32 last_filename = -1; |
13 | 0 |
|
14 | 0 | char *filename_buf = NULL; |
15 | 0 |
|
16 | 0 | while (bb) { |
17 | 0 | MVMSpeshIns *ins = bb->first_ins; |
18 | 0 | MVMSpeshIns *breakpoint_ins; |
19 | 0 |
|
20 | 0 | MVMBytecodeAnnotation *bbba = MVM_bytecode_resolve_annotation(tc, &g->sf->body, bb->initial_pc); |
21 | 0 | MVMint64 line_number = -1; |
22 | 0 | MVMint64 filename_string_index = -1; |
23 | 0 |
|
24 | 0 | MVMuint32 file_bp_idx; |
25 | 0 |
|
26 | 0 | if (bbba) { |
27 | 0 | line_number = bbba->line_number; |
28 | 0 | filename_string_index = bbba->filename_string_heap_index; |
29 | 0 | MVM_free(bbba); |
30 | 0 | } else { |
31 | 0 | line_number = -1; |
32 | 0 | bb = bb->linear_next; |
33 | 0 | continue; |
34 | 0 | } |
35 | 0 |
|
36 | 0 | /* skip PHI instructions, to make sure PHI only occur uninterrupted after start-of-bb */ |
37 | 0 | while (ins && ins->info->opcode == MVM_SSA_PHI) { |
38 | 0 | ins = ins->next; |
39 | 0 | } |
40 | 0 | if (!ins) ins = bb->last_ins; |
41 | 0 |
|
42 | 0 | /* Jumplists require the target BB to start in the goto op. |
43 | 0 | * We must not break this, or we cause the interpreter to derail */ |
44 | 0 | if (bb->last_ins->info->opcode == MVM_OP_jumplist) { |
45 | 0 | MVMint16 to_skip = bb->num_succ; |
46 | 0 | for (; to_skip > 0; to_skip--) { |
47 | 0 | bb = bb->linear_next; |
48 | 0 | } |
49 | 0 | continue; |
50 | 0 | } |
51 | 0 |
|
52 | 0 | if (line_number >= 0) { |
53 | 0 | breakpoint_ins = MVM_spesh_alloc(tc, g, sizeof(MVMSpeshIns)); |
54 | 0 | breakpoint_ins->info = MVM_op_get_op(MVM_OP_breakpoint); |
55 | 0 | breakpoint_ins->operands = MVM_spesh_alloc(tc, g, 2 * sizeof(MVMSpeshOperand)); |
56 | 0 |
|
57 | 0 | if (last_filename != filename_string_index) { |
58 | 0 | if (filename_buf) |
59 | 0 | MVM_free(filename_buf); |
60 | 0 | filename_buf = MVM_string_utf8_encode_C_string(tc, MVM_cu_string(tc, g->sf->body.cu, filename_string_index)); |
61 | 0 | } |
62 | 0 |
|
63 | 0 | MVM_debugserver_register_line(tc, filename_buf, strlen(filename_buf), line_number, &file_bp_idx); |
64 | 0 |
|
65 | 0 | breakpoint_ins->operands[0].lit_i32 = file_bp_idx; |
66 | 0 | breakpoint_ins->operands[1].lit_i32 = line_number; |
67 | 0 |
|
68 | 0 | last_filename = filename_string_index; |
69 | 0 |
|
70 | 0 | MVM_spesh_manipulate_insert_ins(tc, bb, ins->prev, breakpoint_ins); |
71 | 0 | } |
72 | 0 |
|
73 | 0 | /* Now go through instructions to see if any are annotated with a |
74 | 0 | * precise filename/lineno as well. */ |
75 | 0 | while (ins) { |
76 | 0 | MVMSpeshAnn *ann = ins->annotations; |
77 | 0 |
|
78 | 0 | while (ann) { |
79 | 0 | if (ann->type == MVM_SPESH_ANN_LINENO) { |
80 | 0 | /* We are very likely to have one instruction here that has |
81 | 0 | * the same annotation as the bb itself. We skip that one.*/ |
82 | 0 | if (ann->data.lineno.line_number == line_number && ann->data.lineno.filename_string_index == filename_string_index) { |
83 | 0 | break; |
84 | 0 | } |
85 | 0 |
|
86 | 0 | line_number = ann->data.lineno.line_number; |
87 | 0 | filename_string_index = ann->data.lineno.filename_string_index; |
88 | 0 |
|
89 | 0 | /*breakpoint_ins = MVM_spesh_alloc(tc, g, sizeof(MVMSpeshIns));*/ |
90 | 0 | /*breakpoint_ins->info = MVM_op_get_op(MVM_OP_breakpoint);*/ |
91 | 0 | /*breakpoint_ins->operands = MVM_spesh_alloc(tc, g, 2 * sizeof(MVMSpeshOperand));*/ |
92 | 0 |
|
93 | 0 | if (last_filename != filename_string_index) { |
94 | 0 | if (filename_buf) |
95 | 0 | MVM_free(filename_buf); |
96 | 0 | filename_buf = MVM_string_utf8_encode_C_string(tc, MVM_cu_string(tc, g->sf->body.cu, filename_string_index)); |
97 | 0 | } |
98 | 0 |
|
99 | 0 | MVM_debugserver_register_line(tc, filename_buf, strlen(filename_buf), line_number, &file_bp_idx); |
100 | 0 |
|
101 | 0 | /*breakpoint_ins->operands[0].lit_i32 = file_bp_idx;*/ |
102 | 0 | /*breakpoint_ins->operands[1].lit_i32 = ann->data.lineno.line_number;*/ |
103 | 0 |
|
104 | 0 | /* XXX insert breakpoint op here, too, maybe? */ |
105 | 0 |
|
106 | 0 | break; |
107 | 0 | } |
108 | 0 |
|
109 | 0 | ann = ann->next; |
110 | 0 | } |
111 | 0 |
|
112 | 0 | ins = ins->next; |
113 | 0 | } |
114 | 0 |
|
115 | 0 | bb = bb->linear_next; |
116 | 0 | } |
117 | 0 |
|
118 | 0 | if (filename_buf) |
119 | 0 | MVM_free(filename_buf); |
120 | 0 | } |
121 | | |
122 | 0 | static void instrument_graph(MVMThreadContext *tc, MVMSpeshGraph *g) { |
123 | 0 | MVMSpeshBB *bb = g->entry->linear_next; |
124 | 0 | MVMuint16 array_slot = 0; |
125 | 0 |
|
126 | 0 | MVMint32 last_line_number = -2; |
127 | 0 | MVMint32 last_filename = -1; |
128 | 0 |
|
129 | 0 | MVMuint16 allocd_slots = g->num_bbs * 2; |
130 | 0 | char *line_report_store = MVM_calloc(allocd_slots, sizeof(char)); |
131 | 0 |
|
132 | 0 | /* Since we don't know the right size for the line report store |
133 | 0 | * up front, we will have to realloc it along the way. After that |
134 | 0 | * we havee to fix up the arguments to the coverage log instructions */ |
135 | 0 | MVMuint32 fixup_alloc = g->num_bbs * 2; |
136 | 0 | MVMuint32 fixup_elems = 0; |
137 | 0 | MVMuint32 fixup_idx; /* for iterating over the fixup array */ |
138 | 0 | MVMSpeshIns **to_fixup = MVM_malloc(fixup_alloc * sizeof(MVMSpeshIns*)); |
139 | 0 |
|
140 | 0 | while (bb) { |
141 | 0 | MVMSpeshIns *ins = bb->first_ins; |
142 | 0 | MVMSpeshIns *log_ins; |
143 | 0 |
|
144 | 0 | MVMBytecodeAnnotation *bbba = MVM_bytecode_resolve_annotation(tc, &g->sf->body, bb->initial_pc); |
145 | 0 | MVMint64 line_number; |
146 | 0 | MVMint64 filename_string_index; |
147 | 0 | if (bbba) { |
148 | 0 | line_number = bbba->line_number; |
149 | 0 | filename_string_index = bbba->filename_string_heap_index; |
150 | 0 | MVM_free(bbba); |
151 | 0 | } else { |
152 | 0 | line_number = -1; |
153 | 0 | bb = bb->linear_next; |
154 | 0 | continue; |
155 | 0 | } |
156 | 0 |
|
157 | 0 | /* skip PHI instructions, to make sure PHI only occur uninterrupted after start-of-bb */ |
158 | 0 | while (ins && ins->info->opcode == MVM_SSA_PHI) { |
159 | 0 | ins = ins->next; |
160 | 0 | } |
161 | 0 | if (!ins) ins = bb->last_ins; |
162 | 0 |
|
163 | 0 | /* Jumplists require the target BB to start in the goto op. |
164 | 0 | * We must not break this, or we cause the interpreter to derail */ |
165 | 0 | if (bb->last_ins->info->opcode == MVM_OP_jumplist) { |
166 | 0 | MVMint16 to_skip = bb->num_succ; |
167 | 0 | for (; to_skip > 0; to_skip--) { |
168 | 0 | bb = bb->linear_next; |
169 | 0 | } |
170 | 0 | continue; |
171 | 0 | } |
172 | 0 |
|
173 | 0 | log_ins = MVM_spesh_alloc(tc, g, sizeof(MVMSpeshIns)); |
174 | 0 | log_ins->info = MVM_op_get_op(MVM_OP_coverage_log); |
175 | 0 | log_ins->operands = MVM_spesh_alloc(tc, g, 4 * sizeof(MVMSpeshOperand)); |
176 | 0 |
|
177 | 0 | log_ins->operands[0].lit_str_idx = filename_string_index; |
178 | 0 | log_ins->operands[1].lit_i32 = line_number; |
179 | 0 |
|
180 | 0 | if (last_line_number == line_number && last_filename == filename_string_index) { |
181 | 0 | /* Consecutive BBs with the same line number and filename should |
182 | 0 | * share one "already reported" slot. */ |
183 | 0 | log_ins->operands[2].lit_i32 = array_slot; |
184 | 0 | } else { |
185 | 0 | log_ins->operands[2].lit_i32 = array_slot++; |
186 | 0 | last_line_number = line_number; |
187 | 0 | last_filename = filename_string_index; |
188 | 0 |
|
189 | 0 | if (array_slot == allocd_slots) { |
190 | 0 | allocd_slots *= 2; |
191 | 0 | line_report_store = MVM_realloc(line_report_store, sizeof(char) * allocd_slots); |
192 | 0 | } |
193 | 0 | } |
194 | 0 |
|
195 | 0 | to_fixup[fixup_elems++] = log_ins; |
196 | 0 | if (fixup_elems == fixup_alloc) { |
197 | 0 | fixup_alloc *= 2; |
198 | 0 | to_fixup = MVM_realloc(to_fixup, sizeof(MVMSpeshIns*) * fixup_alloc); |
199 | 0 | } |
200 | 0 | MVM_spesh_manipulate_insert_ins(tc, bb, ins, log_ins); |
201 | 0 |
|
202 | 0 | /* Now go through instructions to see if any are annotated with a |
203 | 0 | * precise filename/lineno as well. */ |
204 | 0 | while (ins) { |
205 | 0 | MVMSpeshAnn *ann = ins->annotations; |
206 | 0 |
|
207 | 0 | while (ann) { |
208 | 0 | if (ann->type == MVM_SPESH_ANN_LINENO) { |
209 | 0 | /* We are very likely to have one instruction here that has |
210 | 0 | * the same annotation as the bb itself. We skip that one.*/ |
211 | 0 | if (ann->data.lineno.line_number == line_number && ann->data.lineno.filename_string_index == filename_string_index) { |
212 | 0 | break; |
213 | 0 | } |
214 | 0 |
|
215 | 0 | log_ins = MVM_spesh_alloc(tc, g, sizeof(MVMSpeshIns)); |
216 | 0 | log_ins->info = MVM_op_get_op(MVM_OP_coverage_log); |
217 | 0 | log_ins->operands = MVM_spesh_alloc(tc, g, 4 * sizeof(MVMSpeshOperand)); |
218 | 0 |
|
219 | 0 | log_ins->operands[0].lit_str_idx = ann->data.lineno.filename_string_index; |
220 | 0 | log_ins->operands[1].lit_i32 = ann->data.lineno.line_number; |
221 | 0 | log_ins->operands[2].lit_i32 = array_slot++; |
222 | 0 |
|
223 | 0 | if (array_slot == allocd_slots) { |
224 | 0 | allocd_slots *= 2; |
225 | 0 | line_report_store = MVM_realloc(line_report_store, sizeof(char) * allocd_slots); |
226 | 0 | } |
227 | 0 |
|
228 | 0 | to_fixup[fixup_elems++] = log_ins; |
229 | 0 | if (fixup_elems == fixup_alloc) { |
230 | 0 | fixup_alloc *= 2; |
231 | 0 | to_fixup = MVM_realloc(to_fixup, sizeof(MVMSpeshIns*) * fixup_alloc); |
232 | 0 | } |
233 | 0 | break; |
234 | 0 | } |
235 | 0 |
|
236 | 0 | ann = ann->next; |
237 | 0 | } |
238 | 0 |
|
239 | 0 | ins = ins->next; |
240 | 0 | } |
241 | 0 |
|
242 | 0 | bb = bb->linear_next; |
243 | 0 | } |
244 | 0 |
|
245 | 0 | line_report_store = MVM_realloc(line_report_store, sizeof(char) * (array_slot + 1)); |
246 | 0 |
|
247 | 0 | for (fixup_idx = 0; fixup_idx < fixup_elems; fixup_idx++) { |
248 | 0 | MVMSpeshIns *ins = to_fixup[fixup_idx]; |
249 | 0 |
|
250 | 0 | ins->operands[3].lit_i64 = (MVMint64)line_report_store; |
251 | 0 | } |
252 | 0 |
|
253 | 0 | if (array_slot == 0) { |
254 | 0 | MVM_free(line_report_store); |
255 | 0 | } |
256 | 0 | MVM_free(to_fixup); |
257 | 0 | } |
258 | | |
259 | | /* Adds instrumented version of the unspecialized bytecode. */ |
260 | 0 | static void add_instrumentation(MVMThreadContext *tc, MVMStaticFrame *sf, MVMuint8 want_coverage) { |
261 | 0 | MVMSpeshCode *sc; |
262 | 0 | MVMStaticFrameInstrumentation *ins; |
263 | 0 | MVMSpeshGraph *sg = MVM_spesh_graph_create(tc, sf, 1, 0); |
264 | 0 | if (want_coverage) |
265 | 0 | instrument_graph(tc, sg); |
266 | 0 | else |
267 | 0 | instrument_graph_with_breakpoints(tc, sg); |
268 | 0 | sc = MVM_spesh_codegen(tc, sg); |
269 | 0 | ins = MVM_calloc(1, sizeof(MVMStaticFrameInstrumentation)); |
270 | 0 | ins->instrumented_bytecode = sc->bytecode; |
271 | 0 | ins->instrumented_handlers = sc->handlers; |
272 | 0 | ins->instrumented_bytecode_size = sc->bytecode_size; |
273 | 0 | ins->uninstrumented_bytecode = sf->body.bytecode; |
274 | 0 | ins->uninstrumented_handlers = sf->body.handlers; |
275 | 0 | ins->uninstrumented_bytecode_size = sf->body.bytecode_size; |
276 | 0 | sf->body.instrumentation = ins; |
277 | 0 | MVM_spesh_graph_destroy(tc, sg); |
278 | 0 | MVM_free(sc); |
279 | 0 | } |
280 | | |
281 | | |
282 | | /* Instruments code with per-line logging of code coverage */ |
283 | 0 | static void line_numbers_instrument(MVMThreadContext *tc, MVMStaticFrame *sf, MVMuint8 want_coverage) { |
284 | 0 | if (!sf->body.instrumentation || sf->body.bytecode != sf->body.instrumentation->instrumented_bytecode) { |
285 | 0 | /* Handle main, non-specialized, bytecode. */ |
286 | 0 | if (!sf->body.instrumentation) |
287 | 0 | add_instrumentation(tc, sf, want_coverage); |
288 | 0 | sf->body.bytecode = sf->body.instrumentation->instrumented_bytecode; |
289 | 0 | sf->body.handlers = sf->body.instrumentation->instrumented_handlers; |
290 | 0 | sf->body.bytecode_size = sf->body.instrumentation->instrumented_bytecode_size; |
291 | 0 |
|
292 | 0 | /* Throw away any argument guard so we'll never resolve prior |
293 | 0 | * specializations again. */ |
294 | 0 | MVM_spesh_arg_guard_discard(tc, sf); |
295 | 0 | } |
296 | 0 | } |
297 | | |
298 | | /* Instruments code with per-line logging of code coverage */ |
299 | 0 | void MVM_line_coverage_instrument(MVMThreadContext *tc, MVMStaticFrame *sf) { |
300 | 0 | line_numbers_instrument(tc, sf, 1); |
301 | 0 | } |
302 | | |
303 | | /* Instruments code with a breakpoint check instruction af every line number change */ |
304 | 0 | void MVM_breakpoint_instrument(MVMThreadContext *tc, MVMStaticFrame *sf) { |
305 | 0 | line_numbers_instrument(tc, sf, 0); |
306 | 0 | } |
307 | | |
308 | 0 | void MVM_line_coverage_report(MVMThreadContext *tc, MVMString *filename, MVMuint32 line_number, MVMuint16 cache_slot, char *cache) { |
309 | 0 | if (tc->instance->coverage_control == 2 || (!tc->instance->coverage_control && cache[cache_slot] == 0)) { |
310 | 0 | char *encoded_filename; |
311 | 0 | char composed_line[256]; |
312 | 0 | size_t length; |
313 | 0 |
|
314 | 0 | cache[cache_slot] = 1; |
315 | 0 |
|
316 | 0 | encoded_filename = MVM_string_utf8_encode_C_string(tc, filename); |
317 | 0 | if ((length = snprintf(composed_line, 255, "HIT %s %d\n", encoded_filename, line_number)) > 0) { |
318 | 0 | fputs(composed_line, tc->instance->coverage_log_fh); |
319 | 0 | } |
320 | 0 | MVM_free(encoded_filename); |
321 | 0 | } |
322 | 0 | } |