Coverage Report

Created: 2018-07-03 15:31

/home/travis/build/MoarVM/MoarVM/src/spesh/optimize.c
Line
Count
Source (jump to first uncovered line)
1
#include "moar.h"
2
3
/* This is where the main optimization work on a spesh graph takes place,
4
 * using facts discovered during analysis. */
5
6
/* Writes to stderr about each inline that we perform. */
7
#define MVM_LOG_INLINES 0
8
9
/* Logging of whether we can or can't inline. */
10
static void log_inline(MVMThreadContext *tc, MVMSpeshGraph *g, MVMStaticFrame *target_sf,
11
                       MVMSpeshGraph *inline_graph, MVMuint32 bytecode_size,
12
22.6k
                       char *no_inline_reason) {
13
22.6k
#if MVM_LOG_INLINES
14
    char *c_name_i = MVM_string_utf8_encode_C_string(tc, target_sf->body.name);
15
    char *c_cuid_i = MVM_string_utf8_encode_C_string(tc, target_sf->body.cuuid);
16
    char *c_name_t = MVM_string_utf8_encode_C_string(tc, g->sf->body.name);
17
    char *c_cuid_t = MVM_string_utf8_encode_C_string(tc, g->sf->body.cuuid);
18
    if (inline_graph) {
19
        fprintf(stderr, "Can inline %s (%s) with bytecode size %u into %s (%s)\n",
20
            c_name_i, c_cuid_i, bytecode_size, c_name_t, c_cuid_t);
21
    }
22
    else {
23
        fprintf(stderr, "Can NOT inline %s (%s) with bytecode size %u into %s (%s): %s\n",
24
            c_name_i, c_cuid_i, bytecode_size, c_name_t, c_cuid_t, no_inline_reason);
25
    }
26
    MVM_free(c_name_i);
27
    MVM_free(c_cuid_i);
28
    MVM_free(c_name_t);
29
    MVM_free(c_cuid_t);
30
#endif
31
22.6k
}
32
33
/* Obtains facts for an operand, just directly accessing them without
34
 * inferring any kind of usage. */
35
100M
static MVMSpeshFacts * get_facts_direct(MVMThreadContext *tc, MVMSpeshGraph *g, MVMSpeshOperand o) {
36
100M
    return &g->facts[o.reg.orig][o.reg.i];
37
100M
}
38
39
/* Obtains facts for an operand, indicating they are being used. */
40
321k
MVMSpeshFacts * MVM_spesh_get_and_use_facts(MVMThreadContext *tc, MVMSpeshGraph *g, MVMSpeshOperand o) {
41
321k
    MVMSpeshFacts *facts = get_facts_direct(tc, g, o);
42
321k
    MVM_spesh_use_facts(tc, g, facts);
43
321k
    return facts;
44
321k
}
45
46
/* Obtains facts for an operand, but doesn't (yet) indicate usefulness. */
47
7.39M
MVMSpeshFacts * MVM_spesh_get_facts(MVMThreadContext *tc, MVMSpeshGraph *g, MVMSpeshOperand o) {
48
7.39M
    return get_facts_direct(tc, g, o);
49
7.39M
}
50
51
/* Mark facts for an operand as being relied upon. */
52
478k
void MVM_spesh_use_facts(MVMThreadContext *tc, MVMSpeshGraph *g, MVMSpeshFacts *facts) {
53
478k
    if (facts->flags & MVM_SPESH_FACT_FROM_LOG_GUARD)
54
103k
        g->log_guards[facts->log_guard].used = 1;
55
478k
    if (facts->flags & MVM_SPESH_FACT_MERGED_WITH_LOG_GUARD) {
56
3.16k
        MVMSpeshIns *thePHI = facts->writer;
57
3.16k
        MVMuint32 op_i;
58
3.16k
59
7.02k
        for (op_i = 1; op_i < thePHI->info->num_operands; op_i++) {
60
3.86k
            MVM_spesh_get_and_use_facts(tc, g, thePHI->operands[op_i]);
61
3.86k
        }
62
3.16k
    }
63
478k
}
64
65
/* Obtains a string constant. */
66
51.7k
MVMString * MVM_spesh_get_string(MVMThreadContext *tc, MVMSpeshGraph *g, MVMSpeshOperand o) {
67
51.7k
    return MVM_cu_string(tc, g->sf->body.cu, o.lit_str_idx);
68
51.7k
}
69
70
/* Copy facts between two register operands. */
71
static void copy_facts(MVMThreadContext *tc, MVMSpeshGraph *g, MVMSpeshOperand to,
72
186k
                       MVMSpeshOperand from) {
73
186k
    MVMSpeshFacts *tfacts = get_facts_direct(tc, g, to);
74
186k
    MVMSpeshFacts *ffacts = get_facts_direct(tc, g, from);
75
186k
    tfacts->flags         = ffacts->flags;
76
186k
    tfacts->type          = ffacts->type;
77
186k
    tfacts->decont_type   = ffacts->decont_type;
78
186k
    tfacts->value         = ffacts->value;
79
186k
    tfacts->log_guard     = ffacts->log_guard;
80
186k
}
81
82
/* Adds a value into a spesh slot and returns its index.
83
 * If a spesh slot already holds this value, return that instead. */
84
78.7k
MVMint16 MVM_spesh_add_spesh_slot_try_reuse(MVMThreadContext *tc, MVMSpeshGraph *g, MVMCollectable *c) {
85
78.7k
    MVMint16 prev_slot;
86
871k
    for (prev_slot = 0; prev_slot < g->num_spesh_slots; prev_slot++) {
87
825k
        if (g->spesh_slots[prev_slot] == c)
88
33.6k
            return prev_slot;
89
825k
    }
90
45.1k
    return MVM_spesh_add_spesh_slot(tc, g, c);
91
78.7k
}
92
93
/* Adds a value into a spesh slot and returns its index. */
94
134k
MVMint16 MVM_spesh_add_spesh_slot(MVMThreadContext *tc, MVMSpeshGraph *g, MVMCollectable *c) {
95
134k
    if (g->num_spesh_slots >= g->alloc_spesh_slots) {
96
23.3k
        g->alloc_spesh_slots += 8;
97
23.3k
        if (g->spesh_slots)
98
13.3k
            g->spesh_slots = MVM_realloc(g->spesh_slots,
99
13.3k
                g->alloc_spesh_slots * sizeof(MVMCollectable *));
100
23.3k
        else
101
9.94k
            g->spesh_slots = MVM_malloc(g->alloc_spesh_slots * sizeof(MVMCollectable *));
102
23.3k
    }
103
134k
    g->spesh_slots[g->num_spesh_slots] = c;
104
134k
    return g->num_spesh_slots++;
105
134k
}
106
107
/* If an `isnull` is on something we know the type of or value of, then we
108
 * can quickly verify the type is not based on the null REPR and turn it
109
 * into a constant. */
110
static void optimize_isnull(MVMThreadContext *tc, MVMSpeshGraph *g, MVMSpeshBB *bb,
111
4.68k
                            MVMSpeshIns *ins) {
112
4.68k
    MVMSpeshFacts *obj_facts = MVM_spesh_get_facts(tc, g, ins->operands[1]);
113
4.68k
    if (obj_facts->flags & MVM_SPESH_FACT_KNOWN_TYPE) {
114
1.59k
        MVMint32 is_null = REPR(obj_facts->type)->ID == MVM_REPR_ID_MVMNull;
115
1.59k
        MVMSpeshFacts *result_facts = MVM_spesh_get_facts(tc, g, ins->operands[0]);
116
1.59k
        MVM_spesh_use_facts(tc, g, obj_facts);
117
1.59k
        ins->info = MVM_op_get_op(MVM_OP_const_i64_16);
118
1.59k
        ins->operands[1].lit_i16 = is_null;
119
1.59k
        result_facts->flags |= MVM_SPESH_FACT_KNOWN_VALUE;
120
1.59k
        result_facts->value.i = is_null;
121
1.59k
        MVM_spesh_facts_depend(tc, g, result_facts, obj_facts);
122
1.59k
    }
123
4.68k
}
124
125
static void optimize_repr_op(MVMThreadContext *tc, MVMSpeshGraph *g, MVMSpeshBB *bb,
126
                             MVMSpeshIns *ins, MVMint32 type_operand);
127
128
2.99k
static void optimize_findmeth_s_perhaps_constant(MVMThreadContext *tc, MVMSpeshGraph *g, MVMSpeshIns *ins) {
129
2.99k
    MVMSpeshFacts *name_facts = MVM_spesh_get_facts(tc, g, ins->operands[2]);
130
2.99k
131
2.99k
    if (name_facts->flags & MVM_SPESH_FACT_KNOWN_VALUE) {
132
2.33k
        if (name_facts->writer && name_facts->writer->info->opcode == MVM_OP_const_s) {
133
2.33k
            name_facts->usages--;
134
2.33k
            ins->info = ins->info->opcode == MVM_OP_findmeth_s
135
2.33k
                ? MVM_op_get_op(MVM_OP_findmeth)
136
0
                : MVM_op_get_op(MVM_OP_tryfindmeth);
137
2.33k
            ins->operands[2].lit_i64 = 0;
138
2.33k
            ins->operands[2].lit_str_idx = name_facts->writer->operands[1].lit_str_idx;
139
2.33k
            MVM_spesh_use_facts(tc, g, name_facts);
140
2.33k
        }
141
2.33k
    }
142
2.99k
}
143
144
/* Performs optimization on a method lookup. If we know the type that we'll
145
 * be dispatching on, resolve it right off. If not, add a cache. */
146
29.8k
static void optimize_method_lookup(MVMThreadContext *tc, MVMSpeshGraph *g, MVMSpeshIns *ins) {
147
29.8k
    /* See if we can resolve the method right off due to knowing the type. */
148
29.8k
    MVMSpeshFacts *obj_facts = MVM_spesh_get_facts(tc, g, ins->operands[1]);
149
29.8k
    MVMint32 resolved = 0;
150
29.8k
    if (obj_facts->flags & MVM_SPESH_FACT_KNOWN_TYPE) {
151
15.8k
        /* Try to resolve. */
152
15.8k
        MVMString *name = MVM_spesh_get_string(tc, g, ins->operands[2]);
153
15.8k
        MVMObject *meth = MVM_spesh_try_find_method(tc, obj_facts->type, name);
154
15.8k
        if (!MVM_is_null(tc, meth)) {
155
15.5k
            /* Could compile-time resolve the method. Add it in a spesh slot. */
156
15.5k
            MVMint16 ss = MVM_spesh_add_spesh_slot(tc, g, (MVMCollectable *)meth);
157
15.5k
158
15.5k
            /* Tweak facts for the target, given we know the method. */
159
15.5k
            MVMSpeshFacts *meth_facts = MVM_spesh_get_and_use_facts(tc, g, ins->operands[0]);
160
15.5k
            meth_facts->flags |= MVM_SPESH_FACT_KNOWN_TYPE;
161
15.5k
            meth_facts->type = meth->st->WHAT;
162
15.5k
            meth_facts->flags |= MVM_SPESH_FACT_KNOWN_VALUE;
163
15.5k
            meth_facts->value.o = meth;
164
15.5k
165
15.5k
            /* Update the instruction to grab the spesh slot. */
166
15.5k
            ins->info = MVM_op_get_op(MVM_OP_sp_getspeshslot);
167
15.5k
            ins->operands[1].lit_i16 = ss;
168
15.5k
169
15.5k
            resolved = 1;
170
15.5k
171
15.5k
            MVM_spesh_use_facts(tc, g, obj_facts);
172
15.5k
            obj_facts->usages--;
173
15.5k
        }
174
15.8k
    }
175
29.8k
176
29.8k
    /* If not, add space to cache a single type/method pair, to save hash
177
29.8k
     * lookups in the (common) monomorphic case, and rewrite to caching
178
29.8k
     * version of the instruction. */
179
29.8k
    if (!resolved && ins->info->opcode == MVM_OP_findmeth) {
180
14.2k
        MVMSpeshOperand *orig_o = ins->operands;
181
14.2k
        ins->info = MVM_op_get_op(MVM_OP_sp_findmeth);
182
14.2k
        ins->operands = MVM_spesh_alloc(tc, g, 4 * sizeof(MVMSpeshOperand));
183
14.2k
        memcpy(ins->operands, orig_o, 3 * sizeof(MVMSpeshOperand));
184
14.2k
        ins->operands[3].lit_i16 = MVM_spesh_add_spesh_slot(tc, g, NULL);
185
14.2k
        MVM_spesh_add_spesh_slot(tc, g, NULL);
186
14.2k
    }
187
29.8k
}
188
189
/* Sees if we can resolve an istype at compile time. */
190
1.75k
static void optimize_istype(MVMThreadContext *tc, MVMSpeshGraph *g, MVMSpeshIns *ins) {
191
1.75k
    MVMSpeshFacts *obj_facts  = MVM_spesh_get_facts(tc, g, ins->operands[1]);
192
1.75k
    MVMSpeshFacts *type_facts = MVM_spesh_get_facts(tc, g, ins->operands[2]);
193
1.75k
    MVMSpeshFacts *result_facts;
194
1.75k
195
1.75k
    if (type_facts->flags & MVM_SPESH_FACT_KNOWN_TYPE &&
196
1.63k
         obj_facts->flags & MVM_SPESH_FACT_KNOWN_TYPE) {
197
156
        MVMint32 result;
198
156
        if (!MVM_6model_try_cache_type_check(tc, obj_facts->type, type_facts->type, &result))
199
37
            return;
200
119
        ins->info = MVM_op_get_op(MVM_OP_const_i64_16);
201
119
        result_facts = MVM_spesh_get_facts(tc, g, ins->operands[0]);
202
119
        result_facts->flags |= MVM_SPESH_FACT_KNOWN_VALUE;
203
119
        ins->operands[1].lit_i16 = result;
204
119
        result_facts->value.i  = result;
205
119
206
119
        obj_facts->usages--;
207
119
        type_facts->usages--;
208
119
        MVM_spesh_facts_depend(tc, g, result_facts, obj_facts);
209
119
        MVM_spesh_use_facts(tc, g, obj_facts);
210
119
        MVM_spesh_facts_depend(tc, g, result_facts, type_facts);
211
119
        MVM_spesh_use_facts(tc, g, type_facts);
212
119
    }
213
1.75k
}
214
215
1.09k
static void optimize_is_reprid(MVMThreadContext *tc, MVMSpeshGraph *g, MVMSpeshIns *ins) {
216
1.09k
    MVMSpeshFacts *obj_facts = MVM_spesh_get_facts(tc, g, ins->operands[1]);
217
1.09k
    MVMuint32 wanted_repr_id;
218
1.09k
    MVMuint64 result_value;
219
1.09k
220
1.09k
    if (!(obj_facts->flags & MVM_SPESH_FACT_KNOWN_TYPE)) {
221
631
        return;
222
631
    }
223
1.09k
224
466
    switch (ins->info->opcode) {
225
15
        case MVM_OP_islist: wanted_repr_id = MVM_REPR_ID_VMArray; break;
226
293
        case MVM_OP_ishash: wanted_repr_id = MVM_REPR_ID_MVMHash; break;
227
0
        case MVM_OP_isint:  wanted_repr_id = MVM_REPR_ID_P6int; break;
228
0
        case MVM_OP_isnum:  wanted_repr_id = MVM_REPR_ID_P6num; break;
229
158
        case MVM_OP_isstr:  wanted_repr_id = MVM_REPR_ID_P6str; break;
230
0
        default:            return;
231
466
    }
232
466
233
466
    MVM_spesh_use_facts(tc, g, obj_facts);
234
466
235
466
    result_value = REPR(obj_facts->type)->ID == wanted_repr_id;
236
466
237
466
    if (result_value == 0) {
238
177
        MVMSpeshFacts *result_facts = MVM_spesh_get_facts(tc, g, ins->operands[0]);
239
177
        ins->info = MVM_op_get_op(MVM_OP_const_i64_16);
240
177
        ins->operands[1].lit_i16 = 0;
241
177
        result_facts->flags |= MVM_SPESH_FACT_KNOWN_VALUE;
242
177
        result_facts->value.i = 0;
243
177
        MVM_spesh_facts_depend(tc, g, result_facts, obj_facts);
244
289
    } else {
245
289
        ins->info = MVM_op_get_op(MVM_OP_isnonnull);
246
289
    }
247
466
}
248
249
943
static void optimize_gethow(MVMThreadContext *tc, MVMSpeshGraph *g, MVMSpeshIns *ins) {
250
943
    MVMSpeshFacts *obj_facts = MVM_spesh_get_facts(tc, g, ins->operands[1]);
251
943
    MVMObject       *how_obj = NULL;
252
943
    if (obj_facts->flags & (MVM_SPESH_FACT_KNOWN_TYPE))
253
489
        how_obj = MVM_spesh_try_get_how(tc, obj_facts->type);
254
943
    /* There may be other valid ways to get the facts (known value?) */
255
943
    if (how_obj) {
256
460
        MVMSpeshFacts *how_facts;
257
460
        /* Transform gethow lookup to spesh slot lookup */
258
460
        MVMint16 spesh_slot = MVM_spesh_add_spesh_slot_try_reuse(tc, g, (MVMCollectable*)how_obj);
259
460
        MVM_spesh_get_facts(tc, g, ins->operands[1])->usages--;
260
460
        ins->info = MVM_op_get_op(MVM_OP_sp_getspeshslot);
261
460
        ins->operands[1].lit_i16 = spesh_slot;
262
460
        /* Store facts about the value in the write operand */
263
460
        how_facts = MVM_spesh_get_facts(tc, g, ins->operands[0]);
264
460
        how_facts->flags  |= (MVM_SPESH_FACT_KNOWN_VALUE | MVM_SPESH_FACT_KNOWN_TYPE);
265
460
        how_facts->value.o = how_obj;
266
460
        how_facts->type    = STABLE(how_obj)->WHAT;
267
460
    }
268
943
}
269
270
271
/* Sees if we can resolve an isconcrete at compile time. */
272
5.57k
static void optimize_isconcrete(MVMThreadContext *tc, MVMSpeshGraph *g, MVMSpeshIns *ins) {
273
5.57k
    MVMSpeshFacts *obj_facts = MVM_spesh_get_facts(tc, g, ins->operands[1]);
274
5.57k
    if (obj_facts->flags & (MVM_SPESH_FACT_CONCRETE | MVM_SPESH_FACT_TYPEOBJ)) {
275
2.27k
        MVMSpeshFacts *result_facts = MVM_spesh_get_facts(tc, g, ins->operands[0]);
276
2.27k
        ins->info                   = MVM_op_get_op(MVM_OP_const_i64_16);
277
2.27k
        result_facts->flags        |= MVM_SPESH_FACT_KNOWN_VALUE;
278
2.27k
        result_facts->value.i       = obj_facts->flags & MVM_SPESH_FACT_CONCRETE ? 1 : 0;
279
2.27k
        ins->operands[1].lit_i16    = result_facts->value.i;
280
2.27k
281
2.27k
        MVM_spesh_use_facts(tc, g, obj_facts);
282
2.27k
        MVM_spesh_facts_depend(tc, g, result_facts, obj_facts);
283
2.27k
284
2.27k
        obj_facts->usages--;
285
2.27k
    }
286
5.57k
}
287
288
0
static void optimize_exception_ops(MVMThreadContext *tc, MVMSpeshGraph *g, MVMSpeshBB *bb, MVMSpeshIns *ins) {
289
0
    MVMuint16 op = ins->info->opcode;
290
0
291
0
    if (op == MVM_OP_newexception) {
292
0
        MVMSpeshOperand target   = ins->operands[0];
293
0
        MVMObject      *type     = tc->instance->boot_types.BOOTException;
294
0
        MVMSTable      *st       = STABLE(type);
295
0
        ins->info                = MVM_op_get_op(MVM_OP_sp_fastcreate);
296
0
        ins->operands            = MVM_spesh_alloc(tc, g, 3 * sizeof(MVMSpeshOperand));
297
0
        ins->operands[0]         = target;
298
0
        ins->operands[1].lit_i16 = st->size;
299
0
        ins->operands[2].lit_i16 = MVM_spesh_add_spesh_slot(tc, g, (MVMCollectable *)st);
300
0
    } else {
301
0
        /*
302
0
        MVMSpeshFacts *target_facts;
303
0
        */
304
0
305
0
        /* XXX This currently still causes problems. */
306
0
        return;
307
0
308
0
        /*
309
0
        switch (op) {
310
0
        case MVM_OP_bindexmessage:
311
0
        case MVM_OP_bindexpayload: {
312
0
            MVMSpeshOperand target   = ins->operands[0];
313
0
            MVMSpeshOperand value    = ins->operands[1];
314
0
            target_facts             = MVM_spesh_get_facts(tc, g, target);
315
0
316
0
            if (!(target_facts->flags & MVM_SPESH_FACT_KNOWN_TYPE)
317
0
                || !(REPR(target_facts->type)->ID == MVM_REPR_ID_MVMException))
318
0
                break;
319
0
320
0
            ins->info                = MVM_op_get_op(op == MVM_OP_bindexmessage ? MVM_OP_sp_bind_s : MVM_OP_sp_bind_o);
321
0
            ins->operands            = MVM_spesh_alloc(tc, g, 3 * sizeof(MVMSpeshOperand));
322
0
            ins->operands[0]         = target;
323
0
            ins->operands[1].lit_i16 = op == MVM_OP_bindexmessage ? offsetof(MVMException, body.message)
324
0
                                                                  : offsetof(MVMException, body.payload);
325
0
            ins->operands[2]         = value;
326
0
            break;
327
0
        }
328
0
        case MVM_OP_bindexcategory: {
329
0
            MVMSpeshOperand target   = ins->operands[0];
330
0
            MVMSpeshOperand category = ins->operands[1];
331
0
            target_facts             = MVM_spesh_get_facts(tc, g, target);
332
0
333
0
            if (!(target_facts->flags & MVM_SPESH_FACT_KNOWN_TYPE)
334
0
                || !(REPR(target_facts->type)->ID == MVM_REPR_ID_MVMException))
335
0
                break;
336
0
337
0
            ins->info                = MVM_op_get_op(MVM_OP_sp_bind_i32);
338
0
            ins->operands            = MVM_spesh_alloc(tc, g, 3 * sizeof(MVMSpeshOperand));
339
0
            ins->operands[0]         = target;
340
0
            ins->operands[1].lit_i16 = offsetof(MVMException, body.category);
341
0
            ins->operands[2]         = category;
342
0
            break;
343
0
        }
344
0
        case MVM_OP_getexmessage:
345
0
        case MVM_OP_getexpayload: {
346
0
            MVMSpeshOperand destination = ins->operands[0];
347
0
            MVMSpeshOperand target      = ins->operands[1];
348
0
            target_facts                = MVM_spesh_get_facts(tc, g, target);
349
0
350
0
            if (!(target_facts->flags & MVM_SPESH_FACT_KNOWN_TYPE)
351
0
                || !(REPR(target_facts->type)->ID == MVM_REPR_ID_MVMException))
352
0
                break;
353
0
354
0
            ins->info                = MVM_op_get_op(op == MVM_OP_getexmessage ? MVM_OP_sp_get_s : MVM_OP_sp_get_o);
355
0
            ins->operands            = MVM_spesh_alloc(tc, g, 3 * sizeof(MVMSpeshOperand));
356
0
            ins->operands[0]         = destination;
357
0
            ins->operands[1]         = target;
358
0
            ins->operands[2].lit_i16 = op == MVM_OP_getexmessage ? offsetof(MVMException, body.message)
359
0
                                                                 : offsetof(MVMException, body.payload);
360
0
            break;
361
0
        }
362
0
        case MVM_OP_getexcategory: {
363
0
            MVMSpeshOperand destination = ins->operands[0];
364
0
            MVMSpeshOperand target      = ins->operands[1];
365
0
            target_facts                = MVM_spesh_get_facts(tc, g, target);
366
0
367
0
            if (!(target_facts->flags & MVM_SPESH_FACT_KNOWN_TYPE)
368
0
                || !(REPR(target_facts->type)->ID == MVM_REPR_ID_MVMException))
369
0
                break;
370
0
371
0
            ins->info                = MVM_op_get_op(MVM_OP_sp_get_i32);
372
0
            ins->operands            = MVM_spesh_alloc(tc, g, 3 * sizeof(MVMSpeshOperand));
373
0
            ins->operands[0]         = destination;
374
0
            ins->operands[1]         = target;
375
0
            ins->operands[2].lit_i16 = offsetof(MVMException, body.category);
376
0
            break;
377
0
        }
378
0
        }
379
0
        */
380
0
    }
381
0
}
382
383
384
/* iffy ops that operate on a known value register can turn into goto
385
 * or be dropped. */
386
60.8k
static void optimize_iffy(MVMThreadContext *tc, MVMSpeshGraph *g, MVMSpeshIns *ins, MVMSpeshBB *bb) {
387
60.8k
    MVMSpeshFacts *flag_facts = MVM_spesh_get_facts(tc, g, ins->operands[0]);
388
60.8k
    MVMuint8 negated_op;
389
60.8k
    MVMuint8 truthvalue;
390
60.8k
391
60.8k
    switch (ins->info->opcode) {
392
25.3k
        case MVM_OP_if_i:
393
25.3k
        case MVM_OP_if_s:
394
25.3k
        case MVM_OP_if_n:
395
25.3k
        case MVM_OP_ifnonnull:
396
25.3k
            negated_op = 0;
397
25.3k
            break;
398
35.4k
        case MVM_OP_unless_i:
399
35.4k
        case MVM_OP_unless_s:
400
35.4k
        case MVM_OP_unless_n:
401
35.4k
            negated_op = 1;
402
35.4k
            break;
403
0
        default:
404
0
            return;
405
60.8k
    }
406
60.8k
407
60.8k
    if (flag_facts->flags & MVM_SPESH_FACT_KNOWN_VALUE) {
408
3.36k
        switch (ins->info->opcode) {
409
3.36k
            case MVM_OP_if_i:
410
3.36k
            case MVM_OP_unless_i:
411
3.36k
                truthvalue = flag_facts->value.i;
412
3.36k
                break;
413
0
            case MVM_OP_if_n:
414
0
            case MVM_OP_unless_n:
415
0
                truthvalue = flag_facts->value.n != 0.0;
416
0
                break;
417
0
            default:
418
0
                return;
419
3.36k
        }
420
3.36k
421
3.36k
        MVM_spesh_use_facts(tc, g, flag_facts);
422
3.36k
        flag_facts->usages--;
423
3.36k
424
2.25k
        truthvalue = truthvalue ? 1 : 0;
425
3.36k
        if (truthvalue != negated_op) {
426
1.40k
            /* This conditional can be turned into an unconditional jump. */
427
1.40k
            ins->info = MVM_op_get_op(MVM_OP_goto);
428
1.40k
            ins->operands[0] = ins->operands[1];
429
1.40k
430
1.40k
            /* Since we have an unconditional jump now, we can remove the successor
431
1.40k
             * that's in the linear_next. */
432
1.40k
            MVM_spesh_manipulate_remove_successor(tc, bb, bb->linear_next);
433
1.95k
        } else {
434
1.95k
            /* This conditional can be dropped completely. */
435
1.95k
            MVM_spesh_manipulate_remove_successor(tc, bb, ins->operands[1].ins_bb);
436
1.95k
            MVM_spesh_manipulate_delete_ins(tc, g, bb, ins);
437
1.95k
        }
438
3.36k
439
3.36k
        /* Since the CFG has changed, we may have some dead basic blocks; do
440
3.36k
         * an elimination pass. */
441
3.36k
        MVM_spesh_eliminate_dead_bbs(tc, g, 1);
442
3.36k
443
3.36k
        return;
444
3.36k
    }
445
60.8k
446
60.8k
}
447
448
449
/* A not_i on a known value can be turned into a constant. */
450
2.35k
static void optimize_not_i(MVMThreadContext *tc, MVMSpeshGraph *g, MVMSpeshIns *ins, MVMSpeshBB *bb) {
451
2.35k
    MVMSpeshFacts *src_facts = MVM_spesh_get_facts(tc, g, ins->operands[1]);
452
2.35k
    if (src_facts->flags & MVM_SPESH_FACT_KNOWN_VALUE) {
453
351
        /* Do the not_i. */
454
351
        MVMint64 value = src_facts->value.i;
455
190
        MVMint16 result = value ? 0 : 1;
456
351
457
351
        /* Turn the op into a constant and set result facts. */
458
351
        MVMSpeshFacts *dest_facts = MVM_spesh_get_facts(tc, g, ins->operands[0]);
459
351
        dest_facts->flags |= MVM_SPESH_FACT_KNOWN_VALUE;
460
351
        dest_facts->value.i = result;
461
351
        ins->info = MVM_op_get_op(MVM_OP_const_i64_16);
462
351
        ins->operands[1].lit_i16 = result;
463
351
464
351
        /* This op no longer uses the source value. */
465
351
        src_facts->usages--;
466
351
467
351
        /* Need to depend on the source facts. */
468
351
        MVM_spesh_use_facts(tc, g, src_facts);
469
351
        MVM_spesh_facts_depend(tc, g, dest_facts, src_facts);
470
351
    }
471
2.35k
}
472
473
/* objprimspec can be done at spesh-time if we know the type of something.
474
 * Another thing is, that if we rely on the type being known, we'll be assured
475
 * we'll have a guard that promises the object in question to be non-null. */
476
13
static void optimize_objprimspec(MVMThreadContext *tc, MVMSpeshGraph *g, MVMSpeshIns *ins) {
477
13
    MVMSpeshFacts *obj_facts = MVM_spesh_get_facts(tc, g, ins->operands[1]);
478
13
479
13
    if (obj_facts->flags & MVM_SPESH_FACT_KNOWN_TYPE && obj_facts->type) {
480
13
        MVMSpeshFacts *result_facts = MVM_spesh_get_facts(tc, g, ins->operands[0]);
481
13
        ins->info                   = MVM_op_get_op(MVM_OP_const_i64_16);
482
13
        result_facts->flags        |= MVM_SPESH_FACT_KNOWN_VALUE;
483
13
        result_facts->value.i       = REPR(obj_facts->type)->get_storage_spec(tc, STABLE(obj_facts->type))->boxed_primitive;
484
13
        ins->operands[1].lit_i16    = result_facts->value.i;
485
13
486
13
        MVM_spesh_use_facts(tc, g, obj_facts);
487
13
        obj_facts->usages--;
488
13
    }
489
13
}
490
491
/* Optimizes a hllize instruction away if the type is known and already in the
492
 * right HLL, by turning it into a set. */
493
0
static void optimize_hllize(MVMThreadContext *tc, MVMSpeshGraph *g, MVMSpeshIns *ins) {
494
0
    MVMSpeshFacts *obj_facts = MVM_spesh_get_facts(tc, g, ins->operands[1]);
495
0
    if (obj_facts->flags & MVM_SPESH_FACT_KNOWN_TYPE && obj_facts->type) {
496
0
        if (STABLE(obj_facts->type)->hll_owner == g->sf->body.cu->body.hll_config) {
497
0
            ins->info = MVM_op_get_op(MVM_OP_set);
498
0
499
0
            MVM_spesh_use_facts(tc, g, obj_facts);
500
0
501
0
            copy_facts(tc, g, ins->operands[0], ins->operands[1]);
502
0
        }
503
0
    }
504
0
}
505
506
/* Turns a decont into a set, if we know it's not needed. Also make sure we
507
 * propagate any needed information. */
508
110k
static void optimize_decont(MVMThreadContext *tc, MVMSpeshGraph *g, MVMSpeshBB *bb, MVMSpeshIns *ins) {
509
110k
    MVMSpeshFacts *obj_facts = MVM_spesh_get_facts(tc, g, ins->operands[1]);
510
110k
    if (obj_facts->flags & (MVM_SPESH_FACT_DECONTED | MVM_SPESH_FACT_TYPEOBJ)) {
511
62.4k
        /* Know that we don't need to decont. */
512
62.4k
        ins->info = MVM_op_get_op(MVM_OP_set);
513
62.4k
        MVM_spesh_use_facts(tc, g, obj_facts);
514
62.4k
        copy_facts(tc, g, ins->operands[0], ins->operands[1]);
515
62.4k
        MVM_spesh_manipulate_remove_handler_successors(tc, bb);
516
62.4k
    }
517
48.1k
    else {
518
48.1k
        /* Propagate facts if we know what this deconts to. */
519
48.1k
        MVMSpeshFacts *res_facts = MVM_spesh_get_facts(tc, g, ins->operands[0]);
520
48.1k
        int set_facts = 0;
521
48.1k
        if (obj_facts->flags & MVM_SPESH_FACT_KNOWN_DECONT_TYPE) {
522
0
            res_facts->type   = obj_facts->decont_type;
523
0
            res_facts->flags |= MVM_SPESH_FACT_KNOWN_TYPE;
524
0
            set_facts = 1;
525
0
        }
526
48.1k
        if (obj_facts->flags & MVM_SPESH_FACT_DECONT_CONCRETE) {
527
0
            res_facts->flags |= MVM_SPESH_FACT_CONCRETE;
528
0
            set_facts = 1;
529
0
        }
530
48.1k
        else if (obj_facts->flags & MVM_SPESH_FACT_DECONT_TYPEOBJ) {
531
0
            res_facts->flags |= MVM_SPESH_FACT_TYPEOBJ;
532
0
            set_facts = 1;
533
0
        }
534
48.1k
535
48.1k
        /* If it's a known type... */
536
48.1k
        if (obj_facts->flags & MVM_SPESH_FACT_KNOWN_TYPE && obj_facts->type) {
537
460
            /* Can try to specialize the fetch. */
538
460
            MVMSTable *stable = STABLE(obj_facts->type);
539
460
            MVMContainerSpec const *contspec = stable->container_spec;
540
460
            if (contspec && contspec->fetch_never_invokes && contspec->spesh) {
541
0
                MVMSpeshAnn *ann = ins->annotations;
542
0
                /* Remove deopt annotation since we know we won't invoke. */
543
0
                if (ann && ann->type == MVM_SPESH_ANN_DEOPT_ONE_INS) {
544
0
                    ins->annotations = ann->next;
545
0
                }
546
0
                else {
547
0
                    while (ann) {
548
0
                        if (ann->next && ann->next->type == MVM_SPESH_ANN_DEOPT_ONE_INS) {
549
0
                            ann->next = ann->next->next;
550
0
                            break;
551
0
                        }
552
0
                        ann = ann->next;
553
0
                    }
554
0
                }
555
0
                contspec->spesh(tc, stable, g, bb, ins);
556
0
                MVM_spesh_use_facts(tc, g, obj_facts);
557
0
            }
558
460
559
460
            /* If we didn't yet set facts, and the incoming type is a native
560
460
             * reference, then we can set facts based on knowing what it will
561
460
             * decont/box to. */
562
460
           if (!set_facts && stable->REPR->ID == MVM_REPR_ID_NativeRef) {
563
0
                MVMNativeRefREPRData *repr_data = (MVMNativeRefREPRData *)stable->REPR_data;
564
0
                MVMHLLConfig *hll = stable->hll_owner;
565
0
                MVMObject *out_type = NULL;
566
0
                if (!hll)
567
0
                    hll = g->sf->body.cu->body.hll_config;
568
0
                switch (repr_data->primitive_type) {
569
0
                    case MVM_STORAGE_SPEC_BP_INT:
570
0
                        out_type = hll->int_box_type;
571
0
                        break;
572
0
                    case MVM_STORAGE_SPEC_BP_NUM:
573
0
                        out_type = hll->num_box_type;
574
0
                        break;
575
0
                    case MVM_STORAGE_SPEC_BP_STR:
576
0
                        out_type = hll->str_box_type;
577
0
                        break;
578
0
                }
579
0
                if (out_type) {
580
0
                    res_facts->type = out_type;
581
0
                    res_facts->flags |= MVM_SPESH_FACT_KNOWN_TYPE | MVM_SPESH_FACT_CONCRETE;
582
0
                    set_facts = 1;
583
0
                }
584
0
            }
585
460
        }
586
48.1k
587
48.1k
        /* Depend on incoming facts if we used them. */
588
48.1k
        if (set_facts)
589
0
            MVM_spesh_facts_depend(tc, g, res_facts, obj_facts);
590
48.1k
591
48.1k
        /* If the op is still a decont, then turn it into sp_decont, which
592
48.1k
         * will at least not write log entries. */
593
48.1k
        if (ins->info->opcode == MVM_OP_decont)
594
48.1k
            ins->info = MVM_op_get_op(MVM_OP_sp_decont);
595
48.1k
    }
596
110k
}
597
598
/* Checks like iscont, iscont_[ins] and isrwcont can be done at spesh time. */
599
static void optimize_container_check(MVMThreadContext *tc, MVMSpeshGraph *g,
600
33
                                     MVMSpeshBB *bb, MVMSpeshIns *ins) {
601
33
    MVMSpeshFacts *facts = MVM_spesh_get_facts(tc, g, ins->operands[1]);
602
33
    MVMSpeshFacts *result_facts = MVM_spesh_get_facts(tc, g, ins->operands[0]);
603
33
    MVMint32 known_result = -1;
604
33
    if (ins->info->opcode == MVM_OP_isrwcont) {
605
0
        if (facts->flags & MVM_SPESH_FACT_RW_CONT)
606
0
            known_result = 1;
607
0
    }
608
33
    else {
609
33
        if (facts->flags & MVM_SPESH_FACT_TYPEOBJ) {
610
8
            /* Type object can never be a container. */
611
8
            known_result = 0;
612
8
        }
613
25
        else if ((facts->flags & MVM_SPESH_FACT_CONCRETE) &&
614
0
                (facts->flags & MVM_SPESH_FACT_KNOWN_TYPE)) {
615
0
            /* Know the type and know it's concrete. */
616
0
            MVMContainerSpec const *cs = facts->type->st->container_spec;
617
0
            if (!cs) {
618
0
                /* No container spec, so can be sure it's not a container. */
619
0
                known_result = 0;
620
0
            }
621
0
            else if (ins->info->opcode == MVM_OP_iscont) {
622
0
                /* General is container check, so answer is yes. */
623
0
                known_result = 1;
624
0
            }
625
0
            else {
626
0
                if (REPR(facts->type)->ID == MVM_REPR_ID_NativeRef) {
627
0
                    /* Which native ref primitive? */
628
0
                    switch (((MVMNativeRefREPRData *)STABLE(facts->type)->REPR_data)->primitive_type) {
629
0
                        case MVM_STORAGE_SPEC_BP_INT:
630
0
                            known_result = ins->info->opcode == MVM_OP_iscont_i;
631
0
                            break;
632
0
                        case MVM_STORAGE_SPEC_BP_NUM:
633
0
                            known_result = ins->info->opcode == MVM_OP_iscont_n;
634
0
                            break;
635
0
                        case MVM_STORAGE_SPEC_BP_STR:
636
0
                            known_result = ins->info->opcode == MVM_OP_iscont_s;
637
0
                            break;
638
0
                    }
639
0
                }
640
0
                else {
641
0
                    /* Need a native ref but don't have one, so certain no. */
642
0
                    known_result = 0;
643
0
                }
644
0
            }
645
0
        }
646
33
    }
647
33
    if (known_result != -1) {
648
8
        ins->info                   = MVM_op_get_op(MVM_OP_const_i64_16);
649
8
        result_facts->flags        |= MVM_SPESH_FACT_KNOWN_VALUE;
650
8
        result_facts->value.i       = known_result;
651
8
        ins->operands[1].lit_i16    = known_result;
652
8
        MVM_spesh_use_facts(tc, g, facts);
653
8
        facts->usages--;
654
8
    }
655
33
}
656
657
/* Optimize away assertparamcheck if we know it will pass. */
658
0
static void optimize_assertparamcheck(MVMThreadContext *tc, MVMSpeshGraph *g, MVMSpeshBB *bb, MVMSpeshIns *ins) {
659
0
    MVMSpeshFacts *facts = MVM_spesh_get_facts(tc, g, ins->operands[0]);
660
0
    if (facts->flags & MVM_SPESH_FACT_KNOWN_VALUE && facts->value.i) {
661
0
        MVM_spesh_use_facts(tc, g, facts);
662
0
        MVM_spesh_manipulate_delete_ins(tc, g, bb, ins);
663
0
    }
664
0
}
665
666
292
static void optimize_can_op(MVMThreadContext *tc, MVMSpeshGraph *g, MVMSpeshBB *bb, MVMSpeshIns *ins) {
667
292
    /* This used to cause problems, Spesh: failed to fix up handlers (-1, 110, 110) */
668
292
    MVMSpeshFacts *obj_facts = MVM_spesh_get_facts(tc, g, ins->operands[1]);
669
292
    MVMString *method_name;
670
292
    MVMint64 can_result;
671
292
672
292
    if (ins->info->opcode == MVM_OP_can_s) {
673
292
        MVMSpeshFacts *name_facts = MVM_spesh_get_facts(tc, g, ins->operands[2]);
674
292
        if (!(name_facts->flags & MVM_SPESH_FACT_KNOWN_VALUE)) {
675
10
            return;
676
10
        }
677
282
        method_name = name_facts->value.s;
678
282
679
282
        name_facts->usages--;
680
282
        ins->info = MVM_op_get_op(MVM_OP_can);
681
282
        ins->operands[2].lit_str_idx = name_facts->writer->operands[1].lit_str_idx;
682
0
    } else {
683
0
        method_name = MVM_spesh_get_string(tc, g, ins->operands[2]);
684
0
    }
685
292
686
282
    if (!(obj_facts->flags & MVM_SPESH_FACT_KNOWN_TYPE) || !obj_facts->type) {
687
232
        return;
688
232
    }
689
282
690
50
    if (MVM_is_null(tc, obj_facts->type))
691
16
        can_result = 0; /* VMNull can't have any methods. */
692
50
    else
693
34
        can_result = MVM_spesh_try_can_method(tc, obj_facts->type, method_name);
694
50
695
50
    if (can_result == -1) {
696
0
        return;
697
50
    } else {
698
50
        MVMSpeshFacts *result_facts;
699
50
700
50
        if (ins->info->opcode == MVM_OP_can_s)
701
0
            MVM_spesh_get_facts(tc, g, ins->operands[2])->usages--;
702
50
703
50
        result_facts                = MVM_spesh_get_facts(tc, g, ins->operands[0]);
704
50
        ins->info                   = MVM_op_get_op(MVM_OP_const_i64_16);
705
50
        result_facts->flags        |= MVM_SPESH_FACT_KNOWN_VALUE;
706
50
        ins->operands[1].lit_i16    = can_result;
707
50
        result_facts->value.i       = can_result;
708
50
709
50
        obj_facts->usages--;
710
50
        MVM_spesh_use_facts(tc, g, obj_facts);
711
50
    }
712
50
}
713
714
/* If we have a const_i and a coerce_in, we can emit a const_n instead. */
715
4.59k
static void optimize_coerce(MVMThreadContext *tc, MVMSpeshGraph *g, MVMSpeshBB *bb, MVMSpeshIns *ins) {
716
4.59k
    MVMSpeshFacts *facts = MVM_spesh_get_facts(tc, g, ins->operands[1]);
717
4.59k
718
4.59k
    if (facts->flags & MVM_SPESH_FACT_KNOWN_VALUE) {
719
1.51k
        MVMSpeshFacts *result_facts = MVM_spesh_get_facts(tc, g, ins->operands[0]);
720
1.51k
        MVMnum64 result = facts->value.i;
721
1.51k
722
1.51k
        MVM_spesh_use_facts(tc, g, facts);
723
1.51k
        facts->usages--;
724
1.51k
725
1.51k
        ins->info = MVM_op_get_op(MVM_OP_const_n64);
726
1.51k
        ins->operands[1].lit_n64 = result;
727
1.51k
728
1.51k
        result_facts->flags |= MVM_SPESH_FACT_KNOWN_VALUE;
729
1.51k
        result_facts->value.n = result;
730
1.51k
    }
731
4.59k
}
732
733
732
static void optimize_unbox(MVMThreadContext *tc, MVMSpeshGraph *g, MVMSpeshBB *bb, MVMSpeshIns *ins) {
734
732
    /* try to remove boxing-unboxing sequences */
735
732
    MVMSpeshFacts *box_facts;
736
732
    switch (ins->info->opcode) {
737
732
    case MVM_OP_unbox_i:
738
732
        break;
739
0
    default:
740
0
        return;
741
732
    }
742
732
743
732
    /* As far as I can determine, in rakudo buiding or spectests, this runs
744
732
     * never. So I'm not confident actually enabling it. */
745
732
    return;
746
732
747
0
    box_facts = MVM_spesh_get_facts(tc, g, ins->operands[1]);
748
0
    if (box_facts->flags & MVM_SPESH_FACT_KNOWN_BOX_SRC && box_facts->writer) {
749
0
        /* We may have to go through several layers of set instructions to find
750
0
         * the proper writer. */
751
0
        MVMSpeshIns *cur = box_facts->writer;
752
0
        while (cur && cur->info->opcode == MVM_OP_set) {
753
0
            cur = MVM_spesh_get_facts(tc, g, cur->operands[1])->writer;
754
0
        }
755
0
756
0
        if (cur) {
757
0
            MVMSpeshIns *safety_cur;
758
0
            MVMuint8 orig_operand_type = cur->info->operands[1] & MVM_operand_type_mask;
759
0
760
0
            /* Now we have to be extra careful. Any operation that writes to
761
0
             * our "unboxed flag" register (in any register version) will be
762
0
             * trouble. Also, we'd have to take more care with PHI nodes,
763
0
             * which we'll just consider immediate failure for now. */
764
0
765
0
            safety_cur = ins;
766
0
            while (safety_cur) {
767
0
                if (safety_cur == cur) {
768
0
                    /* If we've made it to here without finding anything
769
0
                     * dangerous, we can consider this optimization
770
0
                     * a winner. */
771
0
                    break;
772
0
                }
773
0
                if (safety_cur->info->opcode == MVM_SSA_PHI) {
774
0
                    /* Oh dear god in heaven! A PHI! */
775
0
                    safety_cur = NULL;
776
0
                    break;
777
0
                }
778
0
                if (((safety_cur->info->operands[0] & MVM_operand_rw_mask) == MVM_operand_write_reg)
779
0
                    && (safety_cur->operands[0].reg.orig == cur->operands[1].reg.orig)) {
780
0
                    /* Someone's clobbering our register between the boxing and
781
0
                     * our attempt to unbox it. We shall give up.
782
0
                     * Maybe in the future we can be clever/sneaky and use
783
0
                     * some other register for bridging the gap? */
784
0
                    safety_cur = NULL;
785
0
                    break;
786
0
                }
787
0
                safety_cur = safety_cur->prev;
788
0
            }
789
0
790
0
            if (safety_cur) {
791
0
                /* this reduces to a set */
792
0
                ins->info = MVM_op_get_op(MVM_OP_set);
793
0
                ins->operands[1] = cur->operands[0];
794
0
                box_facts->usages--;
795
0
                MVM_spesh_get_and_use_facts(tc, g, cur->operands[1])->usages++;
796
0
                copy_facts(tc, g, ins->operands[0], ins->operands[1]);
797
0
                return;
798
0
            }
799
0
        }
800
0
    }
801
0
    optimize_repr_op(tc, g, bb, ins, 1);
802
0
}
803
804
/* If we know the type of a significant operand, we might try to specialize by
805
 * representation. */
806
static void optimize_repr_op(MVMThreadContext *tc, MVMSpeshGraph *g, MVMSpeshBB *bb,
807
136k
                             MVMSpeshIns *ins, MVMint32 type_operand) {
808
136k
    /* Immediately mark guards as used, as the JIT would like to devirtualize
809
136k
     * repr ops later and we don't want guards to be thrown out before that */
810
136k
    MVMSpeshFacts *facts = MVM_spesh_get_and_use_facts(tc, g, ins->operands[type_operand]);
811
136k
    if (facts->flags & MVM_SPESH_FACT_KNOWN_TYPE && facts->type)
812
71.9k
        if (REPR(facts->type)->spesh) {
813
63.5k
            REPR(facts->type)->spesh(tc, STABLE(facts->type), g, bb, ins);
814
63.5k
            MVM_spesh_use_facts(tc, g, facts);
815
63.5k
        }
816
136k
}
817
818
/* smrt_strify and smrt_numify can turn into unboxes, but at least
819
 * for smrt_numify it's "complicated". Also, later when we know how
820
 * to put new invocations into spesh'd code, we could make direct
821
 * invoke calls to the .Str and .Num methods. */
822
18.7k
static void optimize_smart_coerce(MVMThreadContext *tc, MVMSpeshGraph *g, MVMSpeshBB *bb, MVMSpeshIns *ins) {
823
18.7k
    MVMSpeshFacts *facts = MVM_spesh_get_facts(tc, g, ins->operands[1]);
824
18.7k
825
18.7k
    MVMuint16 is_strify = ins->info->opcode == MVM_OP_smrt_strify;
826
18.7k
827
18.7k
    if (facts->flags & (MVM_SPESH_FACT_KNOWN_TYPE | MVM_SPESH_FACT_CONCRETE) && facts->type) {
828
4.16k
        const MVMStorageSpec *ss;
829
4.16k
        MVMint64 can_result;
830
4.16k
831
4.16k
        ss = REPR(facts->type)->get_storage_spec(tc, STABLE(facts->type));
832
4.16k
833
4.16k
        if (is_strify && ss->can_box & MVM_STORAGE_SPEC_CAN_BOX_STR) {
834
1.37k
            MVM_spesh_use_facts(tc, g, facts);
835
1.37k
836
1.37k
            ins->info = MVM_op_get_op(MVM_OP_unbox_s);
837
1.37k
            /* And now that we have a repr op, we can try to optimize
838
1.37k
             * it even further. */
839
1.37k
            optimize_repr_op(tc, g, bb, ins, 1);
840
1.37k
841
1.37k
            return;
842
1.37k
        }
843
2.78k
        can_result = MVM_spesh_try_can_method(tc, facts->type,
844
2.29k
                is_strify ? tc->instance->str_consts.Str : tc->instance->str_consts.Num);
845
2.78k
846
2.78k
        if (can_result == -1) {
847
2.29k
            /* Couldn't safely figure out if the type has a Str method or not. */
848
2.29k
            return;
849
494
        } else if (can_result == 0) {
850
406
            MVM_spesh_use_facts(tc, g, facts);
851
406
            /* We can't .Str this object, so we'll duplicate the "guessing"
852
406
             * logic from smrt_strify here to remove indirection. */
853
406
            if (is_strify && REPR(facts->type)->ID == MVM_REPR_ID_MVMException) {
854
0
                MVMSpeshOperand *operands  = MVM_spesh_alloc(tc, g, sizeof( MVMSpeshOperand ) * 3);
855
0
                MVMSpeshOperand *old_opers = ins->operands;
856
0
857
0
                ins->info = MVM_op_get_op(MVM_OP_sp_get_s);
858
0
859
0
                ins->operands = operands;
860
0
861
0
                operands[0] = old_opers[0];
862
0
                operands[1] = old_opers[1];
863
0
                operands[2].lit_i16 = offsetof( MVMException, body.message );
864
406
            } else if(ss->can_box & (MVM_STORAGE_SPEC_CAN_BOX_NUM | MVM_STORAGE_SPEC_CAN_BOX_INT)) {
865
0
                MVMuint16 register_type =
866
0
                    ss->can_box & MVM_STORAGE_SPEC_CAN_BOX_INT ? MVM_reg_int64 : MVM_reg_num64;
867
0
868
0
                MVMSpeshIns     *new_ins   = MVM_spesh_alloc(tc, g, sizeof( MVMSpeshIns ));
869
0
                MVMSpeshOperand *operands  = MVM_spesh_alloc(tc, g, sizeof( MVMSpeshOperand ) * 2);
870
0
                MVMSpeshOperand  temp      = MVM_spesh_manipulate_get_temp_reg(tc, g, register_type);
871
0
                MVMSpeshOperand  orig_dst  = ins->operands[0];
872
0
873
0
                ins->info = MVM_op_get_op(register_type == MVM_reg_num64 ? MVM_OP_unbox_n : MVM_OP_unbox_i);
874
0
                ins->operands[0] = temp;
875
0
876
0
                if (is_strify)
877
0
                    new_ins->info = MVM_op_get_op(register_type == MVM_reg_num64 ? MVM_OP_coerce_ns : MVM_OP_coerce_is);
878
0
                else
879
0
                    new_ins->info = MVM_op_get_op(register_type == MVM_reg_num64 ? MVM_OP_set : MVM_OP_coerce_in);
880
0
                new_ins->operands = operands;
881
0
                operands[0] = orig_dst;
882
0
                operands[1] = temp;
883
0
884
0
                /* We can directly "eliminate" a set instruction here. */
885
0
                if (new_ins->info->opcode != MVM_OP_set) {
886
0
                    MVM_spesh_manipulate_insert_ins(tc, bb, ins, new_ins);
887
0
888
0
                    MVM_spesh_get_facts(tc, g, temp)->usages++;
889
0
                } else {
890
0
                    ins->operands[0] = orig_dst;
891
0
                }
892
0
893
0
                /* Finally, let's try to optimize the unboxing REPROp. */
894
0
                optimize_repr_op(tc, g, bb, ins, 1);
895
0
896
0
                /* And as a last clean-up step, we release the temporary register. */
897
0
                MVM_spesh_manipulate_release_temp_reg(tc, g, temp);
898
0
899
0
                return;
900
406
            } else if (!is_strify && (REPR(facts->type)->ID == MVM_REPR_ID_VMArray ||
901
359
                                     (REPR(facts->type)->ID == MVM_REPR_ID_MVMHash))) {
902
359
                /* A smrt_numify on an array or hash can be replaced by an
903
359
                 * elems operation, that can then be optimized by our
904
359
                 * versatile and dilligent friend optimize_repr_op. */
905
359
906
359
                MVMSpeshIns     *new_ins   = MVM_spesh_alloc(tc, g, sizeof( MVMSpeshIns ));
907
359
                MVMSpeshOperand *operands  = MVM_spesh_alloc(tc, g, sizeof( MVMSpeshOperand ) * 2);
908
359
                MVMSpeshOperand  temp      = MVM_spesh_manipulate_get_temp_reg(tc, g, MVM_reg_int64);
909
359
                MVMSpeshOperand  orig_dst  = ins->operands[0];
910
359
911
359
                ins->info = MVM_op_get_op(MVM_OP_elems);
912
359
                ins->operands[0] = temp;
913
359
914
359
                new_ins->info = MVM_op_get_op(MVM_OP_coerce_in);
915
359
                new_ins->operands = operands;
916
359
                operands[0] = orig_dst;
917
359
                operands[1] = temp;
918
359
919
359
                MVM_spesh_manipulate_insert_ins(tc, bb, ins, new_ins);
920
359
921
359
                optimize_repr_op(tc, g, bb, ins, 1);
922
359
923
359
                MVM_spesh_get_facts(tc, g, temp)->usages++;
924
359
                MVM_spesh_manipulate_release_temp_reg(tc, g, temp);
925
359
                return;
926
359
            }
927
88
        } else if (can_result == 1) {
928
88
            /* When we know how to generate additional callsites, we could
929
88
             * make an invocation to .Str or .Num here and perhaps have it
930
88
             * in-lined. */
931
88
        }
932
2.78k
    }
933
18.7k
}
934
935
/* Optimize string equality if one param is the empty string */
936
3.97k
static void optimize_string_equality(MVMThreadContext *tc, MVMSpeshGraph *g, MVMSpeshBB *bb, MVMSpeshIns *ins) {
937
3.97k
    MVMSpeshFacts *a_facts = MVM_spesh_get_facts(tc, g, ins->operands[1]);
938
3.97k
    MVMSpeshFacts *b_facts = MVM_spesh_get_facts(tc, g, ins->operands[2]);
939
3.97k
    MVMuint8 was_eq = 0;
940
3.97k
941
3.97k
    return;
942
3.97k
943
0
    if (ins->info->opcode == MVM_OP_eq_s)
944
0
        was_eq = 1;
945
0
946
0
    if (a_facts->flags & MVM_SPESH_FACT_KNOWN_VALUE && b_facts->flags & MVM_SPESH_FACT_KNOWN_VALUE) {
947
0
        /* Cool, we can constant-fold this. */
948
0
        MVMSpeshFacts *target_facts = MVM_spesh_get_facts(tc, g, ins->operands[0]);
949
0
950
0
        a_facts->usages--;
951
0
        b_facts->usages--;
952
0
953
0
        ins->operands[1].lit_i16 = MVM_string_equal(tc, a_facts->value.s, b_facts->value.s);
954
0
        if (!was_eq)
955
0
            ins->operands[1].lit_i16 = !ins->operands[1].lit_i16;
956
0
        ins->info = MVM_op_get_op(MVM_OP_const_i64_16);
957
0
958
0
        target_facts->flags |= MVM_SPESH_FACT_KNOWN_VALUE;
959
0
        target_facts->value.i = ins->operands[1].lit_i16;
960
0
        
961
0
        fprintf(stderr, "turned an eq or ne into a constant\n");
962
0
    }
963
0
    else if (a_facts->flags & MVM_SPESH_FACT_KNOWN_VALUE || b_facts->flags & MVM_SPESH_FACT_KNOWN_VALUE) {
964
0
        MVMSpeshFacts *the_facts =
965
0
            a_facts->flags & MVM_SPESH_FACT_KNOWN_VALUE ? a_facts : b_facts;
966
0
967
0
        if (MVM_string_graphs(tc, the_facts->value.s) == 0) {
968
0
            /* Turn this into an istrue_s or isfalse_s */
969
0
            ins->info = MVM_op_get_op(was_eq ? MVM_OP_isfalse_s : MVM_OP_istrue_s);
970
0
971
0
            /* Throw out the string argument that was the empty string */
972
0
            if (the_facts == a_facts) {
973
0
                ins->operands[1] = ins->operands[2];
974
0
            }
975
0
            the_facts->usages--;
976
0
977
0
            fprintf(stderr, "turned an eq or ne into an istrue/isfalse\n");
978
0
        }
979
0
    }
980
0
}
981
982
/* boolification has a major indirection, which we can spesh away.
983
 * Afterwards, we may be able to spesh even further, so we defer
984
 * to other optimization methods. */
985
8.92k
static void optimize_istrue_isfalse(MVMThreadContext *tc, MVMSpeshGraph *g, MVMSpeshBB *bb, MVMSpeshIns *ins) {
986
8.92k
    MVMuint8 negated_op;
987
8.92k
    MVMSpeshFacts *input_facts = MVM_spesh_get_facts(tc, g, ins->operands[1]);
988
8.92k
    MVMSpeshFacts *result_facts = MVM_spesh_get_facts(tc, g, ins->operands[0]);
989
8.92k
990
8.92k
    if (ins->info->opcode == MVM_OP_istrue) {
991
8.65k
        negated_op = 0;
992
272
    } else if (ins->info->opcode == MVM_OP_isfalse) {
993
272
        negated_op = 1;
994
0
    } else {
995
0
        return;
996
0
    }
997
8.92k
998
8.92k
    /* Known value, maybe possible to coerce to a constant */
999
8.92k
    if (input_facts->flags & MVM_SPESH_FACT_KNOWN_VALUE) {
1000
0
        MVMObject *objval = input_facts->value.o;
1001
0
        MVMBoolificationSpec *bs = objval->st->boolification_spec;
1002
0
        MVMRegister resultreg;
1003
0
        MVMint64 truthvalue;
1004
0
        switch (bs == NULL ? MVM_BOOL_MODE_NOT_TYPE_OBJECT : bs->mode) {
1005
0
        case MVM_BOOL_MODE_UNBOX_INT:
1006
0
        case MVM_BOOL_MODE_UNBOX_NUM:
1007
0
        case MVM_BOOL_MODE_UNBOX_STR_NOT_EMPTY:
1008
0
        case MVM_BOOL_MODE_UNBOX_STR_NOT_EMPTY_OR_ZERO:
1009
0
        case MVM_BOOL_MODE_BIGINT:
1010
0
        case MVM_BOOL_MODE_ITER:
1011
0
        case MVM_BOOL_MODE_HAS_ELEMS:
1012
0
        case MVM_BOOL_MODE_NOT_TYPE_OBJECT:
1013
0
            MVM_coerce_istrue(tc, objval, &resultreg, NULL, NULL, 0);
1014
0
            truthvalue = negated_op ? !resultreg.i64 : !!resultreg.i64;
1015
0
            break;
1016
0
        case MVM_BOOL_MODE_CALL_METHOD:
1017
0
        default:
1018
0
            /* nothing fixed we can say about this */
1019
0
            return;
1020
0
        }
1021
0
        /* assign a constant value */
1022
0
        ins->info = MVM_op_get_op(MVM_OP_const_i64_16);
1023
0
        ins->operands[1].lit_i64 = truthvalue;
1024
0
        /* we're no longer using this object (but we rely on the facts provided) */
1025
0
        MVM_spesh_use_facts(tc, g, input_facts);
1026
0
        input_facts->usages--;
1027
0
        /* and we know the result of this operations as a constant value */
1028
0
        result_facts->flags  |= MVM_SPESH_FACT_KNOWN_VALUE;
1029
0
        result_facts->value.i = truthvalue;
1030
0
        return;
1031
0
    }
1032
8.92k
1033
8.92k
    /* Unknown value, known type. We may be able to lower this to a
1034
8.92k
     * nonpolymorphic operation */
1035
8.92k
    if (input_facts->flags & MVM_SPESH_FACT_KNOWN_TYPE) {
1036
1.78k
        /* Go by boolification mode to pick a new instruction, if any. */
1037
1.78k
        MVMObject *type            = input_facts->type;
1038
1.78k
        MVMBoolificationSpec *bs   = type->st->boolification_spec;
1039
1.78k
        MVMuint8 guaranteed_concrete = input_facts->flags & MVM_SPESH_FACT_CONCRETE;
1040
1.77k
        MVMuint8 mode = bs == NULL ? MVM_BOOL_MODE_NOT_TYPE_OBJECT : bs->mode;
1041
1.78k
1042
1.78k
        switch (mode) {
1043
0
        case MVM_BOOL_MODE_ITER:
1044
0
            if (!guaranteed_concrete)
1045
0
                break;
1046
0
            if (input_facts->flags & MVM_SPESH_FACT_ARRAY_ITER) {
1047
0
                ins->info = MVM_op_get_op(MVM_OP_sp_boolify_iter_arr);
1048
0
            } else if (input_facts->flags & MVM_SPESH_FACT_HASH_ITER) {
1049
0
                ins->info = MVM_op_get_op(MVM_OP_sp_boolify_iter_hash);
1050
0
            } else {
1051
0
                ins->info = MVM_op_get_op(MVM_OP_sp_boolify_iter);
1052
0
            }
1053
0
            break;
1054
732
        case MVM_BOOL_MODE_UNBOX_INT:
1055
732
            if (!guaranteed_concrete)
1056
0
                return;
1057
732
                /* We can just unbox the int and pretend it's a bool. */
1058
732
            ins->info = MVM_op_get_op(MVM_OP_unbox_i);
1059
732
            /* And then we might be able to optimize this even further. */
1060
732
            optimize_unbox(tc, g, bb, ins);
1061
732
            break;
1062
0
        case MVM_BOOL_MODE_BIGINT:
1063
0
            if (!guaranteed_concrete)
1064
0
                return;
1065
0
            ins->info = MVM_op_get_op(MVM_OP_bool_I);
1066
0
            break;
1067
104
        case MVM_BOOL_MODE_HAS_ELEMS:
1068
104
            if (!guaranteed_concrete)
1069
0
                return;
1070
104
            ins->info = MVM_op_get_op(MVM_OP_elems);
1071
104
            optimize_repr_op(tc, g, bb, ins, 1);
1072
104
            break;
1073
792
        case MVM_BOOL_MODE_NOT_TYPE_OBJECT:
1074
792
            ins->info = MVM_op_get_op(MVM_OP_isconcrete);
1075
792
            /* And now defer another bit of optimization */
1076
792
            optimize_isconcrete(tc, g, ins);
1077
792
            break;
1078
104
            /* We need to change the register type for our result for this,
1079
104
             * means we need to insert a temporarary and a coerce:
1080
104
        case MVM_BOOL_MODE_UNBOX_NUM:
1081
104
             op_info = MVM_op_get_op(MVM_OP_unbox_i);
1082
104
             break;
1083
104
            */
1084
155
        default:
1085
155
            return;
1086
1.78k
        }
1087
1.78k
        /* Now we can take care of the negation. - NB I'm not entirely sure why
1088
1.78k
         * this would need it's own register though! */
1089
1.62k
        if (negated_op) {
1090
109
            /* Insert a not_i instruction that negates temp. This not_i is
1091
109
             * subject to further optimization in the case that temp has a
1092
109
             * known value set on it. */
1093
109
            MVMSpeshOperand orig       = ins->operands[0];
1094
109
            MVMSpeshOperand temp       = MVM_spesh_manipulate_get_temp_reg(tc, g, MVM_reg_int64);
1095
109
            MVMSpeshIns     *new_ins   = MVM_spesh_alloc(tc, g, sizeof( MVMSpeshIns ));
1096
109
            MVMSpeshOperand *operands  = MVM_spesh_alloc(tc, g, sizeof( MVMSpeshOperand ) * 2);
1097
109
            MVMSpeshFacts  *temp_facts = MVM_spesh_get_facts(tc,g,temp);
1098
109
            new_ins->info = MVM_op_get_op(MVM_OP_not_i);
1099
109
            new_ins->operands = operands;
1100
109
            operands[0] = orig;
1101
109
            operands[1] = temp;
1102
109
            ins->operands[0] = temp;
1103
109
            MVM_spesh_manipulate_insert_ins(tc, bb, ins, new_ins);
1104
109
            MVM_spesh_manipulate_release_temp_reg(tc, g, temp);
1105
109
            /* temp is now put in the place of orig (results). Thus
1106
109
             * all facts computed on results are now true about temp */
1107
109
            copy_facts(tc, g, temp, orig);
1108
109
            /* however, only one usage */
1109
109
            temp_facts->usages = 1;
1110
109
            /* the new writer of the result facts = new_ins */
1111
109
            result_facts->writer = new_ins;
1112
109
            /* finally, if result_facts had a known value, forget it.
1113
109
             * (optimize_not_i will set it) */
1114
109
            result_facts->flags &= ~MVM_SPESH_FACT_KNOWN_VALUE;
1115
109
        }
1116
1.62k
1117
1.62k
        MVM_spesh_use_facts(tc, g, input_facts);
1118
1.62k
        return;
1119
1.78k
    }
1120
8.92k
}
1121
1122
/* Optimize an object conditional (if_o, unless_o) to simpler operations.
1123
 *
1124
 * We always perform the split of the if_o to istrue + if_i, because a branch
1125
 * plus an invokish operator is rather problematic for the JIT.
1126
 *
1127
 * We then try to optimize the resulting istrue, either by known value (to a
1128
 * constant), or by known type (to a nonpolymorphic operation), deferring to
1129
 * optimize_istrue_isfalse. The nonpolymorphic operation may itself be further
1130
 * optimized (e.g. by optimize_isconcrete).
1131
 *
1132
 * If the optimization has yielded a known value, we may be able to remove the
1133
 * branch entirely (e.g. optimize_iffy)
1134
 *
1135
 * We push the newly split if_i / unless_i forward (rather than pushing the
1136
 * istrue 'backward'), so that the 'normal' optimizer routines pick them up,
1137
 * rather than having to force picking them ourselves.
1138
 */
1139
8.17k
static void optimize_object_conditional(MVMThreadContext *tc, MVMSpeshGraph *g, MVMSpeshIns *ins, MVMSpeshBB *bb) {
1140
8.17k
    MVMSpeshOperand temp      = MVM_spesh_manipulate_get_temp_reg(tc, g, MVM_reg_int64);
1141
8.17k
    MVMSpeshOperand condition = ins->operands[0];
1142
8.17k
    MVMSpeshOperand target    = ins->operands[1];
1143
8.17k
    MVMSpeshFacts *temp_facts = MVM_spesh_get_facts(tc, g, temp);
1144
8.17k
    MVMSpeshIns *new_ins      = MVM_spesh_alloc(tc, g, sizeof( MVMSpeshIns ));
1145
8.17k
1146
8.17k
    /* Insert if_i/unless_i after the current one */
1147
8.17k
    new_ins->info = MVM_op_get_op(ins->info->opcode == MVM_OP_unless_o ? MVM_OP_unless_i : MVM_OP_if_i);
1148
8.17k
    new_ins->operands = MVM_spesh_alloc(tc, g, sizeof(MVMSpeshOperand) * 2);
1149
8.17k
    new_ins->operands[0] = temp;
1150
8.17k
    new_ins->operands[1] = target;
1151
8.17k
    MVM_spesh_manipulate_insert_ins(tc, bb, ins, new_ins);
1152
8.17k
1153
8.17k
    /* Tweak existing instruction to istrue */
1154
8.17k
    ins->info = MVM_op_get_op(MVM_OP_istrue);
1155
8.17k
    ins->operands[1] = condition;
1156
8.17k
    ins->operands[0] = temp;
1157
8.17k
1158
8.17k
    temp_facts->usages++;
1159
8.17k
    temp_facts->writer = new_ins;
1160
8.17k
1161
8.17k
    /* try to optimize the istrue */
1162
8.17k
    optimize_istrue_isfalse(tc, g, bb, ins);
1163
8.17k
1164
8.17k
    /* Release the temporary register */
1165
8.17k
    MVM_spesh_manipulate_release_temp_reg(tc, g, temp);
1166
8.17k
}
1167
1168
1169
1170
/* Turns a getlex instruction into getlex_o or getlex_ins depending on type;
1171
 * these get rid of some branching as well as don't log. */
1172
11.3k
static void optimize_getlex(MVMThreadContext *tc, MVMSpeshGraph *g, MVMSpeshIns *ins) {
1173
11.3k
    MVMuint16 *lexical_types;
1174
11.3k
    MVMuint16 i;
1175
11.3k
    MVMStaticFrame *sf = g->sf;
1176
25.9k
    for (i = 0; i < ins->operands[1].lex.outers; i++)
1177
14.6k
        sf = sf->body.outer;
1178
2.56k
    lexical_types = sf == g->sf && g->lexical_types
1179
1.54k
        ? g->lexical_types
1180
9.80k
        : sf->body.lexical_types;
1181
11.3k
    ins->info = MVM_op_get_op(lexical_types[ins->operands[1].lex.idx] == MVM_reg_obj
1182
6.83k
        ? MVM_OP_sp_getlex_o
1183
4.51k
        : MVM_OP_sp_getlex_ins);
1184
11.3k
}
1185
1186
/* Transforms a late-bound lexical lookup into a constant. */
1187
static void lex_to_constant(MVMThreadContext *tc, MVMSpeshGraph *g, MVMSpeshIns *ins,
1188
13.9k
                            MVMObject *log_obj) {
1189
13.9k
    MVMSpeshFacts *facts;
1190
13.9k
1191
13.9k
    /* Place in a spesh slot. */
1192
13.9k
    MVMuint16 ss = MVM_spesh_add_spesh_slot_try_reuse(tc, g,
1193
13.9k
        (MVMCollectable *)log_obj);
1194
13.9k
1195
13.9k
    /* Transform lookup instruction into spesh slot read. */
1196
13.9k
    MVM_spesh_get_facts(tc, g, ins->operands[1])->usages--;
1197
13.9k
    ins->info = MVM_op_get_op(MVM_OP_sp_getspeshslot);
1198
13.9k
    ins->operands[1].lit_i16 = ss;
1199
13.9k
1200
13.9k
    /* Set up facts. */
1201
13.9k
    facts = MVM_spesh_get_facts(tc, g, ins->operands[0]);
1202
13.9k
    facts->flags  |= MVM_SPESH_FACT_KNOWN_TYPE | MVM_SPESH_FACT_KNOWN_VALUE;
1203
13.9k
    facts->type    = STABLE(log_obj)->WHAT;
1204
13.9k
    facts->value.o = log_obj;
1205
13.9k
    if (IS_CONCRETE(log_obj)) {
1206
328
        facts->flags |= MVM_SPESH_FACT_CONCRETE;
1207
328
        if (!STABLE(log_obj)->container_spec)
1208
328
            facts->flags |= MVM_SPESH_FACT_DECONTED;
1209
328
    }
1210
13.6k
    else {
1211
13.6k
        facts->flags |= MVM_SPESH_FACT_TYPEOBJ;
1212
13.6k
    }
1213
13.9k
}
1214
1215
/* Optimizes away a lexical lookup when we know the value won't change from
1216
 * the logged one. */
1217
static void optimize_getlex_known(MVMThreadContext *tc, MVMSpeshGraph *g, MVMSpeshBB *bb,
1218
1.15k
                                  MVMSpeshIns *ins) {
1219
1.15k
    /* Try to find logged offset. */
1220
1.15k
    MVMSpeshAnn *ann = ins->annotations;
1221
1.15k
    while (ann) {
1222
1.15k
        if (ann->type == MVM_SPESH_ANN_LOGGED)
1223
1.15k
            break;
1224
0
        ann = ann->next;
1225
0
    }
1226
1.15k
    if (ann) {
1227
1.15k
        /* See if we can find a logged static value. */
1228
1.15k
        MVMSpeshStats *ss = g->sf->body.spesh->body.spesh_stats;
1229
1.15k
        MVMuint32 n = ss->num_static_values;
1230
1.15k
        MVMuint32 i;
1231
6.65k
        for (i = 0; i < n; i++) {
1232
5.82k
            if (ss->static_values[i].bytecode_offset == ann->data.bytecode_offset) {
1233
328
                MVMObject *log_obj = ss->static_values[i].value;
1234
328
                if (log_obj)
1235
328
                    lex_to_constant(tc, g, ins, log_obj);
1236
328
                return;
1237
328
            }
1238
5.82k
        }
1239
1.15k
    }
1240
1.15k
}
1241
1242
/* Optimizes away a lexical lookup when we know the value won't change for a
1243
 * given invocant type (this relies on us being in a typed specialization). */
1244
static void optimize_getlex_per_invocant(MVMThreadContext *tc, MVMSpeshGraph *g,
1245
                                         MVMSpeshBB *bb, MVMSpeshIns *ins,
1246
15.2k
                                         MVMSpeshPlanned *p) {
1247
15.2k
    MVMSpeshAnn *ann;
1248
15.2k
1249
15.2k
    /* Can only do this when we've specialized on the first argument type. */
1250
15.2k
    if (!g->specialized_on_invocant)
1251
473
        return;
1252
15.2k
1253
15.2k
    /* Try to find logged offset. */
1254
14.7k
    ann = ins->annotations;
1255
14.7k
    while (ann) {
1256
14.7k
        if (ann->type == MVM_SPESH_ANN_LOGGED)
1257
14.7k
            break;
1258
0
        ann = ann->next;
1259
0
    }
1260
14.7k
    if (ann) {
1261
14.7k
        MVMuint32 i;
1262
15.8k
        for (i = 0; i < p->num_type_stats; i++) {
1263
14.7k
            MVMSpeshStatsByType *ts = p->type_stats[i];
1264
14.7k
            MVMuint32 j;
1265
89.4k
            for (j = 0; j < ts->num_by_offset; j++) {
1266
88.3k
                if (ts->by_offset[j].bytecode_offset == ann->data.bytecode_offset) {
1267
13.6k
                    if (ts->by_offset[j].num_types) {
1268
13.6k
                        MVMObject *log_obj = ts->by_offset[j].types[0].type;
1269
13.6k
                        if (log_obj && !ts->by_offset[j].types[0].type_concrete)
1270
13.6k
                            lex_to_constant(tc, g, ins, log_obj);
1271
13.6k
                        return;
1272
13.6k
                    }
1273
0
                    break;
1274
13.6k
                }
1275
88.3k
            }
1276
14.7k
        }
1277
14.7k
    }
1278
14.7k
}
1279
1280
/* Determines if there's a matching spesh candidate for a callee and a given
1281
 * set of argument info. */
1282
static MVMint32 try_find_spesh_candidate(MVMThreadContext *tc, MVMStaticFrame *sf,
1283
                                         MVMSpeshCallInfo *arg_info,
1284
22.6k
                                         MVMSpeshStatsType *type_tuple) {
1285
22.6k
    MVMSpeshArgGuard *ag = sf->body.spesh->body.spesh_arg_guard;
1286
22.6k
    return type_tuple
1287
14.8k
        ? MVM_spesh_arg_guard_run_types(tc, ag, arg_info->cs, type_tuple)
1288
7.74k
        : MVM_spesh_arg_guard_run_callinfo(tc, ag, arg_info);
1289
22.6k
}
1290
1291
/* Given an invoke instruction, find its logging bytecode offset. Returns 0
1292
 * if not found. */
1293
41.0k
MVMuint32 find_invoke_offset(MVMThreadContext *tc, MVMSpeshIns *ins) {
1294
41.0k
    MVMSpeshAnn *ann = ins->annotations;
1295
102k
    while (ann) {
1296
101k
        if (ann->type == MVM_SPESH_ANN_LOGGED)
1297
39.8k
            return ann->data.bytecode_offset;
1298
61.4k
        ann = ann->next;
1299
61.4k
    }
1300
1.18k
    return 0;
1301
41.0k
}
1302
1303
/* Given an instruction, finds the deopt target on it. Panics if there is not
1304
 * one there. */
1305
16.2k
MVMuint32 find_deopt_target(MVMThreadContext *tc, MVMSpeshGraph *g, MVMSpeshIns *ins) {
1306
16.2k
    MVMuint32 deopt_target;
1307
16.2k
    MVMSpeshAnn *deopt_ann = ins->annotations;
1308
16.2k
    while (deopt_ann) {
1309
16.2k
        if (deopt_ann->type == MVM_SPESH_ANN_DEOPT_ONE_INS)
1310
16.2k
            return g->deopt_addrs[2 * deopt_ann->data.deopt_idx];
1311
0
        deopt_ann = deopt_ann->next;
1312
0
    }
1313
0
    MVM_panic(1, "Spesh: unexpectedly missing deopt annotation on prepargs");
1314
0
}
1315
1316
/* Given a callsite instruction, finds the type tuples there and checks if
1317
 * there is a relatively stable one. */
1318
static MVMSpeshStatsType * find_invokee_type_tuple(MVMThreadContext *tc, MVMSpeshGraph *g,
1319
                                                   MVMSpeshBB *bb, MVMSpeshIns *ins,
1320
23.5k
                                                   MVMSpeshPlanned *p, MVMCallsite *expect_cs) {
1321
23.5k
    MVMuint32 i;
1322
23.5k
    MVMSpeshStatsType *best_result = NULL;
1323
23.5k
    MVMuint32 best_result_hits = 0;
1324
23.5k
    MVMuint32 total_hits = 0;
1325
23.5k
    size_t tt_size = expect_cs->flag_count * sizeof(MVMSpeshStatsType);
1326
23.5k
1327
23.5k
    /* First try to find logging bytecode offset. */
1328
23.5k
    MVMuint32 invoke_offset = find_invoke_offset(tc, ins);
1329
23.5k
    if (!invoke_offset)
1330
991
        return NULL;
1331
23.5k
1332
23.5k
    /* Now look for the best type tuple. */
1333
45.0k
    for (i = 0; i < p->num_type_stats; i++) {
1334
22.4k
        MVMSpeshStatsByType *ts = p->type_stats[i];
1335
22.4k
        MVMuint32 j;
1336
402k
        for (j = 0; j < ts->num_by_offset; j++) {
1337
379k
            if (ts->by_offset[j].bytecode_offset == invoke_offset) {
1338
18.4k
                MVMSpeshStatsByOffset *by_offset = &(ts->by_offset[j]);
1339
18.4k
                MVMuint32 k;
1340
76.5k
                for (k = 0; k < by_offset->num_type_tuples; k++) {
1341
58.1k
                    MVMSpeshStatsTypeTupleCount *tt = &(by_offset->type_tuples[k]);
1342
58.1k
1343
58.1k
                    /* Callsite should always match but skip if not. */
1344
58.1k
                    if (tt->cs != expect_cs)
1345
5
                        continue;
1346
58.1k
1347
58.1k
                    /* Add hits to total we've seen. */
1348
58.1k
                    total_hits += tt->count;
1349
58.1k
1350
58.1k
                    /* If it's the same as the best so far, add hits. */
1351
58.1k
                    if (best_result && memcmp(best_result, tt->arg_types, tt_size) == 0) {
1352
0
                        best_result_hits += tt->count;
1353
0
                    }
1354
58.1k
1355
58.1k
                    /* Otherwise, if it beats the best result in hits, use. */
1356
58.1k
                    else if (tt->count > best_result_hits) {
1357
16.8k
                        best_result = tt->arg_types;
1358
16.8k
                        best_result_hits = tt->count;
1359
16.8k
                    }
1360
58.1k
                }
1361
18.4k
            }
1362
379k
        }
1363
22.4k
    }
1364
22.5k
1365
22.5k
    /* If the type tuple is used consistently enough, return it. */
1366
16.3k
    return total_hits && (100 * best_result_hits) / total_hits >= MVM_SPESH_CALLSITE_STABLE_PERCENT
1367
14.9k
        ? best_result
1368
22.5k
        : NULL;
1369
23.5k
}
1370
1371
/* Inserts an argument type guard as suggested by a logged type tuple. */
1372
static void insert_arg_type_guard(MVMThreadContext *tc, MVMSpeshGraph *g,
1373
                                  MVMSpeshStatsType *type_info,
1374
8.62k
                                  MVMSpeshCallInfo *arg_info, MVMuint32 arg_idx) {
1375
8.62k
    /* Insert guard before prepargs (this means they stack up in order). */
1376
8.62k
    MVMuint32 deopt_target = find_deopt_target(tc, g, arg_info->prepargs_ins);
1377
8.62k
    MVMSpeshIns *guard = MVM_spesh_alloc(tc, g, sizeof(MVMSpeshIns));
1378
8.62k
    guard->info = MVM_op_get_op(type_info->type_concrete
1379
8.51k
        ? MVM_OP_sp_guardconc
1380
114
        : MVM_OP_sp_guardtype);
1381
8.62k
    guard->operands = MVM_spesh_alloc(tc, g, 3 * sizeof(MVMSpeshOperand));
1382
8.62k
    guard->operands[0] = arg_info->arg_ins[arg_idx]->operands[1];
1383
8.62k
    guard->operands[1].lit_i16 = MVM_spesh_add_spesh_slot_try_reuse(tc, g,
1384
8.62k
        (MVMCollectable *)type_info->type->st);
1385
8.62k
    guard->operands[2].lit_ui32 = deopt_target;
1386
8.62k
    MVM_spesh_manipulate_insert_ins(tc, arg_info->prepargs_bb,
1387
8.62k
        arg_info->prepargs_ins->prev, guard);
1388
8.62k
1389
8.62k
    /* Also give the instruction a deopt annotation. */
1390
8.62k
    MVM_spesh_graph_add_deopt_annotation(tc, g, guard, deopt_target,
1391
8.62k
        MVM_SPESH_ANN_DEOPT_ONE_INS);
1392
8.62k
}
1393
1394
/* Inserts an argument decont type guard as suggested by a logged type tuple. */
1395
static void insert_arg_decont_type_guard(MVMThreadContext *tc, MVMSpeshGraph *g,
1396
                                         MVMSpeshStatsType *type_info,
1397
0
                                         MVMSpeshCallInfo *arg_info, MVMuint32 arg_idx) {
1398
0
    MVMSpeshIns *decont, *guard;
1399
0
    MVMuint32 deopt_target;
1400
0
1401
0
    /* We need a temporary register to decont into. */
1402
0
    MVMSpeshOperand temp = MVM_spesh_manipulate_get_temp_reg(tc, g, MVM_reg_obj);
1403
0
1404
0
    /* Insert the decont, then try to optimize it into something cheaper. */
1405
0
    decont = MVM_spesh_alloc(tc, g, sizeof(MVMSpeshIns));
1406
0
    decont->info = MVM_op_get_op(MVM_OP_decont);
1407
0
    decont->operands = MVM_spesh_alloc(tc, g, 2 * sizeof(MVMSpeshOperand));
1408
0
    decont->operands[0] = temp;
1409
0
    decont->operands[1] = arg_info->arg_ins[arg_idx]->operands[1];
1410
0
    MVM_spesh_manipulate_insert_ins(tc, arg_info->prepargs_bb,
1411
0
        arg_info->prepargs_ins->prev, decont);
1412
0
    MVM_spesh_get_facts(tc, g, temp)->usages++;
1413
0
    optimize_decont(tc, g, arg_info->prepargs_bb, decont);
1414
0
1415
0
    /* Guard the decontainerized value. */
1416
0
    deopt_target = find_deopt_target(tc, g, arg_info->prepargs_ins);
1417
0
    guard = MVM_spesh_alloc(tc, g, sizeof(MVMSpeshIns));
1418
0
    guard->info = MVM_op_get_op(type_info->decont_type_concrete
1419
0
        ? MVM_OP_sp_guardconc
1420
0
        : MVM_OP_sp_guardtype);
1421
0
    guard->operands = MVM_spesh_alloc(tc, g, 3 * sizeof(MVMSpeshOperand));
1422
0
    guard->operands[0] = temp;
1423
0
    guard->operands[1].lit_i16 = MVM_spesh_add_spesh_slot_try_reuse(tc, g,
1424
0
        (MVMCollectable *)type_info->decont_type->st);
1425
0
    guard->operands[2].lit_ui32 = deopt_target;
1426
0
    MVM_spesh_manipulate_insert_ins(tc, arg_info->prepargs_bb,
1427
0
        arg_info->prepargs_ins->prev, guard);
1428
0
1429
0
    /* Also give the instruction a deopt annotation. */
1430
0
    MVM_spesh_graph_add_deopt_annotation(tc, g, guard, deopt_target,
1431
0
        MVM_SPESH_ANN_DEOPT_ONE_INS);
1432
0
1433
0
    /* Release the temp register. */
1434
0
    MVM_spesh_manipulate_release_temp_reg(tc, g, temp);
1435
0
}
1436
1437
/* Look through the call info and the type tuple, see what guards we are
1438
 * missing, and insert them. */
1439
static void check_and_tweak_arg_guards(MVMThreadContext *tc, MVMSpeshGraph *g,
1440
                                       MVMSpeshStatsType *type_tuple,
1441
14.9k
                                       MVMSpeshCallInfo *arg_info) {
1442
14.9k
    MVMuint32 n = arg_info->cs->flag_count;
1443
14.9k
    MVMuint32 arg_idx = 0;
1444
14.9k
    MVMuint32 i;
1445
39.8k
    for (i = 0; i < n; i++, arg_idx++) {
1446
24.9k
        if (arg_info->cs->arg_flags[i] & MVM_CALLSITE_ARG_NAMED)
1447
624
            arg_idx++;
1448
24.9k
        if (arg_info->cs->arg_flags[i] & MVM_CALLSITE_ARG_OBJ) {
1449
20.3k
            MVMObject *t_type = type_tuple[i].type;
1450
20.3k
            MVMObject *t_decont_type = type_tuple[i].decont_type;
1451
20.3k
            if (t_type) {
1452
20.3k
                /* Add a guard unless the facts already match. */
1453
20.3k
                MVMSpeshFacts *arg_facts = arg_info->arg_facts[arg_idx];
1454
20.3k
                MVMuint32 need_guard = !arg_facts ||
1455
20.3k
                    !(arg_facts->flags & MVM_SPESH_FACT_KNOWN_TYPE) ||
1456
12.2k
                    arg_facts->type != t_type ||
1457
12.2k
                    type_tuple[i].type_concrete
1458
11.3k
                        && !(arg_facts->flags & MVM_SPESH_FACT_CONCRETE) ||
1459
11.7k
                    !type_tuple[i].type_concrete
1460
885
                        && !(arg_facts->flags & MVM_SPESH_FACT_TYPEOBJ);
1461
20.3k
                if (need_guard)
1462
8.62k
                    insert_arg_type_guard(tc, g, &type_tuple[i], arg_info, arg_idx);
1463
20.3k
            }
1464
20.3k
            if (t_decont_type)
1465
0
                insert_arg_decont_type_guard(tc, g, &type_tuple[i], arg_info, arg_idx);
1466
20.3k
        }
1467
24.9k
    }
1468
14.9k
}
1469
1470
/* Sees if any static frames were logged for this invoke instruction, and
1471
 * if so checks if there was a stable one. A static frame chosen by multi
1472
 * dispatch will for now never count as stable, as we don't have a good way
1473
 * to handle this situation yet and trying results in deopts. */
1474
MVMStaticFrame * find_invokee_static_frame(MVMThreadContext *tc, MVMSpeshPlanned *p,
1475
17.4k
                                           MVMSpeshIns *ins) {
1476
17.4k
    MVMuint32 i;
1477
17.4k
    MVMStaticFrame *best_result = NULL;
1478
17.4k
    MVMuint32 best_result_hits = 0;
1479
17.4k
    MVMuint32 best_result_was_multi_hits = 0;
1480
17.4k
    MVMuint32 total_hits = 0;
1481
17.4k
1482
17.4k
    /* First try to find logging bytecode offset. */
1483
17.4k
    MVMuint32 invoke_offset = find_invoke_offset(tc, ins);
1484
17.4k
    if (!invoke_offset)
1485
189
        return NULL;
1486
17.4k
1487
17.4k
    /* Now look for a stable invokee. */
1488
33.5k
    for (i = 0; i < p->num_type_stats; i++) {
1489
16.3k
        MVMSpeshStatsByType *ts = p->type_stats[i];
1490
16.3k
        MVMuint32 j;
1491
337k
        for (j = 0; j < ts->num_by_offset; j++) {
1492
321k
            if (ts->by_offset[j].bytecode_offset == invoke_offset) {
1493
8.15k
                MVMSpeshStatsByOffset *by_offset = &(ts->by_offset[j]);
1494
8.15k
                MVMuint32 k;
1495
22.6k
                for (k = 0; k < by_offset->num_invokes; k++) {
1496
14.4k
                    MVMSpeshStatsInvokeCount *ic = &(by_offset->invokes[k]);
1497
14.4k
1498
14.4k
                    /* Add hits to total we've seen. */
1499
14.4k
                    total_hits += ic->count;
1500
14.4k
1501
14.4k
                    /* If it's the same as the best so far, add hits. */
1502
14.4k
                    if (best_result && ic->sf == best_result) {
1503
0
                        best_result_hits += ic->count;
1504
0
                        best_result_was_multi_hits += ic->was_multi_count;
1505
0
                    }
1506
14.4k
1507
14.4k
                    /* Otherwise, if it beats the best result in hits, use. */
1508
14.4k
                    else if (ic->count > best_result_hits) {
1509
8.64k
                        best_result = ic->sf;
1510
8.64k
                        best_result_hits = ic->count;
1511
8.64k
                        best_result_was_multi_hits = ic->was_multi_count;
1512
8.64k
                    }
1513
14.4k
                }
1514
8.15k
            }
1515
321k
        }
1516
16.3k
    }
1517
17.2k
1518
17.2k
    /* If the chosen frame was a multi, give up. */
1519
17.2k
    if (best_result_was_multi_hits)
1520
4
        return NULL;
1521
17.2k
1522
17.2k
    /* If the static frame is consistent enough, return it. */
1523
17.2k
    return total_hits && (100 * best_result_hits) / total_hits >= MVM_SPESH_CALLSITE_STABLE_PERCENT
1524
7.66k
        ? best_result
1525
17.2k
        : NULL;
1526
17.2k
}
1527
1528
/* Inserts resolution of the invokee to an MVMCode and the guard on the
1529
 * invocation, and then tweaks the invoke instruction to use the resolved
1530
 * code object (for the case it is further optimized into a fast invoke). */
1531
static void tweak_for_target_sf(MVMThreadContext *tc, MVMSpeshGraph *g,
1532
                                MVMStaticFrame *target_sf, MVMSpeshIns *ins,
1533
7.66k
                                MVMSpeshCallInfo *arg_info, MVMSpeshOperand temp) {
1534
7.66k
    MVMSpeshIns *guard, *resolve;
1535
7.66k
    MVMuint32 deopt_target;
1536
7.66k
1537
7.66k
    /* Work out which operand of the invoke instruction has the invokee. */
1538
7.66k
    MVMuint32 inv_code_index = ins->info->opcode == MVM_OP_invoke_v ? 0 : 1;
1539
7.66k
1540
7.66k
    /* Insert instruction to resolve any code wrapper into the MVMCode before
1541
7.66k
     * prepargs. */
1542
7.66k
    resolve = MVM_spesh_alloc(tc, g, sizeof(MVMSpeshIns));
1543
7.66k
    resolve->info = MVM_op_get_op(MVM_OP_sp_resolvecode);
1544
7.66k
    resolve->operands = MVM_spesh_alloc(tc, g, 2 * sizeof(MVMSpeshOperand));
1545
7.66k
    resolve->operands[0] = temp;
1546
7.66k
    resolve->operands[1] = ins->operands[inv_code_index];
1547
7.66k
    MVM_spesh_manipulate_insert_ins(tc, arg_info->prepargs_bb,
1548
7.66k
        arg_info->prepargs_ins->prev, resolve);
1549
7.66k
1550
7.66k
    /* Insert guard instruction before the prepargs. */
1551
7.66k
    deopt_target = find_deopt_target(tc, g, arg_info->prepargs_ins);
1552
7.66k
    guard = MVM_spesh_alloc(tc, g, sizeof(MVMSpeshIns));
1553
7.66k
    guard->info = MVM_op_get_op(MVM_OP_sp_guardsf);
1554
7.66k
    guard->operands = MVM_spesh_alloc(tc, g, 3 * sizeof(MVMSpeshOperand));
1555
7.66k
    guard->operands[0] = temp;
1556
7.66k
    guard->operands[1].lit_i16 = MVM_spesh_add_spesh_slot_try_reuse(tc, g,
1557
7.66k
        (MVMCollectable *)target_sf);
1558
7.66k
    guard->operands[2].lit_ui32 = deopt_target;
1559
7.66k
    MVM_spesh_manipulate_insert_ins(tc, arg_info->prepargs_bb,
1560
7.66k
        arg_info->prepargs_ins->prev, guard);
1561
7.66k
1562
7.66k
    /* Also give the guard instruction a deopt annotation. */
1563
7.66k
    MVM_spesh_graph_add_deopt_annotation(tc, g, guard, deopt_target,
1564
7.66k
        MVM_SPESH_ANN_DEOPT_ONE_INS);
1565
7.66k
1566
7.66k
    /* Make the invoke instruction call the resolved result. */
1567
7.66k
    ins->operands[inv_code_index] = temp;
1568
7.66k
1569
7.66k
    /* Bump temp usage (one for the guard, one for the invoke). */
1570
7.66k
    MVM_spesh_get_facts(tc, g, temp)->usages += 2;
1571
7.66k
}
1572
1573
/* Drives optimization of a call. */
1574
static void optimize_call(MVMThreadContext *tc, MVMSpeshGraph *g, MVMSpeshBB *bb,
1575
                          MVMSpeshIns *ins, MVMSpeshPlanned *p, MVMint32 callee_idx,
1576
33.3k
                          MVMSpeshCallInfo *arg_info) {
1577
33.3k
    MVMSpeshStatsType *stable_type_tuple;
1578
33.3k
    MVMObject *target = NULL;
1579
33.3k
    MVMuint32 num_arg_slots;
1580
33.3k
    MVMSpeshOperand code_temp;
1581
33.3k
1582
33.3k
    /* Check we know what we're going to be invoking. */
1583
33.3k
    MVMSpeshFacts *callee_facts = MVM_spesh_get_and_use_facts(tc, g, ins->operands[callee_idx]);
1584
33.3k
    MVMObject *code = NULL;
1585
33.3k
    MVMStaticFrame *target_sf = NULL;
1586
33.3k
    MVMint32 have_code_temp = 0;
1587
33.3k
    if (callee_facts->flags & MVM_SPESH_FACT_KNOWN_VALUE) {
1588
15.9k
        /* Already know the target code object based on existing guards or
1589
15.9k
         * a static value. */
1590
15.9k
        code = callee_facts->value.o;
1591
15.9k
    }
1592
17.4k
    else {
1593
17.4k
        /* See if there is a stable static frame at the callsite. If so, add
1594
17.4k
         * the resolution and guard instruction. Note that we must keep the
1595
17.4k
         * temporary alive throughout the whole guard and invocation sequence,
1596
17.4k
         * as an inline may use it during deopt to find the code ref. */
1597
17.4k
        target_sf = find_invokee_static_frame(tc, p, ins);
1598
17.4k
        if (target_sf) {
1599
7.66k
            code_temp = MVM_spesh_manipulate_get_temp_reg(tc, g, MVM_reg_obj);
1600
7.66k
            have_code_temp = 1;
1601
7.66k
            tweak_for_target_sf(tc, g, target_sf, ins, arg_info, code_temp);
1602
7.66k
        }
1603
17.4k
    }
1604
33.3k
    if (!code && !target_sf)
1605
9.80k
        return;
1606
33.3k
1607
33.3k
    /* See if there's a stable type tuple at this callsite. If so, see if we
1608
33.3k
     * are missing any guards required, and try to insert them if so. Only do
1609
33.3k
     * this if the callsite isn't too big for arg_info. */
1610
23.5k
    num_arg_slots = arg_info->cs->num_pos +
1611
23.5k
        2 * (arg_info->cs->flag_count - arg_info->cs->num_pos);
1612
23.5k
    stable_type_tuple = num_arg_slots <= MAX_ARGS_FOR_OPT
1613
23.5k
        ? find_invokee_type_tuple(tc, g, bb, ins, p, arg_info->cs)
1614
23.5k
        : NULL;
1615
23.5k
    if (stable_type_tuple)
1616
14.9k
        check_and_tweak_arg_guards(tc, g, stable_type_tuple, arg_info);
1617
23.5k
1618
23.5k
    /* If we don't have a target static frame from speculation, check on what
1619
23.5k
     * we're going to be invoking and see if we can further resolve it. */
1620
23.5k
    if (!target_sf) {
1621
15.9k
        if (REPR(code)->ID == MVM_REPR_ID_MVMCode) {
1622
398
            /* Already have a code object we know we'll call. */
1623
398
            target = code;
1624
398
        }
1625
15.5k
        else if (IS_CONCRETE(code) && STABLE(code)->invocation_spec) {
1626
15.5k
            /* What kind of invocation will it be? */
1627
15.5k
            MVMInvocationSpec *is = STABLE(code)->invocation_spec;
1628
15.5k
            if (!MVM_is_null(tc, is->md_class_handle)) {
1629
14.4k
                /* Multi-dispatch. Check if this is a dispatch where we can
1630
14.4k
                 * use the cache directly. */
1631
14.4k
                MVMRegister dest;
1632
14.4k
                REPR(code)->attr_funcs.get_attribute(tc,
1633
14.4k
                    STABLE(code), code, OBJECT_BODY(code),
1634
14.4k
                    is->md_class_handle, is->md_valid_attr_name,
1635
14.4k
                    is->md_valid_hint, &dest, MVM_reg_int64);
1636
14.4k
                if (dest.i64) {
1637
239
                    /* Yes. Try to obtain the cache. */
1638
239
                    REPR(code)->attr_funcs.get_attribute(tc,
1639
239
                        STABLE(code), code, OBJECT_BODY(code),
1640
239
                        is->md_class_handle, is->md_cache_attr_name,
1641
239
                        is->md_cache_hint, &dest, MVM_reg_obj);
1642
239
                    if (!MVM_is_null(tc, dest.o)) {
1643
239
                        MVMObject *found = MVM_multi_cache_find_spesh(tc, dest.o,
1644
239
                            arg_info, stable_type_tuple);
1645
239
                        if (found) {
1646
129
                            /* Found it. Is it a code object already, or do we
1647
129
                             * have futher unpacking to do? */
1648
129
                            if (REPR(found)->ID == MVM_REPR_ID_MVMCode) {
1649
0
                                target = found;
1650
0
                            }
1651
129
                            else if (STABLE(found)->invocation_spec) {
1652
129
                                MVMInvocationSpec *m_is = STABLE(found)->invocation_spec;
1653
129
                                if (!MVM_is_null(tc, m_is->class_handle)) {
1654
129
                                    REPR(found)->attr_funcs.get_attribute(tc,
1655
129
                                        STABLE(found), found, OBJECT_BODY(found),
1656
129
                                        is->class_handle, is->attr_name,
1657
129
                                        is->hint, &dest, MVM_reg_obj);
1658
129
                                    if (REPR(dest.o)->ID == MVM_REPR_ID_MVMCode)
1659
129
                                        target = dest.o;
1660
129
                                }
1661
129
                            }
1662
129
                        }
1663
239
                    }
1664
239
                }
1665
14.2k
                else if (!MVM_is_null(tc, is->class_handle)) {
1666
14.2k
                    /* This type of code object supports multi-dispatch,
1667
14.2k
                     * but we actually have a single dispatch routine. */
1668
14.2k
                    MVMRegister dest;
1669
14.2k
                    REPR(code)->attr_funcs.get_attribute(tc,
1670
14.2k
                        STABLE(code), code, OBJECT_BODY(code),
1671
14.2k
                        is->class_handle, is->attr_name,
1672
14.2k
                        is->hint, &dest, MVM_reg_obj);
1673
14.2k
                    if (REPR(dest.o)->ID == MVM_REPR_ID_MVMCode)
1674
14.2k
                        target = dest.o;
1675
14.2k
                }
1676
14.4k
            }
1677
1.05k
            else if (!MVM_is_null(tc, is->class_handle)) {
1678
1.05k
                /* Single dispatch; retrieve the code object. */
1679
1.05k
                MVMRegister dest;
1680
1.05k
                REPR(code)->attr_funcs.get_attribute(tc,
1681
1.05k
                    STABLE(code), code, OBJECT_BODY(code),
1682
1.05k
                    is->class_handle, is->attr_name,
1683
1.05k
                    is->hint, &dest, MVM_reg_obj);
1684
1.05k
                if (REPR(dest.o)->ID == MVM_REPR_ID_MVMCode)
1685
1.05k
                    target = dest.o;
1686
1.05k
            }
1687
15.5k
        }
1688
15.9k
        if (!target || !IS_CONCRETE(target))
1689
110
            return;
1690
15.9k
1691
15.9k
        /* If we resolved to something better than the code object, then add
1692
15.9k
         * the resolved item in a spesh slot and insert a lookup. */
1693
15.8k
        if (target != code && !((MVMCode *)target)->body.is_compiler_stub) {
1694
15.4k
            MVMSpeshIns *pa_ins = arg_info->prepargs_ins;
1695
15.4k
            MVMSpeshIns *ss_ins = MVM_spesh_alloc(tc, g, sizeof(MVMSpeshIns));
1696
15.4k
            ss_ins->info        = MVM_op_get_op(MVM_OP_sp_getspeshslot);
1697
15.4k
            ss_ins->operands    = MVM_spesh_alloc(tc, g, 2 * sizeof(MVMSpeshOperand));
1698
15.4k
            ss_ins->operands[0] = ins->operands[callee_idx];
1699
15.4k
            ss_ins->operands[1].lit_i16 = MVM_spesh_add_spesh_slot_try_reuse(tc, g,
1700
15.4k
                (MVMCollectable *)target);
1701
15.4k
            /* Basically, we're inserting between arg* and invoke_*.
1702
15.4k
             * Since invoke_* directly uses the code in the register,
1703
15.4k
             * the register must have held the code during the arg*
1704
15.4k
             * instructions as well, because none of {prepargs, arg*}
1705
15.4k
             * can manipulate the register that holds the code.
1706
15.4k
             *
1707
15.4k
             * To make a long story very short, I think it should be
1708
15.4k
             * safe to move the sp_getspeshslot to /before/ the
1709
15.4k
             * prepargs instruction. And this is very convenient for
1710
15.4k
             * me, as it allows me to treat set of prepargs, arg*,
1711
15.4k
             * invoke, as a /single node/, and this greatly simplifies
1712
15.4k
             * invoke JIT compilation */
1713
15.4k
1714
15.4k
            MVM_spesh_manipulate_insert_ins(tc, bb, pa_ins->prev, ss_ins);
1715
15.4k
            /* XXX TODO: Do this differently so we can eliminate the original
1716
15.4k
             * lookup of the enclosing code object also. */
1717
15.4k
        }
1718
15.8k
1719
15.8k
        /* Extract the target static frame from the target code object; we
1720
15.8k
         * will work in terms of that from here on. */
1721
15.8k
        target_sf = ((MVMCode *)target)->body.sf;
1722
15.8k
    }
1723
23.5k
1724
23.5k
    /* See if we can point the call at a particular specialization. */
1725
23.4k
    if (target_sf->body.instrumentation_level == tc->instance->instrumentation_level) {
1726
22.6k
        MVMint32 spesh_cand = try_find_spesh_candidate(tc, target_sf, arg_info,
1727
22.6k
            stable_type_tuple);
1728
22.6k
        if (spesh_cand >= 0) {
1729
13.7k
            /* Yes. Will we be able to inline? */
1730
13.7k
            char *no_inline_reason = NULL;
1731
13.7k
            MVMSpeshGraph *inline_graph = MVM_spesh_inline_try_get_graph(tc, g,
1732
13.7k
                target_sf, target_sf->body.spesh->body.spesh_candidates[spesh_cand],
1733
13.7k
                ins, &no_inline_reason);
1734
13.7k
            if (inline_graph != NULL && MVM_spesh_debug_enabled(tc)) {
1735
0
                char *dump = MVM_spesh_dump(tc, inline_graph);
1736
0
                MVM_spesh_debug_printf(tc, "Inlining graph\n%s\n", dump);
1737
0
                MVM_free(dump);
1738
0
            }
1739
13.7k
            log_inline(tc, g, target_sf, inline_graph,
1740
13.7k
                target_sf->body.spesh->body.spesh_candidates[spesh_cand]->bytecode_size,
1741
13.7k
                no_inline_reason);
1742
13.7k
            if (inline_graph) {
1743
7.57k
                /* Yes, have inline graph, so go ahead and do it. Make sure we
1744
7.57k
                 * keep the code ref reg alive by giving it a usage count as
1745
7.57k
                 * it will be referenced from the deopt table. */
1746
7.57k
                MVMSpeshOperand code_ref_reg = ins->info->opcode == MVM_OP_invoke_v
1747
632
                        ? ins->operands[0]
1748
6.94k
                        : ins->operands[1];
1749
7.57k
                MVM_spesh_facts_discover(tc, inline_graph, p);
1750
7.57k
                MVM_spesh_get_facts(tc, g, code_ref_reg)->usages++;
1751
7.57k
                MVM_spesh_inline(tc, g, arg_info, bb, ins, inline_graph, target_sf,
1752
7.57k
                        code_ref_reg);
1753
7.57k
                MVM_free(inline_graph->spesh_slots);
1754
7.57k
            }
1755
6.22k
            else {
1756
6.22k
                /* Can't inline, so just identify candidate. */
1757
6.22k
                MVMSpeshOperand *new_operands = MVM_spesh_alloc(tc, g, 3 * sizeof(MVMSpeshOperand));
1758
6.22k
                if (ins->info->opcode == MVM_OP_invoke_v) {
1759
351
                    new_operands[0]         = ins->operands[0];
1760
351
                    new_operands[1].lit_i16 = spesh_cand;
1761
351
                    ins->operands           = new_operands;
1762
351
                    ins->info               = MVM_op_get_op(MVM_OP_sp_fastinvoke_v);
1763
351
                }
1764
5.87k
                else {
1765
5.87k
                    new_operands[0]         = ins->operands[0];
1766
5.87k
                    new_operands[1]         = ins->operands[1];
1767
5.87k
                    new_operands[2].lit_i16 = spesh_cand;
1768
5.87k
                    ins->operands           = new_operands;
1769
5.87k
                    switch (ins->info->opcode) {
1770
0
                    case MVM_OP_invoke_i:
1771
0
                        ins->info = MVM_op_get_op(MVM_OP_sp_fastinvoke_i);
1772
0
                        break;
1773
0
                    case MVM_OP_invoke_n:
1774
0
                        ins->info = MVM_op_get_op(MVM_OP_sp_fastinvoke_n);
1775
0
                        break;
1776
0
                    case MVM_OP_invoke_s:
1777
0
                        ins->info = MVM_op_get_op(MVM_OP_sp_fastinvoke_s);
1778
0
                        break;
1779
5.87k
                    case MVM_OP_invoke_o:
1780
5.87k
                        ins->info = MVM_op_get_op(MVM_OP_sp_fastinvoke_o);
1781
5.87k
                        break;
1782
0
                    default:
1783
0
                        MVM_oops(tc, "Spesh: unhandled invoke instruction");
1784
5.87k
                    }
1785
5.87k
                }
1786
6.22k
            }
1787
13.7k
        }
1788
22.6k
1789
22.6k
        /* We know what we're calling, but there's no specialization available
1790
22.6k
         * to us. If it's small, then we could produce one and inline it. */
1791
8.84k
        else if (target_sf->body.bytecode_size < MVM_SPESH_MAX_INLINE_SIZE) {
1792
5.79k
            log_inline(tc, g, target_sf, NULL, target_sf->body.bytecode_size,
1793
5.79k
                "missed inline opportunity");
1794
5.79k
        }
1795
8.84k
1796
8.84k
        /* Otherwise, nothing to be done. */
1797
3.04k
        else {
1798
3.04k
            log_inline(tc, g, target_sf, NULL, target_sf->body.bytecode_size,
1799
3.04k
                "no spesh candidate available and bytecode too large to produce an inline");
1800
3.04k
        }
1801
22.6k
    }
1802
23.4k
1803
23.4k
    /* If we have a speculated target static frame, then it's now safe to
1804
23.4k
     * release the code temporary (no need to keep it). */
1805
23.4k
    if (have_code_temp)
1806
7.66k
        MVM_spesh_manipulate_release_temp_reg(tc, g, code_temp);
1807
23.4k
}
1808
1809
0
static void optimize_coverage_log(MVMThreadContext *tc, MVMSpeshGraph *g, MVMSpeshBB *bb, MVMSpeshIns *ins) {
1810
0
    char *cache        = (char *)ins->operands[3].lit_i64;
1811
0
    MVMint32 cache_idx = ins->operands[2].lit_i32;
1812
0
1813
0
    if (cache[cache_idx] != 0) {
1814
0
        MVM_spesh_manipulate_delete_ins(tc, g, bb, ins);
1815
0
    }
1816
0
}
1817
1818
/* Optimizes an extension op. */
1819
0
static void optimize_extop(MVMThreadContext *tc, MVMSpeshGraph *g, MVMSpeshBB *bb, MVMSpeshIns *ins) {
1820
0
    MVMExtOpRecord *extops     = g->sf->body.cu->body.extops;
1821
0
    MVMuint16       num_extops = g->sf->body.cu->body.num_extops;
1822
0
    MVMuint16       i;
1823
0
    for (i = 0; i < num_extops; i++) {
1824
0
        if (extops[i].info == ins->info) {
1825
0
            /* Found op; call its spesh function, if any. */
1826
0
            if (extops[i].spesh)
1827
0
                extops[i].spesh(tc, g, bb, ins);
1828
0
            return;
1829
0
        }
1830
0
    }
1831
0
}
1832
1833
0
static void optimize_uniprop_ops(MVMThreadContext *tc, MVMSpeshGraph *g, MVMSpeshBB *bb, MVMSpeshIns *ins) {
1834
0
    MVMSpeshFacts *arg1_facts = MVM_spesh_get_facts(tc, g, ins->operands[1]);
1835
0
    MVMSpeshFacts *result_facts = MVM_spesh_get_facts(tc, g, ins->operands[0]);
1836
0
    if (arg1_facts->flags & MVM_SPESH_FACT_KNOWN_VALUE) {
1837
0
        if (ins->info->opcode == MVM_OP_unipropcode) {
1838
0
            result_facts->flags |= MVM_SPESH_FACT_KNOWN_VALUE;
1839
0
            result_facts->value.i = (MVMint64)MVM_unicode_name_to_property_code(tc, arg1_facts->value.s);
1840
0
            ins->info = MVM_op_get_op(MVM_OP_const_i64);
1841
0
            ins->operands[1].lit_i64 = result_facts->value.i;
1842
0
            arg1_facts->usages--;
1843
0
        } else if (ins->info->opcode == MVM_OP_unipvalcode) {
1844
0
            MVMSpeshFacts *arg2_facts = MVM_spesh_get_facts(tc, g, ins->operands[2]);
1845
0
1846
0
            if (arg2_facts->flags & MVM_SPESH_FACT_KNOWN_VALUE) {
1847
0
                result_facts->flags |= MVM_SPESH_FACT_KNOWN_VALUE;
1848
0
                result_facts->value.i = (MVMint64)MVM_unicode_name_to_property_value_code(tc, arg1_facts->value.i, arg2_facts->value.s);
1849
0
                ins->info = MVM_op_get_op(MVM_OP_const_i64);
1850
0
                ins->operands[1].lit_i64 = result_facts->value.i;
1851
0
                arg1_facts->usages--;
1852
0
                arg2_facts->usages--;
1853
0
            }
1854
0
        }
1855
0
}
1856
0
}
1857
1858
/* If something is only kept alive because we log its allocation, kick out
1859
 * the allocation logging and let the op that creates it die. */
1860
0
static void optimize_prof_allocated(MVMThreadContext *tc, MVMSpeshGraph *g, MVMSpeshBB *bb, MVMSpeshIns *ins) {
1861
0
    MVMSpeshFacts *logee_facts = MVM_spesh_get_facts(tc, g, ins->operands[0]);
1862
0
    if (logee_facts->usages == 1) {
1863
0
        MVM_spesh_manipulate_delete_ins(tc, g, bb, ins);
1864
0
        logee_facts->usages = 0;
1865
0
        /* This check should always succeed, but just in case ... */
1866
0
        if (logee_facts->writer)
1867
0
            MVM_spesh_manipulate_delete_ins(tc, g, bb, logee_facts->writer);
1868
0
    }
1869
0
}
1870
1871
/* Tries to optimize a throwcat instruction. Note that within a given frame
1872
 * (we don't consider inlines here) the throwcat instructions all have the
1873
 * same semantics. */
1874
1.76k
static void optimize_throwcat(MVMThreadContext *tc, MVMSpeshGraph *g, MVMSpeshBB *bb, MVMSpeshIns *ins) {
1875
1.76k
    /* First, see if we have any goto handlers for this category. */
1876
1.76k
    MVMint32 *handlers_found = MVM_malloc(g->num_handlers * sizeof(MVMint32));
1877
1.76k
    MVMint32  num_found      = 0;
1878
1.76k
    MVMuint32 category       = (MVMuint32)ins->operands[1].lit_i64;
1879
1.76k
    MVMint32  i;
1880
34.1k
    for (i = 0; i < g->num_handlers; i++)
1881
32.4k
        if (g->handlers[i].action == MVM_EX_ACTION_GOTO)
1882
31.2k
            if (g->handlers[i].category_mask & category)
1883
8.29k
                handlers_found[num_found++] = i;
1884
1.76k
1885
1.76k
    /* If we found any appropriate handlers, we'll now do a scan through the
1886
1.76k
     * graph to see if we're in the scope of any of them. Note we can't keep
1887
1.76k
     * track of this in optimize_bb as it walks the dominance children, but
1888
1.76k
     * we need a linear view. */
1889
1.76k
    if (num_found) {
1890
1.75k
        MVMint32    *in_handlers = MVM_calloc(g->num_handlers, sizeof(MVMint32));
1891
1.75k
        MVMSpeshBB **goto_bbs    = MVM_calloc(g->num_handlers, sizeof(MVMSpeshBB *));
1892
1.75k
        MVMSpeshBB  *search_bb   = g->entry;
1893
1.75k
        MVMint32     picked      = -1;
1894
302k
        while (search_bb) {
1895
302k
            MVMSpeshIns *search_ins = search_bb->first_ins;
1896
2.60M
            while (search_ins) {
1897
2.30M
                /* Track handlers. */
1898
2.30M
                MVMSpeshAnn *ann = search_ins->annotations;
1899
2.56M
                while (ann) {
1900
268k
                    switch (ann->type) {
1901
26.2k
                    case MVM_SPESH_ANN_FH_START:
1902
26.2k
                        in_handlers[ann->data.frame_handler_index] = 1;
1903
26.2k
                        break;
1904
16.0k
                    case MVM_SPESH_ANN_FH_END:
1905
16.0k
                        in_handlers[ann->data.frame_handler_index] = 0;
1906
16.0k
                        break;
1907
23.2k
                    case MVM_SPESH_ANN_FH_GOTO:
1908
23.2k
                        if (ann->data.frame_handler_index < g->num_handlers) {
1909
23.2k
                            goto_bbs[ann->data.frame_handler_index] = search_bb;
1910
23.2k
                            if (picked >= 0 && ann->data.frame_handler_index == picked)
1911
1.43k
                                goto search_over;
1912
23.2k
                        }
1913
21.7k
                        break;
1914
268k
                    }
1915
267k
                    ann = ann->next;
1916
267k
                }
1917
2.30M
1918
2.30M
                /* Is this instruction the one we're trying to optimize? */
1919
2.30M
                if (search_ins == ins) {
1920
1.75k
                    /* See if we're in any acceptable handler (rely on the
1921
1.75k
                     * table being pre-sorted by nesting depth here, just like
1922
1.75k
                     * normal exception handler search does). */
1923
6.32k
                    for (i = 0; i < num_found; i++) {
1924
6.32k
                        if (in_handlers[handlers_found[i]]) {
1925
1.75k
                            /* Got it! If we already found its goto target, we
1926
1.75k
                             * can finish the search. */
1927
1.75k
                            picked = handlers_found[i];
1928
1.75k
                            if (goto_bbs[picked])
1929
321
                                goto search_over;
1930
1.43k
                            break;
1931
1.75k
                        }
1932
6.32k
                    }
1933
1.75k
                }
1934
2.30M
1935
2.29M
                search_ins = search_ins->next;
1936
2.29M
            }
1937
300k
            search_bb = search_bb->linear_next;
1938
300k
        }
1939
1.75k
      search_over:
1940
1.75k
1941
1.75k
        /* If we picked a handler and know where it should goto, we can do the
1942
1.75k
         * rewrite into a goto. */
1943
1.75k
        if (picked >= 0 && goto_bbs[picked]) {
1944
1.75k
            ins->info               = MVM_op_get_op(MVM_OP_goto);
1945
1.75k
            ins->operands[0].ins_bb = goto_bbs[picked];
1946
1.75k
            bb->succ[0]             = goto_bbs[picked];
1947
1.75k
        }
1948
1.75k
1949
1.75k
        MVM_free(in_handlers);
1950
1.75k
        MVM_free(goto_bbs);
1951
1.75k
    }
1952
1.76k
1953
1.76k
    MVM_free(handlers_found);
1954
1.76k
}
1955
1956
/* Updates rebless with rebless_sp, which will deopt from the current code. */
1957
3
static void tweak_rebless(MVMThreadContext *tc, MVMSpeshGraph *g, MVMSpeshIns *ins) {
1958
3
    MVMuint32 deopt_target = find_deopt_target(tc, g, ins);
1959
3
    MVMSpeshOperand *new_operands = MVM_spesh_alloc(tc, g, 4 * sizeof(MVMSpeshOperand));
1960
3
    new_operands[0] = ins->operands[0];
1961
3
    new_operands[1] = ins->operands[1];
1962
3
    new_operands[2] = ins->operands[2];
1963
3
    new_operands[3].lit_ui32 = deopt_target;
1964
3
    ins->info = MVM_op_get_op(MVM_OP_sp_rebless);
1965
3
    ins->operands = new_operands;
1966
3
}
1967
1968
/* Replaces atomic ops with a version that needs no checking of the target's
1969
 * container spec and concreteness, when we have the facts to hand. */
1970
static void optimize_container_atomic(MVMThreadContext *tc, MVMSpeshGraph *g,
1971
0
                                      MVMSpeshIns *ins, MVMuint16 target_reg) {
1972
0
    MVMSpeshFacts *facts = MVM_spesh_get_facts(tc, g, ins->operands[target_reg]);
1973
0
    if ((facts->flags & MVM_SPESH_FACT_CONCRETE) && (facts->flags & MVM_SPESH_FACT_KNOWN_TYPE)) {
1974
0
        MVMContainerSpec const *cs = facts->type->st->container_spec;
1975
0
        if (!cs)
1976
0
            return;
1977
0
        switch (ins->info->opcode) {
1978
0
            case MVM_OP_cas_o:
1979
0
                if (!cs->cas)
1980
0
                    return;
1981
0
                ins->info = MVM_op_get_op(MVM_OP_sp_cas_o);
1982
0
                break;
1983
0
            case MVM_OP_atomicstore_o:
1984
0
                if (!cs->atomic_store)
1985
0
                    return;
1986
0
                ins->info = MVM_op_get_op(MVM_OP_sp_atomicstore_o);
1987
0
                break;
1988
0
            case MVM_OP_atomicload_o:
1989
0
                if (!cs->cas)
1990
0
                    return;
1991
0
                ins->info = MVM_op_get_op(MVM_OP_sp_atomicload_o);
1992
0
                break;
1993
0
        }
1994
0
        MVM_spesh_use_facts(tc, g, facts);
1995
0
    }
1996
0
}
1997
1998
933k
static void eliminate_phi_dead_reads(MVMThreadContext *tc, MVMSpeshGraph *g, MVMSpeshIns *ins) {
1999
933k
    MVMuint32 operand = 1;
2000
933k
    MVMuint32 insert_pos = 1;
2001
933k
    MVMuint32 num_operands = ins->info->num_operands;
2002
18.2M
    while (operand < ins->info->num_operands) {
2003
17.3M
        if (get_facts_direct(tc, g, ins->operands[operand])->dead_writer) {
2004
16.5k
            num_operands--;
2005
16.5k
        }
2006
17.3M
        else {
2007
17.3M
            ins->operands[insert_pos] = ins->operands[operand];
2008
17.3M
            insert_pos++;
2009
17.3M
        }
2010
17.3M
        operand++;
2011
17.3M
    }
2012
933k
    if (num_operands != ins->info->num_operands)
2013
13.5k
        ins->info = get_phi(tc, g, num_operands);
2014
933k
}
2015
933k
static void analyze_phi(MVMThreadContext *tc, MVMSpeshGraph *g, MVMSpeshIns *ins) {
2016
933k
    MVMuint32 operand;
2017
933k
    MVMint32 common_flags;
2018
933k
    MVMObject *common_type;
2019
933k
    MVMObject *common_decont_type;
2020
933k
    MVMuint32 needs_merged_with_log_guard = 0;
2021
933k
    MVMSpeshFacts *target_facts = get_facts_direct(tc, g, ins->operands[0]);
2022
933k
2023
933k
    eliminate_phi_dead_reads(tc, g, ins);
2024
933k
2025
933k
    common_flags       = get_facts_direct(tc, g, ins->operands[1])->flags;
2026
933k
    common_type        = get_facts_direct(tc, g, ins->operands[1])->type;
2027
933k
    common_decont_type = get_facts_direct(tc, g, ins->operands[1])->decont_type;
2028
933k
2029
933k
    needs_merged_with_log_guard = common_flags & MVM_SPESH_FACT_FROM_LOG_GUARD;
2030
933k
2031
17.3M
    for(operand = 2; operand < ins->info->num_operands; operand++) {
2032
16.3M
        common_flags = common_flags & get_facts_direct(tc, g, ins->operands[operand])->flags;
2033
16.0M
        common_type = common_type == get_facts_direct(tc, g, ins->operands[operand])->type && common_type ? common_type : NULL;
2034
16.3M
        common_decont_type = common_decont_type == get_facts_direct(tc, g, ins->operands[operand])->decont_type && common_decont_type ? common_decont_type : NULL;
2035
16.3M
2036
16.3M
        /* We have to be a bit more careful if one or more of the facts we're
2037
16.3M
         * merging came from a log guard, as that means we'll have to propagate
2038
16.3M
         * the information what guards have been relied upon back "outwards"
2039
16.3M
         * through the PHI node we've merged stuff with. */
2040
16.3M
        if (get_facts_direct(tc, g, ins->operands[operand])->flags & MVM_SPESH_FACT_FROM_LOG_GUARD)
2041
151k
            needs_merged_with_log_guard = 1;
2042
16.3M
    }
2043
933k
2044
933k
    if (common_flags) {
2045
36.6k
        /*fprintf(stderr, "at a PHI node of %d operands: ", ins->info->num_operands);*/
2046
36.6k
        if (common_flags & MVM_SPESH_FACT_KNOWN_TYPE) {
2047
17.1k
            /*fprintf(stderr, "type ");*/
2048
17.1k
            if (common_type) {
2049
8.93k
                /*fprintf(stderr, "(same type) ");*/
2050
8.93k
                target_facts->flags |= MVM_SPESH_FACT_KNOWN_TYPE;
2051
8.93k
                target_facts->type = common_type;
2052
8.93k
            }
2053
17.1k
            /*else fprintf(stderr, "(diverging type) ");*/
2054
17.1k
        }
2055
36.6k
        /*if (common_flags & MVM_SPESH_FACT_KNOWN_VALUE) fprintf(stderr, "value ");*/
2056
36.6k
        if (common_flags & MVM_SPESH_FACT_DECONTED) {
2057
23.7k
            /*fprintf(stderr, "deconted ");*/
2058
23.7k
            target_facts->flags |= MVM_SPESH_FACT_DECONTED;
2059
23.7k
        }
2060
36.6k
        if (common_flags & MVM_SPESH_FACT_CONCRETE) {
2061
5.62k
            /*fprintf(stderr, "concrete ");*/
2062
5.62k
            target_facts->flags |= MVM_SPESH_FACT_CONCRETE;
2063
5.62k
        }
2064
36.6k
        if (common_flags & MVM_SPESH_FACT_TYPEOBJ) {
2065
3.67k
            /*fprintf(stderr, "type_object ");*/
2066
3.67k
        }
2067
36.6k
        if (common_flags & MVM_SPESH_FACT_KNOWN_DECONT_TYPE) {
2068
0
            /*fprintf(stderr, "decont_type ");*/
2069
0
            if (common_decont_type) {
2070
0
                /*fprintf(stderr, "(same type) ");*/
2071
0
                target_facts->flags |= MVM_SPESH_FACT_KNOWN_DECONT_TYPE;
2072
0
                target_facts->decont_type = common_decont_type;
2073
0
            }
2074
0
            /*else fprintf(stderr, "(diverging type) ");*/
2075
0
        }
2076
36.6k
        if (common_flags & MVM_SPESH_FACT_DECONT_CONCRETE) {
2077
0
            /*fprintf(stderr, "decont_concrete ");*/
2078
0
            target_facts->flags |= MVM_SPESH_FACT_DECONT_CONCRETE;
2079
0
        }
2080
36.6k
        if (common_flags & MVM_SPESH_FACT_DECONT_TYPEOBJ) {
2081
0
            /*fprintf(stderr, "decont_typeobj ");*/
2082
0
            target_facts->flags |= MVM_SPESH_FACT_DECONT_TYPEOBJ;
2083
0
        }
2084
36.6k
        if (common_flags & MVM_SPESH_FACT_RW_CONT) {
2085
0
            /*fprintf(stderr, "rw_cont ");*/
2086
0
            target_facts->flags |= MVM_SPESH_FACT_RW_CONT;
2087
0
        }
2088
36.6k
        /*if (common_flags & MVM_SPESH_FACT_FROM_LOG_GUARD) fprintf(stderr, "from_log_guard ");*/
2089
36.6k
        /*if (common_flags & MVM_SPESH_FACT_HASH_ITER) fprintf(stderr, "hash_iter ");*/
2090
36.6k
        /*if (common_flags & MVM_SPESH_FACT_ARRAY_ITER) fprintf(stderr, "array_iter ");*/
2091
36.6k
        /*if (common_flags & MVM_SPESH_FACT_KNOWN_BOX_SRC) fprintf(stderr, "box_source ");*/
2092
36.6k
        /*fprintf(stderr, "\n");*/
2093
36.6k
2094
36.6k
        if (needs_merged_with_log_guard) {
2095
6.76k
            target_facts->flags |= MVM_SPESH_FACT_MERGED_WITH_LOG_GUARD;
2096
6.76k
        }
2097
896k
    } else {
2098
896k
        /*fprintf(stderr, "a PHI node of %d operands had no intersecting flags\n", ins->info->num_operands);*/
2099
896k
    }
2100
933k
}
2101
static void optimize_bb_switch(MVMThreadContext *tc, MVMSpeshGraph *g, MVMSpeshBB *bb,
2102
445k
                        MVMSpeshPlanned *p) {
2103
445k
    MVMSpeshCallInfo arg_info;
2104
445k
    /* Look for instructions that are interesting to optimize. */
2105
445k
    MVMSpeshIns *ins = bb->first_ins;
2106
2.56M
    while (ins) {
2107
2.11M
        switch (ins->info->opcode) {
2108
933k
        case MVM_SSA_PHI:
2109
933k
            analyze_phi(tc, g, ins);
2110
933k
            break;
2111
123k
        case MVM_OP_set:
2112
123k
            copy_facts(tc, g, ins->operands[0], ins->operands[1]);
2113
123k
            break;
2114
4.68k
        case MVM_OP_isnull:
2115
4.68k
            optimize_isnull(tc, g, bb, ins);
2116
4.68k
            break;
2117
749
        case MVM_OP_istrue:
2118
749
        case MVM_OP_isfalse:
2119
749
            optimize_istrue_isfalse(tc, g, bb, ins);
2120
749
            break;
2121
60.8k
        case MVM_OP_if_i:
2122
60.8k
        case MVM_OP_unless_i:
2123
60.8k
        case MVM_OP_if_n:
2124
60.8k
        case MVM_OP_unless_n:
2125
60.8k
            optimize_iffy(tc, g, ins, bb);
2126
60.8k
            break;
2127
8.17k
        case MVM_OP_if_o:
2128
8.17k
        case MVM_OP_unless_o:
2129
8.17k
            optimize_object_conditional(tc, g, ins, bb);
2130
8.17k
            break;
2131
2.35k
        case MVM_OP_not_i:
2132
2.35k
            optimize_not_i(tc, g, ins, bb);
2133
2.35k
            break;
2134
33.3k
        case MVM_OP_prepargs:
2135
33.3k
            arg_info.cs = g->sf->body.cu->body.callsites[ins->operands[0].callsite_idx];
2136
33.3k
            arg_info.prepargs_ins = ins;
2137
33.3k
            arg_info.prepargs_bb  = bb;
2138
33.3k
            break;
2139
63.7k
        case MVM_OP_arg_i:
2140
63.7k
        case MVM_OP_arg_n:
2141
63.7k
        case MVM_OP_arg_s:
2142
63.7k
        case MVM_OP_arg_o: {
2143
63.7k
            MVMint16 idx = ins->operands[0].lit_i16;
2144
63.7k
            if (idx < MAX_ARGS_FOR_OPT) {
2145
63.7k
                arg_info.arg_is_const[idx] = 0;
2146
63.7k
                arg_info.arg_facts[idx]    = MVM_spesh_get_and_use_facts(tc, g, ins->operands[1]);
2147
63.7k
                arg_info.arg_ins[idx]      = ins;
2148
63.7k
            }
2149
63.7k
            break;
2150
63.7k
        }
2151
3.48k
        case MVM_OP_argconst_i:
2152
3.48k
        case MVM_OP_argconst_n:
2153
3.48k
        case MVM_OP_argconst_s: {
2154
3.48k
            MVMint16 idx = ins->operands[0].lit_i16;
2155
3.48k
            if (idx < MAX_ARGS_FOR_OPT) {
2156
3.47k
                arg_info.arg_is_const[idx] = 1;
2157
3.47k
                arg_info.arg_ins[idx]      = ins;
2158
3.47k
            }
2159
3.48k
            break;
2160
3.48k
        }
2161
4.59k
        case MVM_OP_coerce_in:
2162
4.59k
            optimize_coerce(tc, g, bb, ins);
2163
4.59k
            break;
2164
18.7k
        case MVM_OP_smrt_numify:
2165
18.7k
        case MVM_OP_smrt_strify:
2166
18.7k
            optimize_smart_coerce(tc, g, bb, ins);
2167
18.7k
            break;
2168
1.17k
        case MVM_OP_invoke_v:
2169
1.17k
            optimize_call(tc, g, bb, ins, p, 0, &arg_info);
2170
1.17k
            break;
2171
32.2k
        case MVM_OP_invoke_i:
2172
32.2k
        case MVM_OP_invoke_n:
2173
32.2k
        case MVM_OP_invoke_s:
2174
32.2k
        case MVM_OP_invoke_o:
2175
32.2k
            optimize_call(tc, g, bb, ins, p, 1, &arg_info);
2176
32.2k
            break;
2177
1.09k
        case MVM_OP_islist:
2178
1.09k
        case MVM_OP_ishash:
2179
1.09k
        case MVM_OP_isint:
2180
1.09k
        case MVM_OP_isnum:
2181
1.09k
        case MVM_OP_isstr:
2182
1.09k
            optimize_is_reprid(tc, g, ins);
2183
1.09k
            break;
2184
2.82k
        case MVM_OP_findmeth_s:
2185
2.82k
            optimize_findmeth_s_perhaps_constant(tc, g, ins);
2186
2.82k
            if (ins->info->opcode == MVM_OP_findmeth_s)
2187
497
                break;
2188
29.8k
        case MVM_OP_findmeth:
2189
29.8k
            optimize_method_lookup(tc, g, ins);
2190
29.8k
            break;
2191
165
        case MVM_OP_tryfindmeth_s:
2192
165
            optimize_findmeth_s_perhaps_constant(tc, g, ins);
2193
165
            if (ins->info->opcode == MVM_OP_tryfindmeth_s)
2194
165
                break;
2195
0
        case MVM_OP_tryfindmeth:
2196
0
            optimize_method_lookup(tc, g, ins);
2197
0
            break;
2198
292
        case MVM_OP_can:
2199
292
        case MVM_OP_can_s:
2200
292
            optimize_can_op(tc, g, bb, ins);
2201
292
            break;
2202
943
        case MVM_OP_gethow:
2203
943
            optimize_gethow(tc, g, ins);
2204
943
            break;
2205
4.78k
        case MVM_OP_isconcrete:
2206
4.78k
            optimize_isconcrete(tc, g, ins);
2207
4.78k
            break;
2208
1.75k
        case MVM_OP_istype:
2209
1.75k
            optimize_istype(tc, g, ins);
2210
1.75k
            break;
2211
13
        case MVM_OP_objprimspec:
2212
13
            optimize_objprimspec(tc, g, ins);
2213
13
            break;
2214
0
        case MVM_OP_unipropcode:
2215
0
        case MVM_OP_unipvalcode:
2216
0
            optimize_uniprop_ops(tc, g, bb, ins);
2217
0
            break;
2218
25.1k
        case MVM_OP_unshift_i:
2219
25.1k
        case MVM_OP_unshift_n:
2220
25.1k
        case MVM_OP_unshift_s:
2221
25.1k
        case MVM_OP_unshift_o:
2222
25.1k
        case MVM_OP_bindkey_i:
2223
25.1k
        case MVM_OP_bindkey_n:
2224
25.1k
        case MVM_OP_bindkey_s:
2225
25.1k
        case MVM_OP_bindkey_o:
2226
25.1k
        case MVM_OP_bindpos_i:
2227
25.1k
        case MVM_OP_bindpos_n:
2228
25.1k
        case MVM_OP_bindpos_s:
2229
25.1k
        case MVM_OP_bindpos_o:
2230
25.1k
        case MVM_OP_pop_i:
2231
25.1k
        case MVM_OP_pop_n:
2232
25.1k
        case MVM_OP_pop_s:
2233
25.1k
        case MVM_OP_pop_o:
2234
25.1k
        case MVM_OP_deletekey:
2235
25.1k
        case MVM_OP_setelemspos:
2236
25.1k
        case MVM_OP_splice:
2237
25.1k
        case MVM_OP_bindattr_i:
2238
25.1k
        case MVM_OP_bindattr_n:
2239
25.1k
        case MVM_OP_bindattr_s:
2240
25.1k
        case MVM_OP_bindattr_o:
2241
25.1k
        case MVM_OP_bindattrs_i:
2242
25.1k
        case MVM_OP_bindattrs_n:
2243
25.1k
        case MVM_OP_bindattrs_s:
2244
25.1k
        case MVM_OP_bindattrs_o:
2245
25.1k
        case MVM_OP_assign_i:
2246
25.1k
        case MVM_OP_assign_n:
2247
25.1k
            optimize_repr_op(tc, g, bb, ins, 0);
2248
25.1k
            break;
2249
102k
        case MVM_OP_atpos_i:
2250
102k
        case MVM_OP_atpos_n:
2251
102k
        case MVM_OP_atpos_s:
2252
102k
        case MVM_OP_atpos_o:
2253
102k
        case MVM_OP_atkey_i:
2254
102k
        case MVM_OP_atkey_n:
2255
102k
        case MVM_OP_atkey_s:
2256
102k
        case MVM_OP_atkey_o:
2257
102k
        case MVM_OP_elems:
2258
102k
        case MVM_OP_shift_i:
2259
102k
        case MVM_OP_shift_n:
2260
102k
        case MVM_OP_shift_s:
2261
102k
        case MVM_OP_shift_o:
2262
102k
        case MVM_OP_push_i:
2263
102k
        case MVM_OP_push_n:
2264
102k
        case MVM_OP_push_s:
2265
102k
        case MVM_OP_push_o:
2266
102k
        case MVM_OP_existskey:
2267
102k
        case MVM_OP_existspos:
2268
102k
        case MVM_OP_getattr_i:
2269
102k
        case MVM_OP_getattr_n:
2270
102k
        case MVM_OP_getattr_s:
2271
102k
        case MVM_OP_getattr_o:
2272
102k
        case MVM_OP_getattrs_i:
2273
102k
        case MVM_OP_getattrs_n:
2274
102k
        case MVM_OP_getattrs_s:
2275
102k
        case MVM_OP_getattrs_o:
2276
102k
        case MVM_OP_decont_i:
2277
102k
        case MVM_OP_decont_n:
2278
102k
        case MVM_OP_decont_s:
2279
102k
        case MVM_OP_decont_u:
2280
102k
        case MVM_OP_create:
2281
102k
            optimize_repr_op(tc, g, bb, ins, 1);
2282
102k
            break;
2283
6.79k
        case MVM_OP_box_i:
2284
6.79k
        case MVM_OP_box_n:
2285
6.79k
        case MVM_OP_box_s:
2286
6.79k
            optimize_repr_op(tc, g, bb, ins, 2);
2287
6.79k
            break;
2288
0
        case MVM_OP_unbox_i:
2289
0
        case MVM_OP_unbox_n:
2290
0
        case MVM_OP_unbox_s:
2291
0
            optimize_unbox(tc, g, bb, ins);
2292
0
            break;
2293
3.97k
        case MVM_OP_ne_s:
2294
3.97k
        case MVM_OP_eq_s:
2295
3.97k
            optimize_string_equality(tc, g, bb, ins);
2296
3.97k
            break;
2297
0
        case MVM_OP_newexception:
2298
0
        case MVM_OP_bindexmessage:
2299
0
        case MVM_OP_bindexpayload:
2300
0
        case MVM_OP_getexmessage:
2301
0
        case MVM_OP_getexpayload:
2302
0
            optimize_exception_ops(tc, g, bb, ins);
2303
0
            break;
2304
0
        case MVM_OP_hllize:
2305
0
            optimize_hllize(tc, g, ins);
2306
0
            break;
2307
110k
        case MVM_OP_decont:
2308
110k
            optimize_decont(tc, g, bb, ins);
2309
110k
            break;
2310
0
        case MVM_OP_assertparamcheck:
2311
0
            optimize_assertparamcheck(tc, g, bb, ins);
2312
0
            break;
2313
11.3k
        case MVM_OP_getlex:
2314
11.3k
            optimize_getlex(tc, g, ins);
2315
11.3k
            break;
2316
155
        case MVM_OP_getlex_no:
2317
155
            /* Use non-logging variant. */
2318
155
            ins->info = MVM_op_get_op(MVM_OP_sp_getlex_no);
2319
155
            break;
2320
1.15k
        case MVM_OP_getlexstatic_o:
2321
1.15k
            optimize_getlex_known(tc, g, bb, ins);
2322
1.15k
            break;
2323
15.2k
        case MVM_OP_getlexperinvtype_o:
2324
15.2k
            optimize_getlex_per_invocant(tc, g, bb, ins, p);
2325
15.2k
            break;
2326
33
        case MVM_OP_iscont:
2327
33
        case MVM_OP_isrwcont:
2328
33
        case MVM_OP_iscont_i:
2329
33
        case MVM_OP_iscont_n:
2330
33
        case MVM_OP_iscont_s:
2331
33
            optimize_container_check(tc, g, bb, ins);
2332
33
            break;
2333
5.01k
        case MVM_OP_osrpoint:
2334
5.01k
            /* We don't need to poll for OSR in hot loops. (This also moves
2335
5.01k
             * the OSR annotation onto the next instruction.) */
2336
5.01k
            MVM_spesh_manipulate_delete_ins(tc, g, bb, ins);
2337
5.01k
            break;
2338
3
        case MVM_OP_rebless:
2339
3
            tweak_rebless(tc, g, ins);
2340
3
            break;
2341
0
        case MVM_OP_cas_o:
2342
0
        case MVM_OP_atomicload_o:
2343
0
            optimize_container_atomic(tc, g, ins, 1);
2344
0
            break;
2345
0
        case MVM_OP_atomicstore_o:
2346
0
            optimize_container_atomic(tc, g, ins, 0);
2347
0
            break;
2348
0
        case MVM_OP_prof_enter:
2349
0
            /* Profiling entered from spesh should indicate so. */
2350
0
            ins->info = MVM_op_get_op(MVM_OP_prof_enterspesh);
2351
0
            break;
2352
0
        case MVM_OP_coverage_log:
2353
0
            /* A coverage_log op that has already fired can be thrown out. */
2354
0
            optimize_coverage_log(tc, g, bb, ins);
2355
0
            break;
2356
502k
        default:
2357
502k
            if (ins->info->opcode == (MVMuint16)-1)
2358
0
                optimize_extop(tc, g, bb, ins);
2359
2.11M
        }
2360
2.11M
2361
2.11M
        ins = ins->next;
2362
2.11M
    }
2363
445k
}
2364
/* Visits the blocks in dominator tree order, recursively. */
2365
static void optimize_bb(MVMThreadContext *tc, MVMSpeshGraph *g, MVMSpeshBB *bb,
2366
211k
                        MVMSpeshPlanned *p) {
2367
211k
    MVMint64 i = 0;
2368
211k
    /* Because optimize_bb() can be deeply recursive, separate as much code
2369
211k
     * as possible into a separate function optimize_bb_switch(), so we don't
2370
211k
     * trash the stack. (needed on musl) */
2371
211k
    optimize_bb_switch(tc, g, bb, p);
2372
211k
2373
211k
    /* Optimize the case where we only have one child. This avoids having
2374
211k
     * to do a recursive call to optimize_bb() */
2375
445k
    while (bb->num_children == 1) {
2376
234k
        bb = bb->children[0];
2377
234k
        /* Keep following the nodes and running optimize_bb_switch() on them
2378
234k
         * until we hit one with more than 1 child. */
2379
234k
        optimize_bb_switch(tc, g, bb, p);
2380
234k
    }
2381
211k
    /* Visit children. */
2382
411k
    for (; i < bb->num_children; i++) {
2383
200k
        optimize_bb(tc, g, bb->children[i], p);
2384
200k
    }
2385
211k
}
2386
2387
/* Eliminates any unused instructions. */
2388
11.1k
static void eliminate_dead_ins(MVMThreadContext *tc, MVMSpeshGraph *g) {
2389
11.1k
    /* Keep eliminating to a fixed point. */
2390
11.1k
    MVMint8 death = 1;
2391
43.0k
    while (death) {
2392
31.9k
        MVMSpeshBB *bb = g->entry;
2393
31.9k
        death = 0;
2394
1.80M
        while (bb) {
2395
1.76M
            if (!bb->inlined) {
2396
1.67M
                MVMSpeshIns *ins = bb->last_ins;
2397
9.22M
                while (ins) {
2398
7.54M
                    MVMSpeshIns *prev = ins->prev;
2399
7.54M
                    if (ins->info->opcode == MVM_SSA_PHI) {
2400
3.25M
                        MVMSpeshFacts *facts = get_facts_direct(tc, g, ins->operands[0]);
2401
3.25M
                        if (facts->usages == 0) {
2402
254k
                            /* Remove this phi. */
2403
254k
                            MVM_spesh_manipulate_delete_ins(tc, g, bb, ins);
2404
254k
                            death = 1;
2405
254k
                        }
2406
3.25M
                    }
2407
4.29M
                    else if (ins->info->pure) {
2408
2.55M
                        /* Sanity check to make sure it's a write reg as first operand. */
2409
2.55M
                        if ((ins->info->operands[0] & MVM_operand_rw_mask) == MVM_operand_write_reg) {
2410
2.55M
                            MVMSpeshFacts *facts = get_facts_direct(tc, g, ins->operands[0]);
2411
2.55M
                            if (facts->usages == 0) {
2412
97.7k
                                /* Remove this instruction. */
2413
97.7k
                                MVM_spesh_manipulate_delete_ins(tc, g, bb, ins);
2414
97.7k
                                death = 1;
2415
97.7k
                            }
2416
2.55M
                        }
2417
2.55M
                    }
2418
7.54M
                    ins = prev;
2419
7.54M
                }
2420
1.67M
            }
2421
1.76M
            bb = bb->linear_next;
2422
1.76M
        }
2423
31.9k
    }
2424
11.1k
}
2425
2426
/* Optimization turns many things into simple set instructions, which we can
2427
 * often further eliminate; others may become unrequired due to eliminated
2428
 * branches, and some may be from sub-optimizal original code. */
2429
static MVMint32 within_inline(MVMThreadContext *tc, MVMSpeshGraph *g, MVMSpeshBB *bb,
2430
29.6k
                              MVMSpeshOperand target) {
2431
29.6k
    if (bb->inlined) {
2432
0
        MVMSpeshBB *check_bb = bb;
2433
0
        while (check_bb) {
2434
0
            MVMSpeshIns *last_ins = check_bb->last_ins;
2435
0
            MVMSpeshAnn *ann = last_ins->annotations;
2436
0
            MVMint32 max_inline = -1;
2437
0
            while (ann) {
2438
0
                if (ann->type == MVM_SPESH_ANN_INLINE_END)
2439
0
                    if (ann->data.inline_idx > max_inline)
2440
0
                        max_inline = ann->data.inline_idx;
2441
0
                ann = ann->next;
2442
0
            }
2443
0
            if (max_inline >= 0) {
2444
0
                /* We've found the inline that we're inside of. Check if the
2445
0
                 * register is within its range of registers. */
2446
0
                MVMuint16 locals_start = g->inlines[max_inline].locals_start;
2447
0
                MVMuint16 num_locals = g->inlines[max_inline].num_locals;
2448
0
                return target.reg.orig >= locals_start &&
2449
0
                    target.reg.orig < locals_start + num_locals;
2450
0
            }
2451
0
            check_bb = check_bb->linear_next;
2452
0
        }
2453
0
    }
2454
29.6k
    return 1; /* We're not in an inline at all, so automatically yes. */
2455
29.6k
}
2456
static void try_eliminate_set(MVMThreadContext *tc, MVMSpeshGraph *g, MVMSpeshBB *bb,
2457
231k
                              MVMSpeshIns *ins) {
2458
231k
    /* Sometimes, a set takes place between two versions of the same register.
2459
231k
     * This can go.
2460
231k
     * XXX Should rewrite the graph properly. */
2461
231k
    if (ins->operands[0].reg.orig == ins->operands[1].reg.orig) {
2462
13.0k
        MVM_spesh_manipulate_delete_ins(tc, g, bb, ins);
2463
13.0k
    }
2464
231k
2465
231k
    /* Other optimizations depend on having a previous op. */
2466
218k
    else if (!ins->prev) {
2467
58.9k
        return;
2468
58.9k
    }
2469
218k
2470
218k
    /* If we have:
2471
218k
     *      set rT(j), rO(i)
2472
218k
     *      set rO(i + 1), rT(j)
2473
218k
     *  Then the second instruction can go away.
2474
218k
     *  XXX Should rewrite the graph properly. */
2475
159k
    else if (ins->prev->info->opcode == MVM_OP_set) {
2476
20.0k
        if (ins->operands[0].reg.orig == ins->prev->operands[1].reg.orig &&
2477
1.66k
                ins->operands[0].reg.i == ins->prev->operands[1].reg.i + 1 &&
2478
1.49k
                ins->operands[1].reg.orig == ins->prev->operands[0].reg.orig &&
2479
370
                ins->operands[1].reg.i == ins->prev->operands[0].reg.i)
2480
370
            MVM_spesh_manipulate_delete_ins(tc, g, bb, ins);
2481
20.0k
    }
2482
159k
2483
159k
    /* If a write operation is immediately followed by a set, we can look at
2484
159k
     * the usages of the intermediate register and make sure it's only ever
2485
159k
     * read by the set, and not, for example, required by a deopt barrier to
2486
159k
     * have a copy of the value. In that case, we don't need the temporary
2487
159k
     * and can assign the result of the instruction directly into the
2488
159k
     * target register. We must also check, if we're in an inline, that the
2489
159k
     * final target register is within the inline, since deopt depends on the
2490
159k
     * target register of an invoke being within a frame. */
2491
139k
    else if ((ins->prev->info->operands[0] & MVM_operand_rw_mask) == MVM_operand_write_reg &&
2492
99.3k
            ins->prev->info->opcode != MVM_SSA_PHI &&
2493
99.3k
            ins->prev->operands[0].reg.orig == ins->operands[1].reg.orig &&
2494
79.3k
            ins->prev->operands[0].reg.i == ins->operands[1].reg.i) {
2495
79.3k
        MVMSpeshFacts *elim_facts = get_facts_direct(tc, g, ins->operands[1]);
2496
79.3k
        if (elim_facts->usages == 1 && within_inline(tc, g, bb, ins->operands[0])) {
2497
29.6k
            ins->prev->operands[0].reg = ins->operands[0].reg;
2498
29.6k
            get_facts_direct(tc, g, ins->prev->operands[0])->writer = ins->prev;
2499
29.6k
            MVM_spesh_manipulate_delete_ins(tc, g, bb, ins);
2500
29.6k
        }
2501
79.3k
    }
2502
231k
}
2503
2504
/* Drives the second, post-inline, optimization pass. */
2505
322k
static void second_pass(MVMThreadContext *tc, MVMSpeshGraph *g, MVMSpeshBB *bb) {
2506
322k
    MVMint32 i;
2507
322k
2508
322k
    MVMSpeshIns *ins = bb->first_ins;
2509
2.21M
    while (ins) {
2510
1.89M
        MVMSpeshIns *next = ins->next;
2511
1.89M
        switch (ins->info->opcode) {
2512
231k
            case MVM_OP_set:
2513
231k
                try_eliminate_set(tc, g, bb, ins);
2514
231k
                break;
2515
51.7k
            case MVM_OP_sp_getspeshslot:
2516
51.7k
                /* Sometimes we emit two getspeshslots in a row that write into the
2517
51.7k
                 * exact same register. That's clearly wasteful and we can save a
2518
51.7k
                 * tiny shred of code size here. */
2519
51.7k
                if (ins->prev && ins->prev->info->opcode == ins->info->opcode &&
2520
9.72k
                        ins->operands[0].reg.orig == ins->prev->operands[0].reg.orig)
2521
9.72k
                    MVM_spesh_manipulate_delete_ins(tc, g, bb, ins->prev);
2522
51.7k
                break;
2523
0
            case MVM_OP_prof_allocated:
2524
0
                optimize_prof_allocated(tc, g, bb, ins);
2525
0
                break;
2526
1.76k
            case MVM_OP_throwcatdyn:
2527
1.76k
            case MVM_OP_throwcatlex:
2528
1.76k
            case MVM_OP_throwcatlexotic:
2529
1.76k
                optimize_throwcat(tc, g, bb, ins);
2530
1.76k
                break;
2531
1.89M
        }
2532
1.89M
        ins = next;
2533
1.89M
    }
2534
322k
2535
322k
    /* Visit children. */
2536
633k
    for (i = 0; i < bb->num_children; i++)
2537
311k
        second_pass(tc, g, bb->children[i]);
2538
322k
}
2539
2540
/* Goes through the various log-based guard instructions and removes any that
2541
 * are not being used. */
2542
11.1k
static void eliminate_unused_log_guards(MVMThreadContext *tc, MVMSpeshGraph *g) {
2543
11.1k
    MVMint32 i;
2544
41.3k
    for (i = 0; i < g->num_log_guards; i++)
2545
30.2k
        if (!g->log_guards[i].used)
2546
12.0k
            MVM_spesh_manipulate_delete_ins(tc, g, g->log_guards[i].bb,
2547
12.0k
                g->log_guards[i].ins);
2548
11.1k
}
2549
2550
/* Sometimes - almost always due to other optmimizations having done their
2551
 * work - we end up with an unconditional goto at the end of a basic block
2552
 * that points right to the very next basic block. Delete these. */
2553
11.1k
static void eliminate_pointless_gotos(MVMThreadContext *tc, MVMSpeshGraph *g) {
2554
11.1k
    MVMSpeshBB *cur_bb = g->entry;
2555
467k
    while (cur_bb) {
2556
456k
        if (!cur_bb->jumplist) {
2557
450k
            MVMSpeshIns *last_ins = cur_bb->last_ins;
2558
450k
            if (
2559
450k
                last_ins
2560
450k
                && last_ins->info->opcode == MVM_OP_goto
2561
67.9k
                && last_ins->operands[0].ins_bb == cur_bb->linear_next
2562
18.6k
            ) {
2563
18.6k
                MVM_spesh_manipulate_delete_ins(tc, g, cur_bb, last_ins);
2564
18.6k
            }
2565
450k
        }
2566
456k
        cur_bb = cur_bb->linear_next;
2567
456k
    }
2568
11.1k
}
2569
2570
11.1k
static void merge_bbs(MVMThreadContext *tc, MVMSpeshGraph *g) {
2571
11.1k
    MVMSpeshBB *bb = g->entry;
2572
11.1k
    MVMint32 orig_bbs = g->num_bbs;
2573
11.1k
    if (!bb || !bb->linear_next) return; /* looks like there's only a single bb anyway */
2574
11.1k
    bb = bb->linear_next;
2575
11.1k
2576
445k
    while (bb->linear_next) {
2577
434k
        if (bb->num_succ == 1 && bb->succ[0] == bb->linear_next && bb->linear_next->num_pred == 1 && !bb->inlined && !bb->linear_next->inlined) {
2578
134k
            if (bb->linear_next->first_ins) {
2579
133k
                bb->linear_next->first_ins->prev = bb->last_ins;
2580
133k
                if (bb->last_ins) {
2581
133k
                    bb->last_ins->next = bb->linear_next->first_ins;
2582
133k
                    bb->last_ins->next->prev = bb->last_ins;
2583
133k
                    bb->last_ins = bb->linear_next->last_ins;
2584
133k
                }
2585
8
                else {
2586
8
                    bb->first_ins = bb->linear_next->first_ins;
2587
8
                    bb->last_ins = bb->linear_next->last_ins;
2588
8
                }
2589
133k
                bb->linear_next->first_ins = bb->linear_next->last_ins = NULL;
2590
133k
            }
2591
134k
            if (bb->linear_next->num_succ) {
2592
127k
                MVMSpeshBB **succ = MVM_spesh_alloc(tc, g, (bb->num_succ - 1 + bb->linear_next->num_succ) * sizeof(MVMSpeshBB *));
2593
127k
                int i, j = 0;
2594
255k
                for (i = 0; i < bb->num_succ; i++)
2595
127k
                    if (bb->succ[i] != bb->linear_next)
2596
0
                        succ[j++] = bb->succ[i];
2597
296k
                for (i = 0; i < bb->linear_next->num_succ; i++)
2598
168k
                    succ[j++] = bb->linear_next->succ[i];
2599
127k
                bb->succ = succ;
2600
127k
            }
2601
134k
            bb->num_succ--;
2602
134k
            bb->num_succ += bb->linear_next->num_succ;
2603
134k
2604
134k
            bb->linear_next = bb->linear_next->linear_next;
2605
134k
            g->num_bbs--;
2606
134k
        }
2607
300k
        else {
2608
300k
            bb = bb->linear_next;
2609
300k
        }
2610
434k
    }
2611
11.1k
2612
11.1k
    /* Re-number BBs so we get sequential ordering again. */
2613
11.1k
    if (g->num_bbs != orig_bbs) {
2614
10.9k
        MVMint32    new_idx  = 0;
2615
10.9k
        MVMSpeshBB *cur_bb   = g->entry;
2616
332k
        while (cur_bb) {
2617
321k
            cur_bb->idx = new_idx;
2618
321k
            new_idx++;
2619
321k
            cur_bb = cur_bb->linear_next;
2620
321k
        }
2621
10.9k
    }
2622
11.1k
}
2623
2624
/* Drives the overall optimization work taking place on a spesh graph. */
2625
11.1k
void MVM_spesh_optimize(MVMThreadContext *tc, MVMSpeshGraph *g, MVMSpeshPlanned *p) {
2626
11.1k
    /* Before starting, we eliminate dead basic blocks that were tossed by
2627
11.1k
     * arg spesh, to simplify the graph. */
2628
11.1k
    MVM_spesh_eliminate_dead_bbs(tc, g, 1);
2629
11.1k
2630
11.1k
    /* Perform initial optimization pass, which performs a range of opts
2631
11.1k
     * including, most notably, inlining. */
2632
11.1k
    optimize_bb(tc, g, g->entry, p);
2633
11.1k
2634
11.1k
    /* Clear up the graph after this initial pass. */
2635
11.1k
    MVM_spesh_eliminate_dead_bbs(tc, g, 1);
2636
11.1k
    eliminate_unused_log_guards(tc, g);
2637
11.1k
    eliminate_pointless_gotos(tc, g);
2638
11.1k
    eliminate_dead_ins(tc, g);
2639
11.1k
2640
11.1k
    merge_bbs(tc, g);
2641
11.1k
2642
11.1k
    /* Make a second pass through the graph doing things that are better
2643
11.1k
     * done after inlinings have taken place. The dominance tree is first
2644
11.1k
     * recomputed, to account for any inlinings. */
2645
11.1k
    MVM_spesh_graph_recompute_dominance(tc, g);
2646
11.1k
    second_pass(tc, g, g->entry);
2647
11.1k
}