schemurai 2.2.3 → 3.0.0

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 (42) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +81 -7
  3. data/ext/schemurai_native/Cargo.lock +99 -0
  4. data/ext/schemurai_native/Cargo.toml +21 -0
  5. data/ext/schemurai_native/build.rs +35 -0
  6. data/ext/schemurai_native/extconf.rb +63 -0
  7. data/ext/schemurai_native/glue.c +780 -0
  8. data/ext/schemurai_native/protocol.def +63 -0
  9. data/ext/schemurai_native/protocol.h +10 -0
  10. data/ext/schemurai_native/src/budget.rs +101 -0
  11. data/ext/schemurai_native/src/content.rs +574 -0
  12. data/ext/schemurai_native/src/diagnostics.rs +1135 -0
  13. data/ext/schemurai_native/src/enumeration.rs +154 -0
  14. data/ext/schemurai_native/src/evaluation.rs +219 -0
  15. data/ext/schemurai_native/src/format.rs +357 -0
  16. data/ext/schemurai_native/src/graph.rs +50 -0
  17. data/ext/schemurai_native/src/instance_graph.rs +84 -0
  18. data/ext/schemurai_native/src/lib.rs +3139 -0
  19. data/ext/schemurai_native/src/memo.rs +1149 -0
  20. data/ext/schemurai_native/src/memo_scope.rs +240 -0
  21. data/ext/schemurai_native/src/number.rs +1054 -0
  22. data/ext/schemurai_native/src/pattern.rs +431 -0
  23. data/ext/schemurai_native/src/scope.rs +420 -0
  24. data/ext/schemurai_native/src/structural.rs +86 -0
  25. data/ext/schemurai_native/src/tracking.rs +687 -0
  26. data/lib/schemurai/backend.rb +3 -1
  27. data/lib/schemurai/evaluator.rb +45 -3
  28. data/lib/schemurai/limit_budget.rb +78 -0
  29. data/lib/schemurai/limit_exceeded.rb +18 -0
  30. data/lib/schemurai/limited_evaluator.rb +100 -0
  31. data/lib/schemurai/native/diagnostics.rb +52 -0
  32. data/lib/schemurai/native/evaluator.rb +59 -0
  33. data/lib/schemurai/schema_domain.rb +12 -7
  34. data/lib/schemurai/schema_graph.rb +10 -1
  35. data/lib/schemurai/unique_items.rb +2 -0
  36. data/lib/schemurai/version.rb +1 -1
  37. data/lib/schemurai/vm/compiler.rb +7 -3
  38. data/lib/schemurai/vm/evaluator.rb +50 -3
  39. data/lib/schemurai/vm/limited_evaluator.rb +97 -0
  40. data/lib/schemurai.rb +81 -33
  41. data/sig/schemurai.rbs +21 -13
  42. metadata +36 -6
@@ -0,0 +1,1054 @@
1
+ //! Numeric instructions distinguish Ruby's ordinary comparison from decimal
2
+ //! multipleOf semantics. All evaluation helpers operate under the GVL and
3
+ //! allocate no Ruby objects; unsupported magnitudes retain VM compatibility.
4
+ use super::*;
5
+ use std::cmp::Ordering;
6
+
7
+ // Strictly ordered integers are unique without fingerprinting their digits.
8
+ // Each comparison checks both original handles; mixed or unordered inputs
9
+ // retain the indexed JSON equality path. No Ruby allocation occurs here.
10
+ pub(super) fn ordered_big_unique(values: &[Value]) -> Option<Verdict> {
11
+ let direction = unsafe { sn_big_order(values[0], values[1]) };
12
+ match direction {
13
+ 0 => return Some(Invalid),
14
+ -1 | 1 => {}
15
+ _ => return None,
16
+ }
17
+ for (offset, pair) in values[1..].windows(2).enumerate() {
18
+ let order = unsafe { sn_big_order(pair[0], pair[1]) };
19
+ if order == 0 {
20
+ return Some(Invalid);
21
+ }
22
+ if order != direction {
23
+ // A return to the first value is a duplicate even when it breaks
24
+ // the order. Otherwise indexing must examine the entire input.
25
+ if order == ADAPTER_UNSUPPORTED {
26
+ return None;
27
+ }
28
+ if unsafe { sn_big_order(values[0], pair[1]) } == 0 {
29
+ return Some(Invalid);
30
+ }
31
+ let tail = offset + 2;
32
+ return (values.len() - tail <= 16)
33
+ .then(|| ordered_tail_unique(values, tail, direction))
34
+ .flatten();
35
+ }
36
+ }
37
+ Some(Valid)
38
+ }
39
+
40
+ // A short unordered tail can search the proven ordered prefix directly.
41
+ // Bound the tail to keep its internal pair comparisons constant-sized and
42
+ // avoid rebuilding a full fingerprint index after an almost complete scan.
43
+ fn ordered_tail_unique(values: &[Value], prefix: usize, direction: i32) -> Option<Verdict> {
44
+ for at in prefix..values.len() {
45
+ let (mut low, mut high) = (0, prefix);
46
+ while low < high {
47
+ let middle = low + (high - low) / 2;
48
+ match unsafe { sn_big_order(values[middle], values[at]) } {
49
+ 0 => return Some(Invalid),
50
+ ADAPTER_UNSUPPORTED => return None,
51
+ order if order == direction => low = middle + 1,
52
+ _ => high = middle,
53
+ }
54
+ }
55
+ for previous in prefix..at {
56
+ if unsafe { sn_big_order(values[previous], values[at]) } == 0 {
57
+ return Some(Invalid);
58
+ }
59
+ }
60
+ }
61
+ Some(Valid)
62
+ }
63
+
64
+ // A finite binary64 integer has at most 1,024 magnitude bits. Arithmetic
65
+ // adapters fill fixed stack words; there is no instance-tree conversion or
66
+ // Ruby allocation. Larger integers cannot equal any finite Float.
67
+ pub(super) fn integer_float_equal(integer: Value, float: f64) -> Verdict {
68
+ if !float.is_finite() || float.trunc() != float {
69
+ return Invalid;
70
+ }
71
+ let mut actual = [0_u64; 16];
72
+ let sign = unsafe { sn_integer1024(integer, actual.as_mut_ptr()) };
73
+ if sign.abs() > 1 || (sign < 0) != (float < 0.0) {
74
+ return Invalid;
75
+ }
76
+ let mut expected = [0_u64; 16];
77
+ if float != 0.0 {
78
+ let bits = float.to_bits();
79
+ let mantissa = (bits & ((1_u64 << 52) - 1)) | (1_u64 << 52);
80
+ let shift = ((bits >> 52) & 0x7ff) as i32 - 1023 - 52;
81
+ if shift < 0 {
82
+ expected[0] = mantissa >> shift.unsigned_abs();
83
+ } else {
84
+ let word = shift as usize / 64;
85
+ let bit = shift as usize % 64;
86
+ expected[word] = mantissa << bit;
87
+ if bit != 0 && word + 1 < expected.len() {
88
+ expected[word + 1] = mantissa >> (64 - bit);
89
+ }
90
+ }
91
+ }
92
+ verdict(actual == expected)
93
+ }
94
+
95
+ pub(super) fn integer_fingerprint(integer: Value) -> u64 {
96
+ let mut words = [0_u64; 16];
97
+ let sign = unsafe { sn_integer1024(integer, words.as_mut_ptr()) };
98
+ if sign.abs() > 1 {
99
+ // No finite Float can equal this magnitude, so the full native digit
100
+ // hash needs no Float normalization. Sampling only low digits makes
101
+ // mixed/nested uniqueItems quadratic for differences above bit 1024
102
+ // and collapses large enum indexes into a single candidate bucket.
103
+ // The adapter hashes the original Bignum without Ruby dispatch or
104
+ // allocation; exact equality still decides every hash collision.
105
+ return mix(big_only_fingerprint(integer).unwrap() ^ TYPE_NUMBER as u64);
106
+ }
107
+ exact_integer_fingerprint(&words, sign)
108
+ }
109
+
110
+ pub(super) fn big_fingerprint(integer: Value) -> Option<u64> {
111
+ let mut words = [0_u64; 4];
112
+ let sign = unsafe { sn_big256(integer, words.as_mut_ptr()) };
113
+ if sign == ADAPTER_UNSUPPORTED {
114
+ return None;
115
+ }
116
+ Some(if sign.abs() > 1 {
117
+ integer_fingerprint(integer)
118
+ } else {
119
+ exact_integer_fingerprint(&words, sign)
120
+ })
121
+ }
122
+
123
+ // Only used by an index that restarts with canonical fingerprints if any item
124
+ // is not a Bignum. No Float can enter this index, so it needs no binary64
125
+ // normalization. Full integer equality resolves every fingerprint collision.
126
+ pub(super) fn big_only_fingerprint(integer: Value) -> Option<u64> {
127
+ let mut hash = 0;
128
+ (unsafe { sn_big_hash(integer, &mut hash) } != 0).then_some(hash)
129
+ }
130
+
131
+ // This convention is restricted to Big/Big comparisons. The caller restarts
132
+ // with the full hash when collisions accumulate, or canonical fingerprints
133
+ // when a non-Bignum appears. No sampled metadata decides equality.
134
+ pub(super) fn big_low_fingerprint(integer: Value) -> Option<u64> {
135
+ let sample = unsafe { sn_big_low_word(integer) };
136
+ (sample != 1 << 33).then(|| {
137
+ // Fold the short magnitude sample without scrambling adjacent low
138
+ // words. Exact equality and wider bounded retries resolve collisions.
139
+ let bits = u64::from(sample as u32) ^ if sample & (1 << 32) != 0 { u64::MAX } else { 0 };
140
+ let folded = bits ^ (bits >> 33);
141
+ folded ^ (folded >> 11) ^ (folded >> 22)
142
+ })
143
+ }
144
+
145
+ pub(super) fn big_low64_fingerprint(integer: Value) -> Option<u64> {
146
+ let mut word = 0;
147
+ let sign = unsafe { sn_big_low64_word(integer, &mut word) };
148
+ (sign != 3).then(|| {
149
+ let bits = word ^ if sign < 0 { u64::MAX } else { 0 };
150
+ let folded = bits ^ (bits >> 33);
151
+ let folded = folded ^ (folded >> 11) ^ (folded >> 22);
152
+ folded.wrapping_mul(0x9e3779b97f4a7c15)
153
+ })
154
+ }
155
+
156
+ fn exact_integer_fingerprint(words: &[u64], sign: i32) -> u64 {
157
+ let top = words.iter().rposition(|&word| word != 0).unwrap_or(0);
158
+ if top == 0 && (words[0] < (1_u64 << 63) || (sign < 0 && words[0] == 1_u64 << 63)) {
159
+ let bits = if sign < 0 {
160
+ words[0].wrapping_neg()
161
+ } else {
162
+ words[0]
163
+ };
164
+ return mix(bits ^ TYPE_NUMBER as u64);
165
+ }
166
+ let exponent = top * 64 + 63 - words[top].leading_zeros() as usize;
167
+ let shift = exponent - 52;
168
+ let word = shift / 64;
169
+ let bit = shift % 64;
170
+ if words[..word].iter().any(|&word| word != 0)
171
+ || (bit != 0 && words[word] & ((1_u64 << bit) - 1) != 0)
172
+ {
173
+ // Nonrepresentable integers cannot equal any Float. Keep their low
174
+ // digits instead of putting nearby integers into one rounded bucket.
175
+ return integer_bucket(&words[..=top], sign, exponent / 8 + 1);
176
+ }
177
+ let mut mantissa = words[word] >> bit;
178
+ if bit != 0 && word + 1 < words.len() {
179
+ mantissa |= words[word + 1] << (64 - bit);
180
+ }
181
+ // Exactly representable integers share bits with their equal Float.
182
+ // Every collision still goes through full JSON equality.
183
+ let bits = ((sign < 0) as u64) << 63
184
+ | ((exponent + 1023) as u64) << 52
185
+ | (mantissa & ((1_u64 << 52) - 1));
186
+ mix(bits ^ TYPE_NUMBER as u64)
187
+ }
188
+
189
+ // Callers supply the prefix ending at the last nonzero word. It may be empty
190
+ // when all sampled low words of an oversized integer are zero.
191
+ fn integer_bucket(words: &[u64], sign: i32, bytes: usize) -> u64 {
192
+ let mut hash = bytes as u64 ^ TYPE_NUMBER as u64 ^ ((sign < 0) as u64);
193
+ // Independent lanes shorten the multiplication dependency chain while
194
+ // retaining every sampled word. Full VALUE equality resolves collisions.
195
+ let mut other = hash ^ 0x9e37_79b9_7f4a_7c15;
196
+ let (pairs, remainder) = words.as_chunks::<2>();
197
+ for pair in pairs {
198
+ hash = (hash ^ pair[0]).wrapping_mul(0x0000_0100_0000_01b3);
199
+ other = (other ^ pair[1]).wrapping_mul(0x0000_0100_0000_01b3);
200
+ }
201
+ if let [word] = remainder {
202
+ hash = (hash ^ word).wrapping_mul(0x0000_0100_0000_01b3);
203
+ }
204
+ mix(hash ^ other.rotate_left(32))
205
+ }
206
+
207
+ #[derive(Debug, Clone, Copy)]
208
+ struct Fraction {
209
+ numerator: i128,
210
+ denominator: i128,
211
+ }
212
+ impl Fraction {
213
+ fn compare(self, other: Self) -> Ordering {
214
+ let signs = self.numerator.signum().cmp(&other.numerator.signum());
215
+ if signs != Ordering::Equal {
216
+ return signs;
217
+ }
218
+ let (mut a, mut b) = (self.numerator.unsigned_abs(), self.denominator as u128);
219
+ let (mut c, mut d) = (other.numerator.unsigned_abs(), other.denominator as u128);
220
+ let mut reverse = self.numerator < 0;
221
+ loop {
222
+ let order = (a / b).cmp(&(c / d));
223
+ if order != Ordering::Equal {
224
+ return if reverse { order.reverse() } else { order };
225
+ }
226
+ let (r, s) = (a % b, c % d);
227
+ if r == 0 || s == 0 {
228
+ let order = r.cmp(&s);
229
+ return if reverse { order.reverse() } else { order };
230
+ }
231
+ (a, b, c, d) = (b, r, d, s);
232
+ reverse = !reverse;
233
+ }
234
+ }
235
+
236
+ fn multiple_of(self, divisor: Self) -> bool {
237
+ // Both fractions are reduced: p must divide n and d must divide q.
238
+ divisor.numerator > 0
239
+ && self.numerator % divisor.numerator == 0
240
+ && divisor.denominator % self.denominator == 0
241
+ }
242
+ }
243
+
244
+ fn decimal_float(value: f64) -> Option<Fraction> {
245
+ if !value.is_finite() {
246
+ return None;
247
+ }
248
+ let mut buffer = ryu::Buffer::new();
249
+ let mut bytes = buffer.format_finite(value).as_bytes();
250
+ let negative = bytes.first() == Some(&b'-');
251
+ if negative {
252
+ bytes = &bytes[1..];
253
+ }
254
+ let exponent = if let Some(at) = bytes.iter().position(|&byte| byte == b'e') {
255
+ let exponent = std::str::from_utf8(&bytes[at + 1..])
256
+ .ok()?
257
+ .parse::<i32>()
258
+ .ok()?;
259
+ bytes = &bytes[..at];
260
+ exponent
261
+ } else {
262
+ 0
263
+ };
264
+ let fractional = bytes
265
+ .iter()
266
+ .position(|&b| b == b'.')
267
+ .map_or(0, |at| bytes.len() - at - 1);
268
+ let mut end = bytes.len();
269
+ let mut trailing = 0;
270
+ while end > 0 && (bytes[end - 1] == b'0' || bytes[end - 1] == b'.') {
271
+ if bytes[end - 1] == b'0' {
272
+ trailing += 1;
273
+ }
274
+ end -= 1;
275
+ }
276
+ let mut numerator = 0_i128;
277
+ for &byte in &bytes[..end] {
278
+ if byte == b'.' {
279
+ continue;
280
+ }
281
+ if !byte.is_ascii_digit() {
282
+ return None;
283
+ }
284
+ numerator = numerator
285
+ .checked_mul(10)?
286
+ .checked_add(i128::from(byte.checked_sub(b'0')?))?;
287
+ }
288
+ if numerator == 0 {
289
+ return Some(Fraction {
290
+ numerator: 0,
291
+ denominator: 1,
292
+ });
293
+ }
294
+ let power = exponent + trailing - fractional as i32;
295
+ let mut denominator = 1;
296
+ if power >= 0 {
297
+ numerator = numerator.checked_mul(10_i128.checked_pow(power as u32)?)?;
298
+ } else {
299
+ denominator = 10_i128.checked_pow(power.unsigned_abs())?;
300
+ }
301
+ let (mut a, mut b) = (numerator, denominator);
302
+ while b != 0 {
303
+ (a, b) = (b, a % b);
304
+ }
305
+ Some(Fraction {
306
+ numerator: if negative {
307
+ -numerator / a
308
+ } else {
309
+ numerator / a
310
+ },
311
+ denominator: denominator / a,
312
+ })
313
+ }
314
+ #[derive(Debug)]
315
+ struct Limit {
316
+ exact: Option<Fraction>,
317
+ wide_integer: Option<Value>,
318
+ wide_rounded_order: Option<Ordering>,
319
+ rounded: f64,
320
+ integer: bool,
321
+ sum_words: bool,
322
+ }
323
+
324
+ impl Limit {
325
+ fn float_order(&self, n: f64) -> Result<Option<Ordering>, Verdict> {
326
+ if let Some(exact) = self.exact {
327
+ return Ok(compare_float_integer(n, exact.numerator));
328
+ }
329
+ let Some(tie) = self.wide_rounded_order else {
330
+ return Err(Unsupported);
331
+ };
332
+ // Adjacent Floats cannot lie inside the rounding interval of an
333
+ // Integer. Only equality with its rounded Float needs an exact tie.
334
+ Ok(n.partial_cmp(&self.rounded).map(
335
+ |order| {
336
+ if order == Ordering::Equal { tie } else { order }
337
+ },
338
+ ))
339
+ }
340
+ // Runtime callers have classified the instance as an ordinary Integer.
341
+ // Wide schema bounds remain rooted Ruby handles, with no digit export.
342
+ fn integer_order(&self, value: Value, integer: Option<i128>, sign: i32) -> Option<Ordering> {
343
+ if let Some(exact) = self.exact {
344
+ return Some(integer.map_or_else(
345
+ || {
346
+ if sign > 0 {
347
+ Ordering::Greater
348
+ } else {
349
+ Ordering::Less
350
+ }
351
+ },
352
+ |n| compare_integer(n, exact),
353
+ ));
354
+ }
355
+ self.wide_integer
356
+ .map(|bound| unsafe { sn_wide_integer_compare(value, bound) }.cmp(&0))
357
+ }
358
+ }
359
+ #[derive(Debug)]
360
+ pub(super) struct Rules {
361
+ limits: Vec<(i32, Limit)>,
362
+ multiple: Option<Limit>,
363
+ pub(super) fast_limits: Option<Vec<(i32, i64)>>,
364
+ pub(super) fast_multiple: Option<FastMultiple>,
365
+ fixnum_flag: Value,
366
+ bounds_view: bool,
367
+ bounded_signs: Option<[bool; 2]>,
368
+ }
369
+
370
+ #[derive(Debug)]
371
+ pub(super) struct FastMultiple {
372
+ numerator: i64,
373
+ limits: Vec<(i32, i64)>,
374
+ }
375
+
376
+ impl FastMultiple {
377
+ fn failures(&self, n: i64) -> i32 {
378
+ // An Integer has denominator 1. For a reduced divisor p/q, only
379
+ // divisibility by p remains; q is always divisible by 1.
380
+ let mut bits = if self.numerator > 0 && n % self.numerator == 0 {
381
+ 0
382
+ } else {
383
+ MULTIPLE_OF
384
+ };
385
+ for &(bit, limit) in &self.limits {
386
+ if match bit {
387
+ MAXIMUM => n > limit,
388
+ MINIMUM => n < limit,
389
+ EXCLUSIVE_MAXIMUM => n >= limit,
390
+ _ => n <= limit,
391
+ } {
392
+ bits |= bit;
393
+ }
394
+ }
395
+ bits
396
+ }
397
+
398
+ pub(super) fn eval(&self, n: i64) -> Verdict {
399
+ verdict(self.failures(n) == 0)
400
+ }
401
+ }
402
+
403
+ impl Engine {
404
+ pub(super) fn compile_number(&mut self, operand: Value) -> Result<Option<Rules>, Value> {
405
+ let mask = self.field(operand, b"mask\0")?;
406
+ let mask = unsafe { sn_integer(mask) } as i32;
407
+ let mut limits = vec![];
408
+ for (bit, name) in [
409
+ (MAXIMUM, b"maximum\0".as_slice()),
410
+ (MINIMUM, b"minimum\0"),
411
+ (EXCLUSIVE_MAXIMUM, b"exclusive_maximum\0"),
412
+ (EXCLUSIVE_MINIMUM, b"exclusive_minimum\0"),
413
+ ] {
414
+ if mask & bit != 0 {
415
+ let value = self.field(operand, name)?;
416
+ let Some(limit) = self.numeric_limit(value)? else {
417
+ return Ok(None);
418
+ };
419
+ limits.push((bit, limit));
420
+ }
421
+ }
422
+ let multiple = if mask & MULTIPLE_OF == 0 {
423
+ None
424
+ } else {
425
+ let value = self.field(operand, b"multiple_of\0")?;
426
+ let Some(limit) = self.numeric_limit(value)? else {
427
+ return Ok(None);
428
+ };
429
+ Some(limit)
430
+ };
431
+ let fast_limits = if multiple.is_some() {
432
+ None
433
+ } else {
434
+ limits
435
+ .iter()
436
+ .map(|(bit, limit)| {
437
+ if !limit.integer {
438
+ return None;
439
+ }
440
+ Some((*bit, i64::try_from(limit.exact?.numerator).ok()?))
441
+ })
442
+ .collect()
443
+ };
444
+ let fast_multiple = multiple
445
+ .as_ref()
446
+ .and_then(|limit| limit.exact)
447
+ .and_then(|fraction| i64::try_from(fraction.numerator).ok())
448
+ .and_then(|numerator| {
449
+ let limits = limits
450
+ .iter()
451
+ .map(|&(bit, ref limit)| {
452
+ let fraction = limit.exact?;
453
+ (fraction.denominator == 1)
454
+ .then(|| i64::try_from(fraction.numerator).ok())
455
+ .flatten()
456
+ .map(|n| (bit, n))
457
+ })
458
+ .collect::<Option<Vec<_>>>()?;
459
+ Some(FastMultiple { numerator, limits })
460
+ });
461
+ let bounds_view = multiple.is_none() && limits.iter().all(|(_, limit)| limit.integer);
462
+ let bounded_signs = (bounds_view && limits.iter().all(|(_, limit)| limit.exact.is_some()))
463
+ .then(|| {
464
+ [
465
+ !limits
466
+ .iter()
467
+ .any(|(bit, _)| matches!(*bit, MINIMUM | EXCLUSIVE_MINIMUM)),
468
+ !limits
469
+ .iter()
470
+ .any(|(bit, _)| matches!(*bit, MAXIMUM | EXCLUSIVE_MAXIMUM)),
471
+ ]
472
+ });
473
+ Ok(Some(Rules {
474
+ limits,
475
+ multiple,
476
+ fast_limits,
477
+ fast_multiple,
478
+ fixnum_flag: self.fixnum_flag,
479
+ bounds_view,
480
+ bounded_signs,
481
+ }))
482
+ }
483
+
484
+ fn numeric_limit(&mut self, value: Value) -> Result<Option<Limit>, Value> {
485
+ let integer = unsafe { sn_kind(value) } & TYPE_INTEGER != 0;
486
+ if !integer && unsafe { sn_is_rational(value) } == 0 {
487
+ return Ok(None);
488
+ }
489
+ let exact = if integer {
490
+ read_integer(value).map(|numerator| Fraction {
491
+ numerator,
492
+ denominator: 1,
493
+ })
494
+ } else {
495
+ let numerator = self.field(value, b"numerator\0")?;
496
+ let denominator = self.field(value, b"denominator\0")?;
497
+ read_integer(numerator).zip(read_integer(denominator)).map(
498
+ |(numerator, denominator)| Fraction {
499
+ numerator,
500
+ denominator,
501
+ },
502
+ )
503
+ };
504
+ let rounded = self.field(value, b"to_f\0")?;
505
+ let rounded = unsafe { sn_float(rounded) };
506
+ let wide_rounded_order = if integer && exact.is_none() {
507
+ wide_float_tie(value, rounded)
508
+ } else {
509
+ None
510
+ };
511
+ let sum_words = exact.is_some_and(|fraction| {
512
+ fraction.numerator > 1 && (1_u128 << 64) % fraction.numerator as u128 == 1
513
+ });
514
+ Ok(Some(Limit {
515
+ exact,
516
+ wide_integer: (integer && exact.is_none()).then(|| self.retain(value)),
517
+ wide_rounded_order,
518
+ rounded,
519
+ integer,
520
+ sum_words,
521
+ }))
522
+ }
523
+ }
524
+
525
+ fn read_integer(value: Value) -> Option<i128> {
526
+ read_integer_status(value).ok()
527
+ }
528
+
529
+ // Err(sign) identifies a magnitude outside i128; Err(0) is not an Integer.
530
+ fn read_integer_status(value: Value) -> Result<i128, i32> {
531
+ if unsafe { sn_fixnum(value) } != 0 {
532
+ return Ok(unsafe { sn_integer(value) } as i128);
533
+ }
534
+ if unsafe { sn_kind(value) } & TYPE_INTEGER == 0 || unsafe { sn_is_float(value) } != 0 {
535
+ return Err(0);
536
+ }
537
+ unpack_integer_status(value)
538
+ }
539
+
540
+ // Runtime numeric instructions have already rejected non-numeric instances.
541
+ // Their non-Float branch contains only ordinary Integers; compilation uses
542
+ // read_integer_status instead because schema metadata still needs checking.
543
+ fn read_known_integer_status(value: Value, fixnum_flag: Value) -> Result<i128, i32> {
544
+ if value & fixnum_flag != 0 {
545
+ return Ok(immediate_integer(value) as i128);
546
+ }
547
+ let mut words = [0_u64; 2];
548
+ let sign = unsafe { sn_integer_bounds128(value, words.as_mut_ptr()) };
549
+ integer_words_status(&words, sign)
550
+ }
551
+
552
+ fn unpack_integer_status(value: Value) -> Result<i128, i32> {
553
+ let mut words = [0_u64; 2];
554
+ let sign = unsafe { sn_integer128(value, words.as_mut_ptr()) };
555
+ integer_words_status(&words, sign)
556
+ }
557
+
558
+ fn integer_words_status(words: &[u64], sign: i32) -> Result<i128, i32> {
559
+ if sign.abs() > 1 {
560
+ return Err(sign);
561
+ }
562
+ if words[2..].iter().any(|&word| word != 0) {
563
+ return Err(sign);
564
+ }
565
+ let magnitude = u128::from(words[0]) | (u128::from(words[1]) << 64);
566
+ if sign < 0 {
567
+ if magnitude == 1_u128 << 127 {
568
+ Ok(i128::MIN)
569
+ } else {
570
+ i128::try_from(magnitude)
571
+ .map_err(|_| sign)?
572
+ .checked_neg()
573
+ .ok_or(sign)
574
+ }
575
+ } else {
576
+ i128::try_from(magnitude).map_err(|_| sign)
577
+ }
578
+ }
579
+
580
+ fn wide_integer_multiple(value: Value, divisor: Fraction, sum_words: bool) -> Verdict {
581
+ if divisor.numerator <= 0 {
582
+ return Invalid;
583
+ }
584
+ if divisor.numerator == 1 {
585
+ return Valid;
586
+ }
587
+ let mut words = [0_u64; 16];
588
+ if unsafe { sn_integer1024(value, words.as_mut_ptr()) }.abs() > 1 {
589
+ return Unsupported;
590
+ }
591
+ wide_multiple_words(&words, divisor, sum_words)
592
+ }
593
+
594
+ fn wide_multiple_words(words: &[u64; 16], divisor: Fraction, sum_words: bool) -> Verdict {
595
+ let len = words
596
+ .iter()
597
+ .rposition(|&word| word != 0)
598
+ .map_or(0, |i| i + 1);
599
+ let divisor = divisor.numerator as u128;
600
+ let mut remainder = 0_u128;
601
+ if sum_words {
602
+ // If base 2^64 is 1 modulo the divisor, a magnitude has the same
603
+ // remainder as the sum of its words. At most 16 u64 words sum below
604
+ // 2^68, so one u128 remainder replaces per-word division.
605
+ remainder = words[..len]
606
+ .iter()
607
+ .map(|&word| u128::from(word))
608
+ .sum::<u128>()
609
+ % divisor;
610
+ } else if divisor <= u64::MAX as u128 {
611
+ // remainder < divisor <= u64::MAX, so shifting it by 64 fits u128.
612
+ for &word in words[..len].iter().rev() {
613
+ remainder = ((remainder << 64) | u128::from(word)) % divisor;
614
+ }
615
+ } else {
616
+ // The positive divisor is at most i128::MAX. Doubling a remainder
617
+ // below it cannot overflow u128; consume magnitude bits without
618
+ // allocating or multiplying an arbitrary-precision integer.
619
+ for &word in words[..len].iter().rev() {
620
+ for bit in (0..64).rev() {
621
+ remainder = (remainder << 1) | u128::from((word >> bit) & 1);
622
+ if remainder >= divisor {
623
+ remainder -= divisor;
624
+ }
625
+ }
626
+ }
627
+ }
628
+ // The instance denominator is one, which divides the reduced divisor's
629
+ // denominator. Sign does not affect divisibility by its positive numerator.
630
+ verdict(remainder == 0)
631
+ }
632
+
633
+ fn fixnum_bounds(n: i64, limits: &[(i32, i64)]) -> Verdict {
634
+ verdict(limits.iter().all(|&(bit, limit)| match bit {
635
+ MAXIMUM => n <= limit,
636
+ MINIMUM => n >= limit,
637
+ EXCLUSIVE_MAXIMUM => n < limit,
638
+ _ => n > limit,
639
+ }))
640
+ }
641
+
642
+ fn compare_integer(n: i128, limit: Fraction) -> Ordering {
643
+ let whole = limit.numerator / limit.denominator;
644
+ n.cmp(&whole)
645
+ .then_with(|| 0.cmp(&(limit.numerator % limit.denominator)))
646
+ }
647
+
648
+ fn compare_float_integer(n: f64, limit: i128) -> Option<Ordering> {
649
+ if n.is_nan() {
650
+ return None;
651
+ }
652
+ if n < i128::MIN as f64 {
653
+ return Some(Ordering::Less);
654
+ }
655
+ if n >= i128::MAX as f64 {
656
+ return Some(Ordering::Greater);
657
+ }
658
+ Some(
659
+ (n as i128)
660
+ .cmp(&limit)
661
+ .then_with(|| n.fract().partial_cmp(&0.0).unwrap()),
662
+ )
663
+ }
664
+
665
+ // Schema metadata only: record the comparison at the rounded Float once.
666
+ // Every finite binary64 magnitude fits 1,024 bits. No instance digit export
667
+ // or Ruby allocation is needed at runtime, and no word buffer is retained.
668
+ fn wide_float_tie(value: Value, rounded: f64) -> Option<Ordering> {
669
+ if rounded.is_infinite() {
670
+ return Some(if rounded.is_sign_negative() {
671
+ Ordering::Less
672
+ } else {
673
+ Ordering::Greater
674
+ });
675
+ }
676
+ let bits = rounded.to_bits();
677
+ let exponent = ((bits >> 52) & 0x7ff) as usize;
678
+ if !(1023 + 127..2047).contains(&exponent) {
679
+ return None;
680
+ }
681
+ let mut words = [0_u64; 16];
682
+ if unsafe { sn_integer1024(value, words.as_mut_ptr()) }.abs() > 1 {
683
+ return None;
684
+ }
685
+ let shift = exponent - 1023 - 52;
686
+ let magnitude = u128::from((bits & ((1_u64 << 52) - 1)) | (1_u64 << 52)) << (shift % 64);
687
+ let mut float_words = [0_u64; 16];
688
+ float_words[shift / 64] = magnitude as u64;
689
+ if shift / 64 + 1 < float_words.len() {
690
+ float_words[shift / 64 + 1] = (magnitude >> 64) as u64;
691
+ }
692
+ let order = float_words.iter().rev().cmp(words.iter().rev());
693
+ Some(if rounded.is_sign_negative() {
694
+ order.reverse()
695
+ } else {
696
+ order
697
+ })
698
+ }
699
+
700
+ impl Rules {
701
+ // Shared by validity scans and diagnostic prechecks. Numeric traversal
702
+ // borrows only opaque VALUEs and uses the compiled primitive shortcuts.
703
+ pub(super) fn eval_guarded(&self, required: i32, value: Value) -> Verdict {
704
+ let fixnum = value & self.fixnum_flag != 0;
705
+ if fixnum {
706
+ if required != 0 && required & TYPE_INTEGRAL_NUMBER == 0 {
707
+ return Invalid;
708
+ }
709
+ if let Some(multiple) = &self.fast_multiple {
710
+ return multiple.eval(immediate_integer(value));
711
+ }
712
+ if let Some(limits) = &self.fast_limits {
713
+ return fixnum_bounds(immediate_integer(value), limits);
714
+ }
715
+ }
716
+ if self.bounds_view {
717
+ return self.eval_bounds_guarded(required, value);
718
+ }
719
+ let kind = if fixnum {
720
+ TYPE_INTEGRAL_NUMBER
721
+ } else {
722
+ unsafe { sn_kind(value) }
723
+ };
724
+ if kind == TYPE_UNSUPPORTED {
725
+ Unsupported
726
+ } else if required != 0 && kind & required == 0 {
727
+ Invalid
728
+ } else if kind & TYPE_NUMBER == 0 {
729
+ Valid
730
+ } else {
731
+ self.eval(value)
732
+ }
733
+ }
734
+
735
+ pub(super) fn eval_bounds_guarded(&self, required: i32, value: Value) -> Verdict {
736
+ if let [(bit, limit)] = self.limits.as_slice()
737
+ && let Some(bound) = limit.wide_integer
738
+ {
739
+ // Both original handles are checked by the nonallocating Big/Big
740
+ // comparator. No magnitude export is needed for a single bound.
741
+ // Other kinds retain the scalar/type checks and Float tie rules.
742
+ let order = unsafe { sn_big_order(value, bound) };
743
+ if order != 3 {
744
+ return verdict(
745
+ (required == 0 || required & TYPE_INTEGRAL_NUMBER != 0)
746
+ && match *bit {
747
+ MAXIMUM => order <= 0,
748
+ MINIMUM => order >= 0,
749
+ EXCLUSIVE_MAXIMUM => order < 0,
750
+ _ => order > 0,
751
+ },
752
+ );
753
+ }
754
+ }
755
+ let mut words = std::mem::MaybeUninit::<[u64; 2]>::uninit();
756
+ let mut kind = std::mem::MaybeUninit::<i32>::uninit();
757
+ let mut floating = std::mem::MaybeUninit::<f64>::uninit();
758
+ let sign = unsafe {
759
+ sn_number_bounds_view(
760
+ value,
761
+ words.as_mut_ptr().cast(),
762
+ kind.as_mut_ptr(),
763
+ floating.as_mut_ptr(),
764
+ required,
765
+ )
766
+ };
767
+ // The adapter always initializes kind, but only initializes the
768
+ // operand selected by its kind and sign. Never read unused outputs.
769
+ let kind = unsafe { kind.assume_init() };
770
+ if kind == TYPE_UNSUPPORTED {
771
+ return Unsupported;
772
+ }
773
+ if required != 0 && kind & required == 0 {
774
+ return Invalid;
775
+ }
776
+ if kind & TYPE_NUMBER == 0 {
777
+ return Valid;
778
+ }
779
+ if sign == ADAPTER_UNSUPPORTED {
780
+ let floating = unsafe { floating.assume_init() };
781
+ for &(bit, ref limit) in &self.limits {
782
+ let order = match limit.float_order(floating) {
783
+ Ok(order) => order,
784
+ Err(status) => return status,
785
+ };
786
+ if match bit {
787
+ MAXIMUM => order == Some(Ordering::Greater),
788
+ MINIMUM => order == Some(Ordering::Less),
789
+ EXCLUSIVE_MAXIMUM => matches!(order, Some(Ordering::Greater | Ordering::Equal)),
790
+ _ => matches!(order, Some(Ordering::Less | Ordering::Equal)),
791
+ } {
792
+ return Invalid;
793
+ }
794
+ }
795
+ return Valid;
796
+ }
797
+ let integer = if sign.abs() > 1 {
798
+ None
799
+ } else {
800
+ integer_words_status(&unsafe { words.assume_init() }, sign).ok()
801
+ };
802
+ if integer.is_none()
803
+ && let Some(signs) = self.bounded_signs
804
+ {
805
+ // These Integer limits all fit i128. Every larger magnitude is
806
+ // strictly outside them; compilation already combined their bits.
807
+ return verdict(signs[(sign > 0) as usize]);
808
+ }
809
+ for &(bit, ref limit) in &self.limits {
810
+ let Some(order) = limit.integer_order(value, integer, sign) else {
811
+ return Unsupported;
812
+ };
813
+ if match bit {
814
+ MAXIMUM => order == Ordering::Greater,
815
+ MINIMUM => order == Ordering::Less,
816
+ EXCLUSIVE_MAXIMUM => order != Ordering::Less,
817
+ _ => order != Ordering::Greater,
818
+ } {
819
+ return Invalid;
820
+ }
821
+ }
822
+ Valid
823
+ }
824
+
825
+ pub(super) fn eval(&self, value: Value) -> Verdict {
826
+ if let Some(multiple) = &self.fast_multiple
827
+ && value & self.fixnum_flag != 0
828
+ {
829
+ return multiple.eval(immediate_integer(value));
830
+ }
831
+ if let Some(limits) = &self.fast_limits
832
+ && value & self.fixnum_flag != 0
833
+ {
834
+ return fixnum_bounds(immediate_integer(value), limits);
835
+ }
836
+ self.check(value, |_| true)
837
+ }
838
+
839
+ pub(super) fn failures(&self, value: Value) -> Result<i32, Verdict> {
840
+ if let Some(multiple) = &self.fast_multiple
841
+ && value & self.fixnum_flag != 0
842
+ {
843
+ return Ok(multiple.failures(immediate_integer(value)));
844
+ }
845
+ if let Some(limits) = &self.fast_limits
846
+ && value & self.fixnum_flag != 0
847
+ {
848
+ let n = immediate_integer(value);
849
+ return Ok(limits.iter().fold(0, |bits, &(bit, limit)| {
850
+ bits | if match bit {
851
+ MAXIMUM => n > limit,
852
+ MINIMUM => n < limit,
853
+ EXCLUSIVE_MAXIMUM => n >= limit,
854
+ _ => n <= limit,
855
+ } {
856
+ bit
857
+ } else {
858
+ 0
859
+ }
860
+ }));
861
+ }
862
+ let mut bits = 0;
863
+ let status = self.check(value, |bit| {
864
+ bits |= bit;
865
+ false
866
+ });
867
+ if status == Unsupported {
868
+ Err(status)
869
+ } else {
870
+ Ok(bits)
871
+ }
872
+ }
873
+
874
+ fn check_wide_multiple(
875
+ &self,
876
+ value: Value,
877
+ divisor: Fraction,
878
+ sum_words: bool,
879
+ mut failure: impl FnMut(i32) -> bool,
880
+ ) -> Verdict {
881
+ let mut words = [0_u64; 16];
882
+ let sign = unsafe { sn_integer1024(value, words.as_mut_ptr()) };
883
+ let integer = integer_words_status(&words, sign).ok();
884
+ let mut invalid = false;
885
+ for &(bit, ref limit) in &self.limits {
886
+ let Some(order) = limit.integer_order(value, integer, sign) else {
887
+ return Unsupported;
888
+ };
889
+ if match bit {
890
+ MAXIMUM => order == Ordering::Greater,
891
+ MINIMUM => order == Ordering::Less,
892
+ EXCLUSIVE_MAXIMUM => order != Ordering::Less,
893
+ _ => order != Ordering::Greater,
894
+ } {
895
+ invalid = true;
896
+ if failure(bit) {
897
+ return Invalid;
898
+ }
899
+ }
900
+ }
901
+ let result = if let Some(n) = integer {
902
+ verdict(
903
+ Fraction {
904
+ numerator: n,
905
+ denominator: 1,
906
+ }
907
+ .multiple_of(divisor),
908
+ )
909
+ } else if sign.abs() > 1 {
910
+ Unsupported
911
+ } else {
912
+ wide_multiple_words(&words, divisor, sum_words)
913
+ };
914
+ match result {
915
+ Unsupported => return Unsupported,
916
+ Invalid => {
917
+ invalid = true;
918
+ failure(MULTIPLE_OF);
919
+ }
920
+ Valid => {}
921
+ }
922
+ verdict(!invalid)
923
+ }
924
+
925
+ fn check(&self, value: Value, mut failure: impl FnMut(i32) -> bool) -> Verdict {
926
+ let mut invalid = false;
927
+ let float = unsafe { sn_is_float(value) } != 0;
928
+ if !float
929
+ && value & self.fixnum_flag == 0
930
+ && let Some(limit) = &self.multiple
931
+ && let Some(exact) = limit.exact
932
+ && exact.numerator > 1
933
+ {
934
+ return self.check_wide_multiple(value, exact, limit.sum_words, failure);
935
+ }
936
+ let (integer, large_sign) = if float {
937
+ (None, 0)
938
+ } else {
939
+ match read_known_integer_status(value, self.fixnum_flag) {
940
+ Ok(integer) => (Some(integer), 0),
941
+ Err(sign) => (None, sign),
942
+ }
943
+ };
944
+ if !float && integer.is_none() {
945
+ // An ordinary Integer outside i128 is beyond every representable
946
+ // exact limit (whose denominator is positive). Bounds alone need
947
+ // only its sign. Multiples can use a bounded magnitude below.
948
+ if large_sign == 0 {
949
+ return Unsupported;
950
+ }
951
+ for (bit, limit) in &self.limits {
952
+ let Some(order) = limit.integer_order(value, None, large_sign) else {
953
+ return Unsupported;
954
+ };
955
+ let violates = match *bit {
956
+ MAXIMUM => order == Ordering::Greater,
957
+ MINIMUM => order == Ordering::Less,
958
+ EXCLUSIVE_MAXIMUM => order != Ordering::Less,
959
+ _ => order != Ordering::Greater,
960
+ };
961
+ if violates {
962
+ invalid = true;
963
+ if failure(*bit) {
964
+ return Invalid;
965
+ }
966
+ }
967
+ }
968
+ if let Some(limit) = &self.multiple {
969
+ let Some(exact) = limit.exact else {
970
+ return Unsupported;
971
+ };
972
+ match wide_integer_multiple(value, exact, limit.sum_words) {
973
+ Unsupported => return Unsupported,
974
+ Invalid => {
975
+ invalid = true;
976
+ failure(MULTIPLE_OF);
977
+ }
978
+ Valid => {}
979
+ }
980
+ }
981
+ return verdict(!invalid);
982
+ }
983
+ let decimal = if self.multiple.is_some() {
984
+ if let Some(integer) = integer {
985
+ Some(Fraction {
986
+ numerator: integer,
987
+ denominator: 1,
988
+ })
989
+ } else {
990
+ let Some(decimal) = decimal_float(unsafe { sn_float(value) }) else {
991
+ return Unsupported;
992
+ };
993
+ Some(decimal)
994
+ }
995
+ } else {
996
+ None
997
+ };
998
+ for (bit, limit) in &self.limits {
999
+ let order = if integer.is_some() {
1000
+ let Some(order) = limit.integer_order(value, integer, 0) else {
1001
+ return Unsupported;
1002
+ };
1003
+ Some(order)
1004
+ } else if let Some(decimal) = decimal {
1005
+ if let Some(exact) = limit.exact {
1006
+ Some(decimal.compare(exact))
1007
+ } else if limit.wide_integer.is_some() {
1008
+ // The reduced decimal numerator fits i128 and its
1009
+ // denominator is positive. A bound outside i128 is
1010
+ // strictly beyond it; no Float/Big comparison is needed.
1011
+ Some(if limit.rounded.is_sign_negative() {
1012
+ Ordering::Greater
1013
+ } else {
1014
+ Ordering::Less
1015
+ })
1016
+ } else {
1017
+ return Unsupported;
1018
+ }
1019
+ } else {
1020
+ let n = unsafe { sn_float(value) };
1021
+ if limit.integer {
1022
+ match limit.float_order(n) {
1023
+ Ok(order) => order,
1024
+ Err(status) => return status,
1025
+ }
1026
+ } else {
1027
+ n.partial_cmp(&limit.rounded)
1028
+ }
1029
+ };
1030
+ let violates = match *bit {
1031
+ MAXIMUM => order == Some(Ordering::Greater),
1032
+ MINIMUM => order == Some(Ordering::Less),
1033
+ EXCLUSIVE_MAXIMUM => matches!(order, Some(Ordering::Greater | Ordering::Equal)),
1034
+ _ => matches!(order, Some(Ordering::Less | Ordering::Equal)),
1035
+ };
1036
+ if violates {
1037
+ invalid = true;
1038
+ if failure(*bit) {
1039
+ return Invalid;
1040
+ }
1041
+ }
1042
+ }
1043
+ if let Some(limit) = &self.multiple {
1044
+ let Some(exact) = limit.exact else {
1045
+ return Unsupported;
1046
+ };
1047
+ if !decimal.unwrap().multiple_of(exact) {
1048
+ invalid = true;
1049
+ failure(MULTIPLE_OF);
1050
+ }
1051
+ }
1052
+ verdict(!invalid)
1053
+ }
1054
+ }