mini_racer-csim 0.21.1.0

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.
@@ -0,0 +1,2899 @@
1
+ #include <stdatomic.h>
2
+ #include <stdint.h>
3
+ #include <stdio.h>
4
+ #include <stdlib.h>
5
+ #include <string.h>
6
+ #include <pthread.h>
7
+ #include <unistd.h>
8
+ #include <math.h>
9
+
10
+ #if defined(__linux__) && !defined(__GLIBC__)
11
+ // musl compatibility for glibc-linked libraries (e.g. libv8-node)
12
+ // some versions of libv8-node are accidentally linked against glibc symbols
13
+ // or compiled with a toolchain that emits these C23 compatibility symbols
14
+ unsigned long long __isoc23_strtoull(const char *nptr, char **endptr, int base) {
15
+ return strtoull(nptr, endptr, base);
16
+ }
17
+ unsigned long __isoc23_strtoul(const char *nptr, char **endptr, int base) {
18
+ return strtoul(nptr, endptr, base);
19
+ }
20
+ long long __isoc23_strtoll(const char *nptr, char **endptr, int base) {
21
+ return strtoll(nptr, endptr, base);
22
+ }
23
+ long __isoc23_strtol(const char *nptr, char **endptr, int base) {
24
+ return strtol(nptr, endptr, base);
25
+ }
26
+ double __isoc23_strtod(const char *nptr, char **endptr) {
27
+ return strtod(nptr, endptr);
28
+ }
29
+ float __isoc23_strtof(const char *nptr, char **endptr) {
30
+ return strtof(nptr, endptr);
31
+ }
32
+ #endif
33
+
34
+ #include "ruby.h"
35
+ #include "ruby/encoding.h"
36
+ #include "ruby/version.h"
37
+ #include "ruby/thread.h"
38
+ #include "serde.c"
39
+ #include "mini_racer_v8.h"
40
+
41
+ // for debugging
42
+ #define RB_PUTS(v) \
43
+ do { \
44
+ fflush(stdout); \
45
+ rb_funcall(rb_mKernel, rb_intern("puts"), 1, v); \
46
+ } while (0)
47
+
48
+ #if RUBY_API_VERSION_CODE < 3*10000+4*100 // 3.4.0
49
+ static inline void rb_thread_lock_native_thread(void)
50
+ {
51
+ // Without rb_thread_lock_native_thread, V8 in single-threaded mode is
52
+ // prone to crash with debug checks like this...
53
+ //
54
+ // # Fatal error in ../deps/v8/src/base/platform/platform-posix.cc, line 1350
55
+ // # Debug check failed: MainThreadIsCurrentThread().
56
+ //
57
+ // ...because the Ruby runtime clobbers thread-local variables when it
58
+ // context-switches threads. You have been warned.
59
+ }
60
+ #endif
61
+
62
+ #define countof(x) (sizeof(x) / sizeof(*(x)))
63
+ #define endof(x) ((x) + countof(x))
64
+
65
+ // mostly RO: assigned once by platform_set_flag1 while holding |flags_mtx|,
66
+ // from then on read-only and accessible without holding locks
67
+ int single_threaded;
68
+
69
+ // work around missing pthread_barrier_t on macOS
70
+ typedef struct Barrier
71
+ {
72
+ pthread_mutex_t mtx;
73
+ pthread_cond_t cv;
74
+ int count, in, out;
75
+ } Barrier;
76
+
77
+ static inline int barrier_init(Barrier *b, int count)
78
+ {
79
+ int r;
80
+
81
+ if ((r = pthread_mutex_init(&b->mtx, NULL)))
82
+ return r;
83
+ if ((r = pthread_cond_init(&b->cv, NULL))) {
84
+ pthread_mutex_destroy(&b->mtx);
85
+ return r;
86
+ }
87
+ b->count = count;
88
+ b->out = 0;
89
+ b->in = 0;
90
+ return 0;
91
+ }
92
+
93
+ static inline void barrier_destroy(Barrier *b)
94
+ {
95
+ pthread_mutex_destroy(&b->mtx);
96
+ pthread_cond_destroy(&b->cv);
97
+ }
98
+
99
+ static inline int barrier_wait(Barrier *b)
100
+ {
101
+ int last;
102
+
103
+ pthread_mutex_lock(&b->mtx);
104
+ while (b->out)
105
+ pthread_cond_wait(&b->cv, &b->mtx);
106
+ if (++b->in == b->count) {
107
+ b->in = 0;
108
+ b->out = b->count;
109
+ pthread_cond_broadcast(&b->cv);
110
+ } else {
111
+ do
112
+ pthread_cond_wait(&b->cv, &b->mtx);
113
+ while (b->in);
114
+ }
115
+ last = (--b->out == 0);
116
+ if (last)
117
+ pthread_cond_broadcast(&b->cv);
118
+ pthread_mutex_unlock(&b->mtx);
119
+ return last;
120
+ }
121
+
122
+ typedef struct Context
123
+ {
124
+ int depth; // call depth, protected by |rr_mtx|
125
+ // protected by |mtx|; RW for ruby threads, RO for v8 thread;
126
+ // atomic because context_stop (which can be called from other ruby
127
+ // threads) writes it without holding |mtx|, to avoid deadlocking
128
+ // 1=shut down v8, 2=free memory; note that only the v8 thread
129
+ // frees the memory and it intentionally stays around until
130
+ // the ruby object is gc'd, otherwise lifecycle management
131
+ // gets too complicated
132
+ atomic_int quit;
133
+ int verbose_exceptions;
134
+ int64_t idle_gc, max_memory, timeout;
135
+ struct State *pst; // used by v8 thread
136
+ VALUE procs; // array of js -> ruby callbacks
137
+ VALUE exception; // pending exception or Qnil
138
+ // Per-instantiate resolver Proc, Qnil when none active.
139
+ // module_instantiate saves/restores via rb_ensure to keep the slot
140
+ // consistent across exceptions and nested compile_module + instantiate
141
+ // from inside the resolver block. Concurrent Module#instantiate calls
142
+ // on the same Context from different Ruby threads race on this slot;
143
+ // callers must serialize externally — the rest of mini_racer's Context
144
+ // API has the same single-threaded-per-Context expectation.
145
+ VALUE resolve_block;
146
+ // Callable for `import(...)` in JS, or Qnil to reject dynamic imports
147
+ // with a clear error. Set via Context#dynamic_import_resolver=.
148
+ VALUE dynamic_import_resolver;
149
+ // Batched module-loader callbacks set by load_module_graph and persisted for
150
+ // the Context's lifetime (overwritten by the next load, freed on dispose), so
151
+ // registry-backed dynamic import() can reuse them after the call returns.
152
+ // Qnil until the first load_module_graph.
153
+ VALUE graph_resolve_block; // ->(edges) { [url|nil, ...] }
154
+ VALUE graph_fetch_block; // ->(urls) { [[source, cached_data]|nil, ...] }
155
+ Buf req, res; // ruby->v8 request/response, mediated by |mtx| and |cv|
156
+ Buf snapshot;
157
+ Buf host_namespace; // NUL-terminated global name to install host helpers on, or empty
158
+ pthread_t single_threaded_thr;
159
+ pid_t single_threaded_pid;
160
+ int single_threaded_thr_started;
161
+ // |rr_mtx| stands for "recursive ruby mutex"; it's used to exclude
162
+ // other ruby threads but allow reentrancy from the same ruby thread
163
+ // (think ruby->js->ruby->js calls)
164
+ pthread_mutex_t rr_mtx;
165
+ pthread_mutex_t mtx;
166
+ pthread_cond_t cv;
167
+ struct {
168
+ pthread_mutex_t mtx;
169
+ pthread_cond_t cv;
170
+ int cancel;
171
+ } wd; // watchdog
172
+ Barrier early_init, late_init;
173
+ } Context;
174
+
175
+ typedef struct Snapshot {
176
+ VALUE blob;
177
+ } Snapshot;
178
+
179
+ // GC-finalizer caveat: module_free cannot send a dispose RPC (would need
180
+ // to take rr_mtx without a reliable GVL guarantee). Handles freed here
181
+ // rely on State::~State() walking st.modules at isolate teardown — so
182
+ // long-lived Contexts with many short-lived Modules accumulate Persistents
183
+ // until the Context is disposed. Call Module#dispose explicitly to free
184
+ // eagerly.
185
+ typedef struct Module {
186
+ VALUE context; // parent Context VALUE (kept alive via mark)
187
+ VALUE cached_data; // ASCII-8BIT String or Qnil
188
+ int32_t handle_id; // 0 if uninitialized or already freed
189
+ int cache_rejected;
190
+ int disposed;
191
+ } Module;
192
+
193
+ // GC-finalizer caveat: script_free cannot send a dispose RPC (would need
194
+ // to take rr_mtx without a reliable GVL guarantee). Handles freed here
195
+ // rely on State::~State() walking st.scripts at isolate teardown — so
196
+ // long-lived Contexts with many short-lived Scripts accumulate Persistents
197
+ // until the Context is disposed. Call Script#dispose explicitly to free
198
+ // eagerly.
199
+ typedef struct Script {
200
+ VALUE context; // parent Context VALUE (kept alive via mark)
201
+ VALUE cached_data; // ASCII-8BIT String or Qnil
202
+ int32_t handle_id; // 0 if uninitialized or already freed
203
+ int cache_rejected;
204
+ int disposed;
205
+ } Script;
206
+
207
+ static void context_destroy(Context *c);
208
+ static void context_free(void *arg);
209
+ static void context_mark(void *arg);
210
+ static size_t context_size(const void *arg);
211
+
212
+ static const rb_data_type_t context_type = {
213
+ .wrap_struct_name = "mini_racer/context",
214
+ .function = {
215
+ .dfree = context_free,
216
+ .dmark = context_mark,
217
+ .dsize = context_size,
218
+ },
219
+ };
220
+
221
+ static void snapshot_free(void *arg);
222
+ static void snapshot_mark(void *arg);
223
+ static size_t snapshot_size(const void *arg);
224
+
225
+ static const rb_data_type_t snapshot_type = {
226
+ .wrap_struct_name = "mini_racer/snapshot",
227
+ .function = {
228
+ .dfree = snapshot_free,
229
+ .dmark = snapshot_mark,
230
+ .dsize = snapshot_size,
231
+ },
232
+ };
233
+
234
+ static void script_free(void *arg);
235
+ static void script_mark(void *arg);
236
+ static size_t script_size(const void *arg);
237
+
238
+ static const rb_data_type_t script_type = {
239
+ .wrap_struct_name = "mini_racer/script",
240
+ .function = {
241
+ .dfree = script_free,
242
+ .dmark = script_mark,
243
+ .dsize = script_size,
244
+ },
245
+ };
246
+
247
+ static void module_free(void *arg);
248
+ static void module_mark(void *arg);
249
+ static size_t module_size(const void *arg);
250
+
251
+ static const rb_data_type_t module_type = {
252
+ .wrap_struct_name = "mini_racer/module",
253
+ .function = {
254
+ .dfree = module_free,
255
+ .dmark = module_mark,
256
+ .dsize = module_size,
257
+ },
258
+ };
259
+
260
+ static VALUE platform_init_error;
261
+ static VALUE context_disposed_error;
262
+ static VALUE parse_error;
263
+ static VALUE memory_error;
264
+ static VALUE script_error;
265
+ static VALUE runtime_error;
266
+ static VALUE internal_error;
267
+ static VALUE snapshot_error;
268
+ static VALUE terminated_error;
269
+ static VALUE context_class;
270
+ static VALUE snapshot_class;
271
+ static VALUE script_class;
272
+ static VALUE module_class;
273
+ static VALUE date_time_class;
274
+ static VALUE binary_class;
275
+ static VALUE js_function_class;
276
+
277
+ static ID id_filename;
278
+ static ID id_cached_data;
279
+ static ID id_produce_cache;
280
+
281
+ static pthread_mutex_t flags_mtx = PTHREAD_MUTEX_INITIALIZER;
282
+ static Buf flags; // protected by |flags_mtx|
283
+
284
+ // arg == &(struct rendezvous_nogvl){...}
285
+ static void *rendezvous_callback(void *arg);
286
+
287
+ // note: must be stack-allocated or VALUEs won't be visible to ruby's GC
288
+ typedef struct State
289
+ {
290
+ VALUE a, b;
291
+ uint8_t verbatim_keys:1;
292
+ } State;
293
+
294
+ // note: must be stack-allocated or VALUEs won't be visible to ruby's GC
295
+ typedef struct DesCtx
296
+ {
297
+ State *tos;
298
+ VALUE refs; // object refs array
299
+ uint8_t transcode_latin1:1;
300
+ char err[64];
301
+ State stack[512];
302
+ } DesCtx;
303
+
304
+ struct rendezvous_nogvl
305
+ {
306
+ Context *context;
307
+ Buf *req, *res;
308
+ };
309
+
310
+ struct rendezvous_des
311
+ {
312
+ DesCtx *d;
313
+ Buf *res;
314
+ };
315
+
316
+ static void DesCtx_init(DesCtx *c)
317
+ {
318
+ c->tos = c->stack;
319
+ c->refs = rb_ary_new();
320
+ *c->tos = (State){Qundef, Qundef, /*verbatim_keys*/0};
321
+ *c->err = '\0';
322
+ c->transcode_latin1 = 1; // convert to utf8
323
+ }
324
+
325
+ static void put(DesCtx *c, VALUE v)
326
+ {
327
+ VALUE *a, *b;
328
+
329
+ if (*c->err)
330
+ return;
331
+ a = &c->tos->a;
332
+ b = &c->tos->b;
333
+ switch (TYPE(*a)) {
334
+ case T_ARRAY:
335
+ rb_ary_push(*a, v);
336
+ break;
337
+ case T_HASH:
338
+ if (*b == Qundef) {
339
+ *b = v;
340
+ } else {
341
+ if (!c->tos->verbatim_keys)
342
+ *b = rb_funcall(*b, rb_intern("to_s"), 0);
343
+ rb_hash_aset(*a, *b, v);
344
+ *b = Qundef;
345
+ }
346
+ break;
347
+ case T_UNDEF:
348
+ *a = v;
349
+ break;
350
+ default:
351
+ snprintf(c->err, sizeof(c->err), "bad state");
352
+ return;
353
+ }
354
+ }
355
+
356
+ static void push(DesCtx *c, VALUE v)
357
+ {
358
+ if (*c->err)
359
+ return;
360
+ if (c->tos == endof(c->stack)) {
361
+ snprintf(c->err, sizeof(c->err), "stack overflow");
362
+ return;
363
+ }
364
+ *++c->tos = (State){v, Qundef, /*verbatim_keys*/0};
365
+ rb_ary_push(c->refs, v);
366
+ }
367
+
368
+ // see also des_named_props_end
369
+ static void pop(DesCtx *c)
370
+ {
371
+ if (*c->err)
372
+ return;
373
+ if (c->tos == c->stack) {
374
+ snprintf(c->err, sizeof(c->err), "stack underflow");
375
+ return;
376
+ }
377
+ put(c, (*c->tos--).a);
378
+ }
379
+
380
+ static void des_null(void *arg)
381
+ {
382
+ put(arg, Qnil);
383
+ }
384
+
385
+ static void des_undefined(void *arg)
386
+ {
387
+ put(arg, Qnil);
388
+ }
389
+
390
+ static void des_bool(void *arg, int v)
391
+ {
392
+ put(arg, v ? Qtrue : Qfalse);
393
+ }
394
+
395
+ static void des_int(void *arg, int64_t v)
396
+ {
397
+ put(arg, LONG2FIX(v));
398
+ }
399
+
400
+ static void des_num(void *arg, double v)
401
+ {
402
+ if (isfinite(v) && v == trunc(v) && v >= INT64_MIN && v <= INT64_MAX) {
403
+ put(arg, LONG2FIX(v));
404
+ } else {
405
+ put(arg, DBL2NUM(v));
406
+ }
407
+ }
408
+
409
+ static void des_date(void *arg, double v)
410
+ {
411
+ double sec, usec;
412
+
413
+ if (!isfinite(v))
414
+ rb_raise(rb_eRangeError, "invalid Date");
415
+ sec = v/1e3;
416
+ usec = 1e3 * fmod(v, 1e3);
417
+ put(arg, rb_time_new(sec, usec));
418
+ }
419
+
420
+ // note: v8 stores bigints in 1's complement, ruby in 2's complement,
421
+ // so we have to take additional steps to ensure correct conversion
422
+ static void des_bigint(void *arg, const void *p, size_t n, int sign)
423
+ {
424
+ VALUE v;
425
+ size_t i;
426
+ DesCtx *c;
427
+ unsigned long *a, t, limbs[65]; // +1 to suppress sign extension
428
+
429
+ c = arg;
430
+ if (*c->err)
431
+ return;
432
+ if (n > sizeof(limbs) - sizeof(*limbs)) {
433
+ snprintf(c->err, sizeof(c->err), "bigint too big");
434
+ return;
435
+ }
436
+ a = limbs;
437
+ t = 0;
438
+ for (i = 0; i < n; a++, i += sizeof(*a)) {
439
+ memcpy(a, (char *)p + i, sizeof(*a));
440
+ t = *a;
441
+ }
442
+ if (t >> 63)
443
+ *a++ = 0; // suppress sign extension
444
+ v = rb_big_unpack(limbs, a-limbs);
445
+ if (sign < 0) {
446
+ // rb_big_unpack returns T_FIXNUM for smallish bignums
447
+ switch (TYPE(v)) {
448
+ case T_BIGNUM:
449
+ v = rb_big_mul(v, LONG2FIX(-1));
450
+ break;
451
+ case T_FIXNUM:
452
+ v = LONG2FIX(-1 * FIX2LONG(v));
453
+ break;
454
+ default:
455
+ abort();
456
+ }
457
+ }
458
+ put(c, v);
459
+ }
460
+
461
+ static void des_string(void *arg, const char *s, size_t n)
462
+ {
463
+ put(arg, rb_utf8_str_new(s, n));
464
+ }
465
+
466
+ static VALUE str_encode_bang(VALUE v)
467
+ {
468
+ // TODO cache these? this function can get called often
469
+ return rb_funcall(v, rb_intern("encode!"), 1, rb_str_new_cstr("UTF-8"));
470
+ }
471
+
472
+ static void des_string8(void *arg, const uint8_t *s, size_t n)
473
+ {
474
+ rb_encoding *e;
475
+ DesCtx *c;
476
+ VALUE v;
477
+
478
+ c = arg;
479
+ if (*c->err)
480
+ return;
481
+ if (c->transcode_latin1) {
482
+ e = rb_enc_find("ISO-8859-1"); // TODO cache?
483
+ if (!e) {
484
+ snprintf(c->err, sizeof(c->err), "no ISO-8859-1 encoding");
485
+ return;
486
+ }
487
+ v = rb_enc_str_new((char *)s, n, e);
488
+ v = str_encode_bang(v); // cannot fail
489
+ } else {
490
+ v = rb_enc_str_new((char *)s, n, rb_ascii8bit_encoding());
491
+ }
492
+ put(c, v);
493
+ }
494
+
495
+ // des_string16: |s| is not word aligned
496
+ // des_string16: |n| is in bytes, not code points
497
+ static void des_string16(void *arg, const void *s, size_t n)
498
+ {
499
+ rb_encoding *e;
500
+ VALUE v, r;
501
+ DesCtx *c;
502
+ int exc;
503
+
504
+ c = arg;
505
+ if (*c->err)
506
+ return;
507
+ // TODO(bnoordhuis) replace this hack with something more principled
508
+ if (n == sizeof(js_function_marker) && !memcmp(js_function_marker, s, n))
509
+ return put(c, rb_funcall(js_function_class, rb_intern("new"), 0));
510
+ e = rb_enc_find("UTF-16LE"); // TODO cache?
511
+ if (!e) {
512
+ snprintf(c->err, sizeof(c->err), "no UTF16-LE encoding");
513
+ return;
514
+ }
515
+ v = rb_enc_str_new((char *)s, n, e);
516
+ // JS strings can contain unmatched or illegal surrogate pairs
517
+ // that Ruby won't decode; return the string as-is in that case
518
+ r = rb_protect(str_encode_bang, v, &exc);
519
+ if (exc) {
520
+ rb_set_errinfo(Qnil);
521
+ r = v;
522
+ }
523
+ put(c, r);
524
+ }
525
+
526
+ // ruby doesn't really have a concept of a byte array so store it as
527
+ // an 8-bit string instead; it's either that or a regular array of
528
+ // numbers, but the latter is markedly less efficient, storage-wise
529
+ static void des_arraybuffer(void *arg, const void *s, size_t n)
530
+ {
531
+ put(arg, rb_enc_str_new((char *)s, n, rb_ascii8bit_encoding()));
532
+ }
533
+
534
+ static void des_array_begin(void *arg)
535
+ {
536
+ push(arg, rb_ary_new());
537
+ }
538
+
539
+ static void des_array_end(void *arg)
540
+ {
541
+ pop(arg);
542
+ }
543
+
544
+ static void des_named_props_begin(void *arg)
545
+ {
546
+ push(arg, rb_hash_new());
547
+ }
548
+
549
+ // see also pop
550
+ static void des_named_props_end(void *arg)
551
+ {
552
+ DesCtx *c;
553
+
554
+ c = arg;
555
+ if (*c->err)
556
+ return;
557
+ if (c->tos == c->stack) {
558
+ snprintf(c->err, sizeof(c->err), "stack underflow");
559
+ return;
560
+ }
561
+ c->tos--; // dropped, no way to represent in ruby
562
+ }
563
+
564
+ static void des_object_begin(void *arg)
565
+ {
566
+ push(arg, rb_hash_new());
567
+ }
568
+
569
+ static void des_object_end(void *arg)
570
+ {
571
+ pop(arg);
572
+ }
573
+
574
+ static void des_map_begin(void *arg)
575
+ {
576
+ DesCtx *c;
577
+
578
+ c = arg;
579
+ push(c, rb_hash_new());
580
+ c->tos->verbatim_keys = 1; // don't stringify or intern keys
581
+ }
582
+
583
+ static void des_map_end(void *arg)
584
+ {
585
+ pop(arg);
586
+ }
587
+
588
+ static void des_object_ref(void *arg, uint32_t id)
589
+ {
590
+ DesCtx *c;
591
+ VALUE v;
592
+
593
+ c = arg;
594
+ v = rb_ary_entry(c->refs, id);
595
+ put(c, v);
596
+ }
597
+
598
+ static void des_error_begin(void *arg)
599
+ {
600
+ push(arg, rb_ary_new());
601
+ }
602
+
603
+ static void des_error_end(void *arg)
604
+ {
605
+ VALUE *a, h, message, stack, cause, newline;
606
+ DesCtx *c;
607
+
608
+ c = arg;
609
+ if (*c->err)
610
+ return;
611
+ if (c->tos == c->stack) {
612
+ snprintf(c->err, sizeof(c->err), "stack underflow");
613
+ return;
614
+ }
615
+ a = &c->tos->a;
616
+ h = rb_ary_pop(*a);
617
+ message = rb_hash_aref(h, rb_str_new_cstr("message"));
618
+ stack = rb_hash_aref(h, rb_str_new_cstr("stack"));
619
+ cause = rb_hash_aref(h, rb_str_new_cstr("cause"));
620
+ if (NIL_P(message))
621
+ message = rb_str_new_cstr("JS exception");
622
+ if (!NIL_P(stack)) {
623
+ newline = rb_str_new_cstr("\n");
624
+ message = rb_funcall(message, rb_intern("concat"), 2, newline, stack);
625
+ }
626
+ *a = rb_class_new_instance(1, &message, script_error);
627
+ if (!NIL_P(cause))
628
+ rb_iv_set(*a, "@cause", cause);
629
+ pop(c);
630
+ }
631
+
632
+ static int collect(VALUE k, VALUE v, VALUE a)
633
+ {
634
+ rb_ary_push(a, k);
635
+ rb_ary_push(a, v);
636
+ return ST_CONTINUE;
637
+ }
638
+
639
+ static void add_string(Ser *s, VALUE v)
640
+ {
641
+ rb_encoding *e;
642
+ const void *p;
643
+ size_t n;
644
+
645
+ Check_Type(v, T_STRING);
646
+ e = rb_enc_get(v);
647
+ p = RSTRING_PTR(v);
648
+ n = RSTRING_LEN(v);
649
+ if (e) {
650
+ if (!strcmp(e->name, "ISO-8859-1"))
651
+ return ser_string8(s, p, n);
652
+ if (!strcmp(e->name, "UTF-16LE"))
653
+ return ser_string16(s, p, n);
654
+ }
655
+ return ser_string(s, p, n);
656
+ }
657
+
658
+ static int serialize1(Ser *s, VALUE refs, VALUE v)
659
+ {
660
+ unsigned long limbs[64];
661
+ VALUE a, t, id;
662
+ size_t i, n;
663
+ int sign;
664
+
665
+ if (*s->err)
666
+ return -1;
667
+ switch (TYPE(v)) {
668
+ case T_ARRAY:
669
+ {
670
+ VALUE obj_id = rb_obj_id(v);
671
+ id = rb_hash_lookup(refs, obj_id);
672
+ if (NIL_P(id)) {
673
+ n = RARRAY_LENINT(v);
674
+ i = rb_hash_size_num(refs);
675
+ rb_hash_aset(refs, obj_id, LONG2FIX(i));
676
+ ser_array_begin(s, n);
677
+ for (i = 0; i < n; i++)
678
+ if (serialize1(s, refs, rb_ary_entry(v, i)))
679
+ return -1;
680
+ ser_array_end(s, n);
681
+ } else {
682
+ ser_object_ref(s, FIX2LONG(id));
683
+ }
684
+ }
685
+ break;
686
+ case T_HASH:
687
+ {
688
+ VALUE obj_id = rb_obj_id(v);
689
+ id = rb_hash_lookup(refs, obj_id);
690
+ if (NIL_P(id)) {
691
+ a = rb_ary_new();
692
+ i = rb_hash_size_num(refs);
693
+ n = rb_hash_size_num(v);
694
+ rb_hash_aset(refs, obj_id, LONG2FIX(i));
695
+ rb_hash_foreach(v, collect, a);
696
+ for (i = 0; i < 2*n; i += 2) {
697
+ t = rb_ary_entry(a, i);
698
+ switch (TYPE(t)) {
699
+ case T_FIXNUM:
700
+ case T_STRING:
701
+ case T_SYMBOL:
702
+ continue;
703
+ }
704
+ break;
705
+ }
706
+ if (i == 2*n) {
707
+ ser_object_begin(s);
708
+ for (i = 0; i < 2*n; i += 2) {
709
+ if (serialize1(s, refs, rb_ary_entry(a, i+0)))
710
+ return -1;
711
+ if (serialize1(s, refs, rb_ary_entry(a, i+1)))
712
+ return -1;
713
+ }
714
+ ser_object_end(s, n);
715
+ } else {
716
+ return bail(&s->err, "TODO serialize as Map");
717
+ }
718
+ } else {
719
+ ser_object_ref(s, FIX2LONG(id));
720
+ }
721
+ }
722
+ break;
723
+ case T_DATA:
724
+ if (date_time_class == CLASS_OF(v)) {
725
+ v = rb_funcall(v, rb_intern("to_time"), 0);
726
+ }
727
+ if (rb_cTime == CLASS_OF(v)) {
728
+ struct timeval tv = rb_time_timeval(v);
729
+ ser_date(s, tv.tv_sec*1e3 + tv.tv_usec/1e3);
730
+ } else {
731
+ static const char undefined_conversion[] = "Undefined Conversion";
732
+ ser_string(s, undefined_conversion, sizeof(undefined_conversion)-1);
733
+ }
734
+ break;
735
+ case T_NIL:
736
+ ser_null(s);
737
+ break;
738
+ case T_UNDEF:
739
+ ser_undefined(s);
740
+ break;
741
+ case T_TRUE:
742
+ ser_bool(s, 1);
743
+ break;
744
+ case T_FALSE:
745
+ ser_bool(s, 0);
746
+ break;
747
+ case T_BIGNUM:
748
+ // note: v8 stores bigints in 1's complement, ruby in 2's complement,
749
+ // so we have to take additional steps to ensure correct conversion
750
+ memset(limbs, 0, sizeof(limbs));
751
+ sign = rb_big_sign(v) ? 1 : -1;
752
+ if (sign < 0)
753
+ v = rb_big_mul(v, LONG2FIX(-1));
754
+ rb_big_pack(v, limbs, countof(limbs));
755
+ ser_bigint(s, limbs, countof(limbs), sign);
756
+ break;
757
+ case T_FIXNUM:
758
+ ser_int(s, FIX2LONG(v));
759
+ break;
760
+ case T_FLOAT:
761
+ ser_num(s, NUM2DBL(v));
762
+ break;
763
+ case T_SYMBOL:
764
+ v = rb_sym2str(v);
765
+ // fallthru
766
+ case T_STRING:
767
+ add_string(s, v);
768
+ break;
769
+ case T_OBJECT:
770
+ // this is somewhat wide, but we have active support which creates
771
+ // entirely new objects
772
+ if (rb_respond_to(v, rb_intern("to_time"))) {
773
+ v = rb_funcall(v, rb_intern("to_time"), 0);
774
+ if (rb_obj_is_kind_of(v, rb_cTime)) {
775
+ struct timeval tv = rb_time_timeval(v);
776
+ ser_date(s, tv.tv_sec*1e3 + tv.tv_usec/1e3);
777
+ } else {
778
+ snprintf(s->err, sizeof(s->err), "unsupported type %s", rb_class2name(CLASS_OF(v)));
779
+ return -1;
780
+ }
781
+ } else if (!NIL_P(binary_class) && rb_obj_is_kind_of(v, binary_class)) {
782
+ t = rb_ivar_get(v, rb_intern("@data"));
783
+ Check_Type(t, T_STRING);
784
+ ser_uint8array(s, RSTRING_PTR(t), RSTRING_LEN(t));
785
+ } else {
786
+ snprintf(s->err, sizeof(s->err), "unsupported type %s", rb_class2name(CLASS_OF(v)));
787
+ return -1;
788
+ }
789
+ break;
790
+ default:
791
+ snprintf(s->err, sizeof(s->err), "unsupported type %x", TYPE(v));
792
+ return -1;
793
+ }
794
+ if (*s->err)
795
+ return -1;
796
+ return 0;
797
+ }
798
+
799
+ // don't mix with ser_array_begin/ser_object_begin because
800
+ // that will throw off the object reference count
801
+ static int serialize(Ser *s, VALUE v)
802
+ {
803
+ return serialize1(s, rb_hash_new(), v);
804
+ }
805
+
806
+ static struct timespec deadline_ms(int ms)
807
+ {
808
+ static const int64_t ns_per_sec = 1000*1000*1000;
809
+ struct timespec t;
810
+
811
+ #ifdef __APPLE__
812
+ clock_gettime(CLOCK_REALTIME, &t);
813
+ #else
814
+ clock_gettime(CLOCK_MONOTONIC, &t);
815
+ #endif
816
+ t.tv_sec += ms/1000;
817
+ t.tv_nsec += ms%1000 * ns_per_sec/1000;
818
+ while (t.tv_nsec >= ns_per_sec) {
819
+ t.tv_nsec -= ns_per_sec;
820
+ t.tv_sec++;
821
+ }
822
+ return t;
823
+ }
824
+
825
+ static int timespec_le(struct timespec a, struct timespec b)
826
+ {
827
+ if (a.tv_sec < b.tv_sec) return 1;
828
+ return a.tv_sec == b.tv_sec && a.tv_nsec <= b.tv_nsec;
829
+ }
830
+
831
+ static int deadline_exceeded(struct timespec deadline)
832
+ {
833
+ return timespec_le(deadline, deadline_ms(0));
834
+ }
835
+
836
+ static void *v8_watchdog(void *arg)
837
+ {
838
+ struct timespec deadline;
839
+ Context *c;
840
+
841
+ c = arg;
842
+ deadline = deadline_ms(c->timeout);
843
+ pthread_mutex_lock(&c->wd.mtx);
844
+ for (;;) {
845
+ if (c->wd.cancel)
846
+ break;
847
+ pthread_cond_timedwait(&c->wd.cv, &c->wd.mtx, &deadline);
848
+ if (c->wd.cancel)
849
+ break;
850
+ if (deadline_exceeded(deadline)) {
851
+ v8_terminate_execution(c->pst);
852
+ break;
853
+ }
854
+ }
855
+ pthread_mutex_unlock(&c->wd.mtx);
856
+ return NULL;
857
+ }
858
+
859
+ static void v8_timedwait(Context *c, const uint8_t *p, size_t n,
860
+ void (*func)(struct State *pst, const uint8_t *p, size_t n))
861
+ {
862
+ pthread_t thr;
863
+ int r;
864
+
865
+ r = -1;
866
+ if (c->timeout > 0 && (r = pthread_create(&thr, NULL, v8_watchdog, c))) {
867
+ fprintf(stderr, "mini_racer: watchdog: pthread_create: %s\n", strerror(r));
868
+ fflush(stderr);
869
+ }
870
+ func(c->pst, p, n);
871
+ if (r)
872
+ return;
873
+ pthread_mutex_lock(&c->wd.mtx);
874
+ c->wd.cancel = 1;
875
+ pthread_cond_signal(&c->wd.cv);
876
+ pthread_mutex_unlock(&c->wd.mtx);
877
+ pthread_join(thr, NULL);
878
+ c->wd.cancel = 0;
879
+ }
880
+
881
+ static void dispatch1(Context *c, const uint8_t *p, size_t n)
882
+ {
883
+ uint8_t b;
884
+
885
+ assert(n > 0);
886
+ switch (*p) {
887
+ case 'A': return v8_attach(c->pst, p+1, n-1);
888
+ case 'C': return v8_timedwait(c, p+1, n-1, v8_call);
889
+ case 'D': return v8_dispose_script(c->pst, p+1, n-1);
890
+ case 'E': return v8_timedwait(c, p+1, n-1, v8_eval);
891
+ case 'F': return v8_reset_realm(c->pst); // (F)resh realm
892
+ case 'G': return v8_timedwait(c, p+1, n-1, v8_load_module_graph); // (G)raph load
893
+ case 'H': return v8_heap_snapshot(c->pst);
894
+ case 'I': return v8_timedwait(c, p+1, n-1, v8_instantiate_module); // (I)nstantiate module
895
+ case 'K': return v8_timedwait(c, p+1, n-1, v8_compile); // (K)ompile — 'C' is taken
896
+ case 'M': return v8_perform_microtask_checkpoint(c->pst);
897
+ case 'N': return v8_module_namespace(c->pst, p+1, n-1); // (N)amespace
898
+ case 'O': return v8_timedwait(c, p+1, n-1, v8_compile_module); // (O)bject-module compile
899
+ case 'P': return v8_pump_message_loop(c->pst);
900
+ case 'R': return v8_timedwait(c, p+1, n-1, v8_run);
901
+ case 'S': return v8_heap_stats(c->pst);
902
+ case 'T': return v8_snapshot(c->pst, p+1, n-1);
903
+ case 'U': return v8_module_status(c->pst, p+1, n-1); // (U) module status — non-blocking
904
+ case 'V': return v8_timedwait(c, p+1, n-1, v8_evaluate_module); // e(V)aluate
905
+ case 'W': return v8_warmup(c->pst, p+1, n-1);
906
+ case 'Z': return v8_dispose_module(c->pst, p+1, n-1); // (Z) dispose module handle
907
+ case 'L':
908
+ b = 0;
909
+ v8_reply(c, &b, 1); // doesn't matter what as long as it's not empty
910
+ return v8_low_memory_notification(c->pst);
911
+ }
912
+ fprintf(stderr, "mini_racer: bad request %02x\n", *p);
913
+ fflush(stderr);
914
+ }
915
+
916
+ static void dispatch(Context *c)
917
+ {
918
+ buf_reset(&c->res);
919
+ dispatch1(c, c->req.buf, c->req.len);
920
+ buf_reset(&c->req);
921
+ }
922
+
923
+ // called by v8_isolate_and_context
924
+ void v8_thread_main(Context *c, struct State *pst)
925
+ {
926
+ struct timespec deadline;
927
+ bool issued_idle_gc = true;
928
+
929
+ c->pst = pst;
930
+ barrier_wait(&c->late_init);
931
+ pthread_mutex_lock(&c->mtx);
932
+ while (!c->quit) {
933
+ if (!c->req.len) {
934
+ if (c->idle_gc > 0) {
935
+ deadline = deadline_ms(c->idle_gc);
936
+ pthread_cond_timedwait(&c->cv, &c->mtx, &deadline);
937
+ if (deadline_exceeded(deadline) && !issued_idle_gc) {
938
+ v8_low_memory_notification(c->pst);
939
+ issued_idle_gc = true;
940
+ }
941
+ } else {
942
+ pthread_cond_wait(&c->cv, &c->mtx);
943
+ }
944
+ }
945
+ if (!c->req.len)
946
+ continue; // spurious wakeup or quit signal from other thread
947
+ // Restore the realm from its persistents for this request (so
948
+ // reset_realm can swap it between requests), then dispatch. Nested
949
+ // dispatches during a callback roundtrip reuse the same restored realm
950
+ // and so go through v8_dispatch directly, without re-entering.
951
+ v8_threaded_enter(c->pst, c, dispatch);
952
+ issued_idle_gc = false;
953
+ pthread_cond_signal(&c->cv);
954
+ }
955
+ }
956
+
957
+ // called by v8_thread_main and from mini_racer_v8.cc,
958
+ // in all cases with Context.mtx held
959
+ void v8_dispatch(Context *c)
960
+ {
961
+ dispatch1(c, c->req.buf, c->req.len);
962
+ buf_reset(&c->req);
963
+ }
964
+
965
+ // called from mini_racer_v8.cc with Context.mtx held
966
+ // only called when inside v8_call, v8_eval, or v8_pump_message_loop
967
+ void v8_roundtrip(Context *c, const uint8_t **p, size_t *n)
968
+ {
969
+ buf_reset(&c->req);
970
+ pthread_cond_signal(&c->cv);
971
+ while (!c->req.len)
972
+ pthread_cond_wait(&c->cv, &c->mtx);
973
+ buf_reset(&c->res);
974
+ *p = c->req.buf;
975
+ *n = c->req.len;
976
+ }
977
+
978
+ // called from mini_racer_v8.cc with Context.mtx held
979
+ void v8_reply(Context *c, const uint8_t *p, size_t n)
980
+ {
981
+ buf_put(&c->res, p, n);
982
+ }
983
+
984
+ static void v8_once_init(void)
985
+ {
986
+ static pthread_once_t once = PTHREAD_ONCE_INIT;
987
+ pthread_once(&once, v8_global_init);
988
+ }
989
+
990
+ static void *v8_thread_start(void *arg)
991
+ {
992
+ Context *c;
993
+
994
+ c = arg;
995
+ barrier_wait(&c->early_init);
996
+ v8_once_init();
997
+ v8_thread_init(c, c->snapshot.buf, c->snapshot.len, c->max_memory, c->verbose_exceptions,
998
+ c->host_namespace.len ? (const char *)c->host_namespace.buf : NULL);
999
+ while (c->quit < 2)
1000
+ pthread_cond_wait(&c->cv, &c->mtx);
1001
+ context_destroy(c);
1002
+ return NULL;
1003
+ }
1004
+
1005
+ static VALUE deserialize1(DesCtx *d, const uint8_t *p, size_t n)
1006
+ {
1007
+ char err[64];
1008
+
1009
+ if (des(&err, p, n, d))
1010
+ rb_raise(runtime_error, "%s", err);
1011
+ if (d->tos != d->stack) // should not happen
1012
+ rb_raise(runtime_error, "parse stack not empty");
1013
+ return d->tos->a;
1014
+ }
1015
+
1016
+ static VALUE deserialize(VALUE arg)
1017
+ {
1018
+ struct rendezvous_des *a;
1019
+ Buf *b;
1020
+
1021
+ a = (void *)arg;
1022
+ b = a->res;
1023
+ return deserialize1(a->d, b->buf, b->len);
1024
+ }
1025
+
1026
+ // called with |rr_mtx| and GVL held; can raise exception
1027
+ static VALUE rendezvous_callback_do(VALUE arg)
1028
+ {
1029
+ struct rendezvous_nogvl *a;
1030
+ VALUE func, args;
1031
+ Context *c;
1032
+ DesCtx d;
1033
+ Buf *b;
1034
+
1035
+ a = (void *)arg;
1036
+ b = a->res;
1037
+ c = a->context;
1038
+ assert(b->len > 0);
1039
+ assert(*b->buf == 'c');
1040
+ DesCtx_init(&d);
1041
+ args = deserialize1(&d, b->buf+1, b->len-1); // skip 'c' marker
1042
+ func = rb_ary_pop(args); // callback id
1043
+ func = rb_ary_entry(c->procs, FIX2LONG(func));
1044
+ return rb_funcall2(func, rb_intern("call"), RARRAY_LENINT(args), RARRAY_PTR(args));
1045
+ }
1046
+
1047
+ // called with |rr_mtx| and GVL held; |mtx| is unlocked
1048
+ // callback data is in |a->res|, serialized result goes in |a->req|
1049
+ static void *rendezvous_callback(void *arg)
1050
+ {
1051
+ struct rendezvous_nogvl *a;
1052
+ const char *err;
1053
+ Context *c;
1054
+ int exc;
1055
+ VALUE r;
1056
+ Ser s;
1057
+
1058
+ a = arg;
1059
+ c = a->context;
1060
+ r = rb_protect(rendezvous_callback_do, (VALUE)a, &exc);
1061
+ if (exc) {
1062
+ c->exception = rb_errinfo();
1063
+ rb_set_errinfo(Qnil);
1064
+ goto fail;
1065
+ }
1066
+ ser_init1(&s, 'c'); // callback reply
1067
+ if (serialize(&s, r)) { // should not happen
1068
+ c->exception = rb_exc_new_cstr(internal_error, s.err);
1069
+ ser_reset(&s);
1070
+ goto fail;
1071
+ }
1072
+ out:
1073
+ buf_move(&s.b, a->req);
1074
+ return NULL;
1075
+ fail:
1076
+ ser_init0(&s); // ruby exception pending
1077
+ w_byte(&s, 'e'); // send ruby error message to v8 thread
1078
+ r = rb_funcall(c->exception, rb_intern("to_s"), 0);
1079
+ err = StringValueCStr(r);
1080
+ if (err)
1081
+ w(&s, err, strlen(err));
1082
+ goto out;
1083
+ }
1084
+
1085
+ // called with |rr_mtx| and GVL held; can raise exception
1086
+ static VALUE rendezvous_resolve_do(VALUE arg)
1087
+ {
1088
+ struct rendezvous_nogvl *a;
1089
+ VALUE args, specifier, referrer_url, ret;
1090
+ Context *c;
1091
+ Module *m;
1092
+ DesCtx d;
1093
+ Buf *b;
1094
+
1095
+ a = (void *)arg;
1096
+ b = a->res;
1097
+ c = a->context;
1098
+ assert(b->len > 0);
1099
+ assert(*b->buf == 'm');
1100
+ DesCtx_init(&d);
1101
+ args = deserialize1(&d, b->buf+1, b->len-1); // skip 'm' marker
1102
+ // args: [specifier, referrer_url]
1103
+ specifier = rb_ary_entry(args, 0);
1104
+ referrer_url = rb_ary_entry(args, 1);
1105
+ if (NIL_P(c->resolve_block))
1106
+ rb_raise(runtime_error, "module resolver requested but no resolver block is active");
1107
+ ret = rb_funcall(c->resolve_block, rb_intern("call"), 2, specifier, referrer_url);
1108
+ if (!rb_obj_is_kind_of(ret, module_class))
1109
+ rb_raise(runtime_error, "module resolver must return a MiniRacer::Module, got %s",
1110
+ rb_obj_classname(ret));
1111
+ TypedData_Get_Struct(ret, Module, &module_type, m);
1112
+ if (m->disposed)
1113
+ rb_raise(runtime_error, "module resolver returned a disposed Module");
1114
+ // Reject cross-Context modules explicitly: handle ids restart at 1
1115
+ // per Context, so without this check a foreign Module silently maps
1116
+ // to whichever local module happens to share its id.
1117
+ {
1118
+ Context *mc;
1119
+ TypedData_Get_Struct(m->context, Context, &context_type, mc);
1120
+ if (mc != c)
1121
+ rb_raise(runtime_error,
1122
+ "module resolver returned a Module from a different Context");
1123
+ }
1124
+ return INT2FIX(m->handle_id);
1125
+ }
1126
+
1127
+ // called with |rr_mtx| and GVL held; |mtx| is unlocked
1128
+ // resolver data is in |a->res|, serialized handle id goes in |a->req|
1129
+ static void *rendezvous_resolve(void *arg)
1130
+ {
1131
+ struct rendezvous_nogvl *a;
1132
+ const char *err;
1133
+ Context *c;
1134
+ int exc;
1135
+ VALUE r;
1136
+ Ser s;
1137
+
1138
+ a = arg;
1139
+ c = a->context;
1140
+ r = rb_protect(rendezvous_resolve_do, (VALUE)a, &exc);
1141
+ if (exc) {
1142
+ c->exception = rb_errinfo();
1143
+ rb_set_errinfo(Qnil);
1144
+ goto fail;
1145
+ }
1146
+ ser_init1(&s, 'm'); // resolve reply (matches request marker)
1147
+ if (serialize(&s, r)) { // should not happen
1148
+ c->exception = rb_exc_new_cstr(internal_error, s.err);
1149
+ ser_reset(&s);
1150
+ goto fail;
1151
+ }
1152
+ out:
1153
+ buf_move(&s.b, a->req);
1154
+ return NULL;
1155
+ fail:
1156
+ ser_init0(&s);
1157
+ w_byte(&s, 'e');
1158
+ r = rb_funcall(c->exception, rb_intern("to_s"), 0);
1159
+ err = StringValueCStr(r);
1160
+ if (err)
1161
+ w(&s, err, strlen(err));
1162
+ goto out;
1163
+ }
1164
+
1165
+ // called with |rr_mtx| and GVL held; can raise exception
1166
+ static VALUE rendezvous_dynamic_import_do(VALUE arg)
1167
+ {
1168
+ struct rendezvous_nogvl *a;
1169
+ VALUE args, specifier, referrer_url, ret;
1170
+ Context *c;
1171
+ Module *m;
1172
+ DesCtx d;
1173
+ Buf *b;
1174
+
1175
+ a = (void *)arg;
1176
+ b = a->res;
1177
+ c = a->context;
1178
+ assert(b->len > 0);
1179
+ assert(*b->buf == 'd');
1180
+ DesCtx_init(&d);
1181
+ args = deserialize1(&d, b->buf+1, b->len-1); // skip 'd' marker
1182
+ specifier = rb_ary_entry(args, 0);
1183
+ referrer_url = rb_ary_entry(args, 1);
1184
+ if (NIL_P(c->dynamic_import_resolver))
1185
+ rb_raise(runtime_error,
1186
+ "import() called but Context#dynamic_import_resolver is not set");
1187
+ ret = rb_funcall(c->dynamic_import_resolver, rb_intern("call"), 2,
1188
+ specifier, referrer_url);
1189
+ if (!rb_obj_is_kind_of(ret, module_class))
1190
+ rb_raise(runtime_error,
1191
+ "dynamic import resolver must return a MiniRacer::Module, got %s",
1192
+ rb_obj_classname(ret));
1193
+ TypedData_Get_Struct(ret, Module, &module_type, m);
1194
+ if (m->disposed)
1195
+ rb_raise(runtime_error, "dynamic import resolver returned a disposed Module");
1196
+ {
1197
+ Context *mc;
1198
+ TypedData_Get_Struct(m->context, Context, &context_type, mc);
1199
+ if (mc != c)
1200
+ rb_raise(runtime_error,
1201
+ "dynamic import resolver returned a Module from a different Context");
1202
+ }
1203
+ return INT2FIX(m->handle_id);
1204
+ }
1205
+
1206
+ // called with |rr_mtx| and GVL held; |mtx| is unlocked
1207
+ // request data is in |a->res|, serialized handle id goes in |a->req|
1208
+ static void *rendezvous_dynamic_import(void *arg)
1209
+ {
1210
+ struct rendezvous_nogvl *a;
1211
+ const char *err;
1212
+ Context *c;
1213
+ int exc;
1214
+ VALUE r;
1215
+ Ser s;
1216
+
1217
+ a = arg;
1218
+ c = a->context;
1219
+ r = rb_protect(rendezvous_dynamic_import_do, (VALUE)a, &exc);
1220
+ if (exc) {
1221
+ c->exception = rb_errinfo();
1222
+ rb_set_errinfo(Qnil);
1223
+ goto fail;
1224
+ }
1225
+ ser_init1(&s, 'd'); // dynamic import reply (matches request marker)
1226
+ if (serialize(&s, r)) { // should not happen
1227
+ c->exception = rb_exc_new_cstr(internal_error, s.err);
1228
+ ser_reset(&s);
1229
+ goto fail;
1230
+ }
1231
+ out:
1232
+ buf_move(&s.b, a->req);
1233
+ return NULL;
1234
+ fail:
1235
+ ser_init0(&s);
1236
+ w_byte(&s, 'e');
1237
+ r = rb_funcall(c->exception, rb_intern("to_s"), 0);
1238
+ err = StringValueCStr(r);
1239
+ if (err)
1240
+ w(&s, err, strlen(err));
1241
+ goto out;
1242
+ }
1243
+
1244
+ // load_module_graph batched fetch ('f') and resolve ('r') callbacks. Each
1245
+ // deserializes the batch v8 sent, calls the per-load Ruby block, and serializes
1246
+ // the array reply back under the same marker. Mirrors rendezvous_resolve.
1247
+
1248
+ // called with |rr_mtx| and GVL held; can raise exception
1249
+ static VALUE rendezvous_graph_fetch_do(VALUE arg)
1250
+ {
1251
+ struct rendezvous_nogvl *a = (void *)arg;
1252
+ Context *c = a->context;
1253
+ Buf *b = a->res;
1254
+ DesCtx d;
1255
+ VALUE urls;
1256
+
1257
+ assert(b->len > 0 && *b->buf == 'f');
1258
+ DesCtx_init(&d);
1259
+ urls = deserialize1(&d, b->buf+1, b->len-1); // skip 'f' marker; [url, ...]
1260
+ if (NIL_P(c->graph_fetch_block))
1261
+ rb_raise(runtime_error, "module fetch requested but no fetch_batch block is active");
1262
+ // -> [[source, cached_data]|nil, ...]
1263
+ VALUE ret = rb_funcall(c->graph_fetch_block, rb_intern("call"), 1, urls);
1264
+ // The cached_data element is an ASCII-8BIT String (same shape as
1265
+ // Module#cached_data / compile_module(cached_data:)). Wrap it as
1266
+ // MiniRacer::Binary so it crosses to V8 as a Uint8Array — a bare String
1267
+ // would serialize as a JS string and the code cache would be silently
1268
+ // dropped (and binary bytes mangled by UTF-8). Build fresh rows rather than
1269
+ // mutating the array the caller's block returned.
1270
+ if (TYPE(ret) == T_ARRAY && !NIL_P(binary_class)) {
1271
+ long len = RARRAY_LEN(ret), i;
1272
+ VALUE wrapped = rb_ary_new_capa(len);
1273
+ for (i = 0; i < len; i++) {
1274
+ VALUE row = rb_ary_entry(ret, i);
1275
+ if (TYPE(row) == T_ARRAY && RARRAY_LEN(row) >= 2 &&
1276
+ TYPE(rb_ary_entry(row, 1)) == T_STRING) {
1277
+ VALUE r2 = rb_ary_dup(row);
1278
+ rb_ary_store(r2, 1, rb_funcall(binary_class, rb_intern("new"), 1,
1279
+ rb_ary_entry(row, 1)));
1280
+ rb_ary_push(wrapped, r2);
1281
+ } else {
1282
+ rb_ary_push(wrapped, row);
1283
+ }
1284
+ }
1285
+ ret = wrapped;
1286
+ }
1287
+ return ret;
1288
+ }
1289
+
1290
+ // called with |rr_mtx| and GVL held; can raise exception
1291
+ static VALUE rendezvous_graph_resolve_do(VALUE arg)
1292
+ {
1293
+ struct rendezvous_nogvl *a = (void *)arg;
1294
+ Context *c = a->context;
1295
+ Buf *b = a->res;
1296
+ DesCtx d;
1297
+ VALUE edges;
1298
+
1299
+ assert(b->len > 0 && *b->buf == 'r');
1300
+ DesCtx_init(&d);
1301
+ edges = deserialize1(&d, b->buf+1, b->len-1); // skip 'r' marker; [[spec, ref], ...]
1302
+ if (NIL_P(c->graph_resolve_block))
1303
+ rb_raise(runtime_error, "module resolve requested but no resolve block is active");
1304
+ // -> [url|nil, ...]
1305
+ return rb_funcall(c->graph_resolve_block, rb_intern("call"), 1, edges);
1306
+ }
1307
+
1308
+ // called with |rr_mtx| and GVL held; |mtx| is unlocked
1309
+ // batch data is in |a->res| (marker |marker|), serialized array reply in |a->req|
1310
+ static void rendezvous_graph_batch(struct rendezvous_nogvl *a, char marker,
1311
+ VALUE (*body)(VALUE))
1312
+ {
1313
+ const char *err;
1314
+ Context *c = a->context;
1315
+ int exc;
1316
+ VALUE r;
1317
+ Ser s;
1318
+
1319
+ r = rb_protect(body, (VALUE)a, &exc);
1320
+ if (exc) {
1321
+ c->exception = rb_errinfo();
1322
+ rb_set_errinfo(Qnil);
1323
+ goto fail;
1324
+ }
1325
+ ser_init1(&s, (uint8_t)marker); // reply matches request marker
1326
+ if (serialize(&s, r)) {
1327
+ c->exception = rb_exc_new_cstr(internal_error, s.err);
1328
+ ser_reset(&s);
1329
+ goto fail;
1330
+ }
1331
+ out:
1332
+ buf_move(&s.b, a->req);
1333
+ return;
1334
+ fail:
1335
+ ser_init0(&s);
1336
+ w_byte(&s, 'e');
1337
+ r = rb_funcall(c->exception, rb_intern("to_s"), 0);
1338
+ err = StringValueCStr(r);
1339
+ if (err)
1340
+ w(&s, err, strlen(err));
1341
+ goto out;
1342
+ }
1343
+
1344
+ static void *rendezvous_graph_fetch(void *arg)
1345
+ {
1346
+ rendezvous_graph_batch(arg, 'f', rendezvous_graph_fetch_do);
1347
+ return NULL;
1348
+ }
1349
+
1350
+ static void *rendezvous_graph_resolve(void *arg)
1351
+ {
1352
+ rendezvous_graph_batch(arg, 'r', rendezvous_graph_resolve_do);
1353
+ return NULL;
1354
+ }
1355
+
1356
+ static void *single_threaded_runner(void *arg)
1357
+ {
1358
+ Context *c;
1359
+
1360
+ c = arg;
1361
+ pthread_mutex_lock(&c->mtx);
1362
+ for (;;) {
1363
+ while (!c->req.len && atomic_load(&c->quit) < 1)
1364
+ pthread_cond_wait(&c->cv, &c->mtx);
1365
+ if (atomic_load(&c->quit) >= 1)
1366
+ break;
1367
+ v8_single_threaded_enter(c->pst, c, dispatch);
1368
+ pthread_cond_signal(&c->cv);
1369
+ }
1370
+ pthread_mutex_unlock(&c->mtx);
1371
+ return NULL;
1372
+ }
1373
+
1374
+ static int single_threaded_runner_start(Context *c)
1375
+ {
1376
+ pid_t pid;
1377
+ int r;
1378
+
1379
+ pid = getpid();
1380
+ if (c->single_threaded_thr_started && c->single_threaded_pid == pid)
1381
+ return 0;
1382
+ c->single_threaded_thr_started = 0;
1383
+ c->single_threaded_pid = pid;
1384
+ r = pthread_create(&c->single_threaded_thr, NULL, single_threaded_runner, c);
1385
+ if (!r)
1386
+ c->single_threaded_thr_started = 1;
1387
+ return r;
1388
+ }
1389
+
1390
+ static inline void *rendezvous_nogvl(void *arg)
1391
+ {
1392
+ struct rendezvous_nogvl *a;
1393
+ Context *c;
1394
+ int r;
1395
+
1396
+ a = arg;
1397
+ c = a->context;
1398
+ pthread_mutex_lock(&c->rr_mtx);
1399
+ if (c->depth > 0 && c->depth%50 == 0) { // TODO stop steep recursion
1400
+ fprintf(stderr, "mini_racer: deep js->ruby->js recursion, depth=%d\n", c->depth);
1401
+ fflush(stderr);
1402
+ }
1403
+ c->depth++;
1404
+ next:
1405
+ pthread_mutex_lock(&c->mtx);
1406
+ assert(c->req.len == 0);
1407
+ assert(c->res.len == 0);
1408
+ buf_move(a->req, &c->req); // v8 thread takes ownership of req
1409
+ if (single_threaded) {
1410
+ r = single_threaded_runner_start(c);
1411
+ if (r) {
1412
+ buf_move(&c->req, a->req);
1413
+ pthread_mutex_unlock(&c->mtx);
1414
+ c->depth--;
1415
+ pthread_mutex_unlock(&c->rr_mtx);
1416
+ return (void *)(intptr_t)r;
1417
+ }
1418
+ pthread_cond_signal(&c->cv);
1419
+ do pthread_cond_wait(&c->cv, &c->mtx); while (!c->res.len);
1420
+ } else {
1421
+ pthread_cond_signal(&c->cv);
1422
+ do pthread_cond_wait(&c->cv, &c->mtx); while (!c->res.len);
1423
+ }
1424
+ buf_move(&c->res, a->res);
1425
+ pthread_mutex_unlock(&c->mtx);
1426
+ if (*a->res->buf == 'm') { // module resolver request?
1427
+ rb_thread_call_with_gvl(rendezvous_resolve, a);
1428
+ buf_reset(a->res);
1429
+ goto next;
1430
+ }
1431
+ if (*a->res->buf == 'd') { // dynamic import() request?
1432
+ rb_thread_call_with_gvl(rendezvous_dynamic_import, a);
1433
+ buf_reset(a->res);
1434
+ goto next;
1435
+ }
1436
+ if (*a->res->buf == 'f') { // load_module_graph fetch batch?
1437
+ rb_thread_call_with_gvl(rendezvous_graph_fetch, a);
1438
+ buf_reset(a->res);
1439
+ goto next;
1440
+ }
1441
+ if (*a->res->buf == 'r') { // load_module_graph resolve batch?
1442
+ rb_thread_call_with_gvl(rendezvous_graph_resolve, a);
1443
+ buf_reset(a->res);
1444
+ goto next;
1445
+ }
1446
+ if (*a->res->buf == 'c') { // js -> ruby callback?
1447
+ rb_thread_call_with_gvl(rendezvous_callback, a);
1448
+ buf_reset(a->res);
1449
+ goto next;
1450
+ }
1451
+ c->depth--;
1452
+ pthread_mutex_unlock(&c->rr_mtx);
1453
+ return NULL;
1454
+ }
1455
+
1456
+ static void rendezvous_no_des(Context *c, Buf *req, Buf *res)
1457
+ {
1458
+ void *r;
1459
+
1460
+ if (atomic_load(&c->quit)) {
1461
+ buf_reset(req);
1462
+ rb_raise(context_disposed_error, "disposed context");
1463
+ }
1464
+ r = rb_nogvl(rendezvous_nogvl, &(struct rendezvous_nogvl){c, req, res},
1465
+ NULL, NULL, 0);
1466
+ if (r)
1467
+ rb_raise(runtime_error, "pthread_create: %s", strerror((int)(intptr_t)r));
1468
+ }
1469
+
1470
+ // send request to & receive reply from v8 thread; takes ownership of |req|
1471
+ // can raise exceptions and longjmp away but won't leak |req|
1472
+ static VALUE rendezvous1(Context *c, Buf *req, DesCtx *d)
1473
+ {
1474
+ VALUE r;
1475
+ Buf res;
1476
+ int exc;
1477
+
1478
+ rendezvous_no_des(c, req, &res); // takes ownership of |req|
1479
+ r = c->exception;
1480
+ c->exception = Qnil;
1481
+ // if js land didn't handle exception from ruby callback, re-raise it now
1482
+ if (res.len == 1 && *res.buf == 'e') {
1483
+ assert(!NIL_P(r));
1484
+ rb_exc_raise(r);
1485
+ }
1486
+ r = rb_protect(deserialize, (VALUE)&(struct rendezvous_des){d, &res}, &exc);
1487
+ buf_reset(&res);
1488
+ if (exc) {
1489
+ r = rb_errinfo();
1490
+ rb_set_errinfo(Qnil);
1491
+ rb_exc_raise(r);
1492
+ }
1493
+ return r;
1494
+ }
1495
+
1496
+ static VALUE rendezvous(Context *c, Buf *req)
1497
+ {
1498
+ DesCtx d;
1499
+
1500
+ DesCtx_init(&d);
1501
+ return rendezvous1(c, req, &d);
1502
+ }
1503
+
1504
+ static void handle_exception(VALUE e)
1505
+ {
1506
+ const char *s;
1507
+ VALUE klass;
1508
+
1509
+ if (NIL_P(e))
1510
+ return;
1511
+ e = StringValue(e);
1512
+ s = StringValueCStr(e);
1513
+ switch (*s) {
1514
+ case NO_ERROR:
1515
+ return;
1516
+ case INTERNAL_ERROR:
1517
+ klass = internal_error;
1518
+ break;
1519
+ case MEMORY_ERROR:
1520
+ klass = memory_error;
1521
+ break;
1522
+ case PARSE_ERROR:
1523
+ klass = parse_error;
1524
+ break;
1525
+ case RUNTIME_ERROR:
1526
+ klass = runtime_error;
1527
+ break;
1528
+ case TERMINATED_ERROR:
1529
+ klass = terminated_error;
1530
+ break;
1531
+ default:
1532
+ rb_raise(internal_error, "bad error class %02x", *s);
1533
+ }
1534
+ rb_enc_raise(rb_enc_get(e), klass, "%s", s+1);
1535
+ }
1536
+
1537
+ static VALUE context_alloc(VALUE klass)
1538
+ {
1539
+ pthread_mutexattr_t mattr;
1540
+ pthread_condattr_t cattr;
1541
+ const char *cause;
1542
+ Context *c;
1543
+ VALUE f, a;
1544
+ int r;
1545
+
1546
+ // Safe to lazy init because we hold the GVL
1547
+ if (NIL_P(date_time_class)) {
1548
+ f = rb_intern("const_defined?");
1549
+ a = rb_str_new_cstr("DateTime");
1550
+ if (Qtrue == rb_funcall(rb_cObject, f, 1, a))
1551
+ date_time_class = rb_const_get(rb_cObject, rb_intern("DateTime"));
1552
+ }
1553
+ if (NIL_P(binary_class)) {
1554
+ VALUE m = rb_const_get(rb_cObject, rb_intern("MiniRacer"));
1555
+ if (Qtrue == rb_funcall(m, rb_intern("const_defined?"), 1, rb_str_new_cstr("Binary")))
1556
+ binary_class = rb_const_get(m, rb_intern("Binary"));
1557
+ }
1558
+ c = ruby_xmalloc(sizeof(*c));
1559
+ memset(c, 0, sizeof(*c));
1560
+ c->exception = Qnil;
1561
+ c->resolve_block = Qnil;
1562
+ c->dynamic_import_resolver = Qnil;
1563
+ c->graph_resolve_block = Qnil;
1564
+ c->graph_fetch_block = Qnil;
1565
+ c->procs = rb_ary_new();
1566
+ buf_init(&c->snapshot);
1567
+ buf_init(&c->host_namespace);
1568
+ buf_init(&c->req);
1569
+ buf_init(&c->res);
1570
+ cause = "pthread_condattr_init";
1571
+ if ((r = pthread_condattr_init(&cattr)))
1572
+ goto fail0;
1573
+ #ifndef __APPLE__
1574
+ pthread_condattr_setclock(&cattr, CLOCK_MONOTONIC);
1575
+ #endif
1576
+ cause = "pthread_mutexattr_init";
1577
+ if ((r = pthread_mutexattr_init(&mattr)))
1578
+ goto fail1;
1579
+ pthread_mutexattr_settype(&mattr, PTHREAD_MUTEX_RECURSIVE);
1580
+ cause = "pthread_mutex_init";
1581
+ r = pthread_mutex_init(&c->rr_mtx, &mattr);
1582
+ pthread_mutexattr_destroy(&mattr);
1583
+ if (r)
1584
+ goto fail1;
1585
+ if (pthread_mutex_init(&c->mtx, NULL))
1586
+ goto fail2;
1587
+ cause = "pthread_cond_init";
1588
+ if ((r = pthread_cond_init(&c->cv, &cattr)))
1589
+ goto fail3;
1590
+ cause = "pthread_mutex_init";
1591
+ if ((r = pthread_mutex_init(&c->wd.mtx, NULL)))
1592
+ goto fail4;
1593
+ cause = "pthread_cond_init";
1594
+ if (pthread_cond_init(&c->wd.cv, &cattr))
1595
+ goto fail5;
1596
+ cause = "barrier_init";
1597
+ if ((r = barrier_init(&c->early_init, 2)))
1598
+ goto fail6;
1599
+ cause = "barrier_init";
1600
+ if ((r = barrier_init(&c->late_init, 2)))
1601
+ goto fail7;
1602
+ pthread_condattr_destroy(&cattr);
1603
+ return TypedData_Wrap_Struct(klass, &context_type, c);
1604
+ fail7:
1605
+ barrier_destroy(&c->early_init);
1606
+ fail6:
1607
+ pthread_cond_destroy(&c->wd.cv);
1608
+ fail5:
1609
+ pthread_mutex_destroy(&c->wd.mtx);
1610
+ fail4:
1611
+ pthread_cond_destroy(&c->cv);
1612
+ fail3:
1613
+ pthread_mutex_destroy(&c->mtx);
1614
+ fail2:
1615
+ pthread_mutex_destroy(&c->rr_mtx);
1616
+ fail1:
1617
+ pthread_condattr_destroy(&cattr);
1618
+ fail0:
1619
+ ruby_xfree(c);
1620
+ rb_raise(runtime_error, "%s: %s", cause, strerror(r));
1621
+ return Qnil; // pacify compiler
1622
+ }
1623
+
1624
+ static void *context_free_thread_do(void *arg)
1625
+ {
1626
+ Context *c;
1627
+
1628
+ c = arg;
1629
+ if (single_threaded && c->single_threaded_thr_started && c->single_threaded_pid == getpid()) {
1630
+ pthread_mutex_lock(&c->mtx);
1631
+ atomic_store(&c->quit, 2);
1632
+ pthread_cond_signal(&c->cv);
1633
+ pthread_mutex_unlock(&c->mtx);
1634
+ pthread_join(c->single_threaded_thr, NULL);
1635
+ }
1636
+ if (c->pst)
1637
+ v8_single_threaded_dispose(c->pst);
1638
+ pthread_mutex_lock(&c->mtx);
1639
+ context_destroy(c);
1640
+ return NULL;
1641
+ }
1642
+
1643
+ static void context_free_thread(Context *c)
1644
+ {
1645
+ pthread_t thr;
1646
+ int r;
1647
+
1648
+ // dispose on another thread so we don't block when trying to
1649
+ // enter an isolate that's in a stuck state; that *should* be
1650
+ // impossible but apparently it happened regularly before the
1651
+ // rewrite and I'm carrying it over out of an abundance of caution
1652
+ if ((r = pthread_create(&thr, NULL, context_free_thread_do, c))) {
1653
+ fprintf(stderr, "mini_racer: pthread_create: %s", strerror(r));
1654
+ fflush(stderr);
1655
+ context_free_thread_do(c);
1656
+ } else {
1657
+ pthread_detach(thr);
1658
+ }
1659
+ }
1660
+
1661
+ static void context_free(void *arg)
1662
+ {
1663
+ Context *c;
1664
+
1665
+ c = arg;
1666
+ if (single_threaded) {
1667
+ context_free_thread(c);
1668
+ } else {
1669
+ pthread_mutex_lock(&c->mtx);
1670
+ c->quit = 2; // 2 = v8 thread frees
1671
+ pthread_cond_signal(&c->cv);
1672
+ pthread_mutex_unlock(&c->mtx);
1673
+ }
1674
+ }
1675
+
1676
+ static void context_destroy(Context *c)
1677
+ {
1678
+ pthread_mutex_unlock(&c->mtx);
1679
+ pthread_mutex_destroy(&c->mtx);
1680
+ pthread_cond_destroy(&c->cv);
1681
+ barrier_destroy(&c->early_init);
1682
+ barrier_destroy(&c->late_init);
1683
+ pthread_mutex_destroy(&c->wd.mtx);
1684
+ pthread_cond_destroy(&c->wd.cv);
1685
+ buf_reset(&c->snapshot);
1686
+ buf_reset(&c->host_namespace);
1687
+ buf_reset(&c->req);
1688
+ buf_reset(&c->res);
1689
+ ruby_xfree(c);
1690
+ }
1691
+
1692
+ static void context_mark(void *arg)
1693
+ {
1694
+ Context *c;
1695
+
1696
+ c = arg;
1697
+ rb_gc_mark(c->procs);
1698
+ rb_gc_mark(c->exception);
1699
+ rb_gc_mark(c->resolve_block);
1700
+ rb_gc_mark(c->dynamic_import_resolver);
1701
+ rb_gc_mark(c->graph_resolve_block);
1702
+ rb_gc_mark(c->graph_fetch_block);
1703
+ }
1704
+
1705
+ static size_t context_size(const void *arg)
1706
+ {
1707
+ const Context *c = arg;
1708
+ return sizeof(*c);
1709
+ }
1710
+
1711
+ static VALUE context_attach(VALUE self, VALUE name, VALUE proc)
1712
+ {
1713
+ Context *c;
1714
+ VALUE e;
1715
+ Ser s;
1716
+
1717
+ TypedData_Get_Struct(self, Context, &context_type, c);
1718
+ // request is (A)ttach, [name, id] array
1719
+ ser_init1(&s, 'A');
1720
+ ser_array_begin(&s, 2);
1721
+ add_string(&s, name);
1722
+ ser_int(&s, RARRAY_LENINT(c->procs));
1723
+ ser_array_end(&s, 2);
1724
+ rb_ary_push(c->procs, proc);
1725
+ // response is an exception or undefined
1726
+ e = rendezvous(c, &s.b);
1727
+ handle_exception(e);
1728
+ return Qnil;
1729
+ }
1730
+
1731
+ static void *context_dispose_do(void *arg)
1732
+ {
1733
+ Context *c;
1734
+
1735
+ c = arg;
1736
+ if (single_threaded) {
1737
+ pthread_mutex_lock(&c->mtx);
1738
+ while (c->req.len || c->res.len)
1739
+ pthread_cond_wait(&c->cv, &c->mtx);
1740
+ atomic_store(&c->quit, 1); // disposed
1741
+ if (c->single_threaded_thr_started && c->single_threaded_pid == getpid()) {
1742
+ pthread_cond_signal(&c->cv);
1743
+ pthread_mutex_unlock(&c->mtx);
1744
+ pthread_join(c->single_threaded_thr, NULL);
1745
+ pthread_mutex_lock(&c->mtx);
1746
+ c->single_threaded_thr_started = 0;
1747
+ }
1748
+ pthread_mutex_unlock(&c->mtx);
1749
+ } else {
1750
+ pthread_mutex_lock(&c->mtx);
1751
+ while (c->req.len || c->res.len)
1752
+ pthread_cond_wait(&c->cv, &c->mtx);
1753
+ atomic_store(&c->quit, 1); // disposed
1754
+ pthread_cond_signal(&c->cv); // wake up v8 thread
1755
+ pthread_mutex_unlock(&c->mtx);
1756
+ }
1757
+ return NULL;
1758
+ }
1759
+
1760
+ static VALUE context_dispose(VALUE self)
1761
+ {
1762
+ Context *c;
1763
+
1764
+ TypedData_Get_Struct(self, Context, &context_type, c);
1765
+ rb_thread_call_without_gvl(context_dispose_do, c, NULL, NULL);
1766
+ return Qnil;
1767
+ }
1768
+
1769
+ static VALUE context_stop(VALUE self)
1770
+ {
1771
+ Context *c;
1772
+
1773
+ // does not grab |mtx| because Context.stop can be called from another
1774
+ // thread and then we deadlock if e.g. the V8 thread busy-loops in JS
1775
+ TypedData_Get_Struct(self, Context, &context_type, c);
1776
+ if (atomic_load(&c->quit))
1777
+ rb_raise(context_disposed_error, "disposed context");
1778
+ v8_terminate_execution(c->pst);
1779
+ return Qnil;
1780
+ }
1781
+
1782
+ static VALUE context_call(int argc, VALUE *argv, VALUE self)
1783
+ {
1784
+ VALUE name, args;
1785
+ VALUE a, e;
1786
+ Context *c;
1787
+ Ser s;
1788
+
1789
+ TypedData_Get_Struct(self, Context, &context_type, c);
1790
+ rb_scan_args(argc, argv, "1*", &name, &args);
1791
+ Check_Type(name, T_STRING);
1792
+ rb_ary_unshift(args, name);
1793
+ // request is (C)all, [name, args...] array
1794
+ ser_init1(&s, 'C');
1795
+ if (serialize(&s, args)) {
1796
+ ser_reset(&s);
1797
+ rb_raise(runtime_error, "Context.call: %s", s.err);
1798
+ }
1799
+ // response is [result, err] array
1800
+ a = rendezvous(c, &s.b); // takes ownership of |s.b|
1801
+ e = rb_ary_pop(a);
1802
+ handle_exception(e);
1803
+ return rb_ary_pop(a);
1804
+ }
1805
+
1806
+ static VALUE context_eval(int argc, VALUE *argv, VALUE self)
1807
+ {
1808
+ VALUE a, e, source, filename, kwargs;
1809
+ Context *c;
1810
+ Ser s;
1811
+
1812
+ TypedData_Get_Struct(self, Context, &context_type, c);
1813
+ filename = Qnil;
1814
+ rb_scan_args(argc, argv, "1:", &source, &kwargs);
1815
+ Check_Type(source, T_STRING);
1816
+ if (!NIL_P(kwargs))
1817
+ filename = rb_hash_aref(kwargs, rb_id2sym(rb_intern("filename")));
1818
+ if (NIL_P(filename))
1819
+ filename = rb_str_new_cstr("<eval>");
1820
+ Check_Type(filename, T_STRING);
1821
+ // request is (E)val, [filename, source] array
1822
+ ser_init1(&s, 'E');
1823
+ ser_array_begin(&s, 2);
1824
+ add_string(&s, filename);
1825
+ add_string(&s, source);
1826
+ ser_array_end(&s, 2);
1827
+ // response is [result, errname] array
1828
+ a = rendezvous(c, &s.b); // takes ownership of |s.b|
1829
+ e = rb_ary_pop(a);
1830
+ handle_exception(e);
1831
+ return rb_ary_pop(a);
1832
+ }
1833
+
1834
+ static VALUE context_heap_stats(VALUE self)
1835
+ {
1836
+ VALUE a, h, k, v;
1837
+ Context *c;
1838
+ int i, n;
1839
+ Buf b;
1840
+
1841
+ TypedData_Get_Struct(self, Context, &context_type, c);
1842
+ buf_init(&b);
1843
+ buf_putc(&b, 'S'); // (S)tats, returns object
1844
+ h = rendezvous(c, &b); // takes ownership of |b|
1845
+ a = rb_ary_new();
1846
+ rb_hash_foreach(h, collect, a);
1847
+ for (i = 0, n = RARRAY_LENINT(a); i < n; i += 2) {
1848
+ k = rb_ary_entry(a, i+0);
1849
+ v = rb_ary_entry(a, i+1);
1850
+ rb_hash_delete(h, k);
1851
+ rb_hash_aset(h, rb_str_intern(k), v); // turn "key" into :key
1852
+ }
1853
+ return h;
1854
+ }
1855
+
1856
+ static VALUE context_heap_snapshot(VALUE self)
1857
+ {
1858
+ Buf req, res;
1859
+ Context *c;
1860
+
1861
+ TypedData_Get_Struct(self, Context, &context_type, c);
1862
+ buf_init(&req);
1863
+ buf_putc(&req, 'H'); // (H)eap snapshot, returns plain bytes
1864
+ rendezvous_no_des(c, &req, &res); // takes ownership of |req|
1865
+ return rb_utf8_str_new((char *)res.buf, res.len);
1866
+ }
1867
+
1868
+ static VALUE context_perform_microtask_checkpoint(VALUE self)
1869
+ {
1870
+ Context *c;
1871
+ Buf b;
1872
+
1873
+ TypedData_Get_Struct(self, Context, &context_type, c);
1874
+ buf_init(&b);
1875
+ buf_putc(&b, 'M'); // (M)icrotask checkpoint, returns nil
1876
+ return rendezvous(c, &b); // takes ownership of |b|
1877
+ }
1878
+
1879
+ // EXPERIMENTAL (spike): tears down the current JavaScript realm (globalThis,
1880
+ // every global it carried, registered custom elements, pending timers, ...)
1881
+ // and installs a pristine one from the startup snapshot, keeping the warm
1882
+ // isolate. The opt-in host namespace and any attach()ed host functions are
1883
+ // re-applied automatically. Previously compiled scripts/modules are realm-bound
1884
+ // and are invalidated by the reset. Refused from within a host callback.
1885
+ static VALUE context_reset_realm(VALUE self)
1886
+ {
1887
+ Context *c;
1888
+ VALUE e;
1889
+ Buf b;
1890
+
1891
+ TypedData_Get_Struct(self, Context, &context_type, c);
1892
+ buf_init(&b);
1893
+ buf_putc(&b, 'F'); // (F)resh realm, returns err or undefined
1894
+ e = rendezvous(c, &b); // takes ownership of |b|
1895
+ handle_exception(e);
1896
+ return Qnil;
1897
+ }
1898
+
1899
+ static VALUE context_pump_message_loop(VALUE self)
1900
+ {
1901
+ Context *c;
1902
+ Buf b;
1903
+
1904
+ TypedData_Get_Struct(self, Context, &context_type, c);
1905
+ buf_init(&b);
1906
+ buf_putc(&b, 'P'); // (P)ump, returns bool
1907
+ return rendezvous(c, &b); // takes ownership of |b|
1908
+ }
1909
+
1910
+ static VALUE context_low_memory_notification(VALUE self)
1911
+ {
1912
+ Buf req, res;
1913
+ Context *c;
1914
+
1915
+ TypedData_Get_Struct(self, Context, &context_type, c);
1916
+ buf_init(&req);
1917
+ buf_putc(&req, 'L'); // (L)ow memory notification, returns nothing
1918
+ rendezvous_no_des(c, &req, &res); // takes ownership of |req|
1919
+ return Qnil;
1920
+ }
1921
+
1922
+ static int platform_set_flag1(VALUE k, VALUE v)
1923
+ {
1924
+ char *p, *q, buf[256];
1925
+ int ok;
1926
+
1927
+ k = rb_funcall(k, rb_intern("to_s"), 0);
1928
+ Check_Type(k, T_STRING);
1929
+ if (!NIL_P(v)) {
1930
+ v = rb_funcall(v, rb_intern("to_s"), 0);
1931
+ Check_Type(v, T_STRING);
1932
+ }
1933
+ p = RSTRING_PTR(k);
1934
+ if (!strncmp(p, "--", 2))
1935
+ p += 2;
1936
+ if (NIL_P(v)) {
1937
+ snprintf(buf, sizeof(buf), "--%s", p);
1938
+ } else {
1939
+ snprintf(buf, sizeof(buf), "--%s=%s", p, RSTRING_PTR(v));
1940
+ }
1941
+ p = buf;
1942
+ pthread_mutex_lock(&flags_mtx);
1943
+ if (!flags.buf)
1944
+ buf_init(&flags);
1945
+ ok = (*flags.buf != 1);
1946
+ if (ok) {
1947
+ buf_put(&flags, p, 1+strlen(p)); // include trailing \0
1948
+ // strip dashes and underscores to reduce the number of variant
1949
+ // spellings (--no-single-threaded, --nosingle-threaded,
1950
+ // --no_single_threaded, etc.)
1951
+ p = q = buf;
1952
+ for (;;) {
1953
+ if (*p != '-')
1954
+ if (*p != '_')
1955
+ *q++ = *p;
1956
+ if (!*p++)
1957
+ break;
1958
+ }
1959
+ if (!strcmp(buf, "singlethreaded")) {
1960
+ single_threaded = 1;
1961
+ } else if (!strcmp(buf, "nosinglethreaded")) {
1962
+ single_threaded = 0;
1963
+ }
1964
+ }
1965
+ pthread_mutex_unlock(&flags_mtx);
1966
+ return ok;
1967
+ }
1968
+
1969
+ static VALUE platform_set_flags(int argc, VALUE *argv, VALUE klass)
1970
+ {
1971
+ VALUE args, kwargs, k, v;
1972
+ int i, n;
1973
+
1974
+ (void)&klass;
1975
+ rb_scan_args(argc, argv, "*:", &args, &kwargs);
1976
+ Check_Type(args, T_ARRAY);
1977
+ for (i = 0, n = RARRAY_LENINT(args); i < n; i++) {
1978
+ k = rb_ary_entry(args, i);
1979
+ v = Qnil;
1980
+ if (!platform_set_flag1(k, v))
1981
+ goto fail;
1982
+ }
1983
+ if (NIL_P(kwargs))
1984
+ return Qnil;
1985
+ Check_Type(kwargs, T_HASH);
1986
+ args = rb_ary_new();
1987
+ rb_hash_foreach(kwargs, collect, args);
1988
+ for (i = 0, n = RARRAY_LENINT(args); i < n; i += 2) {
1989
+ k = rb_ary_entry(args, i+0);
1990
+ v = rb_ary_entry(args, i+1);
1991
+ if (!platform_set_flag1(k, v))
1992
+ goto fail;
1993
+ }
1994
+ return Qnil;
1995
+ fail:
1996
+ rb_raise(platform_init_error, "platform already initialized");
1997
+ }
1998
+
1999
+ // called by v8_global_init; caller must free |*p| with free()
2000
+ void v8_get_flags(char **p, size_t *n)
2001
+ {
2002
+ *p = NULL;
2003
+ *n = 0;
2004
+ pthread_mutex_lock(&flags_mtx);
2005
+ if (!flags.len)
2006
+ goto out;
2007
+ *p = malloc(flags.len);
2008
+ if (!*p)
2009
+ goto out;
2010
+ *n = flags.len;
2011
+ memcpy(*p, flags.buf, *n);
2012
+ buf_reset(&flags);
2013
+ out:
2014
+ buf_init(&flags);
2015
+ buf_putc(&flags, 1); // marker to indicate it's been cleared
2016
+ pthread_mutex_unlock(&flags_mtx);
2017
+ if (single_threaded)
2018
+ rb_thread_lock_native_thread();
2019
+ }
2020
+
2021
+ // Blocks until the V8 thread finishes booting the isolate, which includes
2022
+ // snapshot deserialization (v8::Isolate::New) and safe-context setup. None of
2023
+ // that touches Ruby objects -- the snapshot is a plain C buffer already copied
2024
+ // into |c->snapshot| -- so we release the GVL while waiting, letting other Ruby
2025
+ // threads (e.g. a background pool refilling more contexts) truly run meanwhile.
2026
+ static void *context_boot_wait(void *arg)
2027
+ {
2028
+ Context *c;
2029
+
2030
+ c = arg;
2031
+ barrier_wait(&c->early_init);
2032
+ barrier_wait(&c->late_init);
2033
+ return NULL;
2034
+ }
2035
+
2036
+ static VALUE context_initialize(int argc, VALUE *argv, VALUE self)
2037
+ {
2038
+ VALUE kwargs, a, k, v;
2039
+ pthread_attr_t attr;
2040
+ const char *cause;
2041
+ pthread_t thr;
2042
+ Snapshot *ss;
2043
+ Context *c;
2044
+ char *s;
2045
+ int r;
2046
+
2047
+ TypedData_Get_Struct(self, Context, &context_type, c);
2048
+ rb_scan_args(argc, argv, ":", &kwargs);
2049
+ if (NIL_P(kwargs))
2050
+ goto init;
2051
+ a = rb_ary_new();
2052
+ rb_hash_foreach(kwargs, collect, a);
2053
+ while (RARRAY_LENINT(a)) {
2054
+ v = rb_ary_pop(a);
2055
+ k = rb_ary_pop(a);
2056
+ k = rb_sym2str(k);
2057
+ s = RSTRING_PTR(k);
2058
+ if (!strcmp(s, "ensure_gc_after_idle")) {
2059
+ Check_Type(v, T_FIXNUM);
2060
+ c->idle_gc = FIX2LONG(v);
2061
+ if (c->idle_gc < 0 || c->idle_gc > INT32_MAX)
2062
+ rb_raise(rb_eArgError, "bad ensure_gc_after_idle");
2063
+ } else if (!strcmp(s, "max_memory")) {
2064
+ Check_Type(v, T_FIXNUM);
2065
+ c->max_memory = FIX2LONG(v);
2066
+ if (c->max_memory < 0 || c->max_memory >= UINT32_MAX)
2067
+ rb_raise(rb_eArgError, "bad max_memory");
2068
+ } else if (!strcmp(s, "marshal_stack_depth")) { // backcompat, ignored
2069
+ Check_Type(v, T_FIXNUM);
2070
+ } else if (!strcmp(s, "timeout")) {
2071
+ Check_Type(v, T_FIXNUM);
2072
+ c->timeout = FIX2LONG(v);
2073
+ if (c->timeout < 0 || c->timeout > INT32_MAX)
2074
+ rb_raise(rb_eArgError, "bad timeout");
2075
+ } else if (!strcmp(s, "snapshot")) {
2076
+ if (NIL_P(v))
2077
+ continue;
2078
+ TypedData_Get_Struct(v, Snapshot, &snapshot_type, ss);
2079
+ if (buf_put(&c->snapshot, RSTRING_PTR(ss->blob), RSTRING_LENINT(ss->blob)))
2080
+ rb_raise(runtime_error, "out of memory");
2081
+ } else if (!strcmp(s, "verbose_exceptions")) {
2082
+ c->verbose_exceptions = !(v == Qfalse || v == Qnil);
2083
+ } else if (!strcmp(s, "host_namespace")) {
2084
+ const char *ns = NULL;
2085
+ if (v == Qtrue) {
2086
+ ns = "MiniRacer"; // default brand, like Deno's `Deno`
2087
+ } else if (v != Qnil && v != Qfalse) {
2088
+ Check_Type(v, T_STRING);
2089
+ ns = StringValueCStr(v); // raises on embedded NUL
2090
+ }
2091
+ if (ns && *ns) {
2092
+ // The name becomes a global, so require a valid (ASCII) JS
2093
+ // identifier; otherwise it would only be reachable through
2094
+ // globalThis["..."] rather than as `<name>.method()`.
2095
+ for (const char *q = ns; *q; q++) {
2096
+ int ch = (unsigned char)*q;
2097
+ int ident_start = ch == '_' || ch == '$' ||
2098
+ (ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z');
2099
+ int ident_char = ident_start || (ch >= '0' && ch <= '9');
2100
+ if (!(q == ns ? ident_start : ident_char))
2101
+ rb_raise(rb_eArgError,
2102
+ "host_namespace must be a valid identifier: %s", ns);
2103
+ }
2104
+ // store the name plus its NUL terminator
2105
+ buf_reset(&c->host_namespace);
2106
+ if (buf_put(&c->host_namespace, ns, strlen(ns) + 1))
2107
+ rb_raise(runtime_error, "out of memory");
2108
+ }
2109
+ } else {
2110
+ rb_raise(runtime_error, "bad keyword: %s", s);
2111
+ }
2112
+ }
2113
+ init:
2114
+ if (single_threaded) {
2115
+ v8_once_init();
2116
+ c->pst = v8_thread_init(c, c->snapshot.buf, c->snapshot.len, c->max_memory, c->verbose_exceptions,
2117
+ c->host_namespace.len ? (const char *)c->host_namespace.buf : NULL);
2118
+ } else {
2119
+ cause = "pthread_attr_init";
2120
+ if ((r = pthread_attr_init(&attr)))
2121
+ goto fail;
2122
+ pthread_attr_setstacksize(&attr, 2<<20); // 2 MiB
2123
+ pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
2124
+ // v8 thread takes ownership of |c|
2125
+ cause = "pthread_create";
2126
+ r = pthread_create(&thr, &attr, v8_thread_start, c);
2127
+ pthread_attr_destroy(&attr);
2128
+ if (r)
2129
+ goto fail;
2130
+ rb_thread_call_without_gvl(context_boot_wait, c, NULL, NULL);
2131
+ }
2132
+ // Deferred to first Context.new so Platform.set_flags! still has effect
2133
+ // on the tag (which depends on V8 flags applied during v8_global_init).
2134
+ {
2135
+ static int version_tag_defined;
2136
+ if (!version_tag_defined) {
2137
+ VALUE m = rb_const_get(rb_cObject, rb_intern("MiniRacer"));
2138
+ rb_define_const(m, "V8_CACHED_DATA_VERSION_TAG",
2139
+ UINT2NUM(v8_cached_data_version_tag()));
2140
+ version_tag_defined = 1;
2141
+ }
2142
+ }
2143
+ return Qnil;
2144
+ fail:
2145
+ rb_raise(runtime_error, "Context.initialize: %s: %s", cause, strerror(r));
2146
+ return Qnil; // pacify compiler
2147
+ }
2148
+
2149
+ static VALUE snapshot_alloc(VALUE klass)
2150
+ {
2151
+ Snapshot *ss;
2152
+
2153
+ ss = ruby_xmalloc(sizeof(*ss));
2154
+ ss->blob = rb_enc_str_new("", 0, rb_ascii8bit_encoding());
2155
+ return TypedData_Wrap_Struct(klass, &snapshot_type, ss);
2156
+ }
2157
+
2158
+ static void snapshot_free(void *arg)
2159
+ {
2160
+ ruby_xfree(arg);
2161
+ }
2162
+
2163
+ static void snapshot_mark(void *arg)
2164
+ {
2165
+ Snapshot *ss;
2166
+
2167
+ ss = arg;
2168
+ rb_gc_mark(ss->blob);
2169
+ }
2170
+
2171
+ static size_t snapshot_size(const void *arg)
2172
+ {
2173
+ const Snapshot *ss;
2174
+
2175
+ ss = arg;
2176
+ return sizeof(*ss) + RSTRING_LENINT(ss->blob);
2177
+ }
2178
+
2179
+ static VALUE snapshot_initialize(int argc, VALUE *argv, VALUE self)
2180
+ {
2181
+ VALUE a, e, code, cv;
2182
+ Snapshot *ss;
2183
+ Context *c;
2184
+ DesCtx d;
2185
+ Ser s;
2186
+
2187
+ TypedData_Get_Struct(self, Snapshot, &snapshot_type, ss);
2188
+ rb_scan_args(argc, argv, "01", &code);
2189
+ if (NIL_P(code))
2190
+ code = rb_str_new_cstr("");
2191
+ Check_Type(code, T_STRING);
2192
+ cv = context_alloc(context_class);
2193
+ context_initialize(0, NULL, cv);
2194
+ TypedData_Get_Struct(cv, Context, &context_type, c);
2195
+ // request is snapsho(T), "code"
2196
+ ser_init1(&s, 'T');
2197
+ add_string(&s, code);
2198
+ // response is [arraybuffer, error]
2199
+ DesCtx_init(&d);
2200
+ d.transcode_latin1 = 0; // don't mangle snapshot binary data
2201
+ a = rendezvous1(c, &s.b, &d);
2202
+ e = rb_ary_pop(a);
2203
+ context_dispose(cv);
2204
+ if (*RSTRING_PTR(e))
2205
+ rb_raise(snapshot_error, "%s", RSTRING_PTR(e)+1);
2206
+ ss->blob = rb_ary_pop(a);
2207
+ return Qnil;
2208
+ }
2209
+
2210
+ static VALUE snapshot_warmup(VALUE self, VALUE arg)
2211
+ {
2212
+ VALUE a, e, cv;
2213
+ Snapshot *ss;
2214
+ Context *c;
2215
+ DesCtx d;
2216
+ Ser s;
2217
+
2218
+ TypedData_Get_Struct(self, Snapshot, &snapshot_type, ss);
2219
+ Check_Type(arg, T_STRING);
2220
+ cv = context_alloc(context_class);
2221
+ context_initialize(0, NULL, cv);
2222
+ TypedData_Get_Struct(cv, Context, &context_type, c);
2223
+ // request is (W)armup, [snapshot, "warmup code"]
2224
+ ser_init1(&s, 'W');
2225
+ ser_array_begin(&s, 2);
2226
+ ser_string8(&s, (const uint8_t *)RSTRING_PTR(ss->blob), RSTRING_LENINT(ss->blob));
2227
+ add_string(&s, arg);
2228
+ ser_array_end(&s, 2);
2229
+ // response is [arraybuffer, error]
2230
+ DesCtx_init(&d);
2231
+ d.transcode_latin1 = 0; // don't mangle snapshot binary data
2232
+ a = rendezvous1(c, &s.b, &d);
2233
+ e = rb_ary_pop(a);
2234
+ context_dispose(cv);
2235
+ if (*RSTRING_PTR(e))
2236
+ rb_raise(snapshot_error, "%s", RSTRING_PTR(e)+1);
2237
+ ss->blob = rb_ary_pop(a);
2238
+ return self;
2239
+ }
2240
+
2241
+ static VALUE snapshot_dump(VALUE self)
2242
+ {
2243
+ Snapshot *ss;
2244
+
2245
+ TypedData_Get_Struct(self, Snapshot, &snapshot_type, ss);
2246
+ return ss->blob;
2247
+ }
2248
+
2249
+ static VALUE snapshot_load(VALUE klass, VALUE blob)
2250
+ {
2251
+ Snapshot *ss;
2252
+ VALUE self;
2253
+
2254
+ Check_Type(blob, T_STRING);
2255
+ self = snapshot_alloc(klass);
2256
+ TypedData_Get_Struct(self, Snapshot, &snapshot_type, ss);
2257
+ ss->blob = rb_str_dup(blob);
2258
+ rb_enc_associate(ss->blob, rb_ascii8bit_encoding());
2259
+ ENC_CODERANGE_SET(ss->blob, ENC_CODERANGE_VALID);
2260
+ return self;
2261
+ }
2262
+
2263
+ static VALUE snapshot_size0(VALUE self)
2264
+ {
2265
+ Snapshot *ss;
2266
+
2267
+ TypedData_Get_Struct(self, Snapshot, &snapshot_type, ss);
2268
+ return LONG2FIX(RSTRING_LENINT(ss->blob));
2269
+ }
2270
+
2271
+ static VALUE script_error_cause(VALUE self)
2272
+ {
2273
+ return rb_iv_get(self, "@cause");
2274
+ }
2275
+
2276
+ static VALUE context_compile(int argc, VALUE *argv, VALUE self)
2277
+ {
2278
+ VALUE a, e, source, filename, cached_data, produce_cache, kwargs;
2279
+ VALUE script_v, result;
2280
+ Script *script;
2281
+ Context *c;
2282
+ Ser s;
2283
+
2284
+ TypedData_Get_Struct(self, Context, &context_type, c);
2285
+ rb_scan_args(argc, argv, "1:", &source, &kwargs);
2286
+ Check_Type(source, T_STRING);
2287
+ filename = Qnil;
2288
+ cached_data = Qnil;
2289
+ produce_cache = Qfalse;
2290
+ if (!NIL_P(kwargs)) {
2291
+ filename = rb_hash_aref(kwargs, ID2SYM(id_filename));
2292
+ cached_data = rb_hash_aref(kwargs, ID2SYM(id_cached_data));
2293
+ produce_cache = rb_hash_aref(kwargs, ID2SYM(id_produce_cache));
2294
+ }
2295
+ if (NIL_P(filename))
2296
+ filename = rb_str_new_cstr("<compile>");
2297
+ Check_Type(filename, T_STRING);
2298
+ if (!NIL_P(cached_data)) {
2299
+ Check_Type(cached_data, T_STRING);
2300
+ // Refuse non-binary encodings so a user reading a cache file without
2301
+ // 'rb' mode gets a clear error instead of mangled bytes flowing to V8.
2302
+ if (rb_enc_get(cached_data) != rb_ascii8bit_encoding())
2303
+ rb_raise(rb_eEncodingError,
2304
+ "cached_data must be ASCII-8BIT (binary), got %s",
2305
+ rb_enc_name(rb_enc_get(cached_data)));
2306
+ }
2307
+ ser_init1(&s, 'K');
2308
+ ser_array_begin(&s, 4);
2309
+ add_string(&s, filename);
2310
+ add_string(&s, source);
2311
+ if (NIL_P(cached_data)) {
2312
+ ser_null(&s);
2313
+ } else {
2314
+ ser_uint8array(&s, (const uint8_t *)RSTRING_PTR(cached_data),
2315
+ RSTRING_LENINT(cached_data));
2316
+ }
2317
+ ser_bool(&s, RTEST(produce_cache));
2318
+ ser_array_end(&s, 4);
2319
+ a = rendezvous(c, &s.b);
2320
+ e = rb_ary_pop(a);
2321
+ handle_exception(e);
2322
+ result = rb_ary_pop(a);
2323
+ Check_Type(result, T_ARRAY);
2324
+
2325
+ script_v = rb_obj_alloc(script_class); // skip the raising initialize
2326
+ TypedData_Get_Struct(script_v, Script, &script_type, script);
2327
+ script->context = self;
2328
+ script->handle_id = NUM2INT(rb_ary_entry(result, 0));
2329
+ script->cached_data = rb_ary_entry(result, 1);
2330
+ script->cache_rejected = RTEST(rb_ary_entry(result, 2));
2331
+ return script_v;
2332
+ }
2333
+
2334
+ static VALUE script_alloc(VALUE klass)
2335
+ {
2336
+ Script *s;
2337
+
2338
+ s = ruby_xmalloc(sizeof(*s));
2339
+ memset(s, 0, sizeof(*s));
2340
+ s->context = Qnil;
2341
+ s->cached_data = Qnil;
2342
+ return TypedData_Wrap_Struct(klass, &script_type, s);
2343
+ }
2344
+
2345
+ static void script_free(void *arg)
2346
+ {
2347
+ // Intentionally does not send a dispose RPC — finalizers can't safely
2348
+ // take rr_mtx. State::~State() walks st.scripts at isolate teardown so
2349
+ // we leak nothing across a Context's lifetime; use Script#dispose to
2350
+ // free eagerly mid-lifetime.
2351
+ ruby_xfree(arg);
2352
+ }
2353
+
2354
+ static void script_mark(void *arg)
2355
+ {
2356
+ Script *s = arg;
2357
+ rb_gc_mark(s->context);
2358
+ rb_gc_mark(s->cached_data);
2359
+ }
2360
+
2361
+ static size_t script_size(const void *arg)
2362
+ {
2363
+ const Script *s = arg;
2364
+ size_t base = sizeof(*s);
2365
+ if (!NIL_P(s->cached_data))
2366
+ base += RSTRING_LENINT(s->cached_data);
2367
+ return base;
2368
+ }
2369
+
2370
+ static VALUE script_initialize(int argc, VALUE *argv, VALUE self)
2371
+ {
2372
+ (void)argc; (void)argv; (void)self;
2373
+ rb_raise(runtime_error, "MiniRacer::Script must be created via Context#compile");
2374
+ return Qnil;
2375
+ }
2376
+
2377
+ static VALUE script_run(VALUE self)
2378
+ {
2379
+ VALUE a, e;
2380
+ Script *script;
2381
+ Context *c;
2382
+ Ser s;
2383
+
2384
+ TypedData_Get_Struct(self, Script, &script_type, script);
2385
+ if (script->disposed)
2386
+ rb_raise(runtime_error, "disposed script");
2387
+ TypedData_Get_Struct(script->context, Context, &context_type, c);
2388
+ if (atomic_load(&c->quit))
2389
+ rb_raise(context_disposed_error, "disposed context");
2390
+ ser_init1(&s, 'R');
2391
+ ser_int(&s, script->handle_id);
2392
+ a = rendezvous(c, &s.b);
2393
+ e = rb_ary_pop(a);
2394
+ handle_exception(e);
2395
+ return rb_ary_pop(a);
2396
+ }
2397
+
2398
+ static VALUE script_cached_data(VALUE self)
2399
+ {
2400
+ Script *script;
2401
+ TypedData_Get_Struct(self, Script, &script_type, script);
2402
+ return script->cached_data;
2403
+ }
2404
+
2405
+ static VALUE script_cache_rejected_p(VALUE self)
2406
+ {
2407
+ Script *script;
2408
+ TypedData_Get_Struct(self, Script, &script_type, script);
2409
+ return script->cache_rejected ? Qtrue : Qfalse;
2410
+ }
2411
+
2412
+ static VALUE script_dispose(VALUE self)
2413
+ {
2414
+ VALUE e;
2415
+ Script *script;
2416
+ Context *c;
2417
+ Ser s;
2418
+
2419
+ TypedData_Get_Struct(self, Script, &script_type, script);
2420
+ if (script->disposed) return Qnil;
2421
+ TypedData_Get_Struct(script->context, Context, &context_type, c);
2422
+ script->disposed = 1;
2423
+ // Context already gone? The handle was cleaned by State::~State().
2424
+ if (atomic_load(&c->quit))
2425
+ return Qnil;
2426
+ ser_init1(&s, 'D');
2427
+ ser_int(&s, script->handle_id);
2428
+ e = rendezvous(c, &s.b);
2429
+ handle_exception(e);
2430
+ return Qnil;
2431
+ }
2432
+
2433
+ static VALUE script_disposed_p(VALUE self)
2434
+ {
2435
+ Script *script;
2436
+ TypedData_Get_Struct(self, Script, &script_type, script);
2437
+ return script->disposed ? Qtrue : Qfalse;
2438
+ }
2439
+
2440
+ static VALUE context_compile_module(int argc, VALUE *argv, VALUE self)
2441
+ {
2442
+ VALUE a, e, source, filename, cached_data, produce_cache, kwargs;
2443
+ VALUE module_v, result;
2444
+ Module *m;
2445
+ Context *c;
2446
+ Ser s;
2447
+
2448
+ TypedData_Get_Struct(self, Context, &context_type, c);
2449
+ if (atomic_load(&c->quit))
2450
+ rb_raise(context_disposed_error, "disposed context");
2451
+ rb_scan_args(argc, argv, "1:", &source, &kwargs);
2452
+ Check_Type(source, T_STRING);
2453
+ filename = Qnil;
2454
+ cached_data = Qnil;
2455
+ produce_cache = Qfalse;
2456
+ if (!NIL_P(kwargs)) {
2457
+ filename = rb_hash_aref(kwargs, ID2SYM(id_filename));
2458
+ cached_data = rb_hash_aref(kwargs, ID2SYM(id_cached_data));
2459
+ produce_cache = rb_hash_aref(kwargs, ID2SYM(id_produce_cache));
2460
+ }
2461
+ if (NIL_P(filename))
2462
+ filename = rb_str_new_cstr("<compile_module>");
2463
+ Check_Type(filename, T_STRING);
2464
+ if (!NIL_P(cached_data)) {
2465
+ Check_Type(cached_data, T_STRING);
2466
+ // Refuse non-binary encodings so a user reading a cache file without
2467
+ // 'rb' mode gets a clear error instead of mangled bytes flowing to V8.
2468
+ if (rb_enc_get(cached_data) != rb_ascii8bit_encoding())
2469
+ rb_raise(rb_eEncodingError,
2470
+ "cached_data must be ASCII-8BIT (binary), got %s",
2471
+ rb_enc_name(rb_enc_get(cached_data)));
2472
+ }
2473
+ ser_init1(&s, 'O');
2474
+ ser_array_begin(&s, 4);
2475
+ add_string(&s, filename);
2476
+ add_string(&s, source);
2477
+ if (NIL_P(cached_data)) {
2478
+ ser_null(&s);
2479
+ } else {
2480
+ ser_uint8array(&s, (const uint8_t *)RSTRING_PTR(cached_data),
2481
+ RSTRING_LENINT(cached_data));
2482
+ }
2483
+ ser_bool(&s, RTEST(produce_cache));
2484
+ ser_array_end(&s, 4);
2485
+ a = rendezvous(c, &s.b);
2486
+ e = rb_ary_pop(a);
2487
+ handle_exception(e);
2488
+ result = rb_ary_pop(a);
2489
+ Check_Type(result, T_ARRAY);
2490
+
2491
+ module_v = rb_obj_alloc(module_class); // skip the raising initialize
2492
+ TypedData_Get_Struct(module_v, Module, &module_type, m);
2493
+ m->context = self;
2494
+ m->handle_id = NUM2INT(rb_ary_entry(result, 0));
2495
+ m->cached_data = rb_ary_entry(result, 1);
2496
+ m->cache_rejected = RTEST(rb_ary_entry(result, 2));
2497
+ return module_v;
2498
+ }
2499
+
2500
+ static VALUE module_cached_data(VALUE self)
2501
+ {
2502
+ Module *m;
2503
+ TypedData_Get_Struct(self, Module, &module_type, m);
2504
+ return m->cached_data;
2505
+ }
2506
+
2507
+ static VALUE module_cache_rejected_p(VALUE self)
2508
+ {
2509
+ Module *m;
2510
+ TypedData_Get_Struct(self, Module, &module_type, m);
2511
+ return m->cache_rejected ? Qtrue : Qfalse;
2512
+ }
2513
+
2514
+ static VALUE module_alloc(VALUE klass)
2515
+ {
2516
+ Module *m;
2517
+
2518
+ m = ruby_xmalloc(sizeof(*m));
2519
+ memset(m, 0, sizeof(*m));
2520
+ m->context = Qnil;
2521
+ m->cached_data = Qnil;
2522
+ return TypedData_Wrap_Struct(klass, &module_type, m);
2523
+ }
2524
+
2525
+ static void module_free(void *arg)
2526
+ {
2527
+ // Intentionally does not send a dispose RPC — finalizers can't safely
2528
+ // take rr_mtx. State::~State() walks st.modules at isolate teardown so
2529
+ // we leak nothing across a Context's lifetime; use Module#dispose to
2530
+ // free eagerly mid-lifetime.
2531
+ ruby_xfree(arg);
2532
+ }
2533
+
2534
+ static void module_mark(void *arg)
2535
+ {
2536
+ Module *m = arg;
2537
+ rb_gc_mark(m->context);
2538
+ rb_gc_mark(m->cached_data);
2539
+ }
2540
+
2541
+ static size_t module_size(const void *arg)
2542
+ {
2543
+ (void)arg;
2544
+ return sizeof(Module);
2545
+ }
2546
+
2547
+ static VALUE module_initialize(int argc, VALUE *argv, VALUE self)
2548
+ {
2549
+ (void)argc; (void)argv; (void)self;
2550
+ rb_raise(runtime_error, "MiniRacer::Module must be created via Context#compile_module");
2551
+ return Qnil;
2552
+ }
2553
+
2554
+ struct instantiate_args {
2555
+ Context *c;
2556
+ int32_t handle_id;
2557
+ VALUE prev_block;
2558
+ };
2559
+
2560
+ static VALUE module_instantiate_body(VALUE arg)
2561
+ {
2562
+ struct instantiate_args *ia = (struct instantiate_args *)arg;
2563
+ VALUE a, e;
2564
+ Ser s;
2565
+
2566
+ ser_init1(&s, 'I');
2567
+ ser_int(&s, ia->handle_id);
2568
+ a = rendezvous(ia->c, &s.b);
2569
+ e = rb_ary_pop(a);
2570
+ handle_exception(e);
2571
+ return Qnil;
2572
+ }
2573
+
2574
+ static VALUE module_instantiate_restore(VALUE arg)
2575
+ {
2576
+ struct instantiate_args *ia = (struct instantiate_args *)arg;
2577
+ ia->c->resolve_block = ia->prev_block;
2578
+ return Qnil;
2579
+ }
2580
+
2581
+ static VALUE module_instantiate(VALUE self)
2582
+ {
2583
+ VALUE block;
2584
+ Module *m;
2585
+ Context *c;
2586
+ struct instantiate_args ia;
2587
+
2588
+ if (!rb_block_given_p())
2589
+ rb_raise(rb_eArgError, "Module#instantiate requires a resolver block");
2590
+ block = rb_block_proc();
2591
+
2592
+ TypedData_Get_Struct(self, Module, &module_type, m);
2593
+ if (m->disposed)
2594
+ rb_raise(runtime_error, "disposed module");
2595
+ TypedData_Get_Struct(m->context, Context, &context_type, c);
2596
+ if (atomic_load(&c->quit))
2597
+ rb_raise(context_disposed_error, "disposed context");
2598
+
2599
+ // Save the previous resolver slot so a re-entrant instantiate from
2600
+ // inside this block restores its caller's block on the way out.
2601
+ // rb_ensure guarantees restoration even when the resolver block
2602
+ // raises (without it, the slot would be left pointing at this call's
2603
+ // block and keep it GC-alive until something else overwrites it).
2604
+ ia.c = c;
2605
+ ia.handle_id = m->handle_id;
2606
+ ia.prev_block = c->resolve_block;
2607
+ c->resolve_block = block;
2608
+ rb_ensure(module_instantiate_body, (VALUE)&ia,
2609
+ module_instantiate_restore, (VALUE)&ia);
2610
+ return self;
2611
+ }
2612
+
2613
+ struct load_graph_args {
2614
+ Context *c;
2615
+ VALUE entry_url;
2616
+ };
2617
+
2618
+ static VALUE context_load_module_graph_body(VALUE arg)
2619
+ {
2620
+ struct load_graph_args *la = (struct load_graph_args *)arg;
2621
+ VALUE a, e, result, value, mods, out;
2622
+ long i, len;
2623
+ Ser s;
2624
+
2625
+ // request is (G)raph, [entry_url]
2626
+ ser_init1(&s, 'G');
2627
+ ser_array_begin(&s, 1);
2628
+ add_string(&s, la->entry_url);
2629
+ ser_array_end(&s, 1);
2630
+ a = rendezvous(la->c, &s.b); // takes ownership of |s.b|
2631
+ e = rb_ary_pop(a);
2632
+ handle_exception(e);
2633
+ result = rb_ary_pop(a); // [value, [[url, cache_rejected], ...]]
2634
+ Check_Type(result, T_ARRAY);
2635
+ value = rb_ary_entry(result, 0);
2636
+ mods = rb_ary_entry(result, 1);
2637
+ Check_Type(mods, T_ARRAY);
2638
+
2639
+ len = RARRAY_LEN(mods);
2640
+ out = rb_ary_new_capa(len);
2641
+ for (i = 0; i < len; i++) {
2642
+ VALUE row = rb_ary_entry(mods, i), h = rb_hash_new();
2643
+ rb_hash_aset(h, ID2SYM(rb_intern("url")), rb_ary_entry(row, 0));
2644
+ rb_hash_aset(h, ID2SYM(rb_intern("cache_rejected")), rb_ary_entry(row, 1));
2645
+ rb_ary_push(out, h);
2646
+ }
2647
+ {
2648
+ VALUE h = rb_hash_new();
2649
+ rb_hash_aset(h, ID2SYM(rb_intern("value")), value);
2650
+ rb_hash_aset(h, ID2SYM(rb_intern("modules")), out);
2651
+ return h;
2652
+ }
2653
+ }
2654
+
2655
+ // EXPERIMENTAL (spike): compile+instantiate+evaluate an entire ES module graph
2656
+ // reachable from |entry_url| in one call, driving the walk on the V8 thread.
2657
+ // resolve:/fetch_batch: are invoked in batches (once per graph level) instead of
2658
+ // once per module/import, collapsing the ~2*N Ruby<->V8 round-trips of the
2659
+ // compile_module + instantiate path to ~2 per level.
2660
+ // resolve: ->(edges) { edges.map { |specifier, referrer| url_or_nil } }
2661
+ // fetch_batch: ->(urls) { urls.map { |u| [source, cached_data] | nil } }
2662
+ // Returns { value:, modules: [{ url:, cache_rejected: }, ...] } for the modules
2663
+ // newly compiled by this call (already-registered URLs are reused, not relisted).
2664
+ //
2665
+ // The resolve/fetch_batch callbacks are persisted on the Context (overwritten by
2666
+ // the next load_module_graph, freed on dispose): a later dynamic import() reuses
2667
+ // them and the URL registry, so a URL the graph already loaded resolves to the
2668
+ // same Module instance instead of being recompiled.
2669
+ static VALUE context_load_module_graph(int argc, VALUE *argv, VALUE self)
2670
+ {
2671
+ VALUE entry_url, kwargs, resolve, fetch_batch;
2672
+ Context *c;
2673
+ struct load_graph_args la;
2674
+
2675
+ TypedData_Get_Struct(self, Context, &context_type, c);
2676
+ if (atomic_load(&c->quit))
2677
+ rb_raise(context_disposed_error, "disposed context");
2678
+ rb_scan_args(argc, argv, "1:", &entry_url, &kwargs);
2679
+ Check_Type(entry_url, T_STRING);
2680
+ resolve = fetch_batch = Qnil;
2681
+ if (!NIL_P(kwargs)) {
2682
+ resolve = rb_hash_aref(kwargs, ID2SYM(rb_intern("resolve")));
2683
+ fetch_batch = rb_hash_aref(kwargs, ID2SYM(rb_intern("fetch_batch")));
2684
+ }
2685
+ if (!rb_respond_to(resolve, rb_intern("call")))
2686
+ rb_raise(rb_eArgError, "load_module_graph requires a resolve: callable");
2687
+ if (!rb_respond_to(fetch_batch, rb_intern("call")))
2688
+ rb_raise(rb_eArgError, "load_module_graph requires a fetch_batch: callable");
2689
+
2690
+ // Persist the callbacks for the Context's lifetime so dynamic import() can
2691
+ // reuse them after this call returns (case A). A later load_module_graph
2692
+ // overwrites them; dispose frees them.
2693
+ c->graph_resolve_block = resolve;
2694
+ c->graph_fetch_block = fetch_batch;
2695
+ la.c = c;
2696
+ la.entry_url = entry_url;
2697
+ return context_load_module_graph_body((VALUE)&la);
2698
+ }
2699
+
2700
+ static VALUE module_evaluate(VALUE self)
2701
+ {
2702
+ VALUE a, e;
2703
+ Module *m;
2704
+ Context *c;
2705
+ Ser s;
2706
+
2707
+ TypedData_Get_Struct(self, Module, &module_type, m);
2708
+ if (m->disposed)
2709
+ rb_raise(runtime_error, "disposed module");
2710
+ TypedData_Get_Struct(m->context, Context, &context_type, c);
2711
+ if (atomic_load(&c->quit))
2712
+ rb_raise(context_disposed_error, "disposed context");
2713
+ ser_init1(&s, 'V');
2714
+ ser_int(&s, m->handle_id);
2715
+ a = rendezvous(c, &s.b);
2716
+ e = rb_ary_pop(a);
2717
+ handle_exception(e);
2718
+ return rb_ary_pop(a);
2719
+ }
2720
+
2721
+ static VALUE module_namespace(VALUE self)
2722
+ {
2723
+ VALUE a, e;
2724
+ Module *m;
2725
+ Context *c;
2726
+ Ser s;
2727
+
2728
+ TypedData_Get_Struct(self, Module, &module_type, m);
2729
+ if (m->disposed)
2730
+ rb_raise(runtime_error, "disposed module");
2731
+ TypedData_Get_Struct(m->context, Context, &context_type, c);
2732
+ if (atomic_load(&c->quit))
2733
+ rb_raise(context_disposed_error, "disposed context");
2734
+ ser_init1(&s, 'N');
2735
+ ser_int(&s, m->handle_id);
2736
+ a = rendezvous(c, &s.b);
2737
+ e = rb_ary_pop(a);
2738
+ handle_exception(e);
2739
+ return rb_ary_pop(a);
2740
+ }
2741
+
2742
+ static VALUE module_status(VALUE self)
2743
+ {
2744
+ VALUE a, e, result;
2745
+ Module *m;
2746
+ Context *c;
2747
+ Ser s;
2748
+
2749
+ TypedData_Get_Struct(self, Module, &module_type, m);
2750
+ if (m->disposed)
2751
+ rb_raise(runtime_error, "disposed module");
2752
+ TypedData_Get_Struct(m->context, Context, &context_type, c);
2753
+ if (atomic_load(&c->quit))
2754
+ rb_raise(context_disposed_error, "disposed context");
2755
+ ser_init1(&s, 'U');
2756
+ ser_int(&s, m->handle_id);
2757
+ a = rendezvous(c, &s.b);
2758
+ e = rb_ary_pop(a);
2759
+ handle_exception(e);
2760
+ result = rb_ary_pop(a);
2761
+ // v8_module_status always replies with a String on success; a non-string
2762
+ // would mean the v8 thread fell through to the Undefined fail path with
2763
+ // a missing error (shouldn't happen, but check defensively rather than
2764
+ // crash rb_str_intern on Qnil).
2765
+ Check_Type(result, T_STRING);
2766
+ return rb_str_intern(result);
2767
+ }
2768
+
2769
+ static VALUE module_dispose(VALUE self)
2770
+ {
2771
+ VALUE e;
2772
+ Module *m;
2773
+ Context *c;
2774
+ Ser s;
2775
+
2776
+ TypedData_Get_Struct(self, Module, &module_type, m);
2777
+ if (m->disposed) return Qnil;
2778
+ TypedData_Get_Struct(m->context, Context, &context_type, c);
2779
+ // Context already gone? The handle was cleaned by State::~State().
2780
+ if (atomic_load(&c->quit)) {
2781
+ m->disposed = 1;
2782
+ return Qnil;
2783
+ }
2784
+ ser_init1(&s, 'Z');
2785
+ ser_int(&s, m->handle_id);
2786
+ e = rendezvous(c, &s.b);
2787
+ handle_exception(e);
2788
+ // Mark disposed only after the V8 handle is actually freed so a retry
2789
+ // after a transient rendezvous failure can still release it.
2790
+ m->disposed = 1;
2791
+ return Qnil;
2792
+ }
2793
+
2794
+ static VALUE module_disposed_p(VALUE self)
2795
+ {
2796
+ Module *m;
2797
+ TypedData_Get_Struct(self, Module, &module_type, m);
2798
+ return m->disposed ? Qtrue : Qfalse;
2799
+ }
2800
+
2801
+ static VALUE context_set_dynamic_import_resolver(VALUE self, VALUE blk)
2802
+ {
2803
+ Context *c;
2804
+ TypedData_Get_Struct(self, Context, &context_type, c);
2805
+ if (!NIL_P(blk) && !rb_respond_to(blk, rb_intern("call")))
2806
+ rb_raise(rb_eTypeError,
2807
+ "dynamic_import_resolver must respond to #call or be nil");
2808
+ c->dynamic_import_resolver = blk;
2809
+ return blk;
2810
+ }
2811
+
2812
+ static VALUE context_get_dynamic_import_resolver(VALUE self)
2813
+ {
2814
+ Context *c;
2815
+ TypedData_Get_Struct(self, Context, &context_type, c);
2816
+ return c->dynamic_import_resolver;
2817
+ }
2818
+
2819
+ __attribute__((visibility("default")))
2820
+ void Init_mini_racer_extension(void)
2821
+ {
2822
+ VALUE c, m;
2823
+
2824
+ id_filename = rb_intern("filename");
2825
+ id_cached_data = rb_intern("cached_data");
2826
+ id_produce_cache = rb_intern("produce_cache");
2827
+
2828
+ m = rb_define_module("MiniRacer");
2829
+ c = rb_define_class_under(m, "Error", rb_eStandardError);
2830
+ snapshot_error = rb_define_class_under(m, "SnapshotError", c);
2831
+ platform_init_error = rb_define_class_under(m, "PlatformAlreadyInitialized", c);
2832
+ context_disposed_error = rb_define_class_under(m, "ContextDisposedError", c);
2833
+
2834
+ c = rb_define_class_under(m, "EvalError", c);
2835
+ parse_error = rb_define_class_under(m, "ParseError", c);
2836
+ memory_error = rb_define_class_under(m, "V8OutOfMemoryError", c);
2837
+ script_error = rb_define_class_under(m, "ScriptError", c);
2838
+ runtime_error = rb_define_class_under(m, "RuntimeError", c);
2839
+ internal_error = rb_define_class_under(m, "InternalError", c);
2840
+ terminated_error = rb_define_class_under(m, "ScriptTerminatedError", c);
2841
+
2842
+ rb_define_method(script_error, "cause", script_error_cause, 0);
2843
+
2844
+ c = context_class = rb_define_class_under(m, "Context", rb_cObject);
2845
+ rb_define_method(c, "initialize", context_initialize, -1);
2846
+ rb_define_method(c, "attach", context_attach, 2);
2847
+ rb_define_method(c, "compile", context_compile, -1);
2848
+ rb_define_method(c, "compile_module", context_compile_module, -1);
2849
+ rb_define_method(c, "load_module_graph", context_load_module_graph, -1);
2850
+ rb_define_method(c, "dispose", context_dispose, 0);
2851
+ rb_define_method(c, "stop", context_stop, 0);
2852
+ rb_define_method(c, "call", context_call, -1);
2853
+ rb_define_method(c, "eval", context_eval, -1);
2854
+ rb_define_method(c, "heap_stats", context_heap_stats, 0);
2855
+ rb_define_method(c, "heap_snapshot", context_heap_snapshot, 0);
2856
+ rb_define_method(c, "perform_microtask_checkpoint", context_perform_microtask_checkpoint, 0);
2857
+ rb_define_method(c, "reset_realm", context_reset_realm, 0);
2858
+ rb_define_method(c, "pump_message_loop", context_pump_message_loop, 0);
2859
+ rb_define_method(c, "low_memory_notification", context_low_memory_notification, 0);
2860
+ rb_define_method(c, "dynamic_import_resolver", context_get_dynamic_import_resolver, 0);
2861
+ rb_define_method(c, "dynamic_import_resolver=", context_set_dynamic_import_resolver, 1);
2862
+ rb_define_alloc_func(c, context_alloc);
2863
+
2864
+ c = script_class = rb_define_class_under(m, "Script", rb_cObject);
2865
+ rb_define_method(c, "initialize", script_initialize, -1);
2866
+ rb_define_method(c, "run", script_run, 0);
2867
+ rb_define_method(c, "cached_data", script_cached_data, 0);
2868
+ rb_define_method(c, "cache_rejected?", script_cache_rejected_p, 0);
2869
+ rb_define_method(c, "dispose", script_dispose, 0);
2870
+ rb_define_method(c, "disposed?", script_disposed_p, 0);
2871
+ rb_define_alloc_func(c, script_alloc);
2872
+
2873
+ c = module_class = rb_define_class_under(m, "Module", rb_cObject);
2874
+ rb_define_method(c, "initialize", module_initialize, -1);
2875
+ rb_define_method(c, "instantiate", module_instantiate, 0);
2876
+ rb_define_method(c, "evaluate", module_evaluate, 0);
2877
+ rb_define_method(c, "namespace", module_namespace, 0);
2878
+ rb_define_method(c, "status", module_status, 0);
2879
+ rb_define_method(c, "cached_data", module_cached_data, 0);
2880
+ rb_define_method(c, "cache_rejected?", module_cache_rejected_p, 0);
2881
+ rb_define_method(c, "dispose", module_dispose, 0);
2882
+ rb_define_method(c, "disposed?", module_disposed_p, 0);
2883
+ rb_define_alloc_func(c, module_alloc);
2884
+
2885
+ c = snapshot_class = rb_define_class_under(m, "Snapshot", rb_cObject);
2886
+ rb_define_method(c, "initialize", snapshot_initialize, -1);
2887
+ rb_define_method(c, "warmup!", snapshot_warmup, 1);
2888
+ rb_define_method(c, "dump", snapshot_dump, 0);
2889
+ rb_define_method(c, "size", snapshot_size0, 0);
2890
+ rb_define_singleton_method(c, "load", snapshot_load, 1);
2891
+ rb_define_alloc_func(c, snapshot_alloc);
2892
+
2893
+ c = rb_define_class_under(m, "Platform", rb_cObject);
2894
+ rb_define_singleton_method(c, "set_flags!", platform_set_flags, -1);
2895
+
2896
+ date_time_class = Qnil; // lazy init
2897
+ binary_class = Qnil; // lazy init
2898
+ js_function_class = rb_define_class_under(m, "JavaScriptFunction", rb_cObject);
2899
+ }