schemurai 3.0.1 → 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 (52) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +6 -4
  3. data/ext/schemurai_native/Cargo.lock +48 -1
  4. data/ext/schemurai_native/Cargo.toml +3 -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 +56 -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 +8 -6
  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/lib.rs +910 -237
  20. data/ext/schemurai_native/src/memo.rs +11 -38
  21. data/ext/schemurai_native/src/number/arbitrary.rs +154 -0
  22. data/ext/schemurai_native/src/number.rs +250 -59
  23. data/ext/schemurai_native/src/pattern.rs +113 -101
  24. data/ext/schemurai_native/src/pointer.rs +97 -0
  25. data/ext/schemurai_native/src/runtime/properties.rs +162 -0
  26. data/ext/schemurai_native/src/runtime/reports.rs +266 -0
  27. data/ext/schemurai_native/src/runtime/scopes.rs +430 -0
  28. data/ext/schemurai_native/src/runtime.rs +1020 -0
  29. data/ext/schemurai_native/src/scope.rs +60 -16
  30. data/ext/schemurai_native/src/string.rs +68 -0
  31. data/ext/schemurai_native/src/tracking.rs +244 -174
  32. data/lib/schemurai/container_checks/array_diagnostics.rb +119 -0
  33. data/lib/schemurai/container_checks/object_diagnostics.rb +152 -0
  34. data/lib/schemurai/container_checks.rb +534 -0
  35. data/lib/schemurai/decimal.rb +11 -0
  36. data/lib/schemurai/dialect.rb +0 -12
  37. data/lib/schemurai/ecma_regexp.rb +46 -0
  38. data/lib/schemurai/evaluation_pool.rb +66 -0
  39. data/lib/schemurai/evaluator.rb +9 -48
  40. data/lib/schemurai/json_pointer.rb +11 -0
  41. data/lib/schemurai/native/diagnostics.rb +4 -3
  42. data/lib/schemurai/native/evaluator.rb +38 -10
  43. data/lib/schemurai/native/null_numeric.rb +23 -0
  44. data/lib/schemurai/number_checks.rb +128 -0
  45. data/lib/schemurai/string_checks.rb +166 -0
  46. data/lib/schemurai/type_checks.rb +59 -0
  47. data/lib/schemurai/value_equality.rb +72 -0
  48. data/lib/schemurai/version.rb +1 -1
  49. data/lib/schemurai/vm/compiler.rb +18 -26
  50. data/lib/schemurai/vm/evaluator.rb +87 -545
  51. data/lib/schemurai/vm/limited_evaluator.rb +7 -22
  52. metadata +24 -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;
@@ -18,6 +19,9 @@ mod memo;
18
19
  mod memo_scope;
19
20
  mod number;
20
21
  mod pattern;
22
+ mod pointer;
23
+ mod runtime;
24
+ mod string;
21
25
  // Some shared constants are consumed only by C or Ruby.
22
26
  #[allow(dead_code)]
23
27
  mod protocol {
@@ -42,6 +46,7 @@ unsafe extern "C" {
42
46
  ) -> i32;
43
47
  fn sn_small_hash(value: Value, entries: *mut Value) -> i32;
44
48
  fn sn_true(value: Value) -> i32;
49
+ fn sn_truthy(value: Value) -> i32;
45
50
  fn sn_fixnum(value: Value) -> i32;
46
51
  fn sn_fixnum_flag() -> Value;
47
52
  fn sn_array_data(value: Value) -> *const Value;
@@ -56,6 +61,8 @@ unsafe extern "C" {
56
61
  required: i32,
57
62
  ) -> i32;
58
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;
59
66
  fn sn_is_bignum(value: Value) -> i32;
60
67
  fn sn_big256(value: Value, words: *mut u64) -> i32;
61
68
  fn sn_big_hash(value: Value, hash: *mut u64) -> i32;
@@ -116,13 +123,24 @@ enum Verdict {
116
123
  }
117
124
  use Verdict::*;
118
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
+ }
119
136
  const MAX_UNIQUE_INDEX_BYTES: usize = 64 * 1024 * 1024;
120
137
  #[derive(Debug)]
121
138
  enum Op {
122
139
  Unsupported,
140
+ UnresolvedRef(Value),
123
141
  Boolean(bool),
124
142
  Ref(scope::Reference),
125
- Type(i32),
143
+ Type(i32, bool),
126
144
  Const(Value),
127
145
  Enum(Box<enumeration::Enumeration>),
128
146
  All(Vec<usize>),
@@ -137,6 +155,7 @@ enum Op {
137
155
  }
138
156
  #[derive(Debug)]
139
157
  struct Array {
158
+ header: container::Header,
140
159
  min: Option<usize>,
141
160
  max: Option<usize>,
142
161
  unique: bool,
@@ -147,10 +166,14 @@ struct Array {
147
166
  contains: Option<usize>,
148
167
  min_contains: usize,
149
168
  max_contains: Option<usize>,
169
+ count_contains: bool,
150
170
  unevaluated: Option<usize>,
151
171
  }
152
172
  #[derive(Debug)]
153
173
  struct StringRules {
174
+ source: Value,
175
+ runtime_flags: i32,
176
+ runtime_format: bool,
154
177
  min: Option<usize>,
155
178
  max: Option<usize>,
156
179
  pattern: Option<pattern::Pattern>,
@@ -197,15 +220,31 @@ impl StringRules {
197
220
  && self.format.is_none()
198
221
  && self.content.is_none()
199
222
  }
200
- fn eval_length_guarded(&self, required: i32, value: Value) -> Verdict {
201
- 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
+ }
202
238
  }
203
- 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
+ }
204
243
  let mut data = std::ptr::null();
205
244
  let mut len = 0;
206
245
  let encoding = unsafe { sn_pattern_item_view(value, &mut data, &mut len) };
207
246
  if encoding == -TYPE_UNSUPPORTED {
208
- Unsupported
247
+ self.runtime_valid(caches, required, value)
209
248
  } else if encoding < 0 {
210
249
  verdict(required == 0)
211
250
  } else {
@@ -213,12 +252,12 @@ impl StringRules {
213
252
  self.eval_with_view::<true>(caches, value, Some((bytes, encoding)))
214
253
  }
215
254
  }
216
- fn eval(&self, caches: &pattern::Caches, value: Value) -> Verdict {
255
+ fn eval(&self, caches: &runtime::Caches, value: Value) -> Verdict {
217
256
  self.eval_with_view::<false>(caches, value, None)
218
257
  }
219
258
  fn eval_with_view<const BORROWED: bool>(
220
259
  &self,
221
- caches: &pattern::Caches,
260
+ caches: &runtime::Caches,
222
261
  value: Value,
223
262
  view: Option<(&[u8], i32)>,
224
263
  ) -> Verdict {
@@ -232,8 +271,8 @@ impl StringRules {
232
271
  }
233
272
  if let Some(pattern) = &self.pattern {
234
273
  let result = match view {
235
- Some((bytes, encoding)) => pattern.eval_bytes(caches, bytes, encoding),
236
- None => pattern.eval(caches, value),
274
+ Some((bytes, encoding)) => pattern.eval_bytes(caches, value, bytes, encoding),
275
+ None => pattern.assertion(caches, value),
237
276
  };
238
277
  if result != Valid {
239
278
  return result;
@@ -249,16 +288,41 @@ impl StringRules {
249
288
  }
250
289
  }
251
290
  self.content.as_ref().map_or(Valid, |content| match view {
252
- Some((bytes, encoding)) => content.eval_borrowed(value, bytes, encoding),
253
- None => content.eval(value),
291
+ Some((bytes, encoding)) => content.eval_borrowed(caches, value, bytes, encoding),
292
+ None => content.eval(caches, value),
254
293
  })
255
294
  }
256
295
  }
257
296
  #[derive(Debug)]
258
297
  enum DiagnosticDependency {
259
- Required(Value, Vec<Value>, &'static str),
298
+ Required(Value, Vec<RequiredKey>, &'static str),
260
299
  Schema(Value, usize, &'static str),
261
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
+ }
262
326
  #[derive(Clone, Copy, Debug)]
263
327
  struct PropertyTarget {
264
328
  name: Value,
@@ -267,11 +331,13 @@ struct PropertyTarget {
267
331
  }
268
332
  #[derive(Debug)]
269
333
  struct Object {
334
+ header: container::Header,
335
+ inspect_required: bool,
270
336
  record: Option<RecordObject>,
271
337
  min: Option<usize>,
272
338
  max: Option<usize>,
273
- required: Vec<Value>,
274
- required_order: Vec<Value>,
339
+ required: Vec<RequiredKey>,
340
+ required_order: Vec<RequiredKey>,
275
341
  properties: Vec<(Value, usize)>,
276
342
  property_targets: HashMap<tracking::JsonKey, PropertyTarget, BuildHasherDefault<BucketHasher>>,
277
343
  patterns: Vec<(pattern::Pattern, usize)>,
@@ -283,7 +349,7 @@ struct Object {
283
349
  property_indices: Vec<usize>,
284
350
  additional: Option<usize>,
285
351
  names: Option<usize>,
286
- dependent_required: Vec<(Value, Vec<Value>)>,
352
+ dependent_required: Vec<(Value, Vec<RequiredKey>)>,
287
353
  dependent_schemas: Vec<(Value, usize)>,
288
354
  diagnostic_dependencies: Vec<DiagnosticDependency>,
289
355
  unevaluated: Option<usize>,
@@ -352,13 +418,18 @@ impl RecordShortIndex {
352
418
  impl Object {
353
419
  // Keep these calls at their original evaluation positions: property
354
420
  // failures and unsupported children must retain their ordering.
355
- #[inline]
356
- fn check_required(&self, value: Value, undefined: Value, budget: &budget::Budget) -> Verdict {
357
- 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)
358
429
  }
359
- #[inline]
360
430
  fn check_keys(
361
- keys: &[Value],
431
+ keys: &[RequiredKey],
432
+ caches: &runtime::Caches,
362
433
  value: Value,
363
434
  undefined: Value,
364
435
  budget: &budget::Budget,
@@ -367,15 +438,17 @@ impl Object {
367
438
  if !budget.checkpoint() {
368
439
  return Unsupported;
369
440
  }
370
- if missing(value, key, undefined) {
371
- return Invalid;
441
+ match key.missing(caches, value, undefined) {
442
+ Ok(false) => {}
443
+ Ok(true) => return Invalid,
444
+ Err(status) => return status,
372
445
  }
373
446
  }
374
447
  Valid
375
448
  }
376
- #[inline]
377
449
  fn check_dependent_required(
378
450
  &self,
451
+ caches: &runtime::Caches,
379
452
  value: Value,
380
453
  undefined: Value,
381
454
  budget: &budget::Budget,
@@ -385,7 +458,7 @@ impl Object {
385
458
  return Unsupported;
386
459
  }
387
460
  if !missing(value, *name, undefined) {
388
- let status = Self::check_keys(required, value, undefined, budget);
461
+ let status = Self::check_keys(required, caches, value, undefined, budget);
389
462
  if status != Valid {
390
463
  return status;
391
464
  }
@@ -482,10 +555,11 @@ impl Object {
482
555
  // All execution policies use the same property/pattern/additional routing.
483
556
  // Schema-order validity scans have already visited declared properties;
484
557
  // input-order diagnostic scans visit them here as well.
485
- fn visit_value_rules<const PROPERTIES: bool>(
558
+ fn visit_value_rules(
486
559
  &self,
487
- caches: &pattern::Caches,
560
+ caches: &runtime::Caches,
488
561
  budget: &budget::Budget,
562
+ properties: bool,
489
563
  key: Value,
490
564
  location: Option<tracking::JsonKey>,
491
565
  mut visit: impl FnMut(&'static str, Option<Value>, usize) -> Result<(), Verdict>,
@@ -494,7 +568,7 @@ impl Object {
494
568
  // Reuse that result, and skip hashing for pattern-only object rules.
495
569
  let mut matched = if self.properties.is_empty() {
496
570
  false
497
- } else if PROPERTIES {
571
+ } else if properties {
498
572
  if let Some(target) = self
499
573
  .property_targets
500
574
  .get(&location.unwrap_or_else(|| tracking::JsonKey::new(key)))
@@ -549,6 +623,7 @@ impl Object {
549
623
 
550
624
  fn with_record(mut self) -> Self {
551
625
  if self.required_order.len() > 64
626
+ || self.required_order.iter().any(|key| !key.ordinary_string)
552
627
  || !self.patterns.is_empty()
553
628
  || self.names.is_some()
554
629
  || !self.diagnostic_dependencies.is_empty()
@@ -573,7 +648,8 @@ impl Object {
573
648
  )
574
649
  })
575
650
  .collect();
576
- for &name in &self.required_order {
651
+ for key in &self.required_order {
652
+ let name = key.name;
577
653
  fields
578
654
  .entry(tracking::JsonKey::new(name))
579
655
  .or_insert(RecordField {
@@ -593,8 +669,8 @@ impl Object {
593
669
  }
594
670
  // Required names share the field bits already used for duplicate-key
595
671
  // detection. Repeated required names therefore need no extra state.
596
- let required = self.required_order.iter().fold(0, |bits, &name| {
597
- 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
598
674
  });
599
675
  let fields: Vec<_> = fields.into_values().collect();
600
676
  let short_index = RecordShortIndex::compile(&fields);
@@ -615,9 +691,15 @@ pub struct Roots {
615
691
  #[derive(Default)]
616
692
  pub struct Engine {
617
693
  nodes: Vec<Vec<Op>>,
694
+ programs: Vec<Value>,
618
695
  pattern_cache_count: usize,
619
696
  error_operands: Vec<Vec<(i32, Value)>>,
620
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,
621
703
  memoizable: Vec<bool>,
622
704
  memo_contextual: Vec<bool>,
623
705
  memo_guarded: Vec<bool>,
@@ -638,6 +720,7 @@ pub struct Engine {
638
720
  root: usize,
639
721
  fixnum_flag: Value,
640
722
  compiler: Value,
723
+ runtime_messages: bool,
641
724
  validate_format: bool,
642
725
  validate_content: bool,
643
726
  undefined: Value,
@@ -657,6 +740,24 @@ impl Engine {
657
740
  }
658
741
  Ok(self.retain(result))
659
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
+ }
660
761
  fn values(&mut self, value: Value) -> Vec<Value> {
661
762
  if unsafe { sn_same(value, sn_nil()) } != 0 {
662
763
  return vec![];
@@ -760,7 +861,7 @@ impl Engine {
760
861
  // shape-independent leaves can ignore resource/path metadata.
761
862
  ka == kb
762
863
  && match (self.nodes[ca].as_slice(), self.nodes[cb].as_slice()) {
763
- ([Op::Type(a)], [Op::Type(b)]) => a == b,
864
+ ([Op::Type(a, ua)], [Op::Type(b, ub)]) => a == b && ua == ub,
764
865
  ([Op::Boolean(a)], [Op::Boolean(b)]) => a == b,
765
866
  _ => false,
766
867
  }
@@ -780,8 +881,10 @@ impl Engine {
780
881
  let index = self.nodes.len();
781
882
  self.indices.insert(program, index);
782
883
  self.nodes.push(vec![]);
884
+ self.programs.push(program);
783
885
  self.error_operands.push(vec![]);
784
886
  self.tracks.push(false);
887
+ self.kind_demand.push(KindDemand::None);
785
888
  self.metadata.push(scope::Metadata::default());
786
889
  let code = self.field(program, b"code\0")?;
787
890
  let flags = unsafe { sn_integer(sn_entry(code, 0)) };
@@ -795,19 +898,20 @@ impl Engine {
795
898
  error_operands.push((opcode, operand));
796
899
  let op = match opcode {
797
900
  OP_BOOLEAN => Op::Boolean(unsafe { sn_true(operand) } != 0),
798
- OP_TYPE_NULL => Op::Type(TYPE_NULL),
799
- OP_TYPE_BOOLEAN => Op::Type(TYPE_BOOLEAN),
800
- OP_TYPE_OBJECT => Op::Type(TYPE_OBJECT),
801
- OP_TYPE_ARRAY => Op::Type(TYPE_ARRAY),
802
- OP_TYPE_NUMBER => Op::Type(TYPE_NUMBER),
803
- OP_TYPE_INTEGER => Op::Type(TYPE_INTEGER),
804
- 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),
805
908
  OP_TYPES => {
806
909
  let mask = self.field(operand, b"mask\0")?;
807
- Op::Type(unsafe { sn_integer(mask) } as i32)
910
+ Op::Type(unsafe { sn_integer(mask) } as i32, true)
808
911
  }
809
912
  OP_CONST => Op::Const(operand),
810
913
  OP_ENUM => Op::Enum(Box::new(enumeration::Enumeration::compile(
914
+ operand,
811
915
  self.values(operand),
812
916
  self.fixnum_flag,
813
917
  ))),
@@ -856,9 +960,10 @@ impl Engine {
856
960
  None
857
961
  };
858
962
  let assertion = self.field(operand, b"format_assertion\0")?;
859
- let format = if (self.validate_format || unsafe { sn_true(assertion) } != 0)
860
- && unsafe { sn_same(format, sn_nil()) } == 0
861
- {
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 {
862
967
  let name = self.field(format, b"name\0")?;
863
968
  if unsafe { sn_same(name, sn_nil()) } != 0 {
864
969
  None
@@ -868,6 +973,10 @@ impl Engine {
868
973
  } else {
869
974
  None
870
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);
871
980
  Op::String(
872
981
  if opcode == OP_TYPED_STRING {
873
982
  TYPE_STRING
@@ -875,11 +984,16 @@ impl Engine {
875
984
  0
876
985
  },
877
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,
878
992
  format,
879
993
  content,
880
994
  min: self.size_field(operand, b"min_length\0")?,
881
995
  max: self.size_field(operand, b"max_length\0")?,
882
- pattern: if unsafe { sn_same(pattern, sn_nil()) } != 0 {
996
+ pattern: if unsafe { sn_truthy(pattern) } == 0 {
883
997
  None
884
998
  } else {
885
999
  Some(pattern::Pattern::compile(
@@ -912,7 +1026,18 @@ impl Engine {
912
1026
  0
913
1027
  },
914
1028
  Box::new(Array {
915
- 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,
916
1041
  min: self.size_field(operand, b"min_items\0")?,
917
1042
  max: self.size_field(operand, b"max_items\0")?,
918
1043
  prefix,
@@ -922,6 +1047,9 @@ impl Engine {
922
1047
  contains: self.child(operand, b"contains\0")?,
923
1048
  min_contains: self.size_field(operand, b"min_contains\0")?.unwrap_or(1),
924
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,
925
1053
  unevaluated: self.child(operand, b"unevaluated\0")?,
926
1054
  }),
927
1055
  )
@@ -944,14 +1072,21 @@ impl Engine {
944
1072
  let pattern_group =
945
1073
  pattern::PatternGroup::compile(&pattern_names, &patterns);
946
1074
  let required = self.field(operand, b"required\0")?;
947
- 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);
948
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();
949
1085
  let properties = self.field(operand, b"properties\0")?;
950
1086
  let properties = self.compile_map(properties)?;
951
- if required
1087
+ if properties
952
1088
  .iter()
953
- .chain(properties.iter().map(|(key, _)| key))
954
- .any(|&key| unsafe { sn_kind(key) } != TYPE_STRING)
1089
+ .any(|&(key, _)| unsafe { sn_kind(key) } != TYPE_STRING)
955
1090
  {
956
1091
  ops.push(Op::Unsupported);
957
1092
  continue;
@@ -983,7 +1118,7 @@ impl Engine {
983
1118
  // content-keyed sets and the already compiled property index.
984
1119
  let indexed_required = properties.len().saturating_mul(required.len()) > 64;
985
1120
  let required_keys: Option<tracking::KeySet> = indexed_required.then(|| {
986
- required
1121
+ required_strings
987
1122
  .iter()
988
1123
  .map(|&name| tracking::JsonKey::new(name))
989
1124
  .collect()
@@ -993,7 +1128,7 @@ impl Engine {
993
1128
  .map(|key| {
994
1129
  required_keys.as_ref().map_or_else(
995
1130
  || {
996
- required.iter().any(
1131
+ required_strings.iter().any(
997
1132
  |&name| unsafe { sn_string_equal(key.0, name) } != 0,
998
1133
  )
999
1134
  },
@@ -1008,7 +1143,11 @@ impl Engine {
1008
1143
  .collect();
1009
1144
  let required = required
1010
1145
  .into_iter()
1011
- .filter(|&name| {
1146
+ .filter(|key| {
1147
+ if !key.ordinary_string {
1148
+ return true;
1149
+ }
1150
+ let name = key.name;
1012
1151
  if indexed_required {
1013
1152
  !property_targets.contains_key(&tracking::JsonKey::new(name))
1014
1153
  } else {
@@ -1026,7 +1165,7 @@ impl Engine {
1026
1165
  let name = self.retain(unsafe { sn_entry(pair, 0) });
1027
1166
  let dependency = self.retain(unsafe { sn_entry(pair, 1) });
1028
1167
  if unsafe { sn_kind(dependency) } == TYPE_ARRAY {
1029
- let names = self.values(dependency);
1168
+ let names = self.required_keys(dependency);
1030
1169
  diagnostic_dependencies.push(DiagnosticDependency::Required(
1031
1170
  name,
1032
1171
  names.clone(),
@@ -1045,7 +1184,7 @@ impl Engine {
1045
1184
  }
1046
1185
  for pair in self.values(dependent) {
1047
1186
  let name = self.retain(unsafe { sn_entry(pair, 0) });
1048
- let names = self.values(unsafe { sn_entry(pair, 1) });
1187
+ let names = self.required_keys(unsafe { sn_entry(pair, 1) });
1049
1188
  diagnostic_dependencies.push(DiagnosticDependency::Required(
1050
1189
  name,
1051
1190
  names.clone(),
@@ -1059,19 +1198,25 @@ impl Engine {
1059
1198
  DiagnosticDependency::Schema(name, child, "dependentSchemas")
1060
1199
  }));
1061
1200
  dependent_schemas.extend(schemas);
1062
- if dependent_required.iter().any(|(name, keys)| {
1063
- (unsafe { sn_kind(*name) }) != TYPE_STRING
1064
- || keys
1065
- .iter()
1066
- .any(|&key| unsafe { sn_kind(key) } != TYPE_STRING)
1067
- }) || dependent_schemas
1201
+ if dependent_required
1068
1202
  .iter()
1069
- .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)
1070
1207
  {
1071
1208
  ops.push(Op::Unsupported);
1072
1209
  continue;
1073
1210
  }
1074
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,
1075
1220
  record: None,
1076
1221
  min: self.size_field(operand, b"min_properties\0")?,
1077
1222
  max: self.size_field(operand, b"max_properties\0")?,
@@ -1148,7 +1293,8 @@ impl Engine {
1148
1293
  let mut target = 0;
1149
1294
  if unsafe { sn_resolve(self.compiler, program, reference, &mut target) } != 0 {
1150
1295
  if unsafe { sn_resolution_error(target) } != 0 {
1151
- Op::Unsupported
1296
+ let message = self.field(target, b"message\0")?;
1297
+ Op::UnresolvedRef(message)
1152
1298
  } else {
1153
1299
  return Err(target);
1154
1300
  }
@@ -1166,12 +1312,53 @@ impl Engine {
1166
1312
  self.error_operands[index] = error_operands;
1167
1313
  Ok(index)
1168
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
+ }
1169
1356
  }
1170
1357
 
1171
- // A view of immutable compiled data and validator-owned regex search caches.
1358
+ // A view of immutable compiled data and validator-owned runtime caches.
1172
1359
  struct Execution<'a> {
1173
1360
  engine: &'a Engine,
1174
- caches: &'a pattern::Caches,
1361
+ caches: &'a runtime::Caches,
1175
1362
  memo: memo::Memo,
1176
1363
  budget: budget::Budget,
1177
1364
  }
@@ -1193,9 +1380,14 @@ impl Execution<'_> {
1193
1380
  value: Value,
1194
1381
  depth: usize,
1195
1382
  active: scope::Context<'_>,
1196
- known_kind: Option<i32>,
1383
+ known_kind: Option<&InstanceKind>,
1197
1384
  ) -> Verdict {
1198
- 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() {
1199
1391
  return Unsupported;
1200
1392
  }
1201
1393
  let Some(key) = self
@@ -1223,7 +1415,7 @@ impl Execution<'_> {
1223
1415
  value: Value,
1224
1416
  depth: usize,
1225
1417
  active: scope::Context<'_>,
1226
- known_kind: Option<i32>,
1418
+ known_kind: Option<&InstanceKind>,
1227
1419
  ) -> Verdict {
1228
1420
  let frame = scope::ScopeFrame {
1229
1421
  resource: self.metadata[node].resource,
@@ -1234,7 +1426,13 @@ impl Execution<'_> {
1234
1426
  .or(self.metadata[node].primary_binding),
1235
1427
  };
1236
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);
1237
1432
  if self.tracks[node] {
1433
+ if let Some(result) = self.caches.callback_tracked(node, value) {
1434
+ return result;
1435
+ }
1238
1436
  return match known_kind {
1239
1437
  Some(kind) => {
1240
1438
  self.eval_tracked_kind(node, value, depth, active, kind)
@@ -1248,26 +1446,42 @@ impl Execution<'_> {
1248
1446
  }
1249
1447
  if let [Op::String(required, rules)] = self.nodes[node].as_slice()
1250
1448
  && rules.pattern.is_some()
1449
+ && !self.caches.scalar_callback()
1251
1450
  {
1252
1451
  return rules.eval_guarded(self.caches, *required, value);
1253
1452
  }
1254
- let kind = known_kind.unwrap_or_else(|| {
1255
- if value & self.fixnum_flag != 0 {
1256
- TYPE_INTEGRAL_NUMBER
1257
- } else {
1258
- self.instance_kind(node, value)
1259
- }
1260
- });
1261
- 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) {
1262
1464
  return Unsupported;
1263
1465
  }
1264
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
+ }
1265
1472
  if !self.budget.checkpoint() {
1266
1473
  return Unsupported;
1267
1474
  }
1268
1475
  let outcome = match op {
1269
1476
  Op::Ref(target) => {
1270
- 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
+ {
1271
1485
  Valid
1272
1486
  } else {
1273
1487
  let frame = RefFrame {
@@ -1275,15 +1489,17 @@ impl Execution<'_> {
1275
1489
  value,
1276
1490
  parent: active.active,
1277
1491
  };
1492
+ let reference = self.caches.reference_frame(program, value);
1493
+ let _guard = self.caches.reference_guard(&reference);
1278
1494
  self.eval_with_kind(
1279
- self.reference_target(*target, active),
1495
+ target,
1280
1496
  value,
1281
1497
  depth + 1,
1282
1498
  scope::Context {
1283
1499
  active: Some(&frame),
1284
1500
  ..active
1285
1501
  },
1286
- Some(kind),
1502
+ Some(kind_state),
1287
1503
  )
1288
1504
  }
1289
1505
  }
@@ -1293,7 +1509,8 @@ impl Execution<'_> {
1293
1509
  if !self.budget.checkpoint() {
1294
1510
  return Unsupported;
1295
1511
  }
1296
- 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));
1297
1514
  if outcome != Valid {
1298
1515
  break;
1299
1516
  }
@@ -1309,30 +1526,61 @@ impl Execution<'_> {
1309
1526
  match evaluation::scan_branches(
1310
1527
  nodes,
1311
1528
  evaluation::BranchPolicy::Validity(branch_kind),
1312
- |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(()),
1313
1539
  ) {
1314
1540
  Ok(result) => verdict(branch_kind.accepts(result.matches)),
1315
1541
  Err(status) => status,
1316
1542
  }
1317
1543
  }
1318
- Op::Not(n) => match self.eval_with_kind(*n, value, depth + 1, active, Some(kind)) {
1319
- Valid => Invalid,
1320
- Invalid => Valid,
1321
- Unsupported => Unsupported,
1322
- },
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
+ }
1323
1551
  Op::Conditional(c, yes, no) => {
1324
- 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)) {
1325
1553
  Unsupported => Unsupported,
1326
1554
  result => match if result == Valid { yes } else { no } {
1327
1555
  Some(n) => {
1328
- self.eval_with_kind(*n, value, depth + 1, active, Some(kind))
1556
+ self.eval_with_kind(*n, value, depth + 1, active, Some(kind_state))
1329
1557
  }
1330
1558
  None => Valid,
1331
1559
  },
1332
1560
  }
1333
1561
  }
1334
1562
  Op::Array(required, rules) => {
1335
- 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 {
1336
1584
  Invalid
1337
1585
  } else if kind != TYPE_ARRAY {
1338
1586
  Valid
@@ -1341,12 +1589,29 @@ impl Execution<'_> {
1341
1589
  }
1342
1590
  }
1343
1591
  Op::Object(required, rules) => {
1344
- 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 {
1345
1600
  Invalid
1346
1601
  } else if kind != TYPE_OBJECT {
1347
1602
  Valid
1603
+ } else if rules.header_only() {
1604
+ rules.valid_header(self.caches, value, self.undefined, &self.budget)
1348
1605
  } else {
1349
- 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
+ }
1350
1615
  }
1351
1616
  }
1352
1617
  _ => self.leaf(op, kind, value, depth),
@@ -1360,20 +1625,49 @@ impl Execution<'_> {
1360
1625
  fn leaf(&self, op: &Op, kind: i32, value: Value, depth: usize) -> Verdict {
1361
1626
  match op {
1362
1627
  Op::Unsupported => Unsupported,
1628
+ Op::UnresolvedRef(_) => Invalid,
1363
1629
  Op::Boolean(v) => verdict(*v),
1364
- Op::Type(mask) => verdict(kind & mask != 0),
1365
- Op::Const(v) => equal_budgeted(*v, value, depth, &self.budget),
1366
- 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
+ }
1367
1655
  Op::Number(required, rules) => {
1656
+ if kind == TYPE_UNSUPPORTED || self.caches.dynamic_calls() {
1657
+ return rules.runtime_valid(self.caches, *required, value);
1658
+ }
1368
1659
  if *required != 0 && kind & required == 0 {
1369
1660
  Invalid
1370
1661
  } else if kind & TYPE_NUMBER == 0 {
1371
1662
  Valid
1372
1663
  } else {
1373
- rules.eval(value)
1664
+ rules.eval(self.caches, value, &self.budget)
1374
1665
  }
1375
1666
  }
1376
1667
  Op::String(required, rules) => {
1668
+ if kind == TYPE_UNSUPPORTED || self.caches.dynamic_calls() {
1669
+ return rules.runtime_valid(self.caches, *required, value);
1670
+ }
1377
1671
  if *required != 0 && kind != TYPE_STRING {
1378
1672
  Invalid
1379
1673
  } else if kind != TYPE_STRING {
@@ -1385,6 +1679,22 @@ impl Execution<'_> {
1385
1679
  _ => unreachable!("applicators must be evaluated with their context"),
1386
1680
  }
1387
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
+ }
1388
1698
  fn array(&self, r: &Array, value: Value, depth: usize, active: scope::Context<'_>) -> Verdict {
1389
1699
  if !self.budget.checkpoint() {
1390
1700
  return Unsupported;
@@ -1394,7 +1704,7 @@ impl Execution<'_> {
1394
1704
  return Invalid;
1395
1705
  }
1396
1706
  if r.unique {
1397
- let result = unique(value, depth, self.fixnum_flag, &self.budget);
1707
+ let result = self.unique_values(value, depth);
1398
1708
  if result != Valid {
1399
1709
  return result;
1400
1710
  }
@@ -1438,15 +1748,18 @@ impl Execution<'_> {
1438
1748
  unsafe { std::slice::from_raw_parts(sn_array_data(value), len) }
1439
1749
  };
1440
1750
  let primitive = match self.nodes[child].as_slice() {
1441
- [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 => {
1442
1752
  Some(&[][..])
1443
1753
  }
1444
1754
  [Op::Number(_, rules)] => rules.fast_limits.as_deref(),
1445
1755
  _ => None,
1446
1756
  };
1757
+ let primitive = primitive.filter(|_| !self.caches.dynamic_calls());
1447
1758
  let number = match self.nodes[child].as_slice() {
1448
1759
  [Op::Number(required, rules)]
1449
- if depth <= MAX_EVALUATION_DEPTH && !self.tracks[child] =>
1760
+ if depth <= MAX_EVALUATION_DEPTH
1761
+ && !self.tracks[child]
1762
+ && !self.caches.dynamic_calls() =>
1450
1763
  {
1451
1764
  Some((*required, rules))
1452
1765
  }
@@ -1479,47 +1792,60 @@ impl Execution<'_> {
1479
1792
  .or(self.metadata[child].primary_binding),
1480
1793
  };
1481
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
+ );
1482
1799
  let pattern = string.and_then(|(required, rules)| {
1483
1800
  rules
1484
1801
  .pattern_only()
1485
1802
  .map(|pattern| (required, pattern))
1486
- .filter(|_| !self.budget.timed())
1803
+ .filter(|_| !self.budget.timed() && !self.caches.dynamic_calls())
1487
1804
  });
1488
1805
  let format = string.and_then(|(required, rules)| {
1489
1806
  rules
1490
1807
  .format_only()
1491
1808
  .map(|format| (required, format))
1492
- .filter(|_| !self.budget.timed())
1809
+ .filter(|_| !self.budget.timed() && !self.caches.dynamic_calls())
1493
1810
  });
1494
1811
  let content = string.and_then(|(required, rules)| {
1495
1812
  rules
1496
1813
  .content_only()
1497
1814
  .map(|content| (required, content))
1498
- .filter(|_| !self.budget.timed())
1815
+ .filter(|_| !self.budget.timed() && !self.caches.dynamic_calls())
1499
1816
  });
1500
1817
  if let Some((required, pattern)) = pattern {
1501
- let result = pattern.eval_items(
1818
+ let result = pattern.visit_invalid_items(
1502
1819
  self.caches,
1503
1820
  required,
1504
1821
  values.get(r.prefix.len()..).unwrap_or(&[]),
1822
+ |_, item| self.eval(child, item, depth, active),
1505
1823
  );
1506
1824
  if result != Valid {
1507
1825
  return result;
1508
1826
  }
1509
1827
  } else if let Some((required, format)) = format {
1510
- let result =
1511
- 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
+ );
1512
1833
  if result != Valid {
1513
1834
  return result;
1514
1835
  }
1515
1836
  } else if let Some((required, content)) = content {
1516
- let result =
1517
- 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
+ );
1518
1843
  if result != Valid {
1519
1844
  return result;
1520
1845
  }
1521
1846
  } else if let Some((required, rules)) = object {
1522
1847
  let result = self.record_items(
1848
+ child,
1523
1849
  rules,
1524
1850
  required,
1525
1851
  values.get(r.prefix.len()..).unwrap_or(&[]),
@@ -1546,16 +1872,21 @@ impl Execution<'_> {
1546
1872
  return Unsupported;
1547
1873
  }
1548
1874
  let result = if let Some((required, rules)) = number {
1549
- 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
+ }
1550
1881
  } else if let Some((required, rules)) = string {
1551
1882
  if rules.pattern.is_some() {
1552
1883
  rules.eval_guarded(self.caches, required, item)
1553
1884
  } else if rules.length_only() {
1554
- rules.eval_length_guarded(required, item)
1885
+ rules.eval_length_guarded(self.caches, required, item)
1555
1886
  } else {
1556
1887
  let kind = unsafe { sn_kind(item) };
1557
- if kind == TYPE_UNSUPPORTED {
1558
- Unsupported
1888
+ if kind == TYPE_UNSUPPORTED || self.caches.dynamic_calls() {
1889
+ rules.runtime_valid(self.caches, required, item)
1559
1890
  } else if kind != TYPE_STRING {
1560
1891
  verdict(required == 0)
1561
1892
  } else {
@@ -1572,18 +1903,30 @@ impl Execution<'_> {
1572
1903
  }
1573
1904
  }
1574
1905
  if let Some(child) = r.contains {
1575
- 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
+ {
1576
1909
  return status;
1577
1910
  }
1578
1911
  let matches = match evaluation::scan_contains(
1579
1912
  len,
1913
+ if r.count_contains {
1914
+ evaluation::ContainsPolicy::AllMatches
1915
+ } else {
1916
+ evaluation::ContainsPolicy::FirstMatch
1917
+ },
1580
1918
  |index| self.eval(child, unsafe { sn_entry(value, index) }, depth, active),
1581
1919
  |_| {},
1582
1920
  ) {
1583
1921
  Ok(matches) => matches,
1584
1922
  Err(status) => return status,
1585
1923
  };
1586
- 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 {
1587
1930
  return Invalid;
1588
1931
  }
1589
1932
  }
@@ -1604,7 +1947,7 @@ impl Execution<'_> {
1604
1947
  return None;
1605
1948
  };
1606
1949
  let total = match op {
1607
- Op::Type(_) | Op::Boolean(_) => true,
1950
+ Op::Type(..) | Op::Boolean(_) => true,
1608
1951
  Op::Const(expected) => expected & self.fixnum_flag != 0,
1609
1952
  Op::Number(_, rules) => rules.fast_limits.is_some(),
1610
1953
  _ => false,
@@ -1630,7 +1973,6 @@ impl Execution<'_> {
1630
1973
  |index| self.leaf(op, TYPE_INTEGRAL_NUMBER, values[index], depth) == Valid,
1631
1974
  ))
1632
1975
  }
1633
- #[inline(never)]
1634
1976
  fn integer_items(
1635
1977
  &self,
1636
1978
  limits: &[(i32, i64)],
@@ -1653,7 +1995,9 @@ impl Execution<'_> {
1653
1995
  );
1654
1996
  let number = match self.nodes[child].as_slice() {
1655
1997
  [Op::Number(required, rules)]
1656
- if depth <= MAX_EVALUATION_DEPTH && !self.tracks[child] =>
1998
+ if depth <= MAX_EVALUATION_DEPTH
1999
+ && !self.tracks[child]
2000
+ && !self.caches.dynamic_calls() =>
1657
2001
  {
1658
2002
  Some((*required, rules))
1659
2003
  }
@@ -1672,7 +2016,12 @@ impl Execution<'_> {
1672
2016
  let n = immediate_integer(value);
1673
2017
  verdict(n >= min && n <= max)
1674
2018
  } else if let Some((required, rules)) = number {
1675
- 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
+ }
1676
2025
  } else {
1677
2026
  self.eval(child, value, depth, active)
1678
2027
  };
@@ -1682,15 +2031,16 @@ impl Execution<'_> {
1682
2031
  }
1683
2032
  Valid
1684
2033
  }
1685
- #[inline(never)]
1686
2034
  fn record_items(
1687
2035
  &self,
2036
+ node: usize,
1688
2037
  rules: &Object,
1689
2038
  required: i32,
1690
2039
  values: &[Value],
1691
2040
  depth: usize,
1692
2041
  active: scope::Context<'_>,
1693
2042
  ) -> Verdict {
2043
+ let header_only = rules.header_only();
1694
2044
  for &value in values {
1695
2045
  if !self.budget.checkpoint() {
1696
2046
  return Unsupported;
@@ -1700,13 +2050,29 @@ impl Execution<'_> {
1700
2050
  } else {
1701
2051
  unsafe { sn_shallow_kind(value) }
1702
2052
  };
1703
- 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 {
1704
2057
  Unsupported
1705
2058
  } else if kind != TYPE_OBJECT {
1706
2059
  verdict(required == 0)
1707
2060
  } else {
1708
2061
  self.object(rules, value, depth, active)
1709
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
+ };
1710
2076
  if result != Valid {
1711
2077
  return result;
1712
2078
  }
@@ -1723,7 +2089,9 @@ impl Execution<'_> {
1723
2089
  if !self.budget.checkpoint() {
1724
2090
  return Unsupported;
1725
2091
  }
1726
- if let Some(record) = &r.record {
2092
+ if !self.caches.runtime_order()
2093
+ && let Some(record) = &r.record
2094
+ {
1727
2095
  // C initializes only the written key/value pairs. The untouched
1728
2096
  // tail is never read, including empty and unsupported records.
1729
2097
  let mut buffer = std::mem::MaybeUninit::<[Value; 16]>::uninit();
@@ -1805,26 +2173,36 @@ impl Execution<'_> {
1805
2173
  if bounds(len, r.min, r.max) == Invalid {
1806
2174
  return Invalid;
1807
2175
  }
1808
- let status = r.check_required(value, self.undefined, &self.budget);
2176
+ let status = r.check_required(self.caches, value, self.undefined, &self.budget);
1809
2177
  if status != Valid {
1810
2178
  return status;
1811
2179
  }
1812
- let found = match r.visit_properties(
1813
- value,
1814
- len,
1815
- self.undefined,
1816
- &self.budget,
1817
- |_, child, item| self.eval(child, item, depth, active),
1818
- ) {
1819
- Ok(found) => found,
1820
- 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
+ }
1821
2194
  };
1822
- 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
+ {
1823
2200
  let mut context = ObjectContext {
1824
2201
  engine: self,
1825
2202
  rules: r,
1826
2203
  depth,
1827
2204
  active,
2205
+ names_only: false,
1828
2206
  };
1829
2207
  let result =
1830
2208
  unsafe { sn_object_each(value, (&mut context as *mut ObjectContext).cast()) };
@@ -1832,7 +2210,13 @@ impl Execution<'_> {
1832
2210
  return if result == 0 { Invalid } else { Unsupported };
1833
2211
  }
1834
2212
  }
1835
- 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);
1836
2220
  if status != Valid {
1837
2221
  return status;
1838
2222
  }
@@ -1849,13 +2233,76 @@ impl Execution<'_> {
1849
2233
  }
1850
2234
  Valid
1851
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
+ }
1852
2269
  fn instance_kind(&self, node: usize, value: Value) -> i32 {
1853
- 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
+ {
1854
2282
  unsafe { sn_shallow_kind(value) }
1855
2283
  } else {
1856
2284
  unsafe { sn_kind(value) }
1857
2285
  }
1858
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
+ }
1859
2306
  fn record_value(
1860
2307
  &self,
1861
2308
  node: usize,
@@ -1870,7 +2317,7 @@ impl Execution<'_> {
1870
2317
  [Op::String(required, rules)]
1871
2318
  if depth <= MAX_EVALUATION_DEPTH && rules.length_only() =>
1872
2319
  {
1873
- rules.eval_length_guarded(*required, value)
2320
+ rules.eval_length_guarded(self.caches, *required, value)
1874
2321
  }
1875
2322
  [Op::String(required, rules)]
1876
2323
  if depth <= MAX_EVALUATION_DEPTH && rules.pattern.is_some() =>
@@ -1878,19 +2325,15 @@ impl Execution<'_> {
1878
2325
  rules.eval_guarded(self.caches, *required, value)
1879
2326
  }
1880
2327
  [
1881
- op @ (Op::Type(_)
2328
+ op @ (Op::Type(..)
1882
2329
  | Op::Boolean(_)
1883
2330
  | Op::Number(..)
1884
2331
  | Op::String(..)
1885
2332
  | Op::Const(_)
1886
2333
  | Op::Enum(_)),
1887
2334
  ] if depth <= MAX_EVALUATION_DEPTH => {
1888
- let kind = if value & self.fixnum_flag != 0 {
1889
- TYPE_INTEGRAL_NUMBER
1890
- } else {
1891
- unsafe { sn_kind(value) }
1892
- };
1893
- if kind == TYPE_UNSUPPORTED {
2335
+ let kind = self.diagnostic_kind(node, value, &InstanceKind::new(TYPE_UNCHECKED));
2336
+ if self.unsupported_kind(node, kind) {
1894
2337
  Unsupported
1895
2338
  } else {
1896
2339
  self.leaf(op, kind, value, depth)
@@ -1969,12 +2412,21 @@ fn record_entry(c: &mut RecordContext<'_, '_>, key: Value, value: Value) -> Verd
1969
2412
  {
1970
2413
  c.status = if c.depth <= MAX_EVALUATION_DEPTH {
1971
2414
  match field.and_then(|field| field.check) {
1972
- 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
+ {
1973
2418
  let n = immediate_integer(value);
1974
2419
  verdict(n >= min && n <= max)
1975
2420
  }
1976
- Some(RecordCheck::StringLength(required, min, max)) => {
1977
- 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
+ }
1978
2430
  }
1979
2431
  _ => c.engine.record_value(child, value, c.depth, c.active),
1980
2432
  }
@@ -2038,20 +2490,79 @@ fn equal_cached(
2038
2490
  return Unsupported;
2039
2491
  }
2040
2492
  if depth > MAX_EVALUATION_DEPTH {
2041
- 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);
2042
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> {
2043
2554
  let (ka, kb) = unsafe { (sn_kind(a), sn_kind(b)) };
2044
2555
  if ka == TYPE_UNSUPPORTED || kb == TYPE_UNSUPPORTED {
2045
- return Unsupported;
2556
+ return Err(Unsupported);
2046
2557
  }
2047
2558
  if ka & TYPE_NUMBER != 0 && kb & TYPE_NUMBER != 0 {
2048
2559
  let (ia, ib) = unsafe { (sn_fixnum(a) != 0, sn_fixnum(b) != 0) };
2049
2560
  let (fa, fb) = unsafe { (sn_is_float(a) != 0, sn_is_float(b) != 0) };
2050
2561
  if ia && ib {
2051
- return verdict(unsafe { sn_integer(a) == sn_integer(b) });
2562
+ return Err(verdict(unsafe { sn_integer(a) == sn_integer(b) }));
2052
2563
  }
2053
2564
  if fa && fb {
2054
- return verdict(unsafe { sn_float(a) == sn_float(b) });
2565
+ return Err(verdict(unsafe { sn_float(a) == sn_float(b) }));
2055
2566
  }
2056
2567
  if (ia && fb) || (fa && ib) {
2057
2568
  let (n, f) = unsafe {
@@ -2063,16 +2574,16 @@ fn equal_cached(
2063
2574
  };
2064
2575
  // Convert the float to an integer, never the integer to a rounded float.
2065
2576
  // Fixnums are strictly inside i64's bounds on supported CRuby targets.
2066
- return verdict(
2577
+ return Err(verdict(
2067
2578
  f.is_finite()
2068
2579
  && f.trunc() == f
2069
2580
  && f >= i64::MIN as f64
2070
2581
  && f < i64::MAX as f64
2071
2582
  && f as i64 == n,
2072
- );
2583
+ ));
2073
2584
  }
2074
2585
  if !fa && !fb {
2075
- return verdict(
2586
+ return Err(verdict(
2076
2587
  unsafe {
2077
2588
  if ia {
2078
2589
  sn_big_equal(b, a)
@@ -2080,80 +2591,150 @@ fn equal_cached(
2080
2591
  sn_big_equal(a, b)
2081
2592
  }
2082
2593
  } != 0,
2083
- );
2594
+ ));
2084
2595
  }
2085
- return if fa {
2596
+ return Err(if fa {
2086
2597
  number::integer_float_equal(b, unsafe { sn_float(a) })
2087
2598
  } else {
2088
2599
  number::integer_float_equal(a, unsafe { sn_float(b) })
2089
- };
2600
+ });
2090
2601
  }
2091
2602
  // JSON container equality visits every value, even for the same object:
2092
2603
  // nested NaN values are unequal and unsupported values need VM evaluation.
2093
2604
  if ka != TYPE_ARRAY && ka != TYPE_OBJECT && unsafe { sn_same(a, b) } != 0 {
2094
- return Valid;
2605
+ return Err(Valid);
2095
2606
  }
2096
2607
  if ka != kb {
2097
- return Invalid;
2608
+ return Err(Invalid);
2098
2609
  }
2099
2610
  if unsafe { sn_same_class(a, b) } == 0 {
2100
- return Invalid;
2611
+ return Err(Invalid);
2101
2612
  }
2102
- let container = ka == TYPE_ARRAY || ka == TYPE_OBJECT;
2103
- let key = (a, b, depth);
2104
- if container
2105
- && memo.eligible(depth)
2106
- && let Some(result) = memo.get(key)
2107
- {
2108
- 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),
2109
2617
  }
2110
- let result = match ka {
2111
- TYPE_STRING => verdict(unsafe { sn_string_equal(a, b) } != 0),
2112
- TYPE_ARRAY => {
2113
- let len = unsafe { sn_len(a) };
2114
- if len != unsafe { sn_len(b) } {
2115
- 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
+ }
2116
2692
  }
2117
- for i in 0..len {
2118
- let result = equal_cached(
2119
- unsafe { sn_entry(a, i) },
2120
- unsafe { sn_entry(b, i) },
2121
- depth + 1,
2122
- memo,
2123
- budget,
2124
- );
2125
- if result != Valid {
2126
- 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));
2127
2697
  }
2698
+ work.push(EqualityWork::Compare(pair.0, pair.1, depth));
2128
2699
  }
2129
- Valid
2130
- }
2131
- TYPE_OBJECT => {
2132
- if unsafe { sn_len(a) != sn_len(b) } {
2133
- 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
+ }
2134
2716
  }
2135
- let mut context = EqualityContext {
2136
- other: b,
2137
- depth: depth + 1,
2138
- memo,
2139
- budget,
2140
- };
2141
- decode(unsafe {
2142
- sn_hash_each(
2143
- a,
2144
- (&mut context as *mut EqualityContext<'_>).cast(),
2145
- equal_entry,
2146
- )
2147
- })
2148
2717
  }
2149
- TYPE_UNSUPPORTED => Unsupported,
2150
- _ => Invalid,
2151
- };
2152
- if container && memo.eligible(depth) {
2153
- memo.record(key, result);
2154
2718
  }
2155
- result
2719
+ Valid
2720
+ }
2721
+
2722
+ struct EqualityEntries<'a> {
2723
+ values: Vec<(Value, Value)>,
2724
+ budget: &'a budget::Budget,
2156
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
+
2157
2738
  fn decode(result: i32) -> Verdict {
2158
2739
  match result {
2159
2740
  VERDICT_INVALID => Invalid,
@@ -2321,13 +2902,12 @@ fn fingerprint_cached(
2321
2902
  // before the depth-based cache handles deeper shared graphs.
2322
2903
  let cacheable = container && memo.eligible_container(depth, unsafe { len.assume_init() });
2323
2904
  let key = (value, depth);
2324
- if cacheable
2325
- && let Some(result) = memo.get(key)
2326
- {
2905
+ if cacheable && let Some(result) = memo.get(key) {
2327
2906
  return result;
2328
2907
  }
2329
2908
  let result = match kind {
2330
2909
  TYPE_INTEGRAL_NUMBER => Some(number::integer_fingerprint(value)),
2910
+ TYPE_IDENTITY_SCALAR => Some(mix(value as u64 ^ TYPE_IDENTITY_SCALAR as u64)),
2331
2911
  TYPE_NULL => Some(mix(TYPE_NULL as u64)),
2332
2912
  TYPE_BOOLEAN => Some(mix(if unsafe { len.assume_init() } != 0 {
2333
2913
  2
@@ -2429,21 +3009,7 @@ fn unique(value: Value, depth: usize, fixnum_flag: Value, budget: &budget::Budge
2429
3009
  return Unsupported;
2430
3010
  }
2431
3011
  if len <= 16 {
2432
- for i in 0..len {
2433
- for previous in 0..i {
2434
- match equal_budgeted(
2435
- unsafe { sn_entry(value, previous) },
2436
- unsafe { sn_entry(value, i) },
2437
- depth,
2438
- budget,
2439
- ) {
2440
- Valid => return Invalid,
2441
- Unsupported => return Unsupported,
2442
- Invalid => {}
2443
- }
2444
- }
2445
- }
2446
- return Valid;
3012
+ return unique_linear(value, depth, budget, false);
2447
3013
  }
2448
3014
  let values = unsafe { std::slice::from_raw_parts(sn_array_data(value), len) };
2449
3015
  // This is a checked optimization hint, not an instance shape assumption.
@@ -2462,11 +3028,54 @@ fn unique(value: Value, depth: usize, fixnum_flag: Value, budget: &budget::Budge
2462
3028
  }
2463
3029
  }
2464
3030
  let mut fingerprints = structural::Cache::batch(depth);
2465
- indexed_unique(
3031
+ let result = indexed_unique(
2466
3032
  values,
2467
- |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
+ },
2468
3043
  |a, b| equal_budgeted(a, b, depth, budget),
2469
- )
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
2470
3079
  }
2471
3080
  // Keep Big-only sampling and retry state out of the common scalar path.
2472
3081
  #[inline(never)]
@@ -2806,6 +3415,7 @@ struct ObjectContext<'a> {
2806
3415
  rules: &'a Object,
2807
3416
  depth: usize,
2808
3417
  active: scope::Context<'a>,
3418
+ names_only: bool,
2809
3419
  }
2810
3420
  /// Called synchronously by the C hash iterator. This never calls allocating Ruby APIs.
2811
3421
  /// # Safety
@@ -2820,15 +3430,21 @@ pub unsafe extern "C" fn sn_object_entry(context: *const c_void, key: Value, val
2820
3430
  if unsafe { sn_kind(key) } != TYPE_STRING {
2821
3431
  return Unsupported as i32;
2822
3432
  }
2823
- 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
+ {
2824
3436
  let result = c.engine.eval(child, key, c.depth, c.active);
2825
3437
  if result != Valid {
2826
3438
  return result as i32;
2827
3439
  }
2828
3440
  }
2829
- 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(
2830
3445
  c.engine.caches,
2831
3446
  &c.engine.budget,
3447
+ c.engine.caches.runtime_order(),
2832
3448
  key,
2833
3449
  None,
2834
3450
  |_, _, child| match c.engine.eval(child, value, c.depth, c.active) {
@@ -2879,9 +3495,17 @@ pub unsafe extern "C" fn sn_compile(
2879
3495
  e.bind_dynamic_anchors()?;
2880
3496
  e.simplify_dynamic_references();
2881
3497
  e.compile_memoization();
2882
- 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
+ )
2883
3506
  })) {
2884
- Ok(Ok(())) => VERDICT_VALID,
3507
+ Ok(Ok(true)) => VERDICT_VALID,
3508
+ Ok(Ok(false)) => VERDICT_UNSUPPORTED,
2885
3509
  Ok(Err(value)) => {
2886
3510
  unsafe { *error = value };
2887
3511
  STATUS_EXCEPTION
@@ -2900,12 +3524,44 @@ pub unsafe extern "C" fn sn_node_index(engine: *const Engine, program: Value) ->
2900
3524
  .unwrap_or(usize::MAX)
2901
3525
  }
2902
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
2903
3547
  /// Engine must be initialized.
2904
3548
  #[unsafe(no_mangle)]
2905
3549
  pub unsafe extern "C" fn sn_root_index(engine: *const Engine) -> usize {
2906
3550
  unsafe { &*engine }.root
2907
3551
  }
2908
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
2909
3565
  /// Roots must be live and compilation must have finished.
2910
3566
  #[unsafe(no_mangle)]
2911
3567
  pub unsafe extern "C" fn sn_root_count(roots: *const Roots) -> usize {
@@ -2923,21 +3579,25 @@ pub unsafe extern "C" fn sn_root_at(roots: *const Roots, index: usize) -> Value
2923
3579
  #[unsafe(no_mangle)]
2924
3580
  pub unsafe extern "C" fn sn_valid(
2925
3581
  engine: *const Engine,
2926
- caches: *const pattern::Caches,
3582
+ caches: *const runtime::Caches,
2927
3583
  root: usize,
2928
3584
  value: Value,
3585
+ depth: usize,
2929
3586
  ) -> i32 {
2930
3587
  catch_unwind(AssertUnwindSafe(|| {
2931
3588
  let e = unsafe { &*engine };
2932
- let local = pattern::Caches::default();
3589
+ let local = runtime::Caches::default();
2933
3590
  let caches = unsafe { caches.as_ref() }.unwrap_or(&local);
2934
- Execution {
3591
+ caches.admit(e, root);
3592
+ let execution = Execution {
2935
3593
  engine: e,
2936
3594
  caches,
2937
3595
  memo: memo::Memo::default(),
2938
3596
  budget: budget::Budget::default(),
2939
- }
2940
- .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)
2941
3601
  }))
2942
3602
  .unwrap_or(STATUS_PANIC)
2943
3603
  }
@@ -2946,24 +3606,26 @@ pub unsafe extern "C" fn sn_valid(
2946
3606
  #[unsafe(no_mangle)]
2947
3607
  pub unsafe extern "C" fn sn_valid_with_limits(
2948
3608
  engine: *const Engine,
2949
- caches: *const pattern::Caches,
3609
+ caches: *const runtime::Caches,
2950
3610
  root: usize,
2951
3611
  value: Value,
3612
+ depth: usize,
2952
3613
  limits: *const budget::Limits,
2953
3614
  outcome: *mut budget::Outcome,
2954
3615
  ) -> i32 {
2955
- let budget = budget::Budget::new(unsafe { *limits });
2956
3616
  let result = catch_unwind(AssertUnwindSafe(|| {
2957
- let local = pattern::Caches::default();
3617
+ let local = runtime::Caches::default();
2958
3618
  let caches = unsafe { caches.as_ref() }.unwrap_or(&local);
3619
+ let e = unsafe { &*engine };
3620
+ caches.admit(e, root);
2959
3621
  let execution = Execution {
2960
3622
  engine: unsafe { &*engine },
2961
3623
  caches,
2962
3624
  memo: memo::Memo::default(),
2963
- budget,
3625
+ budget: caches.budget(unsafe { *limits }),
2964
3626
  };
2965
3627
  let result = if execution.budget.check_deadline() {
2966
- execution.eval(root, value, 0, scope::Context::default())
3628
+ execution.eval(root, value, depth, scope::Context::default())
2967
3629
  } else {
2968
3630
  Unsupported
2969
3631
  };
@@ -2975,7 +3637,8 @@ pub unsafe extern "C" fn sn_valid_with_limits(
2975
3637
  unsafe {
2976
3638
  ((*outcome).kind, (*outcome).attempted) = execution.budget.hit.get();
2977
3639
  }
2978
- result as i32
3640
+ caches.finish_callback_report(result);
3641
+ execution.budget.status(result)
2979
3642
  }));
2980
3643
  result.unwrap_or(STATUS_PANIC)
2981
3644
  }
@@ -3024,7 +3687,12 @@ pub extern "C" fn sn_engine_header_size() -> usize {
3024
3687
  pub unsafe extern "C" fn sn_size(engine: *const Engine) -> usize {
3025
3688
  let nodes = unsafe { &*std::ptr::addr_of!((*engine).nodes) };
3026
3689
  let operands = unsafe { &*std::ptr::addr_of!((*engine).error_operands) };
3690
+ let programs = unsafe { &*std::ptr::addr_of!((*engine).programs) };
3027
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) };
3028
3696
  let guarded = unsafe { &*std::ptr::addr_of!((*engine).memo_guarded) };
3029
3697
  let sources = unsafe { &*std::ptr::addr_of!((*engine).memo_guard_sources) };
3030
3698
  let same = unsafe { &*std::ptr::addr_of!((*engine).memo_guard_same_sources) };
@@ -3033,6 +3701,9 @@ pub unsafe extern "C" fn sn_size(engine: *const Engine) -> usize {
3033
3701
  let projection = unsafe { &*std::ptr::addr_of!((*engine).memo_scope_projection) };
3034
3702
  std::mem::size_of::<Engine>()
3035
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>()
3036
3707
  + (contextual.capacity()
3037
3708
  + guarded.capacity()
3038
3709
  + sources.capacity()
@@ -3040,6 +3711,7 @@ pub unsafe extern "C" fn sn_size(engine: *const Engine) -> usize {
3040
3711
  + scalar.capacity()
3041
3712
  + amplification.capacity())
3042
3713
  * std::mem::size_of::<bool>()
3714
+ + programs.capacity() * std::mem::size_of::<Value>()
3043
3715
  + nodes.capacity() * std::mem::size_of::<Vec<Op>>()
3044
3716
  + operands.capacity() * std::mem::size_of::<Vec<(i32, Value)>>()
3045
3717
  + operands
@@ -3054,6 +3726,7 @@ pub unsafe extern "C" fn sn_size(engine: *const Engine) -> usize {
3054
3726
  .iter()
3055
3727
  .flatten()
3056
3728
  .map(|op| match op {
3729
+ Op::Number(_, rules) => rules.memory_usage(),
3057
3730
  Op::Enum(values) => values.memory_usage(),
3058
3731
  Op::String(_, rules) => {
3059
3732
  std::mem::size_of::<StringRules>()