File Coverage

Cache.c
Criterion Covered Total %
statement 534 630 84.7
branch 161 280 57.5
condition n/a
subroutine n/a
pod n/a
total 695 910 76.3


line stmt bran cond sub pod time code
1             /*
2             * Cache.c - Ultra-fast LRU cache with O(1) operations
3             *
4             * Optimizations:
5             * 1. Inline get_lru_cache with LIKELY/UNLIKELY fast path
6             * 2. Inline key storage in entry struct (no key SV overhead)
7             * 3. PERL_HASH for hash randomization + native integration
8             * 4. Avoid sv_mortalcopy in delete (SvREFCNT_inc + sv_2mortal)
9             * 5. UNLIKELY/LIKELY branch hints on hot paths
10             * 6. Combined hash_find_and_remove for single-pass delete
11             * 7. SvPV_const to avoid spurious string overload calls
12             * 8. Entry freelist pool to reduce malloc/free churn
13             * 9. Variable-length entry allocation for inline keys
14             * 10. PERL_STATIC_INLINE on critical internal functions
15             * 11. Dynamic rehash when load factor exceeds threshold
16             * 12. Struct field reorder for cache-line locality in hash_find
17             * 13. sv_setsv in-place value update (no alloc+free per update)
18             * 14. Single 'if' eviction instead of while loop
19             */
20              
21             #include "EXTERN.h"
22             #include "perl.h"
23             #include "XSUB.h"
24             #include "include/lru_compat.h"
25              
26             /* ============================================
27             Branch prediction hints (#5)
28             ============================================ */
29             #ifndef UNLIKELY
30             # ifdef __GNUC__
31             # define UNLIKELY(x) __builtin_expect(!!(x), 0)
32             # define LIKELY(x) __builtin_expect(!!(x), 1)
33             # else
34             # define UNLIKELY(x) (x)
35             # define LIKELY(x) (x)
36             # endif
37             #endif
38              
39             /* ============================================
40             Entry struct with inline key (#2, #9)
41             Key bytes stored directly in struct tail —
42             eliminates SV allocation and SvPV dereference
43             on every hash_find comparison.
44             ============================================ */
45             typedef struct lru_entry {
46             /* Hot fields for hash_find — packed in first cache line (#12) */
47             U32 hash; /* Cached hash value */
48             U32 klen; /* Key byte length */
49             struct lru_entry *hash_next; /* Hash chain (singly-linked) */
50             SV *value;
51             /* Warm fields for LRU list ops */
52             struct lru_entry *prev; /* LRU doubly-linked list */
53             struct lru_entry *next;
54             U32 alloc_klen; /* Allocated key capacity (for freelist) */
55             char key[1]; /* Variable-length inline key, NUL-terminated */
56             } LRUEntry;
57              
58             #define LRU_ENTRY_SIZE(kl) (offsetof(LRUEntry, key) + (kl) + 1)
59              
60             /* ============================================
61             LRU cache structure (#8, #11)
62             ============================================ */
63             #define LRU_FREELIST_MAX 256
64             #define LRU_LOAD_NUMER 3 /* rehash threshold = bucket_count * 3/4 */
65             #define LRU_LOAD_DENOM 4
66              
67             typedef struct {
68             LRUEntry **buckets;
69             LRUEntry *head; /* Most recently used */
70             LRUEntry *tail; /* Least recently used */
71             LRUEntry *freelist; /* Recycled entries (LIFO via hash_next) */
72             IV capacity;
73             IV size;
74             IV bucket_count; /* Always power of 2 */
75             IV bucket_mask; /* bucket_count - 1 */
76             IV freelist_count;
77             IV rehash_threshold; /* Grow when size exceeds this */
78             } LRUCache;
79              
80             /* ============================================
81             Custom op descriptors
82             ============================================ */
83             static XOP lru_get_xop;
84             static XOP lru_set_xop;
85             static XOP lru_exists_xop;
86             static XOP lru_peek_xop;
87             static XOP lru_delete_xop;
88              
89             static XOP lru_func_get_xop;
90             static XOP lru_func_set_xop;
91             static XOP lru_func_exists_xop;
92             static XOP lru_func_peek_xop;
93             static XOP lru_func_delete_xop;
94             static XOP lru_func_oldest_xop;
95             static XOP lru_func_newest_xop;
96              
97             /* ============================================
98             Magic vtable
99             ============================================ */
100             static int lru_cache_free(pTHX_ SV *sv, MAGIC *mg);
101              
102             static MGVTBL lru_cache_vtbl = {
103             NULL, NULL, NULL, NULL,
104             lru_cache_free,
105             NULL, NULL, NULL
106             };
107              
108             /* ============================================
109             Fast cache extraction (#1, #10)
110             Single mg_find — no vtbl loop, UNLIKELY on
111             error paths. PERL_STATIC_INLINE for hot paths.
112             ============================================ */
113 30537           PERL_STATIC_INLINE LRUCache* get_lru_cache(pTHX_ SV *obj) {
114             MAGIC *mg;
115 30537 50         if (UNLIKELY(!SvROK(obj))) croak("Not a reference");
116 30537           mg = mg_find(SvRV(obj), PERL_MAGIC_ext);
117 30537 50         if (LIKELY(mg != NULL))
118 30537           return (LRUCache*)mg->mg_ptr;
119 0           croak("Not an LRU::Cache object");
120             return NULL; /* unreachable */
121             }
122              
123             /* ============================================
124             Entry allocation with freelist (#8, #9)
125             ============================================ */
126 5782           PERL_STATIC_INLINE LRUEntry* entry_alloc(LRUCache *c, U32 klen) {
127 5782 100         if (c->freelist && c->freelist->alloc_klen >= klen) {
    100          
128 4537           LRUEntry *e = c->freelist;
129 4537           c->freelist = e->hash_next;
130 4537           c->freelist_count--;
131 4537           return e;
132             }
133             {
134 1245           LRUEntry *e = (LRUEntry*)safemalloc(LRU_ENTRY_SIZE(klen));
135 1245           e->alloc_klen = klen;
136 1245           return e;
137             }
138             }
139              
140 4851           static void entry_recycle(pTHX_ LRUCache *c, LRUEntry *e) {
141 4851 50         if (e->value) {
142 4851           SvREFCNT_dec(e->value);
143 4851           e->value = NULL;
144             }
145 4851 50         if (c->freelist_count < LRU_FREELIST_MAX) {
146 4851           e->hash_next = c->freelist;
147 4851           c->freelist = e;
148 4851           c->freelist_count++;
149             } else {
150 0           Safefree(e);
151             }
152 4851           }
153              
154             /* ============================================
155             LRU list operations with branch hints (#5)
156             ============================================ */
157 943           PERL_STATIC_INLINE void lru_unlink(LRUCache *c, LRUEntry *e) {
158 943 100         if (LIKELY(e->prev != NULL))
159 938           e->prev->next = e->next;
160             else
161 5           c->head = e->next;
162              
163 943 100         if (LIKELY(e->next != NULL))
164 6           e->next->prev = e->prev;
165             else
166 937           c->tail = e->prev;
167              
168 943           e->prev = e->next = NULL;
169 943           }
170              
171 5881           PERL_STATIC_INLINE void lru_push_front(LRUCache *c, LRUEntry *e) {
172 5881           e->prev = NULL;
173 5881           e->next = c->head;
174 5881 100         if (LIKELY(c->head != NULL))
175 5572           c->head->prev = e;
176 5881           c->head = e;
177 5881 100         if (UNLIKELY(c->tail == NULL))
178 309           c->tail = e;
179 5881           }
180              
181 10964           PERL_STATIC_INLINE void lru_promote(LRUCache *c, LRUEntry *e) {
182 10964 100         if (LIKELY(e == c->head)) return; /* already MRU — fast path */
183 99           lru_unlink(c, e);
184 99           lru_push_front(c, e);
185             }
186              
187             /* ============================================
188             Hash table operations (#2 inline key compare)
189             ============================================ */
190              
191             /* Find — compares inline key bytes directly, no SvPV */
192 26588           PERL_STATIC_INLINE LRUEntry* hash_find(LRUCache *c, const char *kpv,
193             STRLEN klen, U32 hash)
194             {
195 26588           LRUEntry *e = c->buckets[hash & c->bucket_mask];
196 27001 100         while (e) {
197 17197 100         if (e->hash == hash
198 16784 50         && e->klen == (U32)klen
199 16784 50         && memcmp(e->key, kpv, klen) == 0)
200 16784           return e;
201 413           e = e->hash_next;
202             }
203 9804           return NULL;
204             }
205              
206 5782           PERL_STATIC_INLINE void hash_insert(LRUCache *c, LRUEntry *e) {
207 5782           IV idx = e->hash & c->bucket_mask;
208 5782           e->hash_next = c->buckets[idx];
209 5782           c->buckets[idx] = e;
210 5782           }
211              
212             /* Remove a known entry from its bucket chain */
213 586           static void hash_remove(LRUCache *c, LRUEntry *e) {
214 586           IV idx = e->hash & c->bucket_mask;
215 586           LRUEntry **pp = &c->buckets[idx];
216 835 50         while (*pp) {
217 835 100         if (*pp == e) {
218 586           *pp = e->hash_next;
219 586           e->hash_next = NULL;
220 586           return;
221             }
222 249           pp = &(*pp)->hash_next;
223             }
224             }
225              
226             /* Combined find-and-remove: single chain walk (#6) */
227 508           static LRUEntry* hash_find_and_remove(LRUCache *c, const char *kpv,
228             STRLEN klen, U32 hash)
229             {
230 508           IV idx = hash & c->bucket_mask;
231 508           LRUEntry **pp = &c->buckets[idx];
232 585 100         while (*pp) {
233 335           LRUEntry *e = *pp;
234 335 100         if (e->hash == hash
235 258 50         && e->klen == (U32)klen
236 258 50         && memcmp(e->key, kpv, klen) == 0) {
237 258           *pp = e->hash_next;
238 258           e->hash_next = NULL;
239 258           return e;
240             }
241 77           pp = &e->hash_next;
242             }
243 250           return NULL;
244             }
245              
246             /* ============================================
247             Dynamic rehash (#11)
248             ============================================ */
249 1           static void lru_rehash(LRUCache *c) {
250 1           IV new_count = c->bucket_count * 2;
251 1           IV new_mask = new_count - 1;
252             LRUEntry **new_buckets;
253             LRUEntry *e;
254              
255 1 50         Newxz(new_buckets, new_count, LRUEntry*);
256              
257             /* Walk LRU list to re-insert all entries */
258 97 100         for (e = c->head; e; e = e->next) {
259 96           IV idx = e->hash & new_mask;
260 96           e->hash_next = new_buckets[idx];
261 96           new_buckets[idx] = e;
262             }
263              
264 1           Safefree(c->buckets);
265 1           c->buckets = new_buckets;
266 1           c->bucket_count = new_count;
267 1           c->bucket_mask = new_mask;
268 1           c->rehash_threshold = (new_count * LRU_LOAD_NUMER) / LRU_LOAD_DENOM;
269 1           }
270              
271             /* ============================================
272             Power-of-2 helper
273             ============================================ */
274 115           static IV next_pow2(IV n) {
275 115           n--;
276 115           n |= n >> 1; n |= n >> 2;
277 115           n |= n >> 4; n |= n >> 8;
278 115           n |= n >> 16;
279 115           n++;
280 115           return n < 16 ? 16 : n;
281             }
282              
283             /* ============================================
284             Eviction — removes LRU tail entry
285             ============================================ */
286 586           static void lru_evict(pTHX_ LRUCache *c) {
287 586           LRUEntry *victim = c->tail;
288 586 50         if (UNLIKELY(!victim)) return;
289 586           lru_unlink(c, victim);
290 586           hash_remove(c, victim);
291 586           entry_recycle(aTHX_ c, victim);
292 586           c->size--;
293             }
294              
295             /* ============================================
296             Core internal operations (#3 PERL_HASH,
297             #7 SvPV_const, #10 PERL_STATIC_INLINE)
298             ============================================ */
299              
300 6563           PERL_STATIC_INLINE SV* lru_get_promote(pTHX_ LRUCache *c,
301             const char *kpv, STRLEN klen)
302             {
303             U32 hash;
304             LRUEntry *e;
305 6563 50         PERL_HASH(hash, kpv, klen);
306 6563           e = hash_find(c, kpv, klen, hash);
307 6563 100         if (LIKELY(e != NULL)) {
308 5560           lru_promote(c, e);
309 5560           return e->value;
310             }
311 1003           return NULL;
312             }
313              
314 2403           PERL_STATIC_INLINE SV* lru_peek_internal(pTHX_ LRUCache *c,
315             const char *kpv, STRLEN klen)
316             {
317             U32 hash;
318             LRUEntry *e;
319 2403 50         PERL_HASH(hash, kpv, klen);
320 2403           e = hash_find(c, kpv, klen, hash);
321 2403 50         if (LIKELY(e != NULL))
322 2403           return e->value;
323 0           return NULL;
324             }
325              
326 11186           static void lru_set_internal(pTHX_ LRUCache *c, const char *kpv,
327             STRLEN klen, SV *value)
328             {
329             U32 hash;
330             LRUEntry *e;
331 11186 50         PERL_HASH(hash, kpv, klen);
332 11186           e = hash_find(c, kpv, klen, hash);
333              
334 11186 100         if (e) {
335             /* Update existing — reuse SV body in-place (#13) */
336 5404           sv_setsv(e->value, value);
337 5404           lru_promote(c, e);
338             } else {
339             /* Evict if at capacity */
340 5782 100         if (c->size >= c->capacity)
341 586           lru_evict(aTHX_ c);
342              
343             /* Rehash if load factor exceeded */
344 5782 100         if (UNLIKELY(c->size >= c->rehash_threshold))
345 1           lru_rehash(c);
346              
347             /* Allocate entry with inline key (#2) — no key SV needed */
348 5782           e = entry_alloc(c, (U32)klen);
349 5782           e->hash = hash;
350 5782           e->klen = (U32)klen;
351 5782           e->value = newSVsv(value);
352 5782           e->prev = e->next = e->hash_next = NULL;
353 5782           memcpy(e->key, kpv, klen);
354 5782           e->key[klen] = '\0';
355              
356 5782           hash_insert(c, e);
357 5782           lru_push_front(c, e);
358 5782           c->size++;
359             }
360 11186           }
361              
362 6436           PERL_STATIC_INLINE bool lru_exists_internal(pTHX_ LRUCache *c,
363             const char *kpv, STRLEN klen)
364             {
365             U32 hash;
366 6436 50         PERL_HASH(hash, kpv, klen);
367 6436           return hash_find(c, kpv, klen, hash) != NULL;
368             }
369              
370             /* ============================================
371             Method-style custom op implementations
372             ============================================ */
373              
374 0           static OP* pp_lru_get(pTHX) {
375 0           dSP;
376 0           SV *key_sv = POPs;
377 0           SV *cache_sv = POPs;
378 0           LRUCache *c = get_lru_cache(aTHX_ cache_sv);
379             STRLEN klen;
380 0           const char *kpv = SvPV_const(key_sv, klen);
381 0           SV *val = lru_get_promote(aTHX_ c, kpv, klen);
382 0 0         PUSHs(val ? val : &PL_sv_undef);
383 0           RETURN;
384             }
385              
386 0           static OP* pp_lru_set(pTHX) {
387 0           dSP;
388 0           SV *value = POPs;
389 0           SV *key_sv = POPs;
390 0           SV *cache_sv = POPs;
391 0           LRUCache *c = get_lru_cache(aTHX_ cache_sv);
392             STRLEN klen;
393 0           const char *kpv = SvPV_const(key_sv, klen);
394 0           lru_set_internal(aTHX_ c, kpv, klen, value);
395 0           RETURN;
396             }
397              
398 0           static OP* pp_lru_exists(pTHX) {
399 0           dSP;
400 0           SV *key_sv = POPs;
401 0           SV *cache_sv = POPs;
402 0           LRUCache *c = get_lru_cache(aTHX_ cache_sv);
403             STRLEN klen;
404 0           const char *kpv = SvPV_const(key_sv, klen);
405 0 0         PUSHs(lru_exists_internal(aTHX_ c, kpv, klen) ? &PL_sv_yes : &PL_sv_no);
406 0           RETURN;
407             }
408              
409             /* ============================================
410             Function-style custom ops (fastest path)
411             ============================================ */
412              
413 2429           static OP* pp_lru_func_get(pTHX) {
414 2429           dSP;
415 2429           SV *key_sv = TOPs;
416 2429           SV *cache_sv = TOPm1s;
417 2429           LRUCache *c = get_lru_cache(aTHX_ cache_sv);
418             STRLEN klen;
419 2429           const char *kpv = SvPV_const(key_sv, klen);
420 2429           SV *val = lru_get_promote(aTHX_ c, kpv, klen);
421 2429           SP--;
422 2429 100         SETs(val ? val : &PL_sv_undef);
423 2429           RETURN;
424             }
425              
426 2076           static OP* pp_lru_func_set(pTHX) {
427 2076           dSP;
428 2076           SV *value = TOPs;
429 2076           SV *key_sv = TOPm1s;
430 2076           SV *cache_sv = *(SP - 2);
431 2076           LRUCache *c = get_lru_cache(aTHX_ cache_sv);
432             STRLEN klen;
433 2076           const char *kpv = SvPV_const(key_sv, klen);
434 2076           lru_set_internal(aTHX_ c, kpv, klen, value);
435 2076           *(SP - 2) = value;
436 2076           SP -= 2;
437 2076           RETURN;
438             }
439              
440 2404           static OP* pp_lru_func_exists(pTHX) {
441 2404           dSP;
442 2404           SV *key_sv = TOPs;
443 2404           SV *cache_sv = TOPm1s;
444 2404           LRUCache *c = get_lru_cache(aTHX_ cache_sv);
445             STRLEN klen;
446 2404           const char *kpv = SvPV_const(key_sv, klen);
447 2404           SP--;
448 2404 100         SETs(lru_exists_internal(aTHX_ c, kpv, klen) ? &PL_sv_yes : &PL_sv_no);
449 2404           RETURN;
450             }
451              
452 1402           static OP* pp_lru_func_peek(pTHX) {
453 1402           dSP;
454 1402           SV *key_sv = TOPs;
455 1402           SV *cache_sv = TOPm1s;
456 1402           LRUCache *c = get_lru_cache(aTHX_ cache_sv);
457             STRLEN klen;
458 1402           const char *kpv = SvPV_const(key_sv, klen);
459 1402           SV *val = lru_peek_internal(aTHX_ c, kpv, klen);
460 1402           SP--;
461 1402 50         SETs(val ? val : &PL_sv_undef);
462 1402           RETURN;
463             }
464              
465             /* Delete: combined find+remove (#6) + avoid sv_mortalcopy (#4) */
466 402           static OP* pp_lru_func_delete(pTHX) {
467 402           dSP;
468 402           SV *key_sv = TOPs;
469 402           SV *cache_sv = TOPm1s;
470 402           LRUCache *c = get_lru_cache(aTHX_ cache_sv);
471             STRLEN klen;
472 402           const char *kpv = SvPV_const(key_sv, klen);
473             U32 hash;
474             LRUEntry *e;
475              
476 402 50         PERL_HASH(hash, kpv, klen);
477 402           e = hash_find_and_remove(c, kpv, klen, hash);
478              
479 402           SP--;
480 402 100         if (e) {
481 202           SV *val = e->value;
482 202           SvREFCNT_inc_simple_void_NN(val); /* prevent recycle from freeing */
483 202           lru_unlink(c, e);
484 202           entry_recycle(aTHX_ c, e);
485 202           c->size--;
486 202           SETs(sv_2mortal(val));
487             } else {
488 200           SETs(&PL_sv_undef);
489             }
490 402           RETURN;
491             }
492              
493 3           static OP* pp_lru_func_oldest(pTHX) {
494 3           dSP;
495 3           SV *cache_sv = TOPs;
496 3           LRUCache *c = get_lru_cache(aTHX_ cache_sv);
497              
498 3 100         if (c->tail) {
499 2           SETs(sv_2mortal(newSVpvn(c->tail->key, c->tail->klen)));
500 2 50         XPUSHs(c->tail->value);
501 2           RETURN;
502             }
503 1           POPs;
504 1           RETURN;
505             }
506              
507 2           static OP* pp_lru_func_newest(pTHX) {
508 2           dSP;
509 2           SV *cache_sv = TOPs;
510 2           LRUCache *c = get_lru_cache(aTHX_ cache_sv);
511              
512 2 100         if (c->head) {
513 1           SETs(sv_2mortal(newSVpvn(c->head->key, c->head->klen)));
514 1 50         XPUSHs(c->head->value);
515 1           RETURN;
516             }
517 1           POPs;
518 1           RETURN;
519             }
520              
521             /* ============================================
522             Call checkers (replace entersub with custom ops)
523             ============================================ */
524              
525             typedef OP* (*lru_ppfunc)(pTHX);
526              
527 150           static bool lru_op_is_dollar_underscore(pTHX_ OP *op) {
528 150 50         if (!op) return FALSE;
529 150 100         if (op->op_type == OP_RV2SV) {
530 3           OP *gvop = cUNOPx(op)->op_first;
531 3 50         if (gvop && gvop->op_type == OP_GV) {
    50          
532 3           GV *gv = cGVOPx_gv(gvop);
533 3 50         if (gv && GvNAMELEN(gv) == 1 && GvNAME(gv)[0] == '_')
    50          
    50          
534 3           return TRUE;
535             }
536             }
537 147           return FALSE;
538             }
539              
540             /* 2-arg call checker: lru_get, lru_exists, lru_peek, lru_delete */
541 38           static OP* lru_func_call_checker_2arg(pTHX_ OP *entersubop, GV *namegv, SV *ckobj) {
542 38           lru_ppfunc ppfunc = (lru_ppfunc)SvIVX(ckobj);
543             OP *pushop, *cvop, *cacheop, *keyop;
544             OP *newop;
545              
546             PERL_UNUSED_ARG(namegv);
547              
548 38           pushop = cUNOPx(entersubop)->op_first;
549 38 50         if (!OpHAS_SIBLING(pushop))
550 38           pushop = cUNOPx(pushop)->op_first;
551              
552 38 50         cacheop = OpSIBLING(pushop);
553 38 50         if (!cacheop) return entersubop;
554              
555 38 50         keyop = OpSIBLING(cacheop);
556 38 50         if (!keyop) return entersubop;
557              
558 38 50         cvop = OpSIBLING(keyop);
559 38 50         if (!cvop) return entersubop;
560              
561 38 50         if (OpSIBLING(keyop) != cvop) return entersubop;
    50          
562              
563 76           if (lru_op_is_dollar_underscore(aTHX_ cacheop) ||
564 38           lru_op_is_dollar_underscore(aTHX_ keyop))
565 3           return entersubop;
566              
567 35           OpMORESIB_set(pushop, cvop);
568 35           OpLASTSIB_set(keyop, NULL);
569 35           OpLASTSIB_set(cacheop, NULL);
570              
571 35           newop = newBINOP(OP_NULL, 0, cacheop, keyop);
572 35           newop->op_type = OP_CUSTOM;
573 35           newop->op_ppaddr = ppfunc;
574              
575 35           op_free(entersubop);
576 35           return newop;
577             }
578              
579             /* 3-arg call checker: lru_set */
580 23           static OP* lru_func_call_checker_3arg(pTHX_ OP *entersubop, GV *namegv, SV *ckobj) {
581 23           lru_ppfunc ppfunc = (lru_ppfunc)SvIVX(ckobj);
582             OP *pushop, *cvop, *cacheop, *keyop, *valop;
583             OP *innerop, *newop;
584              
585             PERL_UNUSED_ARG(namegv);
586              
587 23           pushop = cUNOPx(entersubop)->op_first;
588 23 50         if (!OpHAS_SIBLING(pushop))
589 23           pushop = cUNOPx(pushop)->op_first;
590              
591 23 50         cacheop = OpSIBLING(pushop);
592 23 50         if (!cacheop) return entersubop;
593              
594 23 50         keyop = OpSIBLING(cacheop);
595 23 50         if (!keyop) return entersubop;
596              
597 23 50         valop = OpSIBLING(keyop);
598 23 50         if (!valop) return entersubop;
599              
600 23 50         cvop = OpSIBLING(valop);
601 23 50         if (!cvop) return entersubop;
602              
603 23 50         if (OpSIBLING(valop) != cvop) return entersubop;
    50          
604              
605 46           if (lru_op_is_dollar_underscore(aTHX_ cacheop) ||
606 46 50         lru_op_is_dollar_underscore(aTHX_ keyop) ||
607 23           lru_op_is_dollar_underscore(aTHX_ valop))
608 0           return entersubop;
609              
610 23           OpMORESIB_set(pushop, cvop);
611 23           OpLASTSIB_set(cacheop, NULL);
612 23           OpLASTSIB_set(keyop, NULL);
613 23           OpLASTSIB_set(valop, NULL);
614              
615 23           innerop = newBINOP(OP_NULL, 0, cacheop, keyop);
616              
617 23           newop = newBINOP(OP_NULL, 0, innerop, valop);
618 23           newop->op_type = OP_CUSTOM;
619 23           newop->op_ppaddr = ppfunc;
620              
621 23           op_free(entersubop);
622 23           return newop;
623             }
624              
625             /* 1-arg call checker: lru_oldest, lru_newest */
626 5           static OP* lru_func_call_checker_1arg(pTHX_ OP *entersubop, GV *namegv, SV *ckobj) {
627 5           lru_ppfunc ppfunc = (lru_ppfunc)SvIVX(ckobj);
628             OP *pushop, *cvop, *cacheop;
629             OP *newop;
630              
631             PERL_UNUSED_ARG(namegv);
632              
633 5           pushop = cUNOPx(entersubop)->op_first;
634 5 50         if (!OpHAS_SIBLING(pushop))
635 5           pushop = cUNOPx(pushop)->op_first;
636              
637 5 50         cacheop = OpSIBLING(pushop);
638 5 50         if (!cacheop) return entersubop;
639              
640 5 50         cvop = OpSIBLING(cacheop);
641 5 50         if (!cvop) return entersubop;
642              
643 5 50         if (OpSIBLING(cacheop) != cvop) return entersubop;
    50          
644              
645 5 50         if (lru_op_is_dollar_underscore(aTHX_ cacheop))
646 0           return entersubop;
647              
648 5           OpMORESIB_set(pushop, cvop);
649 5           OpLASTSIB_set(cacheop, NULL);
650              
651 5           newop = newUNOP(OP_NULL, 0, cacheop);
652 5           newop->op_type = OP_CUSTOM;
653 5           newop->op_ppaddr = ppfunc;
654              
655 5           op_free(entersubop);
656 5           return newop;
657             }
658              
659             /* No-op fallback checker */
660 0           static OP* lru_func_call_checker(pTHX_ OP *entersubop, GV *namegv, SV *ckobj) {
661             PERL_UNUSED_ARG(namegv);
662             PERL_UNUSED_ARG(ckobj);
663 0           return entersubop;
664             }
665              
666             /* ============================================
667             Install helpers for function-style accessors
668             ============================================ */
669              
670 0           static void install_lru_func(pTHX_ const char *pkg, const char *name,
671             XSUBADDR_t xsub, lru_ppfunc ppfunc)
672             {
673             char full_name[256];
674             CV *cv;
675             SV *ckobj;
676              
677             PERL_UNUSED_ARG(ppfunc);
678              
679 0           snprintf(full_name, sizeof(full_name), "%s::%s", pkg, name);
680 0           cv = newXS(full_name, xsub, __FILE__);
681              
682 0           ckobj = newSViv(PTR2IV(ppfunc));
683 0           cv_set_call_checker(cv, lru_func_call_checker, ckobj);
684 0           }
685              
686 24           static void install_lru_func_2arg(pTHX_ const char *pkg, const char *name,
687             XSUBADDR_t xsub, lru_ppfunc ppfunc)
688             {
689             char full_name[256];
690             CV *cv;
691             SV *ckobj;
692              
693 24           snprintf(full_name, sizeof(full_name), "%s::%s", pkg, name);
694 24           cv = newXS(full_name, xsub, __FILE__);
695              
696 24           ckobj = newSViv(PTR2IV(ppfunc));
697 24           cv_set_call_checker(cv, lru_func_call_checker_2arg, ckobj);
698 24           }
699              
700 6           static void install_lru_func_3arg(pTHX_ const char *pkg, const char *name,
701             XSUBADDR_t xsub, lru_ppfunc ppfunc)
702             {
703             char full_name[256];
704             CV *cv;
705             SV *ckobj;
706              
707 6           snprintf(full_name, sizeof(full_name), "%s::%s", pkg, name);
708 6           cv = newXS(full_name, xsub, __FILE__);
709              
710 6           ckobj = newSViv(PTR2IV(ppfunc));
711 6           cv_set_call_checker(cv, lru_func_call_checker_3arg, ckobj);
712 6           }
713              
714 12           static void install_lru_func_1arg(pTHX_ const char *pkg, const char *name,
715             XSUBADDR_t xsub, lru_ppfunc ppfunc)
716             {
717             char full_name[256];
718             CV *cv;
719             SV *ckobj;
720              
721 12           snprintf(full_name, sizeof(full_name), "%s::%s", pkg, name);
722 12           cv = newXS(full_name, xsub, __FILE__);
723              
724 12           ckobj = newSViv(PTR2IV(ppfunc));
725 12           cv_set_call_checker(cv, lru_func_call_checker_1arg, ckobj);
726 12           }
727              
728             /* ============================================
729             XS fallbacks for function-style accessors
730             ============================================ */
731              
732 6           XS_EXTERNAL(XS_LRU__Cache_func_get) {
733 6           dXSARGS;
734 6 50         if (items != 2) croak("Usage: lru_get($cache, $key)");
735             {
736 6           LRUCache *c = get_lru_cache(aTHX_ ST(0));
737             STRLEN klen;
738 6           const char *kpv = SvPV_const(ST(1), klen);
739 6           SV *val = lru_get_promote(aTHX_ c, kpv, klen);
740 6 50         if (val) { ST(0) = val; XSRETURN(1); }
741             }
742 0           XSRETURN_UNDEF;
743             }
744              
745 0           XS_EXTERNAL(XS_LRU__Cache_func_set) {
746 0           dXSARGS;
747 0 0         if (items != 3) croak("Usage: lru_set($cache, $key, $value)");
748             {
749 0           LRUCache *c = get_lru_cache(aTHX_ ST(0));
750             STRLEN klen;
751 0           const char *kpv = SvPV_const(ST(1), klen);
752 0           lru_set_internal(aTHX_ c, kpv, klen, ST(2));
753 0           ST(0) = ST(2);
754             }
755 0           XSRETURN(1);
756             }
757              
758 4           XS_EXTERNAL(XS_LRU__Cache_func_exists) {
759 4           dXSARGS;
760 4 50         if (items != 2) croak("Usage: lru_exists($cache, $key)");
761             {
762 4           LRUCache *c = get_lru_cache(aTHX_ ST(0));
763             STRLEN klen;
764 4           const char *kpv = SvPV_const(ST(1), klen);
765 4 100         if (lru_exists_internal(aTHX_ c, kpv, klen)) XSRETURN_YES;
766             }
767 2           XSRETURN_NO;
768             }
769              
770 0           XS_EXTERNAL(XS_LRU__Cache_func_peek) {
771 0           dXSARGS;
772 0 0         if (items != 2) croak("Usage: lru_peek($cache, $key)");
773             {
774 0           LRUCache *c = get_lru_cache(aTHX_ ST(0));
775             STRLEN klen;
776 0           const char *kpv = SvPV_const(ST(1), klen);
777 0           SV *val = lru_peek_internal(aTHX_ c, kpv, klen);
778 0 0         if (val) { ST(0) = val; XSRETURN(1); }
779             }
780 0           XSRETURN_UNDEF;
781             }
782              
783             /* Delete: combined find+remove (#6) + avoid sv_mortalcopy (#4) */
784 0           XS_EXTERNAL(XS_LRU__Cache_func_delete) {
785 0           dXSARGS;
786 0 0         if (items != 2) croak("Usage: lru_delete($cache, $key)");
787             {
788 0           LRUCache *c = get_lru_cache(aTHX_ ST(0));
789             STRLEN klen;
790 0           const char *kpv = SvPV_const(ST(1), klen);
791             U32 hash;
792             LRUEntry *e;
793              
794 0 0         PERL_HASH(hash, kpv, klen);
795 0           e = hash_find_and_remove(c, kpv, klen, hash);
796 0 0         if (e) {
797 0           SV *val = e->value;
798 0           SvREFCNT_inc_simple_void_NN(val);
799 0           lru_unlink(c, e);
800 0           entry_recycle(aTHX_ c, e);
801 0           c->size--;
802 0           ST(0) = sv_2mortal(val);
803 0           XSRETURN(1);
804             }
805             }
806 0           XSRETURN_UNDEF;
807             }
808              
809 0           XS_EXTERNAL(XS_LRU__Cache_func_oldest) {
810 0           dXSARGS;
811 0 0         if (items != 1) croak("Usage: lru_oldest($cache)");
812 0           SP -= items;
813             {
814 0           LRUCache *c = get_lru_cache(aTHX_ ST(0));
815 0 0         if (c->tail) {
816 0 0         EXTEND(SP, 2);
817 0           PUSHs(sv_2mortal(newSVpvn(c->tail->key, c->tail->klen)));
818 0           PUSHs(c->tail->value);
819 0           XSRETURN(2);
820             }
821             }
822 0           XSRETURN_EMPTY;
823             }
824              
825 0           XS_EXTERNAL(XS_LRU__Cache_func_newest) {
826 0           dXSARGS;
827 0 0         if (items != 1) croak("Usage: lru_newest($cache)");
828 0           SP -= items;
829             {
830 0           LRUCache *c = get_lru_cache(aTHX_ ST(0));
831 0 0         if (c->head) {
832 0 0         EXTEND(SP, 2);
833 0           PUSHs(sv_2mortal(newSVpvn(c->head->key, c->head->klen)));
834 0           PUSHs(c->head->value);
835 0           XSRETURN(2);
836             }
837             }
838 0           XSRETURN_EMPTY;
839             }
840              
841             /* ============================================
842             Import handler
843             ============================================ */
844 12           XS_EXTERNAL(XS_LRU__Cache_import) {
845 12           dXSARGS;
846             const char *pkg;
847             int i;
848 12           bool want_import = FALSE;
849              
850 12 50         pkg = CopSTASHPV(PL_curcop);
    50          
    50          
    50          
    0          
    50          
    50          
851              
852 18 100         for (i = 1; i < items; i++) {
853             STRLEN len;
854 6           const char *arg = SvPV_const(ST(i), len);
855 6 50         if (len == 6 && strEQ(arg, "import")) {
    50          
856 6           want_import = TRUE;
857             }
858             }
859              
860 12 100         if (want_import) {
861 6           install_lru_func_2arg(aTHX_ pkg, "lru_get", XS_LRU__Cache_func_get, pp_lru_func_get);
862 6           install_lru_func_2arg(aTHX_ pkg, "lru_exists", XS_LRU__Cache_func_exists, pp_lru_func_exists);
863 6           install_lru_func_2arg(aTHX_ pkg, "lru_peek", XS_LRU__Cache_func_peek, pp_lru_func_peek);
864 6           install_lru_func_2arg(aTHX_ pkg, "lru_delete", XS_LRU__Cache_func_delete, pp_lru_func_delete);
865 6           install_lru_func_3arg(aTHX_ pkg, "lru_set", XS_LRU__Cache_func_set, pp_lru_func_set);
866 6           install_lru_func_1arg(aTHX_ pkg, "lru_oldest", XS_LRU__Cache_func_oldest, pp_lru_func_oldest);
867 6           install_lru_func_1arg(aTHX_ pkg, "lru_newest", XS_LRU__Cache_func_newest, pp_lru_func_newest);
868             }
869              
870 12           XSRETURN_EMPTY;
871             }
872              
873             /* ============================================
874             Destructor — frees active entries + freelist
875             ============================================ */
876 115           static int lru_cache_free(pTHX_ SV *sv, MAGIC *mg) {
877 115           LRUCache *c = (LRUCache*)mg->mg_ptr;
878             LRUEntry *e, *next;
879              
880             PERL_UNUSED_ARG(sv);
881              
882             /* Free active entries (walk LRU list) */
883 1046 100         for (e = c->head; e; e = next) {
884 931           next = e->next;
885 931 50         if (e->value) SvREFCNT_dec(e->value);
886 931           Safefree(e);
887             }
888              
889             /* Free pooled freelist entries */
890 429 100         for (e = c->freelist; e; e = next) {
891 314           next = e->hash_next;
892 314           Safefree(e);
893             }
894              
895 115           Safefree(c->buckets);
896 115           Safefree(c);
897 115           return 0;
898             }
899              
900             /* ============================================
901             Constructor
902             ============================================ */
903 115           XS_EXTERNAL(XS_LRU__Cache_new) {
904 115           dXSARGS;
905             LRUCache *c;
906             SV *obj_sv, *rv;
907             IV capacity;
908             HV *stash;
909              
910 115 50         if (items < 1 || items > 2)
    50          
911 0           croak("Usage: LRU::Cache::new(capacity)");
912              
913 115 100         capacity = SvIV(items == 2 ? ST(1) : ST(0));
914 115 50         if (capacity < 1) croak("Capacity must be positive");
915              
916 115           Newxz(c, 1, LRUCache);
917 115           c->capacity = capacity;
918 115           c->size = 0;
919 115           c->bucket_count = next_pow2(capacity); /* Start at capacity, grow via rehash (#11) */
920 115           c->bucket_mask = c->bucket_count - 1;
921 115           c->rehash_threshold = (c->bucket_count * LRU_LOAD_NUMER) / LRU_LOAD_DENOM;
922 115           c->freelist = NULL;
923 115           c->freelist_count = 0;
924 115 50         Newxz(c->buckets, c->bucket_count, LRUEntry*);
925 115           c->head = c->tail = NULL;
926              
927 115           obj_sv = newSV(0);
928 115           sv_magicext(obj_sv, NULL, PERL_MAGIC_ext, &lru_cache_vtbl, (char*)c, 0);
929              
930 115           rv = newRV_noinc(obj_sv);
931 115           stash = gv_stashpvn("LRU::Cache", 10, GV_ADD);
932 115           sv_bless(rv, stash);
933              
934 115           ST(0) = rv;
935 115           XSRETURN(1);
936             }
937              
938             /* ============================================
939             Method-style XS functions
940             ============================================ */
941              
942 9110           XS_EXTERNAL(XS_LRU__Cache_set) {
943 9110           dXSARGS;
944 9110 50         if (items != 3) croak("Usage: $cache->set($key, $value)");
945             {
946 9110           LRUCache *c = get_lru_cache(aTHX_ ST(0));
947             STRLEN klen;
948 9110           const char *kpv = SvPV_const(ST(1), klen);
949 9110           lru_set_internal(aTHX_ c, kpv, klen, ST(2));
950             }
951 9110           XSRETURN_EMPTY;
952             }
953              
954 4128           XS_EXTERNAL(XS_LRU__Cache_get) {
955 4128           dXSARGS;
956 4128 50         if (items != 2) croak("Usage: $cache->get($key)");
957             {
958 4128           LRUCache *c = get_lru_cache(aTHX_ ST(0));
959             STRLEN klen;
960 4128           const char *kpv = SvPV_const(ST(1), klen);
961 4128           SV *val = lru_get_promote(aTHX_ c, kpv, klen);
962 4128 100         if (val) {
963 4126           ST(0) = val;
964 4126           XSRETURN(1);
965             }
966             }
967 2           XSRETURN_UNDEF;
968             }
969              
970 1001           XS_EXTERNAL(XS_LRU__Cache_peek) {
971 1001           dXSARGS;
972 1001 50         if (items != 2) croak("Usage: $cache->peek($key)");
973             {
974 1001           LRUCache *c = get_lru_cache(aTHX_ ST(0));
975             STRLEN klen;
976 1001           const char *kpv = SvPV_const(ST(1), klen);
977 1001           SV *val = lru_peek_internal(aTHX_ c, kpv, klen);
978 1001 50         if (val) {
979 1001           ST(0) = val;
980 1001           XSRETURN(1);
981             }
982             }
983 0           XSRETURN_UNDEF;
984             }
985              
986 4028           XS_EXTERNAL(XS_LRU__Cache_exists) {
987 4028           dXSARGS;
988 4028 50         if (items != 2) croak("Usage: $cache->exists($key)");
989             {
990 4028           LRUCache *c = get_lru_cache(aTHX_ ST(0));
991             STRLEN klen;
992 4028           const char *kpv = SvPV_const(ST(1), klen);
993 4028 100         if (lru_exists_internal(aTHX_ c, kpv, klen))
994 2014           XSRETURN_YES;
995             }
996 2014           XSRETURN_NO;
997             }
998              
999             /* Delete: combined find+remove (#6) + avoid sv_mortalcopy (#4) */
1000 106           XS_EXTERNAL(XS_LRU__Cache_delete) {
1001 106           dXSARGS;
1002 106 50         if (items != 2) croak("Usage: $cache->delete($key)");
1003             {
1004 106           LRUCache *c = get_lru_cache(aTHX_ ST(0));
1005             STRLEN klen;
1006 106           const char *kpv = SvPV_const(ST(1), klen);
1007             U32 hash;
1008             LRUEntry *e;
1009              
1010 106 50         PERL_HASH(hash, kpv, klen);
1011 106           e = hash_find_and_remove(c, kpv, klen, hash);
1012 106 100         if (e) {
1013 56           SV *val = e->value;
1014 56           SvREFCNT_inc_simple_void_NN(val);
1015 56           lru_unlink(c, e);
1016 56           entry_recycle(aTHX_ c, e);
1017 56           c->size--;
1018 56           ST(0) = sv_2mortal(val);
1019 56           XSRETURN(1);
1020             }
1021             }
1022 50           XSRETURN_UNDEF;
1023             }
1024              
1025 2011           XS_EXTERNAL(XS_LRU__Cache_size) {
1026 2011           dXSARGS;
1027 2011 50         if (items != 1) croak("Usage: $cache->size");
1028             {
1029 2011           LRUCache *c = get_lru_cache(aTHX_ ST(0));
1030 2011           XSRETURN_IV(c->size);
1031             }
1032             }
1033              
1034 1002           XS_EXTERNAL(XS_LRU__Cache_capacity) {
1035 1002           dXSARGS;
1036 1002 50         if (items != 1) croak("Usage: $cache->capacity");
1037             {
1038 1002           LRUCache *c = get_lru_cache(aTHX_ ST(0));
1039 1002           XSRETURN_IV(c->capacity);
1040             }
1041             }
1042              
1043             /* Clear: recycles entries to freelist (#8) */
1044 203           XS_EXTERNAL(XS_LRU__Cache_clear) {
1045 203           dXSARGS;
1046 203 50         if (items != 1) croak("Usage: $cache->clear");
1047             {
1048 203           LRUCache *c = get_lru_cache(aTHX_ ST(0));
1049 203           LRUEntry *e = c->head;
1050              
1051 4210 100         while (e) {
1052 4007           LRUEntry *next = e->next;
1053 4007           entry_recycle(aTHX_ c, e);
1054 4007           e = next;
1055             }
1056              
1057 203 50         Zero(c->buckets, c->bucket_count, LRUEntry*);
1058 203           c->head = c->tail = NULL;
1059 203           c->size = 0;
1060             }
1061 203           XSRETURN_EMPTY;
1062             }
1063              
1064             /* Keys: creates mortal SVs from inline key bytes */
1065 203           XS_EXTERNAL(XS_LRU__Cache_keys) {
1066 203           dXSARGS;
1067 203 50         if (items != 1) croak("Usage: $cache->keys");
1068 203           SP -= items;
1069             {
1070 203           LRUCache *c = get_lru_cache(aTHX_ ST(0));
1071             LRUEntry *e;
1072 203 50         EXTEND(SP, c->size);
    50          
1073 4212 100         for (e = c->head; e; e = e->next)
1074 4009           PUSHs(sv_2mortal(newSVpvn(e->key, e->klen)));
1075 203           XSRETURN(c->size);
1076             }
1077             }
1078              
1079 8           XS_EXTERNAL(XS_LRU__Cache_oldest) {
1080 8           dXSARGS;
1081 8 50         if (items != 1) croak("Usage: $cache->oldest");
1082 8           SP -= items;
1083             {
1084 8           LRUCache *c = get_lru_cache(aTHX_ ST(0));
1085 8 100         if (c->tail) {
1086 6 50         EXTEND(SP, 2);
1087 6           PUSHs(sv_2mortal(newSVpvn(c->tail->key, c->tail->klen)));
1088 6           PUSHs(c->tail->value);
1089 6           XSRETURN(2);
1090             }
1091             }
1092 2           XSRETURN_EMPTY;
1093             }
1094              
1095 9           XS_EXTERNAL(XS_LRU__Cache_newest) {
1096 9           dXSARGS;
1097 9 50         if (items != 1) croak("Usage: $cache->newest");
1098 9           SP -= items;
1099             {
1100 9           LRUCache *c = get_lru_cache(aTHX_ ST(0));
1101 9 100         if (c->head) {
1102 7 50         EXTEND(SP, 2);
1103 7           PUSHs(sv_2mortal(newSVpvn(c->head->key, c->head->klen)));
1104 7           PUSHs(c->head->value);
1105 7           XSRETURN(2);
1106             }
1107             }
1108 2           XSRETURN_EMPTY;
1109             }
1110              
1111             /* ============================================
1112             Boot function
1113             ============================================ */
1114 11           XS_EXTERNAL(boot_LRU__Cache) {
1115 11           dXSBOOTARGSXSAPIVERCHK;
1116              
1117             /* Register method-style custom ops */
1118 11           XopENTRY_set(&lru_get_xop, xop_name, "lru_get");
1119 11           XopENTRY_set(&lru_get_xop, xop_desc, "lru cache get");
1120 11           Perl_custom_op_register(aTHX_ pp_lru_get, &lru_get_xop);
1121              
1122 11           XopENTRY_set(&lru_set_xop, xop_name, "lru_set");
1123 11           XopENTRY_set(&lru_set_xop, xop_desc, "lru cache set");
1124 11           Perl_custom_op_register(aTHX_ pp_lru_set, &lru_set_xop);
1125              
1126 11           XopENTRY_set(&lru_exists_xop, xop_name, "lru_exists");
1127 11           XopENTRY_set(&lru_exists_xop, xop_desc, "lru cache exists");
1128 11           Perl_custom_op_register(aTHX_ pp_lru_exists, &lru_exists_xop);
1129              
1130             /* Register function-style custom ops */
1131 11           XopENTRY_set(&lru_func_get_xop, xop_name, "lru_func_get");
1132 11           XopENTRY_set(&lru_func_get_xop, xop_desc, "lru function get");
1133 11           Perl_custom_op_register(aTHX_ pp_lru_func_get, &lru_func_get_xop);
1134              
1135 11           XopENTRY_set(&lru_func_set_xop, xop_name, "lru_func_set");
1136 11           XopENTRY_set(&lru_func_set_xop, xop_desc, "lru function set");
1137 11           Perl_custom_op_register(aTHX_ pp_lru_func_set, &lru_func_set_xop);
1138              
1139 11           XopENTRY_set(&lru_func_exists_xop, xop_name, "lru_func_exists");
1140 11           XopENTRY_set(&lru_func_exists_xop, xop_desc, "lru function exists");
1141 11           Perl_custom_op_register(aTHX_ pp_lru_func_exists, &lru_func_exists_xop);
1142              
1143 11           XopENTRY_set(&lru_func_peek_xop, xop_name, "lru_func_peek");
1144 11           XopENTRY_set(&lru_func_peek_xop, xop_desc, "lru function peek");
1145 11           Perl_custom_op_register(aTHX_ pp_lru_func_peek, &lru_func_peek_xop);
1146              
1147 11           XopENTRY_set(&lru_func_delete_xop, xop_name, "lru_func_delete");
1148 11           XopENTRY_set(&lru_func_delete_xop, xop_desc, "lru function delete");
1149 11           Perl_custom_op_register(aTHX_ pp_lru_func_delete, &lru_func_delete_xop);
1150              
1151 11           XopENTRY_set(&lru_func_oldest_xop, xop_name, "lru_func_oldest");
1152 11           XopENTRY_set(&lru_func_oldest_xop, xop_desc, "lru function oldest");
1153 11           Perl_custom_op_register(aTHX_ pp_lru_func_oldest, &lru_func_oldest_xop);
1154              
1155 11           XopENTRY_set(&lru_func_newest_xop, xop_name, "lru_func_newest");
1156 11           XopENTRY_set(&lru_func_newest_xop, xop_desc, "lru function newest");
1157 11           Perl_custom_op_register(aTHX_ pp_lru_func_newest, &lru_func_newest_xop);
1158              
1159             /* Register XS subs */
1160 11           newXS("LRU::Cache::new", XS_LRU__Cache_new, __FILE__);
1161 11           newXS("LRU::Cache::set", XS_LRU__Cache_set, __FILE__);
1162 11           newXS("LRU::Cache::get", XS_LRU__Cache_get, __FILE__);
1163 11           newXS("LRU::Cache::peek", XS_LRU__Cache_peek, __FILE__);
1164 11           newXS("LRU::Cache::exists", XS_LRU__Cache_exists, __FILE__);
1165 11           newXS("LRU::Cache::delete", XS_LRU__Cache_delete, __FILE__);
1166 11           newXS("LRU::Cache::size", XS_LRU__Cache_size, __FILE__);
1167 11           newXS("LRU::Cache::capacity", XS_LRU__Cache_capacity, __FILE__);
1168 11           newXS("LRU::Cache::clear", XS_LRU__Cache_clear, __FILE__);
1169 11           newXS("LRU::Cache::keys", XS_LRU__Cache_keys, __FILE__);
1170 11           newXS("LRU::Cache::oldest", XS_LRU__Cache_oldest, __FILE__);
1171 11           newXS("LRU::Cache::newest", XS_LRU__Cache_newest, __FILE__);
1172 11           newXS("LRU::Cache::import", XS_LRU__Cache_import, __FILE__);
1173              
1174             #if PERL_VERSION_GE(5,22,0)
1175 11           Perl_xs_boot_epilog(aTHX_ ax);
1176             #else
1177             XSRETURN_YES;
1178             #endif
1179 11           }