Coverage Report

Created: 2017-04-15 07:07

/home/travis/build/MoarVM/MoarVM/src/6model/reprs/MVMMultiCache.c
Line
Count
Source (jump to first uncovered line)
1
#include "moar.h"
2
3
/* This representation's function pointer table. */
4
static const MVMREPROps MVMMultiCache_this_repr;
5
6
/* Creates a new type object of this representation, and associates it with
7
 * the given HOW. */
8
130
static MVMObject * type_object_for(MVMThreadContext *tc, MVMObject *HOW) {
9
130
    MVMSTable *st = MVM_gc_allocate_stable(tc, &MVMMultiCache_this_repr, HOW);
10
130
11
130
    MVMROOT(tc, st, {
12
130
        MVMObject *obj = MVM_gc_allocate_type_object(tc, st);
13
130
        MVM_ASSIGN_REF(tc, &(st->header), st->WHAT, obj);
14
130
        st->size = sizeof(MVMMultiCache);
15
130
    });
16
130
17
130
    return st->WHAT;
18
130
}
19
20
/* Copies the body of one object to another. */
21
0
static void copy_to(MVMThreadContext *tc, MVMSTable *st, void *src, MVMObject *dest_root, void *dest) {
22
0
    MVM_exception_throw_adhoc(tc, "Cannot copy object with representation MultiCache");
23
0
}
24
25
/* Called by the VM to mark any GCable items. */
26
128
static void gc_mark(MVMThreadContext *tc, MVMSTable *st, void *data, MVMGCWorklist *worklist) {
27
128
    MVMMultiCacheBody *mc = (MVMMultiCacheBody *)data;
28
128
    size_t i;
29
1.21k
    for (i = 0; i < mc->num_results; i++)
30
1.08k
        MVM_gc_worklist_add(tc, worklist, &(mc->results[i]));
31
128
}
32
33
/* Called by the VM in order to free memory associated with this object. */
34
0
static void gc_free(MVMThreadContext *tc, MVMObject *obj) {
35
0
    MVMMultiCache *mc = (MVMMultiCache *)obj;
36
0
    if (mc->body.node_hash_head)
37
0
        MVM_fixed_size_free(tc, tc->instance->fsa, mc->body.cache_memory_size,
38
0
            mc->body.node_hash_head);
39
0
    if (mc->body.results)
40
0
        MVM_fixed_size_free(tc, tc->instance->fsa,
41
0
            mc->body.num_results * sizeof(MVMObject *),
42
0
            mc->body.results);
43
0
}
44
45
static const MVMStorageSpec storage_spec = {
46
    MVM_STORAGE_SPEC_REFERENCE, /* inlineable */
47
    0,                          /* bits */
48
    0,                          /* align */
49
    MVM_STORAGE_SPEC_BP_NONE,   /* boxed_primitive */
50
    0,                          /* can_box */
51
    0,                          /* is_unsigned */
52
};
53
54
/* Gets the storage specification for this representation. */
55
0
static const MVMStorageSpec * get_storage_spec(MVMThreadContext *tc, MVMSTable *st) {
56
0
    return &storage_spec;
57
0
}
58
59
/* Compose the representation. */
60
0
static void compose(MVMThreadContext *tc, MVMSTable *st, MVMObject *info) {
61
0
    /* Nothing to do for this REPR. */
62
0
}
63
64
/* Calculates the non-GC-managed memory we hold on to. */
65
35
static MVMuint64 unmanaged_size(MVMThreadContext *tc, MVMSTable *st, void *data) {
66
35
    MVMMultiCacheBody *body = (MVMMultiCacheBody *)data;
67
35
    return body->num_results * sizeof(MVMObject *) + body->cache_memory_size;
68
35
}
69
70
/* Initializes the representation. */
71
130
const MVMREPROps * MVMMultiCache_initialize(MVMThreadContext *tc) {
72
130
    return &MVMMultiCache_this_repr;
73
130
}
74
75
static const MVMREPROps MVMMultiCache_this_repr = {
76
    type_object_for,
77
    MVM_gc_allocate_object,
78
    NULL, /* initialize */
79
    copy_to,
80
    MVM_REPR_DEFAULT_ATTR_FUNCS,
81
    MVM_REPR_DEFAULT_BOX_FUNCS,
82
    MVM_REPR_DEFAULT_POS_FUNCS,
83
    MVM_REPR_DEFAULT_ASS_FUNCS,
84
    MVM_REPR_DEFAULT_ELEMS,
85
    get_storage_spec,
86
    NULL, /* change_type */
87
    NULL, /* serialize */
88
    NULL, /* deserialize */
89
    NULL, /* serialize_repr_data */
90
    NULL, /* deserialize_repr_data */
91
    NULL, /* deserialize_stable_size */
92
    gc_mark,
93
    gc_free,
94
    NULL, /* gc_cleanup */
95
    NULL, /* gc_mark_repr_data */
96
    NULL, /* gc_free_repr_data */
97
    compose,
98
    NULL, /* spesh */
99
    "MVMMultiCache", /* name */
100
    MVM_REPR_ID_MVMMultiCache,
101
    unmanaged_size, /* unmanaged_size */
102
    NULL, /* describe_refs */
103
};
104
105
/* Filters for various parts of action.arg_match. */
106
743k
#define MVM_MULTICACHE_ARG_IDX_FILTER  (2 * MVM_INTERN_ARITY_LIMIT - 1)
107
333k
#define MVM_MULTICACHE_ARG_CONC_FILTER 0x10
108
324k
#define MVM_MULTICACHE_ARG_RW_FILTER   0x20
109
724k
#define MVM_MULTICACHE_TYPE_ID_FILTER  (0xFFFFFFFFFFFFFFFFULL ^ (MVM_TYPE_CACHE_ID_INCR - 1))
110
111
/* Debug support dumps the tree after each addition. */
112
#define MVM_MULTICACHE_DEBUG 0
113
#if MVM_MULTICACHE_DEBUG
114
static void dump_cache(MVMThreadContext *tc, MVMMultiCacheBody *cache) {
115
    MVMint32 num_nodes = cache->cache_memory_size / sizeof(MVMMultiCacheNode);
116
    MVMint32 i;
117
    printf("Multi cache at %p (%d nodes, %d results)\n",
118
        cache, num_nodes, cache->num_results);
119
    for (i = 0; i < num_nodes; i++)
120
        printf(" - %p -> (Y: %d, N: %d)\n",
121
            cache->node_hash_head[i].action.cs,
122
            cache->node_hash_head[i].match,
123
            cache->node_hash_head[i].no_match);
124
    printf("\n");
125
}
126
#endif
127
128
/* Big cache profiling. */
129
#define MVM_MULTICACHE_BIG_PROFILE 0
130
#if MVM_MULTICACHE_BIG_PROFILE
131
static MVMint32 is_power_of_2(MVMint32 value) {
132
    return ((value != 0) && !(value & (value - 1)));
133
}
134
#endif
135
136
/* Takes a pointer to a callsite and turns it into an index into the multi cache
137
 * keyed by callsite. We don't do anything too clever here: just shift away the
138
 * bits of the pointer we know will be zero, and the take the least significant
139
 * few bits of it. Hopefully the distribution of memory addresses over time will
140
 * be sufficient. */
141
142k
MVM_STATIC_INLINE size_t hash_callsite(MVMThreadContext *tc, MVMCallsite *cs) {
142
142k
    return ((size_t)cs >> 3) & MVM_MULTICACHE_HASH_FILTER;
143
142k
}
144
145
/* Adds an entry to the multi-dispatch cache. */
146
3.87k
MVMObject * MVM_multi_cache_add(MVMThreadContext *tc, MVMObject *cache_obj, MVMObject *capture, MVMObject *result) {
147
3.87k
    MVMMultiCacheBody *cache;
148
3.87k
    MVMCallsite       *cs;
149
3.87k
    MVMArgProcContext *apc;
150
3.87k
    MVMuint64          match_flags[2 * MVM_INTERN_ARITY_LIMIT];
151
3.87k
    size_t             match_arg_idx[MVM_INTERN_ARITY_LIMIT];
152
3.87k
    MVMuint32          flag, i, num_obj_args, have_head, have_tree,
153
3.87k
                       have_callsite, matched_args, unmatched_arg,
154
3.87k
                       tweak_node, insert_node;
155
3.87k
    size_t             new_size;
156
3.87k
    MVMMultiCacheNode *new_head;
157
3.87k
    MVMObject        **new_results;
158
3.87k
    int                unlock_necessary = 0;
159
3.87k
160
3.87k
    /* Allocate a cache if needed. */
161
3.87k
    if (MVM_is_null(tc, cache_obj) || !IS_CONCRETE(cache_obj) || REPR(cache_obj)->ID != MVM_REPR_ID_MVMMultiCache) {
162
401
        MVMROOT(tc, capture, {
163
401
        MVMROOT(tc, result, {
164
401
            cache_obj = MVM_repr_alloc_init(tc, tc->instance->boot_types.BOOTMultiCache);
165
401
        });
166
401
        });
167
401
    }
168
3.87k
    cache = &((MVMMultiCache *)cache_obj)->body;
169
3.87k
170
3.87k
    /* Ensure we got a capture in to cache on; bail if not interned. */
171
3.87k
    if (REPR(capture)->ID == MVM_REPR_ID_MVMCallCapture) {
172
3.87k
        cs         = ((MVMCallCapture *)capture)->body.effective_callsite;
173
3.87k
        apc        = ((MVMCallCapture *)capture)->body.apc;
174
3.87k
        if (!cs->is_interned)
175
0
            return cache_obj;
176
3.87k
    }
177
0
    else {
178
0
        MVM_exception_throw_adhoc(tc, "Multi cache addition requires an MVMCallCapture");
179
0
    }
180
3.87k
181
3.87k
    /* Calculate matcher flags for all the object arguments. */
182
3.87k
    num_obj_args = 0;
183
13.3k
    for (i = 0, flag = 0; flag < cs->flag_count; i++, flag++) {
184
9.49k
        if (cs->arg_flags[flag] & MVM_CALLSITE_ARG_NAMED)
185
1.76k
            i++;
186
9.49k
        if ((cs->arg_flags[flag] & MVM_CALLSITE_ARG_MASK) == MVM_CALLSITE_ARG_OBJ) {
187
9.48k
            MVMRegister  arg   = apc->args[i];
188
9.48k
            MVMSTable   *st    = STABLE(arg.o);
189
9.48k
            MVMuint32    is_rw = 0;
190
9.48k
            if (st->container_spec && IS_CONCRETE(arg.o)) {
191
2
                MVMContainerSpec const *contspec = st->container_spec;
192
2
                if (!contspec->fetch_never_invokes)
193
2
                    goto DONE; /* Impossible to cache. */
194
0
                if (REPR(arg.o)->ID != MVM_REPR_ID_NativeRef) {
195
0
                    is_rw = contspec->can_store(tc, arg.o);
196
0
                    contspec->fetch(tc, arg.o, &arg);
197
0
                }
198
0
                else {
199
0
                    is_rw = 1;
200
0
                }
201
0
            }
202
9.48k
            match_flags[i] = STABLE(arg.o)->type_cache_id |
203
9.48k
                (is_rw ? MVM_MULTICACHE_ARG_RW_FILTER : 0) |
204
9.48k
                (IS_CONCRETE(arg.o) ? MVM_MULTICACHE_ARG_CONC_FILTER : 0);
205
9.48k
            match_arg_idx[num_obj_args] = i;
206
9.48k
            num_obj_args++;
207
9.48k
        }
208
9.49k
    }
209
3.87k
210
3.87k
    /* If we're in a multi-threaded context, obtain the cache additions
211
3.87k
     * lock, and then do another lookup to ensure nobody beat us to
212
3.87k
     * making this entry. */
213
3.87k
    if (MVM_instance_have_user_threads(tc)) {
214
0
        uv_mutex_lock(&(tc->instance->mutex_multi_cache_add));
215
0
        unlock_necessary = 1;
216
0
        if (MVM_multi_cache_find(tc, cache_obj, capture))
217
0
            goto DONE;
218
0
    }
219
3.87k
220
3.87k
    /* We're now udner the insertion lock and know nobody else can tweak the
221
3.87k
     * cache. First, see if there's even a current version and search tree. */
222
3.87k
    have_head = 0;
223
3.87k
    have_tree = 0;
224
3.87k
    have_callsite = 0;
225
3.87k
    matched_args = 0;
226
3.87k
    unmatched_arg = 0;
227
3.87k
    tweak_node = hash_callsite(tc, cs);
228
3.87k
    if (cache->node_hash_head) {
229
3.47k
        MVMMultiCacheNode *tree = cache->node_hash_head;
230
3.47k
        MVMint32 cur_node = tweak_node;
231
3.47k
        have_head = 1;
232
3.47k
        if (tree[cur_node].action.cs)
233
3.33k
            have_tree = 1;
234
3.47k
235
3.47k
        /* Now see if we already have this callsite. */
236
3.47k
        do {
237
3.47k
            if (tree[cur_node].action.cs == cs) {
238
3.33k
                have_callsite = 1;
239
3.33k
                cur_node = tree[cur_node].match;
240
3.33k
                break;
241
3.33k
            }
242
138
            tweak_node = cur_node;
243
138
            cur_node = tree[cur_node].no_match;
244
138
        } while (cur_node > 0);
245
3.47k
246
3.47k
        /* Chase until we reach an arg we don't match. */
247
23.1k
        while (cur_node > 0) {
248
19.6k
            MVMuint64 arg_match = tree[cur_node].action.arg_match;
249
19.6k
            MVMuint64 arg_idx   = arg_match & MVM_MULTICACHE_ARG_IDX_FILTER;
250
19.6k
            tweak_node = cur_node;
251
19.6k
            if ((match_flags[arg_idx] | arg_idx) == arg_match) {
252
3.62k
                matched_args++;
253
3.62k
                unmatched_arg = 0;
254
3.62k
                cur_node = tree[cur_node].match;
255
3.62k
            }
256
16.0k
            else {
257
16.0k
                unmatched_arg = 1;
258
16.0k
                cur_node = tree[cur_node].no_match;
259
16.0k
            }
260
19.6k
        }
261
3.47k
262
3.47k
        /* If we found a candidate, something inconsistent, as we
263
3.47k
         * checked for non-entry above. */
264
3.47k
        if (cur_node != 0)
265
0
            MVM_panic(1, "Corrupt multi dispatch cache: cur_node == 0");
266
3.47k
    }
267
3.87k
268
3.87k
    /* Now calculate the new size we'll need to allocate. */
269
3.87k
    new_size = cache->cache_memory_size;
270
3.87k
    if (!have_head)
271
400
        new_size += MVM_MULTICACHE_HASH_SIZE * sizeof(MVMMultiCacheNode);
272
3.47k
    else if (!have_callsite)
273
138
        new_size += sizeof(MVMMultiCacheNode);
274
3.87k
    new_size += (num_obj_args - matched_args) * sizeof(MVMMultiCacheNode);
275
3.87k
276
3.87k
    /* Allocate and copy existing cache. */
277
3.87k
    new_head = MVM_fixed_size_alloc(tc, tc->instance->fsa, new_size);
278
3.87k
    memcpy(new_head, cache->node_hash_head, cache->cache_memory_size);
279
3.87k
280
3.87k
    /* If we had no head, set it up. */
281
3.87k
    if (!have_head)
282
400
        memset(new_head, 0, MVM_MULTICACHE_HASH_SIZE * sizeof(MVMMultiCacheNode));
283
3.87k
284
3.87k
    /* Calculate storage location of new nodes. */
285
3.87k
    insert_node = have_head
286
3.47k
        ? cache->cache_memory_size / sizeof(MVMMultiCacheNode)
287
400
        : MVM_MULTICACHE_HASH_SIZE;
288
3.87k
289
3.87k
    /* If we had no callsite, add a node for it. */
290
3.87k
    if (!have_callsite) {
291
538
        if (!have_tree) {
292
537
            /* We'll put it in the tree root. */
293
537
            new_head[tweak_node].action.cs = cs;
294
537
        }
295
1
        else {
296
1
            /* We'll insert a new node and chain it from the tweak node. */
297
1
            new_head[insert_node].action.cs = cs;
298
1
            new_head[insert_node].no_match = 0;
299
1
            new_head[tweak_node].no_match = insert_node;
300
1
            tweak_node = insert_node;
301
1
            insert_node++;
302
1
        }
303
538
    }
304
3.87k
305
3.87k
    /* Now insert any needed arg matchers. */
306
9.73k
    for (i = matched_args; i < num_obj_args; i++) {
307
5.86k
        MVMuint32 arg_idx = match_arg_idx[i];
308
5.86k
        new_head[insert_node].action.arg_match = match_flags[arg_idx] | arg_idx;
309
5.86k
        new_head[insert_node].no_match = 0;
310
5.86k
        if (unmatched_arg) {
311
3.33k
            new_head[tweak_node].no_match = insert_node;
312
3.33k
            unmatched_arg = 0;
313
3.33k
        }
314
2.52k
        else {
315
2.52k
            new_head[tweak_node].match = insert_node;
316
2.52k
        }
317
5.86k
        tweak_node = insert_node;
318
5.86k
        insert_node++;
319
5.86k
    }
320
3.87k
321
3.87k
    /* Make a copy of the results, or allocate new (first result is NULL
322
3.87k
     * always) and insert the new result. Schedule old results for freeing. */
323
3.87k
    if (cache->num_results) {
324
3.47k
        new_results = MVM_fixed_size_alloc(tc, tc->instance->fsa,
325
3.47k
            (cache->num_results + 1) * sizeof(MVMObject *));
326
3.47k
        memcpy(new_results, cache->results, cache->num_results * sizeof(MVMObject *));
327
3.47k
        MVM_ASSIGN_REF(tc, &(cache_obj->header), new_results[cache->num_results], result);
328
3.47k
        MVM_fixed_size_free_at_safepoint(tc, tc->instance->fsa,
329
3.47k
            cache->num_results * sizeof(MVMObject *), cache->results);
330
3.47k
        cache->results = new_results;
331
3.47k
        cache->num_results++;
332
3.47k
    }
333
400
    else {
334
400
        new_results = MVM_fixed_size_alloc(tc, tc->instance->fsa,
335
400
            2 * sizeof(MVMObject *));
336
400
        new_results[0] = NULL; /* Sentinel */
337
400
        MVM_ASSIGN_REF(tc, &(cache_obj->header), new_results[1], result);
338
400
        cache->results = new_results;
339
400
        cache->num_results = 2;
340
400
    }
341
3.87k
    MVM_barrier();
342
3.87k
343
3.87k
    /* Associate final node with result index. */
344
3.87k
    new_head[tweak_node].match = -(cache->num_results - 1);
345
3.87k
346
3.87k
    /* Update the rest. */
347
3.87k
    if (cache->node_hash_head)
348
3.47k
        MVM_fixed_size_free_at_safepoint(tc, tc->instance->fsa,
349
3.47k
            cache->cache_memory_size, cache->node_hash_head);
350
3.87k
    cache->node_hash_head = new_head;
351
3.87k
    cache->cache_memory_size = new_size;
352
3.87k
353
3.87k
#if MVM_MULTICACHE_DEBUG
354
    printf("Made new entry for callsite with %d object arguments\n", num_obj_args);
355
    dump_cache(tc, cache);
356
#endif
357
3.87k
#if MVM_MULTICACHE_BIG_PROFILE
358
    if (cache->num_results >= 32 && is_power_of_2(cache->num_results)) {
359
        MVMCode *code = (MVMCode *)MVM_frame_find_invokee(tc, result, NULL);
360
        char *name = MVM_string_utf8_encode_C_string(tc, code->body.sf->body.name);
361
        printf("Multi cache for %s reached %d entries\n", name, cache->num_results);
362
        MVM_free(name);
363
    }
364
#endif
365
3.87k
366
3.87k
    /* Release lock if needed. */
367
3.87k
  DONE:
368
3.87k
    if (unlock_necessary)
369
0
        uv_mutex_unlock(&(tc->instance->mutex_multi_cache_add));
370
3.87k
371
3.87k
    /* Hand back the created/updated cache. */
372
3.87k
    return cache_obj;
373
3.87k
}
374
375
/* Does a lookup in a multi-dispatch cache using a capture. */
376
3.88k
MVMObject * MVM_multi_cache_find(MVMThreadContext *tc, MVMObject *cache_obj, MVMObject *capture) {
377
3.88k
    if (REPR(capture)->ID == MVM_REPR_ID_MVMCallCapture) {
378
3.88k
        MVMCallsite       *cs  = ((MVMCallCapture *)capture)->body.effective_callsite;
379
3.88k
        MVMArgProcContext *apc = ((MVMCallCapture *)capture)->body.apc;
380
3.88k
        return MVM_multi_cache_find_callsite_args(tc, cache_obj, cs, apc->args);
381
3.88k
    }
382
0
    else {
383
0
        MVM_exception_throw_adhoc(tc, "Multi cache lookup requires an MVMCallCapture");
384
0
    }
385
3.88k
}
386
387
/* Does a lookup in the multi-dispatch cache using a callsite and args. */
388
MVMObject * MVM_multi_cache_find_callsite_args(MVMThreadContext *tc, MVMObject *cache_obj,
389
137k
    MVMCallsite *cs, MVMRegister *args) {
390
137k
    MVMMultiCacheBody *cache;
391
137k
    MVMMultiCacheNode *tree;
392
137k
    MVMint32 cur_node;
393
137k
394
137k
    /* Bail if callsite not interned. */
395
137k
    if (!cs->is_interned)
396
0
        return NULL;
397
137k
398
137k
    /* If no cache, no result. */
399
137k
    if (MVM_is_null(tc, cache_obj) || !IS_CONCRETE(cache_obj) || REPR(cache_obj)->ID != MVM_REPR_ID_MVMMultiCache)
400
397
        return NULL;
401
137k
    cache = &((MVMMultiCache *)cache_obj)->body;
402
137k
    if (!cache->node_hash_head)
403
4
        return NULL;
404
137k
405
137k
    /* Use hashed callsite to find the node to start with. */
406
137k
    cur_node = hash_callsite(tc, cs);
407
137k
408
137k
    /* Walk tree until we match callsite. */
409
137k
    tree = cache->node_hash_head;
410
137k
    do {
411
137k
        if (tree[cur_node].action.cs == cs) {
412
137k
            cur_node = tree[cur_node].match;
413
137k
            break;
414
137k
        }
415
273
        cur_node = tree[cur_node].no_match;
416
273
    } while (cur_node > 0);
417
137k
418
137k
    /* Now walk until we match argument type/concreteness/rw. */
419
858k
    while (cur_node > 0) {
420
720k
        MVMuint64    arg_match = tree[cur_node].action.arg_match;
421
720k
        MVMuint64    arg_idx   = arg_match & MVM_MULTICACHE_ARG_IDX_FILTER;
422
720k
        MVMuint64    type_id   = arg_match & MVM_MULTICACHE_TYPE_ID_FILTER;
423
720k
        MVMRegister  arg       = args[arg_idx];
424
720k
        MVMSTable   *st        = STABLE(arg.o);
425
720k
        MVMuint64    is_rw     = 0;
426
720k
        if (st->container_spec && IS_CONCRETE(arg.o)) {
427
4
            MVMContainerSpec const *contspec = st->container_spec;
428
4
            if (!contspec->fetch_never_invokes)
429
4
                return NULL;
430
0
            if (REPR(arg.o)->ID != MVM_REPR_ID_NativeRef) {
431
0
                is_rw = contspec->can_store(tc, arg.o);
432
0
                contspec->fetch(tc, arg.o, &arg);
433
0
            }
434
0
            else {
435
0
                is_rw = 1;
436
0
            }
437
0
        }
438
720k
        if (STABLE(arg.o)->type_cache_id == type_id) {
439
323k
            MVMuint32 need_concrete = (arg_match & MVM_MULTICACHE_ARG_CONC_FILTER) ? 1 : 0;
440
323k
            if (IS_CONCRETE(arg.o) == need_concrete) {
441
323k
                MVMuint32 need_rw = (arg_match & MVM_MULTICACHE_ARG_RW_FILTER) ? 1 : 0;
442
323k
                if (need_rw == is_rw) {
443
323k
                    cur_node = tree[cur_node].match;
444
323k
                    continue;
445
323k
                }
446
323k
            }
447
323k
        }
448
397k
        cur_node = tree[cur_node].no_match;
449
397k
    }
450
137k
451
137k
    /* Negate result and index into results (the first result is always NULL
452
137k
     * to save flow control around "no match"). */
453
137k
    return cache->results[-cur_node];
454
137k
}
455
456
/* Do a multi cache lookup based upon spesh arg facts. */
457
787
MVMObject * MVM_multi_cache_find_spesh(MVMThreadContext *tc, MVMObject *cache_obj, MVMSpeshCallInfo *arg_info) {
458
787
    MVMMultiCacheBody *cache;
459
787
    MVMMultiCacheNode *tree;
460
787
    MVMint32 cur_node;
461
787
462
787
    /* Bail if callsite not interned. */
463
787
    if (!arg_info->cs->is_interned)
464
0
        return NULL;
465
787
466
787
    /* If no cache, no result. */
467
787
    if (MVM_is_null(tc, cache_obj) || !IS_CONCRETE(cache_obj) || REPR(cache_obj)->ID != MVM_REPR_ID_MVMMultiCache)
468
0
        return NULL;
469
787
    cache = &((MVMMultiCache *)cache_obj)->body;
470
787
    if (!cache->node_hash_head)
471
0
        return NULL;
472
787
473
787
    /* Use hashed callsite to find the node to start with. */
474
787
    cur_node = hash_callsite(tc, arg_info->cs);
475
787
476
787
    /* Walk tree until we match callsite. */
477
787
    tree = cache->node_hash_head;
478
787
    do {
479
787
        if (tree[cur_node].action.cs == arg_info->cs) {
480
787
            cur_node = tree[cur_node].match;
481
787
            break;
482
787
        }
483
0
        cur_node = tree[cur_node].no_match;
484
0
    } while (cur_node > 0);
485
787
486
787
    /* Now walk until we match argument type/concreteness/rw. */
487
3.57k
    while (cur_node > 0) {
488
3.29k
        MVMuint64      arg_match = tree[cur_node].action.arg_match;
489
3.29k
        MVMuint64      arg_idx   = arg_match & MVM_MULTICACHE_ARG_IDX_FILTER;
490
3.29k
        MVMuint64      type_id   = arg_match & MVM_MULTICACHE_TYPE_ID_FILTER;
491
3.29k
        MVMSpeshFacts *facts     = arg_info->arg_facts[arg_idx];
492
3.29k
        if (facts) {
493
3.29k
            /* Figure out type, concreteness, and rw-ness from facts. */
494
3.29k
            MVMSTable *known_type_st;
495
3.29k
            MVMuint32  is_conc;
496
3.29k
            MVMuint32  is_rw;
497
3.29k
498
3.29k
            /* Must know type. */
499
3.29k
            if (!(facts->flags & MVM_SPESH_FACT_KNOWN_TYPE))
500
505
                return NULL;
501
3.29k
502
3.29k
            /* Must know if it's concrete or not. */
503
2.79k
            if (!(facts->flags & (MVM_SPESH_FACT_CONCRETE | MVM_SPESH_FACT_TYPEOBJ)))
504
1
                return NULL;
505
2.79k
506
2.79k
            /* If it's a container, must know what's inside it. Otherwise,
507
2.79k
             * we're already good on type info. */
508
2.79k
            if ((facts->flags & MVM_SPESH_FACT_CONCRETE) && STABLE(facts->type)->container_spec) {
509
0
                /* Again, need to know type and concreteness. */
510
0
                if (!(facts->flags & MVM_SPESH_FACT_KNOWN_DECONT_TYPE))
511
0
                    return NULL;
512
0
                if (!(facts->flags & (MVM_SPESH_FACT_DECONT_CONCRETE | MVM_SPESH_FACT_DECONT_TYPEOBJ)))
513
0
                    return NULL;
514
0
                known_type_st = STABLE(facts->decont_type);
515
0
                is_conc = (facts->flags & MVM_SPESH_FACT_DECONT_CONCRETE) ? 1 : 0;
516
0
                is_rw = (facts->flags & MVM_SPESH_FACT_RW_CONT) ? 1 : 0;
517
0
            }
518
2.79k
            else {
519
2.79k
                known_type_st = STABLE(facts->type);
520
2.79k
                is_conc = (facts->flags & MVM_SPESH_FACT_CONCRETE) ? 1 : 0;
521
2.79k
                is_rw = 0;
522
2.79k
            }
523
2.79k
524
2.79k
            /* Now check if what we have matches what we need. */
525
2.79k
            if (known_type_st->type_cache_id == type_id) {
526
1.28k
                MVMuint32 need_concrete = (arg_match & MVM_MULTICACHE_ARG_CONC_FILTER) ? 1 : 0;
527
1.28k
                if (is_conc == need_concrete) {
528
1.28k
                    MVMuint32 need_rw = (arg_match & MVM_MULTICACHE_ARG_RW_FILTER) ? 1 : 0;
529
1.28k
                    if (need_rw == is_rw) {
530
1.28k
                        cur_node = tree[cur_node].match;
531
1.28k
                        continue;
532
1.28k
                    }
533
1.28k
                }
534
1.28k
            }
535
1.51k
            cur_node = tree[cur_node].no_match;
536
1.51k
        }
537
0
        else {
538
0
            /* No facts about this argument available from analysis, so
539
0
             * can't resolve the dispatch. */
540
0
            return NULL;
541
0
        }
542
3.29k
    }
543
787
544
787
    /* Negate result and index into results (the first result is always NULL
545
787
     * to save flow control around "no match"). */
546
281
    return cache->results[-cur_node];
547
787
}