/home/travis/build/MoarVM/MoarVM/src/spesh/arg_guard.c
Line | Count | Source (jump to first uncovered line) |
1 | | #include "moar.h" |
2 | | |
3 | | /* Calculates the maxium number of new nodes that might be needed to add a |
4 | | * guard for the specified callsite and types. (It may be less in reality |
5 | | * due to head sharing.) */ |
6 | 43.2k | static size_t max_new_nodes(MVMCallsite *cs, MVMSpeshStatsType *types) { |
7 | 43.2k | size_t needed = 2; /* One for callsite, one for result */ |
8 | 43.2k | if (types) { |
9 | 39.1k | MVMuint32 i; |
10 | 116k | for (i = 0; i < cs->flag_count; i++) { |
11 | 76.8k | if (cs->arg_flags[i] & MVM_CALLSITE_ARG_OBJ) { |
12 | 60.5k | if (types[i].type) |
13 | 60.5k | needed += 2; /* One to read arg, one to check */ |
14 | 60.5k | if (types[i].rw_cont) |
15 | 0 | needed++; |
16 | 60.5k | if (types[i].decont_type) |
17 | 0 | needed += 2; /* One to decont, one to check */ |
18 | 60.5k | } |
19 | 76.8k | } |
20 | 39.1k | } |
21 | 43.2k | return needed + 1; |
22 | 43.2k | } |
23 | | |
24 | | /* Allocates a new set of spesh argument guards extended by the extras amount |
25 | | * of nodes specified. Copies the original argument guards into it. */ |
26 | | static MVMSpeshArgGuard * copy_and_extend(MVMThreadContext *tc, MVMSpeshArgGuard *orig, |
27 | 43.2k | size_t extra) { |
28 | 24.7k | size_t orig_nodes = orig ? orig->used_nodes : 0; |
29 | 43.2k | size_t total_nodes = orig_nodes + extra; |
30 | 43.2k | size_t node_size = total_nodes * sizeof(MVMSpeshArgGuardNode); |
31 | 43.2k | size_t size = sizeof(MVMSpeshArgGuard) + node_size; |
32 | 43.2k | MVMSpeshArgGuard *copy = MVM_fixed_size_alloc(tc, tc->instance->fsa, size); |
33 | 43.2k | copy->nodes = (MVMSpeshArgGuardNode *)((char *)copy + sizeof(MVMSpeshArgGuard)); |
34 | 43.2k | copy->used_nodes = orig_nodes; |
35 | 43.2k | copy->num_nodes = total_nodes; |
36 | 43.2k | if (orig_nodes) |
37 | 24.7k | memcpy(copy->nodes, orig->nodes, orig_nodes * sizeof(MVMSpeshArgGuardNode)); |
38 | 43.2k | return copy; |
39 | 43.2k | } |
40 | | |
41 | | /* Locates an existing node that matches a particular callsite. If there is |
42 | | * no such node, adds it. */ |
43 | 43.2k | static MVMuint32 get_callsite_node(MVMThreadContext *tc, MVMSpeshArgGuard *ag, MVMCallsite *cs) { |
44 | 43.2k | MVMuint32 have_fixup_node = 0; |
45 | 43.2k | MVMuint32 fixup_node; |
46 | 43.2k | if (ag->used_nodes) { |
47 | 24.7k | MVMuint32 current_node = 0; |
48 | 27.0k | do { |
49 | 27.0k | MVMSpeshArgGuardNode *agn = &(ag->nodes[current_node]); |
50 | 27.0k | if (agn->op == MVM_SPESH_GUARD_OP_CALLSITE) { |
51 | 27.0k | /* If it matches, we've found it. */ |
52 | 27.0k | if (agn->cs == cs) |
53 | 23.4k | return current_node; |
54 | 27.0k | |
55 | 27.0k | /* Otherwise, treat this as the working fixup node, and take |
56 | 27.0k | * the no branch. */ |
57 | 3.67k | fixup_node = current_node; |
58 | 3.67k | have_fixup_node = 1; |
59 | 3.67k | current_node = agn->no; |
60 | 3.67k | } |
61 | 0 | else { |
62 | 0 | /* We only expect callsite nodes at the top level. */ |
63 | 0 | MVM_panic(1, "Spesh arg guard: unexpected callsite structure in tree"); |
64 | 0 | } |
65 | 3.67k | } while (current_node != 0); |
66 | 24.7k | } |
67 | 43.2k | |
68 | 43.2k | /* If we get here, we need to add a node for this callsite. */ |
69 | 19.7k | ag->nodes[ag->used_nodes].op = MVM_SPESH_GUARD_OP_CALLSITE; |
70 | 19.7k | ag->nodes[ag->used_nodes].cs = cs; |
71 | 19.7k | ag->nodes[ag->used_nodes].yes = 0; |
72 | 19.7k | ag->nodes[ag->used_nodes].no = 0; |
73 | 19.7k | if (have_fixup_node) |
74 | 1.29k | ag->nodes[fixup_node].no = ag->used_nodes; |
75 | 19.7k | return ag->used_nodes++; |
76 | 43.2k | } |
77 | | |
78 | | /* Resolves or inserts the argument load node. This is a little complex, in |
79 | | * that we may (though it should be quite unusual) have multiple starting |
80 | | * points in the argument list to consider. For example, there may be for |
81 | | * ($obj, $obj) specializations of (Foo, <no guard>) and (<no guard>, Foo). |
82 | | * In that case, we tweak the previous tree(s) of other starting points so |
83 | | * any "no result" points to instead try the added subtree. */ |
84 | | static MVMuint32 get_load_node(MVMThreadContext *tc, MVMSpeshArgGuard *ag, MVMuint32 base_node, |
85 | 60.5k | MVMuint16 arg_idx) { |
86 | 60.5k | MVMuint16 new_no = 0; |
87 | 60.5k | if (ag->nodes[base_node].yes) { |
88 | 33.1k | MVMuint32 check_node = ag->nodes[base_node].yes; |
89 | 33.1k | MVMSpeshArgGuardOp op = ag->nodes[check_node].op; |
90 | 33.1k | if (op == MVM_SPESH_GUARD_OP_LOAD_ARG) { |
91 | 33.1k | if (ag->nodes[check_node].arg_index == arg_idx) |
92 | 33.1k | return check_node; |
93 | 0 | MVM_panic(1, "Spesh arg guard: unimplemented sparse guard case"); |
94 | 0 | } |
95 | 0 | else if (op == MVM_SPESH_GUARD_OP_RESULT) { |
96 | 0 | new_no = check_node; |
97 | 0 | } |
98 | 0 | else { |
99 | 0 | MVM_panic(1, "Spesh arg guard: unexpected op %d in get_load_node", op); |
100 | 0 | } |
101 | 33.1k | } |
102 | 60.5k | |
103 | 60.5k | /* If we get here, need to add a new load node. */ |
104 | 27.3k | ag->nodes[ag->used_nodes].op = MVM_SPESH_GUARD_OP_LOAD_ARG; |
105 | 27.3k | ag->nodes[ag->used_nodes].arg_index = arg_idx; |
106 | 27.3k | ag->nodes[ag->used_nodes].yes = 0; |
107 | 27.3k | ag->nodes[ag->used_nodes].no = new_no; |
108 | 27.3k | ag->nodes[base_node].yes = ag->used_nodes; |
109 | 27.3k | return ag->used_nodes++; |
110 | 60.5k | } |
111 | | |
112 | | /* Resolves or inserts a node for testing the curernt type loaded into the |
113 | | * test buffer. If it needs to insert a new node, it chains it on to the |
114 | | * end of the existing set of type tests. */ |
115 | | static MVMuint32 get_type_check_node(MVMThreadContext *tc, MVMSpeshArgGuard *ag, |
116 | 60.5k | MVMuint32 base_node, MVMObject *type, MVMuint8 concrete) { |
117 | 60.5k | MVMuint32 current_node = ag->nodes[base_node].yes; |
118 | 60.5k | MVMuint32 have_fixup_node = 0; |
119 | 60.5k | MVMuint32 fixup_node; |
120 | 66.0k | while (current_node != 0) { |
121 | 36.4k | MVMSpeshArgGuardNode *agn = &(ag->nodes[current_node]); |
122 | 36.4k | if (agn->op == MVM_SPESH_GUARD_OP_STABLE_CONC) { |
123 | 31.7k | /* If it matches, we've found it. */ |
124 | 31.7k | if (concrete && agn->st == type->st) |
125 | 26.9k | return current_node; |
126 | 31.7k | |
127 | 31.7k | /* Otherwise, treat this as the working fixup node, and take |
128 | 31.7k | * the no branch. */ |
129 | 4.72k | fixup_node = current_node; |
130 | 4.72k | have_fixup_node = 1; |
131 | 4.72k | current_node = agn->no; |
132 | 4.72k | } |
133 | 4.72k | else if (agn->op == MVM_SPESH_GUARD_OP_STABLE_TYPE) { |
134 | 4.72k | /* If it matches, we've found it. */ |
135 | 4.72k | if (!concrete && agn->st == type->st) |
136 | 3.99k | return current_node; |
137 | 4.72k | |
138 | 4.72k | /* Otherwise, treat this as the working fixup node, and take |
139 | 4.72k | * the no branch. */ |
140 | 734 | fixup_node = current_node; |
141 | 734 | have_fixup_node = 1; |
142 | 734 | current_node = agn->no; |
143 | 734 | } |
144 | 0 | else { |
145 | 0 | /* We only expect type matching nodes at the top level. */ |
146 | 0 | MVM_panic(1, "Spesh arg guard: unexpected type structure in tree"); |
147 | 0 | } |
148 | 36.4k | } |
149 | 60.5k | |
150 | 60.5k | /* If we get here, we need to add a node for this callsite. */ |
151 | 29.5k | ag->nodes[ag->used_nodes].op = concrete |
152 | 26.4k | ? MVM_SPESH_GUARD_OP_STABLE_CONC |
153 | 3.16k | : MVM_SPESH_GUARD_OP_STABLE_TYPE; |
154 | 29.5k | ag->nodes[ag->used_nodes].st = type->st; |
155 | 29.5k | ag->nodes[ag->used_nodes].yes = 0; |
156 | 29.5k | ag->nodes[ag->used_nodes].no = 0; |
157 | 29.5k | if (have_fixup_node) |
158 | 2.19k | ag->nodes[fixup_node].no = ag->used_nodes; |
159 | 29.5k | else |
160 | 27.3k | ag->nodes[base_node].yes = ag->used_nodes; |
161 | 29.5k | return ag->used_nodes++; |
162 | 60.5k | } |
163 | | |
164 | | /* Resolves or inserts a guard for "is this an rw container" hanging off the |
165 | | * specified base node. We will always have the rw-or-not check right after |
166 | | * the container type check, so if there's already a "yes" branch off the base |
167 | | * node that is not an rw container check, we'll add the rw container check in |
168 | | * its place and attach the "no" branch to where it used to point. This means |
169 | | * we can know if there is such a node for this container type by just looking |
170 | | * at the "yes" branch of the base node we are passed. */ |
171 | | static MVMuint32 get_rw_cont_node(MVMThreadContext *tc, MVMSpeshArgGuard *ag, |
172 | 0 | MVMuint32 base_node) { |
173 | 0 | MVMuint32 yes_node = ag->nodes[base_node].yes; |
174 | 0 | if (yes_node && ag->nodes[yes_node].op == MVM_SPESH_GUARD_OP_DEREF_RW) |
175 | 0 | return yes_node; |
176 | 0 | ag->nodes[ag->used_nodes].op = MVM_SPESH_GUARD_OP_DEREF_RW; |
177 | 0 | ag->nodes[ag->used_nodes].offset = 0; /* TODO populate this properly */ |
178 | 0 | ag->nodes[ag->used_nodes].yes = 0; |
179 | 0 | ag->nodes[ag->used_nodes].no = yes_node; |
180 | 0 | ag->nodes[base_node].yes = ag->used_nodes; |
181 | 0 | return ag->used_nodes++; |
182 | 0 | } |
183 | | |
184 | | /* Resolves or inserts a guard op that decontainerizes the current test |
185 | | * register content. We only do this once before a possible chain of nodes |
186 | | * that test the decontainerized type. Therefore, we can expect that such a |
187 | | * node is already in the tree at this point, *or* that there is an RW |
188 | | * guard node and *then* the one we're looking for. */ |
189 | | static MVMuint32 get_decont_node(MVMThreadContext *tc, MVMSpeshArgGuard *ag, |
190 | 0 | MVMuint32 base_node) { |
191 | 0 | MVMuint32 check_node = ag->nodes[base_node].yes; |
192 | 0 | MVMuint32 update_no_node = 0; |
193 | 0 | if (check_node) { |
194 | 0 | if (ag->nodes[check_node].op == MVM_SPESH_GUARD_OP_DEREF_VALUE) { |
195 | 0 | return check_node; |
196 | 0 | } |
197 | 0 | else if (ag->nodes[check_node].op == MVM_SPESH_GUARD_OP_DEREF_RW) { |
198 | 0 | MVMuint32 no_node = ag->nodes[check_node].no; |
199 | 0 | if (no_node) { |
200 | 0 | if (ag->nodes[no_node].op == MVM_SPESH_GUARD_OP_DEREF_VALUE) |
201 | 0 | return no_node; |
202 | 0 | } |
203 | 0 | else { |
204 | 0 | update_no_node = check_node; |
205 | 0 | } |
206 | 0 | } |
207 | 0 | if (!update_no_node) |
208 | 0 | MVM_panic(1, "Spesh arg guard: unexpected tree structure adding deref value"); |
209 | 0 | } |
210 | 0 | ag->nodes[ag->used_nodes].op = MVM_SPESH_GUARD_OP_DEREF_VALUE; |
211 | 0 | ag->nodes[ag->used_nodes].offset = 0; /* TODO populate this properly */ |
212 | 0 | ag->nodes[ag->used_nodes].yes = 0; |
213 | 0 | ag->nodes[ag->used_nodes].no = 0; |
214 | 0 | if (update_no_node) |
215 | 0 | ag->nodes[update_no_node].no = ag->used_nodes; |
216 | 0 | else |
217 | 0 | ag->nodes[base_node].yes = ag->used_nodes; |
218 | 0 | return ag->used_nodes++; |
219 | 0 | } |
220 | | |
221 | | /* Resolves or inserts guards for the specified type information, rooted off |
222 | | * the given node. */ |
223 | | static MVMuint32 get_type_node(MVMThreadContext *tc, MVMSpeshArgGuard *ag, MVMuint32 base_node, |
224 | 60.5k | MVMSpeshStatsType *type, MVMuint16 arg_idx) { |
225 | 60.5k | MVMuint32 current_node = get_load_node(tc, ag, base_node, arg_idx); |
226 | 60.5k | current_node = get_type_check_node(tc, ag, current_node, type->type, type->type_concrete); |
227 | 60.5k | if (type->rw_cont) |
228 | 0 | current_node = get_rw_cont_node(tc, ag, current_node); |
229 | 60.5k | if (type->decont_type) { |
230 | 0 | current_node = get_decont_node(tc, ag, current_node); |
231 | 0 | current_node = get_type_check_node(tc, ag, current_node, type->decont_type, |
232 | 0 | type->decont_type_concrete); |
233 | 0 | } |
234 | 60.5k | return current_node; |
235 | 60.5k | } |
236 | | |
237 | | /* Inserts a guard for the specified types into the tree. */ |
238 | | static MVMint32 try_add_guard(MVMThreadContext *tc, MVMSpeshArgGuard *ag, MVMCallsite *cs, |
239 | 43.2k | MVMSpeshStatsType *types, MVMuint32 candidate) { |
240 | 43.2k | MVMuint32 current_node = get_callsite_node(tc, ag, cs); |
241 | 43.2k | if (types) { |
242 | 39.1k | /* We're adding a type-based result, and thus for a speculative |
243 | 39.1k | * specialization. Certain specializations come ahead of those, and |
244 | 39.1k | * hang off the callsite node; skip over any such node. */ |
245 | 39.1k | MVMuint16 arg_idx = 0; |
246 | 39.1k | MVMuint16 i; |
247 | 39.1k | if (ag->nodes[ag->nodes[current_node].yes].op == MVM_SPESH_GUARD_OP_CERTAIN_RESULT) |
248 | 732 | current_node = ag->nodes[current_node].yes; |
249 | 116k | for (i = 0; i < cs->flag_count; i++) { |
250 | 76.8k | if (cs->arg_flags[i] & MVM_CALLSITE_ARG_NAMED) |
251 | 2.85k | arg_idx++; /* Skip over name */ |
252 | 76.8k | if (cs->arg_flags[i] & MVM_CALLSITE_ARG_OBJ) { |
253 | 60.5k | MVMSpeshStatsType *type = &(types[i]); |
254 | 60.5k | if (type->type) |
255 | 60.5k | current_node = get_type_node(tc, ag, current_node, type, arg_idx); |
256 | 60.5k | } |
257 | 76.8k | arg_idx++; |
258 | 76.8k | } |
259 | 39.1k | if (ag->nodes[current_node].yes) |
260 | 18.3k | return 0; |
261 | 20.7k | ag->nodes[ag->used_nodes].op = MVM_SPESH_GUARD_OP_RESULT; |
262 | 20.7k | ag->nodes[ag->used_nodes].yes = 0; |
263 | 20.7k | ag->nodes[ag->used_nodes].no = 0; |
264 | 20.7k | } |
265 | 4.02k | else { |
266 | 4.02k | /* We're adding a certain result. If there already is such a node, we |
267 | 4.02k | * already have that specialization. Otherwise, we need to insert it |
268 | 4.02k | * and redirect the current_node's .yes to point to it, and it to |
269 | 4.02k | * point to whatever current_node's .yes used to point to (so it goes |
270 | 4.02k | * in ahead of type guards etc.). */ |
271 | 4.02k | if (ag->nodes[ag->nodes[current_node].yes].op == MVM_SPESH_GUARD_OP_CERTAIN_RESULT) |
272 | 2.29k | return 0; |
273 | 1.72k | ag->nodes[ag->used_nodes].op = MVM_SPESH_GUARD_OP_CERTAIN_RESULT; |
274 | 1.72k | ag->nodes[ag->used_nodes].yes = ag->nodes[current_node].yes; |
275 | 1.72k | ag->nodes[ag->used_nodes].no = 0; |
276 | 1.72k | } |
277 | 22.5k | ag->nodes[ag->used_nodes].result = candidate; |
278 | 22.5k | ag->nodes[current_node].yes = ag->used_nodes++; |
279 | 22.5k | return 1; |
280 | 43.2k | } |
281 | | |
282 | | /* Takes a pointer to a guard set. Replaces it with a guard set that also |
283 | | * includes a guard for the specified type tuple (passed with callsite to |
284 | | * know how many types are involved), and resolving to the specified spesh |
285 | | * candidate index. Any previous guard set will be scheduled for freeing at |
286 | | * the next safepoint. */ |
287 | | void MVM_spesh_arg_guard_add(MVMThreadContext *tc, MVMSpeshArgGuard **orig, |
288 | | MVMCallsite *cs, MVMSpeshStatsType *types, |
289 | 11.0k | MVMuint32 candidate) { |
290 | 11.0k | MVMSpeshArgGuard *new_guard = copy_and_extend(tc, *orig, max_new_nodes(cs, types)); |
291 | 11.0k | if (!try_add_guard(tc, new_guard, cs, types, candidate)) |
292 | 0 | MVM_panic(1, "Spesh arg guard: trying to add duplicate result for same guard"); |
293 | 11.0k | if (*orig) { |
294 | 2.29k | MVMSpeshArgGuard *prev = *orig; |
295 | 2.29k | *orig = new_guard; |
296 | 2.29k | MVM_spesh_arg_guard_destroy(tc, prev, 1); |
297 | 2.29k | } |
298 | 8.73k | else { |
299 | 8.73k | *orig = new_guard; |
300 | 8.73k | } |
301 | 11.0k | } |
302 | | |
303 | | /* Checks if we already have a guard that precisely matches the specified |
304 | | * pair of callsite and type tuple. This is a more exact check that "would |
305 | | * the guard match", since a less precise specialization would match if we |
306 | | * just ran the guard tree against the arguments. This answers the question of |
307 | | * "if I added this, would it collide with an existing entry" instead. */ |
308 | | MVMint32 MVM_spesh_arg_guard_exists(MVMThreadContext *tc, MVMSpeshArgGuard *ag, |
309 | 32.1k | MVMCallsite *cs, MVMSpeshStatsType *types) { |
310 | 32.1k | MVMSpeshArgGuard *try_guard = copy_and_extend(tc, ag, max_new_nodes(cs, types)); |
311 | 32.1k | MVMint32 exists = !try_add_guard(tc, try_guard, cs, types, 0); |
312 | 32.1k | MVM_spesh_arg_guard_destroy(tc, try_guard, 0); |
313 | 32.1k | return exists; |
314 | 32.1k | } |
315 | | |
316 | | /* Runs the guard against a type tuple, which is used primarily for detecting |
317 | | * if an existing specialization already exists. Returns the index of that |
318 | | * specialization, or -1 if there is no match. */ |
319 | | MVMint32 MVM_spesh_arg_guard_run_types(MVMThreadContext *tc, MVMSpeshArgGuard *ag, |
320 | 14.8k | MVMCallsite *cs, MVMSpeshStatsType *types) { |
321 | 14.8k | MVMuint32 current_node = 0; |
322 | 14.8k | MVMSpeshStatsType *test = NULL; |
323 | 14.8k | MVMuint32 use_decont_type = 0; |
324 | 14.8k | MVMint32 current_result = -1; |
325 | 14.8k | if (!ag) |
326 | 4.07k | return -1; |
327 | 49.8k | do { |
328 | 49.8k | MVMSpeshArgGuardNode *agn = &(ag->nodes[current_node]); |
329 | 49.8k | switch (agn->op) { |
330 | 11.3k | case MVM_SPESH_GUARD_OP_CALLSITE: |
331 | 10.5k | current_node = agn->cs == cs ? agn->yes : agn->no; |
332 | 11.3k | break; |
333 | 13.5k | case MVM_SPESH_GUARD_OP_LOAD_ARG: { |
334 | 13.5k | test = &(types[agn->arg_index < cs->num_pos |
335 | 13.4k | ? agn->arg_index |
336 | 150 | : cs->num_pos + (((agn->arg_index - 1) - cs->num_pos) / 2)]); |
337 | 13.5k | use_decont_type = 0; |
338 | 13.5k | current_node = agn->yes; |
339 | 13.5k | break; |
340 | 13.6k | case MVM_SPESH_GUARD_OP_STABLE_CONC: |
341 | 13.6k | if (use_decont_type) |
342 | 0 | current_node = test->decont_type_concrete && test->decont_type && |
343 | 0 | test->decont_type->st == agn->st |
344 | 0 | ? agn->yes |
345 | 0 | : agn->no; |
346 | 13.6k | else |
347 | 13.6k | current_node = test->type_concrete && test->type && |
348 | 13.6k | test->type->st == agn->st |
349 | 12.5k | ? agn->yes |
350 | 1.10k | : agn->no; |
351 | 13.6k | break; |
352 | 693 | case MVM_SPESH_GUARD_OP_STABLE_TYPE: |
353 | 693 | if (use_decont_type) |
354 | 0 | current_node = !test->decont_type_concrete && test->decont_type && |
355 | 0 | test->decont_type->st == agn->st |
356 | 0 | ? agn->yes |
357 | 0 | : agn->no; |
358 | 693 | else |
359 | 693 | current_node = !test->type_concrete && test->type && |
360 | 690 | test->type->st == agn->st |
361 | 661 | ? agn->yes |
362 | 32 | : agn->no; |
363 | 693 | break; |
364 | 0 | case MVM_SPESH_GUARD_OP_DEREF_VALUE: |
365 | 0 | if (test->decont_type) { |
366 | 0 | use_decont_type = 1; |
367 | 0 | current_node = agn->yes; |
368 | 0 | } |
369 | 0 | else { |
370 | 0 | current_node = agn->no; |
371 | 0 | } |
372 | 0 | break; |
373 | 11.3k | } |
374 | 0 | case MVM_SPESH_GUARD_OP_DEREF_RW: |
375 | 0 | current_node = test->rw_cont |
376 | 0 | ? agn->yes |
377 | 0 | : agn->no; |
378 | 0 | break; |
379 | 346 | case MVM_SPESH_GUARD_OP_CERTAIN_RESULT: |
380 | 346 | current_result = agn->result; |
381 | 346 | current_node = agn->yes; |
382 | 346 | break; |
383 | 10.2k | case MVM_SPESH_GUARD_OP_RESULT: |
384 | 10.2k | return agn->result; |
385 | 49.8k | } |
386 | 39.6k | } while (current_node != 0); |
387 | 607 | return current_result; |
388 | 10.8k | } |
389 | | |
390 | | /* Evaluates the argument guards. Returns >= 0 if there is a matching spesh |
391 | | * candidate, or -1 if there is not. */ |
392 | | MVMint32 MVM_spesh_arg_guard_run(MVMThreadContext *tc, MVMSpeshArgGuard *ag, |
393 | | MVMCallsite *cs, MVMRegister *args, |
394 | 15.4M | MVMint32 *certain) { |
395 | 15.4M | MVMuint32 current_node = 0; |
396 | 15.4M | MVMObject *test = NULL; |
397 | 15.4M | MVMint32 current_result = -1; |
398 | 15.4M | if (!ag) |
399 | 5.81M | return -1; |
400 | 43.7M | do { |
401 | 43.7M | MVMSpeshArgGuardNode *agn = &(ag->nodes[current_node]); |
402 | 43.7M | switch (agn->op) { |
403 | 11.0M | case MVM_SPESH_GUARD_OP_CALLSITE: |
404 | 7.44M | current_node = agn->cs == cs ? agn->yes : agn->no; |
405 | 11.0M | break; |
406 | 10.6M | case MVM_SPESH_GUARD_OP_LOAD_ARG: |
407 | 10.6M | test = args[agn->arg_index].o; |
408 | 10.6M | current_node = agn->yes; |
409 | 10.6M | break; |
410 | 10.6M | case MVM_SPESH_GUARD_OP_STABLE_CONC: |
411 | 10.6M | current_node = IS_CONCRETE(test) && test->st == agn->st |
412 | 8.60M | ? agn->yes |
413 | 2.09M | : agn->no; |
414 | 10.6M | break; |
415 | 3.86M | case MVM_SPESH_GUARD_OP_STABLE_TYPE: |
416 | 3.86M | current_node = !IS_CONCRETE(test) && test->st == agn->st |
417 | 1.78M | ? agn->yes |
418 | 2.08M | : agn->no; |
419 | 3.86M | break; |
420 | 0 | case MVM_SPESH_GUARD_OP_DEREF_VALUE: { |
421 | 0 | /* TODO Use offset approach later to avoid these calls. */ |
422 | 0 | MVMRegister dc; |
423 | 0 | test->st->container_spec->fetch(tc, test, &dc); |
424 | 0 | test = dc.o; |
425 | 0 | current_node = test ? agn->yes : agn->no; |
426 | 0 | break; |
427 | 11.0M | } |
428 | 0 | case MVM_SPESH_GUARD_OP_DEREF_RW: |
429 | 0 | /* TODO Use offset approach later to avoid these calls. */ |
430 | 0 | current_node = STABLE(test)->container_spec->can_store(tc, test) |
431 | 0 | ? agn->yes |
432 | 0 | : agn->no; |
433 | 0 | break; |
434 | 399k | case MVM_SPESH_GUARD_OP_CERTAIN_RESULT: |
435 | 399k | current_result = agn->result; |
436 | 399k | if (certain) |
437 | 0 | *certain = agn->result; |
438 | 399k | current_node = agn->yes; |
439 | 399k | break; |
440 | 7.01M | case MVM_SPESH_GUARD_OP_RESULT: |
441 | 7.01M | return agn->result; |
442 | 43.7M | } |
443 | 36.6M | } while (current_node != 0); |
444 | 2.66M | return current_result; |
445 | 9.67M | } |
446 | | |
447 | | /* Runs the guards using call information gathered by the optimizer. This is |
448 | | * used for finding existing candidates to emit fast calls to or inline. */ |
449 | | MVMint32 MVM_spesh_arg_guard_run_callinfo(MVMThreadContext *tc, MVMSpeshArgGuard *ag, |
450 | 7.74k | MVMSpeshCallInfo *arg_info) { |
451 | 7.74k | MVMuint32 current_node = 0; |
452 | 7.74k | MVMSpeshFacts *facts = NULL; |
453 | 7.74k | MVMuint8 use_decont_facts = 0; |
454 | 7.74k | MVMint32 current_result = -1; |
455 | 7.74k | if (!ag) |
456 | 2.06k | return -1; |
457 | 22.3k | do { |
458 | 22.3k | MVMSpeshArgGuardNode *agn = &(ag->nodes[current_node]); |
459 | 22.3k | switch (agn->op) { |
460 | 6.68k | case MVM_SPESH_GUARD_OP_CALLSITE: |
461 | 4.98k | current_node = agn->cs == arg_info->cs ? agn->yes : agn->no; |
462 | 6.68k | break; |
463 | 5.73k | case MVM_SPESH_GUARD_OP_LOAD_ARG: |
464 | 5.73k | if (agn->arg_index >= MAX_ARGS_FOR_OPT) |
465 | 0 | return -1; |
466 | 5.73k | facts = arg_info->arg_facts[agn->arg_index]; |
467 | 5.73k | use_decont_facts = 0; |
468 | 5.73k | current_node = agn->yes; |
469 | 5.73k | break; |
470 | 6.06k | case MVM_SPESH_GUARD_OP_STABLE_CONC: |
471 | 6.06k | if (use_decont_facts) { |
472 | 0 | current_node = facts->flags & MVM_SPESH_FACT_DECONT_CONCRETE && |
473 | 0 | facts->flags & MVM_SPESH_FACT_KNOWN_DECONT_TYPE && |
474 | 0 | facts->decont_type->st == agn->st |
475 | 0 | ? agn->yes |
476 | 0 | : agn->no; |
477 | 0 | } |
478 | 6.06k | else { |
479 | 6.06k | current_node = facts->flags & MVM_SPESH_FACT_CONCRETE && |
480 | 4.32k | facts->flags & MVM_SPESH_FACT_KNOWN_TYPE && |
481 | 4.30k | facts->type->st == agn->st |
482 | 3.52k | ? agn->yes |
483 | 2.53k | : agn->no; |
484 | 6.06k | } |
485 | 6.06k | break; |
486 | 466 | case MVM_SPESH_GUARD_OP_STABLE_TYPE: |
487 | 466 | if (use_decont_facts) { |
488 | 0 | current_node = facts->flags & MVM_SPESH_FACT_DECONT_TYPEOBJ && |
489 | 0 | facts->flags & MVM_SPESH_FACT_KNOWN_DECONT_TYPE && |
490 | 0 | facts->decont_type->st == agn->st |
491 | 0 | ? agn->yes |
492 | 0 | : agn->no; |
493 | 0 | } |
494 | 466 | else { |
495 | 466 | current_node = facts->flags & MVM_SPESH_FACT_TYPEOBJ && |
496 | 284 | facts->flags & MVM_SPESH_FACT_KNOWN_TYPE && |
497 | 284 | facts->type->st == agn->st |
498 | 284 | ? agn->yes |
499 | 182 | : agn->no; |
500 | 466 | } |
501 | 466 | break; |
502 | 0 | case MVM_SPESH_GUARD_OP_DEREF_VALUE: { |
503 | 0 | if (facts->flags & MVM_SPESH_FACT_KNOWN_DECONT_TYPE) { |
504 | 0 | use_decont_facts = 1; |
505 | 0 | current_node = agn->yes; |
506 | 0 | } |
507 | 0 | else { |
508 | 0 | current_node = agn->no; |
509 | 0 | } |
510 | 0 | break; |
511 | 5.73k | } |
512 | 0 | case MVM_SPESH_GUARD_OP_DEREF_RW: |
513 | 0 | current_node = facts->flags & MVM_SPESH_FACT_RW_CONT |
514 | 0 | ? agn->yes |
515 | 0 | : agn->no; |
516 | 0 | break; |
517 | 1.24k | case MVM_SPESH_GUARD_OP_CERTAIN_RESULT: |
518 | 1.24k | current_result = agn->result; |
519 | 1.24k | current_node = agn->yes; |
520 | 1.24k | break; |
521 | 2.21k | case MVM_SPESH_GUARD_OP_RESULT: |
522 | 2.21k | return agn->result; |
523 | 22.3k | } |
524 | 20.1k | } while (current_node != 0); |
525 | 3.47k | return current_result; |
526 | 5.68k | } |
527 | | |
528 | | /* Marks any objects held by an argument guard. */ |
529 | | void MVM_spesh_arg_guard_gc_mark(MVMThreadContext *tc, MVMSpeshArgGuard *ag, |
530 | 11.1k | MVMGCWorklist *worklist) { |
531 | 11.1k | if (ag) { |
532 | 5.45k | MVMuint32 i; |
533 | 36.5k | for (i = 0; i < ag->used_nodes; i++) { |
534 | 31.1k | switch (ag->nodes[i].op) { |
535 | 9.47k | case MVM_SPESH_GUARD_OP_STABLE_CONC: |
536 | 9.47k | case MVM_SPESH_GUARD_OP_STABLE_TYPE: |
537 | 9.47k | MVM_gc_worklist_add(tc, worklist, &(ag->nodes[i].st)); |
538 | 9.47k | break; |
539 | 31.1k | } |
540 | 31.1k | } |
541 | 5.45k | } |
542 | 11.1k | } |
543 | | |
544 | | void MVM_spesh_arg_guard_gc_describe(MVMThreadContext *tc, MVMHeapSnapshotState *ss, |
545 | 0 | MVMSpeshArgGuard *ag) { |
546 | 0 | if (ag) { |
547 | 0 | MVMuint32 i; |
548 | 0 | for (i = 0; i < ag->used_nodes; i++) { |
549 | 0 | switch (ag->nodes[i].op) { |
550 | 0 | case MVM_SPESH_GUARD_OP_STABLE_CONC: |
551 | 0 | case MVM_SPESH_GUARD_OP_STABLE_TYPE: |
552 | 0 | MVM_profile_heap_add_collectable_rel_idx(tc, ss, |
553 | 0 | (MVMCollectable*)(ag->nodes[i].st), i); |
554 | 0 | break; |
555 | 0 | } |
556 | 0 | } |
557 | 0 | } |
558 | 0 | } |
559 | | |
560 | | /* Frees the memory associated with an argument guard. If `safe` is set to a |
561 | | * non-zero value then the memory is freed at the next safepoint. If it is set |
562 | | * to zero, the memory is freed immediately. */ |
563 | 34.4k | void MVM_spesh_arg_guard_destroy(MVMThreadContext *tc, MVMSpeshArgGuard *ag, MVMuint32 safe) { |
564 | 34.4k | if (ag) { |
565 | 34.4k | size_t total_size = sizeof(MVMSpeshArgGuard) + |
566 | 34.4k | ag->num_nodes * sizeof(MVMSpeshArgGuardNode); |
567 | 34.4k | if (safe) |
568 | 2.29k | MVM_fixed_size_free_at_safepoint(tc, tc->instance->fsa, total_size, ag); |
569 | 34.4k | else |
570 | 32.1k | MVM_fixed_size_free(tc, tc->instance->fsa, total_size, ag); |
571 | 34.4k | } |
572 | 34.4k | } |
573 | | |
574 | | /* Discards an arg guard held on a static frame, if any, NULLing it out so the |
575 | | * candidates will no longer be reachable. */ |
576 | 0 | void MVM_spesh_arg_guard_discard(MVMThreadContext *tc, MVMStaticFrame *sf) { |
577 | 0 | MVMStaticFrameSpesh *spesh = sf->body.spesh; |
578 | 0 | if (spesh && spesh->body.spesh_arg_guard) { |
579 | 0 | MVM_spesh_arg_guard_destroy(tc, spesh->body.spesh_arg_guard, 1); |
580 | 0 | spesh->body.spesh_arg_guard = NULL; |
581 | 0 | } |
582 | 0 | } |