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.
- checksums.yaml +7 -0
- data/AI-DISCLOSURE.md +13 -0
- data/CHANGELOG.md +45 -0
- data/COMPATIBILITY.md +235 -0
- data/Cargo.lock +1263 -0
- data/Cargo.toml +62 -0
- data/LICENSE +21 -0
- data/README.md +254 -0
- data/THIRD_PARTY_NOTICES.md +4501 -0
- data/crates/mini_racer_engine/Cargo.toml +25 -0
- data/crates/mini_racer_engine/build.rs +13 -0
- data/crates/mini_racer_engine/src/context.rs +802 -0
- data/crates/mini_racer_engine/src/control.rs +239 -0
- data/crates/mini_racer_engine/src/error.rs +88 -0
- data/crates/mini_racer_engine/src/fast_calls.rs +681 -0
- data/crates/mini_racer_engine/src/lib.rs +20 -0
- data/crates/mini_racer_engine/src/notify.rs +107 -0
- data/crates/mini_racer_engine/src/process/code_cache.rs +412 -0
- data/crates/mini_racer_engine/src/process/fork.rs +261 -0
- data/crates/mini_racer_engine/src/process/platform.rs +194 -0
- data/crates/mini_racer_engine/src/process/tasks.rs +96 -0
- data/crates/mini_racer_engine/src/process/watchdog.rs +185 -0
- data/crates/mini_racer_engine/src/process/workers.rs +165 -0
- data/crates/mini_racer_engine/src/process.rs +69 -0
- data/crates/mini_racer_engine/src/runner.rs +1560 -0
- data/crates/mini_racer_engine/src/serialize.rs +253 -0
- data/crates/mini_racer_engine/src/snapshot.rs +221 -0
- data/crates/mini_racer_engine/src/spin.rs +23 -0
- data/crates/mini_racer_engine/src/sys.rs +566 -0
- data/crates/mini_racer_sync/Cargo.toml +18 -0
- data/crates/mini_racer_sync/src/channel.rs +621 -0
- data/crates/mini_racer_sync/src/fork.rs +116 -0
- data/crates/mini_racer_sync/src/lib.rs +20 -0
- data/crates/mini_racer_sync/src/lock.rs +49 -0
- data/crates/mini_racer_sync/src/parker.rs +44 -0
- data/crates/mini_racer_sync/src/primitives.rs +10 -0
- data/crates/mini_racer_sync/src/tasks.rs +345 -0
- data/crates/mini_racer_sync/src/termination.rs +1234 -0
- data/crates/mini_racer_sync/src/workers.rs +283 -0
- data/crates/mini_racer_wire/Cargo.toml +18 -0
- data/crates/mini_racer_wire/src/decode.rs +926 -0
- data/crates/mini_racer_wire/src/lib.rs +67 -0
- data/crates/mini_racer_wire/src/marker.rs +43 -0
- data/crates/mini_racer_wire/src/text.rs +106 -0
- data/crates/mini_racer_wire/src/write.rs +187 -0
- data/ext/mini_racer_rs/Cargo.toml +26 -0
- data/ext/mini_racer_rs/build.rs +16 -0
- data/ext/mini_racer_rs/extconf.rb +78 -0
- data/ext/mini_racer_rs/src/context.rs +485 -0
- data/ext/mini_racer_rs/src/decode.rs +450 -0
- data/ext/mini_racer_rs/src/encode.rs +333 -0
- data/ext/mini_racer_rs/src/errors.rs +156 -0
- data/ext/mini_racer_rs/src/fork.rs +31 -0
- data/ext/mini_racer_rs/src/gc_root.rs +117 -0
- data/ext/mini_racer_rs/src/guard.rs +7 -0
- data/ext/mini_racer_rs/src/host.rs +339 -0
- data/ext/mini_racer_rs/src/lib.rs +44 -0
- data/ext/mini_racer_rs/src/options.rs +269 -0
- data/ext/mini_racer_rs/src/platform.rs +42 -0
- data/ext/mini_racer_rs/src/rb_raw.rs +368 -0
- data/ext/mini_racer_rs/src/snapshot.rs +149 -0
- data/ext/mini_racer_rs/v8-archives.sha256 +8 -0
- data/lib/mini_racer/shared.rb +420 -0
- data/lib/mini_racer/truffleruby.rb +439 -0
- data/lib/mini_racer/version.rb +6 -0
- data/lib/mini_racer.rb +3 -0
- data/lib/mini_racer_rs/internal_error.rb +7 -0
- data/lib/mini_racer_rs/version.rb +5 -0
- data/lib/mini_racer_rs/vocabulary.rb +105 -0
- data/lib/mini_racer_rs.rb +33 -0
- data/licenses/mini_racer.txt +21 -0
- data/licenses/rust/SOURCES.tsv +6 -0
- data/licenses/rust/std/COPYRIGHT +34 -0
- data/licenses/rust/std/COPYRIGHT-library.html +29357 -0
- data/licenses/rust/std/LICENSE-APACHE +176 -0
- data/licenses/rust/std/LICENSE-MIT +25 -0
- data/licenses/v8/SOURCES.tsv +20 -0
- data/licenses/v8/abseil-cpp/LICENSE +203 -0
- data/licenses/v8/chromium-build/LICENSE +27 -0
- data/licenses/v8/components.tsv +29 -0
- data/licenses/v8/dragonbox/LICENSE-Boost +23 -0
- data/licenses/v8/fast_float/LICENSE-MIT +27 -0
- data/licenses/v8/highway/LICENSE +371 -0
- data/licenses/v8/icu/LICENSE +568 -0
- data/licenses/v8/inspector_protocol/LICENSE +27 -0
- data/licenses/v8/libc++/LICENSE.TXT +311 -0
- data/licenses/v8/libc++abi/LICENSE.TXT +311 -0
- data/licenses/v8/llvm-libc/LICENSE.TXT +278 -0
- data/licenses/v8/rapidhash/LICENSE +34 -0
- data/licenses/v8/simdutf/LICENSE +18 -0
- data/licenses/v8/siphash/LICENSE +116 -0
- data/licenses/v8/utf8-decoder/LICENSE +19 -0
- data/licenses/v8/v8/LICENSE +61 -0
- data/licenses/v8/v8/LICENSE.fdlibm +6 -0
- data/licenses/v8/v8/LICENSE.strongtalk +29 -0
- data/licenses/v8/v8/LICENSE.v8 +26 -0
- 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
|