Skip to content

Commit 6238e27

Browse files
committed
Implement multi-dispatch cache in NQP. It was fairly fast without this anyway, but this helps a bit more; we do a lot of multi-dispatch when compiling QAST. Also fixes a memory leak.
1 parent a15cdd3 commit 6238e27

File tree

4 files changed

+224
-17
lines changed

4 files changed

+224
-17
lines changed

src/core/NQPRoutine.pm

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ my knowhow NQPRoutine {
44
has $!dispatchees;
55
has $!dispatch_cache;
66
method add_dispatchee($code) {
7+
$!dispatch_cache := nqp::null();
78
$!dispatchees.push($code);
89
}
910
method is_dispatcher() {

src/guts/multi_dispatch.c

Lines changed: 174 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ static INTVAL smo_id = 0;
4343
#define DEFINED_ONLY 1
4444
#define UNDEFINED_ONLY 2
4545

46+
/* Register type. */
47+
#define BIND_VAL_OBJ 4
4648

4749
/* Compares two types to see if the first is narrower than the second. */
4850
static INTVAL is_narrower_type(PARROT_INTERP, PMC *a, PMC *b) {
@@ -262,26 +264,182 @@ static PMC *get_dispatchees(PARROT_INTERP, PMC *dispatcher) {
262264
}
263265
}
264266

267+
/* Gets (creating if needed) a multi-dispatch cache. */
268+
static NQP_md_cache *get_dispatch_cache(PARROT_INTERP, PMC *dispatcher) {
269+
PMC *cache_ptr;
270+
if (!smo_id)
271+
smo_id = Parrot_pmc_get_type_str(interp, Parrot_str_new(interp, "SixModelObject", 0));
272+
if (dispatcher->vtable->base_type == enum_class_Sub && PARROT_SUB(dispatcher)->multi_signature->vtable->base_type == smo_id) {
273+
NQP_Routine *r = (NQP_Routine *)PMC_data(PARROT_SUB(dispatcher)->multi_signature);
274+
if (PMC_IS_NULL(r->dispatch_cache)) {
275+
NQP_md_cache *c = mem_sys_allocate_zeroed(sizeof(NQP_md_cache));
276+
cache_ptr = Parrot_pmc_new(interp, enum_class_Pointer);
277+
VTABLE_set_pointer(interp, cache_ptr, c);
278+
r->dispatch_cache = cache_ptr;
279+
PARROT_GC_WRITE_BARRIER(interp, PARROT_SUB(dispatcher)->multi_signature);
280+
}
281+
else {
282+
cache_ptr = r->dispatch_cache;
283+
}
284+
}
285+
else {
286+
if (PMC_IS_NULL(PARROT_DISPATCHERSUB(dispatcher)->dispatch_cache)) {
287+
NQP_md_cache *c = mem_sys_allocate_zeroed(sizeof(NQP_md_cache));
288+
cache_ptr = Parrot_pmc_new(interp, enum_class_Pointer);
289+
VTABLE_set_pointer(interp, cache_ptr, c);
290+
PARROT_DISPATCHERSUB(dispatcher)->dispatch_cache = cache_ptr;
291+
PARROT_GC_WRITE_BARRIER(interp, dispatcher);
292+
}
293+
else {
294+
cache_ptr = PARROT_DISPATCHERSUB(dispatcher)->dispatch_cache;
295+
}
296+
}
297+
return (NQP_md_cache *)VTABLE_get_pointer(interp, cache_ptr);
298+
}
299+
300+
/*
301+
302+
=item C<static PMC * find_in_cache(PARROT_INTERP, NQP_md_cache *cache, PMC *capture, INTVAL num_args)>
303+
304+
Looks for an entry in the multi-dispatch cache.
305+
306+
=cut
307+
308+
*/
309+
static PMC *
310+
find_in_cache(PARROT_INTERP, NQP_md_cache *cache, PMC *capture, INTVAL num_args) {
311+
INTVAL arg_tup[MD_CACHE_MAX_ARITY];
312+
INTVAL i, j, entries, t_pos;
313+
struct Pcc_cell * pc_positionals;
314+
315+
/* If it's zero-arity, return result right off. */
316+
if (num_args == 0)
317+
return cache->zero_arity;
318+
319+
/* Create arg tuple. */
320+
if (capture->vtable->base_type == enum_class_CallContext)
321+
GETATTR_CallContext_positionals(interp, capture, pc_positionals);
322+
else
323+
return NULL;
324+
for (i = 0; i < num_args; i++) {
325+
if (pc_positionals[i].type == BIND_VAL_OBJ) {
326+
PMC *arg = pc_positionals[i].u.p;
327+
if (arg->vtable->base_type != smo_id)
328+
return NULL;
329+
arg_tup[i] = STABLE(arg)->type_cache_id | (IS_CONCRETE(arg) ? 1 : 0);
330+
}
331+
else {
332+
arg_tup[i] = (pc_positionals[i].type << 1) | 1;
333+
}
334+
}
335+
336+
/* Look through entries. */
337+
entries = cache->arity_caches[num_args - 1].num_entries;
338+
t_pos = 0;
339+
for (i = 0; i < entries; i++) {
340+
INTVAL match = 1;
341+
for (j = 0; j < num_args; j++) {
342+
if (cache->arity_caches[num_args - 1].type_ids[t_pos + j] != arg_tup[j]) {
343+
match = 0;
344+
break;
345+
}
346+
}
347+
if (match)
348+
return cache->arity_caches[num_args - 1].results[i];
349+
t_pos += num_args;
350+
}
351+
352+
return NULL;
353+
}
354+
355+
356+
/*
357+
358+
=item C<static void add_to_cache(PARROT_INTERP, NQP_md_cache *cache, PMC *capture, INTVAL num_args)>
359+
360+
Adds an entry to the multi-dispatch cache.
361+
362+
=cut
363+
364+
*/
365+
static void
366+
add_to_cache(PARROT_INTERP, NQP_md_cache *cache, PMC *capture, INTVAL num_args, PMC *result) {
367+
INTVAL arg_tup[MD_CACHE_MAX_ARITY];
368+
INTVAL i, entries, ins_type;
369+
struct Pcc_cell * pc_positionals;
370+
371+
/* If it's zero arity, just stick it in that slot. */
372+
if (num_args == 0) {
373+
cache->zero_arity = result;
374+
return;
375+
}
376+
377+
/* If the cache is saturated, don't do anything (we could instead do a random
378+
* replacement). */
379+
entries = cache->arity_caches[num_args - 1].num_entries;
380+
if (entries == MD_CACHE_MAX_ENTRIES)
381+
return;
382+
383+
/* Create arg tuple. */
384+
if (capture->vtable->base_type == enum_class_CallContext)
385+
GETATTR_CallContext_positionals(interp, capture, pc_positionals);
386+
else
387+
return;
388+
for (i = 0; i < num_args; i++) {
389+
if (pc_positionals[i].type == BIND_VAL_OBJ) {
390+
PMC *arg = pc_positionals[i].u.p;
391+
if (arg->vtable->base_type != smo_id)
392+
return;
393+
arg_tup[i] = STABLE(arg)->type_cache_id | (IS_CONCRETE(arg) ? 1 : 0);
394+
}
395+
else {
396+
arg_tup[i] = (pc_positionals[i].type << 1) | 1;
397+
}
398+
}
399+
400+
/* If there's no entries yet, need to do some allocation. */
401+
if (entries == 0) {
402+
cache->arity_caches[num_args - 1].type_ids = mem_sys_allocate(num_args * sizeof(INTVAL) * MD_CACHE_MAX_ENTRIES);
403+
cache->arity_caches[num_args - 1].results = mem_sys_allocate(sizeof(PMC *) * MD_CACHE_MAX_ENTRIES);
404+
}
405+
406+
/* Add entry. */
407+
ins_type = entries * num_args;
408+
for (i = 0; i < num_args; i++)
409+
cache->arity_caches[num_args - 1].type_ids[ins_type + i] = arg_tup[i];
410+
cache->arity_caches[num_args - 1].results[entries] = result;
411+
cache->arity_caches[num_args - 1].num_entries = entries + 1;
412+
}
413+
265414
/* Performs a multiple dispatch using the candidates held in the passed
266415
* dispatcher and using the arguments in the passed capture. */
267416
PMC *nqp_multi_dispatch(PARROT_INTERP, PMC *dispatcher, PMC *capture) {
268-
/* Get list and number of dispatchees. */
269-
PMC *dispatchees = get_dispatchees(interp, dispatcher);
270-
const INTVAL num_candidates = VTABLE_elements(interp, dispatchees);
417+
NQP_md_cache *disp_cache;
418+
PMC *dispatchees, *cache_result;
419+
INTVAL type_mismatch, possibles_count, type_check_count,
420+
num_candidates, num_args;
421+
candidate_info **possibles, **candidates, **cur_candidate;
271422

272423
/* Count arguments. */
273-
const INTVAL num_args = VTABLE_elements(interp, capture);
424+
num_args = VTABLE_elements(interp, capture);
425+
426+
/* See if the dispatcher cache will resolve it right off. */
427+
disp_cache = get_dispatch_cache(interp, dispatcher);
428+
cache_result = find_in_cache(interp, disp_cache, capture, num_args);
429+
if (!PMC_IS_NULL(cache_result))
430+
return cache_result;
431+
432+
/* Get list and number of dispatchees. */
433+
dispatchees = get_dispatchees(interp, dispatcher);
434+
num_candidates = VTABLE_elements(interp, dispatchees);
274435

275436
/* Initialize dispatcher state. */
276-
INTVAL type_mismatch;
277-
INTVAL possibles_count = 0;
278-
candidate_info **possibles = mem_allocate_n_typed(num_candidates, candidate_info *);
279-
INTVAL type_check_count;
437+
possibles_count = 0;
438+
possibles = mem_allocate_n_typed(num_candidates, candidate_info *);
280439

281-
/* Get sorted candidate list.
282-
* XXX We'll cache this in the future. */
283-
candidate_info** candidates = sort_candidates(interp, dispatchees);
284-
candidate_info** cur_candidate = candidates;
440+
/* Get sorted candidate list. */
441+
candidates = sort_candidates(interp, dispatchees);
442+
cur_candidate = candidates;
285443

286444
/* Iterate over the candidates and collect best ones; terminate
287445
* when we see two nulls (may break out earlier). */
@@ -350,12 +508,13 @@ PMC *nqp_multi_dispatch(PARROT_INTERP, PMC *dispatcher, PMC *capture) {
350508

351509
/* Cache the result if there's a single chosen one. */
352510
if (possibles_count == 1) {
353-
/* XXX TODO: Cache entry. */
511+
add_to_cache(interp, disp_cache, capture, num_args, possibles[0]->sub);
354512
}
355513

356514
/* Need a unique candidate. */
357515
if (possibles_count == 1) {
358516
PMC *result = possibles[0]->sub;
517+
mem_sys_free(candidates);
359518
mem_sys_free(possibles);
360519
return result;
361520
}
@@ -373,6 +532,7 @@ PMC *nqp_multi_dispatch(PARROT_INTERP, PMC *dispatcher, PMC *capture) {
373532
cur_candidate++;
374533
}
375534

535+
mem_sys_free(candidates);
376536
mem_sys_free(possibles);
377537
Parrot_ex_throw_from_c_args(interp, NULL, 1,
378538
"No applicable candidates found to dispatch to for '%Ss'. Available candidates are:\n%Ss",
@@ -386,6 +546,7 @@ PMC *nqp_multi_dispatch(PARROT_INTERP, PMC *dispatcher, PMC *capture) {
386546
/* XXX TODO: sig dumping
387547
for (i = 0; i < possibles_count; i++)
388548
signatures = dump_signature(interp, signatures, possibles[i]->sub); */
549+
mem_sys_free(candidates);
389550
mem_sys_free(possibles);
390551
Parrot_ex_throw_from_c_args(interp, NULL, 1,
391552
"Ambiguous dispatch to multi '%Ss'. Ambiguous candidates had signatures:\n%Ss",

src/guts/multi_dispatch.h

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ typedef struct {
55
PMC *_do; /* Lower-level code object. */
66
PMC *signature; /* Signature object. */
77
PMC *dispatchees; /* List of dispatchees, if any. */
8-
PMC *dispatcher_cache; /* Holder for any dispatcher cache. */
8+
PMC *dispatch_cache; /* Holder for any dispatcher cache. */
99
} NQP_Routine;
1010

1111
/* This is how an NQPSignature looks on the inside. */
@@ -16,4 +16,49 @@ typedef struct {
1616
PMC *definednesses; /* Set of definedness flags for arguments. */
1717
} NQP_Signature;
1818

19+
/* Maximum positional arity we cache up to. (Good to make it a
20+
* power of 2.) */
21+
#define MD_CACHE_MAX_ARITY 4
22+
23+
/* Maximum entries we cache per arity. (Good to make it a
24+
* power of 2.) */
25+
#define MD_CACHE_MAX_ENTRIES 16
26+
27+
/* The cached info that we keep per arity. */
28+
typedef struct {
29+
/* The number of entries in the cache. */
30+
INTVAL num_entries;
31+
32+
/* This is a bunch of type IDs. We allocate it arity * MAX_ENTRIES
33+
* big and go through it in arity sized chunks. */
34+
INTVAL *type_ids;
35+
36+
/* The results we return from the cache. */
37+
PMC **results;
38+
} NQP_md_arity_cache;
39+
40+
/* Multi-dispatcher cache info, which we will hang off the dispatcher
41+
* cache slot in a dispatcher sub. */
42+
typedef struct {
43+
/* The fast, per-arity cache. */
44+
NQP_md_arity_cache arity_caches[MD_CACHE_MAX_ARITY];
45+
46+
/* Zero-arity cached result. */
47+
PMC *zero_arity;
48+
} NQP_md_cache;
49+
50+
/* Nabbed from Parrot, since it's not exposed and it's the only way
51+
* (so far as I can tell) to get at the underlying primitive type
52+
* being passed. */
53+
typedef struct Pcc_cell
54+
{
55+
union u {
56+
PMC *p;
57+
STRING *s;
58+
INTVAL i;
59+
FLOATVAL n;
60+
} u;
61+
INTVAL type;
62+
} Pcc_cell;
63+
1964
PMC *nqp_multi_dispatch(PARROT_INTERP, PMC *dispatcher, PMC *capture);

src/pmc/dispatchersub.pmc

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,19 @@ pmclass DispatcherSub extends Sub auto_attrs dynpmc group nqp {
1212
}
1313

1414
VTABLE void mark() {
15-
PMC *dispatchees;
15+
PMC *dispatchees, *dispatch_cache;
1616
SUPER();
1717
GET_ATTR_dispatchees(interp, SELF, dispatchees);
1818
Parrot_gc_mark_PMC_alive(INTERP, dispatchees);
19+
GET_ATTR_dispatch_cache(interp, SELF, dispatch_cache);
20+
Parrot_gc_mark_PMC_alive(INTERP, dispatch_cache);
1921
}
2022

2123
VTABLE PMC * clone() {
2224
PMC *dispatchees, *dispatch_cache;
2325
PMC *cloned = SUPER();
2426
GET_ATTR_dispatchees(interp, SELF, dispatchees);
2527
SET_ATTR_dispatchees(interp, cloned, dispatchees);
26-
GET_ATTR_dispatch_cache(interp, SELF, dispatch_cache);
27-
SET_ATTR_dispatch_cache(interp, cloned, dispatch_cache);
2828
return cloned;
2929
}
3030

0 commit comments

Comments
 (0)