mini_racer_rs 1.0.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.
Files changed (97) hide show
  1. checksums.yaml +7 -0
  2. data/AI-DISCLOSURE.md +13 -0
  3. data/CHANGELOG.md +45 -0
  4. data/COMPATIBILITY.md +235 -0
  5. data/Cargo.lock +1263 -0
  6. data/Cargo.toml +62 -0
  7. data/LICENSE +21 -0
  8. data/README.md +254 -0
  9. data/THIRD_PARTY_NOTICES.md +4501 -0
  10. data/crates/mini_racer_engine/Cargo.toml +25 -0
  11. data/crates/mini_racer_engine/build.rs +13 -0
  12. data/crates/mini_racer_engine/src/context.rs +802 -0
  13. data/crates/mini_racer_engine/src/control.rs +239 -0
  14. data/crates/mini_racer_engine/src/error.rs +88 -0
  15. data/crates/mini_racer_engine/src/fast_calls.rs +681 -0
  16. data/crates/mini_racer_engine/src/lib.rs +20 -0
  17. data/crates/mini_racer_engine/src/notify.rs +107 -0
  18. data/crates/mini_racer_engine/src/process/code_cache.rs +412 -0
  19. data/crates/mini_racer_engine/src/process/fork.rs +261 -0
  20. data/crates/mini_racer_engine/src/process/platform.rs +194 -0
  21. data/crates/mini_racer_engine/src/process/tasks.rs +96 -0
  22. data/crates/mini_racer_engine/src/process/watchdog.rs +185 -0
  23. data/crates/mini_racer_engine/src/process/workers.rs +165 -0
  24. data/crates/mini_racer_engine/src/process.rs +69 -0
  25. data/crates/mini_racer_engine/src/runner.rs +1560 -0
  26. data/crates/mini_racer_engine/src/serialize.rs +253 -0
  27. data/crates/mini_racer_engine/src/snapshot.rs +221 -0
  28. data/crates/mini_racer_engine/src/spin.rs +23 -0
  29. data/crates/mini_racer_engine/src/sys.rs +566 -0
  30. data/crates/mini_racer_sync/Cargo.toml +18 -0
  31. data/crates/mini_racer_sync/src/channel.rs +621 -0
  32. data/crates/mini_racer_sync/src/fork.rs +116 -0
  33. data/crates/mini_racer_sync/src/lib.rs +20 -0
  34. data/crates/mini_racer_sync/src/lock.rs +49 -0
  35. data/crates/mini_racer_sync/src/parker.rs +44 -0
  36. data/crates/mini_racer_sync/src/primitives.rs +10 -0
  37. data/crates/mini_racer_sync/src/tasks.rs +345 -0
  38. data/crates/mini_racer_sync/src/termination.rs +1234 -0
  39. data/crates/mini_racer_sync/src/workers.rs +283 -0
  40. data/crates/mini_racer_wire/Cargo.toml +18 -0
  41. data/crates/mini_racer_wire/src/decode.rs +926 -0
  42. data/crates/mini_racer_wire/src/lib.rs +67 -0
  43. data/crates/mini_racer_wire/src/marker.rs +43 -0
  44. data/crates/mini_racer_wire/src/text.rs +106 -0
  45. data/crates/mini_racer_wire/src/write.rs +187 -0
  46. data/ext/mini_racer_rs/Cargo.toml +26 -0
  47. data/ext/mini_racer_rs/build.rs +16 -0
  48. data/ext/mini_racer_rs/extconf.rb +78 -0
  49. data/ext/mini_racer_rs/src/context.rs +485 -0
  50. data/ext/mini_racer_rs/src/decode.rs +450 -0
  51. data/ext/mini_racer_rs/src/encode.rs +333 -0
  52. data/ext/mini_racer_rs/src/errors.rs +156 -0
  53. data/ext/mini_racer_rs/src/fork.rs +31 -0
  54. data/ext/mini_racer_rs/src/gc_root.rs +117 -0
  55. data/ext/mini_racer_rs/src/guard.rs +7 -0
  56. data/ext/mini_racer_rs/src/host.rs +339 -0
  57. data/ext/mini_racer_rs/src/lib.rs +44 -0
  58. data/ext/mini_racer_rs/src/options.rs +269 -0
  59. data/ext/mini_racer_rs/src/platform.rs +42 -0
  60. data/ext/mini_racer_rs/src/rb_raw.rs +368 -0
  61. data/ext/mini_racer_rs/src/snapshot.rs +149 -0
  62. data/ext/mini_racer_rs/v8-archives.sha256 +8 -0
  63. data/lib/mini_racer/shared.rb +420 -0
  64. data/lib/mini_racer/truffleruby.rb +439 -0
  65. data/lib/mini_racer/version.rb +6 -0
  66. data/lib/mini_racer.rb +3 -0
  67. data/lib/mini_racer_rs/internal_error.rb +7 -0
  68. data/lib/mini_racer_rs/version.rb +5 -0
  69. data/lib/mini_racer_rs/vocabulary.rb +105 -0
  70. data/lib/mini_racer_rs.rb +33 -0
  71. data/licenses/mini_racer.txt +21 -0
  72. data/licenses/rust/SOURCES.tsv +6 -0
  73. data/licenses/rust/std/COPYRIGHT +34 -0
  74. data/licenses/rust/std/COPYRIGHT-library.html +29357 -0
  75. data/licenses/rust/std/LICENSE-APACHE +176 -0
  76. data/licenses/rust/std/LICENSE-MIT +25 -0
  77. data/licenses/v8/SOURCES.tsv +20 -0
  78. data/licenses/v8/abseil-cpp/LICENSE +203 -0
  79. data/licenses/v8/chromium-build/LICENSE +27 -0
  80. data/licenses/v8/components.tsv +29 -0
  81. data/licenses/v8/dragonbox/LICENSE-Boost +23 -0
  82. data/licenses/v8/fast_float/LICENSE-MIT +27 -0
  83. data/licenses/v8/highway/LICENSE +371 -0
  84. data/licenses/v8/icu/LICENSE +568 -0
  85. data/licenses/v8/inspector_protocol/LICENSE +27 -0
  86. data/licenses/v8/libc++/LICENSE.TXT +311 -0
  87. data/licenses/v8/libc++abi/LICENSE.TXT +311 -0
  88. data/licenses/v8/llvm-libc/LICENSE.TXT +278 -0
  89. data/licenses/v8/rapidhash/LICENSE +34 -0
  90. data/licenses/v8/simdutf/LICENSE +18 -0
  91. data/licenses/v8/siphash/LICENSE +116 -0
  92. data/licenses/v8/utf8-decoder/LICENSE +19 -0
  93. data/licenses/v8/v8/LICENSE +61 -0
  94. data/licenses/v8/v8/LICENSE.fdlibm +6 -0
  95. data/licenses/v8/v8/LICENSE.strongtalk +29 -0
  96. data/licenses/v8/v8/LICENSE.v8 +26 -0
  97. metadata +156 -0
@@ -0,0 +1,368 @@
1
+ use std::ffi::{c_int, c_void};
2
+
3
+ use magnus::rb_sys::{AsRawId, AsRawValue, FromRawValue, protect};
4
+ use magnus::value::{IntoId, LazyId, ReprValue};
5
+ use magnus::{Error, RArray, RHash, RString, Ruby, Value};
6
+
7
+ static CALL: LazyId = LazyId::new("call");
8
+
9
+ use crate::guard::{Panicked, guard};
10
+
11
+ struct Call<F, R> {
12
+ work: Option<F>,
13
+ result: Option<Result<R, Panicked>>,
14
+ }
15
+
16
+ unsafe extern "C" fn call_without_gvl<F, R>(data: *mut c_void) -> *mut c_void
17
+ where
18
+ F: FnOnce() -> R,
19
+ {
20
+ // SAFETY: `data` is the `Call` that `nogvl` keeps alive on its stack for the
21
+ // whole synchronous rb_nogvl call.
22
+ let call = unsafe { &mut *data.cast::<Call<F, R>>() };
23
+ if let Some(work) = call.work.take() {
24
+ call.result = Some(guard(work));
25
+ }
26
+ std::ptr::null_mut()
27
+ }
28
+
29
+ unsafe extern "C" fn call_unblock<U: Fn()>(data: *mut c_void) {
30
+ // SAFETY: `data` is the `&U` that `nogvl` borrows for the whole rb_nogvl call;
31
+ // Ruby only calls the unblock function while that call is in progress.
32
+ let unblock = unsafe { &*data.cast::<U>() };
33
+ let _ = guard(unblock);
34
+ }
35
+
36
+ // RB_NOGVL_INTR_FAIL makes rb_nogvl return instead of raising when an interrupt
37
+ // is pending, so no Ruby exception can unwind through these Rust frames (I6).
38
+ // Returns None when Ruby skipped `work` because an interrupt was already pending.
39
+ pub fn nogvl<F, U, R>(_ruby: &Ruby, work: F, unblock: &U) -> Option<Result<R, Panicked>>
40
+ where
41
+ F: FnOnce() -> R + Send,
42
+ R: Send,
43
+ U: Fn() + Sync,
44
+ {
45
+ let mut call = Call {
46
+ work: Some(work),
47
+ result: None,
48
+ };
49
+ let flags = c_int::try_from(rb_sys::RB_NOGVL_INTR_FAIL).unwrap_or(1);
50
+ // SAFETY: rb_nogvl runs both callbacks synchronously before returning, so
51
+ // `call` and `unblock` outlive every access through the raw pointers.
52
+ unsafe {
53
+ rb_sys::rb_nogvl(
54
+ Some(call_without_gvl::<F, R>),
55
+ std::ptr::addr_of_mut!(call).cast::<c_void>(),
56
+ Some(call_unblock::<U>),
57
+ std::ptr::from_ref(unblock).cast_mut().cast::<c_void>(),
58
+ flags,
59
+ );
60
+ }
61
+ call.result
62
+ }
63
+
64
+ // Runs pending interrupts: signal traps return Ok, while Thread#raise, #kill,
65
+ // Timeout and SIGINT come back as the Err the caller must eventually return.
66
+ pub fn check_ints(_ruby: &Ruby) -> Result<(), Error> {
67
+ protect(|| {
68
+ // SAFETY: the `&Ruby` argument proves this thread holds the GVL, and
69
+ // `protect` catches any non-local exit the interrupt handlers raise.
70
+ unsafe { rb_sys::rb_thread_check_ints() };
71
+ rb_sys::Qnil as rb_sys::VALUE
72
+ })
73
+ .map(|_| ())
74
+ }
75
+
76
+ // Mutex#lock releases the GVL while it waits and runs interrupts; magnus wraps
77
+ // it in protect, so a Thread#kill comes back as the Err (I6).
78
+ pub fn mutex_lock(_ruby: &Ruby, mutex: magnus::Mutex) -> Result<(), Error> {
79
+ mutex.lock()
80
+ }
81
+
82
+ // Ruby's GC cannot see an isolate's native memory, so it is reported here;
83
+ // without it, contexts left to finalizers pile up between collections.
84
+ pub fn adjust_memory(_ruby: &Ruby, bytes: isize) {
85
+ // SAFETY: the `&Ruby` argument proves this thread holds the GVL.
86
+ unsafe { rb_sys::rb_gc_adjust_memory_usage(rb_sys::ssize_t::try_from(bytes).unwrap_or(0)) };
87
+ }
88
+
89
+ /// For a free function, which runs with the GVL held but no `Ruby` handle.
90
+ /// Only decreases are allowed there, and they never start a collection.
91
+ pub fn release_memory(bytes: usize) {
92
+ let diff = rb_sys::ssize_t::try_from(bytes).map_or(0, |n| -n);
93
+ // SAFETY: free functions run during GC with the GVL held, and a negative
94
+ // adjustment only lowers the malloc counter without triggering GC.
95
+ unsafe { rb_sys::rb_gc_adjust_memory_usage(diff) };
96
+ }
97
+
98
+ /// Whether the current fiber has a scheduler that waits for it, so a wait can
99
+ /// park the fiber instead of the thread.
100
+ pub fn fiber_scheduler(_ruby: &Ruby) -> bool {
101
+ // SAFETY: the `&Ruby` argument proves this thread holds the GVL, and
102
+ // rb_fiber_scheduler_current only reads the current thread and fiber.
103
+ let scheduler = unsafe { rb_sys::rb_fiber_scheduler_current() };
104
+ scheduler != rb_sys::Qnil as rb_sys::VALUE
105
+ }
106
+
107
+ pub fn dbl2big(_ruby: &Ruby, n: f64) -> Value {
108
+ // SAFETY: rb_dbl2big allocates a Bignum and requires the GVL, which the
109
+ // `&Ruby` argument proves this thread holds.
110
+ let raw = unsafe { rb_sys::rb_dbl2big(n) };
111
+ // SAFETY: `raw` is the live VALUE rb_dbl2big just returned.
112
+ unsafe { Value::from_raw(raw) }
113
+ }
114
+
115
+ // mini_racer checks its String arguments with Check_Type, whose TypeError
116
+ // names the argument's type as Ruby itself does.
117
+ pub fn expect_string(ruby: &Ruby, value: Value) -> Result<RString, Error> {
118
+ let raw = value.as_raw();
119
+ protect(|| {
120
+ // SAFETY: the `&Ruby` argument proves this thread holds the GVL, and
121
+ // `protect` catches the TypeError rb_check_type raises.
122
+ unsafe { rb_sys::rb_check_type(raw, rb_sys::ruby_value_type::RUBY_T_STRING as c_int) };
123
+ raw
124
+ })?;
125
+ RString::from_value(value)
126
+ .ok_or_else(|| Error::new(ruby.exception_type_error(), "expected String"))
127
+ }
128
+
129
+ /// `digits` is a `BigInt` magnitude as little-endian 64-bit words.
130
+ pub fn integer_unpack(_ruby: &Ruby, digits: &[u8], negative: bool) -> Result<Value, Error> {
131
+ let words = (digits.len() / 8).try_into().unwrap_or_default();
132
+ let sign = if negative {
133
+ rb_sys::INTEGER_PACK_NEGATIVE
134
+ } else {
135
+ 0
136
+ };
137
+ let flags = c_int::try_from(rb_sys::INTEGER_PACK_LITTLE_ENDIAN | sign).unwrap_or(0);
138
+ let raw = protect(|| {
139
+ // SAFETY: `digits` holds `words` whole 8-byte words and outlives the
140
+ // call, which copies them; `protect` catches an allocation failure.
141
+ unsafe { rb_sys::rb_integer_unpack(digits.as_ptr().cast(), words, 8, 0, flags) }
142
+ })?;
143
+ // SAFETY: `raw` is the live Integer rb_integer_unpack just returned.
144
+ Ok(unsafe { Value::from_raw(raw) })
145
+ }
146
+
147
+ /// The builtin types `mini_racer`'s encoder tells apart; `Other` carries the
148
+ /// number `TYPE()` gives, which it prints in hex.
149
+ #[derive(Debug, Clone, Copy, PartialEq, Eq)]
150
+ pub enum RubyType {
151
+ Object,
152
+ Float,
153
+ String,
154
+ Array,
155
+ Hash,
156
+ Bignum,
157
+ Data,
158
+ Nil,
159
+ True,
160
+ False,
161
+ Symbol,
162
+ Fixnum,
163
+ Other(u32),
164
+ }
165
+
166
+ pub fn ruby_type(_ruby: &Ruby, value: Value) -> RubyType {
167
+ use rb_sys::ruby_value_type as T;
168
+ // SAFETY: the `&Ruby` argument proves this thread holds the GVL, and
169
+ // `value` is a live VALUE the caller holds.
170
+ let kind = unsafe { rb_sys::RB_TYPE(value.as_raw()) };
171
+ match kind {
172
+ T::RUBY_T_OBJECT => RubyType::Object,
173
+ T::RUBY_T_FLOAT => RubyType::Float,
174
+ T::RUBY_T_STRING => RubyType::String,
175
+ T::RUBY_T_ARRAY => RubyType::Array,
176
+ T::RUBY_T_HASH => RubyType::Hash,
177
+ T::RUBY_T_BIGNUM => RubyType::Bignum,
178
+ T::RUBY_T_DATA => RubyType::Data,
179
+ T::RUBY_T_NIL => RubyType::Nil,
180
+ T::RUBY_T_TRUE => RubyType::True,
181
+ T::RUBY_T_FALSE => RubyType::False,
182
+ T::RUBY_T_SYMBOL => RubyType::Symbol,
183
+ T::RUBY_T_FIXNUM => RubyType::Fixnum,
184
+ other => RubyType::Other(other as u32),
185
+ }
186
+ }
187
+
188
+ /// An object's address, the identity `mini_racer`'s encoder keys repeated
189
+ /// containers by; stable only while the object is pinned.
190
+ pub fn identity(value: Value) -> u64 {
191
+ value.as_raw()
192
+ }
193
+
194
+ /// `super(*values)`, from the method Ruby is running.
195
+ pub fn call_super(_ruby: &Ruby, values: &[Value]) -> Result<Value, Error> {
196
+ let raw_values: Vec<rb_sys::VALUE> = values.iter().map(|value| value.as_raw()).collect();
197
+ let count = c_int::try_from(raw_values.len()).unwrap_or(c_int::MAX);
198
+ let raw = protect(|| {
199
+ // SAFETY: the `&Ruby` argument proves this thread holds the GVL, the
200
+ // caller is a method body Ruby called, `raw_values` holds `count`
201
+ // values the caller keeps alive on its stack, and `protect` catches
202
+ // anything super raises or throws.
203
+ unsafe { rb_sys::rb_call_super(count, raw_values.as_ptr()) }
204
+ })?;
205
+ // SAFETY: `raw` is the live value super returned.
206
+ Ok(unsafe { Value::from_raw(raw) })
207
+ }
208
+
209
+ /// `receiver.call(*args)`, with Ruby reading the arguments from `args`
210
+ /// itself, so they stay where the GC sees them for the whole call.
211
+ pub fn call_with_array(ruby: &Ruby, receiver: Value, args: RArray) -> Result<Value, Error> {
212
+ let call = (*CALL).into_id_with(ruby);
213
+ let raw = protect(|| {
214
+ // SAFETY: the `&Ruby` argument proves this thread holds the GVL, both
215
+ // values are live on this stack, and `protect` catches any non-local
216
+ // exit the call raises.
217
+ unsafe { rb_sys::rb_apply(receiver.as_raw(), call.as_raw(), args.as_raw()) }
218
+ })?;
219
+ // SAFETY: `raw` is the live value the call returned.
220
+ Ok(unsafe { Value::from_raw(raw) })
221
+ }
222
+
223
+ /// Stores each key and value as `Hash#[]=` would, under one `rb_protect` for
224
+ /// the whole slice. `pairs` must be where the GC sees them, on the native
225
+ /// stack: storing a key can start a compacting collection.
226
+ // rb_hash_bulk_insert would take the slice in one call, but it rehashes the
227
+ // whole table whenever the hash already has entries.
228
+ pub fn hash_aset_pairs(_ruby: &Ruby, hash: RHash, pairs: &[[Value; 2]]) -> Result<(), Error> {
229
+ let raw = hash.as_raw();
230
+ protect(|| {
231
+ for [key, value] in pairs {
232
+ // SAFETY: the `&Ruby` argument proves this thread holds the GVL,
233
+ // the caller keeps `pairs` where the GC sees them, and `protect`
234
+ // catches anything rb_hash_aset raises.
235
+ unsafe { rb_sys::rb_hash_aset(raw, key.as_raw(), value.as_raw()) };
236
+ }
237
+ raw
238
+ })?;
239
+ Ok(())
240
+ }
241
+
242
+ /// How Ruby's own type errors name `value`: nil, true and false by value,
243
+ /// anything else by its class, never a singleton class, printed with `to_s`
244
+ /// as Ruby prints it, overridden or not.
245
+ pub fn type_name(ruby: &Ruby, value: Value) -> String {
246
+ if value.is_nil() {
247
+ return "nil".to_owned();
248
+ }
249
+ if value.is_kind_of(ruby.class_true_class()) {
250
+ return "true".to_owned();
251
+ }
252
+ if value.is_kind_of(ruby.class_false_class()) {
253
+ return "false".to_owned();
254
+ }
255
+ // SAFETY: the `&Ruby` argument proves this thread holds the GVL, and
256
+ // rb_obj_class only reads the value's class.
257
+ let raw = unsafe { rb_sys::rb_obj_class(value.as_raw()) };
258
+ // SAFETY: `raw` is the live class rb_obj_class returned.
259
+ let class = unsafe { Value::from_raw(raw) };
260
+ class
261
+ .funcall::<_, _, String>("to_s", ())
262
+ .unwrap_or_else(|_| class_name(ruby, value))
263
+ }
264
+
265
+ /// The struct name a typed data object was wrapped under, which Ruby's
266
+ /// typed data check names it by.
267
+ pub fn typed_data_name(ruby: &Ruby, value: Value) -> Option<String> {
268
+ if !matches!(ruby_type(ruby, value), RubyType::Data) {
269
+ return None;
270
+ }
271
+ // SAFETY: `value` is a live T_DATA object and the GVL is held.
272
+ if !unsafe { rb_sys::RTYPEDDATA_P(value.as_raw()) } {
273
+ return None;
274
+ }
275
+ // SAFETY: as above; the type of a typed data object is static data.
276
+ let data_type = unsafe { rb_sys::RTYPEDDATA_TYPE(value.as_raw()) };
277
+ // SAFETY: `data_type` points at the object's static rb_data_type_t.
278
+ let name = unsafe { (*data_type).wrap_struct_name };
279
+ if name.is_null() {
280
+ return None;
281
+ }
282
+ // SAFETY: wrap_struct_name is a NUL-terminated static string.
283
+ Some(
284
+ unsafe { std::ffi::CStr::from_ptr(name) }
285
+ .to_string_lossy()
286
+ .into_owned(),
287
+ )
288
+ }
289
+
290
+ /// `rb_str_dup`: a copy of the same class, without `String#dup`'s instance
291
+ /// variables.
292
+ pub fn str_dup(_ruby: &Ruby, s: RString) -> RString {
293
+ // SAFETY: the `&Ruby` argument proves this thread holds the GVL; rb_str_dup
294
+ // returns a new live String.
295
+ let raw = unsafe { rb_sys::rb_str_dup(s.as_raw()) };
296
+ // SAFETY: `raw` is the String rb_str_dup returned.
297
+ RString::from_value(unsafe { Value::from_raw(raw) }).unwrap_or(s)
298
+ }
299
+
300
+ /// A Symbol's name as a String, without calling `Symbol#to_s`.
301
+ pub fn sym2str(ruby: &Ruby, symbol: Value) -> RString {
302
+ // SAFETY: the `&Ruby` argument proves this thread holds the GVL, and the
303
+ // caller passes a Symbol, for which rb_sym2str returns its interned name.
304
+ let raw = unsafe { rb_sys::rb_sym2str(symbol.as_raw()) };
305
+ // SAFETY: `raw` is the live String rb_sym2str returned.
306
+ let name = unsafe { Value::from_raw(raw) };
307
+ RString::from_value(name).unwrap_or_else(|| ruby.str_new(""))
308
+ }
309
+
310
+ /// Runs `read` over the string's bytes without copying them; `read` must not
311
+ /// call Ruby, which could change or move the string.
312
+ pub fn with_string_bytes<R>(_ruby: &Ruby, s: RString, read: impl FnOnce(&[u8]) -> R) -> R {
313
+ // SAFETY: the bytes are only read by `read`, which runs no Ruby code.
314
+ read(unsafe { s.as_slice() })
315
+ }
316
+
317
+ /// The magnitude of an Integer as little-endian 64-bit words, and whether it is
318
+ /// negative; `None` past `max_words`.
319
+ pub fn integer_words(
320
+ _ruby: &Ruby,
321
+ value: Value,
322
+ max_words: usize,
323
+ ) -> Result<Option<(bool, Vec<u8>)>, Error> {
324
+ let raw = value.as_raw();
325
+ let mut count = 0;
326
+ protect(|| {
327
+ // SAFETY: the `&Ruby` argument proves this thread holds the GVL, and
328
+ // `protect` catches what rb_absint_numwords raises for a non-Integer.
329
+ count = unsafe { rb_sys::rb_absint_numwords(raw, 64, std::ptr::null_mut()) };
330
+ rb_sys::Qnil as rb_sys::VALUE
331
+ })?;
332
+ let Some(words) = usize::try_from(count)
333
+ .ok()
334
+ .filter(|&words| words <= max_words)
335
+ else {
336
+ return Ok(None);
337
+ };
338
+ let mut digits = vec![0u64; words];
339
+ let flags = c_int::try_from(rb_sys::INTEGER_PACK_LITTLE_ENDIAN).unwrap_or(0);
340
+ let mut sign = 0;
341
+ protect(|| {
342
+ // SAFETY: `digits` holds `words` words, which rb_integer_pack fills.
343
+ sign =
344
+ unsafe { rb_sys::rb_integer_pack(raw, digits.as_mut_ptr().cast(), count, 8, 0, flags) };
345
+ rb_sys::Qnil as rb_sys::VALUE
346
+ })?;
347
+ if !(-1..=1).contains(&sign) {
348
+ return Ok(None);
349
+ }
350
+ Ok(Some((
351
+ sign < 0,
352
+ digits.iter().flat_map(|word| word.to_le_bytes()).collect(),
353
+ )))
354
+ }
355
+
356
+ /// The name `rb_class2name` gives the class of `value`, as `mini_racer` prints it.
357
+ pub fn class_name(_ruby: &Ruby, value: Value) -> String {
358
+ use magnus::Class as _;
359
+ use magnus::value::ReprValue as _;
360
+ let class = value.class();
361
+ // SAFETY: the name is copied before any Ruby code can run and free it.
362
+ unsafe { class.name() }.into_owned()
363
+ }
364
+
365
+ pub fn string_bytes(_ruby: &Ruby, s: RString) -> Vec<u8> {
366
+ // SAFETY: the slice is copied before any Ruby code can run and move or free it.
367
+ unsafe { s.as_slice() }.to_vec()
368
+ }
@@ -0,0 +1,149 @@
1
+ use std::cell::RefCell;
2
+
3
+ use magnus::encoding::EncodingCapable;
4
+ use magnus::gc::Marker;
5
+ use magnus::scan_args::scan_args;
6
+ use magnus::typed_data::Obj;
7
+ use magnus::value::ReprValue;
8
+ use magnus::{
9
+ Class, DataTypeFunctions, Error, Module, Object, RClass, RModule, RString, Ruby, TryConvert,
10
+ TypedData, Value,
11
+ };
12
+ use mini_racer_engine::{ContextHandle, InvalidSnapshot, SnapshotBlob, WeakSnapshotBlob};
13
+
14
+ use crate::gc_root::OwnedRoot;
15
+ use crate::host::RubyHost;
16
+ use crate::{context, errors, export, rb_raw};
17
+
18
+ #[derive(Default, TypedData)]
19
+ #[magnus(
20
+ class = "MiniRacer::Snapshot",
21
+ name = "mini_racer/snapshot",
22
+ free_immediately,
23
+ mark,
24
+ size
25
+ )]
26
+ pub struct Snapshot {
27
+ blob: OwnedRoot<RString>,
28
+ // What contexts made from this snapshot start from, reused while one of
29
+ // them lives and `blob` holds the same bytes: `dump` hands `blob` out, and
30
+ // a context of mini_racer's starts from whatever it holds then. Held
31
+ // weakly, so once its contexts are gone a snapshot holds only `blob`, as
32
+ // mini_racer's does.
33
+ shared: RefCell<Option<WeakSnapshotBlob>>,
34
+ }
35
+
36
+ impl DataTypeFunctions for Snapshot {
37
+ fn mark(&self, marker: &Marker) {
38
+ self.blob.mark(marker);
39
+ }
40
+
41
+ fn size(&self) -> usize {
42
+ let blob = Ruby::get().ok().and_then(|ruby| self.blob.get(&ruby));
43
+ size_of::<Self>().saturating_add(blob.map_or(0, RString::len))
44
+ }
45
+ }
46
+
47
+ pub fn init(ruby: &Ruby, racer: RModule) -> Result<(), Error> {
48
+ let class = racer.define_class("Snapshot", ruby.class_object())?;
49
+ class.define_alloc_func::<Snapshot>();
50
+ class.define_method("initialize", export!(method Snapshot::initialize, -1))?;
51
+ class.define_method("dump", export!(method Snapshot::dump, 0))?;
52
+ class.define_method("size", export!(method Snapshot::size, 0))?;
53
+ class.define_method("warmup!", export!(method Snapshot::warmup, 1))?;
54
+ class.define_singleton_method("load", export!(method Snapshot::load, 1))?;
55
+ Ok(())
56
+ }
57
+
58
+ /// The blob a context made from `value`, when it is a `MiniRacer::Snapshot`,
59
+ /// starts from, for `Context.new`.
60
+ pub fn shared_of(ruby: &Ruby, value: Value) -> Option<Result<SnapshotBlob, InvalidSnapshot>> {
61
+ let snapshot = Obj::<Snapshot>::try_convert(value).ok()?;
62
+ Some(snapshot.shared(ruby))
63
+ }
64
+
65
+ // mini_racer raises every failure of a build as a snapshot error, V8's
66
+ // refusal of an odd-length UTF-16LE string included, which it meets before
67
+ // the base blob.
68
+ fn build(ruby: &Ruby, code: RString, base: Option<&Snapshot>) -> Result<RString, Error> {
69
+ let code = context::js_text(ruby, code)
70
+ .map_err(|_| errors::snapshot(ruby, "Error: Unable to deserialize cloned data."))?;
71
+ let base = base
72
+ .map(|base| base.shared(ruby))
73
+ .transpose()
74
+ .map_err(|invalid| errors::engine(ruby, invalid.into()))?;
75
+ let mut host = RubyHost::new(ruby);
76
+ let built = ContextHandle::create_snapshot(code, base, &mut host);
77
+ let blob = host.finish(built)?;
78
+ Ok(ruby.str_from_slice(&blob))
79
+ }
80
+
81
+ impl Snapshot {
82
+ fn shared(&self, ruby: &Ruby) -> Result<SnapshotBlob, InvalidSnapshot> {
83
+ let blob = self.blob(ruby);
84
+ let mut shared = self.shared.borrow_mut();
85
+ let kept = shared.as_ref().and_then(WeakSnapshotBlob::upgrade);
86
+ let current = rb_raw::with_string_bytes(ruby, blob, |bytes| match kept {
87
+ Some(kept) if kept.as_bytes() == bytes => Ok(kept),
88
+ _ => SnapshotBlob::new(bytes),
89
+ })?;
90
+ *shared = Some(current.downgrade());
91
+ Ok(current)
92
+ }
93
+
94
+ fn replace(&self, ruby: &Ruby, blob: RString) {
95
+ self.blob.set(ruby, blob);
96
+ self.shared.borrow_mut().take();
97
+ }
98
+
99
+ // mini_racer allocates every snapshot with an empty blob it then hands
100
+ // out, so `dump` on one never built is that same String.
101
+ fn blob(&self, ruby: &Ruby) -> RString {
102
+ self.blob.get(ruby).unwrap_or_else(|| {
103
+ let empty = ruby.str_from_slice(b"");
104
+ self.blob.set(ruby, empty);
105
+ empty
106
+ })
107
+ }
108
+
109
+ fn initialize(ruby: &Ruby, rb_self: &Self, args: &[Value]) -> Result<(), Error> {
110
+ let args = scan_args::<(), (Option<Value>,), (), (), (), ()>(args)?;
111
+ let code = match args.optional.0 {
112
+ Some(code) if !code.is_nil() => rb_raw::expect_string(ruby, code)?,
113
+ _ => ruby.str_new(""),
114
+ };
115
+ rb_self.replace(ruby, build(ruby, code, None)?);
116
+ Ok(())
117
+ }
118
+
119
+ fn dump(ruby: &Ruby, rb_self: &Self) -> RString {
120
+ rb_self.blob(ruby)
121
+ }
122
+
123
+ fn size(ruby: &Ruby, rb_self: &Self) -> usize {
124
+ rb_self.blob(ruby).len()
125
+ }
126
+
127
+ // The blob is replaced only once the warmed-up one is built, so a failed
128
+ // warmup leaves the snapshot as it was.
129
+ fn warmup(ruby: &Ruby, rb_self: Obj<Self>, code: Value) -> Result<Obj<Self>, Error> {
130
+ let code = rb_raw::expect_string(ruby, code)?;
131
+ let warmed = build(ruby, code, Some(&rb_self))?;
132
+ rb_self.replace(ruby, warmed);
133
+ Ok(rb_self)
134
+ }
135
+
136
+ // mini_racer keeps whatever it is given and aborts when V8 reads a bad
137
+ // blob; refused here, a stale blob read back from disk can be rescued
138
+ // and built again.
139
+ fn load(ruby: &Ruby, class: RClass, blob: Value) -> Result<Obj<Self>, Error> {
140
+ let blob = rb_raw::expect_string(ruby, blob)?;
141
+ let copy = rb_raw::str_dup(ruby, blob);
142
+ copy.enc_associate(ruby.ascii8bit_encindex())?;
143
+ rb_raw::with_string_bytes(ruby, copy, SnapshotBlob::check)
144
+ .map_err(|invalid| errors::engine(ruby, invalid.into()))?;
145
+ let snapshot = ruby.obj_wrap_as(Self::default(), class);
146
+ snapshot.blob.set(ruby, copy);
147
+ Ok(snapshot)
148
+ }
149
+ }
@@ -0,0 +1,8 @@
1
+ a4a5484444ad4b5f10dc7bed141236dd9dbeabf2006d14520f19b935d4db4227 librusty_v8_release_aarch64-apple-darwin.a.gz
2
+ c40196bbf182532e583f65b7daf0196fdbb3354e6fac59b31d0539c20f931f3d librusty_v8_release_aarch64-unknown-linux-gnu.a.gz
3
+ 04cbc1c36c4cd5459c54049775d92889337c69d936fb0b011f9b26f1d454610d librusty_v8_release_x86_64-apple-darwin.a.gz
4
+ b6683e9afcb77fbd8cb2c3acf45d34d52029ab216b332522c95179c12f0fed3c librusty_v8_release_x86_64-unknown-linux-gnu.a.gz
5
+ b9d5652ebca515eb2bf4c2ee4a6551b2567117b5aa720f430d2032616dd7e3b5 src_binding_release_aarch64-apple-darwin.rs
6
+ 3e4d5fecdbe0f98a6017e69d7ca82519a77fb032c5eb5e4cdbecdd3e2ec519d5 src_binding_release_aarch64-unknown-linux-gnu.rs
7
+ b9d5652ebca515eb2bf4c2ee4a6551b2567117b5aa720f430d2032616dd7e3b5 src_binding_release_x86_64-apple-darwin.rs
8
+ 3e4d5fecdbe0f98a6017e69d7ca82519a77fb032c5eb5e4cdbecdd3e2ec519d5 src_binding_release_x86_64-unknown-linux-gnu.rs