mini_racer_rs 1.0.0 → 1.0.2

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.
@@ -1,4 +1,5 @@
1
1
  use std::collections::HashMap;
2
+ use std::hash::{BuildHasherDefault, Hasher};
2
3
 
3
4
  use magnus::encoding::{EncodingCapable, RbEncoding};
4
5
  use magnus::r_hash::ForEach;
@@ -8,6 +9,7 @@ use magnus::{
8
9
  Error, Fixnum, Float, Module, Object, RArray, RClass, RHash, RModule, RObject, RString, Ruby,
9
10
  Time, Value,
10
11
  };
12
+ use mini_racer_engine::CallRequest;
11
13
  use mini_racer_wire::Writer;
12
14
 
13
15
  use crate::gc_root::{OnceRoot, Pins, StaticRoot};
@@ -61,17 +63,44 @@ enum Item {
61
63
  EndObject(u32),
62
64
  }
63
65
 
66
+ // A pinned object's address is distinct for the whole encode; multiplying it
67
+ // spreads its aligned low bits over the bits the map buckets and tags by.
68
+ #[derive(Default)]
69
+ struct Address(u64);
70
+
71
+ impl Hasher for Address {
72
+ fn finish(&self) -> u64 {
73
+ self.0
74
+ }
75
+
76
+ fn write(&mut self, bytes: &[u8]) {
77
+ for &byte in bytes {
78
+ self.0 = self.0.rotate_left(8) ^ u64::from(byte);
79
+ }
80
+ }
81
+
82
+ fn write_u64(&mut self, address: u64) {
83
+ self.0 = (address >> 3).wrapping_mul(0x9E37_79B9_7F4A_7C15);
84
+ }
85
+ }
86
+
64
87
  // Iterative so a deeply nested argument cannot overflow the Ruby thread's
65
88
  // stack; how deep V8 then reads is V8's own limit.
66
89
  struct Encoder<'r, 'o> {
67
90
  ruby: &'r Ruby,
68
- // The object, not a reference into its data: each use of the object keeps
69
- // it where the GC finds it, while a bare data pointer lets an optimized
70
- // build drop it and the GC free it mid-encode.
71
- pins: Obj<Pins>,
91
+ // Made for the first value that holds others or can run Ruby code, before
92
+ // anything is pinned or keyed by address. The object, not a reference into
93
+ // its data: each use of the object keeps it where the GC finds it, while a
94
+ // bare data pointer lets an optimized build drop it and the GC free it
95
+ // mid-encode.
96
+ pins: Option<Obj<Pins>>,
72
97
  writer: Writer<'o>,
73
98
  work: Vec<Item>,
74
- seen: HashMap<u64, u32>,
99
+ seen: HashMap<u64, u32, BuildHasherDefault<Address>>,
100
+ // The hashes written and their pairs in all, which tell the runner
101
+ // whether to build a call's arguments itself.
102
+ objects: usize,
103
+ properties: usize,
75
104
  }
76
105
 
77
106
  /// Writes `value` as `mini_racer`'s `serialize` does, with the object ids V8
@@ -83,33 +112,102 @@ pub fn encode(
83
112
  unsupported: impl FnOnce(Vec<u8>) -> Error,
84
113
  ) -> Result<Vec<u8>, Error> {
85
114
  let mut out = Vec::new();
86
- let mut encoder = Encoder {
87
- ruby,
88
- pins: Pins::new(ruby),
89
- writer: Writer::new(&mut out),
90
- work: Vec::new(),
91
- seen: HashMap::new(),
92
- };
93
- let root = encoder.pins.push(value);
94
- encoder.work.push(Item::Value(root));
95
- encoder.run().map_err(|error| match error {
96
- EncodeError::Unsupported(message) => unsupported(message),
97
- EncodeError::Raised(error) => error,
98
- })?;
115
+ Encoder::new(ruby, &mut out)
116
+ .top(value)
117
+ .map_err(|error| error.raise(unsupported))?;
99
118
  Ok(out)
100
119
  }
101
120
 
121
+ /// Writes `[name, *args]`, the array `mini_racer` sends for a call, without
122
+ /// making it: no code can reach that array, so reading `args` as each turn
123
+ /// comes is reading it.
124
+ pub fn call(
125
+ ruby: &Ruby,
126
+ name: RString,
127
+ args: &[Value],
128
+ unsupported: impl FnOnce(Vec<u8>) -> Error,
129
+ ) -> Result<CallRequest, Error> {
130
+ let mut bytes = Vec::new();
131
+ let mut encoder = Encoder::new(ruby, &mut bytes);
132
+ let count = length(args.len().saturating_add(1));
133
+ encoder.writer.begin_array(count);
134
+ encoder.string(name);
135
+ args.iter()
136
+ .try_for_each(|&arg| encoder.top(arg))
137
+ .map_err(|error| error.raise(unsupported))?;
138
+ encoder.writer.end_array(count);
139
+ let (objects, properties) = (encoder.objects, encoder.properties);
140
+ Ok(CallRequest {
141
+ bytes,
142
+ objects,
143
+ properties,
144
+ })
145
+ }
146
+
147
+ impl EncodeError {
148
+ fn raise(self, unsupported: impl FnOnce(Vec<u8>) -> Error) -> Error {
149
+ match self {
150
+ Self::Unsupported(message) => unsupported(message),
151
+ Self::Raised(error) => error,
152
+ }
153
+ }
154
+ }
155
+
102
156
  fn length(len: usize) -> u32 {
103
157
  u32::try_from(len).unwrap_or(u32::MAX)
104
158
  }
105
159
 
160
+ // The values whose encoding runs no Ruby code and holds no other value.
161
+ const fn immediate(kind: RubyType) -> bool {
162
+ matches!(
163
+ kind,
164
+ RubyType::Fixnum
165
+ | RubyType::Float
166
+ | RubyType::Nil
167
+ | RubyType::True
168
+ | RubyType::False
169
+ | RubyType::String
170
+ | RubyType::Symbol
171
+ )
172
+ }
173
+
174
+ impl<'r, 'o> Encoder<'r, 'o> {
175
+ fn new(ruby: &'r Ruby, out: &'o mut Vec<u8>) -> Self {
176
+ Self {
177
+ ruby,
178
+ pins: None,
179
+ writer: Writer::new(out),
180
+ work: Vec::new(),
181
+ seen: HashMap::default(),
182
+ objects: 0,
183
+ properties: 0,
184
+ }
185
+ }
186
+ }
187
+
106
188
  impl Encoder<'_, '_> {
189
+ fn pin(&mut self, value: Value) -> usize {
190
+ let ruby = self.ruby;
191
+ self.pins.get_or_insert_with(|| Pins::new(ruby)).push(value)
192
+ }
193
+
107
194
  fn pinned(&self, index: usize) -> Value {
108
195
  self.pins
109
- .get(self.ruby, index)
196
+ .as_ref()
197
+ .and_then(|pins| pins.get(self.ruby, index))
110
198
  .unwrap_or_else(|| self.ruby.qnil().as_value())
111
199
  }
112
200
 
201
+ // Writes an immediate at once; anything else waits, pinned, for `run`.
202
+ fn top(&mut self, value: Value) -> Result<(), EncodeError> {
203
+ if self.immediate(value, rb_raw::ruby_type(self.ruby, value)) {
204
+ return Ok(());
205
+ }
206
+ let root = self.pin(value);
207
+ self.work.push(Item::Value(root));
208
+ self.run()
209
+ }
210
+
113
211
  fn run(&mut self) -> Result<(), EncodeError> {
114
212
  while let Some(item) = self.work.pop() {
115
213
  match item {
@@ -128,92 +226,135 @@ impl Encoder<'_, '_> {
128
226
  Ok(())
129
227
  }
130
228
 
131
- fn value(&mut self, value: Value) -> Result<(), EncodeError> {
229
+ fn immediate(&mut self, value: Value, kind: RubyType) -> bool {
132
230
  let ruby = self.ruby;
133
- match rb_raw::ruby_type(ruby, value) {
231
+ match kind {
232
+ RubyType::Fixnum => self
233
+ .writer
234
+ .integer(Fixnum::from_value(value).map_or(0, Fixnum::to_i64)),
235
+ RubyType::Float => self
236
+ .writer
237
+ .number(Float::from_value(value).map_or(f64::NAN, Float::to_f64)),
238
+ RubyType::Nil => self.writer.null(),
239
+ RubyType::True => self.writer.bool(true),
240
+ RubyType::False => self.writer.bool(false),
241
+ RubyType::String => {
242
+ self.string(RString::from_value(value).unwrap_or_else(|| ruby.str_new("")));
243
+ }
244
+ RubyType::Symbol => self.string(rb_raw::sym2str(ruby, value)),
245
+ _ => return false,
246
+ }
247
+ true
248
+ }
249
+
250
+ fn value(&mut self, value: Value) -> Result<(), EncodeError> {
251
+ let kind = rb_raw::ruby_type(self.ruby, value);
252
+ if self.immediate(value, kind) {
253
+ return Ok(());
254
+ }
255
+ match kind {
134
256
  RubyType::Array => {
135
257
  self.array(value);
136
258
  Ok(())
137
259
  }
138
260
  RubyType::Hash => self.hash(value),
139
261
  RubyType::Data => self.data(value),
140
- RubyType::Nil => {
141
- self.writer.null();
142
- Ok(())
143
- }
144
- RubyType::True => {
145
- self.writer.bool(true);
146
- Ok(())
147
- }
148
- RubyType::False => {
149
- self.writer.bool(false);
150
- Ok(())
151
- }
152
262
  RubyType::Bignum => self.bigint(value),
153
- RubyType::Fixnum => {
154
- self.writer
155
- .integer(Fixnum::from_value(value).map_or(0, Fixnum::to_i64));
156
- Ok(())
157
- }
158
- RubyType::Float => {
159
- self.writer
160
- .number(Float::from_value(value).map_or(f64::NAN, Float::to_f64));
161
- Ok(())
162
- }
163
- RubyType::Symbol => {
164
- self.string(rb_raw::sym2str(ruby, value));
165
- Ok(())
166
- }
167
- RubyType::String => {
168
- self.string(RString::from_value(value).unwrap_or_else(|| ruby.str_new("")));
169
- Ok(())
170
- }
171
263
  RubyType::Object => self.object(value),
172
264
  RubyType::Other(kind) => Err(EncodeError::unsupported(&format!(
173
265
  "unsupported type {kind:x}"
174
266
  ))),
267
+ RubyType::Fixnum
268
+ | RubyType::Float
269
+ | RubyType::Nil
270
+ | RubyType::True
271
+ | RubyType::False
272
+ | RubyType::String
273
+ | RubyType::Symbol => Ok(()),
175
274
  }
176
275
  }
177
276
 
277
+ // The leading immediates are written at once: reading one runs no code
278
+ // that could change the array before its turn. The rest wait their turns.
178
279
  fn array(&mut self, value: Value) {
179
280
  if let Some(&id) = self.seen.get(&rb_raw::identity(value)) {
180
281
  self.writer.object_ref(id);
181
282
  return;
182
283
  }
183
- let len = RArray::from_value(value).map_or(0, RArray::len);
284
+ let array = RArray::from_value(value);
285
+ let len = array.map_or(0, RArray::len);
184
286
  let count = length(len);
185
287
  let id = self.writer.begin_array(count);
186
- let array = self.pins.push(value);
288
+ let pinned = self.pin(value);
187
289
  self.seen.insert(rb_raw::identity(value), id);
290
+ let end = isize::try_from(len).unwrap_or(isize::MAX);
291
+ let mut next = 0;
292
+ while let Some(array) = array
293
+ && next < end
294
+ {
295
+ let element = array
296
+ .entry::<Value>(next)
297
+ .unwrap_or_else(|_| self.ruby.qnil().as_value());
298
+ if !self.immediate(element, rb_raw::ruby_type(self.ruby, element)) {
299
+ break;
300
+ }
301
+ next += 1;
302
+ }
303
+ if next >= end {
304
+ self.writer.end_array(count);
305
+ return;
306
+ }
188
307
  self.work.push(Item::EndArray(count));
189
- for index in (0..isize::try_from(len).unwrap_or(isize::MAX)).rev() {
190
- self.work.push(Item::Element { array, index });
308
+ for index in (next..end).rev() {
309
+ self.work.push(Item::Element {
310
+ array: pinned,
311
+ index,
312
+ });
191
313
  }
192
314
  }
193
315
 
316
+ // mini_racer collects every pair before it writes one. Until a value that
317
+ // is not an immediate, each pair is written as it is collected, which no
318
+ // Ruby code can tell apart; from there on, the pairs wait their turns.
194
319
  fn hash(&mut self, value: Value) -> Result<(), EncodeError> {
195
320
  if let Some(&id) = self.seen.get(&rb_raw::identity(value)) {
196
321
  self.writer.object_ref(id);
197
322
  return Ok(());
198
323
  }
199
- self.pins.push(value);
324
+ self.pin(value);
200
325
  let hash = RHash::from_value(value).unwrap_or_else(|| self.ruby.hash_new());
326
+ self.objects = self.objects.saturating_add(1);
327
+ self.properties = self.properties.saturating_add(hash.len());
328
+ let id = self.writer.begin_object();
329
+ self.seen.insert(rb_raw::identity(value), id);
201
330
  let mut pairs = Vec::new();
202
331
  let mut keys_fit = true;
332
+ let mut written = 0u32;
203
333
  hash.foreach(|key: Value, item: Value| {
334
+ let key_kind = rb_raw::ruby_type(self.ruby, key);
204
335
  keys_fit &= matches!(
205
- rb_raw::ruby_type(self.ruby, key),
336
+ key_kind,
206
337
  RubyType::Fixnum | RubyType::String | RubyType::Symbol
207
338
  );
208
- pairs.push((self.pins.push(key), self.pins.push(item)));
339
+ let item_kind = rb_raw::ruby_type(self.ruby, item);
340
+ if keys_fit && pairs.is_empty() && immediate(item_kind) {
341
+ self.immediate(key, key_kind);
342
+ self.immediate(item, item_kind);
343
+ written = written.saturating_add(1);
344
+ } else {
345
+ pairs.push((self.pin(key), self.pin(item)));
346
+ }
209
347
  Ok(ForEach::Continue)
210
348
  })?;
211
- let id = self.writer.begin_object();
212
- self.seen.insert(rb_raw::identity(value), id);
213
349
  if !keys_fit {
214
350
  return Err(EncodeError::unsupported("TODO serialize as Map"));
215
351
  }
216
- self.work.push(Item::EndObject(length(pairs.len())));
352
+ let count = written.saturating_add(length(pairs.len()));
353
+ if pairs.is_empty() {
354
+ self.writer.end_object(count);
355
+ return Ok(());
356
+ }
357
+ self.work.push(Item::EndObject(count));
217
358
  for (key, item) in pairs.into_iter().rev() {
218
359
  self.work.push(Item::Value(item));
219
360
  self.work.push(Item::Value(key));
@@ -232,7 +373,7 @@ impl Encoder<'_, '_> {
232
373
  }
233
374
 
234
375
  fn remember(&mut self, value: Value, id: u32) {
235
- self.pins.push(value);
376
+ self.pin(value);
236
377
  self.seen.insert(rb_raw::identity(value), id);
237
378
  }
238
379
 
@@ -246,7 +387,7 @@ impl Encoder<'_, '_> {
246
387
  rb_raw::identity(value.class().as_value()) == rb_raw::identity(date_time)
247
388
  }) {
248
389
  time = value.funcall("to_time", ())?;
249
- self.pins.push(time);
390
+ self.pin(time);
250
391
  }
251
392
  if rb_raw::identity(time.class().as_value())
252
393
  == rb_raw::identity(ruby.class_time().as_value())
@@ -266,7 +407,7 @@ impl Encoder<'_, '_> {
266
407
  }
267
408
  if value.respond_to("to_time", false)? {
268
409
  let time: Value = value.funcall("to_time", ())?;
269
- self.pins.push(time);
410
+ self.pin(time);
270
411
  if time.is_kind_of(ruby.class_time()) {
271
412
  let id = self.date(time)?;
272
413
  self.remember(value, id);
@@ -322,12 +463,23 @@ impl Encoder<'_, '_> {
322
463
  .map_err(|error| EncodeError::unsupported(&error.to_string()))
323
464
  }
324
465
 
466
+ // mini_racer tells encodings apart by name. The ones it sends as UTF-8
467
+ // that strings most often have are told by index, without the lookup.
325
468
  fn string(&mut self, text: RString) {
326
- let encoding = RbEncoding::from(text.enc_get());
327
- rb_raw::with_string_bytes(self.ruby, text, |bytes| match encoding.name() {
328
- "ISO-8859-1" => self.writer.latin1(bytes),
329
- "UTF-16LE" => self.writer.utf16le(bytes),
330
- _ => self.writer.utf8(bytes),
469
+ let ruby = self.ruby;
470
+ let index = text.enc_get();
471
+ let utf8 = index == ruby.utf8_encindex()
472
+ || index == ruby.usascii_encindex()
473
+ || index == ruby.ascii8bit_encindex();
474
+ rb_raw::with_string_bytes(ruby, text, |bytes| {
475
+ if utf8 {
476
+ return self.writer.utf8(bytes);
477
+ }
478
+ match RbEncoding::from(index).name() {
479
+ "ISO-8859-1" => self.writer.latin1(bytes),
480
+ "UTF-16LE" => self.writer.utf16le(bytes),
481
+ _ => self.writer.utf8(bytes),
482
+ }
331
483
  });
332
484
  }
333
485
  }
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module MiniRacerRs
4
- VERSION = "1.0.0"
4
+ VERSION = "1.0.2"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mini_racer_rs
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.0.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andy Aragon
@@ -54,9 +54,11 @@ files:
54
54
  - crates/mini_racer_engine/src/process/code_cache.rs
55
55
  - crates/mini_racer_engine/src/process/fork.rs
56
56
  - crates/mini_racer_engine/src/process/platform.rs
57
+ - crates/mini_racer_engine/src/process/sources.rs
57
58
  - crates/mini_racer_engine/src/process/tasks.rs
58
59
  - crates/mini_racer_engine/src/process/watchdog.rs
59
60
  - crates/mini_racer_engine/src/process/workers.rs
61
+ - crates/mini_racer_engine/src/request.rs
60
62
  - crates/mini_racer_engine/src/runner.rs
61
63
  - crates/mini_racer_engine/src/serialize.rs
62
64
  - crates/mini_racer_engine/src/snapshot.rs