/home/travis/build/MoarVM/MoarVM/src/profiler/instrument.c
Line | Count | Source (jump to first uncovered line) |
1 | | #include "moar.h" |
2 | | |
3 | | /* Adds an instruction to log an allocation. */ |
4 | 0 | static void add_allocation_logging(MVMThreadContext *tc, MVMSpeshGraph *g, MVMSpeshBB *bb, MVMSpeshIns *ins) { |
5 | 0 | MVMSpeshIns *alloc_ins = MVM_spesh_alloc(tc, g, sizeof(MVMSpeshIns)); |
6 | 0 | alloc_ins->info = MVM_op_get_op(MVM_OP_prof_allocated); |
7 | 0 | alloc_ins->operands = MVM_spesh_alloc(tc, g, sizeof(MVMSpeshOperand)); |
8 | 0 | alloc_ins->operands[0] = ins->operands[0]; |
9 | 0 | MVM_spesh_manipulate_insert_ins(tc, bb, ins, alloc_ins); |
10 | 0 | } |
11 | | |
12 | 0 | static void add_nativecall_logging(MVMThreadContext *tc, MVMSpeshGraph *g, MVMSpeshBB *bb, MVMSpeshIns *ins) { |
13 | 0 | MVMSpeshIns *enter_ins = MVM_spesh_alloc(tc, g, sizeof(MVMSpeshIns)); |
14 | 0 | MVMSpeshIns *exit_ins = MVM_spesh_alloc(tc, g, sizeof(MVMSpeshIns)); |
15 | 0 |
|
16 | 0 | enter_ins->info = MVM_op_get_op(MVM_OP_prof_enternative); |
17 | 0 | enter_ins->operands = MVM_spesh_alloc(tc, g, sizeof(MVMSpeshOperand)); |
18 | 0 | enter_ins->operands[0] = ins->operands[2]; |
19 | 0 |
|
20 | 0 | MVM_spesh_manipulate_insert_ins(tc, bb, ins->prev, enter_ins); |
21 | 0 |
|
22 | 0 | exit_ins->info = MVM_op_get_op(MVM_OP_prof_exit); |
23 | 0 |
|
24 | 0 | MVM_spesh_manipulate_insert_ins(tc, bb, ins, exit_ins); |
25 | 0 | } |
26 | | |
27 | 0 | static void instrument_graph(MVMThreadContext *tc, MVMSpeshGraph *g) { |
28 | 0 | /* Insert entry instruction. */ |
29 | 0 | MVMSpeshBB *bb = g->entry->linear_next; |
30 | 0 | MVMSpeshIns *enter_ins = MVM_spesh_alloc(tc, g, sizeof(MVMSpeshIns)); |
31 | 0 | enter_ins->info = MVM_op_get_op(MVM_OP_prof_enter); |
32 | 0 | MVM_spesh_manipulate_insert_ins(tc, bb, NULL, enter_ins); |
33 | 0 |
|
34 | 0 | /* Walk the code and insert profile logging instructions as needed. */ |
35 | 0 | while (bb) { |
36 | 0 | MVMSpeshIns *ins = bb->first_ins; |
37 | 0 | while (ins) { |
38 | 0 | switch (ins->info->opcode) { |
39 | 0 | case MVM_OP_return_i: |
40 | 0 | case MVM_OP_return_n: |
41 | 0 | case MVM_OP_return_s: |
42 | 0 | case MVM_OP_return_o: |
43 | 0 | case MVM_OP_return: { |
44 | 0 | /* Log a normal exit prior to returning. */ |
45 | 0 | MVMSpeshIns *exit_ins = MVM_spesh_alloc(tc, g, sizeof(MVMSpeshIns)); |
46 | 0 | exit_ins->info = MVM_op_get_op(MVM_OP_prof_exit); |
47 | 0 | MVM_spesh_manipulate_insert_ins(tc, bb, ins->prev, exit_ins); |
48 | 0 |
|
49 | 0 | /* If the return instruction is a goto target, move to the |
50 | 0 | * instrumentation instruction. */ |
51 | 0 | if (ins->annotations) { |
52 | 0 | MVMSpeshAnn *ann = ins->annotations; |
53 | 0 | MVMSpeshAnn *prev_ann = NULL; |
54 | 0 | while (ann) { |
55 | 0 | if (ann->type == MVM_SPESH_ANN_FH_GOTO) { |
56 | 0 | if (prev_ann) |
57 | 0 | prev_ann->next = ann->next; |
58 | 0 | else |
59 | 0 | ins->annotations = ann->next; |
60 | 0 | exit_ins->annotations = ann; |
61 | 0 | ann->next = NULL; |
62 | 0 | break; |
63 | 0 | } |
64 | 0 | prev_ann = ann; |
65 | 0 | ann = ann->next; |
66 | 0 | } |
67 | 0 | } |
68 | 0 |
|
69 | 0 | break; |
70 | 0 | } |
71 | 0 | case MVM_OP_invoke_o: |
72 | 0 | case MVM_OP_param_rp_o: |
73 | 0 | case MVM_OP_param_op_o: |
74 | 0 | case MVM_OP_param_rn_o: |
75 | 0 | case MVM_OP_param_on_o: |
76 | 0 | case MVM_OP_param_sp: |
77 | 0 | case MVM_OP_param_sn: |
78 | 0 | case MVM_OP_newexception: |
79 | 0 | case MVM_OP_usecapture: |
80 | 0 | case MVM_OP_savecapture: |
81 | 0 | case MVM_OP_takeclosure: |
82 | 0 | case MVM_OP_getattr_o: |
83 | 0 | case MVM_OP_getattrs_o: |
84 | 0 | case MVM_OP_sp_p6ogetvc_o: |
85 | 0 | case MVM_OP_create: |
86 | 0 | case MVM_OP_sp_fastcreate: |
87 | 0 | case MVM_OP_clone: |
88 | 0 | case MVM_OP_box_i: |
89 | 0 | case MVM_OP_box_n: |
90 | 0 | case MVM_OP_box_s: |
91 | 0 | case MVM_OP_iter: |
92 | 0 | case MVM_OP_add_I: |
93 | 0 | case MVM_OP_sub_I: |
94 | 0 | case MVM_OP_mul_I: |
95 | 0 | case MVM_OP_div_I: |
96 | 0 | case MVM_OP_mod_I: |
97 | 0 | case MVM_OP_neg_I: |
98 | 0 | case MVM_OP_abs_I: |
99 | 0 | case MVM_OP_bor_I: |
100 | 0 | case MVM_OP_bxor_I: |
101 | 0 | case MVM_OP_band_I: |
102 | 0 | case MVM_OP_bnot_I: |
103 | 0 | case MVM_OP_blshift_I: |
104 | 0 | case MVM_OP_brshift_I: |
105 | 0 | case MVM_OP_pow_I: |
106 | 0 | case MVM_OP_gcd_I: |
107 | 0 | case MVM_OP_lcm_I: |
108 | 0 | case MVM_OP_expmod_I: |
109 | 0 | case MVM_OP_rand_I: |
110 | 0 | case MVM_OP_coerce_nI: |
111 | 0 | case MVM_OP_coerce_sI: |
112 | 0 | case MVM_OP_radix_I: { |
113 | 0 | add_allocation_logging(tc, g, bb, ins); |
114 | 0 | break; |
115 | 0 | } |
116 | 0 | case MVM_OP_getlex: |
117 | 0 | case MVM_OP_getlex_no: |
118 | 0 | case MVM_OP_getlexstatic_o: |
119 | 0 | case MVM_OP_getlexperinvtype_o: |
120 | 0 | case MVM_OP_getlexouter: |
121 | 0 | case MVM_OP_getlexrel: |
122 | 0 | case MVM_OP_getlexreldyn: |
123 | 0 | case MVM_OP_getlexrelcaller: |
124 | 0 | case MVM_OP_getlexcaller: |
125 | 0 | { |
126 | 0 | /* We have to check if the target register is actually |
127 | 0 | * an object register. */ |
128 | 0 | if ((g->local_types && g->local_types[ins->operands[0].reg.orig] == MVM_reg_obj) |
129 | 0 | || (!g->local_types && g->sf->body.local_types[ins->operands[0].reg.orig] == MVM_reg_obj)) |
130 | 0 | add_allocation_logging(tc, g, bb, ins); |
131 | 0 | break; |
132 | 0 | } |
133 | 0 | case MVM_OP_getlexref_i: |
134 | 0 | case MVM_OP_getlexref_n: |
135 | 0 | case MVM_OP_getlexref_s: |
136 | 0 | case MVM_OP_getlexref_ni: |
137 | 0 | case MVM_OP_getlexref_nn: |
138 | 0 | case MVM_OP_getlexref_ns: |
139 | 0 | case MVM_OP_atposref_i: |
140 | 0 | case MVM_OP_atposref_n: |
141 | 0 | case MVM_OP_atposref_s: |
142 | 0 | case MVM_OP_getattrref_i: |
143 | 0 | case MVM_OP_getattrref_n: |
144 | 0 | case MVM_OP_getattrref_s: |
145 | 0 | case MVM_OP_getattrsref_i: |
146 | 0 | case MVM_OP_getattrsref_n: |
147 | 0 | case MVM_OP_getattrsref_s: |
148 | 0 | add_allocation_logging(tc, g, bb, ins); |
149 | 0 | break; |
150 | 0 | case MVM_OP_nativecallinvoke: |
151 | 0 | add_nativecall_logging(tc, g, bb, ins); |
152 | 0 | break; |
153 | 0 | default: |
154 | 0 | /* See if it's an allocating extop. */ |
155 | 0 | if (ins->info->opcode == (MVMuint16)-1) { |
156 | 0 | MVMExtOpRecord *extops = g->sf->body.cu->body.extops; |
157 | 0 | MVMuint16 num_extops = g->sf->body.cu->body.num_extops; |
158 | 0 | MVMuint16 i; |
159 | 0 | for (i = 0; i < num_extops; i++) { |
160 | 0 | if (extops[i].info == ins->info) { |
161 | 0 | if (extops[i].allocating && extops[i].info->num_operands >= 1) |
162 | 0 | add_allocation_logging(tc, g, bb, ins); |
163 | 0 | break; |
164 | 0 | } |
165 | 0 | } |
166 | 0 | } |
167 | 0 | break; |
168 | 0 | } |
169 | 0 | ins = ins->next; |
170 | 0 | } |
171 | 0 | bb = bb->linear_next; |
172 | 0 | } |
173 | 0 | } |
174 | | |
175 | | /* Adds instrumented versions of the unspecialized bytecode. */ |
176 | 0 | static void add_instrumentation(MVMThreadContext *tc, MVMStaticFrame *sf) { |
177 | 0 | MVMSpeshCode *sc; |
178 | 0 | MVMStaticFrameInstrumentation *ins; |
179 | 0 | MVMSpeshGraph *sg = MVM_spesh_graph_create(tc, sf, 1, 0); |
180 | 0 | instrument_graph(tc, sg); |
181 | 0 | sc = MVM_spesh_codegen(tc, sg); |
182 | 0 | ins = MVM_calloc(1, sizeof(MVMStaticFrameInstrumentation)); |
183 | 0 | ins->instrumented_bytecode = sc->bytecode; |
184 | 0 | ins->instrumented_handlers = sc->handlers; |
185 | 0 | ins->instrumented_bytecode_size = sc->bytecode_size; |
186 | 0 | ins->uninstrumented_bytecode = sf->body.bytecode; |
187 | 0 | ins->uninstrumented_handlers = sf->body.handlers; |
188 | 0 | ins->uninstrumented_bytecode_size = sf->body.bytecode_size; |
189 | 0 | sf->body.instrumentation = ins; |
190 | 0 | MVM_spesh_graph_destroy(tc, sg); |
191 | 0 | MVM_free(sc); |
192 | 0 | } |
193 | | |
194 | | /* Instruments a static frame for profiling, or uses an existing |
195 | | * instrumentation if it exists. */ |
196 | 0 | void MVM_profile_instrument(MVMThreadContext *tc, MVMStaticFrame *sf) { |
197 | 0 | if (!sf->body.instrumentation || sf->body.bytecode != sf->body.instrumentation->instrumented_bytecode) { |
198 | 0 | /* Handle main, non-specialized, bytecode. */ |
199 | 0 | if (!sf->body.instrumentation) |
200 | 0 | add_instrumentation(tc, sf); |
201 | 0 | sf->body.bytecode = sf->body.instrumentation->instrumented_bytecode; |
202 | 0 | sf->body.handlers = sf->body.instrumentation->instrumented_handlers; |
203 | 0 | sf->body.bytecode_size = sf->body.instrumentation->instrumented_bytecode_size; |
204 | 0 |
|
205 | 0 | /* Throw away any specializations; we'll need to reproduce them as |
206 | 0 | * instrumented versions. */ |
207 | 0 | sf->body.num_spesh_candidates = 0; |
208 | 0 | sf->body.spesh_candidates = NULL; |
209 | 0 | } |
210 | 0 | } |
211 | | |
212 | | /* Ensures we're no longer in instrumented code. */ |
213 | 102k | void MVM_profile_ensure_uninstrumented(MVMThreadContext *tc, MVMStaticFrame *sf) { |
214 | 102k | if (sf->body.instrumentation && sf->body.bytecode == sf->body.instrumentation->instrumented_bytecode) { |
215 | 0 | /* Switch to uninstrumented code. */ |
216 | 0 | sf->body.bytecode = sf->body.instrumentation->uninstrumented_bytecode; |
217 | 0 | sf->body.handlers = sf->body.instrumentation->uninstrumented_handlers; |
218 | 0 | sf->body.bytecode_size = sf->body.instrumentation->uninstrumented_bytecode_size; |
219 | 0 |
|
220 | 0 | /* Throw away specializations, which may also be instrumented. */ |
221 | 0 | sf->body.num_spesh_candidates = 0; |
222 | 0 | sf->body.spesh_candidates = NULL; |
223 | 0 |
|
224 | 0 | /* XXX For now, due to bugs, disable spesh here. */ |
225 | 0 | tc->instance->spesh_enabled = 0; |
226 | 0 | } |
227 | 102k | } |
228 | | |
229 | | /* Starts instrumted profiling. */ |
230 | 0 | void MVM_profile_instrumented_start(MVMThreadContext *tc, MVMObject *config) { |
231 | 0 | /* Enable profiling. */ |
232 | 0 | tc->instance->profiling = 1; |
233 | 0 | tc->instance->instrumentation_level++; |
234 | 0 | } |
235 | | |
236 | | /* Simple allocation functions. */ |
237 | 0 | static MVMObject * new_array(MVMThreadContext *tc) { |
238 | 0 | return MVM_repr_alloc_init(tc, MVM_hll_current(tc)->slurpy_array_type); |
239 | 0 | } |
240 | 0 | static MVMObject * new_hash(MVMThreadContext *tc) { |
241 | 0 | return MVM_repr_alloc_init(tc, MVM_hll_current(tc)->slurpy_hash_type); |
242 | 0 | } |
243 | 0 | static MVMObject * box_i(MVMThreadContext *tc, MVMint64 i) { |
244 | 0 | return MVM_repr_box_int(tc, MVM_hll_current(tc)->int_box_type, i); |
245 | 0 | } |
246 | 0 | static MVMObject * box_s(MVMThreadContext *tc, MVMString *s) { |
247 | 0 | return MVM_repr_box_str(tc, MVM_hll_current(tc)->str_box_type, s); |
248 | 0 | } |
249 | 0 | static MVMString * str(MVMThreadContext *tc, const char *buf) { |
250 | 0 | return MVM_string_ascii_decode_nt(tc, tc->instance->VMString, buf); |
251 | 0 | } |
252 | | |
253 | | /* String constants we'll reuse. */ |
254 | | typedef struct { |
255 | | MVMString *total_time; |
256 | | MVMString *call_graph; |
257 | | MVMString *name; |
258 | | MVMString *id; |
259 | | MVMString *file; |
260 | | MVMString *line; |
261 | | MVMString *entries; |
262 | | MVMString *spesh_entries; |
263 | | MVMString *jit_entries; |
264 | | MVMString *inlined_entries; |
265 | | MVMString *inclusive_time; |
266 | | MVMString *exclusive_time; |
267 | | MVMString *callees; |
268 | | MVMString *allocations; |
269 | | MVMString *spesh; |
270 | | MVMString *jit; |
271 | | MVMString *type; |
272 | | MVMString *count; |
273 | | MVMString *gcs; |
274 | | MVMString *time; |
275 | | MVMString *full; |
276 | | MVMString *cleared_bytes; |
277 | | MVMString *retained_bytes; |
278 | | MVMString *promoted_bytes; |
279 | | MVMString *gen2_roots; |
280 | | MVMString *osr; |
281 | | MVMString *deopt_one; |
282 | | MVMString *deopt_all; |
283 | | MVMString *spesh_time; |
284 | | MVMString *native_lib; |
285 | | } ProfDumpStrs; |
286 | | |
287 | | /* Dumps a call graph node. */ |
288 | | static MVMObject * dump_call_graph_node(MVMThreadContext *tc, ProfDumpStrs *pds, |
289 | 0 | const MVMProfileCallNode *pcn) { |
290 | 0 | MVMObject *node_hash = new_hash(tc); |
291 | 0 | MVMuint32 i; |
292 | 0 |
|
293 | 0 | /* Let's see if we're dealing with a native call or a regular moar call */ |
294 | 0 | if (pcn->sf) { |
295 | 0 | /* Try to resolve the code filename and line number. */ |
296 | 0 | MVMBytecodeAnnotation *annot = MVM_bytecode_resolve_annotation(tc, |
297 | 0 | &(pcn->sf->body), 0); |
298 | 0 | MVMint32 fshi = annot ? (MVMint32)annot->filename_string_heap_index : -1; |
299 | 0 |
|
300 | 0 | /* Add name of code object. */ |
301 | 0 | MVM_repr_bind_key_o(tc, node_hash, pds->name, |
302 | 0 | box_s(tc, pcn->sf->body.name)); |
303 | 0 |
|
304 | 0 | /* Add line number and file name. */ |
305 | 0 | if (fshi >= 0 && fshi < pcn->sf->body.cu->body.num_strings) |
306 | 0 | MVM_repr_bind_key_o(tc, node_hash, pds->file, |
307 | 0 | box_s(tc, MVM_cu_string(tc, pcn->sf->body.cu, fshi))); |
308 | 0 | else if (pcn->sf->body.cu->body.filename) |
309 | 0 | MVM_repr_bind_key_o(tc, node_hash, pds->file, |
310 | 0 | box_s(tc, pcn->sf->body.cu->body.filename)); |
311 | 0 | else |
312 | 0 | MVM_repr_bind_key_o(tc, node_hash, pds->file, |
313 | 0 | box_s(tc, tc->instance->str_consts.empty)); |
314 | 0 | MVM_repr_bind_key_o(tc, node_hash, pds->line, |
315 | 0 | box_i(tc, annot ? (MVMint32)annot->line_number : -1)); |
316 | 0 | MVM_free(annot); |
317 | 0 |
|
318 | 0 | /* Use static frame memory address to get a unique ID. */ |
319 | 0 | MVM_repr_bind_key_o(tc, node_hash, pds->id, |
320 | 0 | box_i(tc, (MVMint64)pcn->sf)); |
321 | 0 | } else { |
322 | 0 | MVMString *function_name_string = |
323 | 0 | MVM_string_utf8_c8_decode(tc, tc->instance->VMString, |
324 | 0 | pcn->native_target_name, strlen(pcn->native_target_name)); |
325 | 0 |
|
326 | 0 | MVM_repr_bind_key_o(tc, node_hash, pds->name, |
327 | 0 | box_s(tc, function_name_string)); |
328 | 0 | MVM_repr_bind_key_o(tc, node_hash, pds->file, |
329 | 0 | box_s(tc, pds->native_lib)); |
330 | 0 |
|
331 | 0 | MVM_repr_bind_key_o(tc, node_hash, pds->line, |
332 | 0 | box_i(tc, -2)); |
333 | 0 |
|
334 | 0 | /* Use the address of the name string as unique ID. a hack, but oh well. */ |
335 | 0 | MVM_repr_bind_key_o(tc, node_hash, pds->id, |
336 | 0 | box_i(tc, (MVMint64)pcn->native_target_name)); |
337 | 0 | } |
338 | 0 |
|
339 | 0 | /* Entry counts. */ |
340 | 0 | if (pcn->total_entries) |
341 | 0 | MVM_repr_bind_key_o(tc, node_hash, pds->entries, |
342 | 0 | box_i(tc, pcn->total_entries)); |
343 | 0 | if (pcn->specialized_entries) |
344 | 0 | MVM_repr_bind_key_o(tc, node_hash, pds->spesh_entries, |
345 | 0 | box_i(tc, pcn->specialized_entries)); |
346 | 0 | if (pcn->jit_entries) |
347 | 0 | MVM_repr_bind_key_o(tc, node_hash, pds->jit_entries, |
348 | 0 | box_i(tc, pcn->jit_entries)); |
349 | 0 | if (pcn->inlined_entries) |
350 | 0 | MVM_repr_bind_key_o(tc, node_hash, pds->inlined_entries, |
351 | 0 | box_i(tc, pcn->inlined_entries)); |
352 | 0 |
|
353 | 0 | /* Total (inclusive) time. */ |
354 | 0 | MVM_repr_bind_key_o(tc, node_hash, pds->inclusive_time, |
355 | 0 | box_i(tc, pcn->total_time / 1000)); |
356 | 0 |
|
357 | 0 | /* OSR and deopt counts. */ |
358 | 0 | if (pcn->osr_count) |
359 | 0 | MVM_repr_bind_key_o(tc, node_hash, pds->osr, |
360 | 0 | box_i(tc, pcn->osr_count)); |
361 | 0 | if (pcn->deopt_one_count) |
362 | 0 | MVM_repr_bind_key_o(tc, node_hash, pds->deopt_one, |
363 | 0 | box_i(tc, pcn->deopt_one_count)); |
364 | 0 | if (pcn->deopt_all_count) |
365 | 0 | MVM_repr_bind_key_o(tc, node_hash, pds->deopt_all, |
366 | 0 | box_i(tc, pcn->deopt_all_count)); |
367 | 0 |
|
368 | 0 | /* Visit successors in the call graph, dumping them and working out the |
369 | 0 | * exclusive time. */ |
370 | 0 | if (pcn->num_succ) { |
371 | 0 | MVMObject *callees = new_array(tc); |
372 | 0 | MVMuint64 exclusive_time = pcn->total_time; |
373 | 0 | for (i = 0; i < pcn->num_succ; i++) { |
374 | 0 | MVM_repr_push_o(tc, callees, |
375 | 0 | dump_call_graph_node(tc, pds, pcn->succ[i])); |
376 | 0 | exclusive_time -= pcn->succ[i]->total_time; |
377 | 0 | } |
378 | 0 | MVM_repr_bind_key_o(tc, node_hash, pds->exclusive_time, |
379 | 0 | box_i(tc, exclusive_time / 1000)); |
380 | 0 | MVM_repr_bind_key_o(tc, node_hash, pds->callees, callees); |
381 | 0 | } |
382 | 0 | else { |
383 | 0 | MVM_repr_bind_key_o(tc, node_hash, pds->exclusive_time, |
384 | 0 | box_i(tc, pcn->total_time / 1000)); |
385 | 0 | } |
386 | 0 |
|
387 | 0 | if (pcn->num_alloc) { |
388 | 0 | /* Emit allocations. */ |
389 | 0 | MVMObject *alloc_list = new_array(tc); |
390 | 0 | MVM_repr_bind_key_o(tc, node_hash, pds->allocations, alloc_list); |
391 | 0 | for (i = 0; i < pcn->num_alloc; i++) { |
392 | 0 | MVMObject *alloc_info = new_hash(tc); |
393 | 0 | MVMProfileAllocationCount *alloc = &pcn->alloc[i]; |
394 | 0 |
|
395 | 0 | MVMObject *type = pcn->alloc[i].type; |
396 | 0 |
|
397 | 0 | MVM_repr_bind_key_o(tc, alloc_info, pds->id, box_i(tc, (MVMint64)type)); |
398 | 0 | MVM_repr_bind_key_o(tc, alloc_info, pds->type, type); |
399 | 0 | if (alloc->allocations_spesh) |
400 | 0 | MVM_repr_bind_key_o(tc, alloc_info, pds->spesh, |
401 | 0 | box_i(tc, alloc->allocations_spesh)); |
402 | 0 | if (alloc->allocations_jit) |
403 | 0 | MVM_repr_bind_key_o(tc, alloc_info, pds->jit, |
404 | 0 | box_i(tc, alloc->allocations_jit)); |
405 | 0 | MVM_repr_bind_key_o(tc, alloc_info, pds->count, |
406 | 0 | box_i(tc, alloc->allocations_interp |
407 | 0 | + alloc->allocations_spesh |
408 | 0 | + alloc->allocations_jit)); |
409 | 0 | MVM_repr_push_o(tc, alloc_list, alloc_info); |
410 | 0 | } |
411 | 0 | } |
412 | 0 |
|
413 | 0 | return node_hash; |
414 | 0 | } |
415 | | |
416 | | /* Dumps data from a single thread. */ |
417 | | static MVMObject * dump_thread_data(MVMThreadContext *tc, ProfDumpStrs *pds, |
418 | 0 | const MVMProfileThreadData *ptd) { |
419 | 0 | MVMObject *thread_hash = new_hash(tc); |
420 | 0 | MVMObject *thread_gcs = new_array(tc); |
421 | 0 | MVMuint32 i; |
422 | 0 |
|
423 | 0 | /* Add time. */ |
424 | 0 | MVM_repr_bind_key_o(tc, thread_hash, pds->total_time, |
425 | 0 | box_i(tc, (ptd->end_time - ptd->start_time) / 1000)); |
426 | 0 |
|
427 | 0 | /* Add call graph. */ |
428 | 0 | if (ptd->call_graph) |
429 | 0 | MVM_repr_bind_key_o(tc, thread_hash, pds->call_graph, |
430 | 0 | dump_call_graph_node(tc, pds, ptd->call_graph)); |
431 | 0 |
|
432 | 0 | /* Add GCs. */ |
433 | 0 | for (i = 0; i < ptd->num_gcs; i++) { |
434 | 0 | MVMObject *gc_hash = new_hash(tc); |
435 | 0 | MVM_repr_bind_key_o(tc, gc_hash, pds->time, |
436 | 0 | box_i(tc, ptd->gcs[i].time / 1000)); |
437 | 0 | MVM_repr_bind_key_o(tc, gc_hash, pds->full, |
438 | 0 | box_i(tc, ptd->gcs[i].full)); |
439 | 0 | MVM_repr_bind_key_o(tc, gc_hash, pds->cleared_bytes, |
440 | 0 | box_i(tc, ptd->gcs[i].cleared_bytes)); |
441 | 0 | MVM_repr_bind_key_o(tc, gc_hash, pds->retained_bytes, |
442 | 0 | box_i(tc, ptd->gcs[i].retained_bytes)); |
443 | 0 | MVM_repr_bind_key_o(tc, gc_hash, pds->promoted_bytes, |
444 | 0 | box_i(tc, ptd->gcs[i].promoted_bytes)); |
445 | 0 | MVM_repr_bind_key_o(tc, gc_hash, pds->gen2_roots, |
446 | 0 | box_i(tc, ptd->gcs[i].num_gen2roots)); |
447 | 0 | MVM_repr_push_o(tc, thread_gcs, gc_hash); |
448 | 0 | } |
449 | 0 | MVM_repr_bind_key_o(tc, thread_hash, pds->gcs, thread_gcs); |
450 | 0 |
|
451 | 0 | /* Add spesh time. */ |
452 | 0 | MVM_repr_bind_key_o(tc, thread_hash, pds->spesh_time, |
453 | 0 | box_i(tc, ptd->spesh_time / 1000)); |
454 | 0 |
|
455 | 0 | return thread_hash; |
456 | 0 | } |
457 | | |
458 | | /* Dumps data from all threads into an array of per-thread data. */ |
459 | 0 | static MVMObject * dump_data(MVMThreadContext *tc) { |
460 | 0 | MVMObject *threads_array; |
461 | 0 | ProfDumpStrs pds; |
462 | 0 |
|
463 | 0 | /* We'll allocate the data in gen2, but as we want to keep it, but to be |
464 | 0 | * sure we don't trigger a GC run. */ |
465 | 0 | MVM_gc_allocate_gen2_default_set(tc); |
466 | 0 |
|
467 | 0 | /* Some string constants to re-use. */ |
468 | 0 | pds.total_time = str(tc, "total_time"); |
469 | 0 | pds.call_graph = str(tc, "call_graph"); |
470 | 0 | pds.name = str(tc, "name"); |
471 | 0 | pds.id = str(tc, "id"); |
472 | 0 | pds.file = str(tc, "file"); |
473 | 0 | pds.line = str(tc, "line"); |
474 | 0 | pds.entries = str(tc, "entries"); |
475 | 0 | pds.spesh_entries = str(tc, "spesh_entries"); |
476 | 0 | pds.jit_entries = str(tc, "jit_entries"); |
477 | 0 | pds.inlined_entries = str(tc, "inlined_entries"); |
478 | 0 | pds.inclusive_time = str(tc, "inclusive_time"); |
479 | 0 | pds.exclusive_time = str(tc, "exclusive_time"); |
480 | 0 | pds.callees = str(tc, "callees"); |
481 | 0 | pds.allocations = str(tc, "allocations"); |
482 | 0 | pds.type = str(tc, "type"); |
483 | 0 | pds.count = str(tc, "count"); |
484 | 0 | pds.spesh = str(tc, "spesh"); |
485 | 0 | pds.jit = str(tc, "jit"); |
486 | 0 | pds.gcs = str(tc, "gcs"); |
487 | 0 | pds.time = str(tc, "time"); |
488 | 0 | pds.full = str(tc, "full"); |
489 | 0 | pds.cleared_bytes = str(tc, "cleared_bytes"); |
490 | 0 | pds.retained_bytes = str(tc, "retained_bytes"); |
491 | 0 | pds.promoted_bytes = str(tc, "promoted_bytes"); |
492 | 0 | pds.gen2_roots = str(tc, "gen2_roots"); |
493 | 0 | pds.osr = str(tc, "osr"); |
494 | 0 | pds.deopt_one = str(tc, "deopt_one"); |
495 | 0 | pds.deopt_all = str(tc, "deopt_all"); |
496 | 0 | pds.spesh_time = str(tc, "spesh_time"); |
497 | 0 | pds.native_lib = str(tc, "native library"); |
498 | 0 |
|
499 | 0 | /* Build up threads array. */ |
500 | 0 | /* XXX Only main thread for now. */ |
501 | 0 | threads_array = new_array(tc); |
502 | 0 | if (tc->prof_data) |
503 | 0 | MVM_repr_push_o(tc, threads_array, dump_thread_data(tc, &pds, tc->prof_data)); |
504 | 0 |
|
505 | 0 | /* Switch back to default allocation and return result; */ |
506 | 0 | MVM_gc_allocate_gen2_default_clear(tc); |
507 | 0 | return threads_array; |
508 | 0 | } |
509 | | |
510 | | /* Ends profiling, builds the result data structure, and returns it. */ |
511 | 0 | MVMObject * MVM_profile_instrumented_end(MVMThreadContext *tc) { |
512 | 0 | if (tc->prof_data) { |
513 | 0 | /* If we have any call frames still on the profile stack, exit them. */ |
514 | 0 | while (tc->prof_data->current_call) |
515 | 0 | MVM_profile_log_exit(tc); |
516 | 0 |
|
517 | 0 | /* Record end time. */ |
518 | 0 | tc->prof_data->end_time = uv_hrtime(); |
519 | 0 | } |
520 | 0 |
|
521 | 0 | /* Disable profiling. */ |
522 | 0 | /* XXX Needs to account for multiple threads. */ |
523 | 0 | tc->instance->profiling = 0; |
524 | 0 | tc->instance->instrumentation_level++; |
525 | 0 |
|
526 | 0 | /* Build and return result data structure. */ |
527 | 0 | return dump_data(tc); |
528 | 0 | } |
529 | | |
530 | | |
531 | | /* Marks objects held in the profiling graph. */ |
532 | 0 | static void mark_call_graph_node(MVMThreadContext *tc, MVMProfileCallNode *node, MVMGCWorklist *worklist) { |
533 | 0 | MVMuint32 i; |
534 | 0 | MVM_gc_worklist_add(tc, worklist, &(node->sf)); |
535 | 0 | for (i = 0; i < node->num_alloc; i++) |
536 | 0 | MVM_gc_worklist_add(tc, worklist, &(node->alloc[i].type)); |
537 | 0 | for (i = 0; i < node->num_succ; i++) |
538 | 0 | mark_call_graph_node(tc, node->succ[i], worklist); |
539 | 0 | } |
540 | 146 | void MVM_profile_instrumented_mark_data(MVMThreadContext *tc, MVMGCWorklist *worklist) { |
541 | 146 | if (tc->prof_data) |
542 | 0 | mark_call_graph_node(tc, tc->prof_data->call_graph, worklist); |
543 | 146 | } |