/
deopt.c
364 lines (335 loc) · 15.7 KB
/
deopt.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
#include "moar.h"
/* In some cases, we may have specialized bytecode "on the stack" and need to
* back out of it, because some assumption it made has been invalidated. This
* file contains implementations of those various forms of de-opt. */
#define MVM_LOG_DEOPTS 0
/* Uninlining can invalidate what the dynlex cache points to, so we'll
* clear it in various caches. */
MVM_STATIC_INLINE void clear_dynlex_cache(MVMThreadContext *tc, MVMFrame *f) {
MVMFrameExtra *e = f->extra;
if (e) {
e->dynlex_cache_name = NULL;
e->dynlex_cache_reg = NULL;
}
}
/* If we have to deopt inside of a frame containing inlines, and we're in
* an inlined frame at the point we hit deopt, we need to undo the inlining
* by switching all levels of inlined frame out for a bunch of frames that
* are running the de-optimized code. We may, of course, be in the original,
* non-inline, bit of the code - in which case we've nothing to do. */
static void uninline(MVMThreadContext *tc, MVMFrame *f, MVMSpeshCandidate *cand,
MVMint32 offset, MVMint32 deopt_offset, MVMFrame *callee) {
MVMFrame *last_uninlined = NULL;
MVMuint16 last_res_reg;
MVMReturnType last_res_type;
MVMuint32 last_return_deopt_idx;
MVMint32 i;
for (i = 0; i < cand->num_inlines; i++) {
if (offset > cand->inlines[i].start && offset <= cand->inlines[i].end) {
/* Create the frame. */
MVMCode *ucode = (MVMCode *)f->work[cand->inlines[i].code_ref_reg].o;
MVMStaticFrame *usf = cand->inlines[i].sf;
MVMFrame *uf;
if (REPR(ucode)->ID != MVM_REPR_ID_MVMCode)
MVM_panic(1, "Deopt: did not find code object when uninlining");
MVMROOT4(tc, f, callee, last_uninlined, usf, {
uf = MVM_frame_create_for_deopt(tc, usf, ucode);
});
#if MVM_LOG_DEOPTS
fprintf(stderr, " Recreated frame '%s' (cuid '%s')\n",
MVM_string_utf8_encode_C_string(tc, usf->body.name),
MVM_string_utf8_encode_C_string(tc, usf->body.cuuid));
#endif
/* Copy the locals and lexicals into place. */
if (usf->body.num_locals)
memcpy(uf->work, f->work + cand->inlines[i].locals_start,
usf->body.num_locals * sizeof(MVMRegister));
if (usf->body.num_lexicals)
memcpy(uf->env, f->env + cand->inlines[i].lexicals_start,
usf->body.num_lexicals * sizeof(MVMRegister));
/* Store the named argument used bit field, since if we deopt in
* argument handling code we may have missed some. */
if (cand->inlines[i].deopt_named_used_bit_field)
uf->params.named_used.bit_field = cand->inlines[i].deopt_named_used_bit_field;
/* Did we already uninline a frame? */
if (last_uninlined) {
/* Yes; multi-level un-inline. Switch it back to deopt'd
* code. */
uf->effective_spesh_slots = NULL;
uf->spesh_cand = NULL;
/* Set up the return location. */
uf->return_address = usf->body.bytecode +
cand->deopts[2 * last_return_deopt_idx];
/* Set result type and register. */
uf->return_type = last_res_type;
if (last_res_type == MVM_RETURN_VOID)
uf->return_value = NULL;
else
uf->return_value = uf->work + last_res_reg;
/* Set up last uninlined's caller to us. */
MVM_ASSERT_NOT_FROMSPACE(tc, uf);
MVM_ASSIGN_REF(tc, &(last_uninlined->header), last_uninlined->caller, uf);
}
else {
/* First uninlined frame. Are we in the middle of the call
* stack (and thus in deopt_all)? */
if (callee) {
/* Tweak the callee's caller to the uninlined frame, not
* the frame holding the inlinings. */
MVM_ASSERT_NOT_FROMSPACE(tc, uf);
MVM_ASSIGN_REF(tc, &(callee->header), callee->caller, uf);
/* Copy over the return location. */
uf->return_address = usf->body.bytecode + deopt_offset;
/* Set result type and register. */
uf->return_type = f->return_type;
if (uf->return_type == MVM_RETURN_VOID) {
uf->return_value = NULL;
}
else {
MVMuint16 orig_reg = (MVMuint16)(f->return_value - f->work);
MVMuint16 ret_reg = orig_reg - cand->inlines[i].locals_start;
uf->return_value = uf->work + ret_reg;
}
}
else {
/* No, it's the deopt_one case, so this is where we'll point
* the interpreter. */
tc->cur_frame = uf;
tc->current_frame_nr = uf->sequence_nr;
*(tc->interp_cur_op) = usf->body.bytecode + deopt_offset;
*(tc->interp_bytecode_start) = usf->body.bytecode;
*(tc->interp_reg_base) = uf->work;
*(tc->interp_cu) = usf->body.cu;
}
}
/* Update tracking variables for last uninline. */
last_uninlined = uf;
last_res_reg = cand->inlines[i].res_reg;
last_res_type = cand->inlines[i].res_type;
last_return_deopt_idx = cand->inlines[i].return_deopt_idx;
}
}
if (last_uninlined) {
/* Set return address, which we need to resolve to the deopt'd one. */
f->return_address = f->static_info->body.bytecode +
cand->deopts[2 * last_return_deopt_idx];
/* Set result type and register. */
f->return_type = last_res_type;
if (last_res_type == MVM_RETURN_VOID)
f->return_value = NULL;
else
f->return_value = f->work + last_res_reg;
/* Set up inliner as the caller, given we now have a direct inline. */
MVM_ASSERT_NOT_FROMSPACE(tc, f);
MVM_ASSIGN_REF(tc, &(last_uninlined->header), last_uninlined->caller, f);
}
else {
/* Weren't in an inline after all. What kind of deopt? */
if (callee) {
/* Deopt all. Move return address. */
f->return_address = f->static_info->body.bytecode + deopt_offset;
}
else {
/* Deopt one. Move interpreter. */
*(tc->interp_cur_op) = f->static_info->body.bytecode + deopt_offset;
*(tc->interp_bytecode_start) = f->static_info->body.bytecode;
}
}
}
static void deopt_named_args_used(MVMThreadContext *tc, MVMFrame *f) {
if (f->spesh_cand->deopt_named_used_bit_field)
f->params.named_used.bit_field = f->spesh_cand->deopt_named_used_bit_field;
}
/* Materialize an individual replaced object. */
static void materialize_object(MVMThreadContext *tc, MVMFrame *f, MVMuint32 info_idx) {
MVM_panic(1, "Deopt: materialize_object NYI");
}
/* Materialize all replaced objects that need to be at this deopt point. */
static void materialize_replaced_objects(MVMThreadContext *tc, MVMFrame *f, MVMint32 deopt_offset) {
MVMint32 i;
MVMSpeshCandidate *cand = f->spesh_cand;
for (i = 0; i < MVM_VECTOR_ELEMS(cand->deopt_pea.deopt_point); i++) {
if (cand->deopt_pea.deopt_point[i].deopt_point_idx == deopt_offset)
materialize_object(tc, f, cand->deopt_pea.deopt_point[i].materialize_info_idx);
}
}
static void deopt_frame(MVMThreadContext *tc, MVMFrame *f, MVMint32 deopt_offset, MVMint32 deopt_target) {
/* Found it. We materialize any replaced objects first, then if
* we have stuff replaced in inlines then uninlining will take
* care of moving it out into the frames where it belongs. */
deopt_named_args_used(tc, f);
materialize_replaced_objects(tc, f, deopt_offset);
/* Check if we have inlines. */
if (f->spesh_cand->inlines) {
/* Yes, going to have to re-create the frames; uninline
* moves the interpreter, so we can just tweak the last
* frame. For the moment, uninlining creates its frames
* on the heap, so we'll force the current call stack to
* the heap to preserve the "no heap -> stack pointers"
* invariant. */
f = MVM_frame_force_to_heap(tc, f);
MVMROOT(tc, f, {
uninline(tc, f, f->spesh_cand, deopt_offset, deopt_target, NULL);
});
f->effective_spesh_slots = NULL;
f->spesh_cand = NULL;
#if MVM_LOG_DEOPTS
fprintf(stderr, "Completed deopt_one in '%s' (cuid '%s') with potential uninlining\n",
MVM_string_utf8_encode_C_string(tc, tc->cur_frame->static_info->body.name),
MVM_string_utf8_encode_C_string(tc, tc->cur_frame->static_info->body.cuuid));
#endif
}
else {
/* No inlining; simple case. Switch back to the original code. */
*(tc->interp_cur_op) = f->static_info->body.bytecode + deopt_target;
*(tc->interp_bytecode_start) = f->static_info->body.bytecode;
f->effective_spesh_slots = NULL;
f->spesh_cand = NULL;
#if MVM_LOG_DEOPTS
fprintf(stderr, "Completed deopt_one in '%s' (cuid '%s')\n",
MVM_string_utf8_encode_C_string(tc, tc->cur_frame->static_info->body.name),
MVM_string_utf8_encode_C_string(tc, tc->cur_frame->static_info->body.cuuid));
#endif
}
}
/* De-optimizes the currently executing frame, provided it is specialized and
* at a valid de-optimization point. Typically used when a guard fails. */
void MVM_spesh_deopt_one(MVMThreadContext *tc, MVMuint32 deopt_target) {
MVMFrame *f = tc->cur_frame;
if (tc->instance->profiling)
MVM_profiler_log_deopt_one(tc);
#if MVM_LOG_DEOPTS
fprintf(stderr, "Deopt one requested by interpreter in frame '%s' (cuid '%s')\n",
MVM_string_utf8_encode_C_string(tc, tc->cur_frame->static_info->body.name),
MVM_string_utf8_encode_C_string(tc, tc->cur_frame->static_info->body.cuuid));
#endif
clear_dynlex_cache(tc, f);
if (f->spesh_cand) {
MVMuint32 deopt_offset = *(tc->interp_cur_op) - f->spesh_cand->bytecode;
#if MVM_LOG_DEOPTS
fprintf(stderr, " Will deopt %u -> %u\n", deopt_offset, deopt_target);
#endif
deopt_frame(tc, tc->cur_frame, deopt_offset, deopt_target);
}
else {
MVM_oops(tc, "deopt_one failed for %s (%s)",
MVM_string_utf8_encode_C_string(tc, tc->cur_frame->static_info->body.name),
MVM_string_utf8_encode_C_string(tc, tc->cur_frame->static_info->body.cuuid));
}
MVM_CHECK_CALLER_CHAIN(tc, tc->cur_frame);
}
/* De-optimizes the current frame by directly specifying the addresses */
void MVM_spesh_deopt_one_direct(MVMThreadContext *tc, MVMuint32 deopt_offset,
MVMuint32 deopt_target) {
MVMFrame *f = tc->cur_frame;
#if MVM_LOG_DEOPTS
fprintf(stderr, "Deopt one requested by JIT in frame '%s' (cuid '%s') (%u -> %u)\n",
MVM_string_utf8_encode_C_string(tc, f->static_info->body.name),
MVM_string_utf8_encode_C_string(tc, f->static_info->body.cuuid),
deopt_offset, deopt_target);
#endif
if (tc->instance->profiling)
MVM_profiler_log_deopt_one(tc);
clear_dynlex_cache(tc, f);
deopt_frame(tc, tc->cur_frame, deopt_offset, deopt_target);
MVM_CHECK_CALLER_CHAIN(tc, tc->cur_frame);
}
/* Takes a frame that is *not* the one currently running on the call stack
* but is in specialized code. Finds the currently active deopt index at
* the point of its latest call. Returns -1 if none can be resolved. */
MVMint32 MVM_spesh_deopt_find_inactive_frame_deopt_idx(MVMThreadContext *tc, MVMFrame *f) {
/* Is it JITted code? */
if (f->spesh_cand->jitcode) {
MVMJitCode *jitcode = f->spesh_cand->jitcode;
MVMint32 idx = MVM_jit_code_get_active_deopt_idx(tc, jitcode, f);
if (idx < jitcode->num_deopts) {
MVMint32 deopt_idx = jitcode->deopts[idx].idx;
#if MVM_LOG_DEOPTS
fprintf(stderr, " Found deopt label for JIT (idx %d)\n", deopt_idx);
#endif
return deopt_idx;
}
}
else {
/* Not JITted; see if we can find the return address in the deopt table. */
MVMint32 ret_offset = f->return_address - f->spesh_cand->bytecode;
MVMint32 n = f->spesh_cand->num_deopts * 2;
MVMint32 i;
for (i = 0; i < n; i += 2) {
if (f->spesh_cand->deopts[i + 1] == ret_offset) {
MVMint32 deopt_idx = i / 2;
#if MVM_LOG_DEOPTS
fprintf(stderr, " Found deopt index for interpeter (idx %d)\n", deopt_idx);
#endif
return deopt_idx;
}
}
}
#if MVM_LOG_DEOPTS
fprintf(stderr, " Can't find deopt all idx\n");
#endif
return -1;
}
/* De-optimizes all specialized frames on the call stack. Used when a change
* is made the could invalidate all kinds of assumptions all over the place
* (such as a mix-in). */
void MVM_spesh_deopt_all(MVMThreadContext *tc) {
/* Walk frames looking for any callers in specialized bytecode. */
MVMFrame *l = MVM_frame_force_to_heap(tc, tc->cur_frame);
MVMFrame *f = tc->cur_frame->caller;
#if MVM_LOG_DEOPTS
fprintf(stderr, "Deopt all requested in frame '%s' (cuid '%s')\n",
MVM_string_utf8_encode_C_string(tc, l->static_info->body.name),
MVM_string_utf8_encode_C_string(tc, l->static_info->body.cuuid));
#endif
if (tc->instance->profiling)
MVM_profiler_log_deopt_all(tc);
while (f) {
clear_dynlex_cache(tc, f);
if (f->spesh_cand) {
MVMint32 deopt_idx = MVM_spesh_deopt_find_inactive_frame_deopt_idx(tc, f);
if (deopt_idx >= 0) {
/* Re-create any frames needed if we're in an inline; if not,
* just update return address. */
MVMint32 deopt_offset = f->spesh_cand->deopts[2 * deopt_idx + 1];
MVMint32 deopt_target = f->spesh_cand->deopts[2 * deopt_idx];
if (f->spesh_cand->inlines) {
MVMROOT2(tc, f, l, {
uninline(tc, f, f->spesh_cand, deopt_offset, deopt_target, l);
});
#if MVM_LOG_DEOPTS
fprintf(stderr, " Deopted frame '%s' (cuid '%s') with potential uninlining\n",
MVM_string_utf8_encode_C_string(tc, f->static_info->body.name),
MVM_string_utf8_encode_C_string(tc, f->static_info->body.cuuid));
#endif
}
else {
f->return_address = f->static_info->body.bytecode + deopt_target;
#if MVM_LOG_DEOPTS
fprintf(stderr, " Deopted frame '%s' (cuid '%s')\n",
MVM_string_utf8_encode_C_string(tc, f->static_info->body.name),
MVM_string_utf8_encode_C_string(tc, f->static_info->body.cuuid));
#endif
}
/* No spesh cand/slots needed now. */
deopt_named_args_used(tc, f);
f->effective_spesh_slots = NULL;
if (f->spesh_cand->jitcode) {
f->spesh_cand = NULL;
f->jit_entry_label = NULL;
/* XXX This break is wrong and hides a bug. */
break;
}
else {
f->spesh_cand = NULL;
}
}
}
l = f;
f = f->caller;
}
MVM_CHECK_CALLER_CHAIN(tc, tc->cur_frame);
#if MVM_LOG_DEOPTS
fprintf(stderr, "Deopt all completed\n");
#endif
}