schemurai 2.3.0 → 3.0.1

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 (38) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +67 -4
  3. data/ext/schemurai_native/Cargo.lock +8 -1
  4. data/ext/schemurai_native/Cargo.toml +2 -1
  5. data/ext/schemurai_native/glue.c +155 -14
  6. data/ext/schemurai_native/src/budget.rs +101 -0
  7. data/ext/schemurai_native/src/content.rs +18 -0
  8. data/ext/schemurai_native/src/diagnostics.rs +395 -225
  9. data/ext/schemurai_native/src/enumeration.rs +154 -0
  10. data/ext/schemurai_native/src/evaluation.rs +219 -0
  11. data/ext/schemurai_native/src/format.rs +118 -8
  12. data/ext/schemurai_native/src/graph.rs +50 -0
  13. data/ext/schemurai_native/src/instance_graph.rs +84 -0
  14. data/ext/schemurai_native/src/integer_scan.rs +132 -0
  15. data/ext/schemurai_native/src/lib.rs +954 -185
  16. data/ext/schemurai_native/src/memo.rs +1149 -0
  17. data/ext/schemurai_native/src/memo_scope.rs +240 -0
  18. data/ext/schemurai_native/src/number.rs +7 -8
  19. data/ext/schemurai_native/src/pattern.rs +191 -22
  20. data/ext/schemurai_native/src/scope.rs +243 -33
  21. data/ext/schemurai_native/src/structural.rs +86 -0
  22. data/ext/schemurai_native/src/tracking.rs +245 -90
  23. data/lib/schemurai/backend.rb +1 -0
  24. data/lib/schemurai/evaluator.rb +45 -3
  25. data/lib/schemurai/limit_budget.rb +78 -0
  26. data/lib/schemurai/limit_exceeded.rb +18 -0
  27. data/lib/schemurai/limited_evaluator.rb +100 -0
  28. data/lib/schemurai/native/evaluator.rb +32 -5
  29. data/lib/schemurai/schema_domain.rb +12 -7
  30. data/lib/schemurai/schema_graph.rb +10 -1
  31. data/lib/schemurai/unique_items.rb +2 -0
  32. data/lib/schemurai/version.rb +1 -1
  33. data/lib/schemurai/vm/compiler.rb +7 -3
  34. data/lib/schemurai/vm/evaluator.rb +50 -3
  35. data/lib/schemurai/vm/limited_evaluator.rb +97 -0
  36. data/lib/schemurai.rb +74 -27
  37. data/sig/schemurai.rbs +20 -12
  38. metadata +17 -4
@@ -0,0 +1,132 @@
1
+ //! Validate complete blocks of borrowed, header-verified immediate integers.
2
+ //! Stop before any uncertain block so the ordinary evaluator retains ordering.
3
+ use super::Value;
4
+
5
+ pub(super) fn valid_prefix(values: &[Value], min: i64, max: i64, fixnum_flag: Value) -> usize {
6
+ if fixnum_flag != 1 || values.len() < 16 {
7
+ return 0;
8
+ }
9
+ // Comparing tagged values preserves signed integer order. Clamp the
10
+ // interval before tagging, since some schema limits exceed Fixnum range.
11
+ let min = min.max(-(1_i64 << 62));
12
+ let max = max.min((1_i64 << 62) - 1);
13
+ if min > max {
14
+ return 0;
15
+ }
16
+ let min = (min << 1) | 1;
17
+ let max = (max << 1) | 1;
18
+ #[cfg(target_arch = "x86_64")]
19
+ if std::is_x86_feature_detected!("avx2") {
20
+ // Detection includes OS support. The kernel only loads full blocks.
21
+ return unsafe { avx2_prefix(values, min, max) };
22
+ }
23
+ #[cfg(all(target_arch = "aarch64", target_feature = "neon"))]
24
+ return neon_prefix(values, min, max);
25
+ #[allow(unreachable_code)]
26
+ {
27
+ let _ = (min, max);
28
+ 0
29
+ }
30
+ }
31
+
32
+ #[cfg(target_arch = "x86_64")]
33
+ #[target_feature(enable = "avx2")]
34
+ unsafe fn avx2_prefix(values: &[Value], min: i64, max: i64) -> usize {
35
+ use core::arch::x86_64::{
36
+ _mm256_and_si256, _mm256_andnot_si256, _mm256_cmpeq_epi64, _mm256_cmpgt_epi64,
37
+ _mm256_loadu_si256, _mm256_movemask_epi8, _mm256_or_si256, _mm256_set1_epi64x,
38
+ };
39
+ let lower = _mm256_set1_epi64x(min);
40
+ let upper = _mm256_set1_epi64x(max);
41
+ let tag = _mm256_set1_epi64x(1);
42
+ let mut consumed = 0;
43
+ for block in values.as_chunks::<4>().0 {
44
+ // Four VALUE words provide all 32 readable bytes. No Ruby object is
45
+ // dereferenced; the header-validated low bit identifies each Fixnum.
46
+ let valid = unsafe {
47
+ let word = _mm256_loadu_si256(block.as_ptr().cast());
48
+ let tagged = _mm256_cmpeq_epi64(_mm256_and_si256(word, tag), tag);
49
+ let outside = _mm256_or_si256(
50
+ _mm256_cmpgt_epi64(lower, word),
51
+ _mm256_cmpgt_epi64(word, upper),
52
+ );
53
+ _mm256_movemask_epi8(_mm256_andnot_si256(outside, tagged)) == -1
54
+ };
55
+ if !valid {
56
+ break;
57
+ }
58
+ consumed += 4;
59
+ }
60
+ consumed
61
+ }
62
+
63
+ #[cfg(all(target_arch = "aarch64", target_feature = "neon"))]
64
+ fn neon_prefix(values: &[Value], min: i64, max: i64) -> usize {
65
+ use core::arch::aarch64::{
66
+ vandq_u64, vceqq_u64, vcgeq_s64, vcleq_s64, vdupq_n_s64, vdupq_n_u64, vgetq_lane_u64,
67
+ vld1q_u64, vreinterpretq_s64_u64,
68
+ };
69
+ // This function is compiled only when the target guarantees NEON.
70
+ let (lower, upper, tag) = unsafe { (vdupq_n_s64(min), vdupq_n_s64(max), vdupq_n_u64(1)) };
71
+ let mut consumed = 0;
72
+ for block in values.as_chunks::<2>().0 {
73
+ // Baseline NEON; the unaligned load stays within two borrowed VALUEs.
74
+ let valid = unsafe {
75
+ let word = vld1q_u64(block.as_ptr().cast());
76
+ let tagged = vceqq_u64(vandq_u64(word, tag), tag);
77
+ let signed = vreinterpretq_s64_u64(word);
78
+ let bounds = vandq_u64(vcgeq_s64(signed, lower), vcleq_s64(signed, upper));
79
+ let valid = vandq_u64(tagged, bounds);
80
+ vgetq_lane_u64::<0>(valid) & vgetq_lane_u64::<1>(valid) == u64::MAX
81
+ };
82
+ if !valid {
83
+ break;
84
+ }
85
+ consumed += 2;
86
+ }
87
+ consumed
88
+ }
89
+
90
+ #[cfg(test)]
91
+ mod tests {
92
+ use super::*;
93
+
94
+ fn tag(value: i64) -> Value {
95
+ ((value << 1) | 1) as Value
96
+ }
97
+
98
+ #[test]
99
+ fn skips_only_valid_fixnums_before_the_first_uncertain_block() {
100
+ for len in 0..65 {
101
+ let original: Vec<_> = (0..len).map(|at| tag((at % 33) as i64 - 16)).collect();
102
+ for at in 0..len {
103
+ for value in [0, 2, tag(-17), tag(17), tag(-(1 << 62)), tag((1 << 62) - 1)] {
104
+ let mut values = original.clone();
105
+ values[at] = value;
106
+ let prefix = valid_prefix(&values, -16, 16, 1);
107
+ assert!(prefix <= at, "length {len}, position {at}, prefix {prefix}");
108
+ assert_eq!(&values[..prefix], &original[..prefix]);
109
+ }
110
+ }
111
+ }
112
+ }
113
+
114
+ #[test]
115
+ fn clamps_schema_limits_without_overflow_and_obeys_disabled_hints() {
116
+ let values = [tag(-(1 << 62)), tag((1 << 62) - 1)].repeat(32);
117
+ let prefix = valid_prefix(&values, i64::MIN, i64::MAX, 1);
118
+ #[cfg(target_arch = "x86_64")]
119
+ if std::is_x86_feature_detected!("avx2") {
120
+ assert_eq!(prefix, values.len());
121
+ }
122
+ #[cfg(all(target_arch = "aarch64", target_feature = "neon"))]
123
+ assert_eq!(prefix, values.len());
124
+ assert!(prefix <= values.len());
125
+ assert_eq!(valid_prefix(&values, i64::MIN, i64::MAX, 0), 0);
126
+ assert_eq!(valid_prefix(&values, 1 << 62, i64::MAX, 1), 0);
127
+ assert_eq!(valid_prefix(&values, i64::MIN, -(1 << 62) - 1, 1), 0);
128
+ assert_eq!(valid_prefix(&values, 1, 0, 1), 0);
129
+ assert_eq!(valid_prefix(&values, -(1 << 62) + 1, i64::MAX, 1), 0);
130
+ assert_eq!(valid_prefix(&values, i64::MIN, (1 << 62) - 2, 1), 0);
131
+ }
132
+ }