schemurai 3.0.0 → 3.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.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +6 -4
  3. data/ext/schemurai_native/Cargo.lock +55 -1
  4. data/ext/schemurai_native/Cargo.toml +4 -1
  5. data/ext/schemurai_native/extconf.rb +1 -1
  6. data/ext/schemurai_native/glue.c +1679 -62
  7. data/ext/schemurai_native/protocol.def +45 -0
  8. data/ext/schemurai_native/src/budget.rs +49 -0
  9. data/ext/schemurai_native/src/container.rs +460 -0
  10. data/ext/schemurai_native/src/content.rs +74 -27
  11. data/ext/schemurai_native/src/diagnostics/array.rs +392 -0
  12. data/ext/schemurai_native/src/diagnostics/object.rs +218 -0
  13. data/ext/schemurai_native/src/diagnostics.rs +1087 -307
  14. data/ext/schemurai_native/src/enumeration.rs +6 -1
  15. data/ext/schemurai_native/src/evaluation.rs +49 -20
  16. data/ext/schemurai_native/src/format.rs +126 -14
  17. data/ext/schemurai_native/src/graph.rs +37 -0
  18. data/ext/schemurai_native/src/instance_graph.rs +90 -0
  19. data/ext/schemurai_native/src/integer_scan.rs +132 -0
  20. data/ext/schemurai_native/src/lib.rs +917 -238
  21. data/ext/schemurai_native/src/memo.rs +11 -38
  22. data/ext/schemurai_native/src/number/arbitrary.rs +154 -0
  23. data/ext/schemurai_native/src/number.rs +250 -59
  24. data/ext/schemurai_native/src/pattern.rs +147 -109
  25. data/ext/schemurai_native/src/pointer.rs +97 -0
  26. data/ext/schemurai_native/src/runtime/properties.rs +162 -0
  27. data/ext/schemurai_native/src/runtime/reports.rs +266 -0
  28. data/ext/schemurai_native/src/runtime/scopes.rs +430 -0
  29. data/ext/schemurai_native/src/runtime.rs +1020 -0
  30. data/ext/schemurai_native/src/scope.rs +60 -16
  31. data/ext/schemurai_native/src/string.rs +68 -0
  32. data/ext/schemurai_native/src/tracking.rs +244 -174
  33. data/lib/schemurai/container_checks/array_diagnostics.rb +119 -0
  34. data/lib/schemurai/container_checks/object_diagnostics.rb +152 -0
  35. data/lib/schemurai/container_checks.rb +534 -0
  36. data/lib/schemurai/decimal.rb +11 -0
  37. data/lib/schemurai/dialect.rb +0 -12
  38. data/lib/schemurai/ecma_regexp.rb +46 -0
  39. data/lib/schemurai/evaluation_pool.rb +66 -0
  40. data/lib/schemurai/evaluator.rb +9 -48
  41. data/lib/schemurai/json_pointer.rb +11 -0
  42. data/lib/schemurai/native/diagnostics.rb +4 -3
  43. data/lib/schemurai/native/evaluator.rb +38 -10
  44. data/lib/schemurai/native/null_numeric.rb +23 -0
  45. data/lib/schemurai/number_checks.rb +128 -0
  46. data/lib/schemurai/string_checks.rb +166 -0
  47. data/lib/schemurai/type_checks.rb +59 -0
  48. data/lib/schemurai/value_equality.rb +72 -0
  49. data/lib/schemurai/version.rb +1 -1
  50. data/lib/schemurai/vm/compiler.rb +18 -26
  51. data/lib/schemurai/vm/evaluator.rb +87 -545
  52. data/lib/schemurai/vm/limited_evaluator.rb +7 -22
  53. metadata +25 -2
@@ -6,6 +6,7 @@ use std::ffi::{c_char, c_void};
6
6
  use std::hash::{BuildHasherDefault, Hasher};
7
7
  use std::panic::{AssertUnwindSafe, catch_unwind};
8
8
  mod budget;
9
+ mod container;
9
10
  mod content;
10
11
  mod diagnostics;
11
12
  mod enumeration;
@@ -13,10 +14,14 @@ mod evaluation;
13
14
  mod format;
14
15
  mod graph;
15
16
  mod instance_graph;
17
+ mod integer_scan;
16
18
  mod memo;
17
19
  mod memo_scope;
18
20
  mod number;
19
21
  mod pattern;
22
+ mod pointer;
23
+ mod runtime;
24
+ mod string;
20
25
  // Some shared constants are consumed only by C or Ruby.
21
26
  #[allow(dead_code)]
22
27
  mod protocol {
@@ -41,6 +46,7 @@ unsafe extern "C" {
41
46
  ) -> i32;
42
47
  fn sn_small_hash(value: Value, entries: *mut Value) -> i32;
43
48
  fn sn_true(value: Value) -> i32;
49
+ fn sn_truthy(value: Value) -> i32;
44
50
  fn sn_fixnum(value: Value) -> i32;
45
51
  fn sn_fixnum_flag() -> Value;
46
52
  fn sn_array_data(value: Value) -> *const Value;
@@ -55,6 +61,8 @@ unsafe extern "C" {
55
61
  required: i32,
56
62
  ) -> i32;
57
63
  fn sn_integer1024(value: Value, words: *mut u64) -> i32;
64
+ fn sn_integer_word_count(value: Value) -> usize;
65
+ fn sn_integer_words(value: Value, words: *mut u64, count: usize) -> i32;
58
66
  fn sn_is_bignum(value: Value) -> i32;
59
67
  fn sn_big256(value: Value, words: *mut u64) -> i32;
60
68
  fn sn_big_hash(value: Value, hash: *mut u64) -> i32;
@@ -115,13 +123,24 @@ enum Verdict {
115
123
  }
116
124
  use Verdict::*;
117
125
  const MAX_EVALUATION_DEPTH: usize = 256;
126
+ // Control-flow instructions can forward an instance without examining its kind.
127
+ // No real classified Ruby value has this kind.
128
+ const TYPE_UNCHECKED: i32 = 0;
129
+ type InstanceKind = std::cell::Cell<i32>;
130
+ #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
131
+ enum KindDemand {
132
+ None,
133
+ Predicate,
134
+ Data,
135
+ }
118
136
  const MAX_UNIQUE_INDEX_BYTES: usize = 64 * 1024 * 1024;
119
137
  #[derive(Debug)]
120
138
  enum Op {
121
139
  Unsupported,
140
+ UnresolvedRef(Value),
122
141
  Boolean(bool),
123
142
  Ref(scope::Reference),
124
- Type(i32),
143
+ Type(i32, bool),
125
144
  Const(Value),
126
145
  Enum(Box<enumeration::Enumeration>),
127
146
  All(Vec<usize>),
@@ -136,6 +155,7 @@ enum Op {
136
155
  }
137
156
  #[derive(Debug)]
138
157
  struct Array {
158
+ header: container::Header,
139
159
  min: Option<usize>,
140
160
  max: Option<usize>,
141
161
  unique: bool,
@@ -146,10 +166,14 @@ struct Array {
146
166
  contains: Option<usize>,
147
167
  min_contains: usize,
148
168
  max_contains: Option<usize>,
169
+ count_contains: bool,
149
170
  unevaluated: Option<usize>,
150
171
  }
151
172
  #[derive(Debug)]
152
173
  struct StringRules {
174
+ source: Value,
175
+ runtime_flags: i32,
176
+ runtime_format: bool,
153
177
  min: Option<usize>,
154
178
  max: Option<usize>,
155
179
  pattern: Option<pattern::Pattern>,
@@ -196,15 +220,31 @@ impl StringRules {
196
220
  && self.format.is_none()
197
221
  && self.content.is_none()
198
222
  }
199
- fn eval_length_guarded(&self, required: i32, value: Value) -> Verdict {
200
- length_guarded(value, required, self.min, self.max)
223
+ fn eval_length_guarded(
224
+ &self,
225
+ caches: &runtime::Caches,
226
+ required: i32,
227
+ value: Value,
228
+ ) -> Verdict {
229
+ if caches.dynamic_calls() {
230
+ return self.runtime_valid(caches, required, value);
231
+ }
232
+ let result = length_guarded(value, required, self.min, self.max);
233
+ if result == Unsupported {
234
+ self.runtime_valid(caches, required, value)
235
+ } else {
236
+ result
237
+ }
201
238
  }
202
- fn eval_guarded(&self, caches: &pattern::Caches, required: i32, value: Value) -> Verdict {
239
+ fn eval_guarded(&self, caches: &runtime::Caches, required: i32, value: Value) -> Verdict {
240
+ if caches.dynamic_calls() {
241
+ return self.runtime_valid(caches, required, value);
242
+ }
203
243
  let mut data = std::ptr::null();
204
244
  let mut len = 0;
205
245
  let encoding = unsafe { sn_pattern_item_view(value, &mut data, &mut len) };
206
246
  if encoding == -TYPE_UNSUPPORTED {
207
- Unsupported
247
+ self.runtime_valid(caches, required, value)
208
248
  } else if encoding < 0 {
209
249
  verdict(required == 0)
210
250
  } else {
@@ -212,12 +252,12 @@ impl StringRules {
212
252
  self.eval_with_view::<true>(caches, value, Some((bytes, encoding)))
213
253
  }
214
254
  }
215
- fn eval(&self, caches: &pattern::Caches, value: Value) -> Verdict {
255
+ fn eval(&self, caches: &runtime::Caches, value: Value) -> Verdict {
216
256
  self.eval_with_view::<false>(caches, value, None)
217
257
  }
218
258
  fn eval_with_view<const BORROWED: bool>(
219
259
  &self,
220
- caches: &pattern::Caches,
260
+ caches: &runtime::Caches,
221
261
  value: Value,
222
262
  view: Option<(&[u8], i32)>,
223
263
  ) -> Verdict {
@@ -231,8 +271,8 @@ impl StringRules {
231
271
  }
232
272
  if let Some(pattern) = &self.pattern {
233
273
  let result = match view {
234
- Some((bytes, encoding)) => pattern.eval_bytes(caches, bytes, encoding),
235
- None => pattern.eval(caches, value),
274
+ Some((bytes, encoding)) => pattern.eval_bytes(caches, value, bytes, encoding),
275
+ None => pattern.assertion(caches, value),
236
276
  };
237
277
  if result != Valid {
238
278
  return result;
@@ -248,16 +288,41 @@ impl StringRules {
248
288
  }
249
289
  }
250
290
  self.content.as_ref().map_or(Valid, |content| match view {
251
- Some((bytes, encoding)) => content.eval_borrowed(value, bytes, encoding),
252
- None => content.eval(value),
291
+ Some((bytes, encoding)) => content.eval_borrowed(caches, value, bytes, encoding),
292
+ None => content.eval(caches, value),
253
293
  })
254
294
  }
255
295
  }
256
296
  #[derive(Debug)]
257
297
  enum DiagnosticDependency {
258
- Required(Value, Vec<Value>, &'static str),
298
+ Required(Value, Vec<RequiredKey>, &'static str),
259
299
  Schema(Value, usize, &'static str),
260
300
  }
301
+ // Keep ordinary String lookups native; other operands may dispatch Ruby hash
302
+ // methods. Retain the original value for runtime lookup and ordered rendering.
303
+ #[derive(Clone, Copy, Debug, PartialEq, Eq)]
304
+ struct RequiredKey {
305
+ name: Value,
306
+ ordinary_string: bool,
307
+ }
308
+ impl RequiredKey {
309
+ fn missing(
310
+ self,
311
+ caches: &runtime::Caches,
312
+ value: Value,
313
+ undefined: Value,
314
+ ) -> Result<bool, Verdict> {
315
+ if self.ordinary_string {
316
+ Ok(missing(value, self.name, undefined))
317
+ } else {
318
+ match caches.required_missing(self.name, value) {
319
+ Valid => Ok(true),
320
+ Invalid => Ok(false),
321
+ Unsupported => Err(Unsupported),
322
+ }
323
+ }
324
+ }
325
+ }
261
326
  #[derive(Clone, Copy, Debug)]
262
327
  struct PropertyTarget {
263
328
  name: Value,
@@ -266,11 +331,13 @@ struct PropertyTarget {
266
331
  }
267
332
  #[derive(Debug)]
268
333
  struct Object {
334
+ header: container::Header,
335
+ inspect_required: bool,
269
336
  record: Option<RecordObject>,
270
337
  min: Option<usize>,
271
338
  max: Option<usize>,
272
- required: Vec<Value>,
273
- required_order: Vec<Value>,
339
+ required: Vec<RequiredKey>,
340
+ required_order: Vec<RequiredKey>,
274
341
  properties: Vec<(Value, usize)>,
275
342
  property_targets: HashMap<tracking::JsonKey, PropertyTarget, BuildHasherDefault<BucketHasher>>,
276
343
  patterns: Vec<(pattern::Pattern, usize)>,
@@ -282,7 +349,7 @@ struct Object {
282
349
  property_indices: Vec<usize>,
283
350
  additional: Option<usize>,
284
351
  names: Option<usize>,
285
- dependent_required: Vec<(Value, Vec<Value>)>,
352
+ dependent_required: Vec<(Value, Vec<RequiredKey>)>,
286
353
  dependent_schemas: Vec<(Value, usize)>,
287
354
  diagnostic_dependencies: Vec<DiagnosticDependency>,
288
355
  unevaluated: Option<usize>,
@@ -351,13 +418,18 @@ impl RecordShortIndex {
351
418
  impl Object {
352
419
  // Keep these calls at their original evaluation positions: property
353
420
  // failures and unsupported children must retain their ordering.
354
- #[inline]
355
- fn check_required(&self, value: Value, undefined: Value, budget: &budget::Budget) -> Verdict {
356
- Self::check_keys(&self.required, value, undefined, budget)
421
+ fn check_required(
422
+ &self,
423
+ caches: &runtime::Caches,
424
+ value: Value,
425
+ undefined: Value,
426
+ budget: &budget::Budget,
427
+ ) -> Verdict {
428
+ Self::check_keys(&self.required, caches, value, undefined, budget)
357
429
  }
358
- #[inline]
359
430
  fn check_keys(
360
- keys: &[Value],
431
+ keys: &[RequiredKey],
432
+ caches: &runtime::Caches,
361
433
  value: Value,
362
434
  undefined: Value,
363
435
  budget: &budget::Budget,
@@ -366,15 +438,17 @@ impl Object {
366
438
  if !budget.checkpoint() {
367
439
  return Unsupported;
368
440
  }
369
- if missing(value, key, undefined) {
370
- return Invalid;
441
+ match key.missing(caches, value, undefined) {
442
+ Ok(false) => {}
443
+ Ok(true) => return Invalid,
444
+ Err(status) => return status,
371
445
  }
372
446
  }
373
447
  Valid
374
448
  }
375
- #[inline]
376
449
  fn check_dependent_required(
377
450
  &self,
451
+ caches: &runtime::Caches,
378
452
  value: Value,
379
453
  undefined: Value,
380
454
  budget: &budget::Budget,
@@ -384,7 +458,7 @@ impl Object {
384
458
  return Unsupported;
385
459
  }
386
460
  if !missing(value, *name, undefined) {
387
- let status = Self::check_keys(required, value, undefined, budget);
461
+ let status = Self::check_keys(required, caches, value, undefined, budget);
388
462
  if status != Valid {
389
463
  return status;
390
464
  }
@@ -481,10 +555,11 @@ impl Object {
481
555
  // All execution policies use the same property/pattern/additional routing.
482
556
  // Schema-order validity scans have already visited declared properties;
483
557
  // input-order diagnostic scans visit them here as well.
484
- fn visit_value_rules<const PROPERTIES: bool>(
558
+ fn visit_value_rules(
485
559
  &self,
486
- caches: &pattern::Caches,
560
+ caches: &runtime::Caches,
487
561
  budget: &budget::Budget,
562
+ properties: bool,
488
563
  key: Value,
489
564
  location: Option<tracking::JsonKey>,
490
565
  mut visit: impl FnMut(&'static str, Option<Value>, usize) -> Result<(), Verdict>,
@@ -493,7 +568,7 @@ impl Object {
493
568
  // Reuse that result, and skip hashing for pattern-only object rules.
494
569
  let mut matched = if self.properties.is_empty() {
495
570
  false
496
- } else if PROPERTIES {
571
+ } else if properties {
497
572
  if let Some(target) = self
498
573
  .property_targets
499
574
  .get(&location.unwrap_or_else(|| tracking::JsonKey::new(key)))
@@ -548,6 +623,7 @@ impl Object {
548
623
 
549
624
  fn with_record(mut self) -> Self {
550
625
  if self.required_order.len() > 64
626
+ || self.required_order.iter().any(|key| !key.ordinary_string)
551
627
  || !self.patterns.is_empty()
552
628
  || self.names.is_some()
553
629
  || !self.diagnostic_dependencies.is_empty()
@@ -572,7 +648,8 @@ impl Object {
572
648
  )
573
649
  })
574
650
  .collect();
575
- for &name in &self.required_order {
651
+ for key in &self.required_order {
652
+ let name = key.name;
576
653
  fields
577
654
  .entry(tracking::JsonKey::new(name))
578
655
  .or_insert(RecordField {
@@ -592,8 +669,8 @@ impl Object {
592
669
  }
593
670
  // Required names share the field bits already used for duplicate-key
594
671
  // detection. Repeated required names therefore need no extra state.
595
- let required = self.required_order.iter().fold(0, |bits, &name| {
596
- bits | fields[&tracking::JsonKey::new(name)].bit
672
+ let required = self.required_order.iter().fold(0, |bits, key| {
673
+ bits | fields[&tracking::JsonKey::new(key.name)].bit
597
674
  });
598
675
  let fields: Vec<_> = fields.into_values().collect();
599
676
  let short_index = RecordShortIndex::compile(&fields);
@@ -614,9 +691,15 @@ pub struct Roots {
614
691
  #[derive(Default)]
615
692
  pub struct Engine {
616
693
  nodes: Vec<Vec<Op>>,
694
+ programs: Vec<Value>,
617
695
  pattern_cache_count: usize,
618
696
  error_operands: Vec<Vec<(i32, Value)>>,
619
697
  tracks: Vec<bool>,
698
+ kind_demand: Vec<KindDemand>,
699
+ callback_roots: Vec<bool>,
700
+ validity_supported: Vec<bool>,
701
+ report_supported: Vec<bool>,
702
+ has_references: bool,
620
703
  memoizable: Vec<bool>,
621
704
  memo_contextual: Vec<bool>,
622
705
  memo_guarded: Vec<bool>,
@@ -637,6 +720,7 @@ pub struct Engine {
637
720
  root: usize,
638
721
  fixnum_flag: Value,
639
722
  compiler: Value,
723
+ runtime_messages: bool,
640
724
  validate_format: bool,
641
725
  validate_content: bool,
642
726
  undefined: Value,
@@ -656,6 +740,24 @@ impl Engine {
656
740
  }
657
741
  Ok(self.retain(result))
658
742
  }
743
+ fn required_keys(&mut self, value: Value) -> Vec<RequiredKey> {
744
+ // Non-array operands retain their Ruby iteration/error behavior in a
745
+ // runtime frame; they cannot authorize borrowed Array storage.
746
+ if unsafe { sn_kind(value) } != TYPE_ARRAY {
747
+ return vec![];
748
+ }
749
+ self.values(value)
750
+ .into_iter()
751
+ .map(|name| {
752
+ let ordinary_string = unsafe { sn_kind(name) } == TYPE_STRING;
753
+ self.runtime_messages |= !ordinary_string;
754
+ RequiredKey {
755
+ name,
756
+ ordinary_string,
757
+ }
758
+ })
759
+ .collect()
760
+ }
659
761
  fn values(&mut self, value: Value) -> Vec<Value> {
660
762
  if unsafe { sn_same(value, sn_nil()) } != 0 {
661
763
  return vec![];
@@ -759,7 +861,7 @@ impl Engine {
759
861
  // shape-independent leaves can ignore resource/path metadata.
760
862
  ka == kb
761
863
  && match (self.nodes[ca].as_slice(), self.nodes[cb].as_slice()) {
762
- ([Op::Type(a)], [Op::Type(b)]) => a == b,
864
+ ([Op::Type(a, ua)], [Op::Type(b, ub)]) => a == b && ua == ub,
763
865
  ([Op::Boolean(a)], [Op::Boolean(b)]) => a == b,
764
866
  _ => false,
765
867
  }
@@ -779,8 +881,10 @@ impl Engine {
779
881
  let index = self.nodes.len();
780
882
  self.indices.insert(program, index);
781
883
  self.nodes.push(vec![]);
884
+ self.programs.push(program);
782
885
  self.error_operands.push(vec![]);
783
886
  self.tracks.push(false);
887
+ self.kind_demand.push(KindDemand::None);
784
888
  self.metadata.push(scope::Metadata::default());
785
889
  let code = self.field(program, b"code\0")?;
786
890
  let flags = unsafe { sn_integer(sn_entry(code, 0)) };
@@ -794,19 +898,20 @@ impl Engine {
794
898
  error_operands.push((opcode, operand));
795
899
  let op = match opcode {
796
900
  OP_BOOLEAN => Op::Boolean(unsafe { sn_true(operand) } != 0),
797
- OP_TYPE_NULL => Op::Type(TYPE_NULL),
798
- OP_TYPE_BOOLEAN => Op::Type(TYPE_BOOLEAN),
799
- OP_TYPE_OBJECT => Op::Type(TYPE_OBJECT),
800
- OP_TYPE_ARRAY => Op::Type(TYPE_ARRAY),
801
- OP_TYPE_NUMBER => Op::Type(TYPE_NUMBER),
802
- OP_TYPE_INTEGER => Op::Type(TYPE_INTEGER),
803
- OP_TYPE_STRING => Op::Type(TYPE_STRING),
901
+ OP_TYPE_NULL => Op::Type(TYPE_NULL, false),
902
+ OP_TYPE_BOOLEAN => Op::Type(TYPE_BOOLEAN, false),
903
+ OP_TYPE_OBJECT => Op::Type(TYPE_OBJECT, false),
904
+ OP_TYPE_ARRAY => Op::Type(TYPE_ARRAY, false),
905
+ OP_TYPE_NUMBER => Op::Type(TYPE_NUMBER, false),
906
+ OP_TYPE_INTEGER => Op::Type(TYPE_INTEGER, false),
907
+ OP_TYPE_STRING => Op::Type(TYPE_STRING, false),
804
908
  OP_TYPES => {
805
909
  let mask = self.field(operand, b"mask\0")?;
806
- Op::Type(unsafe { sn_integer(mask) } as i32)
910
+ Op::Type(unsafe { sn_integer(mask) } as i32, true)
807
911
  }
808
912
  OP_CONST => Op::Const(operand),
809
913
  OP_ENUM => Op::Enum(Box::new(enumeration::Enumeration::compile(
914
+ operand,
810
915
  self.values(operand),
811
916
  self.fixnum_flag,
812
917
  ))),
@@ -855,9 +960,10 @@ impl Engine {
855
960
  None
856
961
  };
857
962
  let assertion = self.field(operand, b"format_assertion\0")?;
858
- let format = if (self.validate_format || unsafe { sn_true(assertion) } != 0)
859
- && unsafe { sn_same(format, sn_nil()) } == 0
860
- {
963
+ let runtime_format = (self.validate_format
964
+ || unsafe { sn_true(assertion) } != 0)
965
+ && unsafe { sn_same(format, sn_nil()) } == 0;
966
+ let format = if runtime_format {
861
967
  let name = self.field(format, b"name\0")?;
862
968
  if unsafe { sn_same(name, sn_nil()) } != 0 {
863
969
  None
@@ -867,6 +973,10 @@ impl Engine {
867
973
  } else {
868
974
  None
869
975
  };
976
+ let max_length = self.field(operand, b"max_length\0")?;
977
+ let min_length = self.field(operand, b"min_length\0")?;
978
+ let length_flags = (unsafe { sn_truthy(max_length) } << 2)
979
+ | (unsafe { sn_truthy(min_length) } << 3);
870
980
  Op::String(
871
981
  if opcode == OP_TYPED_STRING {
872
982
  TYPE_STRING
@@ -874,11 +984,16 @@ impl Engine {
874
984
  0
875
985
  },
876
986
  Box::new(StringRules {
987
+ source: operand,
988
+ runtime_flags: i32::from(self.validate_format)
989
+ | (i32::from(self.validate_content) << 1)
990
+ | length_flags,
991
+ runtime_format,
877
992
  format,
878
993
  content,
879
994
  min: self.size_field(operand, b"min_length\0")?,
880
995
  max: self.size_field(operand, b"max_length\0")?,
881
- pattern: if unsafe { sn_same(pattern, sn_nil()) } != 0 {
996
+ pattern: if unsafe { sn_truthy(pattern) } == 0 {
882
997
  None
883
998
  } else {
884
999
  Some(pattern::Pattern::compile(
@@ -911,7 +1026,18 @@ impl Engine {
911
1026
  0
912
1027
  },
913
1028
  Box::new(Array {
914
- unique: unsafe { sn_true(unique) } != 0,
1029
+ header: self.container_header(
1030
+ operand,
1031
+ b"max_items\0",
1032
+ b"min_items\0",
1033
+ false,
1034
+ (if unsafe { sn_truthy(unique) } != 0 {
1035
+ 4
1036
+ } else {
1037
+ 0
1038
+ }) | (if list { 8 } else { 0 }),
1039
+ )?,
1040
+ unique: unsafe { sn_truthy(unique) } != 0,
915
1041
  min: self.size_field(operand, b"min_items\0")?,
916
1042
  max: self.size_field(operand, b"max_items\0")?,
917
1043
  prefix,
@@ -921,6 +1047,9 @@ impl Engine {
921
1047
  contains: self.child(operand, b"contains\0")?,
922
1048
  min_contains: self.size_field(operand, b"min_contains\0")?.unwrap_or(1),
923
1049
  max_contains: self.size_field(operand, b"max_contains\0")?,
1050
+ count_contains: unsafe {
1051
+ sn_true(self.field(operand, b"count_contains\0")?)
1052
+ } != 0,
924
1053
  unevaluated: self.child(operand, b"unevaluated\0")?,
925
1054
  }),
926
1055
  )
@@ -943,14 +1072,21 @@ impl Engine {
943
1072
  let pattern_group =
944
1073
  pattern::PatternGroup::compile(&pattern_names, &patterns);
945
1074
  let required = self.field(operand, b"required\0")?;
946
- let required = self.values(required);
1075
+ let inspect_required = unsafe { sn_truthy(required) } != 0
1076
+ && !(unsafe { sn_kind(required) } == TYPE_ARRAY
1077
+ && unsafe { sn_len(required) } == 0);
1078
+ let required = self.required_keys(required);
947
1079
  let required_order = required.clone();
1080
+ let required_strings: Vec<_> = required
1081
+ .iter()
1082
+ .filter(|key| key.ordinary_string)
1083
+ .map(|key| key.name)
1084
+ .collect();
948
1085
  let properties = self.field(operand, b"properties\0")?;
949
1086
  let properties = self.compile_map(properties)?;
950
- if required
1087
+ if properties
951
1088
  .iter()
952
- .chain(properties.iter().map(|(key, _)| key))
953
- .any(|&key| unsafe { sn_kind(key) } != TYPE_STRING)
1089
+ .any(|&(key, _)| unsafe { sn_kind(key) } != TYPE_STRING)
954
1090
  {
955
1091
  ops.push(Op::Unsupported);
956
1092
  continue;
@@ -982,7 +1118,7 @@ impl Engine {
982
1118
  // content-keyed sets and the already compiled property index.
983
1119
  let indexed_required = properties.len().saturating_mul(required.len()) > 64;
984
1120
  let required_keys: Option<tracking::KeySet> = indexed_required.then(|| {
985
- required
1121
+ required_strings
986
1122
  .iter()
987
1123
  .map(|&name| tracking::JsonKey::new(name))
988
1124
  .collect()
@@ -992,7 +1128,7 @@ impl Engine {
992
1128
  .map(|key| {
993
1129
  required_keys.as_ref().map_or_else(
994
1130
  || {
995
- required.iter().any(
1131
+ required_strings.iter().any(
996
1132
  |&name| unsafe { sn_string_equal(key.0, name) } != 0,
997
1133
  )
998
1134
  },
@@ -1007,7 +1143,11 @@ impl Engine {
1007
1143
  .collect();
1008
1144
  let required = required
1009
1145
  .into_iter()
1010
- .filter(|&name| {
1146
+ .filter(|key| {
1147
+ if !key.ordinary_string {
1148
+ return true;
1149
+ }
1150
+ let name = key.name;
1011
1151
  if indexed_required {
1012
1152
  !property_targets.contains_key(&tracking::JsonKey::new(name))
1013
1153
  } else {
@@ -1025,7 +1165,7 @@ impl Engine {
1025
1165
  let name = self.retain(unsafe { sn_entry(pair, 0) });
1026
1166
  let dependency = self.retain(unsafe { sn_entry(pair, 1) });
1027
1167
  if unsafe { sn_kind(dependency) } == TYPE_ARRAY {
1028
- let names = self.values(dependency);
1168
+ let names = self.required_keys(dependency);
1029
1169
  diagnostic_dependencies.push(DiagnosticDependency::Required(
1030
1170
  name,
1031
1171
  names.clone(),
@@ -1044,7 +1184,7 @@ impl Engine {
1044
1184
  }
1045
1185
  for pair in self.values(dependent) {
1046
1186
  let name = self.retain(unsafe { sn_entry(pair, 0) });
1047
- let names = self.values(unsafe { sn_entry(pair, 1) });
1187
+ let names = self.required_keys(unsafe { sn_entry(pair, 1) });
1048
1188
  diagnostic_dependencies.push(DiagnosticDependency::Required(
1049
1189
  name,
1050
1190
  names.clone(),
@@ -1058,19 +1198,25 @@ impl Engine {
1058
1198
  DiagnosticDependency::Schema(name, child, "dependentSchemas")
1059
1199
  }));
1060
1200
  dependent_schemas.extend(schemas);
1061
- if dependent_required.iter().any(|(name, keys)| {
1062
- (unsafe { sn_kind(*name) }) != TYPE_STRING
1063
- || keys
1064
- .iter()
1065
- .any(|&key| unsafe { sn_kind(key) } != TYPE_STRING)
1066
- }) || dependent_schemas
1201
+ if dependent_required
1067
1202
  .iter()
1068
- .any(|&(key, _)| unsafe { sn_kind(key) } != TYPE_STRING)
1203
+ .any(|(name, _)| unsafe { sn_kind(*name) } != TYPE_STRING)
1204
+ || dependent_schemas
1205
+ .iter()
1206
+ .any(|&(key, _)| unsafe { sn_kind(key) } != TYPE_STRING)
1069
1207
  {
1070
1208
  ops.push(Op::Unsupported);
1071
1209
  continue;
1072
1210
  }
1073
1211
  let mut object = Object {
1212
+ header: self.container_header(
1213
+ operand,
1214
+ b"max_properties\0",
1215
+ b"min_properties\0",
1216
+ true,
1217
+ 0,
1218
+ )?,
1219
+ inspect_required,
1074
1220
  record: None,
1075
1221
  min: self.size_field(operand, b"min_properties\0")?,
1076
1222
  max: self.size_field(operand, b"max_properties\0")?,
@@ -1147,7 +1293,8 @@ impl Engine {
1147
1293
  let mut target = 0;
1148
1294
  if unsafe { sn_resolve(self.compiler, program, reference, &mut target) } != 0 {
1149
1295
  if unsafe { sn_resolution_error(target) } != 0 {
1150
- Op::Unsupported
1296
+ let message = self.field(target, b"message\0")?;
1297
+ Op::UnresolvedRef(message)
1151
1298
  } else {
1152
1299
  return Err(target);
1153
1300
  }
@@ -1165,12 +1312,53 @@ impl Engine {
1165
1312
  self.error_operands[index] = error_operands;
1166
1313
  Ok(index)
1167
1314
  }
1315
+ fn compile_kind_demands(&mut self) {
1316
+ self.kind_demand = self
1317
+ .nodes
1318
+ .iter()
1319
+ .map(|ops| self.kind_demand_for(ops))
1320
+ .collect();
1321
+ }
1322
+ fn kind_demand_for(&self, ops: &[Op]) -> KindDemand {
1323
+ ops.iter()
1324
+ .map(|op| match op {
1325
+ Op::Type(..) | Op::Number(..) | Op::String(..) | Op::Const(_) | Op::Enum(_) => {
1326
+ KindDemand::Predicate
1327
+ }
1328
+ Op::Array(_, rules)
1329
+ if rules.header_only()
1330
+ || self.array_callbacks(rules)
1331
+ || self.array_report_callbacks(rules) =>
1332
+ {
1333
+ KindDemand::Predicate
1334
+ }
1335
+ Op::Object(_, rules)
1336
+ if rules.header_only()
1337
+ || self.object_callbacks(rules)
1338
+ || self.object_report_callbacks(rules) =>
1339
+ {
1340
+ KindDemand::Predicate
1341
+ }
1342
+ Op::Array(..) | Op::Object(..) => KindDemand::Data,
1343
+ Op::Unsupported
1344
+ | Op::Boolean(_)
1345
+ | Op::UnresolvedRef(_)
1346
+ | Op::Ref(_)
1347
+ | Op::All(_)
1348
+ | Op::Any(_)
1349
+ | Op::One(_)
1350
+ | Op::Not(_)
1351
+ | Op::Conditional(..) => KindDemand::None,
1352
+ })
1353
+ .max()
1354
+ .unwrap_or(KindDemand::None)
1355
+ }
1168
1356
  }
1169
1357
 
1170
- // A view of immutable compiled data and validator-owned regex search caches.
1358
+ // A view of immutable compiled data and validator-owned runtime caches.
1171
1359
  struct Execution<'a> {
1172
1360
  engine: &'a Engine,
1173
- caches: &'a pattern::Caches,
1361
+ caches: &'a runtime::Caches,
1174
1362
  memo: memo::Memo,
1175
1363
  budget: budget::Budget,
1176
1364
  }
@@ -1192,9 +1380,14 @@ impl Execution<'_> {
1192
1380
  value: Value,
1193
1381
  depth: usize,
1194
1382
  active: scope::Context<'_>,
1195
- known_kind: Option<i32>,
1383
+ known_kind: Option<&InstanceKind>,
1196
1384
  ) -> Verdict {
1197
- if !self.budget.checkpoint() {
1385
+ // Suspend before entering the next node. C runs a fresh native stack
1386
+ // only after these frames and their borrowed views have unwound.
1387
+ if depth >= MAX_EVALUATION_DEPTH && self.caches.continuations_allowed() {
1388
+ return self.caches.native_child(node, value);
1389
+ }
1390
+ if self.caches.callback_checkpoint(value) != Valid || !self.budget.checkpoint() {
1198
1391
  return Unsupported;
1199
1392
  }
1200
1393
  let Some(key) = self
@@ -1222,7 +1415,7 @@ impl Execution<'_> {
1222
1415
  value: Value,
1223
1416
  depth: usize,
1224
1417
  active: scope::Context<'_>,
1225
- known_kind: Option<i32>,
1418
+ known_kind: Option<&InstanceKind>,
1226
1419
  ) -> Verdict {
1227
1420
  let frame = scope::ScopeFrame {
1228
1421
  resource: self.metadata[node].resource,
@@ -1233,7 +1426,13 @@ impl Execution<'_> {
1233
1426
  .or(self.metadata[node].primary_binding),
1234
1427
  };
1235
1428
  let active = self.enter_scope(node, active, &frame);
1429
+ let _scope = self
1430
+ .caches
1431
+ .scope_guard(frame.resource, self.metadata[node].enters_scope);
1236
1432
  if self.tracks[node] {
1433
+ if let Some(result) = self.caches.callback_tracked(node, value) {
1434
+ return result;
1435
+ }
1237
1436
  return match known_kind {
1238
1437
  Some(kind) => {
1239
1438
  self.eval_tracked_kind(node, value, depth, active, kind)
@@ -1247,26 +1446,42 @@ impl Execution<'_> {
1247
1446
  }
1248
1447
  if let [Op::String(required, rules)] = self.nodes[node].as_slice()
1249
1448
  && rules.pattern.is_some()
1449
+ && !self.caches.scalar_callback()
1250
1450
  {
1251
1451
  return rules.eval_guarded(self.caches, *required, value);
1252
1452
  }
1253
- let kind = known_kind.unwrap_or_else(|| {
1254
- if value & self.fixnum_flag != 0 {
1255
- TYPE_INTEGRAL_NUMBER
1256
- } else {
1257
- self.instance_kind(node, value)
1258
- }
1259
- });
1260
- if kind == TYPE_UNSUPPORTED {
1453
+ let local_kind = InstanceKind::new(TYPE_UNCHECKED);
1454
+ let kind_state = known_kind.unwrap_or(&local_kind);
1455
+ let kind = if known_kind.is_some() {
1456
+ // A proof shared with sibling branches must guard every Hash key.
1457
+ self.diagnostic_kind(node, value, kind_state)
1458
+ } else {
1459
+ let kind = self.instance_kind(node, value);
1460
+ kind_state.set(kind);
1461
+ kind
1462
+ };
1463
+ if self.unsupported_kind(node, kind) {
1261
1464
  return Unsupported;
1262
1465
  }
1263
1466
  for op in &self.nodes[node] {
1467
+ // Runtime observations checkpoint each reached opcode once, after
1468
+ // earlier assertions and descendant calls have completed.
1469
+ if self.caches.callback_checkpoint(value) != Valid {
1470
+ return Unsupported;
1471
+ }
1264
1472
  if !self.budget.checkpoint() {
1265
1473
  return Unsupported;
1266
1474
  }
1267
1475
  let outcome = match op {
1268
1476
  Op::Ref(target) => {
1269
- if RefFrame::contains(active.active, node, value) {
1477
+ let target = match self.resolve_reference(*target, value, active) {
1478
+ Ok(target) => target,
1479
+ Err(status) => return status,
1480
+ };
1481
+ let program = self.programs[node];
1482
+ if RefFrame::contains(active.active, node, value)
1483
+ || self.caches.reference_active(program, value)
1484
+ {
1270
1485
  Valid
1271
1486
  } else {
1272
1487
  let frame = RefFrame {
@@ -1274,15 +1489,17 @@ impl Execution<'_> {
1274
1489
  value,
1275
1490
  parent: active.active,
1276
1491
  };
1492
+ let reference = self.caches.reference_frame(program, value);
1493
+ let _guard = self.caches.reference_guard(&reference);
1277
1494
  self.eval_with_kind(
1278
- self.reference_target(*target, active),
1495
+ target,
1279
1496
  value,
1280
1497
  depth + 1,
1281
1498
  scope::Context {
1282
1499
  active: Some(&frame),
1283
1500
  ..active
1284
1501
  },
1285
- Some(kind),
1502
+ Some(kind_state),
1286
1503
  )
1287
1504
  }
1288
1505
  }
@@ -1292,7 +1509,8 @@ impl Execution<'_> {
1292
1509
  if !self.budget.checkpoint() {
1293
1510
  return Unsupported;
1294
1511
  }
1295
- outcome = self.eval_with_kind(n, value, depth + 1, active, Some(kind));
1512
+ outcome =
1513
+ self.eval_with_kind(n, value, depth + 1, active, Some(kind_state));
1296
1514
  if outcome != Valid {
1297
1515
  break;
1298
1516
  }
@@ -1308,30 +1526,61 @@ impl Execution<'_> {
1308
1526
  match evaluation::scan_branches(
1309
1527
  nodes,
1310
1528
  evaluation::BranchPolicy::Validity(branch_kind),
1311
- |child| self.eval_with_kind(child, value, depth + 1, active, Some(kind)),
1529
+ self.caches.dynamic_calls(),
1530
+ |child| {
1531
+ if matches!(op, Op::One(_))
1532
+ && self.caches.callback_checkpoint(value) != Valid
1533
+ {
1534
+ return Unsupported;
1535
+ }
1536
+ self.eval_with_kind(child, value, depth + 1, active, Some(kind_state))
1537
+ },
1538
+ |_, _| Ok(()),
1312
1539
  ) {
1313
1540
  Ok(result) => verdict(branch_kind.accepts(result.matches)),
1314
1541
  Err(status) => status,
1315
1542
  }
1316
1543
  }
1317
- Op::Not(n) => match self.eval_with_kind(*n, value, depth + 1, active, Some(kind)) {
1318
- Valid => Invalid,
1319
- Invalid => Valid,
1320
- Unsupported => Unsupported,
1321
- },
1544
+ Op::Not(n) => {
1545
+ match self.eval_with_kind(*n, value, depth + 1, active, Some(kind_state)) {
1546
+ Valid => Invalid,
1547
+ Invalid => Valid,
1548
+ Unsupported => Unsupported,
1549
+ }
1550
+ }
1322
1551
  Op::Conditional(c, yes, no) => {
1323
- match self.eval_with_kind(*c, value, depth + 1, active, Some(kind)) {
1552
+ match self.eval_with_kind(*c, value, depth + 1, active, Some(kind_state)) {
1324
1553
  Unsupported => Unsupported,
1325
1554
  result => match if result == Valid { yes } else { no } {
1326
1555
  Some(n) => {
1327
- self.eval_with_kind(*n, value, depth + 1, active, Some(kind))
1556
+ self.eval_with_kind(*n, value, depth + 1, active, Some(kind_state))
1328
1557
  }
1329
1558
  None => Valid,
1330
1559
  },
1331
1560
  }
1332
1561
  }
1333
1562
  Op::Array(required, rules) => {
1334
- if *required != 0 && kind != TYPE_ARRAY {
1563
+ if rules.header_only() && rules.header.runtime_required(self.caches, kind) {
1564
+ rules.header.valid(self.caches, *required, value)
1565
+ } else if self.caches.callbacks_allowed()
1566
+ && rules.header.runtime_required(self.caches, kind)
1567
+ && self.scalar_contains(rules)
1568
+ {
1569
+ rules.header.contains(
1570
+ self.caches,
1571
+ *required,
1572
+ value,
1573
+ rules.contains.expect("scalar contains child"),
1574
+ self.scalar_callback_kind(
1575
+ rules.contains.expect("scalar contains child"),
1576
+ ),
1577
+ )
1578
+ } else if self.caches.callbacks_allowed()
1579
+ && rules.header.runtime_required(self.caches, kind)
1580
+ && self.array_callbacks(rules)
1581
+ {
1582
+ rules.header.children(self.caches, *required, value, node)
1583
+ } else if *required != 0 && kind != TYPE_ARRAY {
1335
1584
  Invalid
1336
1585
  } else if kind != TYPE_ARRAY {
1337
1586
  Valid
@@ -1340,12 +1589,29 @@ impl Execution<'_> {
1340
1589
  }
1341
1590
  }
1342
1591
  Op::Object(required, rules) => {
1343
- if *required != 0 && kind != TYPE_OBJECT {
1592
+ if rules.header_only() && rules.header.runtime_required(self.caches, kind) {
1593
+ rules.header.valid(self.caches, *required, value)
1594
+ } else if self.caches.callbacks_allowed()
1595
+ && rules.header.runtime_required(self.caches, kind)
1596
+ && self.object_callbacks(rules)
1597
+ {
1598
+ rules.header.children(self.caches, *required, value, node)
1599
+ } else if *required != 0 && kind != TYPE_OBJECT {
1344
1600
  Invalid
1345
1601
  } else if kind != TYPE_OBJECT {
1346
1602
  Valid
1603
+ } else if rules.header_only() {
1604
+ rules.valid_header(self.caches, value, self.undefined, &self.budget)
1347
1605
  } else {
1348
- self.object(rules, value, depth + 1, active)
1606
+ let result = self.object(rules, value, depth + 1, active);
1607
+ if self.can_call_runtime(result)
1608
+ && self.caches.callbacks_allowed()
1609
+ && self.object_callbacks(rules)
1610
+ {
1611
+ rules.header.children(self.caches, *required, value, node)
1612
+ } else {
1613
+ result
1614
+ }
1349
1615
  }
1350
1616
  }
1351
1617
  _ => self.leaf(op, kind, value, depth),
@@ -1359,20 +1625,49 @@ impl Execution<'_> {
1359
1625
  fn leaf(&self, op: &Op, kind: i32, value: Value, depth: usize) -> Verdict {
1360
1626
  match op {
1361
1627
  Op::Unsupported => Unsupported,
1628
+ Op::UnresolvedRef(_) => Invalid,
1362
1629
  Op::Boolean(v) => verdict(*v),
1363
- Op::Type(mask) => verdict(kind & mask != 0),
1364
- Op::Const(v) => equal_budgeted(*v, value, depth, &self.budget),
1365
- Op::Enum(values) => values.eval(value, kind, depth, self.fixnum_flag, &self.budget),
1630
+ Op::Type(mask, union) => self.type_verdict(*mask, *union, kind, value),
1631
+ Op::Const(v) => {
1632
+ let result = if kind == TYPE_UNSUPPORTED || self.caches.dynamic_calls() {
1633
+ Unsupported
1634
+ } else {
1635
+ equal_budgeted(*v, value, depth, &self.budget)
1636
+ };
1637
+ if self.can_call_runtime(result) {
1638
+ self.caches.equality(*v, value, RUNTIME_EQUAL)
1639
+ } else {
1640
+ result
1641
+ }
1642
+ }
1643
+ Op::Enum(values) => {
1644
+ let result = if kind == TYPE_UNSUPPORTED || self.caches.dynamic_calls() {
1645
+ Unsupported
1646
+ } else {
1647
+ values.eval(value, kind, depth, self.fixnum_flag, &self.budget)
1648
+ };
1649
+ if self.can_call_runtime(result) {
1650
+ values.runtime(self.caches, value)
1651
+ } else {
1652
+ result
1653
+ }
1654
+ }
1366
1655
  Op::Number(required, rules) => {
1656
+ if kind == TYPE_UNSUPPORTED || self.caches.dynamic_calls() {
1657
+ return rules.runtime_valid(self.caches, *required, value);
1658
+ }
1367
1659
  if *required != 0 && kind & required == 0 {
1368
1660
  Invalid
1369
1661
  } else if kind & TYPE_NUMBER == 0 {
1370
1662
  Valid
1371
1663
  } else {
1372
- rules.eval(value)
1664
+ rules.eval(self.caches, value, &self.budget)
1373
1665
  }
1374
1666
  }
1375
1667
  Op::String(required, rules) => {
1668
+ if kind == TYPE_UNSUPPORTED || self.caches.dynamic_calls() {
1669
+ return rules.runtime_valid(self.caches, *required, value);
1670
+ }
1376
1671
  if *required != 0 && kind != TYPE_STRING {
1377
1672
  Invalid
1378
1673
  } else if kind != TYPE_STRING {
@@ -1384,6 +1679,22 @@ impl Execution<'_> {
1384
1679
  _ => unreachable!("applicators must be evaluated with their context"),
1385
1680
  }
1386
1681
  }
1682
+ fn can_call_runtime(&self, result: Verdict) -> bool {
1683
+ result == Unsupported && self.budget.failure.get() == 0 && self.budget.hit.get().0 == 0
1684
+ }
1685
+ fn unique_values(&self, value: Value, depth: usize) -> Verdict {
1686
+ let result = if self.caches.dynamic_calls() {
1687
+ Unsupported
1688
+ } else {
1689
+ unique(value, depth, self.fixnum_flag, &self.budget)
1690
+ };
1691
+ if self.can_call_runtime(result) {
1692
+ self.caches
1693
+ .equality(unsafe { sn_nil() }, value, RUNTIME_UNIQUE)
1694
+ } else {
1695
+ result
1696
+ }
1697
+ }
1387
1698
  fn array(&self, r: &Array, value: Value, depth: usize, active: scope::Context<'_>) -> Verdict {
1388
1699
  if !self.budget.checkpoint() {
1389
1700
  return Unsupported;
@@ -1393,7 +1704,7 @@ impl Execution<'_> {
1393
1704
  return Invalid;
1394
1705
  }
1395
1706
  if r.unique {
1396
- let result = unique(value, depth, self.fixnum_flag, &self.budget);
1707
+ let result = self.unique_values(value, depth);
1397
1708
  if result != Valid {
1398
1709
  return result;
1399
1710
  }
@@ -1437,15 +1748,18 @@ impl Execution<'_> {
1437
1748
  unsafe { std::slice::from_raw_parts(sn_array_data(value), len) }
1438
1749
  };
1439
1750
  let primitive = match self.nodes[child].as_slice() {
1440
- [Op::Type(mask)] if mask & TYPE_NUMBER != 0 || mask & TYPE_INTEGER != 0 => {
1751
+ [Op::Type(mask, _)] if mask & TYPE_NUMBER != 0 || mask & TYPE_INTEGER != 0 => {
1441
1752
  Some(&[][..])
1442
1753
  }
1443
1754
  [Op::Number(_, rules)] => rules.fast_limits.as_deref(),
1444
1755
  _ => None,
1445
1756
  };
1757
+ let primitive = primitive.filter(|_| !self.caches.dynamic_calls());
1446
1758
  let number = match self.nodes[child].as_slice() {
1447
1759
  [Op::Number(required, rules)]
1448
- if depth <= MAX_EVALUATION_DEPTH && !self.tracks[child] =>
1760
+ if depth <= MAX_EVALUATION_DEPTH
1761
+ && !self.tracks[child]
1762
+ && !self.caches.dynamic_calls() =>
1449
1763
  {
1450
1764
  Some((*required, rules))
1451
1765
  }
@@ -1478,47 +1792,60 @@ impl Execution<'_> {
1478
1792
  .or(self.metadata[child].primary_binding),
1479
1793
  };
1480
1794
  let object_active = self.enter_scope(child, active, &frame);
1795
+ let _scope = self.caches.scope_guard(
1796
+ frame.resource,
1797
+ object.is_some() && self.metadata[child].enters_scope,
1798
+ );
1481
1799
  let pattern = string.and_then(|(required, rules)| {
1482
1800
  rules
1483
1801
  .pattern_only()
1484
1802
  .map(|pattern| (required, pattern))
1485
- .filter(|_| !self.budget.timed())
1803
+ .filter(|_| !self.budget.timed() && !self.caches.dynamic_calls())
1486
1804
  });
1487
1805
  let format = string.and_then(|(required, rules)| {
1488
1806
  rules
1489
1807
  .format_only()
1490
1808
  .map(|format| (required, format))
1491
- .filter(|_| !self.budget.timed())
1809
+ .filter(|_| !self.budget.timed() && !self.caches.dynamic_calls())
1492
1810
  });
1493
1811
  let content = string.and_then(|(required, rules)| {
1494
1812
  rules
1495
1813
  .content_only()
1496
1814
  .map(|content| (required, content))
1497
- .filter(|_| !self.budget.timed())
1815
+ .filter(|_| !self.budget.timed() && !self.caches.dynamic_calls())
1498
1816
  });
1499
1817
  if let Some((required, pattern)) = pattern {
1500
- let result = pattern.eval_items(
1818
+ let result = pattern.visit_invalid_items(
1501
1819
  self.caches,
1502
1820
  required,
1503
1821
  values.get(r.prefix.len()..).unwrap_or(&[]),
1822
+ |_, item| self.eval(child, item, depth, active),
1504
1823
  );
1505
1824
  if result != Valid {
1506
1825
  return result;
1507
1826
  }
1508
1827
  } else if let Some((required, format)) = format {
1509
- let result =
1510
- format.eval_items(required, values.get(r.prefix.len()..).unwrap_or(&[]));
1828
+ let result = format.visit_invalid_items(
1829
+ required,
1830
+ values.get(r.prefix.len()..).unwrap_or(&[]),
1831
+ |_, item| self.eval(child, item, depth, active),
1832
+ );
1511
1833
  if result != Valid {
1512
1834
  return result;
1513
1835
  }
1514
1836
  } else if let Some((required, content)) = content {
1515
- let result =
1516
- content.eval_items(required, values.get(r.prefix.len()..).unwrap_or(&[]));
1837
+ let result = content.visit_invalid_items(
1838
+ self.caches,
1839
+ required,
1840
+ values.get(r.prefix.len()..).unwrap_or(&[]),
1841
+ |_, item| self.eval(child, item, depth, active),
1842
+ );
1517
1843
  if result != Valid {
1518
1844
  return result;
1519
1845
  }
1520
1846
  } else if let Some((required, rules)) = object {
1521
1847
  let result = self.record_items(
1848
+ child,
1522
1849
  rules,
1523
1850
  required,
1524
1851
  values.get(r.prefix.len()..).unwrap_or(&[]),
@@ -1545,16 +1872,21 @@ impl Execution<'_> {
1545
1872
  return Unsupported;
1546
1873
  }
1547
1874
  let result = if let Some((required, rules)) = number {
1548
- rules.eval_guarded(required, item)
1875
+ let status = rules.eval_guarded(self.caches, required, item, &self.budget);
1876
+ if status == Unsupported {
1877
+ self.eval(child, item, depth, active)
1878
+ } else {
1879
+ status
1880
+ }
1549
1881
  } else if let Some((required, rules)) = string {
1550
1882
  if rules.pattern.is_some() {
1551
1883
  rules.eval_guarded(self.caches, required, item)
1552
1884
  } else if rules.length_only() {
1553
- rules.eval_length_guarded(required, item)
1885
+ rules.eval_length_guarded(self.caches, required, item)
1554
1886
  } else {
1555
1887
  let kind = unsafe { sn_kind(item) };
1556
- if kind == TYPE_UNSUPPORTED {
1557
- Unsupported
1888
+ if kind == TYPE_UNSUPPORTED || self.caches.dynamic_calls() {
1889
+ rules.runtime_valid(self.caches, required, item)
1558
1890
  } else if kind != TYPE_STRING {
1559
1891
  verdict(required == 0)
1560
1892
  } else {
@@ -1571,18 +1903,30 @@ impl Execution<'_> {
1571
1903
  }
1572
1904
  }
1573
1905
  if let Some(child) = r.contains {
1574
- if let Some(status) = self.fixnum_contains(r, child, value, len, depth) {
1906
+ if r.count_contains
1907
+ && let Some(status) = self.fixnum_contains(r, child, value, len, depth)
1908
+ {
1575
1909
  return status;
1576
1910
  }
1577
1911
  let matches = match evaluation::scan_contains(
1578
1912
  len,
1913
+ if r.count_contains {
1914
+ evaluation::ContainsPolicy::AllMatches
1915
+ } else {
1916
+ evaluation::ContainsPolicy::FirstMatch
1917
+ },
1579
1918
  |index| self.eval(child, unsafe { sn_entry(value, index) }, depth, active),
1580
1919
  |_| {},
1581
1920
  ) {
1582
1921
  Ok(matches) => matches,
1583
1922
  Err(status) => return status,
1584
1923
  };
1585
- if bounds(matches, Some(r.min_contains), r.max_contains) == Invalid {
1924
+ let (min, max) = if r.count_contains {
1925
+ (r.min_contains, r.max_contains)
1926
+ } else {
1927
+ (1, None)
1928
+ };
1929
+ if bounds(matches, Some(min), max) == Invalid {
1586
1930
  return Invalid;
1587
1931
  }
1588
1932
  }
@@ -1603,7 +1947,7 @@ impl Execution<'_> {
1603
1947
  return None;
1604
1948
  };
1605
1949
  let total = match op {
1606
- Op::Type(_) | Op::Boolean(_) => true,
1950
+ Op::Type(..) | Op::Boolean(_) => true,
1607
1951
  Op::Const(expected) => expected & self.fixnum_flag != 0,
1608
1952
  Op::Number(_, rules) => rules.fast_limits.is_some(),
1609
1953
  _ => false,
@@ -1629,7 +1973,6 @@ impl Execution<'_> {
1629
1973
  |index| self.leaf(op, TYPE_INTEGRAL_NUMBER, values[index], depth) == Valid,
1630
1974
  ))
1631
1975
  }
1632
- #[inline(never)]
1633
1976
  fn integer_items(
1634
1977
  &self,
1635
1978
  limits: &[(i32, i64)],
@@ -1652,13 +1995,20 @@ impl Execution<'_> {
1652
1995
  );
1653
1996
  let number = match self.nodes[child].as_slice() {
1654
1997
  [Op::Number(required, rules)]
1655
- if depth <= MAX_EVALUATION_DEPTH && !self.tracks[child] =>
1998
+ if depth <= MAX_EVALUATION_DEPTH
1999
+ && !self.tracks[child]
2000
+ && !self.caches.dynamic_calls() =>
1656
2001
  {
1657
2002
  Some((*required, rules))
1658
2003
  }
1659
2004
  _ => None,
1660
2005
  };
1661
- for &value in values {
2006
+ let prefix = if self.budget.timed() {
2007
+ 0
2008
+ } else {
2009
+ integer_scan::valid_prefix(values, min, max, self.fixnum_flag)
2010
+ };
2011
+ for &value in &values[prefix..] {
1662
2012
  if !self.budget.checkpoint() {
1663
2013
  return Unsupported;
1664
2014
  }
@@ -1666,7 +2016,12 @@ impl Execution<'_> {
1666
2016
  let n = immediate_integer(value);
1667
2017
  verdict(n >= min && n <= max)
1668
2018
  } else if let Some((required, rules)) = number {
1669
- rules.eval_guarded(required, value)
2019
+ let status = rules.eval_guarded(self.caches, required, value, &self.budget);
2020
+ if status == Unsupported {
2021
+ self.eval(child, value, depth, active)
2022
+ } else {
2023
+ status
2024
+ }
1670
2025
  } else {
1671
2026
  self.eval(child, value, depth, active)
1672
2027
  };
@@ -1676,15 +2031,16 @@ impl Execution<'_> {
1676
2031
  }
1677
2032
  Valid
1678
2033
  }
1679
- #[inline(never)]
1680
2034
  fn record_items(
1681
2035
  &self,
2036
+ node: usize,
1682
2037
  rules: &Object,
1683
2038
  required: i32,
1684
2039
  values: &[Value],
1685
2040
  depth: usize,
1686
2041
  active: scope::Context<'_>,
1687
2042
  ) -> Verdict {
2043
+ let header_only = rules.header_only();
1688
2044
  for &value in values {
1689
2045
  if !self.budget.checkpoint() {
1690
2046
  return Unsupported;
@@ -1694,13 +2050,29 @@ impl Execution<'_> {
1694
2050
  } else {
1695
2051
  unsafe { sn_shallow_kind(value) }
1696
2052
  };
1697
- let result = if kind == TYPE_UNSUPPORTED {
2053
+ let runtime = header_only && rules.header.runtime_required(self.caches, kind);
2054
+ let result = if runtime {
2055
+ rules.header.valid(self.caches, required, value)
2056
+ } else if kind == TYPE_UNSUPPORTED {
1698
2057
  Unsupported
1699
2058
  } else if kind != TYPE_OBJECT {
1700
2059
  verdict(required == 0)
1701
2060
  } else {
1702
2061
  self.object(rules, value, depth, active)
1703
2062
  };
2063
+ let result = if self.can_call_runtime(result)
2064
+ && self.caches.callbacks_allowed()
2065
+ && self.object_callbacks(rules)
2066
+ {
2067
+ rules.header.children(self.caches, required, value, node)
2068
+ } else if header_only && !runtime && self.can_call_runtime(result) {
2069
+ // A declined record guard has not invoked instance methods.
2070
+ // Preserve the fast record walk while handing special values
2071
+ // to the same runtime frame as general header evaluation.
2072
+ rules.header.valid(self.caches, required, value)
2073
+ } else {
2074
+ result
2075
+ };
1704
2076
  if result != Valid {
1705
2077
  return result;
1706
2078
  }
@@ -1717,7 +2089,9 @@ impl Execution<'_> {
1717
2089
  if !self.budget.checkpoint() {
1718
2090
  return Unsupported;
1719
2091
  }
1720
- if let Some(record) = &r.record {
2092
+ if !self.caches.runtime_order()
2093
+ && let Some(record) = &r.record
2094
+ {
1721
2095
  // C initializes only the written key/value pairs. The untouched
1722
2096
  // tail is never read, including empty and unsupported records.
1723
2097
  let mut buffer = std::mem::MaybeUninit::<[Value; 16]>::uninit();
@@ -1799,26 +2173,36 @@ impl Execution<'_> {
1799
2173
  if bounds(len, r.min, r.max) == Invalid {
1800
2174
  return Invalid;
1801
2175
  }
1802
- let status = r.check_required(value, self.undefined, &self.budget);
2176
+ let status = r.check_required(self.caches, value, self.undefined, &self.budget);
1803
2177
  if status != Valid {
1804
2178
  return status;
1805
2179
  }
1806
- let found = match r.visit_properties(
1807
- value,
1808
- len,
1809
- self.undefined,
1810
- &self.budget,
1811
- |_, child, item| self.eval(child, item, depth, active),
1812
- ) {
1813
- Ok(found) => found,
1814
- Err(status) => return status,
2180
+ let ordered = self.caches.runtime_order();
2181
+ let found = if ordered {
2182
+ 0
2183
+ } else {
2184
+ match r.visit_properties(
2185
+ value,
2186
+ len,
2187
+ self.undefined,
2188
+ &self.budget,
2189
+ |_, child, item| self.eval(child, item, depth, active),
2190
+ ) {
2191
+ Ok(found) => found,
2192
+ Err(status) => return status,
2193
+ }
1815
2194
  };
1816
- if r.names.is_some() || !r.patterns.is_empty() || (r.additional.is_some() && found < len) {
2195
+ if ordered
2196
+ || r.names.is_some()
2197
+ || !r.patterns.is_empty()
2198
+ || (r.additional.is_some() && found < len)
2199
+ {
1817
2200
  let mut context = ObjectContext {
1818
2201
  engine: self,
1819
2202
  rules: r,
1820
2203
  depth,
1821
2204
  active,
2205
+ names_only: false,
1822
2206
  };
1823
2207
  let result =
1824
2208
  unsafe { sn_object_each(value, (&mut context as *mut ObjectContext).cast()) };
@@ -1826,7 +2210,13 @@ impl Execution<'_> {
1826
2210
  return if result == 0 { Invalid } else { Unsupported };
1827
2211
  }
1828
2212
  }
1829
- let status = r.check_dependent_required(value, self.undefined, &self.budget);
2213
+ if ordered && r.names.is_some() {
2214
+ let status = self.object_names(r, value, depth, active);
2215
+ if status != Valid {
2216
+ return status;
2217
+ }
2218
+ }
2219
+ let status = r.check_dependent_required(self.caches, value, self.undefined, &self.budget);
1830
2220
  if status != Valid {
1831
2221
  return status;
1832
2222
  }
@@ -1843,13 +2233,76 @@ impl Execution<'_> {
1843
2233
  }
1844
2234
  Valid
1845
2235
  }
2236
+ fn object_names(
2237
+ &self,
2238
+ rules: &Object,
2239
+ value: Value,
2240
+ depth: usize,
2241
+ active: scope::Context<'_>,
2242
+ ) -> Verdict {
2243
+ let mut context = ObjectContext {
2244
+ engine: self,
2245
+ rules,
2246
+ depth,
2247
+ active,
2248
+ names_only: true,
2249
+ };
2250
+ decode(unsafe { sn_object_each(value, (&mut context as *mut ObjectContext).cast()) })
2251
+ }
2252
+ fn unsupported_kind(&self, node: usize, kind: i32) -> bool {
2253
+ kind == TYPE_UNSUPPORTED
2254
+ && (self.kind_demand[node] == KindDemand::Data
2255
+ || (!self.caches.callbacks_allowed()
2256
+ && !self.caches.report_continuations_allowed()
2257
+ && self.callback_container_node(node)))
2258
+ }
2259
+ fn type_verdict(&self, mask: i32, union: bool, kind: i32, value: Value) -> Verdict {
2260
+ if kind == TYPE_UNSUPPORTED
2261
+ || self.caches.scalar_callback()
2262
+ || (self.caches.callbacks_allowed() && self.caches.dynamic_calls())
2263
+ {
2264
+ self.caches.type_predicate(value, mask, union)
2265
+ } else {
2266
+ verdict(kind & mask != 0)
2267
+ }
2268
+ }
1846
2269
  fn instance_kind(&self, node: usize, value: Value) -> i32 {
1847
- if matches!(self.nodes[node].as_slice(), [Op::Object(_, rules)] if rules.record.is_some()) {
2270
+ if self.caches.scalar_callback() && self.kind_demand[node] == KindDemand::Predicate {
2271
+ return TYPE_UNSUPPORTED;
2272
+ }
2273
+ if value & self.fixnum_flag != 0 {
2274
+ return TYPE_INTEGRAL_NUMBER;
2275
+ }
2276
+ if self.kind_demand[node] == KindDemand::None {
2277
+ return TYPE_UNCHECKED;
2278
+ }
2279
+ if matches!(self.nodes[node].as_slice(), [Op::Object(_, rules)]
2280
+ if rules.record.is_some() && (!rules.header_only() || (!rules.inspect_required && rules.dependent_required.is_empty())))
2281
+ {
1848
2282
  unsafe { sn_shallow_kind(value) }
1849
2283
  } else {
1850
2284
  unsafe { sn_kind(value) }
1851
2285
  }
1852
2286
  }
2287
+ // Diagnostic/trial paths require a full Hash-key guard. A known parent kind
2288
+ // remains reusable, while control-flow-only children need no classification.
2289
+ fn diagnostic_kind(&self, node: usize, value: Value, known: &InstanceKind) -> i32 {
2290
+ if self.caches.scalar_callback() && self.kind_demand[node] == KindDemand::Predicate {
2291
+ return TYPE_UNSUPPORTED;
2292
+ }
2293
+ if known.get() != TYPE_UNCHECKED {
2294
+ return known.get();
2295
+ }
2296
+ let kind = if value & self.fixnum_flag != 0 {
2297
+ TYPE_INTEGRAL_NUMBER
2298
+ } else if self.kind_demand[node] == KindDemand::None {
2299
+ TYPE_UNCHECKED
2300
+ } else {
2301
+ unsafe { sn_kind(value) }
2302
+ };
2303
+ known.set(kind);
2304
+ kind
2305
+ }
1853
2306
  fn record_value(
1854
2307
  &self,
1855
2308
  node: usize,
@@ -1864,7 +2317,7 @@ impl Execution<'_> {
1864
2317
  [Op::String(required, rules)]
1865
2318
  if depth <= MAX_EVALUATION_DEPTH && rules.length_only() =>
1866
2319
  {
1867
- rules.eval_length_guarded(*required, value)
2320
+ rules.eval_length_guarded(self.caches, *required, value)
1868
2321
  }
1869
2322
  [Op::String(required, rules)]
1870
2323
  if depth <= MAX_EVALUATION_DEPTH && rules.pattern.is_some() =>
@@ -1872,19 +2325,15 @@ impl Execution<'_> {
1872
2325
  rules.eval_guarded(self.caches, *required, value)
1873
2326
  }
1874
2327
  [
1875
- op @ (Op::Type(_)
2328
+ op @ (Op::Type(..)
1876
2329
  | Op::Boolean(_)
1877
2330
  | Op::Number(..)
1878
2331
  | Op::String(..)
1879
2332
  | Op::Const(_)
1880
2333
  | Op::Enum(_)),
1881
2334
  ] if depth <= MAX_EVALUATION_DEPTH => {
1882
- let kind = if value & self.fixnum_flag != 0 {
1883
- TYPE_INTEGRAL_NUMBER
1884
- } else {
1885
- unsafe { sn_kind(value) }
1886
- };
1887
- if kind == TYPE_UNSUPPORTED {
2335
+ let kind = self.diagnostic_kind(node, value, &InstanceKind::new(TYPE_UNCHECKED));
2336
+ if self.unsupported_kind(node, kind) {
1888
2337
  Unsupported
1889
2338
  } else {
1890
2339
  self.leaf(op, kind, value, depth)
@@ -1963,12 +2412,21 @@ fn record_entry(c: &mut RecordContext<'_, '_>, key: Value, value: Value) -> Verd
1963
2412
  {
1964
2413
  c.status = if c.depth <= MAX_EVALUATION_DEPTH {
1965
2414
  match field.and_then(|field| field.check) {
1966
- Some(RecordCheck::IntegerBounds(min, max)) if value & c.engine.fixnum_flag != 0 => {
2415
+ Some(RecordCheck::IntegerBounds(min, max))
2416
+ if value & c.engine.fixnum_flag != 0 && !c.engine.caches.dynamic_calls() =>
2417
+ {
1967
2418
  let n = immediate_integer(value);
1968
2419
  verdict(n >= min && n <= max)
1969
2420
  }
1970
- Some(RecordCheck::StringLength(required, min, max)) => {
1971
- length_guarded(value, required, min, max)
2421
+ Some(RecordCheck::StringLength(required, min, max))
2422
+ if !c.engine.caches.dynamic_calls() =>
2423
+ {
2424
+ let status = length_guarded(value, required, min, max);
2425
+ if status == Unsupported {
2426
+ c.engine.record_value(child, value, c.depth, c.active)
2427
+ } else {
2428
+ status
2429
+ }
1972
2430
  }
1973
2431
  _ => c.engine.record_value(child, value, c.depth, c.active),
1974
2432
  }
@@ -2032,20 +2490,79 @@ fn equal_cached(
2032
2490
  return Unsupported;
2033
2491
  }
2034
2492
  if depth > MAX_EVALUATION_DEPTH {
2035
- return Unsupported;
2493
+ return equal_iterative(a, b, depth, memo, budget);
2494
+ }
2495
+ let ka = match equality_kind(a, b) {
2496
+ Ok(kind) => kind,
2497
+ Err(result) => return result,
2498
+ };
2499
+ let key = (a, b, depth);
2500
+ if memo.eligible(depth)
2501
+ && let Some(result) = memo.get(key)
2502
+ {
2503
+ return result;
2504
+ }
2505
+ let result = match ka {
2506
+ TYPE_ARRAY => {
2507
+ let len = unsafe { sn_len(a) };
2508
+ if len != unsafe { sn_len(b) } {
2509
+ return Invalid;
2510
+ }
2511
+ for i in 0..len {
2512
+ let result = equal_cached(
2513
+ unsafe { sn_entry(a, i) },
2514
+ unsafe { sn_entry(b, i) },
2515
+ depth + 1,
2516
+ memo,
2517
+ budget,
2518
+ );
2519
+ if result != Valid {
2520
+ return result;
2521
+ }
2522
+ }
2523
+ Valid
2524
+ }
2525
+ TYPE_OBJECT => {
2526
+ if unsafe { sn_len(a) != sn_len(b) } {
2527
+ return Invalid;
2528
+ }
2529
+ let mut context = EqualityContext {
2530
+ other: b,
2531
+ depth: depth + 1,
2532
+ memo,
2533
+ budget,
2534
+ };
2535
+ decode(unsafe {
2536
+ sn_hash_each(
2537
+ a,
2538
+ (&mut context as *mut EqualityContext<'_>).cast(),
2539
+ equal_entry,
2540
+ )
2541
+ })
2542
+ }
2543
+ _ => unreachable!("equality_kind returns only container kinds"),
2544
+ };
2545
+ if memo.eligible(depth) {
2546
+ memo.record(key, result);
2036
2547
  }
2548
+ result
2549
+ }
2550
+
2551
+ // Resolve scalar equality once for both recursive and explicit-stack traversal.
2552
+ // Only ordinary equal-class containers require child traversal.
2553
+ fn equality_kind(a: Value, b: Value) -> Result<i32, Verdict> {
2037
2554
  let (ka, kb) = unsafe { (sn_kind(a), sn_kind(b)) };
2038
2555
  if ka == TYPE_UNSUPPORTED || kb == TYPE_UNSUPPORTED {
2039
- return Unsupported;
2556
+ return Err(Unsupported);
2040
2557
  }
2041
2558
  if ka & TYPE_NUMBER != 0 && kb & TYPE_NUMBER != 0 {
2042
2559
  let (ia, ib) = unsafe { (sn_fixnum(a) != 0, sn_fixnum(b) != 0) };
2043
2560
  let (fa, fb) = unsafe { (sn_is_float(a) != 0, sn_is_float(b) != 0) };
2044
2561
  if ia && ib {
2045
- return verdict(unsafe { sn_integer(a) == sn_integer(b) });
2562
+ return Err(verdict(unsafe { sn_integer(a) == sn_integer(b) }));
2046
2563
  }
2047
2564
  if fa && fb {
2048
- return verdict(unsafe { sn_float(a) == sn_float(b) });
2565
+ return Err(verdict(unsafe { sn_float(a) == sn_float(b) }));
2049
2566
  }
2050
2567
  if (ia && fb) || (fa && ib) {
2051
2568
  let (n, f) = unsafe {
@@ -2057,16 +2574,16 @@ fn equal_cached(
2057
2574
  };
2058
2575
  // Convert the float to an integer, never the integer to a rounded float.
2059
2576
  // Fixnums are strictly inside i64's bounds on supported CRuby targets.
2060
- return verdict(
2577
+ return Err(verdict(
2061
2578
  f.is_finite()
2062
2579
  && f.trunc() == f
2063
2580
  && f >= i64::MIN as f64
2064
2581
  && f < i64::MAX as f64
2065
2582
  && f as i64 == n,
2066
- );
2583
+ ));
2067
2584
  }
2068
2585
  if !fa && !fb {
2069
- return verdict(
2586
+ return Err(verdict(
2070
2587
  unsafe {
2071
2588
  if ia {
2072
2589
  sn_big_equal(b, a)
@@ -2074,80 +2591,150 @@ fn equal_cached(
2074
2591
  sn_big_equal(a, b)
2075
2592
  }
2076
2593
  } != 0,
2077
- );
2594
+ ));
2078
2595
  }
2079
- return if fa {
2596
+ return Err(if fa {
2080
2597
  number::integer_float_equal(b, unsafe { sn_float(a) })
2081
2598
  } else {
2082
2599
  number::integer_float_equal(a, unsafe { sn_float(b) })
2083
- };
2600
+ });
2084
2601
  }
2085
2602
  // JSON container equality visits every value, even for the same object:
2086
2603
  // nested NaN values are unequal and unsupported values need VM evaluation.
2087
2604
  if ka != TYPE_ARRAY && ka != TYPE_OBJECT && unsafe { sn_same(a, b) } != 0 {
2088
- return Valid;
2605
+ return Err(Valid);
2089
2606
  }
2090
2607
  if ka != kb {
2091
- return Invalid;
2608
+ return Err(Invalid);
2092
2609
  }
2093
2610
  if unsafe { sn_same_class(a, b) } == 0 {
2094
- return Invalid;
2611
+ return Err(Invalid);
2095
2612
  }
2096
- let container = ka == TYPE_ARRAY || ka == TYPE_OBJECT;
2097
- let key = (a, b, depth);
2098
- if container
2099
- && memo.eligible(depth)
2100
- && let Some(result) = memo.get(key)
2101
- {
2102
- return result;
2613
+ match ka {
2614
+ TYPE_ARRAY | TYPE_OBJECT => Ok(ka),
2615
+ TYPE_STRING => Err(verdict(unsafe { sn_string_equal(a, b) } != 0)),
2616
+ _ => Err(Invalid),
2103
2617
  }
2104
- let result = match ka {
2105
- TYPE_STRING => verdict(unsafe { sn_string_equal(a, b) } != 0),
2106
- TYPE_ARRAY => {
2107
- let len = unsafe { sn_len(a) };
2108
- if len != unsafe { sn_len(b) } {
2109
- return Invalid;
2618
+ }
2619
+
2620
+ enum EqualityWork {
2621
+ Compare(Value, Value, usize),
2622
+ Array(Value, Value, usize, usize, usize),
2623
+ Object(Value, Vec<(Value, Value)>, usize, usize),
2624
+ Finish(Value, Value, usize),
2625
+ }
2626
+
2627
+ // Keep shallow comparisons on their allocation-free recursive path. Beyond
2628
+ // that bounded prefix, explicit continuations preserve depth-first order
2629
+ // without growing the native call stack. Scalar rules remain shared above.
2630
+ fn equal_iterative(
2631
+ a: Value,
2632
+ b: Value,
2633
+ depth: usize,
2634
+ memo: &mut structural::Cache<(Value, Value, usize), Verdict>,
2635
+ budget: &budget::Budget,
2636
+ ) -> Verdict {
2637
+ let mut work = vec![EqualityWork::Compare(a, b, depth)];
2638
+ let mut active = std::collections::HashSet::new();
2639
+ while let Some(task) = work.pop() {
2640
+ if !budget.checkpoint() {
2641
+ return Unsupported;
2642
+ }
2643
+ match task {
2644
+ EqualityWork::Compare(a, b, depth) => {
2645
+ let kind = match equality_kind(a, b) {
2646
+ Ok(kind) => kind,
2647
+ Err(Valid) => continue,
2648
+ Err(result) => return result,
2649
+ };
2650
+ let key = (a, b, depth);
2651
+ if memo.eligible(depth)
2652
+ && let Some(result) = memo.get(key)
2653
+ {
2654
+ if result != Valid {
2655
+ return result;
2656
+ }
2657
+ continue;
2658
+ }
2659
+ let len = unsafe { sn_len(a) };
2660
+ if len != unsafe { sn_len(b) } {
2661
+ return Invalid;
2662
+ }
2663
+ // Report Ruby's recursive-comparison error after unwinding;
2664
+ // an active pair is not proof of JSON equality.
2665
+ if !active.insert((a, b)) {
2666
+ budget.failure.set(STATUS_STACK_ERROR);
2667
+ return Unsupported;
2668
+ }
2669
+ work.push(EqualityWork::Finish(a, b, depth));
2670
+ if len == 0 {
2671
+ continue;
2672
+ }
2673
+ if kind == TYPE_ARRAY {
2674
+ work.push(EqualityWork::Array(a, b, 0, len, depth + 1));
2675
+ } else {
2676
+ let mut entries = EqualityEntries {
2677
+ values: Vec::with_capacity(len),
2678
+ budget,
2679
+ };
2680
+ let status = decode(unsafe {
2681
+ sn_hash_each(
2682
+ a,
2683
+ (&mut entries as *mut EqualityEntries<'_>).cast(),
2684
+ equality_entry,
2685
+ )
2686
+ });
2687
+ if status != Valid {
2688
+ return status;
2689
+ }
2690
+ work.push(EqualityWork::Object(b, entries.values, 0, depth + 1));
2691
+ }
2110
2692
  }
2111
- for i in 0..len {
2112
- let result = equal_cached(
2113
- unsafe { sn_entry(a, i) },
2114
- unsafe { sn_entry(b, i) },
2115
- depth + 1,
2116
- memo,
2117
- budget,
2118
- );
2119
- if result != Valid {
2120
- return result;
2693
+ EqualityWork::Array(a, b, index, len, depth) => {
2694
+ let pair = unsafe { (sn_entry(a, index), sn_entry(b, index)) };
2695
+ if index + 1 < len {
2696
+ work.push(EqualityWork::Array(a, b, index + 1, len, depth));
2121
2697
  }
2698
+ work.push(EqualityWork::Compare(pair.0, pair.1, depth));
2122
2699
  }
2123
- Valid
2124
- }
2125
- TYPE_OBJECT => {
2126
- if unsafe { sn_len(a) != sn_len(b) } {
2127
- return Invalid;
2700
+ EqualityWork::Object(b, entries, index, depth) => {
2701
+ let (key, value) = entries[index];
2702
+ let other = unsafe { sn_lookup(b, key) };
2703
+ if unsafe { sn_missing(other) } != 0 {
2704
+ return Invalid;
2705
+ }
2706
+ if index + 1 < entries.len() {
2707
+ work.push(EqualityWork::Object(b, entries, index + 1, depth));
2708
+ }
2709
+ work.push(EqualityWork::Compare(value, other, depth));
2710
+ }
2711
+ EqualityWork::Finish(a, b, depth) => {
2712
+ active.remove(&(a, b));
2713
+ if memo.eligible(depth) {
2714
+ memo.record((a, b, depth), Valid);
2715
+ }
2128
2716
  }
2129
- let mut context = EqualityContext {
2130
- other: b,
2131
- depth: depth + 1,
2132
- memo,
2133
- budget,
2134
- };
2135
- decode(unsafe {
2136
- sn_hash_each(
2137
- a,
2138
- (&mut context as *mut EqualityContext<'_>).cast(),
2139
- equal_entry,
2140
- )
2141
- })
2142
2717
  }
2143
- TYPE_UNSUPPORTED => Unsupported,
2144
- _ => Invalid,
2145
- };
2146
- if container && memo.eligible(depth) {
2147
- memo.record(key, result);
2148
2718
  }
2149
- result
2719
+ Valid
2720
+ }
2721
+
2722
+ struct EqualityEntries<'a> {
2723
+ values: Vec<(Value, Value)>,
2724
+ budget: &'a budget::Budget,
2150
2725
  }
2726
+ unsafe extern "C" fn equality_entry(context: *mut c_void, key: Value, value: Value) -> i32 {
2727
+ catch_unwind(AssertUnwindSafe(|| {
2728
+ let entries = unsafe { &mut *context.cast::<EqualityEntries<'_>>() };
2729
+ if !entries.budget.checkpoint() {
2730
+ return Unsupported as i32;
2731
+ }
2732
+ entries.values.push((key, value));
2733
+ Valid as i32
2734
+ }))
2735
+ .unwrap_or(Unsupported as i32)
2736
+ }
2737
+
2151
2738
  fn decode(result: i32) -> Verdict {
2152
2739
  match result {
2153
2740
  VERDICT_INVALID => Invalid,
@@ -2315,13 +2902,12 @@ fn fingerprint_cached(
2315
2902
  // before the depth-based cache handles deeper shared graphs.
2316
2903
  let cacheable = container && memo.eligible_container(depth, unsafe { len.assume_init() });
2317
2904
  let key = (value, depth);
2318
- if cacheable
2319
- && let Some(result) = memo.get(key)
2320
- {
2905
+ if cacheable && let Some(result) = memo.get(key) {
2321
2906
  return result;
2322
2907
  }
2323
2908
  let result = match kind {
2324
2909
  TYPE_INTEGRAL_NUMBER => Some(number::integer_fingerprint(value)),
2910
+ TYPE_IDENTITY_SCALAR => Some(mix(value as u64 ^ TYPE_IDENTITY_SCALAR as u64)),
2325
2911
  TYPE_NULL => Some(mix(TYPE_NULL as u64)),
2326
2912
  TYPE_BOOLEAN => Some(mix(if unsafe { len.assume_init() } != 0 {
2327
2913
  2
@@ -2423,21 +3009,7 @@ fn unique(value: Value, depth: usize, fixnum_flag: Value, budget: &budget::Budge
2423
3009
  return Unsupported;
2424
3010
  }
2425
3011
  if len <= 16 {
2426
- for i in 0..len {
2427
- for previous in 0..i {
2428
- match equal_budgeted(
2429
- unsafe { sn_entry(value, previous) },
2430
- unsafe { sn_entry(value, i) },
2431
- depth,
2432
- budget,
2433
- ) {
2434
- Valid => return Invalid,
2435
- Unsupported => return Unsupported,
2436
- Invalid => {}
2437
- }
2438
- }
2439
- }
2440
- return Valid;
3012
+ return unique_linear(value, depth, budget, false);
2441
3013
  }
2442
3014
  let values = unsafe { std::slice::from_raw_parts(sn_array_data(value), len) };
2443
3015
  // This is a checked optimization hint, not an instance shape assumption.
@@ -2456,11 +3028,54 @@ fn unique(value: Value, depth: usize, fixnum_flag: Value, budget: &budget::Budge
2456
3028
  }
2457
3029
  }
2458
3030
  let mut fingerprints = structural::Cache::batch(depth);
2459
- indexed_unique(
3031
+ let result = indexed_unique(
2460
3032
  values,
2461
- |item| fingerprint_cached(item, depth, fixnum_flag, big_hint, &mut fingerprints, budget),
3033
+ |item| {
3034
+ fingerprint_cached(
3035
+ item,
3036
+ depth,
3037
+ fixnum_flag,
3038
+ big_hint,
3039
+ &mut fingerprints,
3040
+ budget,
3041
+ )
3042
+ },
2462
3043
  |a, b| equal_budgeted(a, b, depth, budget),
2463
- )
3044
+ );
3045
+ if result == Unsupported && budget.failure.get() == 0 && budget.checkpoint() {
3046
+ unique_linear(value, depth, budget, true)
3047
+ } else {
3048
+ result
3049
+ }
3050
+ }
3051
+ // Index availability is an optimization decision. Exact ordered comparisons
3052
+ // can still decide uniqueness when a depth or memory bound declines hashing.
3053
+ fn unique_linear(value: Value, depth: usize, budget: &budget::Budget, check_hash: bool) -> Verdict {
3054
+ for i in 0..unsafe { sn_len(value) } {
3055
+ if check_hash {
3056
+ match instance_graph::ordinary_graph(unsafe { sn_entry(value, i) }, budget) {
3057
+ instance_graph::Safety::Ordinary => {}
3058
+ instance_graph::Safety::Cyclic => {
3059
+ budget.failure.set(STATUS_STACK_ERROR);
3060
+ return Unsupported;
3061
+ }
3062
+ instance_graph::Safety::Unsupported => return Unsupported,
3063
+ }
3064
+ }
3065
+ for previous in 0..i {
3066
+ match equal_budgeted(
3067
+ unsafe { sn_entry(value, previous) },
3068
+ unsafe { sn_entry(value, i) },
3069
+ depth,
3070
+ budget,
3071
+ ) {
3072
+ Valid => return Invalid,
3073
+ Unsupported => return Unsupported,
3074
+ Invalid => {}
3075
+ }
3076
+ }
3077
+ }
3078
+ Valid
2464
3079
  }
2465
3080
  // Keep Big-only sampling and retry state out of the common scalar path.
2466
3081
  #[inline(never)]
@@ -2800,6 +3415,7 @@ struct ObjectContext<'a> {
2800
3415
  rules: &'a Object,
2801
3416
  depth: usize,
2802
3417
  active: scope::Context<'a>,
3418
+ names_only: bool,
2803
3419
  }
2804
3420
  /// Called synchronously by the C hash iterator. This never calls allocating Ruby APIs.
2805
3421
  /// # Safety
@@ -2814,15 +3430,21 @@ pub unsafe extern "C" fn sn_object_entry(context: *const c_void, key: Value, val
2814
3430
  if unsafe { sn_kind(key) } != TYPE_STRING {
2815
3431
  return Unsupported as i32;
2816
3432
  }
2817
- if let Some(child) = c.rules.names {
3433
+ if (c.names_only || !c.engine.caches.runtime_order())
3434
+ && let Some(child) = c.rules.names
3435
+ {
2818
3436
  let result = c.engine.eval(child, key, c.depth, c.active);
2819
3437
  if result != Valid {
2820
3438
  return result as i32;
2821
3439
  }
2822
3440
  }
2823
- match c.rules.visit_value_rules::<false>(
3441
+ if c.names_only {
3442
+ return Valid as i32;
3443
+ }
3444
+ match c.rules.visit_value_rules(
2824
3445
  c.engine.caches,
2825
3446
  &c.engine.budget,
3447
+ c.engine.caches.runtime_order(),
2826
3448
  key,
2827
3449
  None,
2828
3450
  |_, _, child| match c.engine.eval(child, value, c.depth, c.active) {
@@ -2873,9 +3495,17 @@ pub unsafe extern "C" fn sn_compile(
2873
3495
  e.bind_dynamic_anchors()?;
2874
3496
  e.simplify_dynamic_references();
2875
3497
  e.compile_memoization();
2876
- Ok::<_, Value>(())
3498
+ e.compile_native_validity_support();
3499
+ e.compile_kind_demands();
3500
+ Ok::<_, Value>(
3501
+ !e.nodes
3502
+ .iter()
3503
+ .flatten()
3504
+ .any(|op| matches!(op, Op::Unsupported)),
3505
+ )
2877
3506
  })) {
2878
- Ok(Ok(())) => VERDICT_VALID,
3507
+ Ok(Ok(true)) => VERDICT_VALID,
3508
+ Ok(Ok(false)) => VERDICT_UNSUPPORTED,
2879
3509
  Ok(Err(value)) => {
2880
3510
  unsafe { *error = value };
2881
3511
  STATUS_EXCEPTION
@@ -2894,12 +3524,44 @@ pub unsafe extern "C" fn sn_node_index(engine: *const Engine, program: Value) ->
2894
3524
  .unwrap_or(usize::MAX)
2895
3525
  }
2896
3526
  /// # Safety
3527
+ /// Engine must be initialized, node compiled, and flags writable.
3528
+ #[unsafe(no_mangle)]
3529
+ pub unsafe extern "C" fn sn_callback_kind(
3530
+ engine: *const Engine,
3531
+ node: usize,
3532
+ flags: *mut i32,
3533
+ ) -> i32 {
3534
+ let (kind, value) = unsafe { &*engine }.scalar_callback_kind(node);
3535
+ unsafe { *flags = value };
3536
+ kind
3537
+ }
3538
+ /// # Safety
3539
+ /// Engine must be initialized and root compiled.
3540
+ #[unsafe(no_mangle)]
3541
+ pub unsafe extern "C" fn sn_reference_context(engine: *const Engine) -> i32 {
3542
+ // Any scalar Ruby primitive can reenter a schema block, including from
3543
+ // tracked/diagnostic traversals that cannot admit container callbacks.
3544
+ i32::from(unsafe { &*engine }.has_references)
3545
+ }
3546
+ /// # Safety
2897
3547
  /// Engine must be initialized.
2898
3548
  #[unsafe(no_mangle)]
2899
3549
  pub unsafe extern "C" fn sn_root_index(engine: *const Engine) -> usize {
2900
3550
  unsafe { &*engine }.root
2901
3551
  }
2902
3552
  /// # Safety
3553
+ /// Engine must be live and compilation must have finished.
3554
+ #[unsafe(no_mangle)]
3555
+ pub unsafe extern "C" fn sn_has_unresolved_references(engine: *const Engine) -> i32 {
3556
+ i32::from(
3557
+ unsafe { &*engine }
3558
+ .nodes
3559
+ .iter()
3560
+ .flatten()
3561
+ .any(|op| matches!(op, Op::UnresolvedRef(_))),
3562
+ )
3563
+ }
3564
+ /// # Safety
2903
3565
  /// Roots must be live and compilation must have finished.
2904
3566
  #[unsafe(no_mangle)]
2905
3567
  pub unsafe extern "C" fn sn_root_count(roots: *const Roots) -> usize {
@@ -2917,21 +3579,25 @@ pub unsafe extern "C" fn sn_root_at(roots: *const Roots, index: usize) -> Value
2917
3579
  #[unsafe(no_mangle)]
2918
3580
  pub unsafe extern "C" fn sn_valid(
2919
3581
  engine: *const Engine,
2920
- caches: *const pattern::Caches,
3582
+ caches: *const runtime::Caches,
2921
3583
  root: usize,
2922
3584
  value: Value,
3585
+ depth: usize,
2923
3586
  ) -> i32 {
2924
3587
  catch_unwind(AssertUnwindSafe(|| {
2925
3588
  let e = unsafe { &*engine };
2926
- let local = pattern::Caches::default();
3589
+ let local = runtime::Caches::default();
2927
3590
  let caches = unsafe { caches.as_ref() }.unwrap_or(&local);
2928
- Execution {
3591
+ caches.admit(e, root);
3592
+ let execution = Execution {
2929
3593
  engine: e,
2930
3594
  caches,
2931
3595
  memo: memo::Memo::default(),
2932
3596
  budget: budget::Budget::default(),
2933
- }
2934
- .eval(root, value, 0, scope::Context::default()) as i32
3597
+ };
3598
+ let result = execution.eval(root, value, depth, scope::Context::default());
3599
+ caches.finish_callback_report(result);
3600
+ execution.budget.status(result)
2935
3601
  }))
2936
3602
  .unwrap_or(STATUS_PANIC)
2937
3603
  }
@@ -2940,24 +3606,26 @@ pub unsafe extern "C" fn sn_valid(
2940
3606
  #[unsafe(no_mangle)]
2941
3607
  pub unsafe extern "C" fn sn_valid_with_limits(
2942
3608
  engine: *const Engine,
2943
- caches: *const pattern::Caches,
3609
+ caches: *const runtime::Caches,
2944
3610
  root: usize,
2945
3611
  value: Value,
3612
+ depth: usize,
2946
3613
  limits: *const budget::Limits,
2947
3614
  outcome: *mut budget::Outcome,
2948
3615
  ) -> i32 {
2949
- let budget = budget::Budget::new(unsafe { *limits });
2950
3616
  let result = catch_unwind(AssertUnwindSafe(|| {
2951
- let local = pattern::Caches::default();
3617
+ let local = runtime::Caches::default();
2952
3618
  let caches = unsafe { caches.as_ref() }.unwrap_or(&local);
3619
+ let e = unsafe { &*engine };
3620
+ caches.admit(e, root);
2953
3621
  let execution = Execution {
2954
3622
  engine: unsafe { &*engine },
2955
3623
  caches,
2956
3624
  memo: memo::Memo::default(),
2957
- budget,
3625
+ budget: caches.budget(unsafe { *limits }),
2958
3626
  };
2959
3627
  let result = if execution.budget.check_deadline() {
2960
- execution.eval(root, value, 0, scope::Context::default())
3628
+ execution.eval(root, value, depth, scope::Context::default())
2961
3629
  } else {
2962
3630
  Unsupported
2963
3631
  };
@@ -2969,7 +3637,8 @@ pub unsafe extern "C" fn sn_valid_with_limits(
2969
3637
  unsafe {
2970
3638
  ((*outcome).kind, (*outcome).attempted) = execution.budget.hit.get();
2971
3639
  }
2972
- result as i32
3640
+ caches.finish_callback_report(result);
3641
+ execution.budget.status(result)
2973
3642
  }));
2974
3643
  result.unwrap_or(STATUS_PANIC)
2975
3644
  }
@@ -3018,7 +3687,12 @@ pub extern "C" fn sn_engine_header_size() -> usize {
3018
3687
  pub unsafe extern "C" fn sn_size(engine: *const Engine) -> usize {
3019
3688
  let nodes = unsafe { &*std::ptr::addr_of!((*engine).nodes) };
3020
3689
  let operands = unsafe { &*std::ptr::addr_of!((*engine).error_operands) };
3690
+ let programs = unsafe { &*std::ptr::addr_of!((*engine).programs) };
3021
3691
  let contextual = unsafe { &*std::ptr::addr_of!((*engine).memo_contextual) };
3692
+ let kinds = unsafe { &*std::ptr::addr_of!((*engine).kind_demand) };
3693
+ let callback_roots = unsafe { &*std::ptr::addr_of!((*engine).callback_roots) };
3694
+ let validity_supported = unsafe { &*std::ptr::addr_of!((*engine).validity_supported) };
3695
+ let report_supported = unsafe { &*std::ptr::addr_of!((*engine).report_supported) };
3022
3696
  let guarded = unsafe { &*std::ptr::addr_of!((*engine).memo_guarded) };
3023
3697
  let sources = unsafe { &*std::ptr::addr_of!((*engine).memo_guard_sources) };
3024
3698
  let same = unsafe { &*std::ptr::addr_of!((*engine).memo_guard_same_sources) };
@@ -3027,6 +3701,9 @@ pub unsafe extern "C" fn sn_size(engine: *const Engine) -> usize {
3027
3701
  let projection = unsafe { &*std::ptr::addr_of!((*engine).memo_scope_projection) };
3028
3702
  std::mem::size_of::<Engine>()
3029
3703
  + projection.memory_usage()
3704
+ + kinds.capacity() * std::mem::size_of::<KindDemand>()
3705
+ + (callback_roots.capacity() + validity_supported.capacity() + report_supported.capacity())
3706
+ * std::mem::size_of::<bool>()
3030
3707
  + (contextual.capacity()
3031
3708
  + guarded.capacity()
3032
3709
  + sources.capacity()
@@ -3034,6 +3711,7 @@ pub unsafe extern "C" fn sn_size(engine: *const Engine) -> usize {
3034
3711
  + scalar.capacity()
3035
3712
  + amplification.capacity())
3036
3713
  * std::mem::size_of::<bool>()
3714
+ + programs.capacity() * std::mem::size_of::<Value>()
3037
3715
  + nodes.capacity() * std::mem::size_of::<Vec<Op>>()
3038
3716
  + operands.capacity() * std::mem::size_of::<Vec<(i32, Value)>>()
3039
3717
  + operands
@@ -3048,6 +3726,7 @@ pub unsafe extern "C" fn sn_size(engine: *const Engine) -> usize {
3048
3726
  .iter()
3049
3727
  .flatten()
3050
3728
  .map(|op| match op {
3729
+ Op::Number(_, rules) => rules.memory_usage(),
3051
3730
  Op::Enum(values) => values.memory_usage(),
3052
3731
  Op::String(_, rules) => {
3053
3732
  std::mem::size_of::<StringRules>()