/home/travis/build/MoarVM/MoarVM/src/profiler/instrument.c
Line | Count | Source (jump to first uncovered line) |
1 | | #include "moar.h" |
2 | | |
3 | | typedef struct { |
4 | | MVMuint32 items; |
5 | | MVMuint32 start; |
6 | | MVMuint32 alloc; |
7 | | |
8 | | MVMProfileCallNode **list; |
9 | | } NodeWorklist; |
10 | | |
11 | 0 | static void add_node(MVMThreadContext *tc, NodeWorklist *list, MVMProfileCallNode *node) { |
12 | 0 | if (list->start + list->items + 1 < list->alloc) { |
13 | 0 | /* Add at the end */ |
14 | 0 | list->items++; |
15 | 0 | list->list[list->start + list->items] = node; |
16 | 0 | } else if (list->start > 0) { |
17 | 0 | /* End reached, add to the start now */ |
18 | 0 | list->start--; |
19 | 0 | list->list[list->start] = node; |
20 | 0 | } else { |
21 | 0 | /* Filled up the whole list. Make it bigger */ |
22 | 0 | list->alloc *= 2; |
23 | 0 | list->list = MVM_realloc(list->list, list->alloc * sizeof(MVMProfileCallNode *)); |
24 | 0 | } |
25 | 0 | } |
26 | | |
27 | 0 | static MVMProfileCallNode *take_node(MVMThreadContext *tc, NodeWorklist *list) { |
28 | 0 | MVMProfileCallNode *result = NULL; |
29 | 0 | if (list->items == 0) { |
30 | 0 | MVM_panic(1, "profiler: tried to take a node from an empty node worklist"); |
31 | 0 | } |
32 | 0 | if (list->start > 0) { |
33 | 0 | result = list->list[list->start]; |
34 | 0 | list->start++; |
35 | 0 | } else { |
36 | 0 | result = list->list[list->start + list->items]; |
37 | 0 | list->items--; |
38 | 0 | } |
39 | 0 | return result; |
40 | 0 | } |
41 | | |
42 | | /* Adds an instruction to log an allocation. */ |
43 | 0 | static void add_allocation_logging_at_location(MVMThreadContext *tc, MVMSpeshGraph *g, MVMSpeshBB *bb, MVMSpeshIns *ins, MVMSpeshIns *location) { |
44 | 0 | MVMSpeshIns *alloc_ins = MVM_spesh_alloc(tc, g, sizeof(MVMSpeshIns)); |
45 | 0 | alloc_ins->info = MVM_op_get_op(MVM_OP_prof_allocated); |
46 | 0 | alloc_ins->operands = MVM_spesh_alloc(tc, g, sizeof(MVMSpeshOperand)); |
47 | 0 | alloc_ins->operands[0] = ins->operands[0]; |
48 | 0 | MVM_spesh_manipulate_insert_ins(tc, bb, location, alloc_ins); |
49 | 0 | } |
50 | | |
51 | 0 | static void add_allocation_logging(MVMThreadContext *tc, MVMSpeshGraph *g, MVMSpeshBB *bb, MVMSpeshIns *ins) { |
52 | 0 | add_allocation_logging_at_location(tc, g, bb, ins, ins); |
53 | 0 | } |
54 | | |
55 | 0 | static void add_nativecall_logging(MVMThreadContext *tc, MVMSpeshGraph *g, MVMSpeshBB *bb, MVMSpeshIns *ins) { |
56 | 0 | MVMSpeshIns *enter_ins = MVM_spesh_alloc(tc, g, sizeof(MVMSpeshIns)); |
57 | 0 | MVMSpeshIns *exit_ins = MVM_spesh_alloc(tc, g, sizeof(MVMSpeshIns)); |
58 | 0 |
|
59 | 0 | enter_ins->info = MVM_op_get_op(MVM_OP_prof_enternative); |
60 | 0 | enter_ins->operands = MVM_spesh_alloc(tc, g, sizeof(MVMSpeshOperand)); |
61 | 0 | enter_ins->operands[0] = ins->operands[2]; |
62 | 0 |
|
63 | 0 | MVM_spesh_manipulate_insert_ins(tc, bb, ins->prev, enter_ins); |
64 | 0 |
|
65 | 0 | exit_ins->info = MVM_op_get_op(MVM_OP_prof_exit); |
66 | 0 |
|
67 | 0 | MVM_spesh_manipulate_insert_ins(tc, bb, ins, exit_ins); |
68 | 0 | } |
69 | | |
70 | 0 | static void instrument_graph(MVMThreadContext *tc, MVMSpeshGraph *g) { |
71 | 0 | /* Insert entry instruction. */ |
72 | 0 | MVMSpeshBB *bb = g->entry->linear_next; |
73 | 0 | MVMSpeshIns *enter_ins = MVM_spesh_alloc(tc, g, sizeof(MVMSpeshIns)); |
74 | 0 | enter_ins->info = MVM_op_get_op(MVM_OP_prof_enter); |
75 | 0 | MVM_spesh_manipulate_insert_ins(tc, bb, NULL, enter_ins); |
76 | 0 |
|
77 | 0 | /* Walk the code and insert profile logging instructions as needed. */ |
78 | 0 | while (bb) { |
79 | 0 | MVMSpeshIns *ins = bb->first_ins; |
80 | 0 | while (ins) { |
81 | 0 | switch (ins->info->opcode) { |
82 | 0 | case MVM_OP_return_i: |
83 | 0 | case MVM_OP_return_n: |
84 | 0 | case MVM_OP_return_s: |
85 | 0 | case MVM_OP_return_o: |
86 | 0 | case MVM_OP_return: { |
87 | 0 | /* Log a normal exit prior to returning. */ |
88 | 0 | MVMSpeshIns *exit_ins = MVM_spesh_alloc(tc, g, sizeof(MVMSpeshIns)); |
89 | 0 | exit_ins->info = MVM_op_get_op(MVM_OP_prof_exit); |
90 | 0 | MVM_spesh_manipulate_insert_ins(tc, bb, ins->prev, exit_ins); |
91 | 0 |
|
92 | 0 | /* If the return instruction is a goto target, move to the |
93 | 0 | * instrumentation instruction. */ |
94 | 0 | if (ins->annotations) { |
95 | 0 | MVMSpeshAnn *ann = ins->annotations; |
96 | 0 | MVMSpeshAnn *prev_ann = NULL; |
97 | 0 | while (ann) { |
98 | 0 | if (ann->type == MVM_SPESH_ANN_FH_GOTO) { |
99 | 0 | if (prev_ann) |
100 | 0 | prev_ann->next = ann->next; |
101 | 0 | else |
102 | 0 | ins->annotations = ann->next; |
103 | 0 | exit_ins->annotations = ann; |
104 | 0 | ann->next = NULL; |
105 | 0 | break; |
106 | 0 | } |
107 | 0 | prev_ann = ann; |
108 | 0 | ann = ann->next; |
109 | 0 | } |
110 | 0 | } |
111 | 0 |
|
112 | 0 | break; |
113 | 0 | } |
114 | 0 | case MVM_OP_invoke_o: |
115 | 0 | case MVM_OP_param_rp_o: |
116 | 0 | case MVM_OP_param_rn_o: |
117 | 0 | case MVM_OP_param_sp: |
118 | 0 | case MVM_OP_param_sn: |
119 | 0 | case MVM_OP_newexception: |
120 | 0 | case MVM_OP_usecapture: |
121 | 0 | case MVM_OP_savecapture: |
122 | 0 | case MVM_OP_takeclosure: |
123 | 0 | case MVM_OP_getattr_o: |
124 | 0 | case MVM_OP_getattrs_o: |
125 | 0 | case MVM_OP_sp_p6ogetvc_o: |
126 | 0 | case MVM_OP_create: |
127 | 0 | case MVM_OP_sp_fastcreate: |
128 | 0 | case MVM_OP_clone: |
129 | 0 | case MVM_OP_box_i: |
130 | 0 | case MVM_OP_box_n: |
131 | 0 | case MVM_OP_box_s: |
132 | 0 | case MVM_OP_iter: |
133 | 0 | case MVM_OP_add_I: |
134 | 0 | case MVM_OP_sub_I: |
135 | 0 | case MVM_OP_mul_I: |
136 | 0 | case MVM_OP_div_I: |
137 | 0 | case MVM_OP_mod_I: |
138 | 0 | case MVM_OP_neg_I: |
139 | 0 | case MVM_OP_abs_I: |
140 | 0 | case MVM_OP_bor_I: |
141 | 0 | case MVM_OP_bxor_I: |
142 | 0 | case MVM_OP_band_I: |
143 | 0 | case MVM_OP_bnot_I: |
144 | 0 | case MVM_OP_blshift_I: |
145 | 0 | case MVM_OP_brshift_I: |
146 | 0 | case MVM_OP_pow_I: |
147 | 0 | case MVM_OP_gcd_I: |
148 | 0 | case MVM_OP_lcm_I: |
149 | 0 | case MVM_OP_expmod_I: |
150 | 0 | case MVM_OP_rand_I: |
151 | 0 | case MVM_OP_coerce_nI: |
152 | 0 | case MVM_OP_coerce_sI: |
153 | 0 | case MVM_OP_radix_I: { |
154 | 0 | add_allocation_logging(tc, g, bb, ins); |
155 | 0 | break; |
156 | 0 | } |
157 | 0 | case MVM_OP_param_op_o: |
158 | 0 | case MVM_OP_param_on_o: { |
159 | 0 | /* These ops jump to a label if they "succeed", so the |
160 | 0 | * allocation logging goes in another bb. */ |
161 | 0 | MVMSpeshBB *target = ins->operands[2].ins_bb; |
162 | 0 | MVMSpeshIns *location = target->first_ins; |
163 | 0 | while (location && location->info->opcode == MVM_SSA_PHI) { |
164 | 0 | location = location->next; |
165 | 0 | } |
166 | 0 | location = location->prev; |
167 | 0 | add_allocation_logging_at_location(tc, g, target, ins, location); |
168 | 0 | break; |
169 | 0 | } |
170 | 0 | case MVM_OP_getlex: |
171 | 0 | case MVM_OP_getlex_no: |
172 | 0 | case MVM_OP_getlexstatic_o: |
173 | 0 | case MVM_OP_getlexperinvtype_o: |
174 | 0 | case MVM_OP_getlexouter: |
175 | 0 | case MVM_OP_getlexrel: |
176 | 0 | case MVM_OP_getlexreldyn: |
177 | 0 | case MVM_OP_getlexrelcaller: |
178 | 0 | case MVM_OP_getlexcaller: |
179 | 0 | { |
180 | 0 | /* We have to check if the target register is actually |
181 | 0 | * an object register. */ |
182 | 0 | if ((g->local_types && g->local_types[ins->operands[0].reg.orig] == MVM_reg_obj) |
183 | 0 | || (!g->local_types && g->sf->body.local_types[ins->operands[0].reg.orig] == MVM_reg_obj)) |
184 | 0 | add_allocation_logging(tc, g, bb, ins); |
185 | 0 | break; |
186 | 0 | } |
187 | 0 | case MVM_OP_getlexref_i: |
188 | 0 | case MVM_OP_getlexref_n: |
189 | 0 | case MVM_OP_getlexref_s: |
190 | 0 | case MVM_OP_getlexref_ni: |
191 | 0 | case MVM_OP_getlexref_nn: |
192 | 0 | case MVM_OP_getlexref_ns: |
193 | 0 | case MVM_OP_atposref_i: |
194 | 0 | case MVM_OP_atposref_n: |
195 | 0 | case MVM_OP_atposref_s: |
196 | 0 | case MVM_OP_getattrref_i: |
197 | 0 | case MVM_OP_getattrref_n: |
198 | 0 | case MVM_OP_getattrref_s: |
199 | 0 | case MVM_OP_getattrsref_i: |
200 | 0 | case MVM_OP_getattrsref_n: |
201 | 0 | case MVM_OP_getattrsref_s: |
202 | 0 | add_allocation_logging(tc, g, bb, ins); |
203 | 0 | break; |
204 | 0 | case MVM_OP_nativecallinvoke: |
205 | 0 | add_nativecall_logging(tc, g, bb, ins); |
206 | 0 | break; |
207 | 0 | default: |
208 | 0 | /* See if it's an allocating extop. */ |
209 | 0 | if (ins->info->opcode == (MVMuint16)-1) { |
210 | 0 | MVMExtOpRecord *extops = g->sf->body.cu->body.extops; |
211 | 0 | MVMuint16 num_extops = g->sf->body.cu->body.num_extops; |
212 | 0 | MVMuint16 i; |
213 | 0 | for (i = 0; i < num_extops; i++) { |
214 | 0 | if (extops[i].info == ins->info) { |
215 | 0 | if (extops[i].allocating && extops[i].info->num_operands >= 1) |
216 | 0 | add_allocation_logging(tc, g, bb, ins); |
217 | 0 | break; |
218 | 0 | } |
219 | 0 | } |
220 | 0 | } |
221 | 0 | break; |
222 | 0 | } |
223 | 0 | ins = ins->next; |
224 | 0 | } |
225 | 0 | bb = bb->linear_next; |
226 | 0 | } |
227 | 0 | } |
228 | | |
229 | | /* Adds instrumented versions of the unspecialized bytecode. */ |
230 | 0 | static void add_instrumentation(MVMThreadContext *tc, MVMStaticFrame *sf) { |
231 | 0 | MVMSpeshCode *sc; |
232 | 0 | MVMStaticFrameInstrumentation *ins; |
233 | 0 | MVMSpeshGraph *sg = MVM_spesh_graph_create(tc, sf, 1, 0); |
234 | 0 | instrument_graph(tc, sg); |
235 | 0 | sc = MVM_spesh_codegen(tc, sg); |
236 | 0 | ins = MVM_calloc(1, sizeof(MVMStaticFrameInstrumentation)); |
237 | 0 | ins->instrumented_bytecode = sc->bytecode; |
238 | 0 | ins->instrumented_handlers = sc->handlers; |
239 | 0 | ins->instrumented_bytecode_size = sc->bytecode_size; |
240 | 0 | ins->uninstrumented_bytecode = sf->body.bytecode; |
241 | 0 | ins->uninstrumented_handlers = sf->body.handlers; |
242 | 0 | ins->uninstrumented_bytecode_size = sf->body.bytecode_size; |
243 | 0 | sf->body.instrumentation = ins; |
244 | 0 | MVM_spesh_graph_destroy(tc, sg); |
245 | 0 | MVM_free(sc); |
246 | 0 | } |
247 | | |
248 | | /* Instruments a static frame for profiling, or uses an existing |
249 | | * instrumentation if it exists. */ |
250 | 0 | void MVM_profile_instrument(MVMThreadContext *tc, MVMStaticFrame *sf) { |
251 | 0 | if (!sf->body.instrumentation || sf->body.bytecode != sf->body.instrumentation->instrumented_bytecode) { |
252 | 0 | /* Handle main, non-specialized, bytecode. */ |
253 | 0 | if (!sf->body.instrumentation) |
254 | 0 | add_instrumentation(tc, sf); |
255 | 0 | sf->body.bytecode = sf->body.instrumentation->instrumented_bytecode; |
256 | 0 | sf->body.handlers = sf->body.instrumentation->instrumented_handlers; |
257 | 0 | sf->body.bytecode_size = sf->body.instrumentation->instrumented_bytecode_size; |
258 | 0 |
|
259 | 0 | /* Throw away any argument guard so we'll never resolve prior |
260 | 0 | * specializations again. */ |
261 | 0 | MVM_spesh_arg_guard_discard(tc, sf); |
262 | 0 | } |
263 | 0 | } |
264 | | |
265 | | /* Ensures we're no longer in instrumented code. */ |
266 | 117k | void MVM_profile_ensure_uninstrumented(MVMThreadContext *tc, MVMStaticFrame *sf) { |
267 | 117k | /* XXX due to multithreading trouble, just turning instrumentation off by |
268 | 117k | * switching bytecode back does not work. Profiling instrumentation is |
269 | 117k | * safe to keep around with only a small performance penalty, and CTW |
270 | 117k | * instrumentation is normally not turned off during run time, |
271 | 117k | * so for now we'll just do nothing. */ |
272 | 117k | } |
273 | | |
274 | | /* Starts instrumented profiling. */ |
275 | 0 | void MVM_profile_instrumented_start(MVMThreadContext *tc, MVMObject *config) { |
276 | 0 | /* Wait for specialization thread to stop working, so it won't trip over |
277 | 0 | * bytecode instrumentation, then enable profiling. */ |
278 | 0 | uv_mutex_lock(&(tc->instance->mutex_spesh_sync)); |
279 | 0 | while (tc->instance->spesh_working != 0) |
280 | 0 | uv_cond_wait(&(tc->instance->cond_spesh_sync), &(tc->instance->mutex_spesh_sync)); |
281 | 0 | tc->instance->profiling = 1; |
282 | 0 | tc->instance->instrumentation_level++; |
283 | 0 | uv_mutex_unlock(&(tc->instance->mutex_spesh_sync)); |
284 | 0 | } |
285 | | |
286 | | /* Simple allocation functions. */ |
287 | 0 | static MVMObject * new_array(MVMThreadContext *tc) { |
288 | 0 | return MVM_repr_alloc_init(tc, MVM_hll_current(tc)->slurpy_array_type); |
289 | 0 | } |
290 | 0 | static MVMObject * new_hash(MVMThreadContext *tc) { |
291 | 0 | return MVM_repr_alloc_init(tc, MVM_hll_current(tc)->slurpy_hash_type); |
292 | 0 | } |
293 | 0 | static MVMObject * box_i(MVMThreadContext *tc, MVMint64 i) { |
294 | 0 | return MVM_repr_box_int(tc, MVM_hll_current(tc)->int_box_type, i); |
295 | 0 | } |
296 | 0 | static MVMObject * box_s(MVMThreadContext *tc, MVMString *s) { |
297 | 0 | return MVM_repr_box_str(tc, MVM_hll_current(tc)->str_box_type, s); |
298 | 0 | } |
299 | 0 | static MVMString * str(MVMThreadContext *tc, const char *buf) { |
300 | 0 | return MVM_string_ascii_decode_nt(tc, tc->instance->VMString, buf); |
301 | 0 | } |
302 | | |
303 | | /* String constants we'll reuse. */ |
304 | | typedef struct { |
305 | | MVMString *total_time; |
306 | | MVMString *call_graph; |
307 | | MVMString *name; |
308 | | MVMString *id; |
309 | | MVMString *file; |
310 | | MVMString *line; |
311 | | MVMString *entries; |
312 | | MVMString *spesh_entries; |
313 | | MVMString *jit_entries; |
314 | | MVMString *inlined_entries; |
315 | | MVMString *inclusive_time; |
316 | | MVMString *exclusive_time; |
317 | | MVMString *callees; |
318 | | MVMString *allocations; |
319 | | MVMString *spesh; |
320 | | MVMString *jit; |
321 | | MVMString *type; |
322 | | MVMString *count; |
323 | | MVMString *gcs; |
324 | | MVMString *time; |
325 | | MVMString *full; |
326 | | MVMString *sequence; |
327 | | MVMString *responsible; |
328 | | MVMString *cleared_bytes; |
329 | | MVMString *retained_bytes; |
330 | | MVMString *promoted_bytes; |
331 | | MVMString *gen2_roots; |
332 | | MVMString *start_time; |
333 | | MVMString *osr; |
334 | | MVMString *deopt_one; |
335 | | MVMString *deopt_all; |
336 | | MVMString *spesh_time; |
337 | | MVMString *thread; |
338 | | MVMString *native_lib; |
339 | | } ProfDumpStrs; |
340 | | |
341 | | typedef struct { |
342 | | MVMThreadContext *tc; |
343 | | ProfDumpStrs *pds; |
344 | | } ProfTcPdsStruct; |
345 | | |
346 | | static MVMObject * dump_call_graph_node(MVMThreadContext *tc, ProfDumpStrs *pds, const MVMProfileCallNode *pcn); |
347 | 0 | static MVMObject * dump_call_graph_node_loop(ProfTcPdsStruct *tcpds, const MVMProfileCallNode *pcn) { |
348 | 0 | MVMuint32 i; |
349 | 0 | MVMObject *node_hash; |
350 | 0 |
|
351 | 0 | node_hash = dump_call_graph_node(tcpds->tc, tcpds->pds, pcn); |
352 | 0 |
|
353 | 0 | /* Visit successors in the call graph, dumping them and working out the |
354 | 0 | * exclusive time. */ |
355 | 0 | if (pcn->num_succ) { |
356 | 0 | MVMObject *callees = new_array(tcpds->tc); |
357 | 0 | MVMuint64 exclusive_time = pcn->total_time; |
358 | 0 | for (i = 0; i < pcn->num_succ; i++) { |
359 | 0 | MVM_repr_push_o(tcpds->tc, callees, |
360 | 0 | dump_call_graph_node_loop(tcpds, pcn->succ[i])); |
361 | 0 | exclusive_time -= pcn->succ[i]->total_time; |
362 | 0 | } |
363 | 0 | MVM_repr_bind_key_o(tcpds->tc, node_hash, tcpds->pds->exclusive_time, |
364 | 0 | box_i(tcpds->tc, exclusive_time / 1000)); |
365 | 0 | MVM_repr_bind_key_o(tcpds->tc, node_hash, tcpds->pds->callees, callees); |
366 | 0 | } |
367 | 0 | else { |
368 | 0 | MVM_repr_bind_key_o(tcpds->tc, node_hash, tcpds->pds->exclusive_time, |
369 | 0 | box_i(tcpds->tc, pcn->total_time / 1000)); |
370 | 0 | } |
371 | 0 |
|
372 | 0 | return node_hash; |
373 | 0 | } |
374 | | |
375 | | /* Dumps a call graph node. */ |
376 | | static MVMObject * dump_call_graph_node(MVMThreadContext *tc, ProfDumpStrs *pds, |
377 | 0 | const MVMProfileCallNode *pcn) { |
378 | 0 | MVMObject *node_hash = new_hash(tc); |
379 | 0 | MVMuint32 i; |
380 | 0 |
|
381 | 0 | /* Let's see if we're dealing with a native call or a regular moar call */ |
382 | 0 | if (pcn->sf) { |
383 | 0 | /* Try to resolve the code filename and line number. */ |
384 | 0 | MVMBytecodeAnnotation *annot = MVM_bytecode_resolve_annotation(tc, |
385 | 0 | &(pcn->sf->body), 0); |
386 | 0 | MVMint32 fshi = annot ? (MVMint32)annot->filename_string_heap_index : -1; |
387 | 0 |
|
388 | 0 | /* Add name of code object. */ |
389 | 0 | MVM_repr_bind_key_o(tc, node_hash, pds->name, |
390 | 0 | box_s(tc, pcn->sf->body.name)); |
391 | 0 |
|
392 | 0 | /* Add line number and file name. */ |
393 | 0 | if (fshi >= 0 && fshi < pcn->sf->body.cu->body.num_strings) |
394 | 0 | MVM_repr_bind_key_o(tc, node_hash, pds->file, |
395 | 0 | box_s(tc, MVM_cu_string(tc, pcn->sf->body.cu, fshi))); |
396 | 0 | else if (pcn->sf->body.cu->body.filename) |
397 | 0 | MVM_repr_bind_key_o(tc, node_hash, pds->file, |
398 | 0 | box_s(tc, pcn->sf->body.cu->body.filename)); |
399 | 0 | else |
400 | 0 | MVM_repr_bind_key_o(tc, node_hash, pds->file, |
401 | 0 | box_s(tc, tc->instance->str_consts.empty)); |
402 | 0 | MVM_repr_bind_key_o(tc, node_hash, pds->line, |
403 | 0 | box_i(tc, annot ? (MVMint32)annot->line_number : -1)); |
404 | 0 | MVM_free(annot); |
405 | 0 |
|
406 | 0 | /* Use static frame memory address to get a unique ID. */ |
407 | 0 | MVM_repr_bind_key_o(tc, node_hash, pds->id, |
408 | 0 | box_i(tc, (MVMint64)pcn->sf)); |
409 | 0 | } else { |
410 | 0 | MVMString *function_name_string = |
411 | 0 | MVM_string_utf8_c8_decode(tc, tc->instance->VMString, |
412 | 0 | pcn->native_target_name, strlen(pcn->native_target_name)); |
413 | 0 |
|
414 | 0 | MVM_repr_bind_key_o(tc, node_hash, pds->name, |
415 | 0 | box_s(tc, function_name_string)); |
416 | 0 | MVM_repr_bind_key_o(tc, node_hash, pds->file, |
417 | 0 | box_s(tc, pds->native_lib)); |
418 | 0 |
|
419 | 0 | MVM_repr_bind_key_o(tc, node_hash, pds->line, |
420 | 0 | box_i(tc, -2)); |
421 | 0 |
|
422 | 0 | /* Use the address of the name string as unique ID. a hack, but oh well. */ |
423 | 0 | MVM_repr_bind_key_o(tc, node_hash, pds->id, |
424 | 0 | box_i(tc, (MVMint64)pcn->native_target_name)); |
425 | 0 | } |
426 | 0 |
|
427 | 0 | /* Entry counts. */ |
428 | 0 | if (pcn->total_entries) |
429 | 0 | MVM_repr_bind_key_o(tc, node_hash, pds->entries, |
430 | 0 | box_i(tc, pcn->total_entries)); |
431 | 0 | if (pcn->specialized_entries) |
432 | 0 | MVM_repr_bind_key_o(tc, node_hash, pds->spesh_entries, |
433 | 0 | box_i(tc, pcn->specialized_entries)); |
434 | 0 | if (pcn->jit_entries) |
435 | 0 | MVM_repr_bind_key_o(tc, node_hash, pds->jit_entries, |
436 | 0 | box_i(tc, pcn->jit_entries)); |
437 | 0 | if (pcn->inlined_entries) |
438 | 0 | MVM_repr_bind_key_o(tc, node_hash, pds->inlined_entries, |
439 | 0 | box_i(tc, pcn->inlined_entries)); |
440 | 0 |
|
441 | 0 | /* Total (inclusive) time. */ |
442 | 0 | MVM_repr_bind_key_o(tc, node_hash, pds->inclusive_time, |
443 | 0 | box_i(tc, pcn->total_time / 1000)); |
444 | 0 |
|
445 | 0 | /* OSR and deopt counts. */ |
446 | 0 | if (pcn->osr_count) |
447 | 0 | MVM_repr_bind_key_o(tc, node_hash, pds->osr, |
448 | 0 | box_i(tc, pcn->osr_count)); |
449 | 0 | if (pcn->deopt_one_count) |
450 | 0 | MVM_repr_bind_key_o(tc, node_hash, pds->deopt_one, |
451 | 0 | box_i(tc, pcn->deopt_one_count)); |
452 | 0 | if (pcn->deopt_all_count) |
453 | 0 | MVM_repr_bind_key_o(tc, node_hash, pds->deopt_all, |
454 | 0 | box_i(tc, pcn->deopt_all_count)); |
455 | 0 |
|
456 | 0 | if (pcn->num_alloc) { |
457 | 0 | /* Emit allocations. */ |
458 | 0 | MVMObject *alloc_list = new_array(tc); |
459 | 0 | MVM_repr_bind_key_o(tc, node_hash, pds->allocations, alloc_list); |
460 | 0 | for (i = 0; i < pcn->num_alloc; i++) { |
461 | 0 | MVMObject *alloc_info = new_hash(tc); |
462 | 0 | MVMProfileAllocationCount *alloc = &pcn->alloc[i]; |
463 | 0 |
|
464 | 0 | MVMObject *type = pcn->alloc[i].type; |
465 | 0 |
|
466 | 0 | MVM_repr_bind_key_o(tc, alloc_info, pds->id, box_i(tc, (MVMint64)type)); |
467 | 0 | MVM_repr_bind_key_o(tc, alloc_info, pds->type, type); |
468 | 0 | if (alloc->allocations_spesh) |
469 | 0 | MVM_repr_bind_key_o(tc, alloc_info, pds->spesh, |
470 | 0 | box_i(tc, alloc->allocations_spesh)); |
471 | 0 | if (alloc->allocations_jit) |
472 | 0 | MVM_repr_bind_key_o(tc, alloc_info, pds->jit, |
473 | 0 | box_i(tc, alloc->allocations_jit)); |
474 | 0 | MVM_repr_bind_key_o(tc, alloc_info, pds->count, |
475 | 0 | box_i(tc, alloc->allocations_interp |
476 | 0 | + alloc->allocations_spesh |
477 | 0 | + alloc->allocations_jit)); |
478 | 0 | MVM_repr_push_o(tc, alloc_list, alloc_info); |
479 | 0 | } |
480 | 0 | } |
481 | 0 |
|
482 | 0 | return node_hash; |
483 | 0 | } |
484 | | |
485 | | /* Dumps data from a single thread. */ |
486 | | static MVMObject * dump_thread_data(MVMThreadContext *tc, ProfDumpStrs *pds, |
487 | | MVMThreadContext *othertc, |
488 | 0 | const MVMProfileThreadData *ptd) { |
489 | 0 | MVMObject *thread_hash = new_hash(tc); |
490 | 0 | MVMObject *thread_gcs = new_array(tc); |
491 | 0 | MVMuint64 absolute_start_time; |
492 | 0 | MVMuint32 i; |
493 | 0 |
|
494 | 0 | ProfTcPdsStruct tcpds; |
495 | 0 |
|
496 | 0 | tcpds.tc = tc; |
497 | 0 | tcpds.pds = pds; |
498 | 0 |
|
499 | 0 | /* Use the main thread's start time for absolute timings */ |
500 | 0 | absolute_start_time = tc->instance->main_thread->prof_data->start_time; |
501 | 0 |
|
502 | 0 | /* Add time. */ |
503 | 0 | MVM_repr_bind_key_o(tc, thread_hash, pds->total_time, |
504 | 0 | box_i(tc, (ptd->end_time - ptd->start_time) / 1000)); |
505 | 0 |
|
506 | 0 | /* Add call graph. */ |
507 | 0 | if (ptd->call_graph) |
508 | 0 | MVM_repr_bind_key_o(tc, thread_hash, pds->call_graph, |
509 | 0 | dump_call_graph_node_loop(&tcpds, ptd->call_graph)); |
510 | 0 |
|
511 | 0 | /* Add GCs. */ |
512 | 0 | for (i = 0; i < ptd->num_gcs; i++) { |
513 | 0 | MVMObject *gc_hash = new_hash(tc); |
514 | 0 | MVM_repr_bind_key_o(tc, gc_hash, pds->time, |
515 | 0 | box_i(tc, ptd->gcs[i].time / 1000)); |
516 | 0 | MVM_repr_bind_key_o(tc, gc_hash, pds->full, |
517 | 0 | box_i(tc, ptd->gcs[i].full)); |
518 | 0 | MVM_repr_bind_key_o(tc, gc_hash, pds->sequence, |
519 | 0 | box_i(tc, ptd->gcs[i].gc_seq_num - 1)); |
520 | 0 | MVM_repr_bind_key_o(tc, gc_hash, pds->responsible, |
521 | 0 | box_i(tc, ptd->gcs[i].responsible)); |
522 | 0 | MVM_repr_bind_key_o(tc, gc_hash, pds->cleared_bytes, |
523 | 0 | box_i(tc, ptd->gcs[i].cleared_bytes)); |
524 | 0 | MVM_repr_bind_key_o(tc, gc_hash, pds->retained_bytes, |
525 | 0 | box_i(tc, ptd->gcs[i].retained_bytes)); |
526 | 0 | MVM_repr_bind_key_o(tc, gc_hash, pds->promoted_bytes, |
527 | 0 | box_i(tc, ptd->gcs[i].promoted_bytes)); |
528 | 0 | MVM_repr_bind_key_o(tc, gc_hash, pds->gen2_roots, |
529 | 0 | box_i(tc, ptd->gcs[i].num_gen2roots)); |
530 | 0 | MVM_repr_bind_key_o(tc, gc_hash, pds->start_time, |
531 | 0 | box_i(tc, (ptd->gcs[i].abstime - absolute_start_time) / 1000)); |
532 | 0 | MVM_repr_push_o(tc, thread_gcs, gc_hash); |
533 | 0 | } |
534 | 0 | MVM_repr_bind_key_o(tc, thread_hash, pds->gcs, thread_gcs); |
535 | 0 |
|
536 | 0 | /* Add spesh time. */ |
537 | 0 | MVM_repr_bind_key_o(tc, thread_hash, pds->spesh_time, |
538 | 0 | box_i(tc, ptd->spesh_time / 1000)); |
539 | 0 |
|
540 | 0 | /* Add thread id. */ |
541 | 0 | MVM_repr_bind_key_o(tc, thread_hash, pds->thread, |
542 | 0 | box_i(tc, othertc->thread_id)); |
543 | 0 |
|
544 | 0 | return thread_hash; |
545 | 0 | } |
546 | | |
547 | 338 | void MVM_profile_dump_instrumented_data(MVMThreadContext *tc) { |
548 | 338 | if (tc->prof_data && tc->prof_data->collected_data) { |
549 | 0 | ProfDumpStrs pds; |
550 | 0 | MVMThread *thread; |
551 | 0 |
|
552 | 0 | /* We'll allocate the data in gen2, but as we want to keep it, but to be |
553 | 0 | * sure we don't trigger a GC run. */ |
554 | 0 | MVM_gc_allocate_gen2_default_set(tc); |
555 | 0 |
|
556 | 0 | /* Some string constants to re-use. */ |
557 | 0 | pds.total_time = str(tc, "total_time"); |
558 | 0 | pds.call_graph = str(tc, "call_graph"); |
559 | 0 | pds.name = str(tc, "name"); |
560 | 0 | pds.id = str(tc, "id"); |
561 | 0 | pds.file = str(tc, "file"); |
562 | 0 | pds.line = str(tc, "line"); |
563 | 0 | pds.entries = str(tc, "entries"); |
564 | 0 | pds.spesh_entries = str(tc, "spesh_entries"); |
565 | 0 | pds.jit_entries = str(tc, "jit_entries"); |
566 | 0 | pds.inlined_entries = str(tc, "inlined_entries"); |
567 | 0 | pds.inclusive_time = str(tc, "inclusive_time"); |
568 | 0 | pds.exclusive_time = str(tc, "exclusive_time"); |
569 | 0 | pds.callees = str(tc, "callees"); |
570 | 0 | pds.allocations = str(tc, "allocations"); |
571 | 0 | pds.type = str(tc, "type"); |
572 | 0 | pds.count = str(tc, "count"); |
573 | 0 | pds.spesh = str(tc, "spesh"); |
574 | 0 | pds.jit = str(tc, "jit"); |
575 | 0 | pds.gcs = str(tc, "gcs"); |
576 | 0 | pds.time = str(tc, "time"); |
577 | 0 | pds.full = str(tc, "full"); |
578 | 0 | pds.sequence = str(tc, "sequence"); |
579 | 0 | pds.responsible = str(tc, "responsible"); |
580 | 0 | pds.cleared_bytes = str(tc, "cleared_bytes"); |
581 | 0 | pds.retained_bytes = str(tc, "retained_bytes"); |
582 | 0 | pds.promoted_bytes = str(tc, "promoted_bytes"); |
583 | 0 | pds.gen2_roots = str(tc, "gen2_roots"); |
584 | 0 | pds.start_time = str(tc, "start_time"); |
585 | 0 | pds.osr = str(tc, "osr"); |
586 | 0 | pds.deopt_one = str(tc, "deopt_one"); |
587 | 0 | pds.deopt_all = str(tc, "deopt_all"); |
588 | 0 | pds.spesh_time = str(tc, "spesh_time"); |
589 | 0 | pds.thread = str(tc, "thread"); |
590 | 0 | pds.native_lib = str(tc, "native library"); |
591 | 0 |
|
592 | 0 | /* Record end time. */ |
593 | 0 | tc->prof_data->end_time = uv_hrtime(); |
594 | 0 |
|
595 | 0 | MVM_repr_push_o(tc, tc->prof_data->collected_data, dump_thread_data(tc, &pds, tc, tc->prof_data)); |
596 | 0 | while (tc->prof_data->current_call) |
597 | 0 | MVM_profile_log_exit(tc); |
598 | 0 |
|
599 | 0 | /* Get all thread's data */ |
600 | 0 | thread = tc->instance->threads; |
601 | 0 |
|
602 | 0 | while (thread) { |
603 | 0 | MVMThreadContext *othertc = thread->body.tc; |
604 | 0 | /* Check for othertc to exist because joining threads nulls out |
605 | 0 | * the tc entry in the thread object. */ |
606 | 0 | if (othertc && othertc->prof_data && othertc != tc) { |
607 | 0 | /* If we have any call frames still on the profile stack, exit them. */ |
608 | 0 | while (othertc->prof_data->current_call) |
609 | 0 | MVM_profile_log_exit(othertc); |
610 | 0 |
|
611 | 0 | /* Record end time. */ |
612 | 0 | othertc->prof_data->end_time = uv_hrtime(); |
613 | 0 |
|
614 | 0 | MVM_gc_allocate_gen2_default_set(othertc); |
615 | 0 | MVM_repr_push_o(tc, tc->prof_data->collected_data, dump_thread_data(tc, &pds, othertc, othertc->prof_data)); |
616 | 0 | MVM_gc_allocate_gen2_default_clear(othertc); |
617 | 0 | } |
618 | 0 | thread = thread->body.next; |
619 | 0 | } |
620 | 0 | MVM_gc_allocate_gen2_default_clear(tc); |
621 | 0 | } |
622 | 338 | } |
623 | | |
624 | | /* Dumps data from all threads into an array of per-thread data. */ |
625 | 0 | static MVMObject * dump_data(MVMThreadContext *tc) { |
626 | 0 | MVMObject *collected_data; |
627 | 0 |
|
628 | 0 | /* Build up threads array. */ |
629 | 0 | /* XXX Only main thread for now. */ |
630 | 0 |
|
631 | 0 | tc->prof_data->collected_data = new_array(tc); |
632 | 0 |
|
633 | 0 | /* We rely on the GC orchestration to stop all threads and the |
634 | 0 | * "main" gc thread to dump all thread data for us */ |
635 | 0 | MVM_gc_enter_from_allocator(tc); |
636 | 0 |
|
637 | 0 | collected_data = tc->prof_data->collected_data; |
638 | 0 | tc->prof_data->collected_data = NULL; |
639 | 0 |
|
640 | 0 | return collected_data; |
641 | 0 | } |
642 | | |
643 | | /* Ends profiling, builds the result data structure, and returns it. */ |
644 | 0 | MVMObject * MVM_profile_instrumented_end(MVMThreadContext *tc) { |
645 | 0 |
|
646 | 0 | /* Disable profiling. */ |
647 | 0 | uv_mutex_lock(&(tc->instance->mutex_spesh_sync)); |
648 | 0 | while (tc->instance->spesh_working != 0) |
649 | 0 | uv_cond_wait(&(tc->instance->cond_spesh_sync), &(tc->instance->mutex_spesh_sync)); |
650 | 0 | tc->instance->profiling = 0; |
651 | 0 | tc->instance->instrumentation_level++; |
652 | 0 | uv_mutex_unlock(&(tc->instance->mutex_spesh_sync)); |
653 | 0 |
|
654 | 0 | /* Build and return result data structure. */ |
655 | 0 | return dump_data(tc); |
656 | 0 | } |
657 | | |
658 | | |
659 | | /* Marks objects held in the profiling graph. */ |
660 | 0 | static void mark_call_graph_node(MVMThreadContext *tc, MVMProfileCallNode *node, NodeWorklist *nodelist, MVMGCWorklist *worklist) { |
661 | 0 | MVMuint32 i; |
662 | 0 | MVM_gc_worklist_add(tc, worklist, &(node->sf)); |
663 | 0 | for (i = 0; i < node->num_alloc; i++) |
664 | 0 | MVM_gc_worklist_add(tc, worklist, &(node->alloc[i].type)); |
665 | 0 | for (i = 0; i < node->num_succ; i++) |
666 | 0 | add_node(tc, nodelist, node->succ[i]); |
667 | 0 | } |
668 | 754 | void MVM_profile_instrumented_mark_data(MVMThreadContext *tc, MVMGCWorklist *worklist) { |
669 | 754 | if (tc->prof_data) { |
670 | 0 | /* Allocate our worklist on the stack. */ |
671 | 0 | NodeWorklist nodelist; |
672 | 0 | nodelist.items = 0; |
673 | 0 | nodelist.start = 0; |
674 | 0 | nodelist.alloc = 256; |
675 | 0 | nodelist.list = MVM_malloc(nodelist.alloc * sizeof(MVMProfileCallNode *)); |
676 | 0 |
|
677 | 0 | add_node(tc, &nodelist, tc->prof_data->call_graph); |
678 | 0 |
|
679 | 0 | while (nodelist.items) { |
680 | 0 | MVMProfileCallNode *node = take_node(tc, &nodelist); |
681 | 0 | if (node) |
682 | 0 | mark_call_graph_node(tc, node, &nodelist, worklist); |
683 | 0 | } |
684 | 0 |
|
685 | 0 | MVM_gc_worklist_add(tc, worklist, &(tc->prof_data->collected_data)); |
686 | 0 |
|
687 | 0 | MVM_free(nodelist.list); |
688 | 0 | } |
689 | 754 | } |