qr_reader-native 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE-mruby.txt +22 -0
  3. data/LICENSE-spinel.txt +19 -0
  4. data/LICENSE.txt +21 -0
  5. data/README.md +61 -0
  6. data/ext/qr_reader_native/SPINEL_VERSION +1 -0
  7. data/ext/qr_reader_native/extconf.rb +8 -0
  8. data/ext/qr_reader_native/mruby/error.h +13 -0
  9. data/ext/qr_reader_native/mruby/internal.h +145 -0
  10. data/ext/qr_reader_native/mruby/string.h +22 -0
  11. data/ext/qr_reader_native/mruby.h +93 -0
  12. data/ext/qr_reader_native/mruby_shim.h +118 -0
  13. data/ext/qr_reader_native/qr_reader_native.c +13513 -0
  14. data/ext/qr_reader_native/qr_reader_native.h +29 -0
  15. data/ext/qr_reader_native/qr_reader_native_ext.c +598 -0
  16. data/ext/qr_reader_native/re_cased.h +71 -0
  17. data/ext/qr_reader_native/re_compile.c +5784 -0
  18. data/ext/qr_reader_native/re_ctype.h +3540 -0
  19. data/ext/qr_reader_native/re_exec.c +2190 -0
  20. data/ext/qr_reader_native/re_internal.h +671 -0
  21. data/ext/qr_reader_native/re_prop.h +4629 -0
  22. data/ext/qr_reader_native/re_spinel.c +363 -0
  23. data/ext/qr_reader_native/re_spinel.h +22 -0
  24. data/ext/qr_reader_native/re_spinel_utf8.inc +97 -0
  25. data/ext/qr_reader_native/re_utf8.c +156 -0
  26. data/ext/qr_reader_native/sp_alloc.c +1447 -0
  27. data/ext/qr_reader_native/sp_alloc.h +1196 -0
  28. data/ext/qr_reader_native/sp_argf.h +33 -0
  29. data/ext/qr_reader_native/sp_array.c +1052 -0
  30. data/ext/qr_reader_native/sp_array.h +456 -0
  31. data/ext/qr_reader_native/sp_bigint.c +5826 -0
  32. data/ext/qr_reader_native/sp_bigint.h +117 -0
  33. data/ext/qr_reader_native/sp_cold.c +5259 -0
  34. data/ext/qr_reader_native/sp_compat.h +402 -0
  35. data/ext/qr_reader_native/sp_core.c +818 -0
  36. data/ext/qr_reader_native/sp_core.h +56 -0
  37. data/ext/qr_reader_native/sp_crypto.c +877 -0
  38. data/ext/qr_reader_native/sp_crypto.h +169 -0
  39. data/ext/qr_reader_native/sp_dtoa.c +1533 -0
  40. data/ext/qr_reader_native/sp_dtoa.h +29 -0
  41. data/ext/qr_reader_native/sp_encoding_names.h +172 -0
  42. data/ext/qr_reader_native/sp_enum.h +86 -0
  43. data/ext/qr_reader_native/sp_exc.c +1185 -0
  44. data/ext/qr_reader_native/sp_exc.h +186 -0
  45. data/ext/qr_reader_native/sp_exc_ctx.h +56 -0
  46. data/ext/qr_reader_native/sp_fiber.c +889 -0
  47. data/ext/qr_reader_native/sp_fiber.h +162 -0
  48. data/ext/qr_reader_native/sp_fiber_ctx.h +84 -0
  49. data/ext/qr_reader_native/sp_format.c +695 -0
  50. data/ext/qr_reader_native/sp_format.h +83 -0
  51. data/ext/qr_reader_native/sp_gc.c +1793 -0
  52. data/ext/qr_reader_native/sp_gc.h +762 -0
  53. data/ext/qr_reader_native/sp_hash.c +131 -0
  54. data/ext/qr_reader_native/sp_hash.h +131 -0
  55. data/ext/qr_reader_native/sp_inspect.c +215 -0
  56. data/ext/qr_reader_native/sp_inspect.h +128 -0
  57. data/ext/qr_reader_native/sp_io.c +1676 -0
  58. data/ext/qr_reader_native/sp_io.h +272 -0
  59. data/ext/qr_reader_native/sp_iobuffer.c +1014 -0
  60. data/ext/qr_reader_native/sp_iobuffer.h +167 -0
  61. data/ext/qr_reader_native/sp_marshal.c +487 -0
  62. data/ext/qr_reader_native/sp_marshal.h +60 -0
  63. data/ext/qr_reader_native/sp_net.c +1029 -0
  64. data/ext/qr_reader_native/sp_net.h +205 -0
  65. data/ext/qr_reader_native/sp_pack.c +1254 -0
  66. data/ext/qr_reader_native/sp_poly_cold.c +1220 -0
  67. data/ext/qr_reader_native/sp_proc.c +410 -0
  68. data/ext/qr_reader_native/sp_proc.h +327 -0
  69. data/ext/qr_reader_native/sp_process.c +547 -0
  70. data/ext/qr_reader_native/sp_process_status.c +211 -0
  71. data/ext/qr_reader_native/sp_process_status.h +66 -0
  72. data/ext/qr_reader_native/sp_random.c +287 -0
  73. data/ext/qr_reader_native/sp_random.h +52 -0
  74. data/ext/qr_reader_native/sp_range.h +144 -0
  75. data/ext/qr_reader_native/sp_re.c +1379 -0
  76. data/ext/qr_reader_native/sp_re.h +186 -0
  77. data/ext/qr_reader_native/sp_re_names.h +59 -0
  78. data/ext/qr_reader_native/sp_sched.c +4044 -0
  79. data/ext/qr_reader_native/sp_sched.h +295 -0
  80. data/ext/qr_reader_native/sp_slab.c +1507 -0
  81. data/ext/qr_reader_native/sp_str.c +1462 -0
  82. data/ext/qr_reader_native/sp_str.h +306 -0
  83. data/ext/qr_reader_native/sp_str_crypt.c +23 -0
  84. data/ext/qr_reader_native/sp_string.c +92 -0
  85. data/ext/qr_reader_native/sp_string.h +253 -0
  86. data/ext/qr_reader_native/sp_system.c +78 -0
  87. data/ext/qr_reader_native/sp_system.h +18 -0
  88. data/ext/qr_reader_native/sp_time.c +860 -0
  89. data/ext/qr_reader_native/sp_time.h +100 -0
  90. data/ext/qr_reader_native/sp_types.h +292 -0
  91. data/ext/qr_reader_native/spinel_rt.h +19017 -0
  92. data/ext/qr_reader_native/unicase.c +399 -0
  93. data/ext/qr_reader_native/unicase.h +901 -0
  94. data/lib/qr_reader/native.rb +6 -0
  95. metadata +153 -0
@@ -0,0 +1,131 @@
1
+ /* sp_hash.c -- cold CRUD ops for the 4 non-poly-valued typed hashes (see
2
+ * sp_hash.h). None of these types are hot in optcarrot, so the whole
3
+ * surface -- not just materializers -- lives here rather than in
4
+ * spinel_rt.h. sp_gc_alloc / sp_mark_string / sp_str_hash / sp_str_eq /
5
+ * sp_inspect_container are lib-visible. */
6
+ #include "sp_hash.h"
7
+
8
+ void sp_StrIntHash_fin(void*p){sp_StrIntHash*h=(sp_StrIntHash*)p;sp_pl_free(h->keys);sp_pl_free(h->vals);sp_pl_free(h->order);}
9
+ void sp_StrIntHash_scan(void*p){sp_StrIntHash*h=(sp_StrIntHash*)p;for(sp_int i=0;i<h->cap;i++){if(h->keys[i])sp_mark_string(h->keys[i]);}}
10
+ /* default_v is SP_INT_NIL for a hash with no explicit default ({} / {k=>v}),
11
+ so a missing-key `[]` read surfaces Ruby nil (#801). Hash.new(N) sets it to
12
+ N via _new_with_default. Proven-present internal reads use _get on present
13
+ keys, so this only governs the miss path. */
14
+ sp_StrIntHash*sp_StrIntHash_new(void){sp_StrIntHash*h=(sp_StrIntHash*)sp_gc_alloc(sizeof(sp_StrIntHash),sp_StrIntHash_fin,sp_StrIntHash_scan);h->cap=16;h->mask=15;h->keys=(const char**)sp_pl_zalloc((size_t)h->cap*sizeof(const char*));h->vals=(sp_int*)sp_pl_zalloc((size_t)h->cap*sizeof(sp_int));h->order=(const char**)sp_pl_alloc(sizeof(const char*)*h->cap);h->len=0;h->default_v=SP_INT_NIL;return h;}
15
+ sp_StrIntHash*sp_StrIntHash_new_with_default(sp_int d){sp_StrIntHash*h=sp_StrIntHash_new();h->default_v=d;return h;}
16
+ void sp_StrIntHash_grow(sp_StrIntHash*h){SP_GC_ROOT(h); sp_gc_wb((void*)h);sp_int oc=h->cap;const char**ok=h->keys;sp_int*ov=h->vals;h->cap*=2;h->mask=h->cap-1;h->keys=(const char**)sp_pl_zalloc((size_t)h->cap*sizeof(const char*));h->vals=(sp_int*)sp_pl_zalloc((size_t)h->cap*sizeof(sp_int));h->order=(const char**)sp_pl_realloc(h->order,sizeof(const char*)*h->cap);h->len=0;for(sp_int i=0;i<oc;i++){if(ok[i]){sp_int idx=(sp_int)(sp_str_hash(ok[i])&h->mask);while(h->keys[idx])idx=(idx+1)&h->mask;h->keys[idx]=ok[i];h->vals[idx]=ov[i];h->len++;}}sp_pl_free(ok);sp_pl_free(ov);}
17
+ sp_int sp_StrIntHash_get(sp_StrIntHash*h,const char*k){SP_GC_ROOT(h);SP_GC_ROOT_STR(k);if(!h)return 0;sp_int idx=(sp_int)(sp_str_hash(k)&h->mask);while(h->keys[idx]){if(sp_str_eq(h->keys[idx],k))return h->vals[idx];idx=(idx+1)&h->mask;}return h->default_v;}
18
+ /* Issue #801: maybe-missing public `[]` read. Returns default_v on a miss,
19
+ which is SP_INT_NIL (Ruby nil at the value level) for a no-default hash and
20
+ the explicit default for Hash.new(N). Proven-present reads keep using _get. */
21
+ sp_int sp_StrIntHash_get_opt(sp_StrIntHash*h,const char*k){SP_GC_ROOT(h);SP_GC_ROOT_STR(k);if(!h)return SP_INT_NIL;sp_int idx=(sp_int)(sp_str_hash(k)&h->mask);while(h->keys[idx]){if(sp_str_eq(h->keys[idx],k))return h->vals[idx];idx=(idx+1)&h->mask;}return h->default_v;}
22
+ void sp_StrIntHash_set(sp_StrIntHash*h,const char*k,sp_int v){SP_GC_ROOT(h);SP_GC_ROOT_STR(k); if(!k){sp_raise_cls("TypeError","no implicit conversion of nil into String");return;} sp_gc_wb((void*)h);if(h->len*2>=h->cap)sp_StrIntHash_grow(h);sp_int idx=(sp_int)(sp_str_hash(k)&h->mask);while(h->keys[idx]){if(sp_str_eq(h->keys[idx],k)){h->vals[idx]=v;return;}idx=(idx+1)&h->mask;}k=sp_hash_key_str(k);sp_gc_wb((void*)h);h->keys[idx]=k;h->vals[idx]=v;h->order[h->len]=k;h->len++;}
23
+ sp_bool sp_StrIntHash_has_key(sp_StrIntHash*h,const char*k){SP_GC_ROOT(h);SP_GC_ROOT_STR(k);sp_int idx=(sp_int)(sp_str_hash(k)&h->mask);while(h->keys[idx]){if(sp_str_eq(h->keys[idx],k))return TRUE;idx=(idx+1)&h->mask;}return FALSE;}
24
+ /* h.fetch(k, d) in one probe: the value, or d when k is absent (the
25
+ hash's own default does not apply to fetch) */
26
+ sp_int sp_StrIntHash_fetch_or(sp_StrIntHash*h,const char*k,sp_int d){SP_GC_ROOT(h);SP_GC_ROOT_STR(k);if(!h)return d;sp_int idx=(sp_int)(sp_str_hash(k)&h->mask);while(h->keys[idx]){if(sp_str_eq(h->keys[idx],k))return h->vals[idx];idx=(idx+1)&h->mask;}return d;}
27
+ /* Hash#value? -- scan values in insertion order. Issue #738. */
28
+ sp_bool sp_StrIntHash_has_value(sp_StrIntHash*h,sp_int v){if(!h)return FALSE;for(sp_int i=0;i<h->len;i++)if(sp_StrIntHash_get(h,h->order[i])==v)return TRUE;return FALSE;}
29
+ sp_int sp_StrIntHash_length(sp_StrIntHash*h){return h->len;}
30
+ void sp_StrIntHash_delete(sp_StrIntHash*h,const char*k){SP_GC_ROOT(h);SP_GC_ROOT_STR(k); sp_gc_wb((void*)h);sp_int idx=(sp_int)(sp_str_hash(k)&h->mask);while(h->keys[idx]){if(sp_str_eq(h->keys[idx],k)){h->keys[idx]=NULL;h->vals[idx]=0;h->len--;sp_int j=(idx+1)&h->mask;while(h->keys[j]){sp_int nj=(sp_int)(sp_str_hash(h->keys[j])&h->mask);if((j>idx&&(nj<=idx||nj>j))||(j<idx&&nj<=idx&&nj>j)){h->keys[idx]=h->keys[j];h->vals[idx]=h->vals[j];h->keys[j]=NULL;h->vals[j]=0;idx=j;}j=(j+1)&h->mask;}{sp_int oi=0;while(oi<=h->len){if(strcmp(h->order[oi],k)==0){while(oi<h->len){h->order[oi]=h->order[oi+1];oi++;}break;}oi++;}}return;}idx=(idx+1)&h->mask;}}
31
+ sp_StrArray*sp_StrIntHash_keys(sp_StrIntHash*h){SP_GC_ROOT(h);sp_StrArray*a=sp_StrArray_new();SP_GC_ROOT(a);for(sp_int i=0;i<h->len;i++)sp_StrArray_push(a,h->order[i]);return a;}
32
+ sp_IntArray*sp_StrIntHash_values(sp_StrIntHash*h){SP_GC_ROOT(h);sp_IntArray*a=sp_IntArray_new();SP_GC_ROOT(a);for(sp_int i=0;i<h->len;i++)sp_IntArray_push_nilable(a,sp_StrIntHash_get(h,h->order[i]));return a;} /* a nil value is the sentinel */
33
+ sp_StrIntHash*sp_StrIntHash_merge(sp_StrIntHash*a,sp_StrIntHash*b){SP_GC_ROOT(a);SP_GC_ROOT(b);sp_StrIntHash*r=sp_StrIntHash_new();r->default_v=a->default_v;for(sp_int i=0;i<a->len;i++)sp_StrIntHash_set(r,a->order[i],sp_StrIntHash_get(a,a->order[i]));for(sp_int i=0;i<b->len;i++)sp_StrIntHash_set(r,b->order[i],sp_StrIntHash_get(b,b->order[i]));return r;}
34
+ void sp_StrIntHash_update(sp_StrIntHash*a,sp_StrIntHash*b){SP_GC_ROOT(a);SP_GC_ROOT(b);for(sp_int i=0;i<b->len;i++)sp_StrIntHash_set(a,b->order[i],sp_StrIntHash_get(b,b->order[i]));}
35
+ sp_StrIntHash*sp_StrIntHash_dup(sp_StrIntHash*h){SP_GC_ROOT(h);sp_StrIntHash*r=sp_StrIntHash_new();r->default_v=h->default_v;for(sp_int i=0;i<h->len;i++)sp_StrIntHash_set(r,h->order[i],sp_StrIntHash_get(h,h->order[i]));return r;}
36
+ sp_StrIntHash*sp_StrIntHash_replace(sp_StrIntHash*h,sp_StrIntHash*o){SP_GC_ROOT(h);SP_GC_ROOT(o); sp_gc_wb((void*)h);if(!h)return h;for(sp_int i=0;i<h->cap;i++)h->keys[i]=NULL;h->len=0;if(o)for(sp_int i=0;i<o->len;i++)sp_StrIntHash_set(h,o->order[i],sp_StrIntHash_get(o,o->order[i]));return h;}
37
+ void sp_StrIntHash_clear(sp_StrIntHash*h){ sp_gc_wb((void*)h);if(!h)return;for(sp_int i=0;i<h->cap;i++)h->keys[i]=NULL;h->len=0;}
38
+ sp_bool sp_StrIntHash_eq(sp_StrIntHash*a,sp_StrIntHash*b){SP_GC_ROOT(a);SP_GC_ROOT(b);if(!a||!b)return a==b;if(a->len!=b->len)return FALSE;for(sp_int i=0;i<a->len;i++){const char*k=a->order[i];if(!sp_StrIntHash_has_key(b,k))return FALSE;if(sp_StrIntHash_get(a,k)!=sp_StrIntHash_get(b,k))return FALSE;}return TRUE;}
39
+ void sp_StrStrHash_fin(void*p){sp_StrStrHash*h=(sp_StrStrHash*)p;sp_pl_free(h->keys);sp_pl_free(h->vals);sp_pl_free(h->order);}
40
+ void sp_StrStrHash_scan(void*p){sp_StrStrHash*h=(sp_StrStrHash*)p;for(sp_int i=0;i<h->cap;i++){if(h->keys[i]){sp_mark_string(h->keys[i]);sp_mark_string(h->vals[i]);}}if(h->default_v)sp_mark_string(h->default_v);}
41
+ sp_StrStrHash*sp_StrStrHash_new(void){sp_StrStrHash*h=(sp_StrStrHash*)sp_gc_alloc(sizeof(sp_StrStrHash),sp_StrStrHash_fin,sp_StrStrHash_scan);h->cap=16;h->mask=15;h->keys=(const char**)sp_pl_zalloc((size_t)h->cap*sizeof(const char*));h->vals=(const char**)sp_pl_zalloc((size_t)h->cap*sizeof(const char*));h->order=(const char**)sp_pl_alloc(sizeof(const char*)*h->cap);h->len=0;h->default_v=NULL;return h;}
42
+ sp_StrStrHash*sp_StrStrHash_new_with_default(const char*d){SP_GC_ROOT_STR(d);sp_StrStrHash*h=sp_StrStrHash_new();h->default_v=d;return h;}
43
+ void sp_StrStrHash_grow(sp_StrStrHash*h){SP_GC_ROOT(h); sp_gc_wb((void*)h);sp_int oc=h->cap;const char**ok=h->keys;const char**ov=h->vals;h->cap*=2;h->mask=h->cap-1;h->keys=(const char**)sp_pl_zalloc((size_t)h->cap*sizeof(const char*));h->vals=(const char**)sp_pl_zalloc((size_t)h->cap*sizeof(const char*));h->order=(const char**)sp_pl_realloc(h->order,sizeof(const char*)*h->cap);h->len=0;for(sp_int i=0;i<oc;i++){if(ok[i]){sp_int idx=(sp_int)(sp_str_hash(ok[i])&h->mask);while(h->keys[idx])idx=(idx+1)&h->mask;h->keys[idx]=ok[i];h->vals[idx]=ov[i];h->len++;}}sp_pl_free(ok);sp_pl_free(ov);}
44
+ const char*sp_StrStrHash_get(sp_StrStrHash*h,const char*k){SP_GC_ROOT(h);SP_GC_ROOT_STR(k);if(!h)return NULL;sp_int idx=(sp_int)(sp_str_hash(k)&h->mask);while(h->keys[idx]){if(sp_str_eq(h->keys[idx],k))return h->vals[idx];idx=(idx+1)&h->mask;}return h->default_v;}
45
+ void sp_StrStrHash_set(sp_StrStrHash*h,const char*k,const char*v){SP_GC_ROOT(h);SP_GC_ROOT_STR(k);SP_GC_ROOT_STR(v); if(!k){sp_raise_cls("TypeError","no implicit conversion of nil into String");return;} sp_gc_wb((void*)h);if(h->len*2>=h->cap)sp_StrStrHash_grow(h);sp_int idx=(sp_int)(sp_str_hash(k)&h->mask);while(h->keys[idx]){if(sp_str_eq(h->keys[idx],k)){h->vals[idx]=v;return;}idx=(idx+1)&h->mask;}k=sp_hash_key_str(k);sp_gc_wb((void*)h);h->keys[idx]=k;h->vals[idx]=v;h->order[h->len]=k;h->len++;}
46
+ sp_bool sp_StrStrHash_has_key(sp_StrStrHash*h,const char*k){SP_GC_ROOT(h);SP_GC_ROOT_STR(k);sp_int idx=(sp_int)(sp_str_hash(k)&h->mask);while(h->keys[idx]){if(sp_str_eq(h->keys[idx],k))return TRUE;idx=(idx+1)&h->mask;}return FALSE;}
47
+ sp_bool sp_StrStrHash_has_value(sp_StrStrHash*h,const char*v){if(!h||!v)return FALSE;for(sp_int i=0;i<h->len;i++){const char*x=sp_StrStrHash_get(h,h->order[i]);if(x&&strcmp(x,v)==0)return TRUE;}return FALSE;}
48
+ sp_int sp_StrStrHash_length(sp_StrStrHash*h){return h->len;}
49
+ void sp_StrStrHash_delete(sp_StrStrHash*h,const char*k){SP_GC_ROOT(h);SP_GC_ROOT_STR(k); sp_gc_wb((void*)h);sp_int idx=(sp_int)(sp_str_hash(k)&h->mask);while(h->keys[idx]){if(sp_str_eq(h->keys[idx],k)){h->keys[idx]=NULL;h->vals[idx]=NULL;h->len--;sp_int j=(idx+1)&h->mask;while(h->keys[j]){sp_int nj=(sp_int)(sp_str_hash(h->keys[j])&h->mask);if((j>idx&&(nj<=idx||nj>j))||(j<idx&&nj<=idx&&nj>j)){h->keys[idx]=h->keys[j];h->vals[idx]=h->vals[j];h->keys[j]=NULL;h->vals[j]=NULL;idx=j;}j=(j+1)&h->mask;}{sp_int oi=0;while(oi<=h->len){if(strcmp(h->order[oi],k)==0){while(oi<h->len){h->order[oi]=h->order[oi+1];oi++;}break;}oi++;}}return;}idx=(idx+1)&h->mask;}}
50
+ sp_StrArray*sp_StrStrHash_keys(sp_StrStrHash*h){SP_GC_ROOT(h);sp_StrArray*a=sp_StrArray_new();SP_GC_ROOT(a);for(sp_int i=0;i<h->len;i++)sp_StrArray_push(a,h->order[i]);return a;}
51
+ sp_StrArray*sp_StrStrHash_values(sp_StrStrHash*h){SP_GC_ROOT(h);sp_StrArray*a=sp_StrArray_new();SP_GC_ROOT(a);for(sp_int i=0;i<h->len;i++)sp_StrArray_push(a,sp_StrStrHash_get(h,h->order[i]));return a;}
52
+ sp_StrStrHash*sp_StrStrHash_invert(sp_StrStrHash*h){SP_GC_ROOT(h);sp_StrStrHash*r=sp_StrStrHash_new();for(sp_int i=0;i<h->len;i++){const char*k=h->order[i];sp_StrStrHash_set(r,sp_StrStrHash_get(h,k),k);}return r;}
53
+ void sp_StrStrHash_update(sp_StrStrHash*a,sp_StrStrHash*b){SP_GC_ROOT(a);SP_GC_ROOT(b);for(sp_int i=0;i<b->len;i++)sp_StrStrHash_set(a,b->order[i],sp_StrStrHash_get(b,b->order[i]));}
54
+ sp_StrStrHash*sp_StrStrHash_dup(sp_StrStrHash*h){SP_GC_ROOT(h);sp_StrStrHash*r=sp_StrStrHash_new();r->default_v=h->default_v;for(sp_int i=0;i<h->len;i++)sp_StrStrHash_set(r,h->order[i],sp_StrStrHash_get(h,h->order[i]));return r;}
55
+ sp_StrStrHash*sp_StrStrHash_merge(sp_StrStrHash*a,sp_StrStrHash*b){SP_GC_ROOT(a);SP_GC_ROOT(b);sp_StrStrHash*r=sp_StrStrHash_new();if(a){r->default_v=a->default_v;for(sp_int i=0;i<a->len;i++)sp_StrStrHash_set(r,a->order[i],sp_StrStrHash_get(a,a->order[i]));}if(b){for(sp_int i=0;i<b->len;i++)sp_StrStrHash_set(r,b->order[i],sp_StrStrHash_get(b,b->order[i]));}return r;}
56
+ sp_StrStrHash*sp_StrStrHash_replace(sp_StrStrHash*h,sp_StrStrHash*o){SP_GC_ROOT(h);SP_GC_ROOT(o); sp_gc_wb((void*)h);if(!h)return h;for(sp_int i=0;i<h->cap;i++)h->keys[i]=NULL;h->len=0;if(o)for(sp_int i=0;i<o->len;i++)sp_StrStrHash_set(h,o->order[i],sp_StrStrHash_get(o,o->order[i]));return h;}
57
+ void sp_StrStrHash_clear(sp_StrStrHash*h){ sp_gc_wb((void*)h);if(!h)return;for(sp_int i=0;i<h->cap;i++)h->keys[i]=NULL;h->len=0;}
58
+ sp_bool sp_StrStrHash_eq(sp_StrStrHash*a,sp_StrStrHash*b){SP_GC_ROOT(a);SP_GC_ROOT(b);if(!a||!b)return a==b;if(a->len!=b->len)return FALSE;for(sp_int i=0;i<a->len;i++){const char*k=a->order[i];if(!sp_StrStrHash_has_key(b,k))return FALSE;if(!sp_str_eq(sp_StrStrHash_get(a,k),sp_StrStrHash_get(b,k)))return FALSE;}return TRUE;}
59
+ void sp_IntStrHash_fin(void*p){sp_IntStrHash*h=(sp_IntStrHash*)p;sp_pl_free(h->keys);sp_pl_free(h->vals);sp_pl_free(h->order);sp_pl_free(h->used);}
60
+ void sp_IntStrHash_scan(void*p){sp_IntStrHash*h=(sp_IntStrHash*)p;for(sp_int i=0;i<h->cap;i++)if(h->used[i])sp_mark_string(h->vals[i]);if(h->default_v)sp_mark_string(h->default_v);}
61
+ sp_IntStrHash*sp_IntStrHash_new(void){sp_IntStrHash*h=(sp_IntStrHash*)sp_gc_alloc(sizeof(sp_IntStrHash),sp_IntStrHash_fin,sp_IntStrHash_scan);h->cap=16;h->mask=15;h->keys=(sp_int*)sp_pl_zalloc((size_t)h->cap*sizeof(sp_int));h->vals=(const char**)sp_pl_zalloc((size_t)h->cap*sizeof(const char*));h->order=(sp_int*)sp_pl_alloc(sizeof(sp_int)*(size_t)h->cap);h->used=(sp_bool*)sp_pl_zalloc((size_t)h->cap*sizeof(sp_bool));h->len=0;h->default_v=NULL;return h;}
62
+ sp_IntStrHash*sp_IntStrHash_new_with_default(const char*d){SP_GC_ROOT_STR(d);sp_IntStrHash*h=sp_IntStrHash_new();h->default_v=d;return h;}
63
+ void sp_IntStrHash_grow(sp_IntStrHash*h){ sp_gc_wb((void*)h);sp_int oc=h->cap,ol=h->len;sp_int*ok=h->keys;const char**ov=h->vals;sp_bool*ou=h->used;sp_int*oo=h->order;h->cap*=2;h->mask=h->cap-1;h->keys=(sp_int*)sp_pl_zalloc((size_t)h->cap*sizeof(sp_int));h->vals=(const char**)sp_pl_zalloc((size_t)h->cap*sizeof(const char*));h->order=(sp_int*)sp_pl_alloc(sizeof(sp_int)*(size_t)h->cap);h->used=(sp_bool*)sp_pl_zalloc((size_t)h->cap*sizeof(sp_bool));h->len=ol;for(sp_int i=0;i<oc;i++){if(!ou[i])continue;sp_int k=ok[i];const char*v=ov[i];sp_int di=_sp_istr_idx(h->mask,k);while(h->used[di])di=(di+1)&h->mask;h->used[di]=TRUE;h->keys[di]=k;h->vals[di]=v;}for(sp_int i=0;i<ol;i++)h->order[i]=oo[i];sp_pl_free(ok);sp_pl_free(ov);sp_pl_free(ou);sp_pl_free(oo);}
64
+ void sp_IntStrHash_set(sp_IntStrHash*h,sp_int k,const char*v){SP_GC_ROOT(h);SP_GC_ROOT_STR(v); sp_gc_wb((void*)h);if(h->len*2>=h->cap)sp_IntStrHash_grow(h);sp_int idx=_sp_istr_idx(h->mask,k);while(h->used[idx]){if(h->keys[idx]==k){h->vals[idx]=v;return;}idx=(idx+1)&h->mask;}h->used[idx]=TRUE;h->keys[idx]=k;h->vals[idx]=v;h->order[h->len++]=k;}
65
+ const char*sp_IntStrHash_get(sp_IntStrHash*h,sp_int k){if(!h)return NULL;sp_int idx=_sp_istr_idx(h->mask,k);while(h->used[idx]){if(h->keys[idx]==k)return h->vals[idx];idx=(idx+1)&h->mask;}return h->default_v;}
66
+ sp_IntStrHash*sp_IntStrHash_merge(sp_IntStrHash*a,sp_IntStrHash*b){SP_GC_ROOT(a);SP_GC_ROOT(b);sp_IntStrHash*r=sp_IntStrHash_new();if(a){r->default_v=a->default_v;for(sp_int i=0;i<a->len;i++)sp_IntStrHash_set(r,a->order[i],sp_IntStrHash_get(a,a->order[i]));}if(b){for(sp_int i=0;i<b->len;i++)sp_IntStrHash_set(r,b->order[i],sp_IntStrHash_get(b,b->order[i]));}return r;}
67
+ void sp_IntStrHash_update(sp_IntStrHash*a,sp_IntStrHash*b){if(!a||!b||a==b)return;SP_GC_ROOT(a);SP_GC_ROOT(b);for(sp_int i=0;i<b->len;i++)sp_IntStrHash_set(a,b->order[i],sp_IntStrHash_get(b,b->order[i]));}
68
+ sp_bool sp_IntStrHash_has_key(sp_IntStrHash*h,sp_int k){sp_int idx=_sp_istr_idx(h->mask,k);while(h->used[idx]){if(h->keys[idx]==k)return TRUE;idx=(idx+1)&h->mask;}return FALSE;}
69
+ sp_bool sp_IntStrHash_has_value(sp_IntStrHash*h,const char*v){if(!h||!v)return FALSE;for(sp_int i=0;i<h->len;i++){const char*x=sp_IntStrHash_get(h,h->order[i]);if(x&&strcmp(x,v)==0)return TRUE;}return FALSE;}
70
+ sp_int sp_IntStrHash_length(sp_IntStrHash*h){return h->len;}
71
+ sp_IntArray*sp_IntStrHash_keys(sp_IntStrHash*h){SP_GC_ROOT(h);sp_IntArray*a=sp_IntArray_new();SP_GC_ROOT(a);for(sp_int i=0;i<h->len;i++)sp_IntArray_push_nilable(a,h->order[i]);return a;} /* so is a nil key */
72
+ sp_StrArray*sp_IntStrHash_values(sp_IntStrHash*h){SP_GC_ROOT(h);sp_StrArray*a=sp_StrArray_new();SP_GC_ROOT(a);for(sp_int i=0;i<h->len;i++)sp_StrArray_push(a,sp_IntStrHash_get(h,h->order[i]));return a;}
73
+ sp_IntStrHash*sp_IntStrHash_dup(sp_IntStrHash*h){SP_GC_ROOT(h);sp_IntStrHash*r=sp_IntStrHash_new();r->default_v=h->default_v;for(sp_int i=0;i<h->len;i++)sp_IntStrHash_set(r,h->order[i],sp_IntStrHash_get(h,h->order[i]));return r;}
74
+ sp_IntStrHash*sp_IntStrHash_replace(sp_IntStrHash*h,sp_IntStrHash*o){SP_GC_ROOT(h);SP_GC_ROOT(o);if(!h)return h;for(sp_int i=0;i<h->cap;i++)h->used[i]=0;h->len=0;if(o)for(sp_int i=0;i<o->len;i++)sp_IntStrHash_set(h,o->order[i],sp_IntStrHash_get(o,o->order[i]));return h;}
75
+ sp_bool sp_IntStrHash_eq(sp_IntStrHash*a,sp_IntStrHash*b){SP_GC_ROOT(a);SP_GC_ROOT(b);if(!a||!b)return a==b;if(a->len!=b->len)return FALSE;for(sp_int i=0;i<a->len;i++){sp_int k=a->order[i];if(!sp_IntStrHash_has_key(b,k))return FALSE;if(!sp_str_eq(sp_IntStrHash_get(a,k),sp_IntStrHash_get(b,k)))return FALSE;}return TRUE;}
76
+ /* Int → Int typed hash. Mirrors sp_IntStrHash's open-addressing
77
+ layout (used[] bitmap so 0/-1 keys are distinguishable from
78
+ empty), with int-valued slots (#865). */
79
+ void sp_IntIntHash_fin(void*p){sp_IntIntHash*h=(sp_IntIntHash*)p;sp_pl_free(h->keys);sp_pl_free(h->vals);sp_pl_free(h->order);sp_pl_free(h->used);}
80
+ /* default_v is SP_INT_NIL for a hash with no explicit default, so a
81
+ missing-key `[]` read surfaces Ruby nil (#801). Hash.new(N) sets it via
82
+ _new_with_default. */
83
+ sp_IntIntHash*sp_IntIntHash_new(void){sp_IntIntHash*h=(sp_IntIntHash*)sp_gc_alloc(sizeof(sp_IntIntHash),sp_IntIntHash_fin,NULL);h->cap=16;h->mask=15;h->keys=(sp_int*)sp_pl_zalloc((size_t)h->cap*sizeof(sp_int));h->vals=(sp_int*)sp_pl_zalloc((size_t)h->cap*sizeof(sp_int));h->order=(sp_int*)sp_pl_alloc(sizeof(sp_int)*(size_t)h->cap);h->used=(sp_bool*)sp_pl_zalloc((size_t)h->cap*sizeof(sp_bool));h->len=0;h->default_v=SP_INT_NIL;return h;}
84
+ sp_IntIntHash*sp_IntIntHash_new_with_default(sp_int d){sp_IntIntHash*h=sp_IntIntHash_new();h->default_v=d;return h;}
85
+ void sp_IntIntHash_grow(sp_IntIntHash*h){sp_int oc=h->cap,ol=h->len;sp_int*ok=h->keys;sp_int*ov=h->vals;sp_bool*ou=h->used;sp_int*oo=h->order;h->cap*=2;h->mask=h->cap-1;h->keys=(sp_int*)sp_pl_zalloc((size_t)h->cap*sizeof(sp_int));h->vals=(sp_int*)sp_pl_zalloc((size_t)h->cap*sizeof(sp_int));h->order=(sp_int*)sp_pl_alloc(sizeof(sp_int)*(size_t)h->cap);h->used=(sp_bool*)sp_pl_zalloc((size_t)h->cap*sizeof(sp_bool));h->len=ol;for(sp_int i=0;i<oc;i++){if(!ou[i])continue;sp_int k=ok[i];sp_int v=ov[i];sp_int di=_sp_istr_idx(h->mask,k);while(h->used[di])di=(di+1)&h->mask;h->used[di]=TRUE;h->keys[di]=k;h->vals[di]=v;}for(sp_int i=0;i<ol;i++)h->order[i]=oo[i];sp_pl_free(ok);sp_pl_free(ov);sp_pl_free(ou);sp_pl_free(oo);}
86
+ void sp_IntIntHash_set(sp_IntIntHash*h,sp_int k,sp_int v){SP_GC_ROOT(h);if(h->len*2>=h->cap)sp_IntIntHash_grow(h);sp_int idx=_sp_istr_idx(h->mask,k);while(h->used[idx]){if(h->keys[idx]==k){h->vals[idx]=v;return;}idx=(idx+1)&h->mask;}h->used[idx]=TRUE;h->keys[idx]=k;h->vals[idx]=v;h->order[h->len++]=k;}
87
+ sp_int sp_IntIntHash_get(sp_IntIntHash*h,sp_int k){if(!h)return 0;sp_int idx=_sp_istr_idx(h->mask,k);while(h->used[idx]){if(h->keys[idx]==k)return h->vals[idx];idx=(idx+1)&h->mask;}return h->default_v;}
88
+ sp_IntIntHash*sp_IntIntHash_merge(sp_IntIntHash*a,sp_IntIntHash*b){SP_GC_ROOT(a);SP_GC_ROOT(b);sp_IntIntHash*r=sp_IntIntHash_new();if(a){r->default_v=a->default_v;for(sp_int i=0;i<a->len;i++)sp_IntIntHash_set(r,a->order[i],sp_IntIntHash_get(a,a->order[i]));}if(b){for(sp_int i=0;i<b->len;i++)sp_IntIntHash_set(r,b->order[i],sp_IntIntHash_get(b,b->order[i]));}return r;}
89
+ void sp_IntIntHash_update(sp_IntIntHash*a,sp_IntIntHash*b){if(!a||!b||a==b)return;SP_GC_ROOT(a);SP_GC_ROOT(b);for(sp_int i=0;i<b->len;i++)sp_IntIntHash_set(a,b->order[i],sp_IntIntHash_get(b,b->order[i]));}
90
+ /* Integer-keyed hash delete: backward-shift the probe cluster so open-addressing
91
+ lookups stay correct, then drop the key from the insertion-order array. */
92
+ void sp_IntIntHash_delete(sp_IntIntHash*h,sp_int k){if(!h)return;sp_int idx=_sp_istr_idx(h->mask,k);while(h->used[idx]){if(h->keys[idx]==k){h->used[idx]=0;h->len--;sp_int j=(idx+1)&h->mask;while(h->used[j]){sp_int nj=_sp_istr_idx(h->mask,h->keys[j]);if((j>idx&&(nj<=idx||nj>j))||(j<idx&&nj<=idx&&nj>j)){h->keys[idx]=h->keys[j];h->vals[idx]=h->vals[j];h->used[idx]=1;h->used[j]=0;idx=j;}j=(j+1)&h->mask;}{sp_int oi=0;while(oi<=h->len){if(h->order[oi]==k){while(oi<h->len){h->order[oi]=h->order[oi+1];oi++;}break;}oi++;}}return;}idx=(idx+1)&h->mask;}}
93
+ void sp_IntStrHash_delete(sp_IntStrHash*h,sp_int k){ sp_gc_wb((void*)h);if(!h)return;sp_int idx=_sp_istr_idx(h->mask,k);while(h->used[idx]){if(h->keys[idx]==k){h->used[idx]=0;h->len--;sp_int j=(idx+1)&h->mask;while(h->used[j]){sp_int nj=_sp_istr_idx(h->mask,h->keys[j]);if((j>idx&&(nj<=idx||nj>j))||(j<idx&&nj<=idx&&nj>j)){h->keys[idx]=h->keys[j];h->vals[idx]=h->vals[j];h->used[idx]=1;h->used[j]=0;idx=j;}j=(j+1)&h->mask;}{sp_int oi=0;while(oi<=h->len){if(h->order[oi]==k){while(oi<h->len){h->order[oi]=h->order[oi+1];oi++;}break;}oi++;}}return;}idx=(idx+1)&h->mask;}}
94
+ /* Issue #801: maybe-missing public `[]` read. Returns default_v on a miss
95
+ (SP_INT_NIL for a no-default hash = Ruby nil; the explicit default for
96
+ Hash.new(N)). Proven-present reads keep using _get. */
97
+ sp_int sp_IntIntHash_get_opt(sp_IntIntHash*h,sp_int k){if(!h)return SP_INT_NIL;sp_int idx=_sp_istr_idx(h->mask,k);while(h->used[idx]){if(h->keys[idx]==k)return h->vals[idx];idx=(idx+1)&h->mask;}return h->default_v;}
98
+ sp_bool sp_IntIntHash_has_key(sp_IntIntHash*h,sp_int k){sp_int idx=_sp_istr_idx(h->mask,k);while(h->used[idx]){if(h->keys[idx]==k)return TRUE;idx=(idx+1)&h->mask;}return FALSE;}
99
+ sp_int sp_IntIntHash_fetch_or(sp_IntIntHash*h,sp_int k,sp_int d){if(!h)return d;sp_int idx=_sp_istr_idx(h->mask,k);while(h->used[idx]){if(h->keys[idx]==k)return h->vals[idx];idx=(idx+1)&h->mask;}return d;}
100
+ sp_int sp_IntIntHash_length(sp_IntIntHash*h){return h?h->len:0;}
101
+ sp_IntArray*sp_IntIntHash_keys(sp_IntIntHash*h){SP_GC_ROOT(h);sp_IntArray*a=sp_IntArray_new();SP_GC_ROOT(a);if(h)for(sp_int i=0;i<h->len;i++)sp_IntArray_push_nilable(a,h->order[i]);return a;}
102
+ sp_IntArray*sp_IntIntHash_values(sp_IntIntHash*h){SP_GC_ROOT(h);sp_IntArray*a=sp_IntArray_new();SP_GC_ROOT(a);if(h)for(sp_int i=0;i<h->len;i++)sp_IntArray_push_nilable(a,sp_IntIntHash_get(h,h->order[i]));return a;}
103
+ sp_bool sp_IntIntHash_has_value(sp_IntIntHash*h,sp_int v){if(!h)return FALSE;for(sp_int i=0;i<h->len;i++)if(sp_IntIntHash_get(h,h->order[i])==v)return TRUE;return FALSE;}
104
+ sp_bool sp_IntIntHash_eq(sp_IntIntHash*a,sp_IntIntHash*b){SP_GC_ROOT(a);SP_GC_ROOT(b);if(!a||!b)return a==b;if(a->len!=b->len)return FALSE;for(sp_int i=0;i<a->len;i++){sp_int k=a->order[i];if(!sp_IntIntHash_has_key(b,k))return FALSE;if(sp_IntIntHash_get(a,k)!=sp_IntIntHash_get(b,k))return FALSE;}return TRUE;}
105
+ sp_IntIntHash*sp_IntIntHash_dup(sp_IntIntHash*h){SP_GC_ROOT(h);sp_IntIntHash*r=sp_IntIntHash_new();r->default_v=h->default_v;for(sp_int i=0;i<h->len;i++)sp_IntIntHash_set(r,h->order[i],sp_IntIntHash_get(h,h->order[i]));return r;}
106
+ sp_IntIntHash*sp_IntIntHash_replace(sp_IntIntHash*h,sp_IntIntHash*o){SP_GC_ROOT(h);SP_GC_ROOT(o);if(!h)return h;for(sp_int i=0;i<h->cap;i++)h->used[i]=0;h->len=0;if(o)for(sp_int i=0;i<o->len;i++)sp_IntIntHash_set(h,o->order[i],sp_IntIntHash_get(o,o->order[i]));return h;}
107
+ void sp_IntIntHash_clear(sp_IntIntHash*h){if(!h)return;for(sp_int i=0;i<h->cap;i++)h->used[i]=0;h->len=0;}
108
+ /* Issue #851: Hash#inspect for typed-hash variants beyond
109
+ sym_int_hash. Renders Ruby's `{"k" => v, ...}` (string keys),
110
+ `{42 => "v", ...}` (int keys), or `{:k => v, ...}` (sym keys but
111
+ non-int value, since the bare `k: v` shorthand only applies
112
+ when values are inspectable as one-liners -- match CRuby). */
113
+ const char*sp_StrIntHash_inspect(sp_StrIntHash*h){SP_GC_ROOT(h);return h?sp_inspect_container(sp_box_obj(h,SP_BUILTIN_STR_INT_HASH)):SPL("nil");}
114
+ /* Hash#to_proc lookup fn -- cap is the hash, args[0] the string key. */
115
+ sp_int sp_StrIntHash_proc_fn(void *cap, sp_int argc, sp_int *args) { if (argc < 1) return 0; return sp_StrIntHash_get((sp_StrIntHash *)cap, (const char *)(uintptr_t)args[0]); }
116
+ const char*sp_StrStrHash_inspect(sp_StrStrHash*h){SP_GC_ROOT(h);return h?sp_inspect_container(sp_box_obj(h,SP_BUILTIN_STR_STR_HASH)):SPL("nil");}
117
+ const char*sp_IntStrHash_inspect(sp_IntStrHash*h){SP_GC_ROOT(h);return h?sp_inspect_container(sp_box_obj(h,SP_BUILTIN_INT_STR_HASH)):SPL("nil");}
118
+ /* A key or a value can be the nil sentinel: a nullable Integer stored there
119
+ (`{i => 1}` for an `i` that missed its array, or `h[k] = i`) keeps the int
120
+ variant, and the slot holds nil as SP_INT_NIL. It renders as the nil it
121
+ stands for, as sp_poly_inspect renders a boxed one and the other typed
122
+ variants' walk (sp_inspect_container) already does. */
123
+ static const char*sp_int_inspect_or_nil(sp_int v){return v==SP_INT_NIL?SPL("nil"):sp_int_to_s(v);}
124
+ const char*sp_IntIntHash_inspect(sp_IntIntHash*h){SP_GC_ROOT(h);if(!h)return SPL("nil");sp_String*s=sp_String_new("{");SP_GC_ROOT(s);if(h){for(sp_int i=0;i<h->len;i++){if(i>0)sp_String_append(s,", ");sp_String_append(s,sp_int_inspect_or_nil(h->order[i]));sp_String_append(s," => ");sp_String_append(s,sp_int_inspect_or_nil(sp_IntIntHash_get(h,h->order[i])));}}sp_String_append(s,"}");return sp_str_dup(s->data);}
125
+
126
+ /* Issue #738: Hash#to_a as poly_array of [key, value] poly_array pairs. */
127
+ sp_PolyArray*sp_StrIntHash_to_a(sp_StrIntHash*h){SP_GC_ROOT(h);sp_PolyArray*r=sp_PolyArray_new();if(!h)return r;for(sp_int i=0;i<h->len;i++){sp_PolyArray*p=sp_PolyArray_new();sp_PolyArray_push(p,sp_box_str(h->order[i]));sp_PolyArray_push(p,sp_box_int(sp_StrIntHash_get(h,h->order[i])));sp_PolyArray_push(r,sp_box_poly_array(p));}return r;}
128
+ sp_PolyArray*sp_StrStrHash_to_a(sp_StrStrHash*h){SP_GC_ROOT(h);sp_PolyArray*r=sp_PolyArray_new();if(!h)return r;for(sp_int i=0;i<h->len;i++){sp_PolyArray*p=sp_PolyArray_new();sp_PolyArray_push(p,sp_box_str(h->order[i]));sp_PolyArray_push(p,sp_box_str(sp_StrStrHash_get(h,h->order[i])));sp_PolyArray_push(r,sp_box_poly_array(p));}return r;}
129
+ sp_PolyArray*sp_IntStrHash_to_a(sp_IntStrHash*h){SP_GC_ROOT(h);sp_PolyArray*r=sp_PolyArray_new();if(!h)return r;for(sp_int i=0;i<h->len;i++){sp_PolyArray*p=sp_PolyArray_new();sp_PolyArray_push(p,sp_box_int(h->order[i]));sp_PolyArray_push(p,sp_box_str(sp_IntStrHash_get(h,h->order[i])));sp_PolyArray_push(r,sp_box_poly_array(p));}return r;}
130
+
131
+ void sp_IntStrHash_clear(sp_IntStrHash*h){if(!h)return;for(sp_int i=0;i<h->cap;i++)h->used[i]=0;h->len=0;}
@@ -0,0 +1,131 @@
1
+ #ifndef SP_HASH_H
2
+ #define SP_HASH_H
3
+ /* sp_hash.h -- cold CRUD-op surface for the 4 non-poly-valued typed hashes
4
+ * (sp_StrIntHash / sp_StrStrHash / sp_IntStrHash / sp_IntIntHash).
5
+ *
6
+ * The struct layouts already live in sp_types.h (shared). None of these
7
+ * four hash types appear in optcarrot's hot loop (0 uses for 3 of them,
8
+ * 4 non-hot uses for IntStrHash -- unlike SymPolyHash/PolyPolyHash, which
9
+ * stay in spinel_rt.h because optcarrot calls their accessors hundreds of
10
+ * times per frame), so the full CRUD surface -- not just the cold
11
+ * materializers -- compiles once into libspinel_rt.a (lib/sp_hash.c)
12
+ * instead of into every generated TU.
13
+ *
14
+ * inspect/to_a/invert functions that bridge to a poly-valued hash
15
+ * (sp_StrPolyHash/sp_PolyPolyHash) or sp_PolyArray stay in spinel_rt.h --
16
+ * moving those would require exposing the poly cluster across the archive
17
+ * boundary, which is excluded (de-inlining poly accessors regresses
18
+ * optcarrot fps ~15%, per project_sp_runtime_lib_eviction).
19
+ */
20
+ #include "sp_types.h" /* sp_StrIntHash / sp_StrStrHash / sp_IntStrHash / sp_IntIntHash */
21
+ #include "sp_gc.h" /* sp_gc_alloc, SP_GC_ROOT, sp_mark_string */
22
+ #include "sp_array.h" /* sp_StrArray / sp_IntArray new/push (keys/values) */
23
+ #include "sp_str.h" /* sp_str_hash / sp_str_eq / _sp_istr_idx */
24
+ #include "sp_inspect.h" /* sp_inspect_container for the #inspect wrappers */
25
+ #include "sp_string.h" /* sp_String builder for sp_IntIntHash_inspect */
26
+ #include "sp_re.h" /* mrb_regexp_pattern for sp_re_gsub_str_str_hash/sub_str_str_hash */
27
+
28
+ void sp_StrIntHash_fin(void*p);
29
+ void sp_StrIntHash_scan(void*p);
30
+ sp_StrIntHash*sp_StrIntHash_new(void);
31
+ sp_StrIntHash*sp_StrIntHash_new_with_default(sp_int d);
32
+ void sp_StrIntHash_grow(sp_StrIntHash*h);
33
+ sp_int sp_StrIntHash_get(sp_StrIntHash*h,const char*k);
34
+ sp_int sp_StrIntHash_get_opt(sp_StrIntHash*h,const char*k);
35
+ sp_int sp_StrIntHash_fetch_or(sp_StrIntHash*h,const char*k,sp_int d);
36
+ void sp_StrIntHash_set(sp_StrIntHash*h,const char*k,sp_int v);
37
+ sp_bool sp_StrIntHash_has_key(sp_StrIntHash*h,const char*k);
38
+ sp_bool sp_StrIntHash_has_value(sp_StrIntHash*h,sp_int v);
39
+ sp_int sp_StrIntHash_length(sp_StrIntHash*h);
40
+ void sp_StrIntHash_delete(sp_StrIntHash*h,const char*k);
41
+ sp_StrArray*sp_StrIntHash_keys(sp_StrIntHash*h);
42
+ sp_IntArray*sp_StrIntHash_values(sp_StrIntHash*h);
43
+ sp_StrIntHash*sp_StrIntHash_merge(sp_StrIntHash*a,sp_StrIntHash*b);
44
+ void sp_StrIntHash_update(sp_StrIntHash*a,sp_StrIntHash*b);
45
+ sp_StrIntHash*sp_StrIntHash_dup(sp_StrIntHash*h);
46
+ sp_StrIntHash*sp_StrIntHash_replace(sp_StrIntHash*h,sp_StrIntHash*o);
47
+ void sp_StrIntHash_clear(sp_StrIntHash*h);
48
+ sp_bool sp_StrIntHash_eq(sp_StrIntHash*a,sp_StrIntHash*b);
49
+ void sp_StrStrHash_fin(void*p);
50
+ void sp_StrStrHash_scan(void*p);
51
+ sp_StrStrHash*sp_StrStrHash_new(void);
52
+ sp_StrStrHash*sp_StrStrHash_new_with_default(const char*d);
53
+ void sp_StrStrHash_grow(sp_StrStrHash*h);
54
+ const char*sp_StrStrHash_get(sp_StrStrHash*h,const char*k);
55
+ void sp_StrStrHash_set(sp_StrStrHash*h,const char*k,const char*v);
56
+ sp_bool sp_StrStrHash_has_key(sp_StrStrHash*h,const char*k);
57
+ sp_bool sp_StrStrHash_has_value(sp_StrStrHash*h,const char*v);
58
+ sp_int sp_StrStrHash_length(sp_StrStrHash*h);
59
+ void sp_StrStrHash_delete(sp_StrStrHash*h,const char*k);
60
+ sp_StrArray*sp_StrStrHash_keys(sp_StrStrHash*h);
61
+ sp_StrArray*sp_StrStrHash_values(sp_StrStrHash*h);
62
+ sp_StrStrHash*sp_StrStrHash_invert(sp_StrStrHash*h);
63
+ void sp_StrStrHash_update(sp_StrStrHash*a,sp_StrStrHash*b);
64
+ sp_StrStrHash*sp_StrStrHash_dup(sp_StrStrHash*h);
65
+ sp_StrStrHash*sp_StrStrHash_merge(sp_StrStrHash*a,sp_StrStrHash*b);
66
+ sp_StrStrHash*sp_StrStrHash_replace(sp_StrStrHash*h,sp_StrStrHash*o);
67
+ void sp_StrStrHash_clear(sp_StrStrHash*h);
68
+ sp_bool sp_StrStrHash_eq(sp_StrStrHash*a,sp_StrStrHash*b);
69
+ void sp_IntStrHash_fin(void*p);
70
+ void sp_IntStrHash_scan(void*p);
71
+ sp_IntStrHash*sp_IntStrHash_new(void);
72
+ sp_IntStrHash*sp_IntStrHash_new_with_default(const char*d);
73
+ void sp_IntStrHash_grow(sp_IntStrHash*h);
74
+ void sp_IntStrHash_set(sp_IntStrHash*h,sp_int k,const char*v);
75
+ const char*sp_IntStrHash_get(sp_IntStrHash*h,sp_int k);
76
+ sp_IntStrHash*sp_IntStrHash_merge(sp_IntStrHash*a,sp_IntStrHash*b);
77
+ void sp_IntStrHash_update(sp_IntStrHash*a,sp_IntStrHash*b);
78
+ sp_bool sp_IntStrHash_has_key(sp_IntStrHash*h,sp_int k);
79
+ sp_bool sp_IntStrHash_has_value(sp_IntStrHash*h,const char*v);
80
+ sp_int sp_IntStrHash_length(sp_IntStrHash*h);
81
+ sp_IntArray*sp_IntStrHash_keys(sp_IntStrHash*h);
82
+ sp_StrArray*sp_IntStrHash_values(sp_IntStrHash*h);
83
+ sp_IntStrHash*sp_IntStrHash_dup(sp_IntStrHash*h);
84
+ sp_IntStrHash*sp_IntStrHash_replace(sp_IntStrHash*h,sp_IntStrHash*o);
85
+ sp_bool sp_IntStrHash_eq(sp_IntStrHash*a,sp_IntStrHash*b);
86
+ void sp_IntIntHash_fin(void*p);
87
+ sp_IntIntHash*sp_IntIntHash_new(void);
88
+ sp_IntIntHash*sp_IntIntHash_new_with_default(sp_int d);
89
+ void sp_IntIntHash_grow(sp_IntIntHash*h);
90
+ void sp_IntIntHash_set(sp_IntIntHash*h,sp_int k,sp_int v);
91
+ sp_int sp_IntIntHash_get(sp_IntIntHash*h,sp_int k);
92
+ sp_IntIntHash*sp_IntIntHash_merge(sp_IntIntHash*a,sp_IntIntHash*b);
93
+ void sp_IntIntHash_update(sp_IntIntHash*a,sp_IntIntHash*b);
94
+ void sp_IntIntHash_delete(sp_IntIntHash*h,sp_int k);
95
+ void sp_IntStrHash_delete(sp_IntStrHash*h,sp_int k);
96
+ sp_int sp_IntIntHash_get_opt(sp_IntIntHash*h,sp_int k);
97
+ sp_int sp_IntIntHash_fetch_or(sp_IntIntHash*h,sp_int k,sp_int d);
98
+ sp_bool sp_IntIntHash_has_key(sp_IntIntHash*h,sp_int k);
99
+ sp_int sp_IntIntHash_length(sp_IntIntHash*h);
100
+ sp_IntArray*sp_IntIntHash_keys(sp_IntIntHash*h);
101
+ sp_IntArray*sp_IntIntHash_values(sp_IntIntHash*h);
102
+ sp_bool sp_IntIntHash_has_value(sp_IntIntHash*h,sp_int v);
103
+ sp_bool sp_IntIntHash_eq(sp_IntIntHash*a,sp_IntIntHash*b);
104
+ sp_IntIntHash*sp_IntIntHash_dup(sp_IntIntHash*h);
105
+ sp_IntIntHash*sp_IntIntHash_replace(sp_IntIntHash*h,sp_IntIntHash*o);
106
+ void sp_IntIntHash_clear(sp_IntIntHash*h);
107
+ const char*sp_StrIntHash_inspect(sp_StrIntHash*h);
108
+ sp_int sp_StrIntHash_proc_fn(void *cap, sp_int argc, sp_int *args);
109
+ const char*sp_StrStrHash_inspect(sp_StrStrHash*h);
110
+ const char*sp_IntStrHash_inspect(sp_IntStrHash*h);
111
+ const char*sp_IntIntHash_inspect(sp_IntIntHash*h);
112
+
113
+ sp_PolyArray*sp_StrIntHash_to_a(sp_StrIntHash*h);
114
+ sp_PolyArray*sp_StrStrHash_to_a(sp_StrStrHash*h);
115
+ sp_PolyArray*sp_IntStrHash_to_a(sp_IntStrHash*h);
116
+
117
+ /* ---- sp_str_sub_str_str_hash relocated from spinel_rt.h (0 optcarrot uses). ---- */
118
+ const char *sp_str_sub_str_str_hash(const char *str, const char *pat, sp_StrStrHash *h);
119
+ const char *sp_str_gsub_str_str_hash(const char *str, const char *pat, sp_StrStrHash *h);
120
+
121
+ /* ---- Regexp#gsub/#sub with a replacement Hash: relocated from
122
+ spinel_rt.h (0 optcarrot uses). Needs mrb_regexp_pattern/re_exec
123
+ (sp_re.h, included by lib/sp_cold.c already). ---- */
124
+ const char *sp_re_gsub_str_str_hash(mrb_regexp_pattern *pat, const char *str, sp_StrStrHash *h);
125
+ const char *sp_re_sub_str_str_hash(mrb_regexp_pattern *pat, const char *str, sp_StrStrHash *h);
126
+
127
+ /* ---- sp_IntStrHash_clear: missed in the original CRUD batch
128
+ (present for the other 3 hash types, overlooked for this one). ---- */
129
+ void sp_IntStrHash_clear(sp_IntStrHash*h);
130
+
131
+ #endif
@@ -0,0 +1,215 @@
1
+ /* sp_inspect.c -- generic container #inspect (see sp_inspect.h).
2
+
3
+ Walks a boxed array / hash via the hooks the generated TU installs
4
+ (sp_json_kind/len/aref/hpair classify and iterate; sp_poly_inspect_fn recurses
5
+ into elements/keys/values; sp_sym_name_fn renders a symbol key shorthand). No
6
+ typed-container accessors are touched, so this stays clear of the hot hash
7
+ probe path. The result is built with the shared sp_String builder. */
8
+ #include "sp_inspect.h"
9
+ #include "sp_string.h" /* sp_String, sp_alloc.h, SP_GC_ROOT via sp_gc.h */
10
+ #include "sp_str.h" /* sp_sym_inspect_key for the symbol hash-key short form */
11
+ #include "sp_array.h" /* sp_PtrArray_to_poly / sp_PtrArray_inspect for sp_PtrArray_inspect_k */
12
+ #include <stdlib.h> /* realloc for the growing path below */
13
+
14
+ /* The walk guard's per-worker path (see sp_inspect.h). It lives here because
15
+ this archive unit is the one every side of the runtime already links for
16
+ #inspect, and the guard has to be one set of frames shared by the generated
17
+ translation unit, the archive and the carried packages -- a per-TU copy would
18
+ let a walk that crosses the boundary forget where it had been. */
19
+ SP_TLS sp_poly_recur_frame *sp_poly_recur_stack = NULL;
20
+ SP_TLS int sp_poly_recur_top = 0;
21
+ SP_TLS int sp_poly_recur_cap = 0;
22
+ SP_TLS unsigned sp_poly_recur_hash_cycles = 0;
23
+ /* Make room for at least `want` frames, doubling from 64. Off the hot path:
24
+ a walk deep enough to reach the end of the buffer has already paid far more
25
+ in its own recursion than this call costs. */
26
+ #define SP_POLY_RECUR_FIRST_CAP 64 /* frames; 1.5 KB, more than any real walk */
27
+ SP_COLD void sp_poly_recur_grow(int want) {
28
+ int cap = sp_poly_recur_cap ? sp_poly_recur_cap : SP_POLY_RECUR_FIRST_CAP;
29
+ while (cap < want) cap *= 2;
30
+ sp_poly_recur_frame *f = (sp_poly_recur_frame *)
31
+ realloc(sp_poly_recur_stack, sizeof(sp_poly_recur_frame) * (size_t)cap);
32
+ if (!f) sp_oom_die();
33
+ sp_poly_recur_stack = f;
34
+ sp_poly_recur_cap = cap;
35
+ }
36
+
37
+ /* ---- the deep-walk index (SP_POLY_RECUR_SCAN, sp_inspect.h) ----
38
+ Open addressing over the frames themselves: a slot names a frame by index
39
+ and repeats its key, and is live only while that frame is still on the path
40
+ with that key. A pop lowers sp_poly_recur_top and sp_poly_recur_ixtop and
41
+ leaves the slots to go stale on their own; a lookup checks the frame, an
42
+ insert may take a stale slot, and once the slots in use (live or stale) pass
43
+ half the table it is rebuilt from the live frames at a quarter load. Nothing
44
+ here runs until a walk is deeper than the scan budget. */
45
+ typedef struct { const void *a, *b; int kind, frame; } sp_poly_recur_slot;
46
+ static SP_TLS sp_poly_recur_slot *sp_poly_recur_ix = NULL;
47
+ static SP_TLS int sp_poly_recur_ixcap = 0; /* a power of two, or 0 before first use */
48
+ static SP_TLS int sp_poly_recur_ixused = 0; /* slots holding a frame, live or stale */
49
+ SP_TLS int sp_poly_recur_ixtop = 0;
50
+
51
+ /* Multipliers for the slot hash: 2^64 / phi and two more odd constants of the
52
+ same shape (xxHash's), so a pointer's bits spread across the word before the
53
+ fold and the mask. Any odd multipliers would do; these are the usual ones. */
54
+ #define SP_POLY_RECUR_HASH_M1 0x9E3779B97F4A7C15ull
55
+ #define SP_POLY_RECUR_HASH_M2 0xC2B2AE3D27D4EB4Full
56
+ #define SP_POLY_RECUR_HASH_M3 0x165667B19E3779F9ull
57
+ #define SP_POLY_RECUR_IX_MIN 256 /* slots; an eighth full at the scan budget */
58
+ /* Callers guarantee a built table: sp_poly_recur_index rebuilds before any probe. */
59
+ static inline int sp_poly_recur_slot_of(int kind, const void *a, const void *b) {
60
+ uint64_t h = ((uint64_t)(uintptr_t)a >> 4) * SP_POLY_RECUR_HASH_M1
61
+ ^ ((uint64_t)(uintptr_t)b >> 4) * SP_POLY_RECUR_HASH_M2
62
+ ^ (uint64_t)kind * SP_POLY_RECUR_HASH_M3;
63
+ h ^= h >> 32;
64
+ return (int)(h & (uint64_t)(sp_poly_recur_ixcap - 1));
65
+ }
66
+ static inline int sp_poly_recur_slot_live(const sp_poly_recur_slot *s) {
67
+ if (s->frame >= sp_poly_recur_top) return 0;
68
+ const sp_poly_recur_frame *f = &sp_poly_recur_stack[s->frame];
69
+ return f->a == s->a && f->b == s->b && f->kind == s->kind;
70
+ }
71
+ static void sp_poly_recur_ix_insert(int frame) {
72
+ const sp_poly_recur_frame *f = &sp_poly_recur_stack[frame];
73
+ int mask = sp_poly_recur_ixcap - 1;
74
+ int i = sp_poly_recur_slot_of(f->kind, f->a, f->b);
75
+ for (;;) {
76
+ sp_poly_recur_slot *s = &sp_poly_recur_ix[i];
77
+ if (s->frame < 0) { sp_poly_recur_ixused++; break; }
78
+ if (!sp_poly_recur_slot_live(s)) break; /* a stale slot is free */
79
+ i = (i + 1) & mask;
80
+ }
81
+ sp_poly_recur_ix[i].a = f->a;
82
+ sp_poly_recur_ix[i].b = f->b;
83
+ sp_poly_recur_ix[i].kind = f->kind;
84
+ sp_poly_recur_ix[i].frame = frame;
85
+ }
86
+ SP_COLD static void sp_poly_recur_ix_rebuild(int frames) {
87
+ int cap = SP_POLY_RECUR_IX_MIN;
88
+ while (cap < frames * 4) cap *= 2;
89
+ if (cap != sp_poly_recur_ixcap) {
90
+ free(sp_poly_recur_ix);
91
+ sp_poly_recur_ix = (sp_poly_recur_slot *)malloc(sizeof(sp_poly_recur_slot) * (size_t)cap);
92
+ if (!sp_poly_recur_ix) sp_oom_die();
93
+ sp_poly_recur_ixcap = cap;
94
+ }
95
+ for (int i = 0; i < cap; i++) sp_poly_recur_ix[i].frame = -1;
96
+ sp_poly_recur_ixused = 0;
97
+ sp_poly_recur_ixtop = 0;
98
+ }
99
+ void sp_poly_recur_index(void) {
100
+ int top = sp_poly_recur_top;
101
+ if (sp_poly_recur_ixused + (top - sp_poly_recur_ixtop) > sp_poly_recur_ixcap / 2)
102
+ sp_poly_recur_ix_rebuild(top);
103
+ for (int i = sp_poly_recur_ixtop; i < top; i++) sp_poly_recur_ix_insert(i);
104
+ sp_poly_recur_ixtop = top;
105
+ }
106
+ int sp_poly_recur_seen_deep(int kind, const void *a, const void *b) {
107
+ /* a fiber switch (sp_exc_ctx_load) puts other frames under the index, and
108
+ the first deep lookup of a worker finds no table at all */
109
+ if (sp_poly_recur_ixcap == 0 || sp_poly_recur_ixtop < sp_poly_recur_top) sp_poly_recur_index();
110
+ int mask = sp_poly_recur_ixcap - 1;
111
+ int i = sp_poly_recur_slot_of(kind, a, b);
112
+ for (;;) {
113
+ const sp_poly_recur_slot *s = &sp_poly_recur_ix[i];
114
+ if (s->frame < 0) return 0;
115
+ if (s->a == a && s->b == b && s->kind == kind && sp_poly_recur_slot_live(s)) return 1;
116
+ i = (i + 1) & mask;
117
+ }
118
+ }
119
+
120
+ /* ---- the Set package's walks (prototypes and kinds in sp_inspect.h) ---- */
121
+ static sp_int sp_poly_recur_enter(int kind, const void *a, const void *b) {
122
+ if (sp_poly_recur_seen(kind, a, b)) return -1;
123
+ return sp_poly_recur_push(kind, a, b);
124
+ }
125
+ sp_int sp_poly_recur_enter_inspect(sp_RbVal set) {
126
+ return sp_poly_recur_enter(SP_POLY_RECUR_SET_INSPECT, set.v.p, NULL);
127
+ }
128
+ sp_int sp_poly_recur_enter_eq(sp_RbVal set, sp_RbVal other) {
129
+ return sp_poly_recur_enter(SP_POLY_RECUR_SET_EQ, set.v.p, other.v.p);
130
+ }
131
+ sp_int sp_poly_recur_enter_flatten(sp_RbVal set) {
132
+ /* the Ruby raises on -1; as in the runtime's flatten, the walk's own frames
133
+ go before the raise (sp_poly_recur_drop_kind) */
134
+ if (sp_poly_recur_seen(SP_POLY_RECUR_SET_FLATTEN, set.v.p, NULL)) {
135
+ sp_poly_recur_drop_kind(SP_POLY_RECUR_SET_FLATTEN);
136
+ return -1;
137
+ }
138
+ return sp_poly_recur_push(SP_POLY_RECUR_SET_FLATTEN, set.v.p, NULL);
139
+ }
140
+ /* The mark comes back from Ruby, where anyone can spell the module's name. One
141
+ this path never handed out to a Set walk -- outside the frames, or naming a
142
+ frame of the runtime's own -- is ignored rather than moving the depth under
143
+ a walk that is still running. A Set walk's own mark whose frame a handler
144
+ has already dropped is beyond top, and ignored the same way. */
145
+ void sp_poly_recur_leave(sp_int mark) {
146
+ if (mark < 0 || mark >= sp_poly_recur_top) return;
147
+ int kind = sp_poly_recur_stack[mark].kind;
148
+ if (kind != SP_POLY_RECUR_SET_INSPECT && kind != SP_POLY_RECUR_SET_EQ &&
149
+ kind != SP_POLY_RECUR_SET_FLATTEN) return;
150
+ sp_poly_recur_pop((int)mark);
151
+ }
152
+
153
+ const char *sp_inspect_container(sp_RbVal v) {
154
+ /* The container is live across the sp_String allocations below; root it so a
155
+ GC mid-inspect can't free it -- otherwise inspecting a freshly built,
156
+ otherwise-unrooted collection (e.g. `p hash.flat_map { ... }`) is a
157
+ use-after-free under GC pressure. */
158
+ SP_GC_ROOT_RBVAL(v);
159
+ int kind = sp_json_kind_fn ? sp_json_kind_fn(v) : 0;
160
+ sp_int n = sp_json_len_fn ? sp_json_len_fn(v) : 0;
161
+ /* A container reached from inside itself renders as the ellipsis and stops:
162
+ CRuby prints [[...]] for `a << a` and {h: {...}} for `h[:h] = h`. The mark
163
+ covers the loop below, so a SIBLING copy of the same object still renders
164
+ in full -- only the one the walk is standing inside is elided. */
165
+ if (sp_poly_recur_seen(SP_POLY_RECUR_INSPECT, v.v.p, NULL))
166
+ return kind == 1 ? SPL("[...]") : SPL("{...}");
167
+ int rmark = sp_poly_recur_push(SP_POLY_RECUR_INSPECT, v.v.p, NULL);
168
+ if (kind == 1) { /* array: [e0, e1, ...] */
169
+ sp_String *s = sp_String_new("[");
170
+ SP_GC_ROOT(s);
171
+ for (sp_int i = 0; i < n; i++) {
172
+ if (i) sp_String_append(s, ", ");
173
+ sp_String_append(s, sp_poly_inspect_fn(sp_json_aref_fn(v, i)));
174
+ }
175
+ sp_String_append(s, "]");
176
+ sp_poly_recur_pop(rmark);
177
+ return sp_str_dup(s->data);
178
+ }
179
+ /* hash: {k => v, ...}, with the `sym: v` shorthand for a Symbol key. */
180
+ sp_String *s = sp_String_new("{");
181
+ SP_GC_ROOT(s);
182
+ for (sp_int i = 0; i < n; i++) {
183
+ if (i) sp_String_append(s, ", ");
184
+ sp_RbVal k, val;
185
+ sp_json_hpair_fn(v, i, &k, &val);
186
+ if (k.tag == SP_TAG_SYM) {
187
+ sp_String_append(s, sp_sym_inspect_key(sp_sym_name_fn ? sp_sym_name_fn((sp_sym)k.v.i) : ""));
188
+ sp_String_append(s, ": ");
189
+ }
190
+ else {
191
+ sp_String_append(s, sp_poly_inspect_fn(k));
192
+ sp_String_append(s, " => ");
193
+ }
194
+ sp_String_append(s, sp_poly_inspect_fn(val));
195
+ }
196
+ sp_String_append(s, "}");
197
+ sp_poly_recur_pop(rmark);
198
+ return sp_str_dup(s->data);
199
+ }
200
+
201
+ /* Array#inspect for heterogeneous poly arrays. Each element dispatches
202
+ through sp_poly_inspect, so a mixed `[1, "x", :y]` renders
203
+ `[1, "x", :y]` byte-for-byte identical to CRuby. NULL renders
204
+ "nil" so callers that store a nil-returning slot (assoc/rassoc
205
+ miss, etc.) round-trip cleanly through `.inspect`. */
206
+ const char *sp_PolyArray_inspect(sp_PolyArray *a) {
207
+ if (!a) { char *r = sp_str_alloc(3); r[0] = 'n'; r[1] = 'i'; r[2] = 'l'; r[3] = 0; sp_str_set_len(r, 3); return r; }
208
+ return sp_inspect_container(sp_box_poly_array(a));
209
+ }
210
+ /* a pointer array by what it holds (#4486) */
211
+ const char *sp_PtrArray_inspect_k(sp_PtrArray *a) {
212
+ if (!a) return SPL("nil");
213
+ if (a->elem_kind == SP_PTR_ELEM_UNKNOWN) return sp_PtrArray_inspect(a); /* opaque, as the erased id always was */
214
+ return sp_PolyArray_inspect(sp_PtrArray_to_poly(a));
215
+ }
@@ -0,0 +1,128 @@
1
+ #ifndef SP_INSPECT_H
2
+ #define SP_INSPECT_H
3
+ /* sp_inspect.h -- generic container #inspect, split out of spinel_rt.h.
4
+
5
+ sp_inspect_container formats a boxed array or hash in Ruby #inspect form,
6
+ walking it through the generic sp_json_* / sp_poly_inspect_fn hooks (sp_gc.h)
7
+ and building the result with sp_String (sp_string.h). The per-type inspect
8
+ helpers in spinel_rt.h are now one-line wrappers that box their receiver and
9
+ call this, so all the inspect string-building compiles once in the archive
10
+ rather than in every generated TU. */
11
+ #include "sp_gc.h" /* sp_RbVal */
12
+
13
+ const char *sp_inspect_container(sp_RbVal v);
14
+
15
+ /* ---- the walk guard: an object already on the path stops the walk ----
16
+ *
17
+ * `a = []; a << a` is legal Ruby, and so is a Struct that holds itself. Every
18
+ * walk that descends into a container -- #inspect, ==, eql?, <=>, #hash,
19
+ * #flatten, #join, puts -- would follow such a cycle until the C stack ran
20
+ * out. CRuby's rb_exec_recursive keeps, per thread, the set of objects (or
21
+ * object PAIRS) the current walk is already inside and answers a fixed result
22
+ * for one it meets again; this is that set, as a small stack.
23
+ *
24
+ * Frames hold raw pointers that are only ever COMPARED, never dereferenced:
25
+ * the collector does not move objects, and pushing a frame allocates nothing,
26
+ * so a walk cannot collect the very object it is remembering. Per-worker
27
+ * (SP_TLS) like the exception stack, and carried across a green thread's
28
+ * suspension by sp_exc_ctx_save/load, so two fibers walking at once each keep
29
+ * their own path. */
30
+ #define SP_POLY_RECUR_INSPECT 1
31
+ #define SP_POLY_RECUR_EQ 2 /* shared by ==, eql? and <=>: all three answer
32
+ "equal" for a pair they are already inside */
33
+ #define SP_POLY_RECUR_HASH 3
34
+ #define SP_POLY_RECUR_FLATTEN 4
35
+ #define SP_POLY_RECUR_JOIN 5
36
+ #define SP_POLY_RECUR_PUTS 6 /* `puts a` walks an array without inspecting it */
37
+ /* What a repeated object contributes to a #hash: a fixed ODD constant, because
38
+ nil, false and 0 all hash to 0 and a Struct holding itself must not hash like
39
+ one holding nil. */
40
+ #define SP_POLY_RECUR_HASH_VALUE ((sp_int)0x3f5b9d7e2c1a4d05LL)
41
+ typedef struct { const void *a, *b; int kind; } sp_poly_recur_frame;
42
+ /* How many times a #hash walk has met a container it was already inside
43
+ (sp_rbval_hash_member compares it before and after a member). It only
44
+ counts up, per worker. */
45
+ extern SP_TLS unsigned sp_poly_recur_hash_cycles;
46
+ /* The frames grow on demand rather than sitting in a fixed TLS array: a fixed
47
+ cap would leave a cycle whose repeated object is deeper than the cap running
48
+ off the C stack, and kilobytes of TLS shift the layout (and the cost) of
49
+ every hot per-worker variable -- the break stack's own comment records
50
+ measuring 8% of optcarrot on exactly that. Only the pointer and the two
51
+ counts live in TLS; the frames are malloc'd on the first push, doubled when
52
+ full, and never shrunk, per worker. */
53
+ extern SP_TLS sp_poly_recur_frame *sp_poly_recur_stack;
54
+ extern SP_TLS int sp_poly_recur_top;
55
+ extern SP_TLS int sp_poly_recur_cap;
56
+ void sp_poly_recur_grow(int want); /* first allocation and every doubling */
57
+ /* A lookup scans the path: a real program's path is under ten frames, and a
58
+ scan of that beats any table. But a scan at every level of a deep walk is
59
+ quadratic -- a 2000-deep nest would compare 2000 frames at each of its 2000
60
+ levels, where CRuby's rb_exec_recursive, a hash, stays linear. So past
61
+ SP_POLY_RECUR_SCAN frames the path is also indexed (sp_inspect.c) and a
62
+ lookup is one probe. Frames [0, sp_poly_recur_ixtop) are in the index; a pop
63
+ below that simply lowers it, and the next deep push indexes what is missing. */
64
+ #define SP_POLY_RECUR_SCAN 32
65
+ extern SP_TLS int sp_poly_recur_ixtop;
66
+ int sp_poly_recur_seen_deep(int kind, const void *a, const void *b);
67
+ void sp_poly_recur_index(void); /* bring the index up to sp_poly_recur_top */
68
+ /* Is (kind, a, b) already on the path? `b` is NULL for the single-object kinds
69
+ (inspect, hash, flatten, join, puts) and the other side of the pair for
70
+ equality. An empty path (the common case) reads one int and returns. */
71
+ static inline int sp_poly_recur_seen(int kind, const void *a, const void *b) {
72
+ int top = sp_poly_recur_top;
73
+ /* the scan covers SCAN frames; the push of the frame at index SCAN is the
74
+ one that indexes (below), and from then on top is SCAN + 1 or more */
75
+ if (SP_UNLIKELY(top > SP_POLY_RECUR_SCAN)) return sp_poly_recur_seen_deep(kind, a, b);
76
+ for (int i = top - 1; i >= 0; i--) {
77
+ if (sp_poly_recur_stack[i].a == a && sp_poly_recur_stack[i].b == b &&
78
+ sp_poly_recur_stack[i].kind == kind) return 1;
79
+ }
80
+ return 0;
81
+ }
82
+ /* Push a frame and answer the mark to hand back to sp_poly_recur_pop. */
83
+ static inline int sp_poly_recur_push(int kind, const void *a, const void *b) {
84
+ int mark = sp_poly_recur_top;
85
+ if (SP_UNLIKELY(mark == sp_poly_recur_cap)) sp_poly_recur_grow(mark + 1);
86
+ sp_poly_recur_stack[mark].a = a;
87
+ sp_poly_recur_stack[mark].b = b;
88
+ sp_poly_recur_stack[mark].kind = kind;
89
+ sp_poly_recur_top = mark + 1;
90
+ if (SP_UNLIKELY(mark >= SP_POLY_RECUR_SCAN)) sp_poly_recur_index();
91
+ return mark;
92
+ }
93
+ static inline void sp_poly_recur_pop(int mark) {
94
+ sp_poly_recur_top = mark;
95
+ if (SP_UNLIKELY(sp_poly_recur_ixtop > mark)) sp_poly_recur_ixtop = mark;
96
+ }
97
+ /* The current depth as a mark, for a walk that took a branch where it did not
98
+ push: the pop that pairs with it then leaves the path exactly as it was. */
99
+ static inline int sp_poly_recur_save(void) { return sp_poly_recur_top; }
100
+ /* Drop every frame of `kind` from the top of the path. A walk about to raise
101
+ with no answer to give -- an unlimited flatten or a join that met itself --
102
+ takes its own frames with it, so nothing is left on the path for a later
103
+ walk to trip over even where no handler restores the depth (an at_exit
104
+ block still walks). A handler further out that does restore re-exposes the
105
+ frames of walks that are still running, which is what it should do. */
106
+ static inline void sp_poly_recur_drop_kind(int kind) {
107
+ int top = sp_poly_recur_top;
108
+ while (top > 0 && sp_poly_recur_stack[top - 1].kind == kind) top--;
109
+ sp_poly_recur_pop(top);
110
+ }
111
+ /* The Set package (packages/set/set.rb) walks its elements in Ruby, so its
112
+ #inspect, #== and #flatten bind these through native_func and keep their
113
+ frames on this same path: per fiber, restored by every longjmp, and with no
114
+ `ensure` in the Ruby. `enter` answers the mark for sp_poly_recur_leave, or
115
+ -1 when the walk is already inside that Set (or pair), which is the caller's
116
+ cue to stop. The module is reachable by name from Ruby, so `leave` ignores a
117
+ mark this path never handed out to a Set walk. The kinds are Set's own so a
118
+ frame the runtime pushed for the same object or pair -- sp_poly_eql on two
119
+ Sets reaches Set#== through the user hook -- is not mistaken for the Set
120
+ walk's. */
121
+ #define SP_POLY_RECUR_SET_INSPECT 7
122
+ #define SP_POLY_RECUR_SET_EQ 8
123
+ #define SP_POLY_RECUR_SET_FLATTEN 9
124
+ sp_int sp_poly_recur_enter_inspect(sp_RbVal set);
125
+ sp_int sp_poly_recur_enter_eq(sp_RbVal set, sp_RbVal other);
126
+ sp_int sp_poly_recur_enter_flatten(sp_RbVal set);
127
+ void sp_poly_recur_leave(sp_int mark);
128
+ #endif /* SP_INSPECT_H */