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,211 @@
1
+ /* sp_process_status.c -- Process::Status runtime.
2
+ *
3
+ * The runtime is deliberately tiny: a heap-allocated struct holding
4
+ * pid + raw status, plus the W*() bit-field accessors from
5
+ * <sys/wait.h> wrapped as sp_int-returning helpers. Spinel's
6
+ * boxing layer (sp_box_obj) carries the type tag (cls_id =
7
+ * SP_BUILTIN_PROCESS_STATUS) so the codegen can dispatch
8
+ * `result[1].signaled?` to the right accessor at the call site.
9
+ *
10
+ * This TU is compiled into libspinel_rt.a; it does NOT need
11
+ * spinel_rt.h. It does include sp_alloc.h for sp_gc_alloc.
12
+ */
13
+ #include <stddef.h>
14
+ #include <stdint.h>
15
+ #include <stdio.h>
16
+ #include <string.h>
17
+ #include <sys/wait.h>
18
+
19
+ #include "sp_alloc.h" /* sp_RbVal, sp_gc_alloc, sp_raise_cls */
20
+ #include "sp_process_status.h"
21
+
22
+ /* The W*() macros must be handed an int LVALUE, not a cast expression. macOS
23
+ still defines them through the legacy `union wait` bridge --
24
+ `#define _W_INT(w) (*(int *)&(w))` -- so the macro takes the ADDRESS of its
25
+ argument, and `WIFEXITED((int)s)` fails to compile there with "cannot take
26
+ the address of an rvalue". glibc casts instead of dereferencing, which is
27
+ why this built on Linux. Each accessor below copies its sp_int argument
28
+ into `int st` first and passes that. */
29
+
30
+ /* CRuby answers nil for exitstatus on a signaled status and for termsig on
31
+ one that exited, and spinel already has a spelling for a nullable Integer:
32
+ SP_INT_NIL, which every renderer and `.nil?` understands. -1 is a real
33
+ Integer here -- `p st.termsig` printed -1 rather than nil. */
34
+ #define SP_STATUS_NIL SP_INT_NIL
35
+
36
+ /* ---- allocator ---- */
37
+
38
+ sp_ProcessStatus *sp_process_status_recv(sp_ProcessStatus *p, const char *meth) {
39
+ extern void sp_nil_recv(const char *meth); /* sp_str.c */
40
+ if (!p) sp_nil_recv(meth);
41
+ return p;
42
+ }
43
+
44
+ sp_ProcessStatus *sp_last_process_status(void) {
45
+ extern int sp_last_status, sp_last_pid; /* sp_system.c */
46
+ if (!sp_last_pid) return NULL;
47
+ sp_ProcessStatus *p = (sp_ProcessStatus *)sp_gc_alloc(sizeof(sp_ProcessStatus), NULL, NULL);
48
+ if (!p) sp_raise_cls("NoMemoryError", "out of memory");
49
+ p->pid = sp_last_pid;
50
+ p->status = sp_last_status;
51
+ return p;
52
+ }
53
+
54
+ sp_ProcessStatus *sp_process_status_new(sp_int pid, sp_int status) {
55
+ if (status < 0 || status > 0xFFFF) {
56
+ sp_raise_cls("ArgumentError", "invalid status word");
57
+ }
58
+ sp_ProcessStatus *p = (sp_ProcessStatus *)sp_gc_alloc(sizeof(sp_ProcessStatus), NULL, NULL);
59
+ if (!p) sp_raise_cls("NoMemoryError", "out of memory");
60
+ p->pid = pid;
61
+ p->status = status;
62
+ return p;
63
+ }
64
+
65
+ /* sp_box_process_status is declared in sp_alloc.h (with a forward
66
+ struct decl, so sp_alloc.h does not need to include
67
+ sp_process_status.h). The impl lives here so a TU that links
68
+ against the runtime (e.g. sp_process.c) does not need to
69
+ include sp_process_status.h to box a Status. */
70
+ sp_RbVal sp_box_process_status(sp_ProcessStatus *p) {
71
+ return sp_box_obj(p, SP_BUILTIN_PROCESS_STATUS);
72
+ }
73
+
74
+ /* ---- predicates ---- */
75
+
76
+ int sp_process_status_exited_p(sp_int s) {
77
+ int st = (int)s;
78
+ return WIFEXITED(st) ? 1 : 0;
79
+ }
80
+
81
+ int sp_process_status_signaled_p(sp_int s) {
82
+ int st = (int)s;
83
+ return WIFSIGNALED(st) ? 1 : 0;
84
+ }
85
+
86
+ int sp_process_status_coredump_p(sp_int s) {
87
+ int st = (int)s;
88
+ if (!WIFSIGNALED(st)) return 0;
89
+ return WCOREDUMP(st) ? 1 : 0;
90
+ }
91
+
92
+ /* Tri-state, because CRuby's is: true when the process exited with status 0,
93
+ false when it exited with anything else, and NIL when it did not exit
94
+ normally at all. -1 is that nil; the call site boxes it. */
95
+ int sp_process_status_success_p(sp_int s) {
96
+ int st = (int)s;
97
+ if (!WIFEXITED(st)) return -1;
98
+ return WEXITSTATUS(st) == 0 ? 1 : 0;
99
+ }
100
+
101
+ /* ---- accessors ---- */
102
+
103
+ sp_int sp_process_status_pid(sp_int s) {
104
+ /* The runtime only sees the status word (the pid travels alongside
105
+ in the boxed struct). Returning the lower byte of the status is
106
+ wrong -- the pid is a separate field. The pid is in the boxed
107
+ struct, accessible via sp_ProcessStatus*.pid. To keep the
108
+ signature simple the accessor takes the status word; the pid
109
+ access is plumbed via a separate sp_process_status_pid_of helper
110
+ that takes the boxed struct. */
111
+ /* This path is unused: the codegen path uses
112
+ sp_process_status_pid_of for the boxed struct. */
113
+ (void)s;
114
+ return 0;
115
+ }
116
+
117
+ sp_int sp_process_status_exitstatus(sp_int s) {
118
+ int st = (int)s;
119
+ if (!WIFEXITED(st)) return SP_STATUS_NIL;
120
+ return (sp_int)WEXITSTATUS(st);
121
+ }
122
+
123
+ sp_int sp_process_status_termsig(sp_int s) {
124
+ int st = (int)s;
125
+ if (!WIFSIGNALED(st)) return SP_STATUS_NIL;
126
+ return (sp_int)WTERMSIG(st);
127
+ }
128
+
129
+ /* ---- boxed-struct pid access ---- */
130
+ /* The codegen needs the pid of a boxed Process::Status. The runtime
131
+ accessor signatures above take the raw status word, so the pid
132
+ accessor goes through a separate helper that takes the boxed
133
+ struct directly. */
134
+
135
+ sp_int sp_process_status_pid_of(sp_ProcessStatus *st) {
136
+ return st->pid;
137
+ }
138
+
139
+ /* ---- equality ---- */
140
+
141
+ int sp_process_status_eq(sp_int a, sp_int b) {
142
+ return a == b;
143
+ }
144
+
145
+ /* ---- to_s / inspect ----
146
+ *
147
+ * CRuby renders:
148
+ * exited normally: "exit 0" (or "exited" prefix for inspect)
149
+ * signaled: "signal 9 (SIGKILL)" (or "signaled" prefix for inspect)
150
+ * unknown: ""? (CRuby raises; we return a generic string)
151
+ *
152
+ * The output goes into a static buffer; the codegen copies it via
153
+ * sp_str_dup_external when boxing the result as a String.
154
+ */
155
+
156
+ static const char *sp_signal_name(int sig) {
157
+ /* A short list. CRuby maps every signal; we cover the common ones
158
+ so dvtm's signal detection reads naturally in log output. */
159
+ switch (sig) {
160
+ case 1: return "SIGHUP";
161
+ case 2: return "SIGINT";
162
+ case 3: return "SIGQUIT";
163
+ case 4: return "SIGILL";
164
+ case 6: return "SIGABRT";
165
+ case 8: return "SIGFPE";
166
+ case 9: return "SIGKILL";
167
+ case 11: return "SIGSEGV";
168
+ case 13: return "SIGPIPE";
169
+ case 15: return "SIGTERM";
170
+ case 24: return "SIGXCPU";
171
+ default: return NULL;
172
+ }
173
+ }
174
+
175
+ static SP_TLS char sp_status_buf[96];
176
+
177
+ const char *sp_process_status_to_s(sp_int pid, sp_int s, int is_inspect) {
178
+ /* CRuby's pst_message: "pid N", then how the child ended */
179
+ const char *prefix = is_inspect ? "#<Process::Status: " : "";
180
+ const char *suffix = is_inspect ? ">" : "";
181
+ int st = (int)s;
182
+ if (WIFEXITED(st)) {
183
+ snprintf(sp_status_buf, sizeof sp_status_buf,
184
+ "%spid %lld exit %d%s", prefix, (long long)pid, WEXITSTATUS(st), suffix);
185
+ }
186
+ else if (WIFSIGNALED(st)) {
187
+ int sig = WTERMSIG(st);
188
+ const char *name = sp_signal_name(sig);
189
+ const char *core = WCOREDUMP(st) ? " (core dumped)" : "";
190
+ if (name) {
191
+ snprintf(sp_status_buf, sizeof sp_status_buf,
192
+ "%spid %lld %s (signal %d)%s%s", prefix, (long long)pid, name, sig, core, suffix);
193
+ }
194
+ else {
195
+ snprintf(sp_status_buf, sizeof sp_status_buf,
196
+ "%spid %lld signal %d%s%s", prefix, (long long)pid, sig, core, suffix);
197
+ }
198
+ }
199
+ else {
200
+ snprintf(sp_status_buf, sizeof sp_status_buf,
201
+ "%spid %lld status 0x%llx%s", prefix, (long long)pid, (unsigned long long)s, suffix);
202
+ }
203
+ return sp_status_buf;
204
+ }
205
+
206
+ /* A fresh String each call: the rendering's static buffer is shared, and
207
+ `[$?.to_s, $?.inspect]` read the second rendering twice. */
208
+ const char *sp_process_status_str(sp_ProcessStatus *p, int is_inspect) {
209
+ if (!p) return sp_str_dup_external(is_inspect ? "nil" : "");
210
+ return sp_str_dup_external(sp_process_status_to_s(p->pid, p->status, is_inspect));
211
+ }
@@ -0,0 +1,66 @@
1
+ /* sp_process_status.h -- Process::Status value type.
2
+ *
3
+ * Process.waitpid2 returns [pid, status] where status is a
4
+ * Process::Status instance. This file provides the boxed runtime
5
+ * type plus the small set of predicates and accessors CRuby
6
+ * exposes on Process::Status.
7
+ *
8
+ * Layout: a single int (the raw waitpid(2) status word) plus the
9
+ * pid of the child the status is from. Heap-allocated because
10
+ * the per-instance id-tag is needed for the typecheck on
11
+ * `result[1].signaled?` and the like; the struct itself is small
12
+ * (16 bytes) so the allocation is cheap.
13
+ */
14
+ #ifndef SP_PROCESS_STATUS_H
15
+ #define SP_PROCESS_STATUS_H
16
+
17
+ #include <stdint.h>
18
+ #include "sp_types.h" /* sp_int */
19
+
20
+ typedef struct sp_ProcessStatus_s {
21
+ sp_int pid;
22
+ sp_int status; /* the raw waitpid(2) status word */
23
+ } sp_ProcessStatus;
24
+
25
+ /* Boxed new: takes pid + raw status word. Returns a sp_ProcessStatus *
26
+ on the GC heap. Raises TypeError on a non-Integer pid or negative
27
+ status. */
28
+ sp_ProcessStatus *sp_process_status_new(sp_int pid, sp_int status);
29
+
30
+ /* `$?`: the status of the last child waited for (Kernel#system, a
31
+ backtick, Process.wait*), or NULL -- nil -- before any. A new object on
32
+ each read, carrying the raw status word as it was stored. */
33
+ sp_ProcessStatus *sp_last_process_status(void);
34
+
35
+ /* A reader's receiver: NULL is nil, which has none of the readers, so it
36
+ raises NoMethodError ("undefined method 'exitstatus' for nil"). */
37
+ sp_ProcessStatus *sp_process_status_recv(sp_ProcessStatus *p, const char *meth);
38
+
39
+ /* to_s / inspect of a status, or of nil ("" / "nil") for NULL */
40
+ const char *sp_process_status_str(sp_ProcessStatus *p, int is_inspect);
41
+
42
+ /* Predicates. All return 0 or 1 except success_p, which is tri-state: -1
43
+ means CRuby's nil (the process did not exit normally). */
44
+ int sp_process_status_exited_p(sp_int s);
45
+ int sp_process_status_signaled_p(sp_int s);
46
+ int sp_process_status_coredump_p(sp_int s);
47
+ int sp_process_status_success_p(sp_int s);
48
+
49
+ /* Accessors. CRuby semantics: exited? -> exitstatus, signaled? -> termsig;
50
+ the un-applicable accessor answers nil, carried as SP_INT_NIL -- the
51
+ nullable-Integer sentinel the rest of the runtime uses, so `p st.termsig`
52
+ renders nil and `st.termsig.nil?` is true. */
53
+ sp_int sp_process_status_pid(sp_int s);
54
+ sp_int sp_process_status_exitstatus(sp_int s);
55
+ sp_int sp_process_status_termsig(sp_int s);
56
+
57
+ /* Render the status to a string for to_s / inspect. The result lives
58
+ in a static buffer; the runtime copies it to a GC-heap string for
59
+ return. */
60
+ const char *sp_process_status_to_s(sp_int pid, sp_int s, int is_inspect);
61
+
62
+ /* Equality: two Process::Status values are equal iff their status
63
+ words are equal. pid is not compared. */
64
+ int sp_process_status_eq(sp_int a, sp_int b);
65
+
66
+ #endif
@@ -0,0 +1,287 @@
1
+ /* sp_random.c -- the shared Kernel-level PRNG stream and the Random
2
+ instance methods (see sp_random.h). */
3
+ #include <time.h>
4
+ #include <string.h>
5
+ #include "sp_random.h"
6
+ #include "sp_crypto.h" /* sp_crypto_entropy: the one secure source */
7
+ #include "sp_alloc.h"
8
+ #include <math.h> /* isnan/isinf for the EDOM domain checks */ /* sp_str_alloc / sp_str_set_len / sp_float_to_s / sp_raise_cls / sp_gc_alloc */
9
+ #include "sp_format.h" /* sp_Range_inspect */
10
+ #include "sp_str.h" /* sp_sprintf (defined in the generated TU) */
11
+ #include "sp_range.h" /* sp_range_int_only */
12
+
13
+ /* PCG-XSH-RR constants (64-bit state, 32-bit output). */
14
+ #define SP_PCG_MULT 6364136223846793005ULL
15
+ #define SP_PCG_INC 1442695040888963407ULL
16
+
17
+ uint32_t sp_pcg32_adv(uint64_t *state) {
18
+ uint64_t old = *state;
19
+ *state = old * SP_PCG_MULT + SP_PCG_INC;
20
+ /* output: xorshift-high folds the state, then a random rotate */
21
+ uint32_t xs = (uint32_t)(((old >> 18u) ^ old) >> 27u);
22
+ uint32_t rot = (uint32_t)(old >> 59u);
23
+ return (xs >> rot) | (xs << ((32 - rot) & 31));
24
+ }
25
+
26
+ /* The PCG init sequence: zero the state, step, add the seed, then mix.
27
+ The extra mixing rounds keep nearby seeds from producing correlated
28
+ early output. */
29
+ void sp_pcg_seed(uint64_t *state, uint64_t seed) {
30
+ *state = 0;
31
+ (void)sp_pcg32_adv(state);
32
+ *state += seed;
33
+ for (int i = 0; i < 10; i++) (void)sp_pcg32_adv(state);
34
+ }
35
+
36
+ /* Per-worker (SP_TLS) in the threaded build: the generator has no
37
+ internal lock, so a shared stream would race across workers. */
38
+ static SP_TLS uint64_t sp_krand_state;
39
+ static SP_TLS int sp_krand_seeded;
40
+
41
+ void sp_krand_srand(uint64_t seed) {
42
+ sp_pcg_seed(&sp_krand_state, seed);
43
+ sp_krand_seeded = 1;
44
+ }
45
+
46
+ uint64_t sp_krand_next(void) {
47
+ if (!sp_krand_seeded) {
48
+ /* Auto-seed on first draw, mirroring CRuby's startup seeding so
49
+ unseeded rand/shuffle/sample vary per run. time() alone repeats
50
+ within a second; mix in clock() and the per-worker state address. */
51
+ sp_krand_srand((uint64_t)time(NULL) ^ ((uint64_t)clock() << 24) ^
52
+ (uint64_t)(uintptr_t)&sp_krand_state);
53
+ }
54
+ uint64_t hi = sp_pcg32_adv(&sp_krand_state);
55
+ return (hi << 32) | sp_pcg32_adv(&sp_krand_state);
56
+ }
57
+
58
+ sp_int sp_krand_below(sp_int n) {
59
+ if (n <= 0) return 0;
60
+ /* uniform in [0, n) without modulo bias: reject draws below the
61
+ wrap-around threshold (0 for powers of two, so the loop is rare) */
62
+ uint64_t umax = (uint64_t)n;
63
+ uint64_t threshold = (uint64_t)(-(int64_t)umax) % umax;
64
+ uint64_t r;
65
+ do {
66
+ r = sp_krand_next();
67
+ } while (r < threshold);
68
+ return (sp_int)(r % umax);
69
+ }
70
+
71
+ sp_float sp_krand_float(void) {
72
+ return (sp_float)(sp_krand_next() >> 11) / (sp_float)(1ULL << 53);
73
+ }
74
+
75
+ /* ---- Random instance methods ---- */
76
+
77
+ /* The default stream is a static, not a GC allocation -- but the class-method
78
+ forms (`Random.bytes`, `Random.rand`) hand it to the same entry points an
79
+ instance uses, and those root their receiver, which puts a .bss address on
80
+ the mark path. Lay a 0xfd skip byte directly before it so sp_gc_mark's
81
+ tag-byte protocol bails out instead of reading whatever precedes it as a GC
82
+ header: under SPINEL_GC_STRESS=1 that read reached a bogus scan hook and
83
+ crashed inside the mark walk. Same shape, and the same reason, as the root
84
+ fiber in sp_fiber.c: the guard array is exactly one alignment unit, so its
85
+ last byte always directly precedes the struct with no padding in between. */
86
+ static SP_TLS struct { char guard[_Alignof(sp_Random)]; sp_Random r; } sp_random_default_box
87
+ = { .guard = { [_Alignof(sp_Random) - 1] = (char)0xfd } };
88
+ #define sp_random_default (sp_random_default_box.r)
89
+ uint64_t sp_random_next(sp_Random *r) {SP_GC_ROOT(r);
90
+ if (r == &sp_random_default) return sp_krand_next();
91
+ uint64_t hi = sp_pcg32_adv(&r->state);
92
+ return (hi << 32) | sp_pcg32_adv(&r->state);
93
+ }
94
+ sp_Random *sp_Random_new(sp_int seed) {
95
+ sp_Random *r = (sp_Random *)sp_gc_alloc(sizeof(sp_Random), NULL, NULL);
96
+ sp_pcg_seed(&r->state, (uint64_t)seed);
97
+ r->seed = seed;
98
+ return r;
99
+ }
100
+ /* Random#dup / #clone: a distinct generator that continues from the same state. */
101
+ sp_Random *sp_Random_dup(sp_Random *r) {
102
+ if (!r) return NULL; /* nil.dup is nil */
103
+ SP_GC_ROOT(r);
104
+ sp_Random *d = (sp_Random *)sp_gc_alloc(sizeof(sp_Random), NULL, NULL);
105
+ *d = *r;
106
+ return d;
107
+ }
108
+ /* Random.new(Float): CRuby truncates the float to an integer seed. A plain
109
+ (sp_int) cast is UB when the truncated value is out of range, so seed from
110
+ the in-range truncation when it fits and from the raw float bits otherwise
111
+ -- always deterministic (same float -> same stream), which is the actual
112
+ contract (the exact sequence is not MT19937 anyway). */
113
+ sp_Random *sp_Random_new_float(sp_float f) {
114
+ uint64_t s;
115
+ if (f >= -9.2233720368547758e18 && f < 9.2233720368547758e18) {
116
+ s = (uint64_t)(int64_t)f;
117
+ }
118
+ else {
119
+ uint64_t bits; memcpy(&bits, &f, sizeof bits);
120
+ s = bits;
121
+ }
122
+ sp_Random *r = (sp_Random *)sp_gc_alloc(sizeof(sp_Random), NULL, NULL);
123
+ sp_pcg_seed(&r->state, s);
124
+ r->seed = (sp_int)s;
125
+ return r;
126
+ }
127
+ /* Random#seed: the seed the instance was constructed from. */
128
+ sp_int sp_Random_seed(sp_Random *r) { return r ? r->seed : 0; }
129
+ /* Random#== compares by internal state (two same-seed, same-position instances
130
+ are equal; advancing one makes them differ). */
131
+ sp_bool sp_Random_eq(sp_Random *a, sp_Random *b) {
132
+ if (a == b) return TRUE;
133
+ if (!a || !b) return FALSE;
134
+ return (a->state == b->state && a->seed == b->seed) ? TRUE : FALSE;
135
+ }
136
+ /* Random.new with no seed: OS-entropy-ish auto seed. time() alone made two
137
+ instances created in the same second identical; mix in a per-thread counter
138
+ and the object address so every instance gets its own stream. */
139
+ sp_Random *sp_Random_new_auto(void) {
140
+ static SP_TLS uint64_t sp_random_auto_ctr = 0;
141
+ sp_Random *r = (sp_Random *)sp_gc_alloc(sizeof(sp_Random), NULL, NULL);
142
+ uint64_t e = (((uint64_t)time(NULL)) << 20) ^ (++sp_random_auto_ctr * 0x9E3779B97F4A7C15ULL) ^
143
+ (uint64_t)(uintptr_t)r;
144
+ sp_pcg_seed(&r->state, e);
145
+ r->seed = (sp_int)(e >> 1);
146
+ return r;
147
+ }
148
+ /* Random#rand(Range): an integer in the (int-endpoint) range, empty raises. */
149
+ sp_int sp_Random_rand_range(sp_Random *r, sp_Range rg) {SP_GC_ROOT(r);
150
+ sp_range_int_only(rg, "Random#rand"); /* (1..2.5): CRuby answers a Float */
151
+ /* an open side has no span: CRuby's Errno::EDOM, where the sentinel drew
152
+ from a span of the whole sp_int range */
153
+ if (rg.first == INTPTR_MIN || rg.last == INTPTR_MAX)
154
+ sp_raise_cls("Errno::EDOM", "Numerical argument out of domain");
155
+ sp_int lo = rg.first, hi = rg.excl ? rg.last - 1 : rg.last;
156
+ if (hi < lo) sp_raise_cls("ArgumentError", sp_sprintf("invalid argument - %s", sp_Range_inspect(&rg)));
157
+ if (!r) return lo;
158
+ return lo + (sp_int)(sp_random_next(r) % ((uint64_t)(hi - lo) + 1));
159
+ }
160
+ sp_int sp_Random_rand_int(sp_Random *r, sp_int n) {SP_GC_ROOT(r);
161
+ if (n <= 0) sp_raise_cls("ArgumentError", sp_sprintf("invalid argument - %lld", (long long)n));
162
+ if (!r) return 0;
163
+ return (sp_int)(sp_random_next(r) % (uint64_t)n);
164
+ }
165
+ sp_float sp_Random_rand_float(sp_Random *r) {SP_GC_ROOT(r);
166
+ if (!r) return 0.0;
167
+ return (sp_float)(sp_random_next(r) >> 11) / (sp_float)(1ULL << 53);
168
+ }
169
+ /* Random#rand(Float bound): a random Float in [0, bound). A non-positive bound
170
+ raises ArgumentError like the Integer form (MRI validates both). */
171
+ sp_float sp_Random_rand_float_bound(sp_Random *r, sp_float bound) {SP_GC_ROOT(r);
172
+ /* CRuby 4: a non-finite bound is Errno::EDOM; a 0.0 bound draws in [0,1) (#3049) */
173
+ if (isnan(bound) || isinf(bound))
174
+ sp_raise_cls("Errno::EDOM", "Numerical argument out of domain");
175
+ if (bound == 0.0) return sp_Random_rand_float(r);
176
+ if (bound < 0) sp_raise_cls("ArgumentError", sp_sprintf("invalid argument - %s", sp_float_to_s(bound)));
177
+ return sp_Random_rand_float(r) * bound;
178
+ }
179
+ /* Class-method forms (`Random.rand` / `Random.bytes`) share the default
180
+ instance, mirroring CRuby's Random::DEFAULT. Its draws redirect to the
181
+ shared Kernel stream (see sp_random_next), which lazily self-seeds and
182
+ is per-worker in the threaded build. */
183
+ sp_Random *sp_random_default_get(void) {
184
+ return &sp_random_default;
185
+ }
186
+ /* Random#bytes(n) -- n random bytes as a String, tagged ASCII-8BIT as CRuby
187
+ does. sp_str_set_len alone was not enough: #length counts UTF-8 units, and a
188
+ short draw is valid UTF-8 by chance often enough that Random.bytes(8).length
189
+ answered less than 8 about three times in a thousand. That is #3474, which
190
+ fixed urandom eight lines below and left this one. */
191
+ const char *sp_Random_bytes(sp_Random *r, sp_int n) {SP_GC_ROOT(r);
192
+ if (n < 0) sp_raise_cls("ArgumentError", "negative string size (or size too big)");
193
+ char *b = sp_str_alloc((size_t)n);
194
+ for (sp_int i = 0; i < n; i++) b[i] = (char)(sp_random_next(r) & 0xff);
195
+ b[n] = 0;
196
+ sp_str_set_len(b, (size_t)n);
197
+ sp_str_mark_binary(b);
198
+ return b;
199
+ }
200
+ /* Random#rand(Float range): a Float in [lo, hi) (or [lo, hi] for an inclusive
201
+ range, though the float boundary is effectively open). */
202
+ sp_float sp_Random_rand_float_range(sp_Random *r, sp_float lo, sp_float hi) {SP_GC_ROOT(r);
203
+ if (isnan(lo) || isinf(lo) || isnan(hi) || isinf(hi))
204
+ sp_raise_cls("Errno::EDOM", "Numerical argument out of domain");
205
+ return lo + sp_Random_rand_float(r) * (hi - lo);
206
+ }
207
+ /* Random.new_seed: a fresh nonneg seed drawn from the default stream. */
208
+ sp_int sp_Random_new_seed(void) {
209
+ sp_int s = (sp_int)(sp_random_next(sp_random_default_get()) >> 1);
210
+ return s < 0 ? -s : s;
211
+ }
212
+ /* Random.urandom(n): n random bytes as a String (spinel has no real OS entropy
213
+ source wired up, so this draws from a freshly time-seeded stream). */
214
+ const char *sp_Random_urandom(sp_int n) {
215
+ if (n < 0) sp_raise_cls("ArgumentError", "negative string size");
216
+ /* CRuby's Random.urandom IS the OS entropy source, and this was a PCG seeded
217
+ from time()/clock() -- a deterministic-per-run stand-in, from before the
218
+ tree had a real source. It does now: sp_crypto_entropy is the one place
219
+ that decides what counts as secure, and it fails closed. A caller of this
220
+ name is minting something that has to be unguessable. */
221
+ char *b = sp_str_alloc((size_t)n);
222
+ { sp_int off = 0;
223
+ while (off < n) {
224
+ int chunk = (int)((n - off) > SPC_RANDOM_MAX ? SPC_RANDOM_MAX : (n - off));
225
+ if (!sp_crypto_entropy((unsigned char *)b + off, chunk))
226
+ sp_raise_cls("RuntimeError", "Random.urandom: no secure random source available");
227
+ off += chunk;
228
+ }
229
+ }
230
+ b[n] = 0;
231
+ sp_str_set_len(b, (size_t)n);
232
+ /* CRuby hands back ASCII-8BIT, so #length is the byte count. Without the
233
+ mark, a short draw that happens to spell valid UTF-8 (about 1.5% of
234
+ 4-byte draws) counted code points and answered short (#3474). */
235
+ sp_str_mark_binary(b);
236
+ return b;
237
+ }
238
+ /* Random#inspect / #to_s: CRuby's default object rendering (the seed is not
239
+ part of it; the address matches CRuby's zero-padded 16-digit form). */
240
+ const char *sp_Random_inspect(sp_Random *r) {SP_GC_ROOT(r);
241
+ /* and its instance variables, as CRuby lists an object's */
242
+ void *t = sp_ivtbl_inspect_fn ? sp_ivtbl_get(r) : NULL;
243
+ if (t) return sp_sprintf("#<Random:0x%016llx%s>", (unsigned long long)(uintptr_t)r, sp_ivtbl_inspect_fn(t));
244
+ return sp_sprintf("#<Random:0x%016llx>", (unsigned long long)(uintptr_t)r);
245
+ }
246
+ /* Kernel#srand: seed the shared Kernel stream and remember the previous
247
+ seed, which srand returns (CRuby returns the prior seed, not the new
248
+ one). Every rand form -- bare/int/range, shuffle, sample, the Random
249
+ default instance -- draws from that one stream, so a single srand makes
250
+ them all reproducible. */
251
+ static SP_TLS sp_int sp_kernel_seed = 0;
252
+ sp_int sp_kernel_srand(sp_int seed) {
253
+ sp_int prev = sp_kernel_seed;
254
+ sp_kernel_seed = seed;
255
+ sp_krand_srand((uint64_t)seed);
256
+ return prev;
257
+ }
258
+ /* Random#rand(Bignum bound): a uniform Bigint in [0, bound). Composed from
259
+ 32-bit chunks of the instance stream; accumulating two extra chunks past
260
+ the bound keeps the modulo bias around 2^-64. A non-positive bound raises
261
+ ArgumentError like the sp_int form (#3058). */
262
+ typedef struct sp_Bigint sp_Bigint;
263
+ extern sp_Bigint *sp_bigint_new_int(int64_t v);
264
+ extern sp_Bigint *sp_bigint_mul(sp_Bigint *a, sp_Bigint *b);
265
+ extern sp_Bigint *sp_bigint_add(sp_Bigint *a, sp_Bigint *b);
266
+ extern sp_Bigint *sp_bigint_mod(sp_Bigint *a, sp_Bigint *b);
267
+ extern int sp_bigint_cmp(sp_Bigint *a, sp_Bigint *b);
268
+ extern const char *sp_bigint_to_s(sp_Bigint *b);
269
+ sp_Bigint *sp_bigint_rand(sp_Random *r, sp_Bigint *bound) {SP_GC_ROOT(r);
270
+ SP_GC_ROOT(bound);
271
+ sp_Bigint *zero = sp_bigint_new_int(0);
272
+ SP_GC_ROOT(zero);
273
+ if (sp_bigint_cmp(bound, zero) <= 0)
274
+ sp_raise_cls("ArgumentError", sp_sprintf("invalid argument - %s", sp_bigint_to_s(bound)));
275
+ sp_Bigint *acc = zero;
276
+ SP_GC_ROOT(acc);
277
+ sp_Bigint *b32 = sp_bigint_new_int((int64_t)1 << 32);
278
+ SP_GC_ROOT(b32);
279
+ int extra = 2;
280
+ while (extra > 0) {
281
+ sp_Bigint *chunk = sp_bigint_new_int((int64_t)(sp_random_next(r) & 0xffffffffULL));
282
+ SP_GC_ROOT(chunk);
283
+ acc = sp_bigint_add(sp_bigint_mul(acc, b32), chunk);
284
+ if (sp_bigint_cmp(acc, bound) >= 0) extra--;
285
+ }
286
+ return sp_bigint_mod(acc, bound);
287
+ }
@@ -0,0 +1,52 @@
1
+ /* sp_random.h -- the shared Kernel-level PRNG stream (lib/sp_random.c).
2
+ *
3
+ * PCG-XSH-RR, the generator mruby's mruby-random uses: much better
4
+ * statistical quality than libc rand(), no cryptographic claims. One
5
+ * stream is shared by every runtime TU -- Kernel#rand/#srand, the array
6
+ * shuffle/sample paths (generated TU and libspinel_rt.a alike) and the
7
+ * Random default instance all draw from it, so a single `srand(n)`
8
+ * makes every consumer reproducible. */
9
+ #ifndef SP_RANDOM_H
10
+ #define SP_RANDOM_H
11
+
12
+ #include <stdint.h>
13
+ #include "sp_types.h"
14
+
15
+ uint32_t sp_pcg32_adv(uint64_t *state); /* one generator step */
16
+ void sp_pcg_seed(uint64_t *state, uint64_t seed);
17
+ uint64_t sp_krand_next(void); /* 64-bit draw, lazily self-seeded */
18
+ void sp_krand_srand(uint64_t seed);
19
+ sp_int sp_krand_below(sp_int n); /* uniform [0, n); 0 when n <= 0 */
20
+ sp_float sp_krand_float(void); /* uniform [0, 1) */
21
+
22
+ /* Random -- per-instance PRNG. CRuby uses MT19937; spinel uses PCG-XSH-RR
23
+ (above), so the *sequence* differs from MRI -- MT19937 is not part of
24
+ the Ruby spec -- but each Random object keeps its own reproducible
25
+ stream from its seed. The default instance is a window onto the shared
26
+ Kernel stream, so srand() governs it too. */
27
+ typedef struct { uint64_t state; sp_int seed; } sp_Random;
28
+ /* The default instance is private to sp_random.c: it is a guarded static
29
+ there (see the comment on its box) and is reached only through
30
+ sp_random_default_get below. */
31
+ uint64_t sp_random_next(sp_Random *r);
32
+ sp_Random *sp_Random_new(sp_int seed);
33
+ sp_Random *sp_Random_new_float(sp_float f);
34
+ sp_Random *sp_Random_new_auto(void);
35
+ sp_Random *sp_Random_dup(sp_Random *r);
36
+ sp_int sp_Random_seed(sp_Random *r);
37
+ sp_bool sp_Random_eq(sp_Random *a, sp_Random *b);
38
+ sp_int sp_Random_rand_range(sp_Random *r, sp_Range rg);
39
+ sp_int sp_Random_rand_int(sp_Random *r, sp_int n);
40
+ struct sp_Bigint;
41
+ struct sp_Bigint *sp_bigint_rand(sp_Random *r, struct sp_Bigint *bound);
42
+ sp_float sp_Random_rand_float(sp_Random *r);
43
+ sp_float sp_Random_rand_float_bound(sp_Random *r, sp_float bound);
44
+ sp_float sp_Random_rand_float_range(sp_Random *r, sp_float lo, sp_float hi);
45
+ sp_Random *sp_random_default_get(void);
46
+ const char *sp_Random_bytes(sp_Random *r, sp_int n);
47
+ sp_int sp_Random_new_seed(void);
48
+ const char *sp_Random_urandom(sp_int n);
49
+ const char *sp_Random_inspect(sp_Random *r);
50
+ sp_int sp_kernel_srand(sp_int seed);
51
+
52
+ #endif