/home/travis/build/MoarVM/MoarVM/src/spesh/graph.c
Line | Count | Source (jump to first uncovered line) |
1 | | #include "moar.h" |
2 | | |
3 | | /* This is where the spesh stuff all begins. The logic in here takes bytecode |
4 | | * and builds a spesh graph from it. This is a CFG in SSA form. Transforming |
5 | | * to SSA involves computing dominance frontiers, done by the algorithm found |
6 | | * in http://www.cs.rice.edu/~keith/EMBED/dom.pdf. The SSA algorithm itself is |
7 | | * from http://www.cs.utexas.edu/~pingali/CS380C/2010/papers/ssaCytron.pdf. */ |
8 | | |
9 | 0 | #define GET_I8(pc, idx) *((MVMint8 *)((pc) + (idx))) |
10 | | #define GET_UI8(pc, idx) *((MVMuint8 *)((pc) + (idx))) |
11 | 406k | #define GET_I16(pc, idx) *((MVMint16 *)((pc) + (idx))) |
12 | 2.14M | #define GET_UI16(pc, idx) *((MVMuint16 *)((pc) + (idx))) |
13 | 14 | #define GET_I32(pc, idx) *((MVMint32 *)((pc) + (idx))) |
14 | 317k | #define GET_UI32(pc, idx) *((MVMuint32 *)((pc) + (idx))) |
15 | 0 | #define GET_N32(pc, idx) *((MVMnum32 *)((pc) + (idx))) |
16 | | |
17 | | /* Allocate a piece of memory from the spesh graph's region |
18 | | * allocator. Deallocated when the spesh graph is. */ |
19 | 8.25M | void * MVM_spesh_alloc(MVMThreadContext *tc, MVMSpeshGraph *g, size_t bytes) { |
20 | 8.25M | return MVM_region_alloc(tc, &g->region_alloc, bytes); |
21 | 8.25M | } |
22 | | |
23 | | /* Looks up op info; doesn't sanity check, since we should be working on code |
24 | | * that already pass validation. */ |
25 | 1.34M | static const MVMOpInfo * get_op_info(MVMThreadContext *tc, MVMCompUnit *cu, MVMuint16 opcode) { |
26 | 1.34M | if (opcode < MVM_OP_EXT_BASE) { |
27 | 1.34M | return MVM_op_get_op(opcode); |
28 | 1.34M | } |
29 | 0 | else { |
30 | 0 | MVMuint16 index = opcode - MVM_OP_EXT_BASE; |
31 | 0 | MVMExtOpRecord *record = &cu->body.extops[index]; |
32 | 0 | return MVM_ext_resolve_extop_record(tc, record); |
33 | 0 | } |
34 | 1.34M | } |
35 | | |
36 | | /* Records a de-optimization annotation and mapping pair. */ |
37 | | static void add_deopt_annotation(MVMThreadContext *tc, MVMSpeshGraph *g, MVMSpeshIns *ins_node, |
38 | 129k | MVMuint8 *pc, MVMint32 type) { |
39 | 129k | /* Add an the annotations. */ |
40 | 129k | MVMSpeshAnn *ann = MVM_spesh_alloc(tc, g, sizeof(MVMSpeshAnn)); |
41 | 129k | ann->type = type; |
42 | 129k | ann->data.deopt_idx = g->num_deopt_addrs; |
43 | 129k | ann->next = ins_node->annotations; |
44 | 129k | ins_node->annotations = ann; |
45 | 129k | |
46 | 129k | /* Record PC in the deopt entries table. */ |
47 | 129k | if (g->num_deopt_addrs == g->alloc_deopt_addrs) { |
48 | 38.2k | g->alloc_deopt_addrs += 4; |
49 | 38.2k | if (g->deopt_addrs) |
50 | 25.1k | g->deopt_addrs = MVM_realloc(g->deopt_addrs, |
51 | 25.1k | g->alloc_deopt_addrs * sizeof(MVMint32) * 2); |
52 | 38.2k | else |
53 | 13.0k | g->deopt_addrs = MVM_malloc(g->alloc_deopt_addrs * sizeof(MVMint32) * 2); |
54 | 38.2k | } |
55 | 129k | g->deopt_addrs[2 * g->num_deopt_addrs] = pc - g->bytecode; |
56 | 129k | g->num_deopt_addrs++; |
57 | 129k | } |
58 | | |
59 | | /* Finds the linearly previous basic block (not cheap, but uncommon). */ |
60 | 26.5k | MVMSpeshBB * MVM_spesh_graph_linear_prev(MVMThreadContext *tc, MVMSpeshGraph *g, MVMSpeshBB *search) { |
61 | 26.5k | MVMSpeshBB *bb = g->entry; |
62 | 3.70M | while (bb) { |
63 | 3.70M | if (bb->linear_next == search) |
64 | 26.5k | return bb; |
65 | 3.68M | bb = bb->linear_next; |
66 | 3.68M | } |
67 | 0 | return NULL; |
68 | 26.5k | } |
69 | | |
70 | | /* Builds the control flow graph, populating the passed spesh graph structure |
71 | | * with it. This also makes nodes for all of the instruction. */ |
72 | 2.82M | #define MVM_CFG_BB_START 1 |
73 | 1.66M | #define MVM_CFG_BB_END 2 |
74 | | static void build_cfg(MVMThreadContext *tc, MVMSpeshGraph *g, MVMStaticFrame *sf, |
75 | 21.8k | MVMint32 *existing_deopts, MVMint32 num_existing_deopts) { |
76 | 21.8k | MVMSpeshBB *cur_bb, *prev_bb; |
77 | 21.8k | MVMSpeshIns *last_ins; |
78 | 21.8k | MVMint64 i; |
79 | 21.8k | MVMint32 bb_idx; |
80 | 21.8k | |
81 | 21.8k | /* Temporary array of all MVMSpeshIns we create (one per instruction). |
82 | 21.8k | * Overestimate at size. Has the flat view, matching the bytecode. */ |
83 | 21.8k | MVMSpeshIns **ins_flat = MVM_calloc(g->bytecode_size / 2, sizeof(MVMSpeshIns *)); |
84 | 21.8k | |
85 | 21.8k | /* Temporary array where each byte in the input bytecode gets a 32-bit |
86 | 21.8k | * integer. This is used for two things: |
87 | 21.8k | * A) When we make the MVMSpeshIns for an instruction starting at the |
88 | 21.8k | * byte, we put the instruction index (into ins_flat) in the slot, |
89 | 21.8k | * shifting it by 2 bits to the left. We will use this to do fixups. |
90 | 21.8k | * B) The first bit is "I have an incoming branch" - that is, start of |
91 | 21.8k | * a basic block. The second bit is "I can branch" - that is, end of |
92 | 21.8k | * a basic block. It's possible to have both bits set. |
93 | 21.8k | * Anything that's just a zero has no instruction starting there. */ |
94 | 21.8k | MVMuint32 *byte_to_ins_flags = MVM_calloc(g->bytecode_size, sizeof(MVMuint32)); |
95 | 21.8k | |
96 | 21.8k | /* Instruction to basic block mapping. Initialized later. */ |
97 | 21.8k | MVMSpeshBB **ins_to_bb = NULL; |
98 | 21.8k | |
99 | 21.8k | /* Make first pass through the bytecode. In this pass, we make MVMSpeshIns |
100 | 21.8k | * nodes for each instruction and set the start/end of block bits. Also |
101 | 21.8k | * set handler targets as basic block starters. */ |
102 | 21.8k | MVMCompUnit *cu = sf->body.cu; |
103 | 21.8k | MVMuint8 *pc = g->bytecode; |
104 | 21.8k | MVMuint8 *end = g->bytecode + g->bytecode_size; |
105 | 21.8k | MVMuint32 ins_idx = 0; |
106 | 21.8k | MVMuint8 next_bbs = 1; /* Next iteration (here, first) starts a BB. */ |
107 | 27.0k | for (i = 0; i < g->num_handlers; i++) |
108 | 5.25k | byte_to_ins_flags[g->handlers[i].goto_offset] |= MVM_CFG_BB_START; |
109 | 1.34M | while (pc < end) { |
110 | 1.32M | /* Look up op info. */ |
111 | 1.32M | MVMuint16 opcode = *(MVMuint16 *)pc; |
112 | 1.32M | MVMuint8 *args = pc + 2; |
113 | 1.32M | MVMuint8 arg_size = 0; |
114 | 1.32M | const MVMOpInfo *info = get_op_info(tc, cu, opcode); |
115 | 1.32M | |
116 | 1.32M | /* Create an instruction node, add it, and record its position. */ |
117 | 1.32M | MVMSpeshIns *ins_node = MVM_spesh_alloc(tc, g, sizeof(MVMSpeshIns)); |
118 | 1.32M | ins_flat[ins_idx] = ins_node; |
119 | 1.32M | byte_to_ins_flags[pc - g->bytecode] |= ins_idx << 2; |
120 | 1.32M | |
121 | 1.32M | /* Did previous instruction end a basic block? */ |
122 | 1.32M | if (next_bbs) { |
123 | 231k | byte_to_ins_flags[pc - g->bytecode] |= MVM_CFG_BB_START; |
124 | 231k | next_bbs = 0; |
125 | 231k | } |
126 | 1.32M | |
127 | 1.32M | /* Also check we're not already a BB start due to being a branch |
128 | 1.32M | * target, in which case we should ensure our prior is marked as |
129 | 1.32M | * a BB end. */ |
130 | 1.09M | else { |
131 | 1.09M | if (byte_to_ins_flags[pc - g->bytecode] & MVM_CFG_BB_START) { |
132 | 75.6k | MVMuint32 hunt = pc - g->bytecode; |
133 | 538k | while (!byte_to_ins_flags[--hunt]); |
134 | 75.6k | byte_to_ins_flags[hunt] |= MVM_CFG_BB_END; |
135 | 75.6k | } |
136 | 1.09M | } |
137 | 1.32M | |
138 | 1.32M | /* Store opcode */ |
139 | 1.32M | ins_node->info = info; |
140 | 1.32M | |
141 | 1.32M | /* Go over operands. */ |
142 | 1.32M | ins_node->operands = MVM_spesh_alloc(tc, g, info->num_operands * sizeof(MVMSpeshOperand)); |
143 | 4.17M | for (i = 0; i < info->num_operands; i++) { |
144 | 2.84M | MVMuint8 flags = info->operands[i]; |
145 | 2.84M | MVMuint8 rw = flags & MVM_operand_rw_mask; |
146 | 2.84M | switch (rw) { |
147 | 2.04M | case MVM_operand_read_reg: |
148 | 2.04M | case MVM_operand_write_reg: |
149 | 2.04M | ins_node->operands[i].reg.orig = GET_UI16(args, arg_size); |
150 | 2.04M | arg_size += 2; |
151 | 2.04M | break; |
152 | 29.9k | case MVM_operand_read_lex: |
153 | 29.9k | case MVM_operand_write_lex: |
154 | 29.9k | ins_node->operands[i].lex.idx = GET_UI16(args, arg_size); |
155 | 29.9k | ins_node->operands[i].lex.outers = GET_UI16(args, arg_size + 2); |
156 | 29.9k | arg_size += 4; |
157 | 29.9k | break; |
158 | 770k | case MVM_operand_literal: { |
159 | 770k | MVMuint32 type = flags & MVM_operand_type_mask; |
160 | 770k | switch (type) { |
161 | 0 | case MVM_operand_int8: |
162 | 0 | ins_node->operands[i].lit_i8 = GET_I8(args, arg_size); |
163 | 0 | arg_size += 1; |
164 | 0 | break; |
165 | 397k | case MVM_operand_int16: |
166 | 397k | ins_node->operands[i].lit_i16 = GET_I16(args, arg_size); |
167 | 397k | arg_size += 2; |
168 | 397k | break; |
169 | 14 | case MVM_operand_int32: |
170 | 14 | ins_node->operands[i].lit_i32 = GET_I32(args, arg_size); |
171 | 14 | arg_size += 4; |
172 | 14 | break; |
173 | 5.42k | case MVM_operand_int64: |
174 | 5.42k | ins_node->operands[i].lit_i64 = MVM_BC_get_I64(args, arg_size); |
175 | 5.42k | arg_size += 8; |
176 | 5.42k | break; |
177 | 0 | case MVM_operand_num32: |
178 | 0 | ins_node->operands[i].lit_n32 = GET_N32(args, arg_size); |
179 | 0 | arg_size += 4; |
180 | 0 | break; |
181 | 6 | case MVM_operand_num64: |
182 | 6 | ins_node->operands[i].lit_n64 = MVM_BC_get_N64(args, arg_size); |
183 | 6 | arg_size += 8; |
184 | 6 | break; |
185 | 39.8k | case MVM_operand_callsite: |
186 | 39.8k | ins_node->operands[i].callsite_idx = GET_UI16(args, arg_size); |
187 | 39.8k | arg_size += 2; |
188 | 39.8k | break; |
189 | 886 | case MVM_operand_coderef: |
190 | 886 | ins_node->operands[i].coderef_idx = GET_UI16(args, arg_size); |
191 | 886 | arg_size += 2; |
192 | 886 | break; |
193 | 155k | case MVM_operand_str: |
194 | 155k | ins_node->operands[i].lit_str_idx = GET_UI32(args, arg_size); |
195 | 155k | arg_size += 4; |
196 | 155k | break; |
197 | 162k | case MVM_operand_ins: { |
198 | 162k | /* Stash instruction offset. */ |
199 | 162k | MVMuint32 target = GET_UI32(args, arg_size); |
200 | 162k | ins_node->operands[i].ins_offset = target; |
201 | 162k | |
202 | 162k | /* This is a branching instruction, so it's a BB end. */ |
203 | 162k | byte_to_ins_flags[pc - g->bytecode] |= MVM_CFG_BB_END; |
204 | 162k | |
205 | 162k | /* Its target is a BB start, and any previous instruction |
206 | 162k | * we already passed needs marking as a BB end. */ |
207 | 162k | byte_to_ins_flags[target] |= MVM_CFG_BB_START; |
208 | 162k | if (target > 0 && target < pc - g->bytecode) { |
209 | 67.6k | while (!byte_to_ins_flags[--target]); |
210 | 10.2k | byte_to_ins_flags[target] |= MVM_CFG_BB_END; |
211 | 10.2k | } |
212 | 162k | |
213 | 162k | /* Next instruction is also a BB start. */ |
214 | 162k | next_bbs = 1; |
215 | 162k | |
216 | 162k | arg_size += 4; |
217 | 162k | break; |
218 | 0 | } |
219 | 9.49k | case MVM_operand_spesh_slot: |
220 | 9.49k | ins_node->operands[i].lit_i16 = GET_I16(args, arg_size); |
221 | 9.49k | arg_size += 2; |
222 | 9.49k | break; |
223 | 0 | default: |
224 | 0 | MVM_oops(tc, |
225 | 0 | "Spesh: unknown operand type %d in graph building (op %s)", |
226 | 0 | (int)type, ins_node->info->name); |
227 | 770k | } |
228 | 770k | break; |
229 | 0 | default: |
230 | 0 | break; |
231 | 770k | } |
232 | 2.84M | } |
233 | 2.84M | } |
234 | 1.32M | |
235 | 1.32M | /* We specially handle the jumplist case, which needs to mark all of |
236 | 1.32M | * the possible places we could jump to in the following instructions |
237 | 1.32M | * as starts of basic blocks. It is, in itself, the end of one. Note |
238 | 1.32M | * we jump to the instruction after the n jump points if none match, |
239 | 1.32M | * so that is marked too. */ |
240 | 1.32M | if (opcode == MVM_OP_jumplist) { |
241 | 416 | MVMint64 n = MVM_BC_get_I64(args, 0); |
242 | 4.69k | for (i = 0; i <= n; i++) |
243 | 4.27k | byte_to_ins_flags[(pc - g->bytecode) + 12 + i * 6] |= MVM_CFG_BB_START; |
244 | 416 | byte_to_ins_flags[pc - g->bytecode] |= MVM_CFG_BB_END; |
245 | 416 | } |
246 | 1.32M | |
247 | 1.32M | /* Invocations, returns, and throws are basic block ends. */ |
248 | 1.32M | switch (opcode) { |
249 | 68.7k | case MVM_OP_invoke_v: |
250 | 68.7k | case MVM_OP_invoke_i: |
251 | 68.7k | case MVM_OP_invoke_n: |
252 | 68.7k | case MVM_OP_invoke_s: |
253 | 68.7k | case MVM_OP_invoke_o: |
254 | 68.7k | case MVM_OP_return_i: |
255 | 68.7k | case MVM_OP_return_n: |
256 | 68.7k | case MVM_OP_return_s: |
257 | 68.7k | case MVM_OP_return_o: |
258 | 68.7k | case MVM_OP_return: |
259 | 68.7k | case MVM_OP_throwdyn: |
260 | 68.7k | case MVM_OP_throwlex: |
261 | 68.7k | case MVM_OP_throwlexotic: |
262 | 68.7k | case MVM_OP_throwcatdyn: |
263 | 68.7k | case MVM_OP_throwcatlex: |
264 | 68.7k | case MVM_OP_throwcatlexotic: |
265 | 68.7k | case MVM_OP_die: |
266 | 68.7k | case MVM_OP_rethrow: |
267 | 68.7k | case MVM_OP_resume: |
268 | 68.7k | byte_to_ins_flags[pc - g->bytecode] |= MVM_CFG_BB_END; |
269 | 68.7k | next_bbs = 1; |
270 | 68.7k | break; |
271 | 1.25M | default: |
272 | 1.25M | break; |
273 | 1.32M | } |
274 | 1.32M | |
275 | 1.32M | /* Final instruction is basic block end. */ |
276 | 1.32M | if (pc + 2 + arg_size == end) |
277 | 21.8k | byte_to_ins_flags[pc - g->bytecode] |= MVM_CFG_BB_END; |
278 | 1.32M | |
279 | 1.32M | /* Caculate next instruction's PC. */ |
280 | 1.32M | pc += 2 + arg_size; |
281 | 1.32M | |
282 | 1.32M | /* If this is a deopt point opcode... */ |
283 | 1.32M | if (!existing_deopts && (info->deopt_point & MVM_DEOPT_MARK_ONE)) |
284 | 86.6k | add_deopt_annotation(tc, g, ins_node, pc, MVM_SPESH_ANN_DEOPT_ONE_INS); |
285 | 1.32M | if (!existing_deopts && (info->deopt_point & MVM_DEOPT_MARK_ALL)) |
286 | 38.7k | add_deopt_annotation(tc, g, ins_node, pc, MVM_SPESH_ANN_DEOPT_ALL_INS); |
287 | 1.32M | if (!existing_deopts && (info->deopt_point & MVM_DEOPT_MARK_OSR)) |
288 | 4.03k | add_deopt_annotation(tc, g, ins_node, pc, MVM_SPESH_ANN_DEOPT_OSR); |
289 | 1.32M | |
290 | 1.32M | /* Go to next instruction. */ |
291 | 1.32M | ins_idx++; |
292 | 1.32M | } |
293 | 21.8k | |
294 | 21.8k | /* Annotate instructions that are handler-significant. */ |
295 | 27.0k | for (i = 0; i < g->num_handlers; i++) { |
296 | 5.25k | MVMSpeshIns *start_ins = ins_flat[byte_to_ins_flags[g->handlers[i].start_offset] >> 2]; |
297 | 5.25k | MVMSpeshIns *end_ins = ins_flat[byte_to_ins_flags[g->handlers[i].end_offset] >> 2]; |
298 | 5.25k | MVMSpeshIns *goto_ins = ins_flat[byte_to_ins_flags[g->handlers[i].goto_offset] >> 2]; |
299 | 5.25k | MVMSpeshAnn *start_ann = MVM_spesh_alloc(tc, g, sizeof(MVMSpeshAnn)); |
300 | 5.25k | MVMSpeshAnn *end_ann = MVM_spesh_alloc(tc, g, sizeof(MVMSpeshAnn)); |
301 | 5.25k | MVMSpeshAnn *goto_ann = MVM_spesh_alloc(tc, g, sizeof(MVMSpeshAnn)); |
302 | 5.25k | |
303 | 5.25k | start_ann->next = start_ins->annotations; |
304 | 5.25k | start_ann->type = MVM_SPESH_ANN_FH_START; |
305 | 5.25k | start_ann->data.frame_handler_index = i; |
306 | 5.25k | start_ins->annotations = start_ann; |
307 | 5.25k | |
308 | 5.25k | end_ann->next = end_ins->annotations; |
309 | 5.25k | end_ann->type = MVM_SPESH_ANN_FH_END; |
310 | 5.25k | end_ann->data.frame_handler_index = i; |
311 | 5.25k | end_ins->annotations = end_ann; |
312 | 5.25k | |
313 | 5.25k | goto_ann->next = goto_ins->annotations; |
314 | 5.25k | goto_ann->type = MVM_SPESH_ANN_FH_GOTO; |
315 | 5.25k | goto_ann->data.frame_handler_index = i; |
316 | 5.25k | goto_ins->annotations = goto_ann; |
317 | 5.25k | } |
318 | 21.8k | |
319 | 21.8k | /* Annotate instructions that are inline start/end points. */ |
320 | 22.2k | for (i = 0; i < g->num_inlines; i++) { |
321 | 387 | MVMSpeshIns *start_ins = ins_flat[byte_to_ins_flags[g->inlines[i].start] >> 2]; |
322 | 387 | MVMSpeshIns *end_ins = ins_flat[byte_to_ins_flags[g->inlines[i].end] >> 2]; |
323 | 387 | MVMSpeshAnn *start_ann = MVM_spesh_alloc(tc, g, sizeof(MVMSpeshAnn)); |
324 | 387 | MVMSpeshAnn *end_ann = MVM_spesh_alloc(tc, g, sizeof(MVMSpeshAnn)); |
325 | 387 | |
326 | 387 | start_ann->next = start_ins->annotations; |
327 | 387 | start_ann->type = MVM_SPESH_ANN_INLINE_START; |
328 | 387 | start_ann->data.inline_idx = i; |
329 | 387 | start_ins->annotations = start_ann; |
330 | 387 | |
331 | 387 | end_ann->next = end_ins->annotations; |
332 | 387 | end_ann->type = MVM_SPESH_ANN_INLINE_END; |
333 | 387 | end_ann->data.inline_idx = i; |
334 | 387 | end_ins->annotations = end_ann; |
335 | 387 | } |
336 | 21.8k | |
337 | 21.8k | /* Now for the second pass, where we assemble the basic blocks. Also we |
338 | 21.8k | * build a lookup table of instructions that start a basic block to that |
339 | 21.8k | * basic block, for the final CFG construction. We make the entry block a |
340 | 21.8k | * special one, containing a noop; it will have any exception handler |
341 | 21.8k | * targets linked from it, so they show up in the graph. */ |
342 | 21.8k | g->entry = MVM_spesh_alloc(tc, g, sizeof(MVMSpeshBB)); |
343 | 21.8k | g->entry->first_ins = MVM_spesh_alloc(tc, g, sizeof(MVMSpeshIns)); |
344 | 21.8k | g->entry->first_ins->info = get_op_info(tc, cu, 0); |
345 | 21.8k | g->entry->last_ins = g->entry->first_ins; |
346 | 21.8k | g->entry->idx = 0; |
347 | 21.8k | cur_bb = NULL; |
348 | 21.8k | prev_bb = g->entry; |
349 | 21.8k | last_ins = NULL; |
350 | 21.8k | ins_to_bb = MVM_calloc(ins_idx, sizeof(MVMSpeshBB *)); |
351 | 21.8k | ins_idx = 0; |
352 | 21.8k | bb_idx = 1; |
353 | 9.10M | for (i = 0; i < g->bytecode_size; i++) { |
354 | 9.07M | MVMSpeshIns *cur_ins; |
355 | 9.07M | |
356 | 9.07M | /* Skip zeros; no instruction here. */ |
357 | 9.07M | if (!byte_to_ins_flags[i]) |
358 | 7.75M | continue; |
359 | 9.07M | |
360 | 9.07M | /* Get current instruction. */ |
361 | 1.32M | cur_ins = ins_flat[byte_to_ins_flags[i] >> 2]; |
362 | 1.32M | |
363 | 1.32M | /* Start of a basic block? */ |
364 | 1.32M | if (byte_to_ins_flags[i] & MVM_CFG_BB_START) { |
365 | 310k | /* Should not already be in a basic block. */ |
366 | 310k | if (cur_bb) { |
367 | 0 | MVM_spesh_graph_destroy(tc, g); |
368 | 0 | MVM_oops(tc, "Spesh: confused during basic block analysis (in block)"); |
369 | 0 | } |
370 | 310k | |
371 | 310k | /* Create it, and set first instruction and index. */ |
372 | 310k | cur_bb = MVM_spesh_alloc(tc, g, sizeof(MVMSpeshBB)); |
373 | 310k | cur_bb->first_ins = cur_ins; |
374 | 310k | cur_bb->idx = bb_idx; |
375 | 310k | cur_bb->initial_pc = i; |
376 | 310k | bb_idx++; |
377 | 310k | |
378 | 310k | /* Record instruction -> BB start mapping. */ |
379 | 310k | ins_to_bb[ins_idx] = cur_bb; |
380 | 310k | |
381 | 310k | /* Link it to the previous one. */ |
382 | 310k | prev_bb->linear_next = cur_bb; |
383 | 310k | } |
384 | 1.32M | |
385 | 1.32M | /* Should always be in a BB at this point. */ |
386 | 1.32M | if (!cur_bb) { |
387 | 0 | MVM_spesh_graph_destroy(tc, g); |
388 | 0 | MVM_oops(tc, "Spesh: confused during basic block analysis (no block)"); |
389 | 0 | } |
390 | 1.32M | |
391 | 1.32M | /* Add instruction into double-linked per-block instruction list. */ |
392 | 1.32M | if (last_ins) { |
393 | 1.01M | last_ins->next = cur_ins; |
394 | 1.01M | cur_ins->prev = last_ins; |
395 | 1.01M | } |
396 | 1.32M | last_ins = cur_ins; |
397 | 1.32M | |
398 | 1.32M | /* End of a basic block? */ |
399 | 1.32M | if (byte_to_ins_flags[i] & MVM_CFG_BB_END) { |
400 | 310k | cur_bb->last_ins = cur_ins; |
401 | 310k | prev_bb = cur_bb; |
402 | 310k | cur_bb = NULL; |
403 | 310k | last_ins = NULL; |
404 | 310k | } |
405 | 1.32M | |
406 | 1.32M | ins_idx++; |
407 | 1.32M | } |
408 | 21.8k | g->num_bbs = bb_idx; |
409 | 21.8k | |
410 | 21.8k | /* Finally, link the basic blocks up to form a CFG. Along the way, any of |
411 | 21.8k | * the instruction operands get the target BB stored. */ |
412 | 21.8k | cur_bb = g->entry; |
413 | 354k | while (cur_bb) { |
414 | 332k | /* If it's the first block, it's a special case; successors are the |
415 | 332k | * real successor and all exception handlers. */ |
416 | 332k | if (cur_bb == g->entry) { |
417 | 21.8k | cur_bb->num_succ = 1 + g->num_handlers; |
418 | 21.8k | cur_bb->succ = MVM_spesh_alloc(tc, g, cur_bb->num_succ * sizeof(MVMSpeshBB *)); |
419 | 21.8k | cur_bb->succ[0] = cur_bb->linear_next; |
420 | 27.0k | for (i = 0; i < g->num_handlers; i++) { |
421 | 5.25k | MVMuint32 offset = g->handlers[i].goto_offset; |
422 | 5.25k | cur_bb->succ[i + 1] = ins_to_bb[byte_to_ins_flags[offset] >> 2]; |
423 | 5.25k | } |
424 | 21.8k | } |
425 | 332k | |
426 | 332k | /* Otherwise, consider the last instruction, to see how we leave the BB. */ |
427 | 310k | else { |
428 | 310k | switch (cur_bb->last_ins->info->opcode) { |
429 | 416 | case MVM_OP_jumplist: { |
430 | 416 | /* Jumplist, so successors are next N+1 basic blocks. */ |
431 | 416 | MVMint64 num_bbs = cur_bb->last_ins->operands[0].lit_i64 + 1; |
432 | 416 | MVMSpeshBB *bb_to_add = cur_bb->linear_next; |
433 | 416 | cur_bb->succ = MVM_spesh_alloc(tc, g, num_bbs * sizeof(MVMSpeshBB *)); |
434 | 4.69k | for (i = 0; i < num_bbs; i++) { |
435 | 4.27k | cur_bb->succ[i] = bb_to_add; |
436 | 4.27k | bb_to_add = bb_to_add->linear_next; |
437 | 4.27k | } |
438 | 416 | cur_bb->num_succ = num_bbs; |
439 | 416 | } |
440 | 416 | break; |
441 | 59.1k | case MVM_OP_goto: { |
442 | 59.1k | /* Unconditional branch, so one successor. */ |
443 | 59.1k | MVMuint32 offset = cur_bb->last_ins->operands[0].ins_offset; |
444 | 59.1k | MVMSpeshBB *tgt = ins_to_bb[byte_to_ins_flags[offset] >> 2]; |
445 | 59.1k | cur_bb->succ = MVM_spesh_alloc(tc, g, sizeof(MVMSpeshBB *)); |
446 | 59.1k | cur_bb->succ[0] = tgt; |
447 | 59.1k | cur_bb->num_succ = 1; |
448 | 59.1k | cur_bb->last_ins->operands[0].ins_bb = tgt; |
449 | 59.1k | } |
450 | 59.1k | break; |
451 | 251k | default: { |
452 | 251k | /* Probably conditional branch, so two successors: one from |
453 | 251k | * the instruction, another from fall-through. Or may just be |
454 | 251k | * a non-branch that exits for other reasons. */ |
455 | 251k | cur_bb->succ = MVM_spesh_alloc(tc, g, 2 * sizeof(MVMSpeshBB *)); |
456 | 761k | for (i = 0; i < cur_bb->last_ins->info->num_operands; i++) { |
457 | 510k | if (cur_bb->last_ins->info->operands[i] == MVM_operand_ins) { |
458 | 103k | MVMuint32 offset = cur_bb->last_ins->operands[i].ins_offset; |
459 | 103k | cur_bb->succ[0] = ins_to_bb[byte_to_ins_flags[offset] >> 2]; |
460 | 103k | cur_bb->num_succ++; |
461 | 103k | cur_bb->last_ins->operands[i].ins_bb = cur_bb->succ[0]; |
462 | 103k | } |
463 | 510k | } |
464 | 251k | if (cur_bb->num_succ > 1) { |
465 | 0 | /* If we ever get instructions with multiple targets, this |
466 | 0 | * area of the code needs an update. */ |
467 | 0 | MVM_spesh_graph_destroy(tc, g); |
468 | 0 | MVM_oops(tc, "Spesh: unhandled multi-target branch"); |
469 | 0 | } |
470 | 251k | if (cur_bb->linear_next) { |
471 | 229k | cur_bb->succ[cur_bb->num_succ] = cur_bb->linear_next; |
472 | 229k | cur_bb->num_succ++; |
473 | 229k | } |
474 | 251k | } |
475 | 251k | break; |
476 | 310k | } |
477 | 310k | } |
478 | 332k | |
479 | 332k | /* Move on to next block. */ |
480 | 332k | cur_bb = cur_bb->linear_next; |
481 | 332k | } |
482 | 21.8k | |
483 | 21.8k | /* If we're building the graph for optimized bytecode, insert existing |
484 | 21.8k | * deopt points. */ |
485 | 21.8k | if (existing_deopts) { |
486 | 10.3k | for (i = 0; i < num_existing_deopts; i ++) { |
487 | 7.91k | if (existing_deopts[2 * i + 1] >= 0) { |
488 | 7.75k | MVMSpeshIns *post_ins = ins_flat[byte_to_ins_flags[existing_deopts[2 * i + 1]] >> 2]; |
489 | 5.72k | MVMSpeshIns *deopt_ins = post_ins->prev ? post_ins->prev : |
490 | 2.02k | MVM_spesh_graph_linear_prev(tc, g, |
491 | 2.02k | ins_to_bb[byte_to_ins_flags[existing_deopts[2 * i + 1]] >> 2])->last_ins; |
492 | 7.75k | MVMSpeshAnn *deopt_ann = MVM_spesh_alloc(tc, g, sizeof(MVMSpeshAnn)); |
493 | 7.75k | deopt_ann->next = deopt_ins->annotations; |
494 | 7.75k | deopt_ann->type = MVM_SPESH_ANN_DEOPT_INLINE; |
495 | 7.75k | deopt_ann->data.deopt_idx = i; |
496 | 7.75k | deopt_ins->annotations = deopt_ann; |
497 | 7.75k | } |
498 | 7.91k | } |
499 | 2.39k | } |
500 | 21.8k | |
501 | 21.8k | /* Clear up the temporary arrays. */ |
502 | 21.8k | MVM_free(byte_to_ins_flags); |
503 | 21.8k | MVM_free(ins_flat); |
504 | 21.8k | MVM_free(ins_to_bb); |
505 | 21.8k | } |
506 | | |
507 | | /* Inserts nulling of object reigsters. A later stage of the optimizer will |
508 | | * throw out any that are unrequired, leaving only those that cover (rare) |
509 | | * "register read before assigned" cases. (We can thus just start off with |
510 | | * them NULL, since zeroed memory is cheaper than copying a VMNulls in to |
511 | | * place). */ |
512 | 140k | static MVMint32 is_handler_reg(MVMThreadContext *tc, MVMSpeshGraph *g, MVMuint16 reg) { |
513 | 140k | MVMuint32 num_handlers = g->num_handlers; |
514 | 140k | MVMuint32 i; |
515 | 288k | for (i = 0; i < num_handlers; i++) |
516 | 148k | if (g->handlers[i].action == MVM_EX_ACTION_INVOKE) |
517 | 158 | if (g->handlers[i].block_reg == reg) |
518 | 7 | return 1; |
519 | 140k | return 0; |
520 | 140k | } |
521 | 17.1k | static void insert_object_null_instructions(MVMThreadContext *tc, MVMSpeshGraph *g) { |
522 | 17.1k | MVMSpeshBB *insert_bb = g->entry->linear_next; |
523 | 17.1k | MVMuint16 *local_types = g->sf->body.local_types; |
524 | 17.1k | MVMuint16 num_locals = g->sf->body.num_locals; |
525 | 17.1k | MVMuint16 i; |
526 | 239k | for (i = 0; i < num_locals; i++) { |
527 | 222k | if (local_types[i] == MVM_reg_obj && !is_handler_reg(tc, g, i)) { |
528 | 140k | MVMSpeshIns *null_ins = MVM_spesh_alloc(tc, g, sizeof(MVMSpeshIns)); |
529 | 140k | null_ins->info = MVM_op_get_op(MVM_OP_null); |
530 | 140k | null_ins->operands = MVM_spesh_alloc(tc, g, sizeof(MVMSpeshOperand)); |
531 | 140k | null_ins->operands[0].reg.orig = i; |
532 | 140k | MVM_spesh_manipulate_insert_ins(tc, insert_bb, NULL, null_ins); |
533 | 140k | } |
534 | 222k | } |
535 | 17.1k | } |
536 | | |
537 | | /* Eliminates any unreachable basic blocks (that is, dead code). Not having |
538 | | * to consider them any further simplifies all that follows. */ |
539 | 21.8k | static void eliminate_dead(MVMThreadContext *tc, MVMSpeshGraph *g) { |
540 | 21.8k | /* Iterate to fixed point. */ |
541 | 21.8k | MVMint8 *seen = MVM_malloc(g->num_bbs); |
542 | 21.8k | MVMint32 orig_bbs = g->num_bbs; |
543 | 21.8k | MVMint8 death = 1; |
544 | 43.7k | while (death) { |
545 | 21.9k | /* First pass: mark every basic block that is the entry point or the |
546 | 21.9k | * successor of some other block. */ |
547 | 21.9k | MVMSpeshBB *cur_bb = g->entry; |
548 | 21.9k | memset(seen, 0, g->num_bbs); |
549 | 21.9k | seen[0] = 1; |
550 | 354k | while (cur_bb) { |
551 | 332k | MVMuint16 i; |
552 | 756k | for (i = 0; i < cur_bb->num_succ; i++) |
553 | 423k | seen[cur_bb->succ[i]->idx] = 1; |
554 | 332k | cur_bb = cur_bb->linear_next; |
555 | 332k | } |
556 | 21.9k | |
557 | 21.9k | /* Second pass: eliminate dead BBs from consideration. */ |
558 | 21.9k | death = 0; |
559 | 21.9k | cur_bb = g->entry; |
560 | 332k | while (cur_bb->linear_next) { |
561 | 310k | if (!seen[cur_bb->linear_next->idx]) { |
562 | 96 | cur_bb->linear_next = cur_bb->linear_next->linear_next; |
563 | 96 | g->num_bbs--; |
564 | 96 | death = 1; |
565 | 96 | } |
566 | 310k | cur_bb = cur_bb->linear_next; |
567 | 310k | } |
568 | 21.9k | } |
569 | 21.8k | MVM_free(seen); |
570 | 21.8k | |
571 | 21.8k | /* If we removed some, need to re-number so they're consecutive, for the |
572 | 21.8k | * post-order and dominance calcs to be happy. */ |
573 | 21.8k | if (g->num_bbs != orig_bbs) { |
574 | 75 | MVMint32 new_idx = 0; |
575 | 75 | MVMSpeshBB *cur_bb = g->entry; |
576 | 443 | while (cur_bb) { |
577 | 368 | cur_bb->idx = new_idx; |
578 | 368 | new_idx++; |
579 | 368 | cur_bb = cur_bb->linear_next; |
580 | 368 | } |
581 | 75 | } |
582 | 21.8k | } |
583 | | |
584 | | /* Annotates the control flow graph with predecessors. */ |
585 | 21.8k | static void add_predecessors(MVMThreadContext *tc, MVMSpeshGraph *g) { |
586 | 21.8k | MVMSpeshBB *cur_bb = g->entry; |
587 | 354k | while (cur_bb) { |
588 | 332k | MVMuint16 i; |
589 | 755k | for (i = 0; i < cur_bb->num_succ; i++) { |
590 | 423k | MVMSpeshBB *tgt = cur_bb->succ[i]; |
591 | 423k | MVMSpeshBB **new_pred = MVM_spesh_alloc(tc, g, |
592 | 423k | (tgt->num_pred + 1) * sizeof(MVMSpeshBB *)); |
593 | 423k | if (tgt->num_pred) |
594 | 112k | memcpy(new_pred, tgt->pred, tgt->num_pred * sizeof(MVMSpeshBB *)); |
595 | 423k | new_pred[tgt->num_pred] = cur_bb; |
596 | 423k | tgt->pred = new_pred; |
597 | 423k | tgt->num_pred++; |
598 | 423k | } |
599 | 332k | cur_bb = cur_bb->linear_next; |
600 | 332k | } |
601 | 21.8k | } |
602 | | |
603 | | /* Produces an array of the basic blocks, sorted in reverse postorder from |
604 | | * the entry point. */ |
605 | 332k | static void dfs(MVMSpeshBB **rpo, MVMint32 *insert_pos, MVMuint8 *seen, MVMSpeshBB *bb) { |
606 | 332k | MVMint32 i; |
607 | 332k | seen[bb->idx] = 1; |
608 | 755k | for (i = 0; i < bb->num_succ; i++) { |
609 | 423k | MVMSpeshBB *succ = bb->succ[i]; |
610 | 423k | if (!seen[succ->idx]) |
611 | 310k | dfs(rpo, insert_pos, seen, succ); |
612 | 423k | } |
613 | 332k | rpo[*insert_pos] = bb; |
614 | 332k | bb->rpo_idx = *insert_pos; |
615 | 332k | (*insert_pos)--; |
616 | 332k | } |
617 | 21.8k | static MVMSpeshBB ** reverse_postorder(MVMThreadContext *tc, MVMSpeshGraph *g) { |
618 | 21.8k | MVMSpeshBB **rpo = MVM_calloc(g->num_bbs, sizeof(MVMSpeshBB *)); |
619 | 21.8k | MVMuint8 *seen = MVM_calloc(g->num_bbs, 1); |
620 | 21.8k | MVMint32 ins = g->num_bbs - 1; |
621 | 21.8k | dfs(rpo, &ins, seen, g->entry); |
622 | 21.8k | MVM_free(seen); |
623 | 21.8k | if (ins != -1) { |
624 | 0 | char *dump_msg = MVM_spesh_dump(tc, g); |
625 | 0 | printf("%s", dump_msg); |
626 | 0 | MVM_free(dump_msg); |
627 | 0 | MVM_spesh_graph_destroy(tc, g); |
628 | 0 | MVM_oops(tc, "Spesh: reverse postorder calculation failed"); |
629 | 0 | } |
630 | 21.8k | return rpo; |
631 | 21.8k | } |
632 | | |
633 | | /* 2-finger intersection algorithm, to find new immediate dominator. */ |
634 | 724k | static void iter_check(MVMThreadContext *tc, MVMSpeshGraph *g, MVMSpeshBB **rpo, MVMint32 *doms, MVMint32 iters) { |
635 | 724k | if (iters > 100000) { |
636 | 0 | #ifdef NDEBUG |
637 | | MVMint32 k; |
638 | | char *dump_msg = MVM_spesh_dump(tc, g); |
639 | | printf("%s", dump_msg); |
640 | | MVM_free(dump_msg); |
641 | | printf("RPO: "); |
642 | | for (k = 0; k < g->num_bbs; k++) |
643 | | printf("%d, ", rpo[k]->idx); |
644 | | printf("\n"); |
645 | | printf("Doms: "); |
646 | | for (k = 0; k < g->num_bbs; k++) |
647 | | printf("%d (%d), ", doms[k], doms[k] >= 0 ? rpo[doms[k]]->idx : -1); |
648 | | printf("\n"); |
649 | | #endif |
650 | 0 | MVM_spesh_graph_destroy(tc, g); |
651 | 0 | MVM_oops(tc, "Spesh: dominator intersection went infinite"); |
652 | 0 | } |
653 | 724k | } |
654 | 229k | static MVMint32 intersect(MVMThreadContext *tc, MVMSpeshGraph *g, MVMSpeshBB **rpo, MVMint32 *doms, MVMint32 finger1, MVMint32 finger2) { |
655 | 229k | MVMint32 iters = 0; |
656 | 464k | while (finger1 != finger2) { |
657 | 797k | while (finger1 > finger2) { |
658 | 561k | iter_check(tc, g, rpo, doms, iters++); |
659 | 561k | finger1 = doms[finger1]; |
660 | 561k | } |
661 | 398k | while (finger2 > finger1) { |
662 | 163k | iter_check(tc, g, rpo, doms, iters++); |
663 | 163k | finger2 = doms[finger2]; |
664 | 163k | } |
665 | 235k | } |
666 | 229k | return finger1; |
667 | 229k | } |
668 | | |
669 | | /* Computes dominator information about the basic blocks. */ |
670 | 21.8k | static MVMint32 * compute_dominators(MVMThreadContext *tc, MVMSpeshGraph *g, MVMSpeshBB **rpo) { |
671 | 21.8k | MVMint32 i, j, changed; |
672 | 21.8k | |
673 | 21.8k | /* Create result list, with all initialized to undefined (use -1, as it's |
674 | 21.8k | * not a valid basic block index). Start node dominates itself. */ |
675 | 21.8k | MVMint32 *doms = MVM_malloc(g->num_bbs * sizeof(MVMint32)); |
676 | 21.8k | doms[0] = 0; |
677 | 332k | for (i = 1; i < g->num_bbs; i++) |
678 | 310k | doms[i] = -1; |
679 | 21.8k | |
680 | 21.8k | /* Iterate to fixed point. */ |
681 | 21.8k | changed = 1; |
682 | 65.8k | while (changed) { |
683 | 43.9k | changed = 0; |
684 | 43.9k | |
685 | 43.9k | /* Visit all except the start node in reverse postorder. */ |
686 | 694k | for (i = 1; i < g->num_bbs; i++) { |
687 | 650k | MVMSpeshBB *b = rpo[i]; |
688 | 650k | |
689 | 650k | /* See if there's a better dominator. */ |
690 | 650k | MVMint32 chosen_pred = -1; |
691 | 650k | MVMint32 new_idom; |
692 | 650k | for (j = 0; j < b->num_pred; j++) { |
693 | 650k | new_idom = b->pred[j]->rpo_idx; |
694 | 650k | if (doms[new_idom] != -1) |
695 | 650k | { |
696 | 650k | chosen_pred = j; |
697 | 650k | break; |
698 | 650k | } |
699 | 650k | } |
700 | 650k | if (chosen_pred == -1) { |
701 | 0 | MVM_spesh_graph_destroy(tc, g); |
702 | 0 | MVM_oops(tc, "Spesh: could not find processed initial dominator"); |
703 | 0 | } |
704 | 1.54M | for (j = 0; j < b->num_pred; j++) { |
705 | 889k | if (j != chosen_pred) { |
706 | 239k | MVMint32 p_idx = b->pred[j]->rpo_idx; |
707 | 239k | if (doms[p_idx] != -1) |
708 | 229k | new_idom = intersect(tc, g, rpo, doms, p_idx, new_idom); |
709 | 239k | } |
710 | 889k | } |
711 | 650k | if (doms[i] != new_idom) { |
712 | 311k | doms[i] = new_idom; |
713 | 311k | changed = 1; |
714 | 311k | } |
715 | 650k | } |
716 | 43.9k | } |
717 | 21.8k | |
718 | 21.8k | return doms; |
719 | 21.8k | } |
720 | | |
721 | | /* Builds the dominance tree children lists for each node. */ |
722 | 310k | static void add_child(MVMThreadContext *tc, MVMSpeshGraph *g, MVMSpeshBB *target, MVMSpeshBB *to_add) { |
723 | 310k | MVMSpeshBB **new_children; |
724 | 310k | MVMint32 i; |
725 | 310k | |
726 | 310k | /* Already in the child list? */ |
727 | 555k | for (i = 0; i < target->num_children; i++) |
728 | 245k | if (target->children[i] == to_add) |
729 | 0 | return; |
730 | 310k | |
731 | 310k | /* Nope, so insert. */ |
732 | 310k | new_children = MVM_spesh_alloc(tc, g, (target->num_children + 1) * sizeof(MVMSpeshBB *)); |
733 | 310k | if (target->num_children) |
734 | 141k | memcpy(new_children, target->children, target->num_children * sizeof(MVMSpeshBB *)); |
735 | 310k | new_children[target->num_children] = to_add; |
736 | 310k | target->children = new_children; |
737 | 310k | target->num_children++; |
738 | 310k | } |
739 | 21.8k | static void add_children(MVMThreadContext *tc, MVMSpeshGraph *g, MVMSpeshBB **rpo, MVMint32 *doms) { |
740 | 21.8k | MVMint32 i; |
741 | 354k | for (i = 0; i < g->num_bbs; i++) { |
742 | 332k | MVMSpeshBB *bb = rpo[i]; |
743 | 332k | MVMint32 idom = doms[i]; |
744 | 332k | if (idom != i) |
745 | 310k | add_child(tc, g, rpo[idom], bb); |
746 | 332k | } |
747 | 21.8k | } |
748 | | |
749 | | /* Builds the dominance frontier set for each node. */ |
750 | 366k | static void add_to_frontier_set(MVMThreadContext *tc, MVMSpeshGraph *g, MVMSpeshBB *target, MVMSpeshBB *to_add) { |
751 | 366k | MVMSpeshBB **new_df; |
752 | 366k | MVMint32 i; |
753 | 366k | |
754 | 366k | /* Already in the set? */ |
755 | 508k | for (i = 0; i < target->num_df; i++) |
756 | 214k | if (target->df[i] == to_add) |
757 | 72.7k | return; |
758 | 366k | |
759 | 366k | /* Nope, so insert. */ |
760 | 293k | new_df = MVM_spesh_alloc(tc, g, (target->num_df + 1) * sizeof(MVMSpeshBB *)); |
761 | 293k | if (target->num_df) |
762 | 35.6k | memcpy(new_df, target->df, target->num_df * sizeof(MVMSpeshBB *)); |
763 | 293k | new_df[target->num_df] = to_add; |
764 | 293k | target->df = new_df; |
765 | 293k | target->num_df++; |
766 | 293k | } |
767 | 21.8k | static void add_dominance_frontiers(MVMThreadContext *tc, MVMSpeshGraph *g, MVMSpeshBB **rpo, MVMint32 *doms) { |
768 | 21.8k | MVMint32 j; |
769 | 21.8k | MVMSpeshBB *b = g->entry; |
770 | 354k | while (b) { |
771 | 332k | if (b->num_pred >= 2) { /* Thus it's a join point */ |
772 | 297k | for (j = 0; j < b->num_pred; j++) { |
773 | 205k | MVMint32 runner = b->pred[j]->rpo_idx; |
774 | 205k | MVMint32 finish_line = doms[b->rpo_idx]; |
775 | 571k | while (runner != finish_line) { |
776 | 366k | add_to_frontier_set(tc, g, rpo[runner], b); |
777 | 366k | runner = doms[runner]; |
778 | 366k | } |
779 | 205k | } |
780 | 92.4k | } |
781 | 332k | b = b->linear_next; |
782 | 332k | } |
783 | 21.8k | } |
784 | | |
785 | | /* Per-local SSA info. */ |
786 | | typedef struct { |
787 | | /* Nodes that assign to the variable. */ |
788 | | MVMSpeshBB **ass_nodes; |
789 | | MVMuint16 num_ass_nodes; |
790 | | |
791 | | /* Count of processed assignments aka. C(V). */ |
792 | | MVMint32 count; |
793 | | |
794 | | /* Stack of integers aka. S(V). */ |
795 | | MVMint32 *stack; |
796 | | MVMint32 stack_top; |
797 | | MVMint32 stack_alloc; |
798 | | } SSAVarInfo; |
799 | | |
800 | | /* Creates an SSAVarInfo for each local, initializing it with a list of nodes |
801 | | * that assign to the local. */ |
802 | 21.8k | static SSAVarInfo * initialize_ssa_var_info(MVMThreadContext *tc, MVMSpeshGraph *g) { |
803 | 21.8k | SSAVarInfo *var_info = MVM_calloc(g->num_locals, sizeof(SSAVarInfo)); |
804 | 21.8k | MVMint32 i; |
805 | 21.8k | |
806 | 21.8k | /* Visit all instructions, looking for local writes. */ |
807 | 21.8k | MVMSpeshBB *bb = g->entry; |
808 | 354k | while (bb) { |
809 | 332k | MVMSpeshIns *ins = bb->first_ins; |
810 | 1.82M | while (ins) { |
811 | 4.47M | for (i = 0; i < ins->info->num_operands; i++) { |
812 | 2.98M | if ((ins->info->operands[i] & MVM_operand_rw_mask) == MVM_operand_write_reg) { |
813 | 1.06M | MVMuint16 written = ins->operands[i].reg.orig; |
814 | 1.06M | MVMint32 found = 0; |
815 | 1.06M | MVMint32 j; |
816 | 6.83M | for (j = 0; j < var_info[written].num_ass_nodes; j++) |
817 | 6.04M | if (var_info[written].ass_nodes[j] == bb) { |
818 | 269k | found = 1; |
819 | 269k | break; |
820 | 269k | } |
821 | 1.06M | if (!found) { |
822 | 793k | if (var_info[written].num_ass_nodes % 8 == 0) { |
823 | 278k | MVMint32 new_size = var_info[written].num_ass_nodes + 8; |
824 | 278k | var_info[written].ass_nodes = MVM_realloc( |
825 | 278k | var_info[written].ass_nodes, |
826 | 278k | new_size * sizeof(MVMSpeshBB *)); |
827 | 278k | } |
828 | 793k | var_info[written].ass_nodes[var_info[written].num_ass_nodes] = bb; |
829 | 793k | var_info[written].num_ass_nodes++; |
830 | 793k | } |
831 | 1.06M | } |
832 | 2.98M | } |
833 | 1.48M | ins = ins->next; |
834 | 1.48M | } |
835 | 332k | bb = bb->linear_next; |
836 | 332k | } |
837 | 21.8k | |
838 | 21.8k | /* Set stack top to -1 sentinel for all nodes, and count = 1 (as we may |
839 | 21.8k | * read the default value of a register). */ |
840 | 279k | for (i = 0; i < g->num_locals; i++) { |
841 | 257k | var_info[i].count = 1; |
842 | 257k | var_info[i].stack_top = -1; |
843 | 257k | } |
844 | 21.8k | |
845 | 21.8k | return var_info; |
846 | 21.8k | } |
847 | | |
848 | 669k | MVMOpInfo *get_phi(MVMThreadContext *tc, MVMSpeshGraph *g, MVMuint32 nrargs) { |
849 | 669k | MVMOpInfo *result = NULL; |
850 | 669k | |
851 | 669k | /* Check number of args to phi isn't huge. */ |
852 | 669k | if (nrargs > 0xFFFF) |
853 | 0 | MVM_panic(1, "Spesh: SSA calculation failed; cannot allocate enormous PHI node"); |
854 | 669k | |
855 | 669k | /* Up to 64 args, almost every number is represented, but after that |
856 | 669k | * we have a sparse array through which we must search */ |
857 | 669k | if (nrargs - 2 < MVMPhiNodeCacheSparseBegin) { |
858 | 669k | result = &g->phi_infos[nrargs - 2]; |
859 | 0 | } else { |
860 | 0 | MVMint32 cache_idx; |
861 | 0 |
|
862 | 0 | for (cache_idx = MVMPhiNodeCacheSparseBegin; !result && cache_idx < MVMPhiNodeCacheSize; cache_idx++) { |
863 | 0 | if (g->phi_infos[cache_idx].opcode == MVM_SSA_PHI) { |
864 | 0 | if (g->phi_infos[cache_idx].num_operands == nrargs) { |
865 | 0 | result = &g->phi_infos[cache_idx]; |
866 | 0 | } |
867 | 0 | } else { |
868 | 0 | result = &g->phi_infos[cache_idx]; |
869 | 0 | } |
870 | 0 | } |
871 | 0 | } |
872 | 669k | |
873 | 669k | if (result == NULL) { |
874 | 0 | result = MVM_spesh_alloc(tc, g, sizeof(MVMOpInfo)); |
875 | 0 | result->opcode = 0; |
876 | 0 | } |
877 | 669k | |
878 | 669k | if (result->opcode != MVM_SSA_PHI) { |
879 | 17.2k | result->opcode = MVM_SSA_PHI; |
880 | 17.2k | result->name = "PHI"; |
881 | 17.2k | result->num_operands = nrargs; |
882 | 17.2k | } |
883 | 669k | |
884 | 669k | return result; |
885 | 669k | } |
886 | | |
887 | | /* Inserts SSA phi functions at the required places in the graph. */ |
888 | 669k | static void place_phi(MVMThreadContext *tc, MVMSpeshGraph *g, MVMSpeshBB *bb, MVMint32 n, MVMuint16 var) { |
889 | 669k | MVMint32 i; |
890 | 669k | MVMOpInfo *phi_op = get_phi(tc, g, n + 1); |
891 | 669k | MVMSpeshIns *ins = MVM_spesh_alloc(tc, g, sizeof(MVMSpeshIns)); |
892 | 669k | ins->info = phi_op; |
893 | 669k | ins->operands = MVM_spesh_alloc(tc, g, phi_op->num_operands * sizeof(MVMSpeshOperand)); |
894 | 3.13M | for (i = 0; i < phi_op->num_operands; i++) |
895 | 2.46M | ins->operands[i].reg.orig = var; |
896 | 669k | ins->next = bb->first_ins; |
897 | 669k | bb->first_ins->prev = ins; |
898 | 669k | bb->first_ins = ins; |
899 | 669k | } |
900 | 21.8k | static void insert_phi_functions(MVMThreadContext *tc, MVMSpeshGraph *g, SSAVarInfo *var_info) { |
901 | 21.8k | MVMint32 *has_already = MVM_calloc(g->num_bbs, sizeof(MVMint32)); |
902 | 21.8k | MVMint32 *work = MVM_calloc(g->num_bbs, sizeof(MVMint32)); |
903 | 21.8k | MVMSpeshBB **worklist = MVM_calloc(g->num_bbs, sizeof(MVMSpeshBB *)); |
904 | 21.8k | MVMint32 worklist_top = 0; |
905 | 21.8k | MVMint32 iter_count = 0; |
906 | 21.8k | |
907 | 21.8k | /* Go over all locals. */ |
908 | 21.8k | MVMint32 var, i, j, found; |
909 | 279k | for (var = 0; var < g->num_locals; var++) { |
910 | 257k | /* Move to next iteration. */ |
911 | 257k | iter_count++; |
912 | 257k | |
913 | 257k | /* Add blocks assigning to this variable to the worklist. */ |
914 | 1.05M | for (i = 0; i < var_info[var].num_ass_nodes; i++) { |
915 | 793k | MVMSpeshBB *bb = var_info[var].ass_nodes[i]; |
916 | 793k | work[bb->idx] = iter_count; |
917 | 793k | worklist[worklist_top++] = bb; /* Algo unions, but ass_nodes unique */ |
918 | 793k | } |
919 | 257k | |
920 | 257k | /* Process the worklist. */ |
921 | 1.65M | while (worklist_top) { |
922 | 1.39M | MVMSpeshBB *x = worklist[--worklist_top]; |
923 | 2.70M | for (i = 0; i < x->num_df; i++) { |
924 | 1.31M | MVMSpeshBB *y = x->df[i]; |
925 | 1.31M | if (has_already[y->idx] < iter_count) { |
926 | 669k | /* Place phi function, and mark we have. */ |
927 | 669k | place_phi(tc, g, y, y->num_pred, var); |
928 | 669k | has_already[y->idx] = iter_count; |
929 | 669k | |
930 | 669k | /* Add this block to worklist if needed. */ |
931 | 669k | if (work[y->idx] < iter_count) { |
932 | 598k | work[y->idx] = iter_count; |
933 | 598k | found = 0; |
934 | 4.01M | for (j = 0; j < worklist_top; j++) |
935 | 3.41M | if (worklist[j] == y) { |
936 | 0 | found = 1; |
937 | 0 | break; |
938 | 0 | } |
939 | 598k | if (!found) |
940 | 598k | worklist[worklist_top++] = y; |
941 | 598k | } |
942 | 669k | } |
943 | 1.31M | } |
944 | 1.39M | } |
945 | 257k | } |
946 | 21.8k | |
947 | 21.8k | MVM_free(has_already); |
948 | 21.8k | MVM_free(work); |
949 | 21.8k | MVM_free(worklist); |
950 | 21.8k | } |
951 | | |
952 | | /* Renames the local variables such that we end up with SSA form. */ |
953 | 423k | static MVMint32 which_pred(MVMThreadContext *tc, MVMSpeshGraph *g, MVMSpeshBB *y, MVMSpeshBB *x) { |
954 | 423k | MVMint32 i; |
955 | 597k | for (i = 0; i < y->num_pred; i++) |
956 | 597k | if (y->pred[i] == x) |
957 | 423k | return i; |
958 | 0 | MVM_spesh_graph_destroy(tc, g); |
959 | 0 | MVM_oops(tc, "Spesh: which_pred failed to find x"); |
960 | 0 | } |
961 | 332k | static void rename_locals(MVMThreadContext *tc, MVMSpeshGraph *g, SSAVarInfo *var_info, MVMSpeshBB *x) { |
962 | 332k | MVMint32 i; |
963 | 332k | |
964 | 332k | /* Visit instructions and do renames in normal (non-phi) instructions. */ |
965 | 332k | MVMSpeshIns *a = x->first_ins; |
966 | 2.49M | while (a) { |
967 | 2.15M | /* Rename reads, provided it's not a PHI. */ |
968 | 2.15M | MVMint32 is_phi = a->info->opcode == MVM_SSA_PHI; |
969 | 2.15M | if (!is_phi) { |
970 | 4.47M | for (i = 0; i < a->info->num_operands; i++) { |
971 | 2.98M | if ((a->info->operands[i] & MVM_operand_rw_mask) == MVM_operand_read_reg) { |
972 | 1.12M | MVMuint16 orig = a->operands[i].reg.orig; |
973 | 1.12M | MVMint32 st = var_info[orig].stack_top; |
974 | 1.12M | if (st >= 0) |
975 | 1.12M | a->operands[i].reg.i = var_info[orig].stack[st]; |
976 | 1.12M | else |
977 | 0 | a->operands[i].reg.i = 0; |
978 | 1.12M | } |
979 | 2.98M | } |
980 | 1.48M | } |
981 | 2.15M | |
982 | 2.15M | /* Rename writes. */ |
983 | 5.14M | for (i = 0; i < a->info->num_operands; i++) { |
984 | 3.65M | if (is_phi || (a->info->operands[i] & MVM_operand_rw_mask) == MVM_operand_write_reg) { |
985 | 1.73M | MVMuint16 orig = a->operands[i].reg.orig; |
986 | 1.73M | MVMint32 reg_i = var_info[orig].count; |
987 | 1.73M | a->operands[i].reg.i = reg_i; |
988 | 1.73M | if (var_info[orig].stack_top + 1 >= var_info[orig].stack_alloc) { |
989 | 269k | if (var_info[orig].stack_alloc) |
990 | 19.5k | var_info[orig].stack_alloc *= 2; |
991 | 269k | else |
992 | 249k | var_info[orig].stack_alloc = 8; |
993 | 269k | var_info[orig].stack = MVM_realloc(var_info[orig].stack, |
994 | 269k | var_info[orig].stack_alloc * sizeof(MVMint32)); |
995 | 269k | } |
996 | 1.73M | var_info[orig].stack[++var_info[orig].stack_top] = reg_i; |
997 | 1.73M | var_info[orig].count++; |
998 | 1.73M | } |
999 | 3.65M | if (is_phi) |
1000 | 669k | break; |
1001 | 3.65M | } |
1002 | 2.15M | |
1003 | 2.15M | a = a->next; |
1004 | 2.15M | } |
1005 | 332k | |
1006 | 332k | /* Visit successors and update their phi functions. */ |
1007 | 755k | for (i = 0; i < x->num_succ; i++) { |
1008 | 423k | MVMSpeshBB *y = x->succ[i]; |
1009 | 423k | MVMint32 j = which_pred(tc, g, y, x); |
1010 | 423k | MVMSpeshIns *p = y->first_ins; |
1011 | 2.22M | while (p && p->info->opcode == MVM_SSA_PHI) { |
1012 | 1.79M | MVMuint16 orig = p->operands[j + 1].reg.orig; |
1013 | 1.79M | MVMint32 st = var_info[orig].stack_top; |
1014 | 1.79M | if (st >= 0) |
1015 | 1.46M | p->operands[j + 1].reg.i = var_info[orig].stack[st]; |
1016 | 1.79M | else |
1017 | 329k | p->operands[j + 1].reg.i = 0; |
1018 | 1.79M | p = p->next; |
1019 | 1.79M | } |
1020 | 423k | } |
1021 | 332k | |
1022 | 332k | /* Rename for all the children in the dominator tree. */ |
1023 | 642k | for (i = 0; i < x->num_children; i++) |
1024 | 310k | rename_locals(tc, g, var_info, x->children[i]); |
1025 | 332k | |
1026 | 332k | /* Go over assignments and pop new variable names. */ |
1027 | 332k | a = x->first_ins; |
1028 | 2.49M | while (a) { |
1029 | 2.15M | MVMint32 is_phi = a->info->opcode == MVM_SSA_PHI; |
1030 | 5.14M | for (i = 0; i < a->info->num_operands; i++) { |
1031 | 3.65M | if (is_phi || (a->info->operands[i] & MVM_operand_rw_mask) == MVM_operand_write_reg) { |
1032 | 1.73M | MVMuint16 orig = a->operands[i].reg.orig; |
1033 | 1.73M | var_info[orig].stack_top--; |
1034 | 1.73M | } |
1035 | 3.65M | if (is_phi) |
1036 | 669k | break; |
1037 | 3.65M | } |
1038 | 2.15M | a = a->next; |
1039 | 2.15M | } |
1040 | 332k | } |
1041 | | |
1042 | | /* Transforms a spesh graph into SSA form. After this, the graph will have all |
1043 | | * register accesses given an SSA "version", and phi instructions inserted as |
1044 | | * needed. */ |
1045 | 21.8k | static void ssa(MVMThreadContext *tc, MVMSpeshGraph *g) { |
1046 | 21.8k | SSAVarInfo *var_info; |
1047 | 21.8k | MVMint32 i, num_locals; |
1048 | 21.8k | |
1049 | 21.8k | /* Compute dominance frontiers. */ |
1050 | 21.8k | MVMSpeshBB **rpo = reverse_postorder(tc, g); |
1051 | 21.8k | MVMint32 *doms = compute_dominators(tc, g, rpo); |
1052 | 21.8k | add_children(tc, g, rpo, doms); |
1053 | 21.8k | add_dominance_frontiers(tc, g, rpo, doms); |
1054 | 21.8k | MVM_free(rpo); |
1055 | 21.8k | MVM_free(doms); |
1056 | 21.8k | |
1057 | 21.8k | /* Initialize per-local data for SSA analysis. */ |
1058 | 21.8k | var_info = initialize_ssa_var_info(tc, g); |
1059 | 21.8k | |
1060 | 21.8k | /* Compute SSA itself. */ |
1061 | 21.8k | insert_phi_functions(tc, g, var_info); |
1062 | 21.8k | rename_locals(tc, g, var_info, g->entry); |
1063 | 21.8k | |
1064 | 21.8k | /* Allocate space for spesh facts for each local; clean up stacks while |
1065 | 21.8k | * we're at it. */ |
1066 | 21.8k | num_locals = g->num_locals; |
1067 | 21.8k | g->facts = MVM_spesh_alloc(tc, g, num_locals * sizeof(MVMSpeshFacts *)); |
1068 | 21.8k | g->fact_counts = MVM_spesh_alloc(tc, g, num_locals * sizeof(MVMuint16)); |
1069 | 279k | for (i = 0; i < num_locals; i++) { |
1070 | 257k | g->fact_counts[i] = var_info[i].count; |
1071 | 257k | g->facts[i] = MVM_spesh_alloc(tc, g, var_info[i].count * sizeof(MVMSpeshFacts)); |
1072 | 257k | if (var_info[i].stack_alloc) { |
1073 | 249k | MVM_free(var_info[i].stack); |
1074 | 249k | MVM_free(var_info[i].ass_nodes); |
1075 | 249k | } |
1076 | 257k | } |
1077 | 21.8k | MVM_free(var_info); |
1078 | 21.8k | } |
1079 | | |
1080 | | /* Takes a static frame and creates a spesh graph for it. */ |
1081 | | MVMSpeshGraph * MVM_spesh_graph_create(MVMThreadContext *tc, MVMStaticFrame *sf, |
1082 | 17.1k | MVMuint32 cfg_only, MVMuint32 insert_object_nulls) { |
1083 | 17.1k | /* Create top-level graph object. */ |
1084 | 17.1k | MVMSpeshGraph *g = MVM_calloc(1, sizeof(MVMSpeshGraph)); |
1085 | 17.1k | g->sf = sf; |
1086 | 17.1k | g->bytecode = sf->body.bytecode; |
1087 | 17.1k | g->bytecode_size = sf->body.bytecode_size; |
1088 | 17.1k | g->handlers = sf->body.handlers; |
1089 | 17.1k | g->num_handlers = sf->body.num_handlers; |
1090 | 17.1k | g->num_locals = sf->body.num_locals; |
1091 | 17.1k | g->num_lexicals = sf->body.num_lexicals; |
1092 | 17.1k | g->phi_infos = MVM_spesh_alloc(tc, g, MVMPhiNodeCacheSize * sizeof(MVMOpInfo)); |
1093 | 17.1k | |
1094 | 17.1k | /* Ensure the frame is validated, since we'll rely on this. */ |
1095 | 17.1k | if (sf->body.instrumentation_level == 0) { |
1096 | 0 | MVM_spesh_graph_destroy(tc, g); |
1097 | 0 | MVM_oops(tc, "Spesh: cannot build CFG from unvalidated frame"); |
1098 | 0 | } |
1099 | 17.1k | |
1100 | 17.1k | /* Build the CFG out of the static frame, and transform it to SSA. */ |
1101 | 17.1k | build_cfg(tc, g, sf, NULL, 0); |
1102 | 17.1k | if (insert_object_nulls) |
1103 | 17.1k | insert_object_null_instructions(tc, g); |
1104 | 17.1k | if (!cfg_only) { |
1105 | 17.1k | eliminate_dead(tc, g); |
1106 | 17.1k | add_predecessors(tc, g); |
1107 | 17.1k | ssa(tc, g); |
1108 | 17.1k | } |
1109 | 17.1k | |
1110 | 17.1k | /* Hand back the completed graph. */ |
1111 | 17.1k | return g; |
1112 | 17.1k | } |
1113 | | |
1114 | | /* Takes a static frame and creates a spesh graph for it. */ |
1115 | | MVMSpeshGraph * MVM_spesh_graph_create_from_cand(MVMThreadContext *tc, MVMStaticFrame *sf, |
1116 | 4.66k | MVMSpeshCandidate *cand, MVMuint32 cfg_only) { |
1117 | 4.66k | /* Create top-level graph object. */ |
1118 | 4.66k | MVMSpeshGraph *g = MVM_calloc(1, sizeof(MVMSpeshGraph)); |
1119 | 4.66k | g->sf = sf; |
1120 | 4.66k | g->bytecode = cand->bytecode; |
1121 | 4.66k | g->bytecode_size = cand->bytecode_size; |
1122 | 4.66k | g->handlers = cand->handlers; |
1123 | 4.66k | g->num_handlers = cand->num_handlers; |
1124 | 4.66k | g->num_locals = cand->num_locals; |
1125 | 4.66k | g->num_lexicals = cand->num_lexicals; |
1126 | 4.66k | g->inlines = cand->inlines; |
1127 | 4.66k | g->num_inlines = cand->num_inlines; |
1128 | 4.66k | g->deopt_addrs = cand->deopts; |
1129 | 4.66k | g->num_deopt_addrs = cand->num_deopts; |
1130 | 4.66k | g->alloc_deopt_addrs = cand->num_deopts; |
1131 | 4.66k | g->local_types = cand->local_types; |
1132 | 4.66k | g->lexical_types = cand->lexical_types; |
1133 | 4.66k | g->spesh_slots = cand->spesh_slots; |
1134 | 4.66k | g->num_spesh_slots = cand->num_spesh_slots; |
1135 | 4.66k | g->phi_infos = MVM_spesh_alloc(tc, g, MVMPhiNodeCacheSize * sizeof(MVMOpInfo)); |
1136 | 4.66k | g->cand = cand; |
1137 | 4.66k | |
1138 | 4.66k | /* Ensure the frame is validated, since we'll rely on this. */ |
1139 | 4.66k | if (sf->body.instrumentation_level == 0) { |
1140 | 0 | MVM_spesh_graph_destroy(tc, g); |
1141 | 0 | MVM_oops(tc, "Spesh: cannot build CFG from unvalidated frame"); |
1142 | 0 | } |
1143 | 4.66k | |
1144 | 4.66k | /* Build the CFG out of the static frame, and transform it to SSA. */ |
1145 | 4.66k | build_cfg(tc, g, sf, cand->deopts, cand->num_deopts); |
1146 | 4.66k | if (!cfg_only) { |
1147 | 4.66k | eliminate_dead(tc, g); |
1148 | 4.66k | add_predecessors(tc, g); |
1149 | 4.66k | ssa(tc, g); |
1150 | 4.66k | } |
1151 | 4.66k | |
1152 | 4.66k | /* Hand back the completed graph. */ |
1153 | 4.66k | return g; |
1154 | 4.66k | } |
1155 | | |
1156 | | /* Marks GCables held in a spesh graph. */ |
1157 | 1.97k | void MVM_spesh_graph_mark(MVMThreadContext *tc, MVMSpeshGraph *g, MVMGCWorklist *worklist) { |
1158 | 1.97k | MVMuint16 i, j, num_locals, num_facts, *local_types; |
1159 | 1.97k | |
1160 | 1.97k | /* Mark static frame. */ |
1161 | 1.97k | MVM_gc_worklist_add(tc, worklist, &g->sf); |
1162 | 1.97k | |
1163 | 1.97k | /* Mark facts. */ |
1164 | 1.97k | num_locals = g->num_locals; |
1165 | 1.62k | local_types = g->local_types ? g->local_types : g->sf->body.local_types; |
1166 | 25.5k | for (i = 0; i < num_locals; i++) { |
1167 | 23.6k | num_facts = g->fact_counts[i]; |
1168 | 199k | for (j = 0; j < num_facts; j++) { |
1169 | 175k | MVMint32 flags = g->facts[i][j].flags; |
1170 | 175k | if (flags & MVM_SPESH_FACT_KNOWN_TYPE) |
1171 | 3.28k | MVM_gc_worklist_add(tc, worklist, &(g->facts[i][j].type)); |
1172 | 175k | if (flags & MVM_SPESH_FACT_KNOWN_DECONT_TYPE) |
1173 | 0 | MVM_gc_worklist_add(tc, worklist, &(g->facts[i][j].decont_type)); |
1174 | 175k | if (flags & MVM_SPESH_FACT_KNOWN_VALUE) { |
1175 | 0 | if (local_types[i] == MVM_reg_obj) |
1176 | 0 | MVM_gc_worklist_add(tc, worklist, &(g->facts[i][j].value.o)); |
1177 | 0 | else if (local_types[i] == MVM_reg_str) |
1178 | 0 | MVM_gc_worklist_add(tc, worklist, &(g->facts[i][j].value.s)); |
1179 | 0 | } |
1180 | 175k | } |
1181 | 23.6k | } |
1182 | 1.97k | } |
1183 | | |
1184 | | /* Destroys a spesh graph, deallocating all its associated memory. */ |
1185 | 18.5k | void MVM_spesh_graph_destroy(MVMThreadContext *tc, MVMSpeshGraph *g) { |
1186 | 18.5k | /* Free all of the allocated node memory. */ |
1187 | 18.5k | MVM_region_destroy(tc, &g->region_alloc); |
1188 | 18.5k | /* Free handlers array, if different from the static frame. */ |
1189 | 18.5k | if (!g->cand && g->handlers != g->sf->body.handlers) |
1190 | 302 | MVM_free(g->handlers); |
1191 | 18.5k | |
1192 | 18.5k | /* Free the graph itself. */ |
1193 | 18.5k | MVM_free(g); |
1194 | 18.5k | } |