mini_racer_rs 1.0.2 → 1.0.4

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.
@@ -9,6 +9,7 @@ use crate::process::code_cache::Key;
9
9
  const MAX_SOURCES: usize = 8;
10
10
  const MAX_BYTES: usize = 256 << 20;
11
11
  const MAX_REFUSED: usize = 8;
12
+ const MAX_SWEPT: usize = 8;
12
13
 
13
14
  /// A large script's source as V8 stores a string of it: a byte a unit when
14
15
  /// every unit fits Latin-1, two otherwise. Never changed once made.
@@ -63,8 +64,9 @@ impl Units {
63
64
 
64
65
  /// One copy of each large script's source, outside every V8 heap.
65
66
  ///
66
- /// A script is kept once the process has seen it before, and every context
67
- /// compiling it then reads the kept copy in place of one on its own heap.
67
+ /// A script is kept by the first context to compile it, and every context
68
+ /// compiling it, that one included, reads the kept copy in place of one on
69
+ /// its own heap.
68
70
  /// Each string V8 makes from one holds a reference to it, which V8 hands back
69
71
  /// when the string dies (`sys::external_string`). A source is dropped only
70
72
  /// when no string holds one, so V8 never releases the last reference itself.
@@ -72,6 +74,8 @@ pub struct Sources {
72
74
  list: Mutex<List>,
73
75
  // How many sources, and how many bytes of them, may be kept and read.
74
76
  bounds: (usize, usize),
77
+ // The number the next context's reads are told apart by.
78
+ next_reader: AtomicU64,
75
79
  made: AtomicU64,
76
80
  reads: AtomicU64,
77
81
  conversions: AtomicU64,
@@ -91,6 +95,9 @@ struct List {
91
95
  // left or, as invalid UTF-8, unbounded, so that while it is among the last
92
96
  // `MAX_REFUSED` refused it is not converted again without room for it.
93
97
  refused: Vec<(Key, usize)>,
98
+ // The last `MAX_SWEPT` sources swept, so one made again, which a second
99
+ // context has now read after all, is kept as such.
100
+ swept: Vec<Key>,
94
101
  }
95
102
 
96
103
  struct Kept {
@@ -99,23 +106,60 @@ struct Kept {
99
106
  // The references from this process's list and every ancestor's, which a
100
107
  // forked child never touches again; the strings reading it hold the rest.
101
108
  held: usize,
109
+ readers: Readers,
110
+ }
111
+
112
+ // The contexts that have read a kept source. One alone, as a script evaluated
113
+ // once has, gains nothing from a copy no string reads any more; a source more
114
+ // have read is likely to be read again, and an inherited one is only copied
115
+ // again if a child drops it, so both stay until a new source needs the room.
116
+ #[derive(Clone, Copy, PartialEq, Eq)]
117
+ enum Readers {
118
+ One(u64),
119
+ Many,
102
120
  }
103
121
 
104
122
  impl Kept {
105
123
  fn unread(&self) -> bool {
106
124
  self.units.references() == self.held
107
125
  }
126
+
127
+ fn spent(&self) -> bool {
128
+ matches!(self.readers, Readers::One(_)) && self.unread()
129
+ }
108
130
  }
109
131
 
110
132
  impl List {
111
- fn find(&mut self, key: &Key) -> Option<Units> {
133
+ fn find(&mut self, key: &Key, reader: u64) -> Option<Units> {
112
134
  let at = self.kept.iter().position(|kept| kept.key == *key)?;
113
- let found = self.kept.remove(at);
135
+ let mut found = self.kept.remove(at);
136
+ if found.readers != Readers::One(reader) {
137
+ found.readers = Readers::Many;
138
+ }
114
139
  let units = found.units.clone();
115
140
  self.kept.push(found);
116
141
  Some(units)
117
142
  }
118
143
 
144
+ // V8's release of a string touches only the count, so a copy whose last
145
+ // string has gone waits for the next `Sources::units` call.
146
+ fn sweep(&mut self) -> Vec<Kept> {
147
+ let spent: Vec<Kept> = self.kept.extract_if(.., |kept| kept.spent()).collect();
148
+ for kept in &spent {
149
+ if self.swept.len() >= MAX_SWEPT {
150
+ self.swept.remove(0);
151
+ }
152
+ self.swept.push(kept.key.clone());
153
+ }
154
+ spent
155
+ }
156
+
157
+ fn unswept(&mut self, key: &Key) -> bool {
158
+ let before = self.swept.len();
159
+ self.swept.retain(|swept| swept != key);
160
+ self.swept.len() < before
161
+ }
162
+
119
163
  // Whether `bytes` more fit beside what strings still read, which nothing
120
164
  // can drop.
121
165
  fn room(&self, bytes: usize, (sources, most): (usize, usize)) -> bool {
@@ -161,30 +205,43 @@ impl Sources {
161
205
  Self {
162
206
  list: Mutex::default(),
163
207
  bounds: (sources, bytes),
208
+ next_reader: AtomicU64::new(0),
164
209
  made: AtomicU64::new(0),
165
210
  reads: AtomicU64::new(0),
166
211
  conversions: AtomicU64::new(0),
167
212
  }
168
213
  }
169
214
 
215
+ /// A number for a new context to read shared sources as, told apart from
216
+ /// every other context of this process.
217
+ pub(crate) fn reader(&self) -> u64 {
218
+ self.next_reader.fetch_add(1, Ordering::Relaxed)
219
+ }
220
+
170
221
  /// The shared source of the script `text` under `key`, kept from the first
171
- /// context that asks for it. `None` leaves the context its own copy: the
172
- /// source is invalid UTF-8, or too large for what is kept and still read.
173
- /// Only a source that may fit is converted, outside the lock.
174
- pub(crate) fn units(&self, key: &Key, text: &JsText) -> Option<Units> {
222
+ /// context that asks for it, for the context `reader`. `None` leaves the
223
+ /// context its own copy: the source is invalid UTF-8, or too large for
224
+ /// what is kept and still read. Only a source that may fit is converted,
225
+ /// outside the lock. Every call looks up the requested source, then drops
226
+ /// each other copy that no string reads and that one context alone ever
227
+ /// read.
228
+ pub(crate) fn units(&self, key: &Key, text: &JsText, reader: u64) -> Option<Units> {
175
229
  let mut list = lock(&self.list);
176
- if let Some(units) = list.find(key) {
177
- return Some(units);
178
- }
179
- if !list.room(list.least_bytes(key, text), self.bounds) {
180
- return None;
181
- }
230
+ // Found before the sweep, so a copy the context before this one left
231
+ // unread is read again, and kept, rather than copied again.
232
+ let found = list.find(key, reader);
233
+ let spent = list.sweep();
234
+ let room = found.is_none() && list.room(list.least_bytes(key, text), self.bounds);
182
235
  drop(list);
236
+ drop(spent);
237
+ if !room {
238
+ return found;
239
+ }
183
240
  self.conversions.fetch_add(1, Ordering::Relaxed);
184
241
  let units = Units::of(text);
185
242
  let mut list = lock(&self.list);
186
243
  // Made meanwhile by a context loading the same script.
187
- if let Some(found) = list.find(key) {
244
+ if let Some(found) = list.find(key, reader) {
188
245
  return Some(found);
189
246
  }
190
247
  let bytes = units.as_ref().map_or(usize::MAX, Units::bytes);
@@ -211,13 +268,20 @@ impl Sources {
211
268
  };
212
269
  dropped.push(list.kept.remove(at));
213
270
  }
271
+ let readers = if list.unswept(key) {
272
+ Readers::Many
273
+ } else {
274
+ Readers::One(reader)
275
+ };
214
276
  list.kept.push(Kept {
215
277
  key: key.clone(),
216
278
  units: units.clone(),
217
279
  held: 1,
280
+ readers,
218
281
  });
219
282
  drop(list);
220
- // Freed once the lock is released, as a large source takes a while.
283
+ // Freed once the lock is released, as a large source takes a while,
284
+ // like the spent copies above.
221
285
  drop(dropped);
222
286
  self.made.fetch_add(1, Ordering::Relaxed);
223
287
  Some(units)
@@ -242,9 +306,11 @@ impl Sources {
242
306
  key: kept.key.clone(),
243
307
  units: kept.units.clone(),
244
308
  held: kept.held + 1,
309
+ readers: Readers::Many,
245
310
  })
246
311
  .collect(),
247
312
  refused: list.refused.clone(),
313
+ swept: list.swept.clone(),
248
314
  })
249
315
  .unwrap_or_default();
250
316
  Self {
@@ -291,16 +357,14 @@ mod tests {
291
357
  use crate::control::JsText;
292
358
  use crate::process::code_cache::{CodeCache, Key};
293
359
 
294
- // A script's key once the code cache has seen it twice.
360
+ // A script's key, as its first sighting in a context sharing it has it.
295
361
  fn key(text: &JsText) -> Key {
296
362
  let cache: &'static CodeCache = Box::leak(Box::default());
297
- let name = JsText::from("bundle.js");
298
- let _ = cache.plan(&name, text, None);
299
363
  cache
300
- .plan(&name, text, None)
364
+ .plan(&JsText::from("bundle.js"), text, None, true)
301
365
  .key()
302
366
  .cloned()
303
- .unwrap_or_else(|| panic!("a second sighting has a key"))
367
+ .unwrap_or_else(|| panic!("a shared first sighting has a key"))
304
368
  }
305
369
 
306
370
  fn script(seed: u8) -> JsText {
@@ -356,8 +420,8 @@ mod tests {
356
420
  let sources = Sources::default();
357
421
  let text = script(1);
358
422
  let key = key(&text);
359
- let first = one_byte(sources.units(&key, &text));
360
- let second = one_byte(sources.units(&key, &text));
423
+ let first = one_byte(sources.units(&key, &text, sources.reader()));
424
+ let second = one_byte(sources.units(&key, &text, sources.reader()));
361
425
  assert!(Arc::ptr_eq(&first, &second));
362
426
  assert_eq!(sources.made(), 1);
363
427
  assert_eq!(sources.reading(), 2);
@@ -377,6 +441,12 @@ mod tests {
377
441
  }
378
442
  }
379
443
 
444
+ // A copy two contexts have read, the second still reading it.
445
+ fn shared(sources: &Sources, key: &Key, text: &JsText) -> Option<Units> {
446
+ drop(sources.units(key, text, sources.reader()));
447
+ sources.units(key, text, sources.reader())
448
+ }
449
+
380
450
  #[test]
381
451
  fn a_source_still_read_is_kept_and_an_unread_one_makes_room() {
382
452
  let sources = Sources::default();
@@ -386,31 +456,108 @@ mod tests {
386
456
  let mut read: Vec<Option<Units>> = texts
387
457
  .iter()
388
458
  .zip(&keys)
389
- .map(|(text, key)| sources.units(key, text))
459
+ .map(|(text, key)| shared(&sources, key, text))
390
460
  .collect();
391
461
  let extra = script(100);
392
462
  let extra_key = key(&extra);
393
- assert!(sources.units(&extra_key, &extra).is_none());
463
+ assert!(shared(&sources, &extra_key, &extra).is_none());
394
464
  drop(read.remove(3));
395
465
  drop(read.remove(0));
396
- drop(sources.units(&keys[3], &texts[3]));
466
+ drop(sources.units(&keys[3], &texts[3], sources.reader()));
397
467
  // Of the two unread, the least recently used makes room.
398
- assert!(sources.units(&extra_key, &extra).is_some());
468
+ assert!(shared(&sources, &extra_key, &extra).is_some());
399
469
  let made = sources.made();
400
470
  assert_eq!(made, u64::from(count) + 1);
401
- drop(sources.units(&keys[3], &texts[3]));
471
+ drop(sources.units(&keys[3], &texts[3], sources.reader()));
402
472
  assert_eq!(sources.made(), made);
403
- drop(sources.units(&keys[0], &texts[0]));
473
+ drop(sources.units(&keys[0], &texts[0], sources.reader()));
404
474
  assert_eq!(sources.made(), made + 1);
405
475
  for (at, held) in [1, 2, 4, 5, 6, 7].into_iter().zip(&read) {
406
476
  assert!(same(
407
- sources.units(&keys[at], &texts[at]).as_ref(),
477
+ sources
478
+ .units(&keys[at], &texts[at], sources.reader())
479
+ .as_ref(),
408
480
  held.as_ref()
409
481
  ));
410
482
  }
411
483
  assert_eq!(sources.made(), made + 1);
412
484
  }
413
485
 
486
+ // One context's copy, however often it read it, waits for no new source
487
+ // once no string reads it: the next call drops and frees it.
488
+ #[test]
489
+ fn a_copy_one_context_read_goes_at_the_next_call_once_unread() {
490
+ let sources = Sources::default();
491
+ let (text, other) = (script(70), script(71));
492
+ let (key, other_key) = (key(&text), key(&other));
493
+ let reader = sources.reader();
494
+ let first = one_byte(sources.units(&key, &text, reader));
495
+ let again = one_byte(sources.units(&key, &text, reader));
496
+ assert!(Arc::ptr_eq(&first, &again));
497
+ let copy = Arc::downgrade(&first);
498
+ drop((first, again));
499
+ assert!(copy.upgrade().is_some());
500
+ drop(sources.units(&other_key, &other, sources.reader()));
501
+ assert!(copy.upgrade().is_none());
502
+ drop(sources.units(&key, &text, sources.reader()));
503
+ assert_eq!(sources.made(), 3);
504
+ }
505
+
506
+ #[test]
507
+ fn a_copy_two_contexts_read_in_turn_stays_once_unread() {
508
+ let sources = Sources::default();
509
+ let (text, other) = (script(72), script(73));
510
+ let (key, other_key) = (key(&text), key(&other));
511
+ let copy = Arc::downgrade(&one_byte(shared(&sources, &key, &text)));
512
+ drop(sources.units(&other_key, &other, sources.reader()));
513
+ let again = one_byte(sources.units(&key, &text, sources.reader()));
514
+ assert!(
515
+ copy.upgrade()
516
+ .is_some_and(|copy| Arc::ptr_eq(&copy, &again))
517
+ );
518
+ assert_eq!(sources.made(), 2);
519
+ }
520
+
521
+ #[test]
522
+ fn a_copy_a_second_context_reads_while_the_first_does_stays_once_unread() {
523
+ let sources = Sources::default();
524
+ let (text, other) = (script(74), script(75));
525
+ let (key, other_key) = (key(&text), key(&other));
526
+ let first = one_byte(sources.units(&key, &text, sources.reader()));
527
+ let second = one_byte(sources.units(&key, &text, sources.reader()));
528
+ let copy = Arc::downgrade(&first);
529
+ drop((first, second));
530
+ drop(sources.units(&other_key, &other, sources.reader()));
531
+ let again = one_byte(sources.units(&key, &text, sources.reader()));
532
+ assert!(
533
+ copy.upgrade()
534
+ .is_some_and(|copy| Arc::ptr_eq(&copy, &again))
535
+ );
536
+ assert_eq!(sources.made(), 2);
537
+ }
538
+
539
+ // Contexts loading two scripts in turn, each gone before the next, sweep
540
+ // each other's copy; a copy made again after its sweep is kept as one more
541
+ // than one context read, so each script is converted twice in all.
542
+ #[test]
543
+ fn scripts_loaded_in_turn_by_short_lived_contexts_stay_after_their_second_copy() {
544
+ let sources = Sources::default();
545
+ let texts = [script(78), script(79)];
546
+ let keys = texts.each_ref().map(key);
547
+ for _ in 0..2 {
548
+ for (key, text) in keys.iter().zip(&texts) {
549
+ drop(sources.units(key, text, sources.reader()));
550
+ }
551
+ }
552
+ assert_eq!(sources.made(), 4);
553
+ for _ in 0..3 {
554
+ for (key, text) in keys.iter().zip(&texts) {
555
+ drop(sources.units(key, text, sources.reader()));
556
+ }
557
+ }
558
+ assert_eq!(sources.made(), 4);
559
+ }
560
+
414
561
  #[test]
415
562
  fn a_full_list_converts_nothing() {
416
563
  let sources = Sources::default();
@@ -418,18 +565,26 @@ mod tests {
418
565
  let read: Vec<Option<Units>> = (40..40 + count)
419
566
  .map(|seed| {
420
567
  let text = script(seed);
421
- sources.units(&key(&text), &text)
568
+ sources.units(&key(&text), &text, sources.reader())
422
569
  })
423
570
  .collect();
424
571
  let converted = sources.conversions();
425
572
  let extra = script(100);
426
573
  let extra_key = key(&extra);
427
574
  for _ in 0..3 {
428
- assert!(sources.units(&extra_key, &extra).is_none());
575
+ assert!(
576
+ sources
577
+ .units(&extra_key, &extra, sources.reader())
578
+ .is_none()
579
+ );
429
580
  }
430
581
  assert_eq!(sources.conversions(), converted);
431
582
  drop(read);
432
- assert!(sources.units(&extra_key, &extra).is_some());
583
+ assert!(
584
+ sources
585
+ .units(&extra_key, &extra, sources.reader())
586
+ .is_some()
587
+ );
433
588
  assert_eq!(sources.conversions(), converted + 1);
434
589
  }
435
590
 
@@ -440,21 +595,29 @@ mod tests {
440
595
  fn a_source_is_converted_only_while_it_may_fit() {
441
596
  let sources = Sources::bounded(MAX_SOURCES, 5 << 20);
442
597
  let read = script(50);
443
- let still_read = sources.units(&key(&read), &read);
598
+ let still_read = sources.units(&key(&read), &read, sources.reader());
444
599
  assert!(still_read.is_some());
445
600
  let too_large = JsText::Latin1(vec![51; 4 << 20]);
446
- assert!(sources.units(&key(&too_large), &too_large).is_none());
601
+ assert!(
602
+ sources
603
+ .units(&key(&too_large), &too_large, sources.reader())
604
+ .is_none()
605
+ );
447
606
  assert_eq!(sources.conversions(), 1);
448
607
  let mut astral = vec![b'a'; 2 << 20];
449
608
  astral.extend_from_slice("\u{1F600}".as_bytes());
450
609
  let astral = JsText::Utf8(astral);
451
610
  let astral_key = key(&astral);
452
611
  for _ in 0..3 {
453
- assert!(sources.units(&astral_key, &astral).is_none());
612
+ assert!(
613
+ sources
614
+ .units(&astral_key, &astral, sources.reader())
615
+ .is_none()
616
+ );
454
617
  }
455
618
  assert_eq!(sources.conversions(), 2);
456
619
  drop(still_read);
457
- assert!(two_byte(sources.units(&astral_key, &astral)).len() > 2 << 20);
620
+ assert!(two_byte(sources.units(&astral_key, &astral, sources.reader())).len() > 2 << 20);
458
621
  assert_eq!((sources.conversions(), sources.made()), (3, 2));
459
622
  }
460
623
 
@@ -466,7 +629,11 @@ mod tests {
466
629
  let invalid = JsText::Utf8(invalid);
467
630
  let invalid_key = key(&invalid);
468
631
  for _ in 0..3 {
469
- assert!(sources.units(&invalid_key, &invalid).is_none());
632
+ assert!(
633
+ sources
634
+ .units(&invalid_key, &invalid, sources.reader())
635
+ .is_none()
636
+ );
470
637
  }
471
638
  assert_eq!((sources.conversions(), sources.made()), (1, 0));
472
639
  }
@@ -476,32 +643,56 @@ mod tests {
476
643
  let parent: &'static Sources = Box::leak(Box::default());
477
644
  let text = script(2);
478
645
  let key = key(&text);
479
- let shared = parent.units(&key, &text);
646
+ let shared = parent.units(&key, &text, parent.reader());
480
647
  let child = parent.inherited();
481
- let in_child = child.units(&key, &text);
648
+ let in_child = child.units(&key, &text, child.reader());
482
649
  assert!(same(shared.as_ref(), in_child.as_ref()));
483
650
  assert_eq!(child.reading(), 2);
484
651
  let copy = Arc::downgrade(&one_byte(shared));
485
652
  drop(in_child);
653
+ // Sources the child still reads fill its list, so the unread
654
+ // inherited copy makes room.
486
655
  let count = u8::try_from(MAX_SOURCES).unwrap_or(u8::MAX);
487
- for seed in 30..30 + count {
488
- let other = script(seed);
489
- drop(child.units(&self::key(&other), &other));
490
- }
656
+ let others: Vec<Option<Units>> = (30..30 + count)
657
+ .map(|seed| {
658
+ let other = script(seed);
659
+ child.units(&self::key(&other), &other, child.reader())
660
+ })
661
+ .collect();
491
662
  assert_eq!(copy.strong_count(), 1);
492
- let again = one_byte(child.units(&key, &text));
663
+ drop(others);
664
+ let again = one_byte(child.units(&key, &text, child.reader()));
493
665
  assert!(
494
666
  !copy
495
667
  .upgrade()
496
668
  .is_some_and(|copy| Arc::ptr_eq(&copy, &again))
497
669
  );
498
- let from_parent = one_byte(parent.units(&key, &text));
670
+ let from_parent = one_byte(parent.units(&key, &text, parent.reader()));
499
671
  assert!(
500
672
  copy.upgrade()
501
673
  .is_some_and(|copy| Arc::ptr_eq(&copy, &from_parent))
502
674
  );
503
675
  }
504
676
 
677
+ // Dropping an inherited copy frees nothing in a child, and its next
678
+ // context loading the script would copy it again, so the child keeps
679
+ // one only its parent's context read.
680
+ #[test]
681
+ fn a_child_keeps_an_inherited_copy_one_context_read() {
682
+ let parent: &'static Sources = Box::leak(Box::default());
683
+ let (text, other) = (script(76), script(77));
684
+ let (key, other_key) = (key(&text), key(&other));
685
+ let copy = Arc::downgrade(&one_byte(parent.units(&key, &text, parent.reader())));
686
+ let child = parent.inherited();
687
+ drop(child.units(&other_key, &other, child.reader()));
688
+ let in_child = one_byte(child.units(&key, &text, child.reader()));
689
+ assert!(
690
+ copy.upgrade()
691
+ .is_some_and(|copy| Arc::ptr_eq(&copy, &in_child))
692
+ );
693
+ assert_eq!(child.made(), 1);
694
+ }
695
+
505
696
  // A child's inherited copies stay read while its parent's strings hold
506
697
  // them, so a child forked with a full list must not convert either.
507
698
  #[test]
@@ -511,14 +702,14 @@ mod tests {
511
702
  let read: Vec<Option<Units>> = (60..60 + count)
512
703
  .map(|seed| {
513
704
  let text = script(seed);
514
- parent.units(&key(&text), &text)
705
+ parent.units(&key(&text), &text, parent.reader())
515
706
  })
516
707
  .collect();
517
708
  let child = parent.inherited();
518
709
  let extra = script(110);
519
710
  let extra_key = key(&extra);
520
711
  for _ in 0..3 {
521
- assert!(child.units(&extra_key, &extra).is_none());
712
+ assert!(child.units(&extra_key, &extra, child.reader()).is_none());
522
713
  }
523
714
  assert_eq!(child.conversions(), 0);
524
715
  drop(read);
@@ -529,11 +720,14 @@ mod tests {
529
720
  let parent = Sources::default();
530
721
  let text = script(3);
531
722
  let key = key(&text);
532
- let shared = one_byte(parent.units(&key, &text));
723
+ let shared = one_byte(parent.units(&key, &text, parent.reader()));
533
724
  let held = parent.list.lock();
534
725
  let child = parent.inherited();
535
726
  drop(held);
536
- assert!(!Arc::ptr_eq(&shared, &one_byte(child.units(&key, &text))));
727
+ assert!(!Arc::ptr_eq(
728
+ &shared,
729
+ &one_byte(child.units(&key, &text, child.reader()))
730
+ ));
537
731
  assert_eq!(child.made(), 1);
538
732
  }
539
733
  }
@@ -62,6 +62,9 @@ struct Runtime {
62
62
  freed: AtomicBool,
63
63
  // The startup snapshot this isolate began from, for the code cache's key.
64
64
  snapshot: Option<u64>,
65
+ // Tells this context's reads of a shared source from other contexts', as
66
+ // a copy only one context read is dropped once no string reads it.
67
+ reader: u64,
65
68
  // Large scripts to cache once their dispatch is over, so making a cache
66
69
  // never counts against a timeout.
67
70
  uncached: Mutex<Vec<Uncached>>,
@@ -142,6 +145,7 @@ impl Runtime {
142
145
  running: AtomicU32::new(0),
143
146
  freed: AtomicBool::new(false),
144
147
  snapshot,
148
+ reader: generation.sources().reader(),
145
149
  uncached: Mutex::default(),
146
150
  hoisting: Mutex::default(),
147
151
  frames: Mutex::default(),
@@ -770,10 +774,12 @@ fn evaluate(
770
774
  }
771
775
  let plan = if large(source) {
772
776
  runtime.freed.store(true, Ordering::Relaxed);
773
- runtime
774
- .generation
775
- .code_cache()
776
- .plan(filename, source, runtime.snapshot)
777
+ runtime.generation.code_cache().plan(
778
+ filename,
779
+ source,
780
+ runtime.snapshot,
781
+ shares_sources(runtime),
782
+ )
777
783
  } else {
778
784
  Plan::Compile(None)
779
785
  };
@@ -796,21 +802,25 @@ fn evaluate(
796
802
  Ok(reply)
797
803
  }
798
804
 
799
- // A large script the process has seen before reads its source from the copy
800
- // every context compiling it shares, outside the V8 heap. `max_memory:`
801
- // measures the heap, where mini_racer keeps the source, so a context with it
802
- // keeps its own copy.
805
+ // `max_memory:` measures the heap, where mini_racer keeps the source, so a
806
+ // context with it keeps its own copy.
807
+ fn shares_sources(runtime: &Runtime) -> bool {
808
+ runtime.block.config.max_memory.is_none()
809
+ }
810
+
811
+ // A large script reads its source from the copy every context compiling it
812
+ // shares, outside the V8 heap, made by the first of them.
803
813
  fn shared_source<'s>(
804
814
  scope: &v8::PinScope<'s, '_>,
805
815
  runtime: &Runtime,
806
816
  key: Option<&Key>,
807
817
  source: &JsText,
808
818
  ) -> Option<v8::Local<'s, v8::String>> {
809
- if runtime.block.config.max_memory.is_some() {
819
+ if !shares_sources(runtime) {
810
820
  return None;
811
821
  }
812
822
  let sources = runtime.generation.sources();
813
- let string = sys::external_string(scope, &sources.units(key?, source)?)?;
823
+ let string = sys::external_string(scope, &sources.units(key?, source, runtime.reader)?)?;
814
824
  sources.read();
815
825
  Some(string)
816
826
  }
@@ -85,6 +85,12 @@ pub trait Visitor {
85
85
 
86
86
  fn end(&mut self, frame: Self::Frame) -> Result<Self::Value, Self::Error>;
87
87
 
88
+ /// A key that is a string, for an entry of `frame` other than an error's
89
+ /// fields, which would otherwise reach `scalar` as text.
90
+ fn key(&mut self, _frame: &Self::Frame, text: Text<'_>) -> Result<Self::Value, Self::Error> {
91
+ self.scalar(Scalar::Text(text))
92
+ }
93
+
88
94
  /// Remembers `value` as V8's object `id` for later back references. Ids
89
95
  /// arrive in increasing order, except that a container whose `begin`
90
96
  /// returned no value is registered when it ends.
@@ -650,11 +656,27 @@ impl<'a, 'v, V: Visitor> Decoder<'a, 'v, V> {
650
656
  }
651
657
 
652
658
  fn insert(&mut self, frame: &mut V::Frame, depth: u32) -> Result<(), DecodeError<V::Error>> {
653
- let key = self.value(Self::child(depth)?)?;
654
- let value = self.value(Self::child(depth)?)?;
659
+ let depth = Self::child(depth)?;
660
+ let key = self.entry_key(frame, depth)?;
661
+ let value = self.value(depth)?;
655
662
  Self::visit(self.visitor.insert(frame, key, value))
656
663
  }
657
664
 
665
+ // A key that is not a string is read again as a value: handing `value` its
666
+ // tag would split `value` in two, and the half with the containers takes
667
+ // them all inline. Out of line, so `insert` stays inside the container's
668
+ // frame; a deep result repeats every frame on the way down.
669
+ #[inline(never)]
670
+ fn entry_key(&mut self, frame: &V::Frame, depth: u32) -> Decoded<V> {
671
+ let start = self.reader;
672
+ let tag = self.tag()?;
673
+ if let Some(text) = self.text(tag)? {
674
+ return Self::visit(self.visitor.key(frame, text));
675
+ }
676
+ self.reader = start;
677
+ self.value(depth)
678
+ }
679
+
658
680
  // Reads keyed entries up to `end`, which may follow padding only if a key does.
659
681
  fn entries(
660
682
  &mut self,