1use super::array;
63use super::bdd::BddBuildError;
64use super::dist::{Dist, DistError, SELECTOR_BASE};
65use super::func::{self, Func, FuncError};
66use super::func_order;
67use super::guard;
68use super::ir::{Assignment, ConstraintEvalError, Expr, Ty, eval, mask};
69use super::joint;
70use super::order;
71use super::priority::{self, Soft};
72use super::randc::RandcState;
73use super::real_domain::{self, FloatKind, RealError, RealProblem};
74use super::reduce::{self, BlockItems};
75use super::solve::{Engine, Group, Measure, Plan, SolveError, Weighted};
76use crate::collections;
77use crate::netlist::ClassTemplateId;
78use crate::objects::{self, FieldSlot, Window};
79use num_bigint::BigUint;
80use num_traits::{ToPrimitive, Zero};
81use std::collections::HashMap;
82use std::sync::atomic::{AtomicU32, Ordering};
83use std::sync::{Arc, Mutex, OnceLock};
84
85#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
87pub enum Home {
88 Instance,
90 Static(u32),
95 Object(u64),
99}
100
101#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
105pub struct ProgHandle {
106 pub slot: FieldSlot,
108 pub home: Home,
110 pub prop_index: Option<usize>,
113 pub bit: u32,
117}
118
119#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
121pub struct ProgVar {
122 pub ty: Ty,
124 pub slot: FieldSlot,
126 pub home: Home,
128 pub prop_index: Option<usize>,
133 pub domain_conjunct: Option<usize>,
137 pub randc: bool,
140 pub window: Option<Window>,
145}
146
147impl ProgVar {
148 fn randc_key(&self) -> usize {
152 randc_key(self.slot, self.window)
153 }
154}
155
156fn randc_key(slot: FieldSlot, window: Option<Window>) -> usize {
159 match window {
160 None => slot.offset,
161 Some(w) => slot.offset | ((w.bit as usize + 1) << 32),
162 }
163}
164
165#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
167pub struct ProgState {
168 pub ty: Ty,
170 pub slot: FieldSlot,
172 pub window: Option<Window>,
175 pub home: Home,
177 pub via: Vec<FieldSlot>,
182 pub root_bit: u32,
186 #[serde(default)]
189 pub class_of: bool,
190}
191
192#[derive(Clone, Debug, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
194pub struct Program {
195 pub vars: Vec<ProgVar>,
197 pub states: Vec<ProgState>,
199 pub conjuncts: Vec<Expr>,
201 pub before: Vec<(u32, u32)>,
206 pub dists: Vec<Dist>,
210 pub real_vars: Vec<ProgRealVar>,
214 pub real: RealProblem,
217 pub arrays: Vec<ProgArray>,
220 pub funcs: Vec<Func>,
223 pub softs: Vec<Soft>,
227 pub rand_handles: Vec<ProgHandle>,
230 pub blocks: Vec<ProgBlock>,
234 pub n_disables: usize,
237 pub disable_keys: Vec<super::priority::SoftKey>,
241}
242
243#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
245pub struct ProgBlock {
246 pub home: Home,
250 pub ordinal: u32,
254 pub conjuncts: std::ops::Range<usize>,
256 pub items: BlockItems,
260}
261
262#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
264pub struct ProgArray {
265 pub elem_ty: Ty,
267 pub elem_words: usize,
270 pub slot: FieldSlot,
272 pub home: Home,
274 pub rand: bool,
277 pub prop_index: Option<usize>,
279 pub elem_domain: Option<Vec<BigUint>>,
282 pub assoc: bool,
286}
287
288#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
290pub struct ProgRealVar {
291 pub kind: FloatKind,
293 pub slot: FieldSlot,
296 pub home: Home,
298 pub prop_index: Option<usize>,
300 pub window: Option<Window>,
302}
303
304#[derive(Clone, Copy, Debug, PartialEq, Eq)]
307#[repr(u64)]
308pub enum Outcome {
309 Unsatisfiable = 0,
312 Solved = 1,
314 DeadHandle = 2,
316 NoProgram = 3,
319 StateUnknown = 4,
321 TooLarge = 5,
324 Failed = 6,
326 StaticUnavailable = 7,
331 BadDistWeight = 8,
334 GuardError = 9,
337 NullHandle = 10,
340 RealDomain = 11,
342 ArraySizeTooLarge = 12,
345 ArrayIndexOutOfRange = 13,
348 ArraySizeSearch = 14,
351 FunctionFailed = 15,
354 ImplicitOrderCycle = 16,
357 FunctionSearch = 17,
360 GlobalUnsupported = 18,
364 SatTimeout = 19,
367 SatUnsupported = 20,
373}
374
375impl Outcome {
376 pub fn code(self) -> u64 {
378 self as u64
379 }
380
381 pub fn from_code(code: u64) -> Option<Outcome> {
383 [
384 Outcome::Unsatisfiable,
385 Outcome::Solved,
386 Outcome::DeadHandle,
387 Outcome::NoProgram,
388 Outcome::StateUnknown,
389 Outcome::TooLarge,
390 Outcome::Failed,
391 Outcome::StaticUnavailable,
392 Outcome::BadDistWeight,
393 Outcome::GuardError,
394 Outcome::NullHandle,
395 Outcome::RealDomain,
396 Outcome::ArraySizeTooLarge,
397 Outcome::ArrayIndexOutOfRange,
398 Outcome::ArraySizeSearch,
399 Outcome::FunctionFailed,
400 Outcome::ImplicitOrderCycle,
401 Outcome::FunctionSearch,
402 Outcome::GlobalUnsupported,
403 Outcome::SatTimeout,
404 Outcome::SatUnsupported,
405 ]
406 .into_iter()
407 .find(|o| o.code() == code)
408 }
409
410 pub fn error_message(self) -> Option<&'static str> {
414 match self {
415 Outcome::Unsatisfiable | Outcome::Solved => None,
416 Outcome::DeadHandle => Some("randomize() on a null or deleted object"),
417 Outcome::NoProgram => Some(
418 "randomize(): the constraints of this object's class could not be compiled \
419 (see the class's constraint blocks)",
420 ),
421 Outcome::StateUnknown => Some(
422 "randomize(): a constraint reads a variable with an X or Z bit; a 4-state \
423 value in a constraint is an error (IEEE 1800-2023 18.3)",
424 ),
425 Outcome::TooLarge => Some(
426 "randomize(): the constraint problem is too large for both the BDD solver \
427 (node budget exceeded) and its SAT fallback (clause limit exceeded)",
428 ),
429 Outcome::SatTimeout => Some(
430 "randomize(): the constraint problem is too large for the BDD solver and \
431 the SAT fallback did not find a solution within its time limit",
432 ),
433 Outcome::SatUnsupported => Some(
434 "randomize(): the constraint problem is too large for the BDD solver and \
435 uses a `dist` over an expression with more than 512 feasible assignments, a `dist` \
436 over a variable wider than 32 bits or with more than 1024 feasible values per \
437 range, or a `randc` variable wider than 10 bits, which the SAT fallback does \
438 not support",
439 ),
440 Outcome::Failed => Some("randomize(): internal solver failure on a malformed program"),
441 Outcome::StaticUnavailable => Some(
442 "randomize(): the object's class declares a static property used by its \
443 constraints or `rand`/`randc` variables that this call site cannot reach \
444 (it is declared in a class derived from the handle's static type); call \
445 randomize() through a handle of that class",
446 ),
447 Outcome::BadDistWeight => Some(
448 "randomize(): a `dist` weight is negative, or a weight or range bound \
449 divides by zero (IEEE 1800-2023 18.5.3)",
450 ),
451 Outcome::GuardError => Some(
452 "randomize(): the guard of an implication or `if` constraint evaluated to \
453 ERROR (a null object handle, or an X/Z value, read where no operand \
454 decides it; IEEE 1800-2023 18.5.12)",
455 ),
456 Outcome::NullHandle => Some(
457 "randomize(): a constraint reads a member through a null or deleted object \
458 handle (IEEE 1800-2023 18.5.12)",
459 ),
460 Outcome::RealDomain => Some(
461 "randomize(): a constraint on a real variable evaluates to NaN or divides by \
462 zero, so it has no well-defined set of solutions",
463 ),
464 Outcome::ArraySizeTooLarge => Some(
465 "randomize(): the constraints on the size of a dynamic array or queue can \
466 only be met by a size above 65536 elements, which is not supported",
467 ),
468 Outcome::ArrayIndexOutOfRange => Some(
469 "randomize(): a constraint reads an array element outside the array's \
470 bounds (guard it with an `if` on the size, IEEE 1800-2023 18.5.7.1)",
471 ),
472 Outcome::ArraySizeSearch => Some(
473 "randomize(): no array size tried satisfied both the size constraints and \
474 the element constraints (the bounded search over sizes gave up)",
475 ),
476 Outcome::FunctionFailed => Some(
477 "randomize(): a function called from a constraint failed (division by zero, \
478 a runaway loop or recursion that is too deep)",
479 ),
480 Outcome::ImplicitOrderCycle => Some(
481 "randomize(): the random arguments of function calls in the constraints \
482 impose a circular solve order (IEEE 1800-2023 18.5.11)",
483 ),
484 Outcome::FunctionSearch => Some(
485 "randomize(): no draw satisfied a constraint that compares the results of \
486 function calls over several random variables (the bounded search gave up)",
487 ),
488 Outcome::GlobalUnsupported => Some(
489 "randomize(): the object tree being randomized together (a `rand` object \
490 member, IEEE 1800-2023 18.5.8) has `real` random variables, or a `static` \
491 random variable that two objects of the tree share, which the joint solver \
492 does not combine yet",
493 ),
494 }
495 }
496}
497
498type ProgramTable = HashMap<ClassTemplateId, Arc<Result<Program, String>>>;
499
500fn table() -> &'static Mutex<ProgramTable> {
501 static T: OnceLock<Mutex<ProgramTable>> = OnceLock::new();
502 T.get_or_init(|| Mutex::new(HashMap::new()))
503}
504
505pub fn register_program(template: ClassTemplateId, program: Result<Program, String>) {
511 table()
512 .lock()
513 .unwrap_or_else(|e| e.into_inner())
514 .insert(template, Arc::new(program));
515}
516
517pub fn program(template: ClassTemplateId) -> Option<Arc<Result<Program, String>>> {
519 table()
520 .lock()
521 .unwrap_or_else(|e| e.into_inner())
522 .get(&template)
523 .cloned()
524}
525
526#[derive(serde::Serialize, serde::Deserialize)]
530struct Snapshot {
531 programs: Vec<(u32, Result<Program, String>)>,
533 specs: Vec<(u32, Spec)>,
535}
536
537pub fn snapshot(templates: &[ClassTemplateId]) -> Vec<u8> {
543 let mut ids: Vec<u32> = templates.iter().map(|t| t.0).collect();
544 ids.sort_unstable();
545 ids.dedup();
546 let programs: Vec<(u32, Result<Program, String>)> = ids
547 .into_iter()
548 .filter_map(|id| program(ClassTemplateId(id)).map(|p| (id, Result::clone(&p))))
549 .collect();
550 let specs: Vec<(u32, Spec)> = all_specs()
551 .into_iter()
552 .map(|(id, s)| (id, Spec::clone(&s)))
553 .collect();
554 if programs.is_empty() && specs.is_empty() {
555 return Vec::new();
556 }
557 postcard::to_allocvec(&Snapshot { programs, specs })
558 .expect("a constraint program is plain data and always serializes")
559}
560
561pub fn preload(bytes: &[u8]) -> Result<(), String> {
564 if bytes.is_empty() {
565 return Ok(());
566 }
567 let snap: Snapshot =
568 postcard::from_bytes(bytes).map_err(|e| format!("randomize section: {e}"))?;
569 for (id, p) in snap.programs {
570 register_program(ClassTemplateId(id), p);
571 }
572 for (id, s) in snap.specs {
573 register_spec_at(id, s);
574 }
575 Ok(())
576}
577
578pub(super) fn unpack(words: &[u64], width: u32) -> (BigUint, BigUint) {
580 let mut n = BigUint::zero();
581 for (i, w) in words.iter().enumerate() {
582 n |= BigUint::from(*w) << (64 * i);
583 }
584 let m = mask(width);
585 (&n & &m, (n >> width) & m)
586}
587
588fn pack(val: &BigUint, words: usize) -> Option<Vec<u64>> {
591 let mut d = val.to_u64_digits();
592 if d.len() > words {
593 return None;
594 }
595 d.resize(words, 0);
596 Some(d)
597}
598
599fn read(
602 h: u64,
603 slot: FieldSlot,
604 window: Option<Window>,
605 ty: Ty,
606) -> Result<Option<BigUint>, Outcome> {
607 let (val, unk) = read_unknown(h, slot, window, ty.width)?;
608 Ok(if unk.is_zero() { Some(val) } else { None })
609}
610
611fn read_unknown(
614 h: u64,
615 slot: FieldSlot,
616 window: Option<Window>,
617 width: u32,
618) -> Result<(BigUint, BigUint), Outcome> {
619 match window {
620 None => {
621 let mut buf = vec![0u64; slot.words];
622 if !objects::field_get(h, slot, &mut buf) {
623 return Err(Outcome::DeadHandle);
624 }
625 Ok(unpack(&buf, width))
626 }
627 Some(w) => {
628 let n = (w.width as usize).div_ceil(64);
629 let (mut val, mut unk) = (vec![0u64; n], vec![0u64; n]);
630 if !objects::field_get_window(h, slot, w, &mut val, &mut unk) {
631 return Err(Outcome::DeadHandle);
632 }
633 let m = mask(width);
634 let to_big = |ws: &[u64]| {
635 ws.iter().enumerate().fold(BigUint::zero(), |a, (i, x)| {
636 a | (BigUint::from(*x) << (64 * i))
637 })
638 };
639 Ok((to_big(&val) & &m, to_big(&unk) & &m))
640 }
641 }
642}
643
644enum Reading {
646 Value(Option<BigUint>),
648 NullHandle,
650}
651
652fn read_state(owner: u64, s: &ProgState) -> Result<Reading, Outcome> {
656 let (mut obj, mut slot) = (owner, s.slot);
657 for (i, hop) in s.via.iter().enumerate() {
658 let mut buf = vec![0u64; slot.words];
659 let bit = if i == 0 { s.root_bit } else { 0 };
660 if !objects::field_get(obj, slot, &mut buf) {
661 return if i == 0 {
662 Err(Outcome::DeadHandle)
663 } else {
664 Ok(Reading::NullHandle)
665 };
666 }
667 let w = (bit / 64) as usize;
671 let half = slot.words / 2;
672 let (Some(&v), Some(&u)) = (buf.get(w), buf.get(half + w)) else {
673 return Err(Outcome::Failed);
674 };
675 let (val, unk) = (BigUint::from(v), BigUint::from(u));
676 if !unk.is_zero() {
677 return Ok(Reading::Value(None));
678 }
679 match val.to_u64() {
680 Some(0) => return Ok(Reading::NullHandle),
681 Some(next) => obj = next,
682 None => return Err(Outcome::Failed),
683 }
684 slot = *hop;
685 }
686 if s.class_of {
687 let (val, unk) = match read_unknown(obj, slot, None, 64) {
688 Ok(r) => r,
689 Err(_) if s.via.is_empty() => return Err(Outcome::DeadHandle),
690 Err(_) => return Ok(Reading::NullHandle),
691 };
692 if !unk.is_zero() {
693 return Ok(Reading::Value(None));
694 }
695 return match val.to_u64() {
696 Some(0) => Ok(Reading::NullHandle),
697 Some(h) => Ok(match objects::dynamic_template(h) {
698 Some(t) => Reading::Value(Some(BigUint::from(t.0))),
699 None => Reading::NullHandle,
700 }),
701 None => Err(Outcome::Failed),
702 };
703 }
704 match read_unknown(obj, slot, s.window, s.ty.width) {
705 Ok((val, unk)) => Ok(Reading::Value(if unk.is_zero() { Some(val) } else { None })),
706 Err(_) if s.via.is_empty() => Err(Outcome::DeadHandle),
707 Err(_) => Ok(Reading::NullHandle),
708 }
709}
710
711pub(super) fn owner(home: Home, h: u64, statics: &[(u32, u64)]) -> Result<u64, Outcome> {
714 match home {
715 Home::Instance => Ok(h),
716 Home::Object(o) => Ok(o),
717 Home::Static(id) => statics
718 .iter()
719 .find(|(i, s)| *i == id && *s != 0)
720 .map(|&(_, s)| s)
721 .ok_or(Outcome::StaticUnavailable),
722 }
723}
724
725fn unreadable_outcome(id: u32, null_state: &[bool]) -> Outcome {
727 if null_state.get(id as usize).copied().unwrap_or(false) {
728 Outcome::NullHandle
729 } else {
730 Outcome::StateUnknown
731 }
732}
733
734fn outcome_of(e: SolveError, null_state: &[bool]) -> Outcome {
736 match e {
737 SolveError::Unsatisfiable => Outcome::Unsatisfiable,
738 SolveError::Build(BddBuildError::StateUnknown(id)) => unreadable_outcome(id, null_state),
739 SolveError::Build(BddBuildError::TooLarge { .. } | BddBuildError::CnfTooLarge { .. }) => {
740 Outcome::TooLarge
741 }
742 SolveError::SatTimeout => Outcome::SatTimeout,
743 SolveError::SatUnsupported { .. } => Outcome::SatUnsupported,
744 SolveError::Build(_)
745 | SolveError::BadOrder { .. }
746 | SolveError::BadPlan { .. }
747 | SolveError::SatModelRejected
748 | SolveError::SatSolverFailure { .. } => Outcome::Failed,
749 }
750}
751
752fn outcome_of_dist(e: DistError, null_state: &[bool]) -> Outcome {
754 match e {
755 DistError::Eval(ConstraintEvalError::StateUnknown(id)) => {
756 unreadable_outcome(id, null_state)
757 }
758 DistError::Eval(ConstraintEvalError::DivideByZero) | DistError::NegativeWeight => {
759 Outcome::BadDistWeight
760 }
761 DistError::Eval(
762 ConstraintEvalError::UnboundVar(_)
763 | ConstraintEvalError::UnboundState(_)
764 | ConstraintEvalError::Unexpanded,
765 ) => Outcome::Failed,
766 }
767}
768
769fn outcome_of_real(e: RealError, null_state: &[bool]) -> Outcome {
771 match e {
772 RealError::Unsatisfiable => Outcome::Unsatisfiable,
773 RealError::GuardError => Outcome::GuardError,
774 RealError::StateUnreadable(id) => unreadable_outcome(id, null_state),
775 RealError::NonFinite(_) => Outcome::RealDomain,
776 RealError::BadWeight => Outcome::BadDistWeight,
777 RealError::Shape(_) => Outcome::Failed,
778 }
779}
780
781struct Write {
783 owner: u64,
784 slot: FieldSlot,
785 window: Option<Window>,
786 words: Vec<u64>,
789}
790
791impl Write {
792 fn new(owner: u64, slot: FieldSlot, window: Option<Window>, val: &BigUint) -> Option<Write> {
795 let n = match window {
796 None => slot.words,
797 Some(w) => (w.width as usize).div_ceil(64),
798 };
799 Some(Write {
800 owner,
801 slot,
802 window,
803 words: pack(val, n)?,
804 })
805 }
806
807 fn apply(&self) -> bool {
809 match self.window {
810 None => objects::field_set(self.owner, self.slot, &self.words),
811 Some(w) => objects::field_set_window(self.owner, self.slot, w, &self.words),
812 }
813 }
814}
815
816#[allow(clippy::too_many_arguments)]
821fn run_real(
822 program: &Program,
823 h: u64,
824 rand_mode: &[bool],
825 statics: &[(u32, u64)],
826 states: &[Option<BigUint>],
827 null_state: &[bool],
828 ints: Option<(&[Option<u32>], &[BigUint])>,
829 seed: &mut u64,
830) -> Result<Vec<Write>, Outcome> {
831 if program.real_vars.is_empty() {
832 return Ok(Vec::new());
833 }
834 if program.real_vars.len() != program.real.kinds.len() {
835 return Err(Outcome::Failed);
836 }
837 let mut owners = Vec::with_capacity(program.real_vars.len());
838 let mut known = Vec::with_capacity(program.real_vars.len());
839 for v in &program.real_vars {
840 let o = owner(v.home, h, statics)?;
841 let active = match v.prop_index {
842 Some(i) => rand_mode.get(i).copied().ok_or(Outcome::Failed)?,
843 None => true,
844 };
845 known.push(if active {
846 None
847 } else {
848 match read(o, v.slot, v.window, Ty::unsigned(v.kind.width()))? {
849 Some(bits) => Some(v.kind.from_bits(&bits)),
850 None => return Err(Outcome::StateUnknown),
851 }
852 });
853 owners.push(o);
854 }
855 let (lowered, ext);
860 let (problem, states) = match ints {
861 Some((var_new, values)) if program.real.reads_int_vars() => {
862 let mut e = states.to_vec();
863 let mut ix = Vec::with_capacity(var_new.len());
864 let mut inactive = program.states.len();
865 for d in var_new {
866 match d {
867 Some(d) => {
868 ix.push(e.len() as u32);
869 e.push(Some(
870 values.get(*d as usize).ok_or(Outcome::Failed)?.clone(),
871 ));
872 }
873 None => {
874 ix.push(inactive as u32);
875 inactive += 1;
876 }
877 }
878 }
879 lowered = program
880 .real
881 .with_int_vars(|v, t| Expr::State(ix[v as usize], t));
882 ext = e;
883 (&lowered, &ext[..])
884 }
885 _ => (&program.real, states),
886 };
887 let solved = real_domain::solve(problem, &known, states, seed)
888 .map_err(|e| outcome_of_real(e, null_state))?;
889 let mut writes = Vec::new();
890 for ((v, o), x) in program.real_vars.iter().zip(owners).zip(solved) {
891 let Some(x) = x else { continue };
892 match Write::new(o, v.slot, v.window, &BigUint::from(v.kind.to_bits(x))) {
893 Some(w) => writes.push(w),
894 None => return Err(Outcome::Failed),
895 }
896 }
897 Ok(writes)
898}
899
900struct Call<'a> {
903 program: &'a Program,
904 h: u64,
905 rand_mode: &'a [bool],
906 statics: &'a [(u32, u64)],
907 var_owner: Vec<u64>,
908 var_new: Vec<Option<u32>>,
910 var_tys: Vec<Ty>,
912 active_ids: Vec<usize>,
914 arrays: Vec<array::ArrayState>,
916 array_owner: Vec<u64>,
917 states: Vec<Option<BigUint>>,
919 null_state: Vec<bool>,
921 real_unsat: std::cell::Cell<bool>,
924}
925
926struct Solution {
928 values: Vec<BigUint>,
930 randc_keys: Vec<(u64, usize)>,
931 randc_work: Vec<RandcState>,
932 real_writes: Vec<Write>,
933}
934
935fn read_array(a: &ProgArray, owner: u64, active: bool) -> Result<array::ArrayState, Outcome> {
937 let mut buf = vec![0u64; a.slot.words];
938 if !objects::field_get(owner, a.slot, &mut buf) {
939 return Err(Outcome::DeadHandle);
940 }
941 let (handle, unk) = unpack(&buf, 64);
942 if !unk.is_zero() {
943 return Err(Outcome::Failed);
944 }
945 let handle = handle.to_u64().ok_or(Outcome::Failed)?;
946 let cur = collections::elements(handle)
947 .iter()
948 .map(|words| {
949 let (val, unk) = unpack(words, a.elem_ty.width);
950 if unk.is_zero() { Some(val) } else { None }
951 })
952 .collect();
953 Ok(array::ArrayState {
954 elem_ty: a.elem_ty,
955 active,
956 cur,
957 domain: a.elem_domain.clone(),
958 })
959}
960
961fn outcome_of_func(e: FuncError, null_state: &[bool]) -> Outcome {
963 match e {
964 FuncError::Eval(ConstraintEvalError::StateUnknown(id)) => {
965 unreadable_outcome(id, null_state)
966 }
967 FuncError::Eval(ConstraintEvalError::DivideByZero)
968 | FuncError::TooDeep { .. }
969 | FuncError::StepLimit { .. } => Outcome::FunctionFailed,
970 FuncError::Eval(
971 ConstraintEvalError::UnboundVar(_)
972 | ConstraintEvalError::UnboundState(_)
973 | ConstraintEvalError::Unexpanded,
974 )
975 | FuncError::Malformed(_) => Outcome::Failed,
976 }
977}
978
979fn outcome_of_expand(e: array::ExpandError, null_state: &[bool]) -> Outcome {
981 match e {
982 array::ExpandError::IndexOutOfRange { .. }
983 | array::ExpandError::LeafIndexOutOfRange { .. } => Outcome::ArrayIndexOutOfRange,
984 array::ExpandError::GuardError => Outcome::GuardError,
985 array::ExpandError::StateUnknown(id) => unreadable_outcome(id, null_state),
986 array::ExpandError::Func(e) => outcome_of_func(e, null_state),
987 array::ExpandError::NonConstantIndex
988 | array::ExpandError::DistRandomBound
989 | array::ExpandError::Malformed(_) => Outcome::Failed,
990 }
991}
992
993fn outcome_of_size(e: array::SizeError, null_state: &[bool]) -> Outcome {
995 match e {
996 array::SizeError::Unsatisfiable => Outcome::Unsatisfiable,
997 array::SizeError::TooLarge => Outcome::ArraySizeTooLarge,
998 array::SizeError::GuardError => Outcome::GuardError,
999 array::SizeError::Solve(e) => outcome_of(e, null_state),
1000 }
1001}
1002
1003const MAX_CHECK_TRIES: usize = 4096;
1006
1007fn checks_hold(
1010 checks: &[Expr],
1011 drawn: &[BigUint],
1012 funcs: &[Func],
1013 states: &[Option<BigUint>],
1014 null_state: &[bool],
1015) -> Result<bool, Outcome> {
1016 let asg = Assignment {
1017 vars: drawn.to_vec(),
1018 states: states.to_vec(),
1019 };
1020 for c in checks {
1021 let c = c.substitute_vars(&mut |v, t| match drawn.get(v as usize) {
1022 Some(val) => Expr::Const(t, val.clone()),
1023 None => Expr::Var(v, t),
1024 });
1025 let c = func::fold_ground_calls(&c, funcs, states)
1026 .map_err(|e| outcome_of_func(e, null_state))?;
1027 let v = eval(&c, &asg).map_err(|e| outcome_of_func(FuncError::Eval(e), null_state))?;
1028 if v.is_zero() {
1029 return Ok(false);
1030 }
1031 }
1032 Ok(true)
1033}
1034
1035fn solve_expanded(
1047 call: &Call<'_>,
1048 x: &array::Expanded,
1049 seed: &mut u64,
1050) -> Result<Solution, Outcome> {
1051 let joint = call.program.real.reads_int_vars();
1052 let tries = if joint { REAL_JOINT_TRIES } else { 1 };
1053 for _ in 1..tries {
1054 call.real_unsat.set(false);
1055 match solve_expanded_once(call, x, seed) {
1056 Err(Outcome::Unsatisfiable) if call.real_unsat.get() => {}
1057 r => return r,
1058 }
1059 }
1060 solve_expanded_once(call, x, seed)
1061}
1062
1063const REAL_JOINT_TRIES: usize = 4096;
1065
1066fn solve_expanded_once(
1067 call: &Call<'_>,
1068 x: &array::Expanded,
1069 seed: &mut u64,
1070) -> Result<Solution, Outcome> {
1071 let program = call.program;
1072 let n_active = call.var_tys.len();
1073 let n_real = n_active + x.elem_vars.len();
1074 let mut states = call.states.clone();
1075 states.extend(x.extra_states.iter().cloned());
1076 let mut null_state = call.null_state.clone();
1077 null_state.resize(states.len(), false);
1078 let mut var_tys = call.var_tys.clone();
1079 var_tys.extend(x.elem_tys.iter().copied());
1080
1081 let conjuncts: Vec<Expr> = x
1084 .conjuncts
1085 .iter()
1086 .map(|c| {
1087 c.substitute_vars(&mut |v, t| {
1088 if v >= SELECTOR_BASE {
1089 Expr::Var(n_real as u32 + (v - SELECTOR_BASE), t)
1090 } else {
1091 Expr::Var(v, t)
1092 }
1093 })
1094 })
1095 .collect();
1096 let assignment = Assignment {
1097 vars: Vec::new(),
1098 states: states.clone(),
1099 };
1100
1101 let has_calls = conjuncts.iter().any(func::has_call);
1106 let stage_of: Vec<u32> = if has_calls {
1107 func_order::stages(n_real, &conjuncts).map_err(|_| Outcome::ImplicitOrderCycle)?
1108 } else {
1109 vec![0; n_real]
1110 };
1111 let n_stages = stage_of.iter().max().map_or(1, |m| *m as usize + 1);
1112 let mut conj_stage: Vec<usize> = Vec::with_capacity(conjuncts.len());
1115 let n_prog_dists = program.dists.len();
1119 let all_dists: Vec<&Dist> = program.dists.iter().chain(&x.extra_dists).collect();
1120 let mut dist_stage: Vec<usize> = vec![0; all_dists.len()];
1121 for c in &conjuncts {
1122 let mut s = 0usize;
1123 c.for_each_var(&mut |v, _| {
1124 if let Some(&st) = stage_of.get(v as usize) {
1125 s = s.max(st as usize);
1126 }
1127 });
1128 c.for_each_var(&mut |v, _| {
1129 if let Some(j) = (v as usize).checked_sub(n_real)
1130 && let Some(d) = dist_stage.get_mut(j)
1131 {
1132 *d = s;
1133 }
1134 });
1135 conj_stage.push(s);
1136 }
1137
1138 let mut edges: Vec<(u32, u32)> = Vec::with_capacity(program.before.len());
1140 for &(a, b) in &program.before {
1141 let (Some(&na), Some(&nb)) = (call.var_new.get(a as usize), call.var_new.get(b as usize))
1142 else {
1143 return Err(Outcome::Failed);
1144 };
1145 if let (Some(na), Some(nb)) = (na, nb) {
1146 edges.push((na, nb));
1147 }
1148 }
1149
1150 let mut randc_dense: Vec<u32> = Vec::new();
1154 let mut randc_keys: Vec<(u64, usize)> = Vec::new();
1155 let mut randc_work: Vec<RandcState> = Vec::new();
1156 for (dense, &i) in call.active_ids.iter().enumerate() {
1157 let v = &program.vars[i];
1158 if !v.randc {
1159 continue;
1160 }
1161 let Some(st) = objects::randc_state(call.var_owner[i], v.randc_key()) else {
1162 return Err(Outcome::DeadHandle);
1163 };
1164 randc_dense.push(dense as u32);
1165 randc_keys.push((call.var_owner[i], v.randc_key()));
1166 randc_work.push(st);
1167 }
1168 let mut dist_weights = Vec::with_capacity(all_dists.len());
1170 for d in &all_dists {
1171 if d.subject.is_some() {
1172 dist_weights.push(Vec::new());
1173 } else {
1174 match d.weights(&states) {
1175 Ok(w) => dist_weights.push(w),
1176 Err(e) => return Err(outcome_of_dist(e, &null_state)),
1177 }
1178 }
1179 var_tys.push(d.selector_ty());
1180 }
1181
1182 let mut values: Vec<BigUint> = vec![BigUint::zero(); n_real];
1183 let mut known: Vec<Option<BigUint>> = vec![None; n_real];
1184 for stage in 0..n_stages {
1185 let in_stage = |v: u32| {
1186 stage_of
1187 .get(v as usize)
1188 .is_some_and(|s| *s as usize == stage)
1189 };
1190 let mut plain: Vec<Expr> = Vec::new();
1195 let mut checks: Vec<Expr> = Vec::new();
1196 for (c, _) in conjuncts
1197 .iter()
1198 .zip(&conj_stage)
1199 .filter(|(_, s)| **s == stage)
1200 {
1201 let mut c = c.clone();
1202 if has_calls {
1203 if stage > 0 {
1204 c = c.substitute_vars(&mut |v, t| match known.get(v as usize) {
1205 Some(Some(val)) => Expr::Const(t, val.clone()),
1206 _ => Expr::Var(v, t),
1207 });
1208 }
1209 c = func::fold_ground_calls(&c, &program.funcs, &states)
1210 .map_err(|e| outcome_of_func(e, &null_state))?;
1211 }
1212 if func::has_call(&c) {
1213 checks.push(c);
1214 continue;
1215 }
1216 match guard::resolve(&c, &assignment) {
1219 Ok(r) => plain.push(r),
1220 Err(_) => return Err(Outcome::GuardError),
1221 }
1222 }
1223
1224 let stage_edges: Vec<(u32, u32)> = edges
1225 .iter()
1226 .copied()
1227 .filter(|&(a, b)| in_stage(a) && in_stage(b))
1228 .collect();
1229 let Ok(groups) = order::groups(n_real, &stage_edges) else {
1230 return Err(Outcome::Failed);
1231 };
1232 let stage_randc: Vec<usize> = (0..randc_dense.len())
1234 .filter(|&k| in_stage(randc_dense[k]))
1235 .collect();
1236 let stage_randc_dense: Vec<u32> = stage_randc.iter().map(|&k| randc_dense[k]).collect();
1237 let mut plan_groups: Vec<Group> = groups
1241 .into_iter()
1242 .map(|g| Group {
1243 vars: g
1244 .into_iter()
1245 .filter(|v| in_stage(*v) && !randc_dense.contains(v))
1246 .collect(),
1247 weighted: Vec::new(),
1248 })
1249 .collect();
1250 let stage_dists: Vec<usize> = (0..all_dists.len())
1251 .filter(|&j| dist_stage[j] == stage && all_dists[j].subject.is_none())
1252 .collect();
1253 if !stage_dists.is_empty() && plan_groups.is_empty() {
1254 plan_groups.push(Group::default());
1255 }
1256 for &j in &stage_dists {
1257 let d = all_dists[j];
1258 let dense = |v: u32| {
1260 if j >= n_prog_dists {
1261 Some(v)
1262 } else {
1263 call.var_new.get(v as usize).copied().flatten()
1264 }
1265 };
1266 let subject: Vec<u32> = d
1267 .vars
1268 .iter()
1269 .filter_map(|&v| dense(v))
1270 .filter(|&v| in_stage(v))
1271 .collect();
1272 let gi = plan_groups
1273 .iter()
1274 .position(|g| g.vars.iter().any(|v| subject.contains(v)))
1275 .unwrap_or(plan_groups.len() - 1);
1276 let measure = match (
1277 d.plain_var.and_then(dense).filter(|&v| in_stage(v)),
1278 subject.is_empty(),
1279 ) {
1280 (Some(s), _) => Measure::Distinct(s),
1281 (None, false) => Measure::Models,
1282 (None, true) => Measure::Flat,
1283 };
1284 plan_groups[gi].weighted.push(Weighted {
1285 var: (n_real + j) as u32,
1286 weights: dist_weights[j].clone(),
1287 measure,
1288 });
1289 }
1290 let plan = Plan {
1291 groups: plan_groups
1292 .into_iter()
1293 .filter(|g| !g.vars.is_empty() || !g.weighted.is_empty())
1294 .collect(),
1295 randc: stage_randc_dense,
1296 };
1297
1298 let solver =
1299 Engine::new(&var_tys, &states, &plain).map_err(|e| outcome_of(e, &null_state))?;
1300 let mut tries = 0;
1301 let drawn = loop {
1302 let mut work: Vec<RandcState> =
1303 stage_randc.iter().map(|&k| randc_work[k].clone()).collect();
1304 let drawn = solver
1305 .sample_plan(&plan, &mut work, seed)
1306 .map_err(|e| outcome_of(e, &null_state))?;
1307 if checks_hold(&checks, &drawn, &program.funcs, &states, &null_state)? {
1308 for (&k, st) in stage_randc.iter().zip(work) {
1309 randc_work[k] = st;
1310 }
1311 break drawn;
1312 }
1313 tries += 1;
1314 if tries >= MAX_CHECK_TRIES {
1315 return Err(Outcome::FunctionSearch);
1316 }
1317 };
1318 for v in 0..n_real {
1319 if in_stage(v as u32) {
1320 values[v] = drawn[v].clone();
1321 known[v] = Some(drawn[v].clone());
1322 }
1323 }
1324 }
1325
1326 let real_writes = run_real(
1329 program,
1330 call.h,
1331 call.rand_mode,
1332 call.statics,
1333 &states,
1334 &null_state,
1335 Some((&call.var_new, &values)),
1336 seed,
1337 )
1338 .inspect_err(|o| call.real_unsat.set(*o == Outcome::Unsatisfiable))?;
1339 Ok(Solution {
1340 values,
1341 randc_keys,
1342 randc_work,
1343 real_writes,
1344 })
1345}
1346
1347type Solved = (Solution, Vec<(u32, usize)>, Vec<usize>);
1350
1351fn solve_all(call: &Call<'_>, conjuncts: &[Expr], seed: &mut u64) -> Result<Solved, Outcome> {
1356 let n_active = call.var_tys.len() as u32;
1357 let mut blocked: Vec<Vec<usize>> = Vec::new();
1358 loop {
1359 let chosen = array::solve_sizes(
1360 conjuncts,
1361 &call.arrays,
1362 &call.var_tys,
1363 &call.states,
1364 &blocked,
1365 seed,
1366 )
1367 .map_err(|e| outcome_of_size(e, &call.null_state))?;
1368 let x = array::expand_with(
1369 conjuncts,
1370 &call.arrays,
1371 &chosen.sizes,
1372 n_active,
1373 &call.states,
1374 &call.program.funcs,
1375 &call.program.dists,
1376 )
1377 .map_err(|e| outcome_of_expand(e, &call.null_state))?;
1378 match solve_expanded(call, &x, seed) {
1379 Ok(s) => return Ok((s, x.elem_vars, chosen.sizes)),
1380 Err(Outcome::Unsatisfiable) if !chosen.sized.is_empty() => {
1383 blocked.push(chosen.tuple);
1384 if blocked.len() >= array::MAX_SIZE_RETRIES {
1385 return Err(Outcome::ArraySizeSearch);
1386 }
1387 }
1388 Err(o) => return Err(o),
1389 }
1390 }
1391}
1392
1393fn run(program: &Program, h: u64, rand_mode: &[bool], statics: &[(u32, u64)]) -> Outcome {
1398 let mut off: Vec<&ProgBlock> = Vec::new();
1403 for b in &program.blocks {
1404 let Ok(o) = owner(b.home, h, statics) else {
1405 continue;
1406 };
1407 match objects::constraint_mode_of(o, b.ordinal) {
1408 Some(true) => {}
1409 Some(false) => off.push(b),
1410 None => return Outcome::DeadHandle,
1411 }
1412 }
1413 if off.is_empty() {
1414 return run_blocks_on(program, h, rand_mode, statics);
1415 }
1416 match reduce::without_blocks(program, &off) {
1417 Ok(p) => run_blocks_on(&p, h, rand_mode, statics),
1418 Err(o) => o,
1419 }
1420}
1421
1422fn run_blocks_on(program: &Program, h: u64, rand_mode: &[bool], statics: &[(u32, u64)]) -> Outcome {
1424 let Some(seed0) = objects::prng_seed(h) else {
1425 return Outcome::DeadHandle;
1426 };
1427 let mut var_owner = Vec::with_capacity(program.vars.len());
1428 for v in &program.vars {
1429 match owner(v.home, h, statics) {
1430 Ok(o) => var_owner.push(o),
1431 Err(o) => return o,
1432 }
1433 }
1434 let mut state_owner = Vec::with_capacity(program.states.len());
1435 for s in &program.states {
1436 match owner(s.home, h, statics) {
1437 Ok(o) => state_owner.push(o),
1438 Err(o) => return o,
1439 }
1440 }
1441 let mut array_owner = Vec::with_capacity(program.arrays.len());
1442 for a in &program.arrays {
1443 match owner(a.home, h, statics) {
1444 Ok(o) => array_owner.push(o),
1445 Err(o) => return o,
1446 }
1447 }
1448 let active = |v: &ProgVar| match v.prop_index {
1449 Some(i) => rand_mode.get(i).copied(),
1450 None => Some(true),
1451 };
1452 let mut var_new: Vec<Option<u32>> = Vec::with_capacity(program.vars.len());
1455 let mut var_tys: Vec<Ty> = Vec::new();
1456 let mut active_ids: Vec<usize> = Vec::new();
1457 let mut inactive_state: Vec<Option<u32>> = Vec::with_capacity(program.vars.len());
1458 let mut inactive_ids: Vec<usize> = Vec::new();
1459 for (i, v) in program.vars.iter().enumerate() {
1460 match active(v) {
1461 None => return Outcome::Failed,
1462 Some(true) => {
1463 var_new.push(Some(var_tys.len() as u32));
1464 inactive_state.push(None);
1465 var_tys.push(v.ty);
1466 active_ids.push(i);
1467 }
1468 Some(false) => {
1469 var_new.push(None);
1470 inactive_state.push(Some((program.states.len() + inactive_ids.len()) as u32));
1471 inactive_ids.push(i);
1472 }
1473 }
1474 }
1475
1476 let mut states: Vec<Option<BigUint>> = Vec::with_capacity(program.states.len());
1477 let mut null_state: Vec<bool> = Vec::with_capacity(program.states.len());
1478 for (s, &o) in program.states.iter().zip(&state_owner) {
1479 match read_state(o, s) {
1480 Ok(Reading::Value(v)) => {
1481 states.push(v);
1482 null_state.push(false);
1483 }
1484 Ok(Reading::NullHandle) => {
1485 states.push(None);
1486 null_state.push(true);
1487 }
1488 Err(o) => return o,
1489 }
1490 }
1491 for &i in &inactive_ids {
1492 let v = &program.vars[i];
1493 match read(var_owner[i], v.slot, v.window, v.ty) {
1494 Ok(x) => states.push(x),
1495 Err(o) => return o,
1496 }
1497 null_state.push(false);
1498 }
1499 let mut arrays = Vec::with_capacity(program.arrays.len());
1500 for (a, &o) in program.arrays.iter().zip(&array_owner) {
1501 let on = a.rand
1502 && match a.prop_index {
1503 Some(i) => match rand_mode.get(i) {
1504 Some(&b) => b,
1505 None => return Outcome::Failed,
1506 },
1507 None => true,
1508 };
1509 match read_array(a, o, on) {
1510 Ok(s) => arrays.push(s),
1511 Err(o) => return o,
1512 }
1513 }
1514
1515 let mut dropped = vec![false; program.conjuncts.len()];
1518 for &i in &inactive_ids {
1519 if let Some(d) = program.vars[i].domain_conjunct {
1520 match dropped.get_mut(d) {
1521 Some(slot) => *slot = true,
1522 None => return Outcome::Failed,
1523 }
1524 }
1525 }
1526 let call = Call {
1527 program,
1528 h,
1529 rand_mode,
1530 statics,
1531 var_owner,
1532 var_new,
1533 var_tys,
1534 active_ids,
1535 arrays,
1536 array_owner,
1537 states,
1538 null_state,
1539 real_unsat: std::cell::Cell::new(false),
1540 };
1541
1542 let has_softs = !program.softs.is_empty();
1550 let prepare = |discard: &[bool]| -> Result<Vec<Expr>, Outcome> {
1551 let mut out = Vec::with_capacity(program.conjuncts.len());
1552 for (i, c) in program.conjuncts.iter().enumerate() {
1553 if dropped[i] {
1554 continue;
1555 }
1556 let resolved;
1557 let c = if has_softs {
1558 resolved = priority::resolve_softs(c, discard).map_err(|_| Outcome::Failed)?;
1559 &resolved
1560 } else {
1561 c
1562 };
1563 let c = c.substitute_vars(&mut |v, t| {
1564 if v >= SELECTOR_BASE {
1565 return Expr::Var(v, t);
1566 }
1567 match (call.var_new[v as usize], inactive_state[v as usize]) {
1568 (Some(n), _) => Expr::Var(n, t),
1569 (None, Some(s)) => Expr::State(s, t),
1570 (None, None) => unreachable!("every variable is active or inactive"),
1571 }
1572 });
1573 out.push(if program.funcs.is_empty() {
1574 c
1575 } else {
1576 func::fold_ground_calls(&c, &program.funcs, &call.states)
1577 .map_err(|e| outcome_of_func(e, &call.null_state))?
1578 });
1579 }
1580 Ok(out)
1581 };
1582
1583 let mut seed = seed0;
1584 let solved = if has_softs {
1585 priority::solve_relaxing(
1588 &program.softs,
1589 &mut seed,
1590 |discard, seed| solve_all(&call, &prepare(discard)?, seed),
1591 |o| matches!(o, Outcome::Unsatisfiable | Outcome::ArraySizeSearch),
1592 )
1593 } else {
1594 prepare(&[]).and_then(|c| solve_all(&call, &c, &mut seed))
1595 };
1596 let (solution, elem_vars, sizes) = match solved {
1597 Ok(s) => s,
1598 Err(o) => return o,
1599 };
1600
1601 let mut writes = solution.real_writes;
1603 writes.reserve(call.active_ids.len());
1604 for (k, &i) in call.active_ids.iter().enumerate() {
1605 let v = &program.vars[i];
1606 match Write::new(call.var_owner[i], v.slot, v.window, &solution.values[k]) {
1607 Some(w) => writes.push(w),
1608 None => return Outcome::Failed,
1609 }
1610 }
1611 let mut new_arrays: Vec<(usize, Vec<u64>)> = Vec::new();
1613 for (ai, a) in call.arrays.iter().enumerate() {
1614 if !a.active {
1615 continue;
1616 }
1617 let ew = program.arrays[ai].elem_words;
1618 let mut flat = Vec::with_capacity(sizes[ai] * ew);
1619 for (e, &(arr, _)) in elem_vars.iter().enumerate() {
1620 if arr as usize != ai {
1621 continue;
1622 }
1623 match pack(&solution.values[call.active_ids.len() + e], ew) {
1624 Some(w) => flat.extend(w),
1625 None => return Outcome::Failed,
1626 }
1627 }
1628 new_arrays.push((ai, flat));
1629 }
1630 for w in writes {
1631 if !w.apply() {
1632 return Outcome::DeadHandle;
1633 }
1634 }
1635 for (ai, flat) in new_arrays {
1636 let pa = &program.arrays[ai];
1637 let o = call.array_owner[ai];
1638 let mut old = vec![0u64; pa.slot.words];
1639 if !objects::field_get(o, pa.slot, &mut old) {
1640 return Outcome::DeadHandle;
1641 }
1642 let new = if pa.assoc {
1643 let (old_h, _) = unpack(&old, 64);
1645 match collections::map_with_values(old_h.to_u64().unwrap_or(0), pa.elem_words, &flat) {
1646 Some(h) => h,
1647 None => return Outcome::Failed,
1648 }
1649 } else {
1650 let shape = collections::Shape {
1651 elem_words: pa.elem_words,
1652 assoc: false,
1653 key: collections::KeyKind::Int {
1654 width: 32,
1655 signed: false,
1656 },
1657 obj: false,
1658 };
1659 collections::from_words(&shape, &flat)
1660 };
1661 let Some(w) = pack(&BigUint::from(new), pa.slot.words) else {
1662 collections::release(new);
1663 return Outcome::Failed;
1664 };
1665 if !objects::field_set(o, pa.slot, &w) {
1666 collections::release(new);
1667 return Outcome::DeadHandle;
1668 }
1669 let (old, _) = unpack(&old, 64);
1670 collections::release(old.to_u64().unwrap_or(0));
1671 }
1672 for ((o, offset), st) in solution.randc_keys.into_iter().zip(solution.randc_work) {
1673 if !objects::set_randc_states(o, vec![(offset, st)]) {
1674 return Outcome::DeadHandle;
1675 }
1676 }
1677 objects::set_prng_seed(h, seed);
1678 Outcome::Solved
1679}
1680
1681#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
1684pub struct Capture {
1685 pub state: u32,
1687 pub width: u32,
1689}
1690
1691impl Capture {
1692 pub fn words(&self) -> usize {
1695 (2 * self.width as usize).div_ceil(64).max(1)
1696 }
1697}
1698
1699#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
1702pub struct Target {
1703 pub home: Home,
1705 pub slot: FieldSlot,
1707 pub window: Option<Window>,
1710}
1711
1712#[derive(Clone, Debug, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
1718pub struct Spec {
1719 pub frag: Program,
1725 pub captures: Vec<Capture>,
1728 pub only: Option<Vec<Target>>,
1733}
1734
1735impl Spec {
1736 pub fn capture_words(&self) -> usize {
1738 self.captures.iter().map(Capture::words).sum()
1739 }
1740
1741 pub fn scope_words(&self) -> usize {
1745 let ints = self.frag.vars.iter().map(|v| v.slot.offset + v.slot.words);
1746 let reals = self
1747 .frag
1748 .real_vars
1749 .iter()
1750 .map(|v| v.slot.offset + v.slot.words);
1751 let arrays = self
1753 .frag
1754 .arrays
1755 .iter()
1756 .map(|v| v.slot.offset + v.slot.words);
1757 ints.chain(reals).chain(arrays).max().unwrap_or(0)
1758 }
1759}
1760
1761type SpecTable = HashMap<u32, Arc<Spec>>;
1762
1763fn specs() -> &'static Mutex<SpecTable> {
1764 static T: OnceLock<Mutex<SpecTable>> = OnceLock::new();
1765 T.get_or_init(|| Mutex::new(HashMap::new()))
1766}
1767
1768static NEXT_SPEC: AtomicU32 = AtomicU32::new(1);
1770
1771pub fn register_spec(spec: Spec) -> u32 {
1774 let id = NEXT_SPEC.fetch_add(1, Ordering::Relaxed);
1775 specs()
1776 .lock()
1777 .unwrap_or_else(|e| e.into_inner())
1778 .insert(id, Arc::new(spec));
1779 id
1780}
1781
1782pub(super) fn register_spec_at(id: u32, spec: Spec) {
1787 NEXT_SPEC.fetch_max(id + 1, Ordering::Relaxed);
1788 specs()
1789 .lock()
1790 .unwrap_or_else(|e| e.into_inner())
1791 .insert(id, Arc::new(spec));
1792}
1793
1794pub(super) fn all_specs() -> Vec<(u32, Arc<Spec>)> {
1797 let mut v: Vec<_> = specs()
1798 .lock()
1799 .unwrap_or_else(|e| e.into_inner())
1800 .iter()
1801 .map(|(&id, s)| (id, Arc::clone(s)))
1802 .collect();
1803 v.sort_by_key(|(id, _)| *id);
1804 v
1805}
1806
1807pub fn spec(id: u32) -> Option<Arc<Spec>> {
1809 specs()
1810 .lock()
1811 .unwrap_or_else(|e| e.into_inner())
1812 .get(&id)
1813 .cloned()
1814}
1815
1816pub fn spec_count() -> usize {
1819 specs().lock().unwrap_or_else(|e| e.into_inner()).len()
1820}
1821
1822static OBJECT_SITES: AtomicU32 = AtomicU32::new(0);
1823
1824pub fn note_object_site() {
1828 OBJECT_SITES.fetch_add(1, Ordering::Relaxed);
1829}
1830
1831pub fn object_site_count() -> u32 {
1834 OBJECT_SITES.load(Ordering::Relaxed)
1835}
1836
1837pub fn spec_capture_words(id: u32) -> usize {
1840 spec(id).map_or(0, |s| s.capture_words())
1841}
1842
1843fn capture_consts(spec: &Spec, words: &[u64]) -> Result<Vec<Option<BigUint>>, Outcome> {
1848 let mut consts: Vec<Option<BigUint>> = vec![None; spec.frag.states.len()];
1849 let mut at = 0;
1850 for c in &spec.captures {
1851 let n = c.words();
1852 let chunk = words.get(at..at + n).ok_or(Outcome::Failed)?;
1853 at += n;
1854 let (val, unk) = unpack(chunk, c.width);
1855 if !unk.is_zero() {
1856 return Err(Outcome::StateUnknown);
1857 }
1858 *consts.get_mut(c.state as usize).ok_or(Outcome::Failed)? = Some(val);
1859 }
1860 Ok(consts)
1861}
1862
1863pub fn set_all_constraint_modes(h: u64, on: bool) -> Outcome {
1869 let Some(template) = objects::dynamic_template(h) else {
1870 return Outcome::DeadHandle;
1871 };
1872 let Some(entry) = program(template) else {
1873 return Outcome::NoProgram;
1874 };
1875 let Ok(prog) = entry.as_ref() else {
1876 return Outcome::NoProgram;
1877 };
1878 for b in prog.blocks.iter().filter(|b| b.home == Home::Instance) {
1879 if !objects::set_constraint_mode_of(h, b.ordinal, on) {
1880 return Outcome::DeadHandle;
1881 }
1882 }
1883 Outcome::Solved
1884}
1885
1886pub fn randomize(h: u64, statics: &[(u32, u64)]) -> Outcome {
1890 randomize_spec(h, statics, 0, &[])
1891}
1892
1893pub fn randomize_spec(h: u64, statics: &[(u32, u64)], spec_id: u32, captures: &[u64]) -> Outcome {
1897 let Some(template) = objects::dynamic_template(h) else {
1898 return Outcome::DeadHandle;
1899 };
1900 let Some(base) = program(template) else {
1901 return Outcome::NoProgram;
1902 };
1903 let Ok(base_prog) = base.as_ref() else {
1904 return Outcome::NoProgram;
1905 };
1906 let Some(meta) = objects::rand_meta(h) else {
1907 return Outcome::DeadHandle;
1908 };
1909 let mut rand_mode = meta.rand_mode;
1910 let entry = if spec_id == 0 {
1911 base.clone()
1912 } else {
1913 let Some(spec) = spec(spec_id) else {
1914 return Outcome::Failed;
1915 };
1916 let consts = match capture_consts(&spec, captures) {
1917 Ok(c) => c,
1918 Err(o) => return o,
1919 };
1920 let mut p = match joint::overlay(base_prog, &spec.frag, &consts) {
1921 Ok(p) => p,
1922 Err(o) => return o,
1923 };
1924 if let Some(only) = &spec.only
1925 && let Err(o) = joint::restrict(&mut p, &mut rand_mode, only)
1926 {
1927 return o;
1928 }
1929 Arc::new(Ok(p))
1930 };
1931 let Ok(prog) = entry.as_ref() else {
1932 return Outcome::NoProgram;
1933 };
1934 let entry = match joint::apply_leaf_modes(prog, &mut rand_mode, &meta.leaf_off, &meta.leaf_on) {
1936 Some(p) => Arc::new(Ok(p)),
1937 None => entry,
1938 };
1939 let Ok(prog) = entry.as_ref() else {
1940 return Outcome::NoProgram;
1941 };
1942 let entry = match joint::apply_static_modes(prog, &mut rand_mode, statics) {
1945 Some(p) => Arc::new(Ok(p)),
1946 None => entry,
1947 };
1948 let Ok(prog) = entry.as_ref() else {
1949 return Outcome::NoProgram;
1950 };
1951 let closure = match joint::closure(h, &entry, &rand_mode, statics) {
1954 Ok(c) => c,
1955 Err(o) => return o,
1956 };
1957 if closure.members.len() == 1 {
1958 match joint::self_alias(&closure, prog, statics) {
1961 Ok(joint::SelfAlias::None) => return run(prog, h, &rand_mode, statics),
1962 Ok(joint::SelfAlias::Integral) => {}
1963 Err(o) => return o,
1964 }
1965 }
1966 match joint::merge(&closure, statics) {
1967 Ok(m) => run(&m.program, h, &m.rand_mode, statics),
1968 Err(o) => o,
1969 }
1970}
1971
1972const SCOPE_TEMPLATE: ClassTemplateId = ClassTemplateId(u32::MAX - 1);
1976
1977pub fn randomize_scope(spec_id: u32, captures: &[u64], out: &mut [u64], seed: u64) -> Outcome {
1987 let Some(spec) = spec(spec_id) else {
1988 return Outcome::Failed;
1989 };
1990 if out.len() != spec.scope_words() || captures.len() != spec.capture_words() {
1992 return Outcome::Failed;
1993 }
1994 let consts = match capture_consts(&spec, captures) {
1995 Ok(c) => c,
1996 Err(o) => return o,
1997 };
1998 let prog = match joint::overlay(&Program::default(), &spec.frag, &consts) {
1999 Ok(p) => p,
2000 Err(o) => return o,
2001 };
2002 let h = objects::alloc(SCOPE_TEMPLATE, out.len());
2003 objects::set_prng_seed(h, seed);
2004 let slot = FieldSlot {
2005 offset: 0,
2006 words: out.len(),
2007 };
2008 if !out.is_empty() && !objects::field_set(h, slot, out) {
2011 objects::release(h);
2012 return Outcome::Failed;
2013 }
2014 let outcome = run(&prog, h, &[], &[]);
2015 if outcome == Outcome::Solved && !out.is_empty() && !objects::field_get(h, slot, out) {
2016 objects::release(h);
2017 return Outcome::Failed;
2018 }
2019 objects::release(h);
2020 outcome
2021}
2022
2023#[cfg(test)]
2024mod tests {
2025 use super::*;
2026 use crate::constraints::ir::BinOp;
2027 use crate::objects::{RandKind, RandProp, alloc, register_rand_props, release};
2028
2029 pub(super) fn u(w: u32) -> Ty {
2030 Ty::unsigned(w)
2031 }
2032 pub(super) fn var(i: u32, t: Ty) -> Expr {
2033 Expr::Var(i, t)
2034 }
2035 pub(super) fn k(t: Ty, v: u64) -> Expr {
2036 Expr::constant(t, v)
2037 }
2038 pub(super) fn bin(op: BinOp, a: Expr, b: Expr, t: Ty) -> Expr {
2039 Expr::Binary(op, Box::new(a), Box::new(b), t)
2040 }
2041
2042 fn randomize(h: u64) -> Outcome {
2044 super::randomize(h, &[])
2045 }
2046
2047 pub(super) fn setup(id: u32, n: usize, conjuncts: Vec<Expr>) -> (ClassTemplateId, u64) {
2052 setup_with_softs(id, n, conjuncts, Vec::new())
2053 }
2054
2055 fn setup_with_softs(
2056 id: u32,
2057 n: usize,
2058 conjuncts: Vec<Expr>,
2059 softs: Vec<Soft>,
2060 ) -> (ClassTemplateId, u64) {
2061 let t = ClassTemplateId(id);
2062 let vars = (0..n)
2063 .map(|i| ProgVar {
2064 ty: u(8),
2065 slot: FieldSlot {
2066 offset: i,
2067 words: 1,
2068 },
2069 home: Home::Instance,
2070 prop_index: Some(i),
2071 domain_conjunct: None,
2072 randc: false,
2073 window: None,
2074 })
2075 .collect();
2076 register_rand_props(
2077 t,
2078 (0..n)
2079 .map(|i| RandProp {
2080 slot: FieldSlot {
2081 offset: i,
2082 words: 1,
2083 },
2084 kind: RandKind::Rand,
2085 })
2086 .collect(),
2087 );
2088 register_program(
2089 t,
2090 Ok(Program {
2091 vars,
2092 states: vec![ProgState {
2093 class_of: false,
2094 root_bit: 0,
2095 ty: u(8),
2096 slot: FieldSlot {
2097 offset: n,
2098 words: 1,
2099 },
2100 window: None,
2101 home: Home::Instance,
2102 via: Vec::new(),
2103 }],
2104 conjuncts,
2105 before: Vec::new(),
2106 dists: Vec::new(),
2107 softs,
2108 ..Program::default()
2109 }),
2110 );
2111 let h = alloc(t, n + 1);
2112 objects::set_prng_seed(h, 0x1234_5678);
2113 (t, h)
2114 }
2115
2116 fn soft_on_x0(seq: u32) -> Soft {
2118 Soft {
2119 priority: priority::Priority {
2120 object: 0,
2121 level: 0,
2122 seq,
2123 },
2124 dists: Vec::new(),
2125 keys: vec![priority::SoftKey::Var(0)],
2126 disabled: false,
2127 disabled_by: Vec::new(),
2128 }
2129 }
2130
2131 #[test]
2132 fn soft_constraints_hold_unless_a_hard_or_higher_soft_conflicts() {
2133 let eq = |v: u64| bin(BinOp::Eq, var(0, u(8)), k(u(8), v), Ty::BIT);
2135 let c = vec![priority::wrap_soft(0, eq(5)), priority::wrap_soft(1, eq(6))];
2136 let (_, h) = setup_with_softs(0x7000_0f01, 1, c, vec![soft_on_x0(0), soft_on_x0(1)]);
2137 for _ in 0..20 {
2138 assert_eq!(randomize(h), Outcome::Solved);
2139 assert_eq!(get(h, 0), 6);
2140 }
2141 release(h);
2142
2143 let c = vec![
2145 bin(BinOp::Ge, var(0, u(8)), k(u(8), 1), Ty::BIT),
2146 bin(BinOp::Le, var(0, u(8)), k(u(8), 3), Ty::BIT),
2147 priority::wrap_soft(0, eq(5)),
2148 priority::wrap_soft(1, eq(6)),
2149 ];
2150 let (_, h) = setup_with_softs(0x7000_0f02, 1, c, vec![soft_on_x0(0), soft_on_x0(1)]);
2151 let mut seen = std::collections::BTreeSet::new();
2152 for _ in 0..100 {
2153 assert_eq!(randomize(h), Outcome::Solved);
2154 seen.insert(get(h, 0));
2155 }
2156 assert_eq!(seen.len(), 3, "{seen:?}");
2157 release(h);
2158
2159 let c = vec![
2161 bin(BinOp::Gt, var(0, u(8)), k(u(8), 5), Ty::BIT),
2162 bin(BinOp::Lt, var(0, u(8)), k(u(8), 3), Ty::BIT),
2163 priority::wrap_soft(0, eq(4)),
2164 ];
2165 let (_, h) = setup_with_softs(0x7000_0f03, 1, c, vec![soft_on_x0(0)]);
2166 set(h, 0, 0x42);
2167 assert_eq!(randomize(h), Outcome::Unsatisfiable);
2168 assert_eq!(get(h, 0), 0x42, "a failed randomize writes nothing");
2169 release(h);
2170 }
2171
2172 pub(super) fn get(h: u64, off: usize) -> u64 {
2173 let mut b = [0u64];
2174 assert!(objects::field_get(
2175 h,
2176 FieldSlot {
2177 offset: off,
2178 words: 1
2179 },
2180 &mut b
2181 ));
2182 b[0]
2183 }
2184 fn set(h: u64, off: usize, v: u64) {
2185 assert!(objects::field_set(
2186 h,
2187 FieldSlot {
2188 offset: off,
2189 words: 1
2190 },
2191 &[v]
2192 ));
2193 }
2194
2195 #[test]
2196 fn solves_writes_two_state_values_and_advances_the_seed() {
2197 let c = vec![
2199 bin(BinOp::Ge, var(0, u(8)), k(u(8), 10), Ty::BIT),
2200 bin(BinOp::Le, var(0, u(8)), k(u(8), 12), Ty::BIT),
2201 ];
2202 let (_, h) = setup(0x7000_0001, 1, c);
2203 let before = objects::prng_seed(h);
2204 let mut seen = std::collections::BTreeSet::new();
2205 for _ in 0..200 {
2206 assert_eq!(randomize(h), Outcome::Solved);
2207 let v = get(h, 0);
2208 assert!((10..=12).contains(&v), "{v}");
2209 seen.insert(v);
2210 }
2211 assert_eq!(seen.len(), 3, "all three legal values are reachable");
2212 assert_ne!(objects::prng_seed(h), before);
2213 release(h);
2214 }
2215
2216 #[test]
2217 fn failure_writes_nothing_and_consumes_no_draws() {
2218 let c = vec![
2220 bin(BinOp::Gt, var(0, u(8)), k(u(8), 5), Ty::BIT),
2221 bin(BinOp::Lt, var(0, u(8)), k(u(8), 3), Ty::BIT),
2222 ];
2223 let (_, h) = setup(0x7000_0002, 2, c);
2224 set(h, 0, 0x77);
2225 set(h, 1, 0x88);
2226 let seed = objects::prng_seed(h);
2227 assert_eq!(randomize(h), Outcome::Unsatisfiable);
2228 assert_eq!((get(h, 0), get(h, 1)), (0x77, 0x88), "values unchanged");
2229 assert_eq!(objects::prng_seed(h), seed, "stream not perturbed");
2230 release(h);
2231 }
2232
2233 #[test]
2234 fn determinism_by_seed() {
2235 let (_, a) = setup(0x7000_0003, 2, vec![]);
2236 let b = alloc(ClassTemplateId(0x7000_0003), 3);
2237 objects::set_prng_seed(b, 0x1234_5678);
2238 for _ in 0..5 {
2239 assert_eq!(randomize(a), Outcome::Solved);
2240 assert_eq!(randomize(b), Outcome::Solved);
2241 assert_eq!((get(a, 0), get(a, 1)), (get(b, 0), get(b, 1)));
2242 }
2243 objects::set_prng_seed(b, 99);
2244 let mut diff = false;
2245 for _ in 0..8 {
2246 randomize(a);
2247 randomize(b);
2248 diff |= get(a, 0) != get(b, 0);
2249 }
2250 assert!(diff, "different seeds give different streams");
2251 release(a);
2252 release(b);
2253 }
2254
2255 #[test]
2256 fn rand_mode_off_variable_is_state_not_solved() {
2257 let c = vec![bin(
2259 BinOp::Eq,
2260 bin(BinOp::Add, var(0, u(8)), var(1, u(8)), u(8)),
2261 k(u(8), 100),
2262 Ty::BIT,
2263 )];
2264 let (_, h) = setup(0x7000_0004, 2, c);
2265 set(h, 0, 30);
2266 let mut m = objects::rand_meta(h).unwrap();
2267 m.rand_mode[0] = false;
2268 objects::set_rand_meta(h, m);
2269 for _ in 0..20 {
2270 assert_eq!(randomize(h), Outcome::Solved);
2271 assert_eq!((get(h, 0), get(h, 1)), (30, 70));
2272 }
2273 let c2 = vec![bin(BinOp::Lt, var(0, u(8)), k(u(8), 10), Ty::BIT)];
2276 let (_, h2) = setup(0x7000_0005, 1, c2);
2277 set(h2, 0, 200);
2278 let mut m = objects::rand_meta(h2).unwrap();
2279 m.rand_mode[0] = false;
2280 objects::set_rand_meta(h2, m);
2281 assert_eq!(randomize(h2), Outcome::Unsatisfiable, "acts as a checker");
2282 assert_eq!(get(h2, 0), 200);
2283 release(h);
2284 release(h2);
2285 }
2286
2287 #[test]
2288 fn all_variables_inactive_checks_the_constraints() {
2289 let c = vec![bin(BinOp::Eq, var(0, u(8)), k(u(8), 5), Ty::BIT)];
2290 let (_, h) = setup(0x7000_0006, 1, c);
2291 let mut m = objects::rand_meta(h).unwrap();
2292 m.rand_mode[0] = false;
2293 objects::set_rand_meta(h, m);
2294 set(h, 0, 5);
2295 assert_eq!(randomize(h), Outcome::Solved);
2296 set(h, 0, 6);
2297 assert_eq!(randomize(h), Outcome::Unsatisfiable);
2298 release(h);
2299 }
2300
2301 fn windowed(id: u32, randc_hi: bool) -> (ClassTemplateId, u64) {
2306 let t = ClassTemplateId(id);
2307 let slot = FieldSlot {
2308 offset: 0,
2309 words: 1,
2310 };
2311 register_rand_props(
2312 t,
2313 vec![RandProp {
2314 slot,
2315 kind: RandKind::Rand,
2316 }],
2317 );
2318 let leaf = |bit| {
2319 Some(Window {
2320 bit,
2321 width: 8,
2322 field_width: 24,
2323 })
2324 };
2325 let c = vec![
2326 bin(BinOp::Ge, var(0, u(8)), k(u(8), 10), Ty::BIT),
2327 bin(BinOp::Le, var(0, u(8)), k(u(8), 12), Ty::BIT),
2328 bin(
2329 BinOp::Eq,
2330 var(1, u(8)),
2331 bin(BinOp::Add, var(0, u(8)), k(u(8), 1), u(8)),
2332 Ty::BIT,
2333 ),
2334 ];
2335 let mk = |bit, randc| ProgVar {
2336 ty: u(8),
2337 slot,
2338 home: Home::Instance,
2339 prop_index: Some(0),
2340 domain_conjunct: None,
2341 randc,
2342 window: leaf(bit),
2343 };
2344 register_program(
2345 t,
2346 Ok(Program {
2347 vars: vec![mk(0, false), mk(16, randc_hi)],
2348 conjuncts: c,
2349 ..Program::default()
2350 }),
2351 );
2352 let h = alloc(t, 2);
2353 set(h, 0, (0x5au64 << 8) | (0xffu64 << (24 + 8)));
2355 set(h, 1, 0xdead);
2356 (t, h)
2357 }
2358
2359 #[test]
2360 fn leaf_windows_of_one_property_are_solved_and_written_in_place() {
2361 let (_, h) = windowed(0x7000_0f10, false);
2362 let mut seen = std::collections::BTreeSet::new();
2363 for _ in 0..60 {
2364 assert_eq!(randomize(h), Outcome::Solved);
2365 let w = get(h, 0);
2366 let (lo, mid, hi) = (w & 0xff, (w >> 8) & 0xff, (w >> 16) & 0xff);
2367 assert!((10..=12).contains(&lo), "{lo}");
2368 assert_eq!(hi, lo + 1);
2369 assert_eq!(mid, 0x5a, "the neighbouring leaf keeps its value");
2370 assert_eq!(w >> 24, 0xff << 8, "the neighbour's unknown bits survive");
2371 assert_eq!(get(h, 1), 0xdead, "the next property is untouched");
2372 seen.insert(lo);
2373 }
2374 assert_eq!(seen.len(), 3, "all three legal values are reachable");
2375 release(h);
2376 }
2377
2378 #[test]
2381 fn an_inactive_leaf_is_read_through_its_window() {
2382 let (_, h) = windowed(0x7000_0f11, false);
2383 let mut m = objects::rand_meta(h).unwrap();
2384 m.rand_mode[0] = false;
2385 objects::set_rand_meta(h, m);
2386 set(h, 0, 11 | (0x5 << 8) | (12 << 16));
2388 assert_eq!(randomize(h), Outcome::Solved);
2389 assert_eq!(get(h, 0), 11 | (0x5 << 8) | (12 << 16));
2390 set(h, 0, 11 | (13 << 16));
2392 assert_eq!(randomize(h), Outcome::Unsatisfiable);
2393 set(h, 0, 11 | (12 << 16) | (1u64 << (24 + 16)));
2396 assert_eq!(randomize(h), Outcome::StateUnknown);
2397 set(h, 0, 11 | (12 << 16) | (1u64 << (24 + 8)));
2398 assert_eq!(randomize(h), Outcome::Solved);
2399 release(h);
2400 }
2401
2402 #[test]
2404 fn randc_cycles_of_two_leaves_of_one_property_are_independent() {
2405 let (_, h) = windowed(0x7000_0f12, true);
2406 assert_eq!(randomize(h), Outcome::Solved);
2407 let hi_key = randc_key(
2408 FieldSlot {
2409 offset: 0,
2410 words: 1,
2411 },
2412 Some(Window {
2413 bit: 16,
2414 width: 8,
2415 field_width: 24,
2416 }),
2417 );
2418 assert_ne!(hi_key, 0, "a leaf's key differs from its property's");
2419 assert!(objects::randc_state(h, hi_key).unwrap().live);
2420 assert!(!objects::randc_state(h, 0).unwrap().live);
2421 let mut cycle = vec![get(h, 0) >> 16 & 0xff];
2423 for _ in 0..2 {
2424 assert_eq!(randomize(h), Outcome::Solved);
2425 cycle.push(get(h, 0) >> 16 & 0xff);
2426 }
2427 cycle.sort_unstable();
2428 assert_eq!(cycle, vec![11, 12, 13]);
2429 release(h);
2430 }
2431
2432 #[test]
2433 fn enum_domain_dropped_only_while_inactive() {
2434 let dom = bin(
2436 BinOp::LogOr,
2437 bin(BinOp::Eq, var(0, u(8)), k(u(8), 1), Ty::BIT),
2438 bin(BinOp::Eq, var(0, u(8)), k(u(8), 3), Ty::BIT),
2439 Ty::BIT,
2440 );
2441 let (t, h) = setup(0x7000_0007, 1, vec![dom]);
2442 let mut p = program(t).unwrap().as_ref().clone().unwrap();
2443 p.vars[0].domain_conjunct = Some(0);
2444 register_program(t, Ok(p));
2445 for _ in 0..20 {
2446 assert_eq!(randomize(h), Outcome::Solved);
2447 assert!(matches!(get(h, 0), 1 | 3));
2448 }
2449 set(h, 0, 2);
2451 let mut m = objects::rand_meta(h).unwrap();
2452 m.rand_mode[0] = false;
2453 objects::set_rand_meta(h, m);
2454 assert_eq!(randomize(h), Outcome::Solved);
2455 assert_eq!(get(h, 0), 2);
2456 release(h);
2457 }
2458
2459 #[test]
2460 fn x_state_is_a_loud_error_and_changes_nothing() {
2461 let c = vec![bin(BinOp::Eq, var(0, u(8)), Expr::State(0, u(8)), Ty::BIT)];
2463 let (_, h) = setup(0x7000_0008, 1, c);
2464 set(h, 0, 9);
2465 set(h, 1, 1 << 8);
2466 let seed = objects::prng_seed(h);
2467 assert_eq!(randomize(h), Outcome::StateUnknown);
2468 assert_eq!(get(h, 0), 9);
2469 assert_eq!(objects::prng_seed(h), seed);
2470 set(h, 1, 0x42);
2472 assert_eq!(randomize(h), Outcome::Solved);
2473 assert_eq!(get(h, 0), 0x42);
2474 release(h);
2475 }
2476
2477 #[test]
2478 fn unreferenced_x_state_is_not_an_error() {
2479 let (_, h) = setup(0x7000_0009, 1, vec![]);
2480 set(h, 1, 1 << 8);
2481 assert_eq!(randomize(h), Outcome::Solved);
2482 release(h);
2483 }
2484
2485 #[test]
2486 fn dead_handle_missing_program_and_failed_program() {
2487 assert_eq!(randomize(0), Outcome::DeadHandle);
2488 let t = ClassTemplateId(0x7000_000a);
2489 let h = alloc(t, 1);
2490 assert_eq!(randomize(h), Outcome::NoProgram, "nothing registered");
2491 register_program(t, Err("unsupported thing".into()));
2492 assert_eq!(randomize(h), Outcome::NoProgram, "build failure");
2493 assert_eq!(
2494 program(t).unwrap().as_ref().clone(),
2495 Err("unsupported thing".to_string())
2496 );
2497 release(h);
2498 assert_eq!(randomize(h), Outcome::DeadHandle, "stale handle");
2499 }
2500
2501 #[test]
2502 fn four_state_field_round_trip_packs_value_low_unknown_high() {
2503 let (v, x) = unpack(&[0xde_adbe_ef12, 0], 40);
2505 assert_eq!(v, BigUint::from(0xde_adbe_ef12u64));
2506 assert!(x.is_zero());
2507 let (_, x) = unpack(&[0, 1 << 8], 40);
2508 assert!(!x.is_zero(), "bit 72 of the packed value is unknown bit 32");
2509 assert_eq!(pack(&BigUint::from(7u8), 2), Some(vec![7, 0]));
2510 assert_eq!(pack(&(BigUint::from(1u8) << 70), 1), None);
2511 }
2512
2513 fn skewed(id: u32, before: Vec<(u32, u32)>) -> (ClassTemplateId, u64) {
2516 let c = vec![
2517 bin(BinOp::Lt, var(0, u(8)), k(u(8), 2), Ty::BIT),
2518 bin(
2519 BinOp::LogImpl,
2520 bin(BinOp::Eq, var(0, u(8)), k(u(8), 1), Ty::BIT),
2521 bin(BinOp::Eq, var(1, u(8)), k(u(8), 0), Ty::BIT),
2522 Ty::BIT,
2523 ),
2524 ];
2525 let (t, h) = setup(id, 2, c);
2526 let mut p = program(t).unwrap().as_ref().clone().unwrap();
2527 p.before = before;
2528 register_program(t, Ok(p));
2529 (t, h)
2530 }
2531
2532 fn count_x0_is_one(h: u64, n: u32) -> u32 {
2533 let mut ones = 0;
2534 for _ in 0..n {
2535 assert_eq!(randomize(h), Outcome::Solved);
2536 ones += u32::from(get(h, 0) == 1);
2537 }
2538 ones
2539 }
2540
2541 #[test]
2542 fn solve_before_orders_the_solve_but_not_the_solution_space() {
2543 let (_, h) = skewed(0x7000_0020, vec![]);
2548 let ones = count_x0_is_one(h, 3000);
2549 assert!(ones < 40, "unordered: {ones}/3000");
2550 let (_, h) = skewed(0x7000_0021, vec![(0, 1)]);
2551 let ones = count_x0_is_one(h, 3000);
2552 assert!((1350..1650).contains(&ones), "x0 before x1: {ones}/3000");
2553 let (_, h) = skewed(0x7000_0022, vec![(1, 0)]);
2554 let ones = count_x0_is_one(h, 3000);
2555 assert!(ones < 40, "x1 before x0: {ones}/3000");
2556 }
2557
2558 #[test]
2559 fn an_edge_through_an_inactive_variable_is_ignored() {
2560 let (_, h) = skewed(0x7000_0023, vec![(0, 1)]);
2561 set(h, 0, 1);
2562 let mut m = objects::rand_meta(h).unwrap();
2563 m.rand_mode[0] = false;
2564 objects::set_rand_meta(h, m);
2565 for _ in 0..20 {
2566 assert_eq!(randomize(h), Outcome::Solved);
2567 assert_eq!(
2568 (get(h, 0), get(h, 1)),
2569 (1, 0),
2570 "x0 is state, x1 forced to 0"
2571 );
2572 }
2573 }
2574
2575 #[test]
2576 fn a_circular_or_out_of_range_order_is_a_failed_program_not_a_guess() {
2577 let (_, h) = skewed(0x7000_0024, vec![(0, 1), (1, 0)]);
2578 let seed = objects::prng_seed(h);
2579 assert_eq!(randomize(h), Outcome::Failed);
2580 assert_eq!(objects::prng_seed(h), seed);
2581 let (_, h) = skewed(0x7000_0025, vec![(0, 9)]);
2582 assert_eq!(randomize(h), Outcome::Failed);
2583 }
2584
2585 #[test]
2586 fn outcome_codes_round_trip_and_every_error_has_a_message() {
2587 for c in 0..=20u64 {
2588 let o = Outcome::from_code(c).unwrap();
2589 assert_eq!(o.code(), c);
2590 assert_eq!(o.error_message().is_some(), c >= 2, "{o:?}");
2591 }
2592 assert_eq!(Outcome::from_code(21), None);
2593 }
2594
2595 #[test]
2596 fn every_sat_fallback_failure_has_its_own_outcome() {
2597 use crate::constraints::bdd::BddBuildError;
2598 let none: &[bool] = &[];
2599 for (e, want) in [
2600 (
2601 SolveError::Build(BddBuildError::TooLarge {
2602 budget: 1,
2603 during: "x",
2604 }),
2605 Outcome::TooLarge,
2606 ),
2607 (
2608 SolveError::Build(BddBuildError::CnfTooLarge {
2609 limit: 1,
2610 during: "x",
2611 }),
2612 Outcome::TooLarge,
2613 ),
2614 (SolveError::SatTimeout, Outcome::SatTimeout),
2615 (
2616 SolveError::SatUnsupported { what: "x" },
2617 Outcome::SatUnsupported,
2618 ),
2619 (SolveError::SatModelRejected, Outcome::Failed),
2620 (SolveError::SatSolverFailure { what: "x" }, Outcome::Failed),
2621 ] {
2622 assert_eq!(outcome_of(e.clone(), none), want, "{e}");
2623 }
2624 assert!(
2626 Outcome::TooLarge
2627 .error_message()
2628 .unwrap()
2629 .contains("SAT fallback")
2630 );
2631 assert!(
2632 Outcome::SatTimeout
2633 .error_message()
2634 .unwrap()
2635 .contains("time limit")
2636 );
2637 assert!(
2638 Outcome::SatUnsupported
2639 .error_message()
2640 .unwrap()
2641 .contains("does not support")
2642 );
2643 }
2644
2645 fn dist_program(id: u32) -> (ClassTemplateId, u64) {
2648 use crate::constraints::dist::{Dist, Item, SELECTOR_BASE, Span};
2649 let x = var(0, u(8));
2650 let item = |v: u64, w: Expr| Item {
2651 member: bin(BinOp::Eq, x.clone(), k(u(8), v), Ty::BIT),
2652 ty: u(8),
2653 span: Span::Point(k(u(8), v)),
2654 weight: w,
2655 per_range: false,
2656 };
2657 let d = Dist {
2658 subject_ty: u(8),
2659 plain_var: Some(0),
2660 vars: vec![0],
2661 items: vec![
2662 item(
2663 0,
2664 Expr::Resize(Box::new(Expr::State(0, u(8))), Ty::signed(8)),
2665 ),
2666 item(1, k(Ty::signed(8), 1)),
2667 ],
2668 default: None,
2669 subject: None,
2670 };
2671 let (t, h) = setup(id, 1, vec![d.conjunct(SELECTOR_BASE)]);
2672 let mut p = program(t).unwrap().as_ref().clone().unwrap();
2673 p.dists = vec![d];
2674 register_program(t, Ok(p));
2675 (t, h)
2676 }
2677
2678 #[test]
2679 fn a_dist_weight_is_read_from_the_state_at_each_call() {
2680 let (_, h) = dist_program(0x7000_0040);
2681 set(h, 1, 3);
2683 let mut zeros = 0;
2684 for _ in 0..2000 {
2685 assert_eq!(randomize(h), Outcome::Solved);
2686 let x = get(h, 0);
2687 assert!(x <= 1, "x = {x}");
2688 zeros += u32::from(x == 0);
2689 }
2690 assert!(
2691 (1400..1600).contains(&zeros),
2692 "x == 0: {zeros}/2000 (want 1500)"
2693 );
2694 set(h, 1, 0);
2696 for _ in 0..50 {
2697 assert_eq!(randomize(h), Outcome::Solved);
2698 assert_eq!(get(h, 0), 1);
2699 }
2700 }
2701
2702 #[test]
2703 fn a_negative_dist_weight_fails_the_call_and_changes_nothing() {
2704 let (_, h) = dist_program(0x7000_0041);
2705 set(h, 1, 3);
2706 assert_eq!(randomize(h), Outcome::Solved);
2707 let (before, seed) = (get(h, 0), objects::prng_seed(h));
2708 set(h, 1, 200); assert_eq!(randomize(h), Outcome::BadDistWeight);
2710 assert_eq!((get(h, 0), objects::prng_seed(h)), (before, seed));
2711 set(h, 1, 1 << 8);
2713 assert_eq!(randomize(h), Outcome::StateUnknown);
2714 }
2715
2716 #[test]
2717 fn a_dist_on_an_inactive_variable_is_a_membership_test_on_its_value() {
2718 let (_, h) = dist_program(0x7000_0042);
2719 set(h, 1, 3);
2720 let mut m = objects::rand_meta(h).unwrap();
2721 m.rand_mode[0] = false;
2722 objects::set_rand_meta(h, m);
2723 set(h, 0, 1);
2724 assert_eq!(randomize(h), Outcome::Solved);
2725 assert_eq!(get(h, 0), 1);
2726 set(h, 0, 7);
2728 assert_eq!(randomize(h), Outcome::Unsatisfiable);
2729 set(h, 0, 0);
2730 set(h, 1, 0);
2731 assert_eq!(randomize(h), Outcome::Unsatisfiable);
2732 }
2733
2734 #[test]
2738 fn a_dist_default_spreads_its_weight_over_the_values_no_item_names() {
2739 use crate::constraints::dist::{DefaultWeight, Dist, Item, SELECTOR_BASE, Span};
2740 let x = var(0, u(8));
2741 let d = Dist {
2742 subject_ty: u(8),
2743 plain_var: Some(0),
2744 vars: vec![0],
2745 items: vec![Item {
2746 member: bin(BinOp::Le, x.clone(), k(u(8), 3), Ty::BIT),
2747 ty: u(8),
2748 span: Span::Range {
2749 lo: Some(k(u(8), 0)),
2750 hi: Some(k(u(8), 3)),
2751 },
2752 weight: k(u(32), 8),
2753 per_range: true,
2754 }],
2755 default: Some(DefaultWeight {
2756 weight: k(u(32), 8),
2757 per_range: true,
2758 }),
2759 subject: None,
2760 };
2761 let (t, h) = setup(0x7000_0043, 1, vec![d.conjunct(SELECTOR_BASE)]);
2762 let mut p = program(t).unwrap().as_ref().clone().unwrap();
2763 p.dists = vec![d];
2764 register_program(t, Ok(p));
2765 let (mut low, mut distinct) = (0u32, std::collections::BTreeSet::new());
2766 for _ in 0..4000 {
2767 assert_eq!(randomize(h), Outcome::Solved);
2768 let v = get(h, 0);
2769 if v <= 3 {
2770 low += 1;
2771 } else {
2772 distinct.insert(v);
2773 }
2774 }
2775 assert!(
2776 (1850..2150).contains(&low),
2777 "x <= 3: {low}/4000 (want 2000)"
2778 );
2779 assert!(
2780 distinct.len() > 100,
2781 "the default values are spread: {}",
2782 distinct.len()
2783 );
2784 }
2785
2786 fn randc_program(id: u32, limit: u64) -> (ClassTemplateId, u64) {
2789 let c = vec![
2790 bin(BinOp::Lt, var(0, u(8)), k(u(8), limit), Ty::BIT),
2791 bin(BinOp::Ge, var(0, u(8)), Expr::State(0, u(8)), Ty::BIT),
2792 bin(BinOp::Gt, var(1, u(8)), var(0, u(8)), Ty::BIT),
2793 ];
2794 let (t, h) = setup(id, 2, c);
2795 let mut p = program(t).unwrap().as_ref().clone().unwrap();
2796 p.vars[0].randc = true;
2797 register_program(t, Ok(p));
2798 (t, h)
2799 }
2800
2801 fn draw_cycle(h: u64, n: usize) -> Vec<u64> {
2802 (0..n)
2803 .map(|_| {
2804 assert_eq!(randomize(h), Outcome::Solved);
2805 get(h, 0)
2806 })
2807 .collect()
2808 }
2809
2810 #[test]
2811 fn randc_visits_every_legal_value_once_per_cycle() {
2812 let (_, h) = randc_program(0x7000_0030, 6);
2815 let mut orders = std::collections::BTreeSet::new();
2816 for _ in 0..40 {
2817 let mut cyc = draw_cycle(h, 6);
2818 orders.insert(cyc.clone());
2819 cyc.sort_unstable();
2820 assert_eq!(cyc, vec![0, 1, 2, 3, 4, 5]);
2821 }
2822 assert!(orders.len() > 10, "cycles are shuffled, not a fixed order");
2823 release(h);
2824 }
2825
2826 #[test]
2827 fn randc_state_is_committed_only_when_the_call_succeeds() {
2828 let (_, h) = randc_program(0x7000_0031, 4);
2829 let first = draw_cycle(h, 2);
2830 let saved = objects::randc_state(h, 0).unwrap();
2833 set(h, 2, 200);
2834 let seed = objects::prng_seed(h);
2835 assert_eq!(randomize(h), Outcome::Unsatisfiable);
2836 assert_eq!(objects::randc_state(h, 0).unwrap(), saved);
2837 assert_eq!(objects::prng_seed(h), seed);
2838 set(h, 2, 0);
2839 let rest = draw_cycle(h, 2);
2840 let mut all = [first, rest].concat();
2841 all.sort_unstable();
2842 assert_eq!(all, vec![0, 1, 2, 3], "the interrupted cycle completes");
2843 release(h);
2844 }
2845
2846 #[test]
2847 fn randc_constraint_change_starts_a_new_cycle() {
2848 let (_, h) = randc_program(0x7000_0032, 8);
2850 draw_cycle(h, 3);
2851 set(h, 2, 6); let next = draw_cycle(h, 2);
2853 let mut sorted = next.clone();
2854 sorted.sort_unstable();
2855 assert_eq!(sorted, vec![6, 7], "{next:?}: a fresh cycle of the new set");
2856 release(h);
2857 }
2858
2859 #[test]
2860 fn randc_interacts_with_rand_without_failing() {
2861 let (_, h) = randc_program(0x7000_0033, 6);
2864 for _ in 0..10 {
2865 let mut cyc = Vec::new();
2866 for _ in 0..6 {
2867 assert_eq!(randomize(h), Outcome::Solved);
2868 assert!(get(h, 1) > get(h, 0));
2869 cyc.push(get(h, 0));
2870 }
2871 cyc.sort_unstable();
2872 assert_eq!(cyc, vec![0, 1, 2, 3, 4, 5]);
2873 }
2874 release(h);
2875 }
2876
2877 #[test]
2878 fn an_inactive_randc_variable_is_state_and_keeps_its_cycle() {
2879 let (_, h) = randc_program(0x7000_0034, 4);
2880 draw_cycle(h, 2);
2881 let saved = objects::randc_state(h, 0).unwrap();
2882 set(h, 0, 2);
2883 let mut m = objects::rand_meta(h).unwrap();
2884 m.rand_mode[0] = false;
2885 objects::set_rand_meta(h, m);
2886 for _ in 0..5 {
2887 assert_eq!(randomize(h), Outcome::Solved);
2888 assert_eq!(get(h, 0), 2, "not redrawn");
2889 assert!(get(h, 1) > 2);
2890 }
2891 assert_eq!(objects::randc_state(h, 0).unwrap(), saved);
2892 release(h);
2893 }
2894
2895 fn static_randc(id: u32) -> (u64, u64, u64) {
2898 let (t, a) = randc_program(id, 4);
2899 let mut p = program(t).unwrap().as_ref().clone().unwrap();
2900 p.vars[0].home = Home::Static(7);
2901 p.vars[0].prop_index = None;
2902 register_program(t, Ok(p));
2903 let b = alloc(t, 3);
2904 objects::set_prng_seed(b, 0x9999);
2905 let st = alloc(ClassTemplateId(id + 0x100), 1);
2906 (a, b, st)
2907 }
2908
2909 #[test]
2910 fn a_static_randc_cycles_across_all_instances() {
2911 let (a, b, st) = static_randc(0x7000_0035);
2915 let pairs = [(7u32, st)];
2916 for _ in 0..10 {
2917 let mut cyc = Vec::new();
2918 for i in 0..4 {
2919 let h = if i % 2 == 0 { a } else { b };
2920 assert_eq!(super::randomize(h, &pairs), Outcome::Solved);
2921 let mut w = [0u64];
2922 assert!(objects::field_get(
2923 st,
2924 FieldSlot {
2925 offset: 0,
2926 words: 1
2927 },
2928 &mut w
2929 ));
2930 cyc.push(w[0]);
2931 assert_eq!(get(h, 0), 0, "the instance's own word is untouched");
2932 }
2933 cyc.sort_unstable();
2934 assert_eq!(cyc, vec![0, 1, 2, 3]);
2935 }
2936 assert_eq!(
2937 objects::randc_state(a, 0).unwrap(),
2938 RandcState::default(),
2939 "the cycle lives in the static object, not the instance"
2940 );
2941 assert_ne!(objects::randc_state(st, 0).unwrap(), RandcState::default());
2942 release(a);
2943 release(b);
2944 release(st);
2945 }
2946
2947 #[test]
2948 fn instance_randc_cycles_are_per_object() {
2949 let (_, a) = randc_program(0x7000_0036, 4);
2951 let b = alloc(ClassTemplateId(0x7000_0036), 3);
2952 objects::set_prng_seed(b, 0x4242);
2953 for _ in 0..6 {
2954 let mut ca = Vec::new();
2955 let mut cb = Vec::new();
2956 for _ in 0..4 {
2957 assert_eq!(randomize(a), Outcome::Solved);
2958 ca.push(get(a, 0));
2959 assert_eq!(randomize(b), Outcome::Solved);
2960 cb.push(get(b, 0));
2961 }
2962 ca.sort_unstable();
2963 cb.sort_unstable();
2964 assert_eq!((ca, cb), (vec![0, 1, 2, 3], vec![0, 1, 2, 3]));
2965 }
2966 release(a);
2967 release(b);
2968 }
2969
2970 #[test]
2971 fn a_missing_static_object_is_a_loud_error_that_changes_nothing() {
2972 let (a, _, st) = static_randc(0x7000_0037);
2973 let seed = objects::prng_seed(a);
2974 assert_eq!(super::randomize(a, &[]), Outcome::StaticUnavailable);
2975 assert_eq!(super::randomize(a, &[(8, st)]), Outcome::StaticUnavailable);
2976 assert_eq!(objects::prng_seed(a), seed);
2977 assert_eq!(super::randomize(a, &[(7, st)]), Outcome::Solved);
2978 release(a);
2979 release(st);
2980 }
2981
2982 const HANDLE_A: usize = 2;
2989 const HANDLE_B: usize = 4;
2990
2991 fn handle_slot(offset: usize) -> FieldSlot {
2992 FieldSlot { offset, words: 2 }
2993 }
2994
2995 fn d_object(id: u32, x: u64) -> u64 {
2997 let d = alloc(ClassTemplateId(id), 1);
2998 set(d, 0, x);
2999 d
3000 }
3001
3002 fn set_handle(h: u64, offset: usize, target: u64) {
3003 assert!(objects::field_set(h, handle_slot(offset), &[target, 0]));
3004 }
3005
3006 fn guard_class(id: u32, conjuncts: Vec<Expr>, a_x: Option<u64>, b_x: Option<u64>) -> u64 {
3010 let t = ClassTemplateId(id);
3011 let rand_slot = |i: usize| FieldSlot {
3012 offset: i,
3013 words: 1,
3014 };
3015 register_rand_props(
3016 t,
3017 (0..2)
3018 .map(|i| RandProp {
3019 slot: rand_slot(i),
3020 kind: RandKind::Rand,
3021 })
3022 .collect(),
3023 );
3024 let state = |root: usize| ProgState {
3025 class_of: false,
3026 root_bit: 0,
3027 ty: u(8),
3028 slot: handle_slot(root),
3029 window: None,
3030 home: Home::Instance,
3031 via: vec![rand_slot(0)],
3032 };
3033 register_program(
3034 t,
3035 Ok(Program {
3036 vars: (0..2)
3037 .map(|i| ProgVar {
3038 ty: u(8),
3039 slot: rand_slot(i),
3040 home: Home::Instance,
3041 prop_index: Some(i),
3042 domain_conjunct: None,
3043 randc: false,
3044 window: None,
3045 })
3046 .collect(),
3047 states: vec![state(HANDLE_A), state(HANDLE_B)],
3048 conjuncts,
3049 before: Vec::new(),
3050 dists: Vec::new(),
3051 ..Program::default()
3052 }),
3053 );
3054 let h = alloc(t, 6);
3055 objects::set_prng_seed(h, 0x5eed_0018);
3056 if let Some(x) = a_x {
3057 set_handle(h, HANDLE_A, d_object(id + 0x100, x));
3058 }
3059 if let Some(x) = b_x {
3060 set_handle(h, HANDLE_B, d_object(id + 0x200, x));
3061 }
3062 h
3063 }
3064
3065 fn x() -> Expr {
3066 var(0, u(8))
3067 }
3068 fn y() -> Expr {
3069 var(1, u(8))
3070 }
3071 fn a_x() -> Expr {
3072 Expr::State(0, u(8))
3073 }
3074 fn b_x() -> Expr {
3075 Expr::State(1, u(8))
3076 }
3077 fn lt_xy() -> Expr {
3078 bin(BinOp::Lt, x(), y(), Ty::BIT)
3079 }
3080 fn a_gt_b() -> Expr {
3081 bin(BinOp::Gt, a_x(), b_x(), Ty::BIT)
3082 }
3083 fn a_is_5() -> Expr {
3084 bin(BinOp::Eq, a_x(), k(u(8), 5), Ty::BIT)
3085 }
3086 fn or(l: Expr, r: Expr) -> Expr {
3087 bin(BinOp::LogOr, l, r, Ty::BIT)
3088 }
3089 fn and(l: Expr, r: Expr) -> Expr {
3090 bin(BinOp::LogAnd, l, r, Ty::BIT)
3091 }
3092 fn guard_sum(pred: Expr) -> Vec<Expr> {
3094 vec![Expr::Guard(
3095 Box::new(pred),
3096 Box::new(bin(
3097 BinOp::Eq,
3098 bin(BinOp::Add, x(), y(), u(8)),
3099 k(u(8), 10),
3100 Ty::BIT,
3101 )),
3102 Box::new(k(Ty::BIT, 1)),
3103 Ty::BIT,
3104 )]
3105 }
3106
3107 fn sum_draws(h: u64) -> (u32, u32, bool) {
3110 let (mut sums, mut lts, mut implied) = (0, 0, true);
3111 for _ in 0..400 {
3112 assert_eq!(randomize(h), Outcome::Solved);
3113 let (x, y) = (get(h, 0) as u8, get(h, 1) as u8);
3114 let sum = x.wrapping_add(y) == 10;
3115 sums += u32::from(sum);
3116 lts += u32::from(x < y);
3117 implied &= x >= y || sum;
3118 }
3119 (sums, lts, implied)
3120 }
3121
3122 fn assert_body_unconditional(h: u64) {
3124 let (sums, _, _) = sum_draws(h);
3125 assert_eq!(sums, 400, "an unconditional `x + y == 10`");
3126 }
3127
3128 fn assert_body_conditional(h: u64) {
3131 let (sums, lts, implied) = sum_draws(h);
3132 assert!(implied, "(x < y) -> (x + y == 10) always holds");
3133 assert!(lts < 400 && sums < 400, "not unconditional: {sums}/{lts}");
3134 }
3135
3136 fn assert_eliminated(h: u64) {
3138 let (sums, lts, _) = sum_draws(h);
3139 assert!(sums < 20, "x + y == 10 is a 1/256 accident: {sums}");
3140 assert!((120..280).contains(<s), "x and y are free: {lts}/400");
3141 }
3142
3143 fn assert_error(h: u64) {
3144 let (seed, x0) = (objects::prng_seed(h), get(h, 0));
3145 assert_eq!(randomize(h), Outcome::GuardError);
3146 assert_eq!((objects::prng_seed(h), get(h, 0)), (seed, x0), "no change");
3147 }
3148
3149 #[test]
3151 fn lrm_example_1_or_guard() {
3152 let c = || guard_sum(or(or(lt_xy(), a_gt_b()), a_is_5()));
3153 assert_body_unconditional(guard_class(0x7100_0001, c(), Some(5), None));
3156 assert_error(guard_class(0x7100_0002, c(), None, Some(1)));
3158 assert_body_conditional(guard_class(0x7100_0003, c(), Some(10), Some(20)));
3160 }
3161
3162 #[test]
3164 fn lrm_example_2_and_guard() {
3165 let c = || guard_sum(and(and(lt_xy(), a_gt_b()), a_is_5()));
3166 assert_eliminated(guard_class(0x7100_0011, c(), Some(6), None));
3169 assert_error(guard_class(0x7100_0012, c(), None, Some(1)));
3171 assert_body_conditional(guard_class(0x7100_0013, c(), Some(5), Some(2)));
3173 }
3174
3175 #[test]
3177 fn lrm_example_3_nested_guard() {
3178 let c = || guard_sum(and(lt_xy(), or(a_gt_b(), a_is_5())));
3179 assert_body_conditional(guard_class(0x7100_0021, c(), Some(5), None));
3181 assert_error(guard_class(0x7100_0022, c(), Some(8), None));
3183 assert_error(guard_class(0x7100_0023, c(), None, Some(1)));
3185 assert_body_conditional(guard_class(0x7100_0024, c(), Some(5), Some(2)));
3187 }
3188
3189 #[test]
3192 fn a_false_guard_selects_the_else_arm_and_never_examines_the_other() {
3193 let inner = Expr::Guard(
3195 Box::new(bin(BinOp::Eq, b_x(), k(u(8), 1), Ty::BIT)),
3196 Box::new(bin(BinOp::Eq, x(), k(u(8), 9), Ty::BIT)),
3197 Box::new(k(Ty::BIT, 1)),
3198 Ty::BIT,
3199 );
3200 let c = vec![Expr::Guard(
3201 Box::new(a_is_5()),
3202 Box::new(inner),
3203 Box::new(bin(BinOp::Eq, y(), k(u(8), 3), Ty::BIT)),
3204 Ty::BIT,
3205 )];
3206 let h = guard_class(0x7100_0031, c.clone(), Some(6), None);
3208 for _ in 0..20 {
3209 assert_eq!(randomize(h), Outcome::Solved);
3210 assert_eq!(get(h, 1), 3);
3211 }
3212 assert_error(guard_class(0x7100_0032, c.clone(), Some(5), None));
3214 let h = guard_class(0x7100_0033, c, Some(5), Some(1));
3216 assert_eq!(randomize(h), Outcome::Solved);
3217 assert_eq!(get(h, 0), 9);
3218 }
3219
3220 #[test]
3223 fn a_null_handle_outside_a_guard_fails_the_call_with_its_own_outcome() {
3224 let c = vec![bin(BinOp::Eq, x(), a_x(), Ty::BIT)];
3225 let h = guard_class(0x7100_0041, c.clone(), None, None);
3226 let seed = objects::prng_seed(h);
3227 assert_eq!(randomize(h), Outcome::NullHandle);
3228 assert_eq!(objects::prng_seed(h), seed);
3229 let h = guard_class(0x7100_0042, c.clone(), Some(77), None);
3231 assert_eq!(randomize(h), Outcome::Solved);
3232 assert_eq!(get(h, 0), 77);
3233 let h = guard_class(0x7100_0043, c.clone(), Some(77), None);
3235 let mut buf = [0u64; 2];
3236 assert!(objects::field_get(h, handle_slot(HANDLE_A), &mut buf));
3237 release(buf[0]);
3238 assert_eq!(randomize(h), Outcome::NullHandle);
3239 let h = guard_class(0x7100_0044, c, Some(77), None);
3241 assert!(objects::field_set(h, handle_slot(HANDLE_A), &[1, 1]));
3242 assert_eq!(randomize(h), Outcome::StateUnknown);
3243 }
3244
3245 #[test]
3248 fn an_unknown_value_in_a_guard_is_an_error() {
3249 let h = guard_class(0x7100_0051, guard_sum(a_is_5()), Some(5), None);
3250 let mut buf = [0u64; 2];
3252 assert!(objects::field_get(h, handle_slot(HANDLE_A), &mut buf));
3253 assert!(objects::field_set(
3254 buf[0],
3255 FieldSlot {
3256 offset: 0,
3257 words: 1
3258 },
3259 &[5 | (1 << 8)]
3260 ));
3261 assert_error(h);
3262 }
3263
3264 #[test]
3267 fn a_two_hop_handle_chain_reads_the_far_member_and_fails_on_a_null_link() {
3268 let node = |id: u32, v: u64| {
3270 let o = alloc(ClassTemplateId(id), 3);
3271 set(o, 2, v);
3272 o
3273 };
3274 let t = ClassTemplateId(0x7100_0061);
3275 register_rand_props(
3276 t,
3277 vec![RandProp {
3278 slot: FieldSlot {
3279 offset: 0,
3280 words: 1,
3281 },
3282 kind: RandKind::Rand,
3283 }],
3284 );
3285 let root = |offset| ProgState {
3286 class_of: false,
3287 root_bit: 0,
3288 ty: u(8),
3289 slot: handle_slot(offset),
3290 window: None,
3291 home: Home::Instance,
3292 via: vec![
3293 handle_slot(0),
3294 FieldSlot {
3295 offset: 2,
3296 words: 1,
3297 },
3298 ],
3299 };
3300 register_program(
3302 t,
3303 Ok(Program {
3304 vars: vec![ProgVar {
3305 ty: u(8),
3306 slot: FieldSlot {
3307 offset: 0,
3308 words: 1,
3309 },
3310 home: Home::Instance,
3311 prop_index: Some(0),
3312 domain_conjunct: None,
3313 randc: false,
3314 window: None,
3315 }],
3316 states: vec![root(1)],
3317 conjuncts: vec![bin(BinOp::Eq, x(), Expr::State(0, u(8)), Ty::BIT)],
3318 before: Vec::new(),
3319 dists: Vec::new(),
3320 ..Program::default()
3321 }),
3322 );
3323 let h = alloc(t, 3);
3324 objects::set_prng_seed(h, 1);
3325 let (n0, n1) = (node(0x7100_0062, 1), node(0x7100_0063, 42));
3326 set_handle(h, 1, n0);
3327 set_handle(n0, 0, n1);
3328 assert_eq!(randomize(h), Outcome::Solved);
3329 assert_eq!(get(h, 0), 42);
3330 set_handle(n0, 0, 0);
3331 assert_eq!(randomize(h), Outcome::NullHandle);
3332 }
3333
3334 use crate::constraints::real_domain::{
3337 CmpOp, RealCond, RealDist, RealDistItem, RealExpr, RealSpan,
3338 };
3339
3340 fn rslot(offset: usize) -> FieldSlot {
3341 FieldSlot { offset, words: 2 }
3342 }
3343 fn rk(x: f64) -> RealExpr {
3344 RealExpr::Const(real_domain::Real::new(x), FloatKind::F64)
3345 }
3346 fn rcmp(op: CmpOp, a: RealExpr, b: RealExpr) -> RealCond {
3347 RealCond::Cmp(op, a, b)
3348 }
3349 fn rget(h: u64, off: usize) -> f64 {
3350 let mut b = [0u64; 2];
3351 assert!(objects::field_get(h, rslot(off), &mut b));
3352 assert_eq!(b[1], 0, "a solved real has no unknown bits");
3353 f64::from_bits(b[0])
3354 }
3355 fn rset_bits(h: u64, off: usize, bits: u64, unk: u64) {
3356 assert!(objects::field_set(h, rslot(off), &[bits, unk]));
3357 }
3358
3359 fn setup_real(id: u32, real: RealProblem, conjuncts: Vec<Expr>) -> u64 {
3363 let t = ClassTemplateId(id);
3364 register_rand_props(
3365 t,
3366 vec![
3367 RandProp {
3368 slot: rslot(0),
3369 kind: RandKind::Rand,
3370 },
3371 RandProp {
3372 slot: FieldSlot {
3373 offset: 2,
3374 words: 1,
3375 },
3376 kind: RandKind::Rand,
3377 },
3378 ],
3379 );
3380 register_program(
3381 t,
3382 Ok(Program {
3383 vars: vec![ProgVar {
3384 ty: u(8),
3385 slot: FieldSlot {
3386 offset: 2,
3387 words: 1,
3388 },
3389 home: Home::Instance,
3390 prop_index: Some(1),
3391 domain_conjunct: None,
3392 randc: false,
3393 window: None,
3394 }],
3395 states: vec![ProgState {
3396 class_of: false,
3397 root_bit: 0,
3398 ty: u(64),
3399 slot: rslot(3),
3400 window: None,
3401 home: Home::Instance,
3402 via: Vec::new(),
3403 }],
3404 conjuncts,
3405 real_vars: vec![ProgRealVar {
3406 kind: FloatKind::F64,
3407 slot: rslot(0),
3408 home: Home::Instance,
3409 prop_index: Some(0),
3410 window: None,
3411 }],
3412 real,
3413 ..Program::default()
3414 }),
3415 );
3416 let h = alloc(t, 5);
3417 objects::set_prng_seed(h, 0x5eed_0c09);
3418 h
3419 }
3420
3421 fn unit_interval() -> RealProblem {
3422 RealProblem {
3423 kinds: vec![FloatKind::F64],
3424 conds: vec![
3425 rcmp(CmpOp::Ge, RealExpr::Var(0), rk(1.0)),
3426 rcmp(CmpOp::Lt, RealExpr::Var(0), rk(2.0)),
3427 ],
3428 ..RealProblem::default()
3429 }
3430 }
3431
3432 #[test]
3433 fn real_variable_is_solved_with_the_integral_ones_and_the_seed_advances() {
3434 let c = vec![bin(BinOp::Eq, var(0, u(8)), k(u(8), 7), Ty::BIT)];
3436 let h = setup_real(0x7000_0c01, unit_interval(), c);
3437 let before = objects::prng_seed(h);
3438 let mut bins = [0u32; 4];
3439 for _ in 0..400 {
3440 assert_eq!(randomize(h), Outcome::Solved);
3441 let r = rget(h, 0);
3442 assert!((1.0..2.0).contains(&r), "{r}");
3443 assert_eq!(get(h, 2), 7);
3444 bins[((r - 1.0) * 4.0) as usize] += 1;
3445 }
3446 assert!(
3447 bins.iter().all(|&n| n > 60),
3448 "uniform over the interval: {bins:?}"
3449 );
3450 assert_ne!(objects::prng_seed(h), before);
3451 release(h);
3452 }
3453
3454 #[test]
3455 fn unsatisfiable_real_constraint_writes_nothing_and_keeps_the_seed() {
3456 let mut p = unit_interval();
3457 p.conds.push(rcmp(CmpOp::Gt, RealExpr::Var(0), rk(5.0)));
3458 let h = setup_real(0x7000_0c02, p, Vec::new());
3459 rset_bits(h, 0, 3.25f64.to_bits(), 0);
3460 set(h, 2, 99);
3461 let seed = objects::prng_seed(h);
3462 assert_eq!(randomize(h), Outcome::Unsatisfiable);
3463 assert_eq!(rget(h, 0), 3.25);
3464 assert_eq!(get(h, 2), 99, "the integral variable is untouched too");
3465 assert_eq!(objects::prng_seed(h), seed);
3466 release(h);
3467 }
3468
3469 #[test]
3470 fn inactive_real_variable_is_a_constant_and_is_not_written() {
3471 let h = setup_real(0x7000_0c03, unit_interval(), Vec::new());
3472 let mut m = objects::rand_meta(h).unwrap();
3473 m.rand_mode[0] = false;
3474 objects::set_rand_meta(h, m);
3475 rset_bits(h, 0, 1.5f64.to_bits(), 0);
3476 for _ in 0..10 {
3477 assert_eq!(randomize(h), Outcome::Solved);
3478 assert_eq!(rget(h, 0), 1.5);
3479 }
3480 rset_bits(h, 0, 2.0f64.to_bits(), 0);
3482 assert_eq!(randomize(h), Outcome::Unsatisfiable);
3483 assert_eq!(rget(h, 0), 2.0);
3484 rset_bits(h, 0, 0, 1);
3486 assert_eq!(randomize(h), Outcome::StateUnknown);
3487 release(h);
3488 }
3489
3490 #[test]
3491 fn real_state_variable_bounds_the_solution_and_division_by_zero_is_a_loud_error() {
3492 let p = RealProblem {
3494 kinds: vec![FloatKind::F64],
3495 conds: vec![
3496 rcmp(CmpOp::Ge, RealExpr::Var(0), rk(0.0)),
3497 rcmp(
3498 CmpOp::Lt,
3499 RealExpr::Var(0),
3500 RealExpr::State(0, FloatKind::F64),
3501 ),
3502 ],
3503 ..RealProblem::default()
3504 };
3505 let h = setup_real(0x7000_0c04, p, Vec::new());
3506 rset_bits(h, 3, 0.5f64.to_bits(), 0);
3507 for _ in 0..50 {
3508 assert_eq!(randomize(h), Outcome::Solved);
3509 assert!((0.0..0.5).contains(&rget(h, 0)));
3510 }
3511 rset_bits(h, 3, f64::NAN.to_bits(), 0);
3514 assert_eq!(randomize(h), Outcome::Unsatisfiable);
3515 rset_bits(h, 3, 0, 1);
3516 assert_eq!(randomize(h), Outcome::StateUnknown);
3517 release(h);
3518
3519 let p = RealProblem {
3521 kinds: vec![FloatKind::F64],
3522 conds: vec![rcmp(
3523 CmpOp::Lt,
3524 RealExpr::Div(
3525 Box::new(RealExpr::Var(0)),
3526 Box::new(RealExpr::State(0, FloatKind::F64)),
3527 ),
3528 rk(1.0),
3529 )],
3530 ..RealProblem::default()
3531 };
3532 let h = setup_real(0x7000_0c08, p, Vec::new());
3533 rset_bits(h, 3, 2.0f64.to_bits(), 0);
3534 assert_eq!(randomize(h), Outcome::Solved);
3535 assert!(rget(h, 0) < 2.0);
3536 rset_bits(h, 3, 0.0f64.to_bits(), 0);
3537 assert_eq!(randomize(h), Outcome::RealDomain);
3538 release(h);
3539 }
3540
3541 #[test]
3542 fn real_dist_follows_its_weights() {
3543 let p = RealProblem {
3546 kinds: vec![FloatKind::F64],
3547 dists: vec![RealDist {
3548 var: 0,
3549 items: vec![
3550 RealDistItem {
3551 span: RealSpan::Point(rk(1.0)),
3552 weight: k(u(32), 3),
3553 },
3554 RealDistItem {
3555 span: RealSpan::Range {
3556 lo: Some(rk(10.0)),
3557 hi: Some(rk(20.0)),
3558 },
3559 weight: k(u(32), 1),
3560 },
3561 ],
3562 default: None,
3563 }],
3564 ..RealProblem::default()
3565 };
3566 let h = setup_real(0x7000_0c05, p, Vec::new());
3567 let (mut point, mut range) = (0u32, 0u32);
3568 for _ in 0..2000 {
3569 assert_eq!(randomize(h), Outcome::Solved);
3570 let r = rget(h, 0);
3571 if r == 1.0 {
3572 point += 1;
3573 } else {
3574 assert!((10.0..=20.0).contains(&r), "{r}");
3575 range += 1;
3576 }
3577 }
3578 assert!((1400..=1600).contains(&point), "{point} / {range}");
3580 release(h);
3581 }
3582
3583 #[test]
3584 fn the_integral_solution_is_drawn_before_the_real_one() {
3585 let c = vec![bin(BinOp::Ge, var(0, u(8)), k(u(8), 3), Ty::BIT)];
3589 let (_, plain) = setup(0x7000_0c06, 1, c.clone());
3590 let h = setup_real(0x7000_0c07, unit_interval(), c);
3591 objects::set_prng_seed(h, objects::prng_seed(plain).unwrap());
3592 let seed = objects::prng_seed(plain).unwrap();
3593 assert_eq!(randomize(plain), Outcome::Solved);
3594 assert_eq!(randomize(h), Outcome::Solved);
3595 assert_eq!(get(plain, 0), get(h, 2));
3597 assert_ne!(
3598 objects::prng_seed(h),
3599 objects::prng_seed(plain),
3600 "real draws follow"
3601 );
3602 let mut m = objects::rand_meta(h).unwrap();
3604 m.rand_mode[0] = false;
3605 objects::set_rand_meta(h, m);
3606 rset_bits(h, 0, 1.5f64.to_bits(), 0);
3607 objects::set_prng_seed(h, seed);
3608 assert_eq!(randomize(h), Outcome::Solved);
3609 assert_eq!(objects::prng_seed(h), objects::prng_seed(plain));
3610 release(plain);
3611 release(h);
3612 }
3613}
3614
3615#[cfg(test)]
3617mod array_tests {
3618 use super::*;
3619 use crate::collections::{KeyKind, Shape};
3620 use crate::constraints::ir::{ArrayNode, BinOp, LoopId, ReduceOp};
3621 use crate::objects::{RandKind, RandProp, alloc, register_rand_props, release};
3622
3623 fn u8t() -> Ty {
3624 Ty::unsigned(8)
3625 }
3626 fn s32() -> Ty {
3627 Ty::signed(32)
3628 }
3629 fn bin(op: BinOp, a: Expr, b: Expr, t: Ty) -> Expr {
3630 Expr::Binary(op, Box::new(a), Box::new(b), t)
3631 }
3632 fn k(t: Ty, v: u64) -> Expr {
3633 Expr::constant(t, v)
3634 }
3635 fn node(n: ArrayNode) -> Expr {
3636 Expr::Array(n)
3637 }
3638 fn lp(id: LoopId) -> Expr {
3639 node(ArrayNode::Loop(id, s32()))
3640 }
3641 fn elem(a: u32, i: Expr) -> Expr {
3642 node(ArrayNode::Elem {
3643 array: a,
3644 index: Box::new(i),
3645 ty: u8t(),
3646 })
3647 }
3648 fn shape() -> Shape {
3649 Shape {
3650 elem_words: 1,
3651 assoc: false,
3652 key: KeyKind::Int {
3653 width: 32,
3654 signed: false,
3655 },
3656 obj: false,
3657 }
3658 }
3659 fn slot(o: usize) -> FieldSlot {
3660 FieldSlot {
3661 offset: o,
3662 words: 1,
3663 }
3664 }
3665 fn contents(h: u64) -> Vec<u64> {
3666 let mut b = [0u64];
3667 assert!(objects::field_get(h, slot(0), &mut b));
3668 collections::elements(b[0])
3669 .into_iter()
3670 .map(|e| e[0])
3671 .collect()
3672 }
3673
3674 fn setup(id: u32, conjuncts: Vec<Expr>, initial: &[u64]) -> u64 {
3677 let t = ClassTemplateId(id);
3678 register_rand_props(
3679 t,
3680 vec![
3681 RandProp {
3682 slot: slot(0),
3683 kind: RandKind::Rand,
3684 },
3685 RandProp {
3686 slot: slot(1),
3687 kind: RandKind::Rand,
3688 },
3689 ],
3690 );
3691 register_program(
3692 t,
3693 Ok(Program {
3694 vars: vec![ProgVar {
3695 ty: u8t(),
3696 slot: slot(1),
3697 home: Home::Instance,
3698 prop_index: Some(1),
3699 domain_conjunct: None,
3700 randc: false,
3701 window: None,
3702 }],
3703 conjuncts,
3704 arrays: vec![ProgArray {
3705 elem_ty: u8t(),
3706 elem_words: 1,
3707 slot: slot(0),
3708 home: Home::Instance,
3709 rand: true,
3710 prop_index: Some(0),
3711 elem_domain: None,
3712 assoc: false,
3713 }],
3714 ..Program::default()
3715 }),
3716 );
3717 let h = alloc(t, 2);
3718 objects::set_prng_seed(h, 0xA11A_5EED);
3719 let c = collections::from_words(&shape(), initial);
3720 assert!(objects::field_set(h, slot(0), &[c]));
3721 h
3722 }
3723
3724 #[test]
3727 fn a_size_constraint_resizes_and_randomizes_every_element() {
3728 let size_eq = bin(
3729 BinOp::Eq,
3730 node(ArrayNode::Size(0)),
3731 Expr::Resize(Box::new(Expr::Var(0, u8t())), s32()),
3732 Ty::BIT,
3733 );
3734 let lo = bin(BinOp::Ge, Expr::Var(0, u8t()), k(u8t(), 2), Ty::BIT);
3735 let hi = bin(BinOp::Le, Expr::Var(0, u8t()), k(u8t(), 4), Ty::BIT);
3736 let each = node(ArrayNode::Foreach {
3737 array: 0,
3738 var: 0,
3739 body: Box::new(bin(
3740 BinOp::Gt,
3741 Expr::Resize(Box::new(elem(0, lp(0))), s32()),
3742 lp(0),
3743 Ty::BIT,
3744 )),
3745 });
3746 let h = setup(0x7a00_0001, vec![size_eq, lo, hi, each], &[9]);
3747 let mut sizes = std::collections::BTreeSet::new();
3748 for _ in 0..60 {
3749 assert_eq!(randomize(h, &[]), Outcome::Solved);
3750 let a = contents(h);
3751 let mut len = [0u64];
3752 assert!(objects::field_get(h, slot(1), &mut len));
3753 assert_eq!(a.len() as u64, len[0], "size == len");
3754 assert!((2..=4).contains(&a.len()));
3755 for (i, v) in a.iter().enumerate() {
3756 assert!(*v > i as u64, "element {i} = {v}");
3757 }
3758 sizes.insert(a.len());
3759 }
3760 assert_eq!(sizes.len(), 3, "every size 2..=4 is reached: {sizes:?}");
3761 release(h);
3762 }
3763
3764 #[test]
3767 fn an_unsized_array_keeps_its_length_but_gets_new_elements() {
3768 let each = node(ArrayNode::Foreach {
3769 array: 0,
3770 var: 0,
3771 body: Box::new(bin(BinOp::Ge, elem(0, lp(0)), k(u8t(), 200), Ty::BIT)),
3772 });
3773 let h = setup(0x7a00_0002, vec![each], &[1, 2, 3]);
3774 assert_eq!(randomize(h, &[]), Outcome::Solved);
3775 let a = contents(h);
3776 assert_eq!(a.len(), 3);
3777 assert!(a.iter().all(|v| *v >= 200), "{a:?}");
3778 release(h);
3779 }
3780
3781 #[test]
3784 fn an_inactive_array_is_state_and_unchanged() {
3785 let sum = node(ArrayNode::Reduce {
3786 op: ReduceOp::Sum,
3787 array: 0,
3788 var: 0,
3789 index_var: 1,
3790 body: Box::new(Expr::Resize(
3791 Box::new(node(ArrayNode::Loop(0, u8t()))),
3792 s32(),
3793 )),
3794 });
3795 let c = bin(
3797 BinOp::Eq,
3798 Expr::Resize(Box::new(Expr::Var(0, u8t())), s32()),
3799 sum,
3800 Ty::BIT,
3801 );
3802 let h = setup(0x7a00_0003, vec![c], &[3, 4, 5]);
3803 let mut m = objects::rand_meta(h).unwrap();
3804 m.rand_mode[0] = false;
3805 objects::set_rand_meta(h, m);
3806 assert_eq!(randomize(h, &[]), Outcome::Solved);
3807 assert_eq!(contents(h), vec![3, 4, 5]);
3808 let mut len = [0u64];
3809 assert!(objects::field_get(h, slot(1), &mut len));
3810 assert_eq!(len[0], 12);
3811 release(h);
3812 }
3813
3814 #[test]
3817 fn an_out_of_range_element_fails_loudly_and_changes_nothing() {
3818 let c = bin(BinOp::Eq, elem(0, k(s32(), 7)), k(u8t(), 1), Ty::BIT);
3819 let h = setup(0x7a00_0004, vec![c], &[1, 2]);
3820 let seed = objects::prng_seed(h);
3821 assert_eq!(randomize(h, &[]), Outcome::ArrayIndexOutOfRange);
3822 assert_eq!(contents(h), vec![1, 2]);
3823 assert_eq!(objects::prng_seed(h), seed);
3824 release(h);
3825 }
3826
3827 #[test]
3830 fn a_size_that_fails_the_element_constraints_is_retried() {
3831 let lo = bin(BinOp::Ge, node(ArrayNode::Size(0)), k(s32(), 1), Ty::BIT);
3832 let hi = bin(BinOp::Le, node(ArrayNode::Size(0)), k(s32(), 3), Ty::BIT);
3833 let guarded = Expr::Guard(
3836 Box::new(bin(
3837 BinOp::Gt,
3838 node(ArrayNode::Size(0)),
3839 k(s32(), 2),
3840 Ty::BIT,
3841 )),
3842 Box::new(bin(BinOp::Eq, elem(0, k(s32(), 2)), k(u8t(), 5), Ty::BIT)),
3843 Box::new(k(Ty::BIT, 0)),
3844 Ty::BIT,
3845 );
3846 let h = setup(0x7a00_0005, vec![lo, hi, guarded], &[]);
3847 for _ in 0..20 {
3848 assert_eq!(randomize(h, &[]), Outcome::Solved);
3849 let a = contents(h);
3850 assert_eq!(a.len(), 3);
3851 assert_eq!(a[2], 5);
3852 }
3853 release(h);
3854 }
3855
3856 #[test]
3857 fn an_impossible_size_is_unsatisfiable_not_an_error() {
3858 let c = bin(BinOp::Lt, node(ArrayNode::Size(0)), k(s32(), 0), Ty::BIT);
3859 let h = setup(0x7a00_0006, vec![c], &[1]);
3860 assert_eq!(randomize(h, &[]), Outcome::Unsatisfiable);
3861 assert_eq!(contents(h), vec![1]);
3862 release(h);
3863 }
3864
3865 #[test]
3869 fn a_rand_associative_array_keeps_its_keys_and_redraws_its_values() {
3870 use crate::collections::Key;
3871 let t = ClassTemplateId(0x7a00_0007);
3872 register_rand_props(
3873 t,
3874 vec![RandProp {
3875 slot: slot(0),
3876 kind: RandKind::Rand,
3877 }],
3878 );
3879 register_program(
3880 t,
3881 Ok(Program {
3882 arrays: vec![ProgArray {
3883 elem_ty: u8t(),
3884 elem_words: 1,
3885 slot: slot(0),
3886 home: Home::Instance,
3887 rand: true,
3888 prop_index: Some(0),
3889 elem_domain: None,
3890 assoc: true,
3891 }],
3892 ..Program::default()
3893 }),
3894 );
3895 let h = alloc(t, 1);
3896 objects::set_prng_seed(h, 0xA550_0001);
3897 let src = collections::frozen_map(
3898 vec![
3899 (Key::Int(7), vec![1]),
3900 (Key::Int(3), vec![1]),
3901 (Key::Int(40), vec![1]),
3902 ],
3903 Some(vec![9]),
3904 );
3905 let c = collections::clone(src);
3906 assert!(objects::field_set(h, slot(0), &[c]));
3907 let mut seen = std::collections::BTreeSet::new();
3908 for _ in 0..30 {
3909 assert_eq!(randomize(h, &[]), Outcome::Solved);
3910 let mut b = [0u64];
3911 assert!(objects::field_get(h, slot(0), &mut b));
3912 assert_eq!(
3913 collections::keys(b[0]),
3914 vec![Key::Int(3), Key::Int(7), Key::Int(40)],
3915 "the keys never change"
3916 );
3917 assert_eq!(collections::size(b[0]), 3);
3918 let mut dflt = [0u64];
3919 assert!(collections::lookup(b[0], &Key::Int(1000), &mut dflt));
3920 assert_eq!(dflt[0], 9, "the default survives");
3921 for e in collections::elements(b[0]) {
3922 seen.insert(e[0]);
3923 }
3924 }
3925 assert!(seen.len() > 20, "the values are redrawn: {seen:?}");
3926 release(h);
3927 }
3928}
3929
3930#[cfg(test)]
3933mod mode_tests {
3934 use super::tests::{bin, get, k, setup, u, var};
3935 use super::*;
3936 use crate::constraints::ir::BinOp;
3937 use crate::objects::{alloc, register_rand_props, release};
3938
3939 fn slot(offset: usize) -> FieldSlot {
3940 FieldSlot { offset, words: 1 }
3941 }
3942
3943 fn randomize(h: u64) -> Outcome {
3944 super::randomize(h, &[])
3945 }
3946
3947 fn with_blocks(t: ClassTemplateId, blocks: Vec<ProgBlock>) {
3949 let mut p = program(t).unwrap().as_ref().clone().unwrap();
3950 p.blocks = blocks;
3951 register_program(t, Ok(p));
3952 }
3953
3954 fn block(home: Home, ordinal: u32, conjuncts: std::ops::Range<usize>) -> ProgBlock {
3955 ProgBlock {
3956 home,
3957 ordinal,
3958 conjuncts,
3959 items: BlockItems::default(),
3960 }
3961 }
3962
3963 #[test]
3966 fn a_constraint_block_switched_off_is_left_out_of_the_solve() {
3967 let c = vec![
3969 bin(BinOp::Gt, var(0, u(8)), k(u(8), 200), Ty::BIT),
3970 bin(BinOp::Lt, var(0, u(8)), k(u(8), 100), Ty::BIT),
3971 ];
3972 let (t, h) = setup(0x7c00_0001, 1, c);
3973 with_blocks(
3974 t,
3975 vec![
3976 block(Home::Instance, 0, 0..1),
3977 block(Home::Instance, 1, 1..2),
3978 ],
3979 );
3980 assert_eq!(randomize(h), Outcome::Unsatisfiable);
3981 assert!(objects::set_constraint_mode_of(h, 0, false));
3982 for _ in 0..20 {
3983 assert_eq!(randomize(h), Outcome::Solved);
3984 assert!(get(h, 0) < 100, "block 1 still holds");
3985 }
3986 assert!(objects::set_constraint_mode_of(h, 0, true));
3987 assert!(objects::set_constraint_mode_of(h, 1, false));
3988 for _ in 0..20 {
3989 assert_eq!(randomize(h), Outcome::Solved);
3990 assert!(get(h, 0) > 200, "block 0 holds again, block 1 is off");
3991 }
3992 assert!(objects::set_constraint_mode_of(h, 1, true));
3993 assert_eq!(randomize(h), Outcome::Unsatisfiable);
3994 release(h);
3995 }
3996
3997 #[test]
4000 fn constraint_mode_is_per_object() {
4001 let c = vec![
4002 bin(BinOp::Gt, var(0, u(8)), k(u(8), 200), Ty::BIT),
4003 bin(BinOp::Lt, var(0, u(8)), k(u(8), 100), Ty::BIT),
4004 ];
4005 let (t, a) = setup(0x7c00_0002, 1, c);
4006 with_blocks(
4007 t,
4008 vec![
4009 block(Home::Instance, 0, 0..1),
4010 block(Home::Instance, 1, 1..2),
4011 ],
4012 );
4013 let b = alloc(t, 2);
4014 objects::set_prng_seed(b, 7);
4015 assert!(objects::set_constraint_mode_of(a, 0, false));
4016 assert_eq!(randomize(a), Outcome::Solved);
4017 assert_eq!(randomize(b), Outcome::Unsatisfiable);
4018 release(a);
4019 release(b);
4020 }
4021
4022 #[test]
4025 fn a_static_blocks_mode_is_shared_by_every_instance() {
4026 let c = vec![
4027 bin(BinOp::Gt, var(0, u(8)), k(u(8), 200), Ty::BIT),
4028 bin(BinOp::Lt, var(0, u(8)), k(u(8), 100), Ty::BIT),
4029 ];
4030 let (t, a) = setup(0x7c00_0004, 1, c);
4031 with_blocks(
4032 t,
4033 vec![
4034 block(Home::Static(9), 0, 0..1),
4035 block(Home::Instance, 1, 1..2),
4036 ],
4037 );
4038 let b = alloc(t, 2);
4039 objects::set_prng_seed(b, 11);
4040 let st = alloc(ClassTemplateId(0x7c00_0104), 1);
4041 let pairs = [(9u32, st)];
4042 assert_eq!(super::randomize(a, &pairs), Outcome::Unsatisfiable);
4043 assert!(objects::set_constraint_mode_of(st, 0, false));
4044 assert_eq!(super::randomize(a, &pairs), Outcome::Solved);
4045 assert_eq!(super::randomize(b, &pairs), Outcome::Solved);
4046 assert_eq!(super::randomize(a, &[]), Outcome::Unsatisfiable);
4048 release(a);
4049 release(b);
4050 release(st);
4051 }
4052
4053 #[test]
4056 fn a_static_rand_mode_is_shared_and_makes_the_variable_state() {
4057 let t = ClassTemplateId(0x7c00_0005);
4058 register_rand_props(t, Vec::new());
4059 register_program(
4060 t,
4061 Ok(Program {
4062 vars: vec![ProgVar {
4063 ty: u(8),
4064 slot: slot(0),
4065 home: Home::Static(5),
4066 prop_index: None,
4067 domain_conjunct: None,
4068 randc: false,
4069 window: None,
4070 }],
4071 conjuncts: vec![bin(BinOp::Gt, var(0, u(8)), k(u(8), 200), Ty::BIT)],
4072 ..Program::default()
4073 }),
4074 );
4075 let a = alloc(t, 1);
4076 objects::set_prng_seed(a, 3);
4077 let st = alloc(ClassTemplateId(0x7c00_0105), 1);
4078 let pairs = [(5u32, st)];
4079 assert!(objects::field_set(st, slot(0), &[7]));
4080 assert_eq!(super::randomize(a, &pairs), Outcome::Solved);
4082 assert!(get(st, 0) > 200);
4083 assert!(objects::field_set(st, slot(0), &[7]));
4085 assert!(objects::set_static_rand_mode(st, 0, false));
4086 assert_eq!(super::randomize(a, &pairs), Outcome::Unsatisfiable);
4087 assert_eq!(get(st, 0), 7);
4088 assert!(objects::set_static_rand_mode(st, 0, true));
4089 assert_eq!(super::randomize(a, &pairs), Outcome::Solved);
4090 release(a);
4091 release(st);
4092 }
4093}
4094
4095#[cfg(test)]
4097mod func_tests {
4098 use super::*;
4099 use crate::constraints::func::{Stmt, tests::count_ones};
4100 use crate::constraints::ir::{ArrayNode, BinOp};
4101 use crate::objects::{RandKind, RandProp, alloc, register_rand_props, release};
4102
4103 fn u(w: u32) -> Ty {
4104 Ty::unsigned(w)
4105 }
4106 fn s32() -> Ty {
4107 Ty::signed(32)
4108 }
4109 fn var(i: u32, t: Ty) -> Expr {
4110 Expr::Var(i, t)
4111 }
4112 fn k(t: Ty, v: u64) -> Expr {
4113 Expr::constant(t, v)
4114 }
4115 fn bin(op: BinOp, a: Expr, b: Expr, t: Ty) -> Expr {
4116 Expr::Binary(op, Box::new(a), Box::new(b), t)
4117 }
4118 fn widen(x: Expr, t: Ty) -> Expr {
4119 Expr::Resize(Box::new(x), t)
4120 }
4121 fn call(func: u32, args: Vec<Expr>, ty: Ty) -> Expr {
4122 Expr::Array(ArrayNode::Call { func, args, ty })
4123 }
4124 fn slot(o: usize) -> FieldSlot {
4125 FieldSlot {
4126 offset: o,
4127 words: 1,
4128 }
4129 }
4130
4131 fn twice() -> Func {
4133 Func {
4134 name: "twice".into(),
4135 slots: vec![u(8), u(8)],
4136 n_params: 1,
4137 ret: 1,
4138 body: vec![Stmt::Assign(
4139 1,
4140 bin(BinOp::Add, var(0, u(8)), var(0, u(8)), u(8)),
4141 )],
4142 }
4143 }
4144
4145 fn zero() -> Func {
4147 Func {
4148 name: "zero".into(),
4149 slots: vec![u(8), u(8)],
4150 n_params: 1,
4151 ret: 1,
4152 body: vec![Stmt::Assign(1, k(u(8), 0))],
4153 }
4154 }
4155
4156 fn setup(id: u32, n: usize, funcs: Vec<Func>, conjuncts: Vec<Expr>) -> u64 {
4159 let t = ClassTemplateId(id);
4160 register_rand_props(
4161 t,
4162 (0..n)
4163 .map(|i| RandProp {
4164 slot: slot(i),
4165 kind: RandKind::Rand,
4166 })
4167 .collect(),
4168 );
4169 register_program(
4170 t,
4171 Ok(Program {
4172 vars: (0..n)
4173 .map(|i| ProgVar {
4174 ty: u(8),
4175 slot: slot(i),
4176 home: Home::Instance,
4177 prop_index: Some(i),
4178 domain_conjunct: None,
4179 randc: false,
4180 window: None,
4181 })
4182 .collect(),
4183 conjuncts,
4184 funcs,
4185 ..Program::default()
4186 }),
4187 );
4188 let h = alloc(t, n);
4189 objects::set_prng_seed(h, 0xF00D_0001);
4190 h
4191 }
4192
4193 fn get(h: u64, off: usize) -> u64 {
4194 let mut b = [0u64];
4195 assert!(objects::field_get(h, slot(off), &mut b));
4196 b[0]
4197 }
4198
4199 #[test]
4203 fn a_return_value_follows_its_argument_which_is_solved_first() {
4204 let c = bin(
4206 BinOp::Eq,
4207 widen(var(1, u(8)), s32()),
4208 call(0, vec![widen(var(0, u(8)), u(10))], s32()),
4209 Ty::BIT,
4210 );
4211 let h = setup(0x7a00_0008, 2, vec![count_ones()], vec![c]);
4212 let mut by_len = [0u32; 9];
4213 for _ in 0..400 {
4214 assert_eq!(randomize(h, &[]), Outcome::Solved);
4215 let (v, len) = (get(h, 0), get(h, 1));
4216 assert_eq!(len, u64::from(v.count_ones()), "len == count_ones({v})");
4217 by_len[len as usize] += 1;
4218 }
4219 assert!(by_len[4] > 60, "len 4 is the likeliest: {by_len:?}");
4220 assert!(by_len[0] < 10, "len 0 needs v == 0: {by_len:?}");
4221 release(h);
4222 }
4223
4224 #[test]
4228 fn the_argument_is_drawn_uniformly_before_the_variable_it_bounds() {
4229 let le = bin(
4231 BinOp::Le,
4232 widen(var(0, u(8)), u(8)),
4233 call(0, vec![var(1, u(8))], u(8)),
4234 Ty::BIT,
4235 );
4236 let y = var(1, u(8));
4237 let inside = [2u64, 4, 8]
4238 .into_iter()
4239 .map(|v| bin(BinOp::Eq, y.clone(), k(u(8), v), Ty::BIT))
4240 .reduce(|a, b| bin(BinOp::LogOr, a, b, Ty::BIT))
4241 .unwrap();
4242 let h = setup(0x7a00_0009, 2, vec![twice()], vec![le, inside]);
4243 let mut hits = [0u32; 3];
4244 for _ in 0..300 {
4245 assert_eq!(randomize(h, &[]), Outcome::Solved);
4246 let (x, y) = (get(h, 0), get(h, 1));
4247 assert!(x <= 2 * y, "x={x} y={y}");
4248 hits[match y {
4249 2 => 0,
4250 4 => 1,
4251 8 => 2,
4252 other => panic!("y = {other}"),
4253 }] += 1;
4254 }
4255 assert!(
4256 hits.iter().all(|&n| n > 75),
4257 "each y about a third of the time: {hits:?}"
4258 );
4259 release(h);
4260 }
4261
4262 #[test]
4264 fn a_circular_implicit_order_is_a_loud_outcome() {
4265 let cs = vec![
4266 bin(
4267 BinOp::Eq,
4268 var(0, u(8)),
4269 call(0, vec![var(1, u(8))], u(8)),
4270 Ty::BIT,
4271 ),
4272 bin(
4273 BinOp::Eq,
4274 var(1, u(8)),
4275 call(0, vec![var(0, u(8))], u(8)),
4276 Ty::BIT,
4277 ),
4278 ];
4279 let h = setup(0x7a00_000a, 2, vec![twice()], cs);
4280 let seed = objects::prng_seed(h);
4281 assert_eq!(randomize(h, &[]), Outcome::ImplicitOrderCycle);
4282 assert_eq!(objects::prng_seed(h), seed, "a failed call leaves the seed");
4283 release(h);
4284 }
4285
4286 #[test]
4289 fn an_unsatisfiable_check_is_function_search_not_a_wrong_value() {
4290 let c = bin(
4291 BinOp::Eq,
4292 call(0, vec![var(0, u(8))], u(8)),
4293 k(u(8), 1),
4294 Ty::BIT,
4295 );
4296 let h = setup(0x7a00_000b, 1, vec![zero()], vec![c]);
4297 assert_eq!(randomize(h, &[]), Outcome::FunctionSearch);
4298 release(h);
4299 }
4300
4301 #[test]
4303 fn a_satisfiable_check_is_found_by_sampling() {
4304 let c = bin(
4306 BinOp::Eq,
4307 call(0, vec![var(0, u(8))], u(8)),
4308 k(u(8), 6),
4309 Ty::BIT,
4310 );
4311 let h = setup(0x7a00_000c, 1, vec![twice()], vec![c]);
4312 for _ in 0..10 {
4313 assert_eq!(randomize(h, &[]), Outcome::Solved);
4314 assert!(matches!(get(h, 0), 3 | 131));
4315 }
4316 release(h);
4317 }
4318}
4319
4320#[cfg(test)]
4322mod scope_tests {
4323 use super::tests::{bin, k, u, var};
4324 use super::*;
4325 use crate::constraints::ir::BinOp;
4326
4327 fn scope_var(offset: usize, width: u32) -> ProgVar {
4328 ProgVar {
4329 ty: u(width),
4330 slot: FieldSlot {
4331 offset,
4332 words: (2 * width as usize).div_ceil(64).max(1),
4333 },
4334 home: Home::Instance,
4335 prop_index: None,
4336 domain_conjunct: None,
4337 randc: false,
4338 window: None,
4339 }
4340 }
4341
4342 fn captured_state(width: u32) -> ProgState {
4343 ProgState {
4344 class_of: false,
4345 root_bit: 0,
4346 ty: u(width),
4347 slot: FieldSlot {
4348 offset: 0,
4349 words: 1,
4350 },
4351 home: Home::Instance,
4352 via: Vec::new(),
4353 window: None,
4354 }
4355 }
4356
4357 fn register(vars: Vec<ProgVar>, states: Vec<ProgState>, conjuncts: Vec<Expr>) -> u32 {
4358 let captures = states
4359 .iter()
4360 .enumerate()
4361 .map(|(i, s)| Capture {
4362 state: i as u32,
4363 width: s.ty.width,
4364 })
4365 .collect();
4366 register_spec(Spec {
4367 frag: Program {
4368 vars,
4369 states,
4370 conjuncts,
4371 ..Program::default()
4372 },
4373 captures,
4374 only: None,
4375 })
4376 }
4377
4378 fn lt(a: Expr, b: Expr) -> Expr {
4379 bin(BinOp::Lt, a, b, Ty::BIT)
4380 }
4381
4382 #[test]
4383 fn a_scope_randomize_solves_without_any_object_and_returns_the_values() {
4384 let id = register(
4386 vec![scope_var(0, 8), scope_var(1, 8)],
4387 Vec::new(),
4388 vec![
4389 lt(var(0, u(8)), var(1, u(8))),
4390 bin(BinOp::Le, var(1, u(8)), k(u(8), 10), Ty::BIT),
4391 ],
4392 );
4393 let mut distinct = std::collections::BTreeSet::new();
4394 for seed in 1..60u64 {
4395 let mut out = [0xffu64; 2];
4396 assert_eq!(randomize_scope(id, &[], &mut out, seed), Outcome::Solved);
4397 assert!(out[0] < out[1] && out[1] <= 10, "{out:?}");
4398 distinct.insert((out[0], out[1]));
4399 }
4400 assert!(
4401 distinct.len() > 10,
4402 "the draw follows the seed: {distinct:?}"
4403 );
4404 }
4405
4406 #[test]
4407 fn the_same_seed_draws_the_same_values() {
4408 let id = register(
4409 vec![scope_var(0, 16)],
4410 Vec::new(),
4411 vec![lt(var(0, u(16)), k(u(16), 1000))],
4412 );
4413 let (mut a, mut b) = ([0u64], [0u64]);
4414 assert_eq!(randomize_scope(id, &[], &mut a, 77), Outcome::Solved);
4415 assert_eq!(randomize_scope(id, &[], &mut b, 77), Outcome::Solved);
4416 assert_eq!(a, b);
4417 }
4418
4419 #[test]
4420 fn captured_locals_are_constants_of_the_solve() {
4421 let id = register(
4423 vec![scope_var(0, 8)],
4424 vec![captured_state(8)],
4425 vec![bin(
4426 BinOp::Eq,
4427 var(0, u(8)),
4428 bin(BinOp::Add, Expr::State(0, u(8)), k(u(8), 1), u(8)),
4429 Ty::BIT,
4430 )],
4431 );
4432 for s in [0u64, 41, 200] {
4433 let mut out = [0u64];
4434 assert_eq!(randomize_scope(id, &[s], &mut out, 5), Outcome::Solved);
4435 assert_eq!(out[0], s + 1);
4436 }
4437 }
4438
4439 #[test]
4440 fn an_unsatisfiable_scope_randomize_writes_nothing() {
4441 let id = register(
4442 vec![scope_var(0, 8)],
4443 Vec::new(),
4444 vec![
4445 bin(BinOp::Gt, var(0, u(8)), k(u(8), 5), Ty::BIT),
4446 lt(var(0, u(8)), k(u(8), 3)),
4447 ],
4448 );
4449 let mut out = [0x77u64];
4450 assert_eq!(
4451 randomize_scope(id, &[], &mut out, 1),
4452 Outcome::Unsatisfiable
4453 );
4454 assert_eq!(out, [0x77], "a failed randomize changes nothing (18.6.1)");
4455 }
4456
4457 #[test]
4458 fn with_no_variable_the_call_is_a_checker() {
4459 let id = register(
4461 Vec::new(),
4462 vec![captured_state(8), captured_state(8)],
4463 vec![lt(Expr::State(0, u(8)), Expr::State(1, u(8)))],
4464 );
4465 let mut none: [u64; 0] = [];
4466 assert_eq!(randomize_scope(id, &[3, 9], &mut none, 1), Outcome::Solved);
4467 assert_eq!(
4468 randomize_scope(id, &[9, 3], &mut none, 1),
4469 Outcome::Unsatisfiable
4470 );
4471 assert_eq!(
4472 randomize_scope(id, &[4, 4], &mut none, 1),
4473 Outcome::Unsatisfiable
4474 );
4475 }
4476
4477 #[test]
4478 fn an_unknown_bit_in_a_captured_local_is_an_error_not_a_value() {
4479 let id = register(
4480 Vec::new(),
4481 vec![captured_state(8)],
4482 vec![lt(Expr::State(0, u(8)), k(u(8), 9))],
4483 );
4484 let mut none: [u64; 0] = [];
4485 let x_bit = 1u64 << 8;
4487 assert_eq!(
4488 randomize_scope(id, &[x_bit], &mut none, 1),
4489 Outcome::StateUnknown
4490 );
4491 }
4492
4493 #[test]
4494 fn a_wide_variable_spans_words_and_keeps_its_pinned_bits() {
4495 let wide = scope_var(0, 70);
4497 assert_eq!(wide.slot.words, 3);
4498 let top = bin(
4499 BinOp::Eq,
4500 Expr::Slice(Box::new(var(0, u(70))), 64, u(6)),
4501 k(u(6), 0x2a),
4502 Ty::BIT,
4503 );
4504 let id = register(vec![wide], Vec::new(), vec![top]);
4505 let mut seen_low = std::collections::BTreeSet::new();
4506 for seed in 1..20u64 {
4507 let mut out = [0u64; 3];
4508 assert_eq!(randomize_scope(id, &[], &mut out, seed), Outcome::Solved);
4509 assert_eq!(out[1] & 0x3f, 0x2a, "{out:x?}");
4510 assert_eq!(out[2], 0, "no unknown bits: {out:x?}");
4511 seen_low.insert(out[0]);
4512 }
4513 assert!(seen_low.len() > 10, "the other bits are random");
4514 }
4515
4516 #[test]
4517 fn a_call_that_does_not_match_its_spec_fails_loudly() {
4518 let id = register(
4519 vec![scope_var(0, 8)],
4520 vec![captured_state(8)],
4521 vec![lt(var(0, u(8)), Expr::State(0, u(8)))],
4522 );
4523 let spec = spec(id).expect("registered");
4524 assert_eq!((spec.scope_words(), spec.capture_words()), (1, 1));
4525 let mut one = [0u64];
4526 let mut two = [0u64; 2];
4527 assert_eq!(randomize_scope(id, &[], &mut one, 1), Outcome::Failed);
4528 assert_eq!(randomize_scope(id, &[5], &mut two, 1), Outcome::Failed);
4529 assert_eq!(randomize_scope(0, &[], &mut one, 1), Outcome::Failed);
4530 assert_eq!(randomize_scope(u32::MAX, &[], &mut one, 1), Outcome::Failed);
4531 }
4532}