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 #[serde(default)]
290 pub nested: bool,
291 #[serde(default)]
296 pub template_of: Option<u32>,
297 #[serde(default)]
302 pub inner_fixed: u32,
303 #[serde(default)]
308 pub row_depth: u32,
309 #[serde(default)]
313 pub window: Option<Window>,
314}
315
316#[derive(Clone, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
318pub struct ProgRealVar {
319 pub kind: FloatKind,
321 pub slot: FieldSlot,
324 pub home: Home,
326 pub prop_index: Option<usize>,
328 pub window: Option<Window>,
330}
331
332#[derive(Clone, Copy, Debug, PartialEq, Eq)]
335#[repr(u64)]
336pub enum Outcome {
337 Unsatisfiable = 0,
340 Solved = 1,
342 DeadHandle = 2,
344 NoProgram = 3,
347 StateUnknown = 4,
349 TooLarge = 5,
352 Failed = 6,
354 StaticUnavailable = 7,
359 BadDistWeight = 8,
362 GuardError = 9,
365 NullHandle = 10,
368 RealDomain = 11,
370 ArraySizeTooLarge = 12,
373 ArrayIndexOutOfRange = 13,
376 ArraySizeSearch = 14,
379 FunctionFailed = 15,
382 ImplicitOrderCycle = 16,
385 FunctionSearch = 17,
388 GlobalUnsupported = 18,
392 SatTimeout = 19,
395 SatUnsupported = 20,
401}
402
403impl Outcome {
404 pub fn code(self) -> u64 {
406 self as u64
407 }
408
409 pub fn from_code(code: u64) -> Option<Outcome> {
411 [
412 Outcome::Unsatisfiable,
413 Outcome::Solved,
414 Outcome::DeadHandle,
415 Outcome::NoProgram,
416 Outcome::StateUnknown,
417 Outcome::TooLarge,
418 Outcome::Failed,
419 Outcome::StaticUnavailable,
420 Outcome::BadDistWeight,
421 Outcome::GuardError,
422 Outcome::NullHandle,
423 Outcome::RealDomain,
424 Outcome::ArraySizeTooLarge,
425 Outcome::ArrayIndexOutOfRange,
426 Outcome::ArraySizeSearch,
427 Outcome::FunctionFailed,
428 Outcome::ImplicitOrderCycle,
429 Outcome::FunctionSearch,
430 Outcome::GlobalUnsupported,
431 Outcome::SatTimeout,
432 Outcome::SatUnsupported,
433 ]
434 .into_iter()
435 .find(|o| o.code() == code)
436 }
437
438 pub fn error_message(self) -> Option<&'static str> {
442 match self {
443 Outcome::Unsatisfiable | Outcome::Solved => None,
444 Outcome::DeadHandle => Some("randomize() on a null or deleted object"),
445 Outcome::NoProgram => Some(
446 "randomize(): the constraints of this object's class could not be compiled \
447 (see the class's constraint blocks)",
448 ),
449 Outcome::StateUnknown => Some(
450 "randomize(): a constraint reads a variable with an X or Z bit; a 4-state \
451 value in a constraint is an error (IEEE 1800-2023 18.3)",
452 ),
453 Outcome::TooLarge => Some(
454 "randomize(): the constraint problem is too large for both the BDD solver \
455 (node budget exceeded) and its SAT fallback (clause limit exceeded)",
456 ),
457 Outcome::SatTimeout => Some(
458 "randomize(): the constraint problem is too large for the BDD solver and \
459 the SAT fallback did not find a solution within its time limit",
460 ),
461 Outcome::SatUnsupported => Some(
462 "randomize(): the constraint problem is too large for the BDD solver and \
463 uses a `dist` over an expression with more than 512 feasible assignments, a `dist` \
464 over a variable wider than 32 bits or with more than 1024 feasible values per \
465 range, or a `randc` variable wider than 10 bits, which the SAT fallback does \
466 not support",
467 ),
468 Outcome::Failed => Some("randomize(): internal solver failure on a malformed program"),
469 Outcome::StaticUnavailable => Some(
470 "randomize(): the object's class declares a static property used by its \
471 constraints or `rand`/`randc` variables that this call site cannot reach \
472 (it is declared in a class derived from the handle's static type); call \
473 randomize() through a handle of that class",
474 ),
475 Outcome::BadDistWeight => Some(
476 "randomize(): a `dist` weight is negative, or a weight or range bound \
477 divides by zero (IEEE 1800-2023 18.5.3)",
478 ),
479 Outcome::GuardError => Some(
480 "randomize(): the guard of an implication or `if` constraint evaluated to \
481 ERROR (a null object handle, or an X/Z value, read where no operand \
482 decides it; IEEE 1800-2023 18.5.12)",
483 ),
484 Outcome::NullHandle => Some(
485 "randomize(): a constraint reads a member through a null or deleted object \
486 handle (IEEE 1800-2023 18.5.12)",
487 ),
488 Outcome::RealDomain => Some(
489 "randomize(): a constraint on a real variable evaluates to NaN or divides by \
490 zero, so it has no well-defined set of solutions",
491 ),
492 Outcome::ArraySizeTooLarge => Some(
493 "randomize(): the constraints on the size of a dynamic array or queue can \
494 only be met by a size above 65536 elements, which is not supported",
495 ),
496 Outcome::ArrayIndexOutOfRange => Some(
497 "randomize(): a constraint reads an array element outside the array's \
498 bounds (guard it with an `if` on the size, IEEE 1800-2023 18.5.7.1)",
499 ),
500 Outcome::ArraySizeSearch => Some(
501 "randomize(): no array size tried satisfied both the size constraints and \
502 the element constraints (the bounded search over sizes gave up)",
503 ),
504 Outcome::FunctionFailed => Some(
505 "randomize(): a function called from a constraint failed (division by zero, \
506 a runaway loop or recursion that is too deep)",
507 ),
508 Outcome::ImplicitOrderCycle => Some(
509 "randomize(): the random arguments of function calls in the constraints \
510 impose a circular solve order (IEEE 1800-2023 18.5.11)",
511 ),
512 Outcome::FunctionSearch => Some(
513 "randomize(): no draw satisfied a constraint that compares the results of \
514 function calls over several random variables (the bounded search gave up)",
515 ),
516 Outcome::GlobalUnsupported => Some(
517 "randomize(): the object tree being randomized together (a `rand` object \
518 member, IEEE 1800-2023 18.5.8) has `real` random variables, or a `static` \
519 random variable that two objects of the tree share, which the joint solver \
520 does not combine yet",
521 ),
522 }
523 }
524}
525
526type ProgramTable = HashMap<ClassTemplateId, Arc<Result<Program, String>>>;
527
528fn table() -> &'static Mutex<ProgramTable> {
529 static T: OnceLock<Mutex<ProgramTable>> = OnceLock::new();
530 T.get_or_init(|| Mutex::new(HashMap::new()))
531}
532
533pub fn register_program(template: ClassTemplateId, program: Result<Program, String>) {
539 table()
540 .lock()
541 .unwrap_or_else(|e| e.into_inner())
542 .insert(template, Arc::new(program));
543}
544
545pub fn program(template: ClassTemplateId) -> Option<Arc<Result<Program, String>>> {
547 table()
548 .lock()
549 .unwrap_or_else(|e| e.into_inner())
550 .get(&template)
551 .cloned()
552}
553
554#[derive(serde::Serialize, serde::Deserialize)]
558struct Snapshot {
559 programs: Vec<(u32, Result<Program, String>)>,
561 specs: Vec<(u32, Spec)>,
563}
564
565pub fn snapshot(templates: &[ClassTemplateId]) -> Vec<u8> {
571 let mut ids: Vec<u32> = templates.iter().map(|t| t.0).collect();
572 ids.sort_unstable();
573 ids.dedup();
574 let programs: Vec<(u32, Result<Program, String>)> = ids
575 .into_iter()
576 .filter_map(|id| program(ClassTemplateId(id)).map(|p| (id, Result::clone(&p))))
577 .collect();
578 let specs: Vec<(u32, Spec)> = all_specs()
579 .into_iter()
580 .map(|(id, s)| (id, Spec::clone(&s)))
581 .collect();
582 if programs.is_empty() && specs.is_empty() {
583 return Vec::new();
584 }
585 postcard::to_allocvec(&Snapshot { programs, specs })
586 .expect("a constraint program is plain data and always serializes")
587}
588
589pub fn preload(bytes: &[u8]) -> Result<(), String> {
592 if bytes.is_empty() {
593 return Ok(());
594 }
595 let snap: Snapshot =
596 postcard::from_bytes(bytes).map_err(|e| format!("randomize section: {e}"))?;
597 for (id, p) in snap.programs {
598 register_program(ClassTemplateId(id), p);
599 }
600 for (id, s) in snap.specs {
601 register_spec_at(id, s);
602 }
603 Ok(())
604}
605
606pub(super) fn unpack(words: &[u64], width: u32) -> (BigUint, BigUint) {
608 let mut n = BigUint::zero();
609 for (i, w) in words.iter().enumerate() {
610 n |= BigUint::from(*w) << (64 * i);
611 }
612 let m = mask(width);
613 (&n & &m, (n >> width) & m)
614}
615
616fn pack(val: &BigUint, words: usize) -> Option<Vec<u64>> {
619 let mut d = val.to_u64_digits();
620 if d.len() > words {
621 return None;
622 }
623 d.resize(words, 0);
624 Some(d)
625}
626
627fn read(
630 h: u64,
631 slot: FieldSlot,
632 window: Option<Window>,
633 ty: Ty,
634) -> Result<Option<BigUint>, Outcome> {
635 let (val, unk) = read_unknown(h, slot, window, ty.width)?;
636 Ok(if unk.is_zero() { Some(val) } else { None })
637}
638
639fn read_unknown(
642 h: u64,
643 slot: FieldSlot,
644 window: Option<Window>,
645 width: u32,
646) -> Result<(BigUint, BigUint), Outcome> {
647 match window {
648 None => {
649 let mut buf = vec![0u64; slot.words];
650 if !objects::field_get(h, slot, &mut buf) {
651 return Err(Outcome::DeadHandle);
652 }
653 Ok(unpack(&buf, width))
654 }
655 Some(w) => {
656 let n = (w.width as usize).div_ceil(64);
657 let (mut val, mut unk) = (vec![0u64; n], vec![0u64; n]);
658 if !objects::field_get_window(h, slot, w, &mut val, &mut unk) {
659 return Err(Outcome::DeadHandle);
660 }
661 let m = mask(width);
662 let to_big = |ws: &[u64]| {
663 ws.iter().enumerate().fold(BigUint::zero(), |a, (i, x)| {
664 a | (BigUint::from(*x) << (64 * i))
665 })
666 };
667 Ok((to_big(&val) & &m, to_big(&unk) & &m))
668 }
669 }
670}
671
672enum Reading {
674 Value(Option<BigUint>),
676 NullHandle,
678}
679
680fn read_state(owner: u64, s: &ProgState) -> Result<Reading, Outcome> {
684 let (mut obj, mut slot) = (owner, s.slot);
685 for (i, hop) in s.via.iter().enumerate() {
686 let mut buf = vec![0u64; slot.words];
687 let bit = if i == 0 { s.root_bit } else { 0 };
688 if !objects::field_get(obj, slot, &mut buf) {
689 return if i == 0 {
690 Err(Outcome::DeadHandle)
691 } else {
692 Ok(Reading::NullHandle)
693 };
694 }
695 let w = (bit / 64) as usize;
699 let half = slot.words / 2;
700 let (Some(&v), Some(&u)) = (buf.get(w), buf.get(half + w)) else {
701 return Err(Outcome::Failed);
702 };
703 let (val, unk) = (BigUint::from(v), BigUint::from(u));
704 if !unk.is_zero() {
705 return Ok(Reading::Value(None));
706 }
707 match val.to_u64() {
708 Some(0) => return Ok(Reading::NullHandle),
709 Some(next) => obj = next,
710 None => return Err(Outcome::Failed),
711 }
712 slot = *hop;
713 }
714 if s.class_of {
715 let (val, unk) = match read_unknown(obj, slot, None, 64) {
716 Ok(r) => r,
717 Err(_) if s.via.is_empty() => return Err(Outcome::DeadHandle),
718 Err(_) => return Ok(Reading::NullHandle),
719 };
720 if !unk.is_zero() {
721 return Ok(Reading::Value(None));
722 }
723 return match val.to_u64() {
724 Some(0) => Ok(Reading::NullHandle),
725 Some(h) => Ok(match objects::dynamic_template(h) {
726 Some(t) => Reading::Value(Some(BigUint::from(t.0))),
727 None => Reading::NullHandle,
728 }),
729 None => Err(Outcome::Failed),
730 };
731 }
732 match read_unknown(obj, slot, s.window, s.ty.width) {
733 Ok((val, unk)) => Ok(Reading::Value(if unk.is_zero() { Some(val) } else { None })),
734 Err(_) if s.via.is_empty() => Err(Outcome::DeadHandle),
735 Err(_) => Ok(Reading::NullHandle),
736 }
737}
738
739pub(super) fn owner(home: Home, h: u64, statics: &[(u32, u64)]) -> Result<u64, Outcome> {
742 match home {
743 Home::Instance => Ok(h),
744 Home::Object(o) => Ok(o),
745 Home::Static(id) => statics
746 .iter()
747 .find(|(i, s)| *i == id && *s != 0)
748 .map(|&(_, s)| s)
749 .ok_or(Outcome::StaticUnavailable),
750 }
751}
752
753fn unreadable_outcome(id: u32, null_state: &[bool]) -> Outcome {
755 if null_state.get(id as usize).copied().unwrap_or(false) {
756 Outcome::NullHandle
757 } else {
758 Outcome::StateUnknown
759 }
760}
761
762fn outcome_of(e: SolveError, null_state: &[bool]) -> Outcome {
764 match e {
765 SolveError::Unsatisfiable => Outcome::Unsatisfiable,
766 SolveError::Build(BddBuildError::StateUnknown(id)) => unreadable_outcome(id, null_state),
767 SolveError::Build(BddBuildError::TooLarge { .. } | BddBuildError::CnfTooLarge { .. }) => {
768 Outcome::TooLarge
769 }
770 SolveError::SatTimeout => Outcome::SatTimeout,
771 SolveError::SatUnsupported { .. } => Outcome::SatUnsupported,
772 SolveError::Build(_)
773 | SolveError::BadOrder { .. }
774 | SolveError::BadPlan { .. }
775 | SolveError::SatModelRejected
776 | SolveError::SatSolverFailure { .. } => Outcome::Failed,
777 }
778}
779
780fn outcome_of_dist(e: DistError, null_state: &[bool]) -> Outcome {
782 match e {
783 DistError::Eval(ConstraintEvalError::StateUnknown(id)) => {
784 unreadable_outcome(id, null_state)
785 }
786 DistError::Eval(ConstraintEvalError::DivideByZero) | DistError::NegativeWeight => {
787 Outcome::BadDistWeight
788 }
789 DistError::Eval(
790 ConstraintEvalError::UnboundVar(_)
791 | ConstraintEvalError::UnboundState(_)
792 | ConstraintEvalError::Unexpanded,
793 ) => Outcome::Failed,
794 }
795}
796
797fn outcome_of_real(e: RealError, null_state: &[bool]) -> Outcome {
799 match e {
800 RealError::Unsatisfiable => Outcome::Unsatisfiable,
801 RealError::GuardError => Outcome::GuardError,
802 RealError::StateUnreadable(id) => unreadable_outcome(id, null_state),
803 RealError::NonFinite(_) => Outcome::RealDomain,
804 RealError::BadWeight => Outcome::BadDistWeight,
805 RealError::Shape(_) => Outcome::Failed,
806 }
807}
808
809struct Write {
811 owner: u64,
812 slot: FieldSlot,
813 window: Option<Window>,
814 words: Vec<u64>,
817}
818
819impl Write {
820 fn new(owner: u64, slot: FieldSlot, window: Option<Window>, val: &BigUint) -> Option<Write> {
823 let n = match window {
824 None => slot.words,
825 Some(w) => (w.width as usize).div_ceil(64),
826 };
827 Some(Write {
828 owner,
829 slot,
830 window,
831 words: pack(val, n)?,
832 })
833 }
834
835 fn apply(&self) -> bool {
837 match self.window {
838 None => objects::field_set(self.owner, self.slot, &self.words),
839 Some(w) => objects::field_set_window(self.owner, self.slot, w, &self.words),
840 }
841 }
842}
843
844#[allow(clippy::too_many_arguments)]
849fn run_real(
850 program: &Program,
851 h: u64,
852 rand_mode: &[bool],
853 statics: &[(u32, u64)],
854 states: &[Option<BigUint>],
855 null_state: &[bool],
856 ints: Option<(&[Option<u32>], &[BigUint])>,
857 seed: &mut u64,
858) -> Result<Vec<Write>, Outcome> {
859 if program.real_vars.is_empty() {
860 return Ok(Vec::new());
861 }
862 if program.real_vars.len() != program.real.kinds.len() {
863 return Err(Outcome::Failed);
864 }
865 let mut owners = Vec::with_capacity(program.real_vars.len());
866 let mut known = Vec::with_capacity(program.real_vars.len());
867 for v in &program.real_vars {
868 let o = owner(v.home, h, statics)?;
869 let active = match v.prop_index {
870 Some(i) => rand_mode.get(i).copied().ok_or(Outcome::Failed)?,
871 None => true,
872 };
873 known.push(if active {
874 None
875 } else {
876 match read(o, v.slot, v.window, Ty::unsigned(v.kind.width()))? {
877 Some(bits) => Some(v.kind.from_bits(&bits)),
878 None => return Err(Outcome::StateUnknown),
879 }
880 });
881 owners.push(o);
882 }
883 let (lowered, ext);
888 let (problem, states) = match ints {
889 Some((var_new, values)) if program.real.reads_int_vars() => {
890 let mut e = states.to_vec();
891 let mut ix = Vec::with_capacity(var_new.len());
892 let mut inactive = program.states.len();
893 for d in var_new {
894 match d {
895 Some(d) => {
896 ix.push(e.len() as u32);
897 e.push(Some(
898 values.get(*d as usize).ok_or(Outcome::Failed)?.clone(),
899 ));
900 }
901 None => {
902 ix.push(inactive as u32);
903 inactive += 1;
904 }
905 }
906 }
907 lowered = program
908 .real
909 .with_int_vars(|v, t| Expr::State(ix[v as usize], t));
910 ext = e;
911 (&lowered, &ext[..])
912 }
913 _ => (&program.real, states),
914 };
915 let solved = real_domain::solve(problem, &known, states, seed)
916 .map_err(|e| outcome_of_real(e, null_state))?;
917 let mut writes = Vec::new();
918 for ((v, o), x) in program.real_vars.iter().zip(owners).zip(solved) {
919 let Some(x) = x else { continue };
920 match Write::new(o, v.slot, v.window, &BigUint::from(v.kind.to_bits(x))) {
921 Some(w) => writes.push(w),
922 None => return Err(Outcome::Failed),
923 }
924 }
925 Ok(writes)
926}
927
928struct Call<'a> {
931 program: &'a Program,
932 h: u64,
933 rand_mode: &'a [bool],
934 statics: &'a [(u32, u64)],
935 var_owner: Vec<u64>,
936 var_new: Vec<Option<u32>>,
938 var_tys: Vec<Ty>,
940 active_ids: Vec<usize>,
942 arrays: Vec<array::ArrayState>,
944 array_owner: Vec<u64>,
945 states: Vec<Option<BigUint>>,
947 null_state: Vec<bool>,
949 real_unsat: std::cell::Cell<bool>,
952}
953
954struct Solution {
956 values: Vec<BigUint>,
958 randc_keys: Vec<(u64, usize)>,
959 randc_work: Vec<RandcState>,
960 real_writes: Vec<Write>,
961}
962
963fn read_array(a: &ProgArray, owner: u64, active: bool) -> Result<array::ArrayState, Outcome> {
965 if let Some(outer) = a.template_of {
966 return Ok(array::ArrayState {
968 elem_ty: a.elem_ty,
969 active: false,
970 cur: Vec::new(),
971 domain: None,
972 rows: None,
973 template_of: Some(outer),
974 });
975 }
976 let (handle, unk) = read_unknown(owner, a.slot, a.window, 64)?;
977 if !unk.is_zero() {
978 return Err(Outcome::Failed);
979 }
980 let handle = handle.to_u64().ok_or(Outcome::Failed)?;
981 let leaves = |h: u64| -> Vec<Option<BigUint>> {
982 collections::elements(h)
983 .iter()
984 .map(|words| {
985 let (val, unk) = unpack(words, a.elem_ty.width);
986 if unk.is_zero() { Some(val) } else { None }
987 })
988 .collect()
989 };
990 if a.nested {
991 return read_rows(a, handle, a.row_depth.max(1), active, &leaves);
992 }
993 Ok(array::ArrayState::flat(a.elem_ty, active, leaves(handle))
994 .with_domain(a.elem_domain.clone()))
995}
996
997fn read_rows(
1002 a: &ProgArray,
1003 handle: u64,
1004 levels: u32,
1005 active: bool,
1006 leaves: &dyn Fn(u64) -> Vec<Option<BigUint>>,
1007) -> Result<array::ArrayState, Outcome> {
1008 let mut cur_rows = Vec::new();
1009 let mut deeper = Vec::new();
1010 for words in collections::elements(handle) {
1011 if levels == 1 && a.inner_fixed > 0 {
1012 let w = a.elem_ty.width;
1015 let (val, unk) = unpack(&words, w * a.inner_fixed);
1016 let m = mask(w);
1017 cur_rows.push(
1018 (0..a.inner_fixed)
1019 .map(|e| {
1020 let sh = u64::from(e * w);
1021 ((&unk >> sh) & &m).is_zero().then(|| (&val >> sh) & &m)
1022 })
1023 .collect(),
1024 );
1025 continue;
1026 }
1027 let (row, unk) = unpack(&words, 64);
1028 if !unk.is_zero() {
1029 return Err(Outcome::Failed);
1030 }
1031 let row = row.to_u64().ok_or(Outcome::Failed)?;
1032 if levels == 1 {
1033 cur_rows.push(leaves(row));
1034 } else {
1035 deeper.push(read_rows(a, row, levels - 1, active, leaves)?);
1036 }
1037 }
1038 let n = cur_rows.len().max(deeper.len());
1039 Ok(array::ArrayState {
1040 elem_ty: a.elem_ty,
1041 active,
1042 cur: vec![Some(BigUint::from(0u8)); n],
1043 domain: a.elem_domain.clone(),
1044 rows: Some(array::RowsState::new(levels, cur_rows, deeper)),
1045 template_of: None,
1046 })
1047}
1048
1049fn outcome_of_func(e: FuncError, null_state: &[bool]) -> Outcome {
1051 match e {
1052 FuncError::Eval(ConstraintEvalError::StateUnknown(id)) => {
1053 unreadable_outcome(id, null_state)
1054 }
1055 FuncError::Eval(ConstraintEvalError::DivideByZero)
1056 | FuncError::TooDeep { .. }
1057 | FuncError::StepLimit { .. } => Outcome::FunctionFailed,
1058 FuncError::Eval(
1059 ConstraintEvalError::UnboundVar(_)
1060 | ConstraintEvalError::UnboundState(_)
1061 | ConstraintEvalError::Unexpanded,
1062 )
1063 | FuncError::Malformed(_) => Outcome::Failed,
1064 }
1065}
1066
1067fn outcome_of_expand(e: array::ExpandError, null_state: &[bool]) -> Outcome {
1069 match e {
1070 array::ExpandError::IndexOutOfRange { .. }
1071 | array::ExpandError::LeafIndexOutOfRange { .. } => Outcome::ArrayIndexOutOfRange,
1072 array::ExpandError::GuardError => Outcome::GuardError,
1073 array::ExpandError::StateUnknown(id) => unreadable_outcome(id, null_state),
1074 array::ExpandError::Func(e) => outcome_of_func(e, null_state),
1075 array::ExpandError::NonConstantIndex
1076 | array::ExpandError::DistRandomBound
1077 | array::ExpandError::Malformed(_) => Outcome::Failed,
1078 }
1079}
1080
1081fn outcome_of_size(e: array::SizeError, null_state: &[bool]) -> Outcome {
1083 match e {
1084 array::SizeError::Unsatisfiable => Outcome::Unsatisfiable,
1085 array::SizeError::TooLarge => Outcome::ArraySizeTooLarge,
1086 array::SizeError::GuardError => Outcome::GuardError,
1087 array::SizeError::Solve(e) => outcome_of(e, null_state),
1088 }
1089}
1090
1091const MAX_CHECK_TRIES: usize = 4096;
1094
1095fn checks_hold(
1098 checks: &[Expr],
1099 drawn: &[BigUint],
1100 funcs: &[Func],
1101 states: &[Option<BigUint>],
1102 null_state: &[bool],
1103) -> Result<bool, Outcome> {
1104 let asg = Assignment {
1105 vars: drawn.to_vec(),
1106 states: states.to_vec(),
1107 };
1108 for c in checks {
1109 let c = c.substitute_vars(&mut |v, t| match drawn.get(v as usize) {
1110 Some(val) => Expr::Const(t, val.clone()),
1111 None => Expr::Var(v, t),
1112 });
1113 let c = func::fold_ground_calls(&c, funcs, states)
1114 .map_err(|e| outcome_of_func(e, null_state))?;
1115 let v = eval(&c, &asg).map_err(|e| outcome_of_func(FuncError::Eval(e), null_state))?;
1116 if v.is_zero() {
1117 return Ok(false);
1118 }
1119 }
1120 Ok(true)
1121}
1122
1123fn solve_expanded(
1135 call: &Call<'_>,
1136 x: &array::Expanded,
1137 seed: &mut u64,
1138) -> Result<Solution, Outcome> {
1139 let joint = call.program.real.reads_int_vars();
1140 let tries = if joint { REAL_JOINT_TRIES } else { 1 };
1141 for _ in 1..tries {
1142 call.real_unsat.set(false);
1143 match solve_expanded_once(call, x, seed) {
1144 Err(Outcome::Unsatisfiable) if call.real_unsat.get() => {}
1145 r => return r,
1146 }
1147 }
1148 solve_expanded_once(call, x, seed)
1149}
1150
1151const REAL_JOINT_TRIES: usize = 4096;
1153
1154fn solve_expanded_once(
1155 call: &Call<'_>,
1156 x: &array::Expanded,
1157 seed: &mut u64,
1158) -> Result<Solution, Outcome> {
1159 let program = call.program;
1160 let n_active = call.var_tys.len();
1161 let n_real = n_active + x.elem_vars.len();
1162 let mut states = call.states.clone();
1163 states.extend(x.extra_states.iter().cloned());
1164 let mut null_state = call.null_state.clone();
1165 null_state.resize(states.len(), false);
1166 let mut var_tys = call.var_tys.clone();
1167 var_tys.extend(x.elem_tys.iter().copied());
1168
1169 let conjuncts: Vec<Expr> = x
1172 .conjuncts
1173 .iter()
1174 .map(|c| {
1175 c.substitute_vars(&mut |v, t| {
1176 if v >= SELECTOR_BASE {
1177 Expr::Var(n_real as u32 + (v - SELECTOR_BASE), t)
1178 } else {
1179 Expr::Var(v, t)
1180 }
1181 })
1182 })
1183 .collect();
1184 let assignment = Assignment {
1185 vars: Vec::new(),
1186 states: states.clone(),
1187 };
1188
1189 let has_calls = conjuncts.iter().any(func::has_call);
1194 let stage_of: Vec<u32> = if has_calls {
1195 func_order::stages(n_real, &conjuncts).map_err(|_| Outcome::ImplicitOrderCycle)?
1196 } else {
1197 vec![0; n_real]
1198 };
1199 let n_stages = stage_of.iter().max().map_or(1, |m| *m as usize + 1);
1200 let mut conj_stage: Vec<usize> = Vec::with_capacity(conjuncts.len());
1203 let n_prog_dists = program.dists.len();
1207 let all_dists: Vec<&Dist> = program.dists.iter().chain(&x.extra_dists).collect();
1208 let mut dist_stage: Vec<usize> = vec![0; all_dists.len()];
1209 for c in &conjuncts {
1210 let mut s = 0usize;
1211 c.for_each_var(&mut |v, _| {
1212 if let Some(&st) = stage_of.get(v as usize) {
1213 s = s.max(st as usize);
1214 }
1215 });
1216 c.for_each_var(&mut |v, _| {
1217 if let Some(j) = (v as usize).checked_sub(n_real)
1218 && let Some(d) = dist_stage.get_mut(j)
1219 {
1220 *d = s;
1221 }
1222 });
1223 conj_stage.push(s);
1224 }
1225
1226 let mut edges: Vec<(u32, u32)> = Vec::with_capacity(program.before.len());
1228 for &(a, b) in &program.before {
1229 let (Some(&na), Some(&nb)) = (call.var_new.get(a as usize), call.var_new.get(b as usize))
1230 else {
1231 return Err(Outcome::Failed);
1232 };
1233 if let (Some(na), Some(nb)) = (na, nb) {
1234 edges.push((na, nb));
1235 }
1236 }
1237
1238 let mut randc_dense: Vec<u32> = Vec::new();
1242 let mut randc_keys: Vec<(u64, usize)> = Vec::new();
1243 let mut randc_work: Vec<RandcState> = Vec::new();
1244 for (dense, &i) in call.active_ids.iter().enumerate() {
1245 let v = &program.vars[i];
1246 if !v.randc {
1247 continue;
1248 }
1249 let Some(st) = objects::randc_state(call.var_owner[i], v.randc_key()) else {
1250 return Err(Outcome::DeadHandle);
1251 };
1252 randc_dense.push(dense as u32);
1253 randc_keys.push((call.var_owner[i], v.randc_key()));
1254 randc_work.push(st);
1255 }
1256 let mut dist_weights = Vec::with_capacity(all_dists.len());
1258 for d in &all_dists {
1259 if d.subject.is_some() {
1260 dist_weights.push(Vec::new());
1261 } else {
1262 match d.weights(&states) {
1263 Ok(w) => dist_weights.push(w),
1264 Err(e) => return Err(outcome_of_dist(e, &null_state)),
1265 }
1266 }
1267 var_tys.push(d.selector_ty());
1268 }
1269
1270 let mut values: Vec<BigUint> = vec![BigUint::zero(); n_real];
1271 let mut known: Vec<Option<BigUint>> = vec![None; n_real];
1272 for stage in 0..n_stages {
1273 let in_stage = |v: u32| {
1274 stage_of
1275 .get(v as usize)
1276 .is_some_and(|s| *s as usize == stage)
1277 };
1278 let mut plain: Vec<Expr> = Vec::new();
1283 let mut checks: Vec<Expr> = Vec::new();
1284 for (c, _) in conjuncts
1285 .iter()
1286 .zip(&conj_stage)
1287 .filter(|(_, s)| **s == stage)
1288 {
1289 let mut c = c.clone();
1290 if has_calls {
1291 if stage > 0 {
1292 c = c.substitute_vars(&mut |v, t| match known.get(v as usize) {
1293 Some(Some(val)) => Expr::Const(t, val.clone()),
1294 _ => Expr::Var(v, t),
1295 });
1296 }
1297 c = func::fold_ground_calls(&c, &program.funcs, &states)
1298 .map_err(|e| outcome_of_func(e, &null_state))?;
1299 }
1300 if func::has_call(&c) {
1301 checks.push(c);
1302 continue;
1303 }
1304 match guard::resolve(&c, &assignment) {
1307 Ok(r) => plain.push(r),
1308 Err(_) => return Err(Outcome::GuardError),
1309 }
1310 }
1311
1312 let stage_edges: Vec<(u32, u32)> = edges
1313 .iter()
1314 .copied()
1315 .filter(|&(a, b)| in_stage(a) && in_stage(b))
1316 .collect();
1317 let Ok(groups) = order::groups(n_real, &stage_edges) else {
1318 return Err(Outcome::Failed);
1319 };
1320 let stage_randc: Vec<usize> = (0..randc_dense.len())
1322 .filter(|&k| in_stage(randc_dense[k]))
1323 .collect();
1324 let stage_randc_dense: Vec<u32> = stage_randc.iter().map(|&k| randc_dense[k]).collect();
1325 let mut plan_groups: Vec<Group> = groups
1329 .into_iter()
1330 .map(|g| Group {
1331 vars: g
1332 .into_iter()
1333 .filter(|v| in_stage(*v) && !randc_dense.contains(v))
1334 .collect(),
1335 weighted: Vec::new(),
1336 })
1337 .collect();
1338 let stage_dists: Vec<usize> = (0..all_dists.len())
1339 .filter(|&j| dist_stage[j] == stage && all_dists[j].subject.is_none())
1340 .collect();
1341 if !stage_dists.is_empty() && plan_groups.is_empty() {
1342 plan_groups.push(Group::default());
1343 }
1344 for &j in &stage_dists {
1345 let d = all_dists[j];
1346 let dense = |v: u32| {
1348 if j >= n_prog_dists {
1349 Some(v)
1350 } else {
1351 call.var_new.get(v as usize).copied().flatten()
1352 }
1353 };
1354 let subject: Vec<u32> = d
1355 .vars
1356 .iter()
1357 .filter_map(|&v| dense(v))
1358 .filter(|&v| in_stage(v))
1359 .collect();
1360 let gi = plan_groups
1361 .iter()
1362 .position(|g| g.vars.iter().any(|v| subject.contains(v)))
1363 .unwrap_or(plan_groups.len() - 1);
1364 let measure = match (
1365 d.plain_var.and_then(dense).filter(|&v| in_stage(v)),
1366 subject.is_empty(),
1367 ) {
1368 (Some(s), _) => Measure::Distinct(s),
1369 (None, false) => Measure::Models,
1370 (None, true) => Measure::Flat,
1371 };
1372 plan_groups[gi].weighted.push(Weighted {
1373 var: (n_real + j) as u32,
1374 weights: dist_weights[j].clone(),
1375 measure,
1376 });
1377 }
1378 let plan = Plan {
1379 groups: plan_groups
1380 .into_iter()
1381 .filter(|g| !g.vars.is_empty() || !g.weighted.is_empty())
1382 .collect(),
1383 randc: stage_randc_dense,
1384 };
1385
1386 let solver =
1387 Engine::new(&var_tys, &states, &plain).map_err(|e| outcome_of(e, &null_state))?;
1388 let mut tries = 0;
1389 let drawn = loop {
1390 let mut work: Vec<RandcState> =
1391 stage_randc.iter().map(|&k| randc_work[k].clone()).collect();
1392 let drawn = solver
1393 .sample_plan(&plan, &mut work, seed)
1394 .map_err(|e| outcome_of(e, &null_state))?;
1395 if checks_hold(&checks, &drawn, &program.funcs, &states, &null_state)? {
1396 for (&k, st) in stage_randc.iter().zip(work) {
1397 randc_work[k] = st;
1398 }
1399 break drawn;
1400 }
1401 tries += 1;
1402 if tries >= MAX_CHECK_TRIES {
1403 return Err(Outcome::FunctionSearch);
1404 }
1405 };
1406 for v in 0..n_real {
1407 if in_stage(v as u32) {
1408 values[v] = drawn[v].clone();
1409 known[v] = Some(drawn[v].clone());
1410 }
1411 }
1412 }
1413
1414 let real_writes = run_real(
1417 program,
1418 call.h,
1419 call.rand_mode,
1420 call.statics,
1421 &states,
1422 &null_state,
1423 Some((&call.var_new, &values)),
1424 seed,
1425 )
1426 .inspect_err(|o| call.real_unsat.set(*o == Outcome::Unsatisfiable))?;
1427 Ok(Solution {
1428 values,
1429 randc_keys,
1430 randc_work,
1431 real_writes,
1432 })
1433}
1434
1435type Solved = (Solution, Vec<(u32, usize)>, Vec<usize>, Vec<(u32, u32)>);
1438
1439fn solve_all(call: &Call<'_>, conjuncts: &[Expr], seed: &mut u64) -> Result<Solved, Outcome> {
1444 if call.arrays.iter().any(|a| a.rows.is_some()) {
1445 return solve_all_rows(call, conjuncts, seed);
1446 }
1447 let n_active = call.var_tys.len() as u32;
1448 let mut blocked: Vec<Vec<usize>> = Vec::new();
1449 loop {
1450 let chosen = array::solve_sizes(
1451 conjuncts,
1452 &call.arrays,
1453 &call.var_tys,
1454 &call.states,
1455 &blocked,
1456 seed,
1457 )
1458 .map_err(|e| outcome_of_size(e, &call.null_state))?;
1459 let x = array::expand_with(
1460 conjuncts,
1461 &call.arrays,
1462 &chosen.sizes,
1463 n_active,
1464 &call.states,
1465 &call.program.funcs,
1466 &call.program.dists,
1467 )
1468 .map_err(|e| outcome_of_expand(e, &call.null_state))?;
1469 match solve_expanded(call, &x, seed) {
1470 Ok(s) => return Ok((s, x.elem_vars, chosen.sizes, Vec::new())),
1471 Err(Outcome::Unsatisfiable) if !chosen.sized.is_empty() => {
1474 blocked.push(chosen.tuple);
1475 if blocked.len() >= array::MAX_SIZE_RETRIES {
1476 return Err(Outcome::ArraySizeSearch);
1477 }
1478 }
1479 Err(o) => return Err(o),
1480 }
1481 }
1482}
1483
1484fn solve_all_rows(call: &Call<'_>, conjuncts: &[Expr], seed: &mut u64) -> Result<Solved, Outcome> {
1490 let mut tries = 0usize;
1491 rows_stage(call, conjuncts, &call.arrays, &[], seed, &mut tries)
1492}
1493
1494fn rows_stage(
1502 call: &Call<'_>,
1503 conjuncts: &[Expr],
1504 arrays: &[array::ArrayState],
1505 bases: &[(u32, u32)],
1506 seed: &mut u64,
1507 tries: &mut usize,
1508) -> Result<Solved, Outcome> {
1509 let n_active = call.var_tys.len() as u32;
1510 let mut blocked: Vec<Vec<usize>> = Vec::new();
1511 let unbound = arrays
1512 .iter()
1513 .any(|a| a.rows.as_ref().is_some_and(|r| !r.lowered));
1514 loop {
1515 *tries += 1;
1516 if *tries >= array::MAX_SIZE_RETRIES {
1517 return Err(Outcome::ArraySizeSearch);
1518 }
1519 let sz = array::solve_sizes(
1520 conjuncts,
1521 arrays,
1522 &call.var_tys,
1523 &call.states,
1524 &blocked,
1525 seed,
1526 )
1527 .map_err(|e| outcome_of_size(e, &call.null_state))?;
1529 let res = if unbound {
1530 let low = array::lower_rows(conjuncts, arrays, &sz.sizes, &call.states)
1531 .map_err(|e| outcome_of_expand(e, &call.null_state))?;
1532 let mut all = bases.to_vec();
1533 all.extend_from_slice(&low.bases);
1534 rows_stage(call, &low.conjuncts, &low.arrays, &all, seed, tries)
1535 } else {
1536 let x = array::expand_with(
1537 conjuncts,
1538 arrays,
1539 &sz.sizes,
1540 n_active,
1541 &call.states,
1542 &call.program.funcs,
1543 &call.program.dists,
1544 )
1545 .map_err(|e| outcome_of_expand(e, &call.null_state))?;
1546 solve_expanded(call, &x, seed)
1547 .map(|s| (s, x.elem_vars, sz.sizes.clone(), bases.to_vec()))
1548 };
1549 match res {
1550 Err(Outcome::Unsatisfiable) if !sz.sized.is_empty() => blocked.push(sz.tuple),
1551 other => return other,
1552 }
1553 }
1554}
1555
1556fn run(program: &Program, h: u64, rand_mode: &[bool], statics: &[(u32, u64)]) -> Outcome {
1561 let mut off: Vec<&ProgBlock> = Vec::new();
1566 for b in &program.blocks {
1567 let Ok(o) = owner(b.home, h, statics) else {
1568 continue;
1569 };
1570 match objects::constraint_mode_of(o, b.ordinal) {
1571 Some(true) => {}
1572 Some(false) => off.push(b),
1573 None => return Outcome::DeadHandle,
1574 }
1575 }
1576 if off.is_empty() {
1577 return run_blocks_on(program, h, rand_mode, statics);
1578 }
1579 match reduce::without_blocks(program, &off) {
1580 Ok(p) => run_blocks_on(&p, h, rand_mode, statics),
1581 Err(o) => o,
1582 }
1583}
1584
1585fn run_blocks_on(program: &Program, h: u64, rand_mode: &[bool], statics: &[(u32, u64)]) -> Outcome {
1587 let Some(seed0) = objects::prng_seed(h) else {
1588 return Outcome::DeadHandle;
1589 };
1590 let mut var_owner = Vec::with_capacity(program.vars.len());
1591 for v in &program.vars {
1592 match owner(v.home, h, statics) {
1593 Ok(o) => var_owner.push(o),
1594 Err(o) => return o,
1595 }
1596 }
1597 let mut state_owner = Vec::with_capacity(program.states.len());
1598 for s in &program.states {
1599 match owner(s.home, h, statics) {
1600 Ok(o) => state_owner.push(o),
1601 Err(o) => return o,
1602 }
1603 }
1604 let mut array_owner = Vec::with_capacity(program.arrays.len());
1605 for a in &program.arrays {
1606 match owner(a.home, h, statics) {
1607 Ok(o) => array_owner.push(o),
1608 Err(o) => return o,
1609 }
1610 }
1611 let active = |v: &ProgVar| match v.prop_index {
1612 Some(i) => rand_mode.get(i).copied(),
1613 None => Some(true),
1614 };
1615 let mut var_new: Vec<Option<u32>> = Vec::with_capacity(program.vars.len());
1618 let mut var_tys: Vec<Ty> = Vec::new();
1619 let mut active_ids: Vec<usize> = Vec::new();
1620 let mut inactive_state: Vec<Option<u32>> = Vec::with_capacity(program.vars.len());
1621 let mut inactive_ids: Vec<usize> = Vec::new();
1622 for (i, v) in program.vars.iter().enumerate() {
1623 match active(v) {
1624 None => return Outcome::Failed,
1625 Some(true) => {
1626 var_new.push(Some(var_tys.len() as u32));
1627 inactive_state.push(None);
1628 var_tys.push(v.ty);
1629 active_ids.push(i);
1630 }
1631 Some(false) => {
1632 var_new.push(None);
1633 inactive_state.push(Some((program.states.len() + inactive_ids.len()) as u32));
1634 inactive_ids.push(i);
1635 }
1636 }
1637 }
1638
1639 let mut states: Vec<Option<BigUint>> = Vec::with_capacity(program.states.len());
1640 let mut null_state: Vec<bool> = Vec::with_capacity(program.states.len());
1641 for (s, &o) in program.states.iter().zip(&state_owner) {
1642 match read_state(o, s) {
1643 Ok(Reading::Value(v)) => {
1644 states.push(v);
1645 null_state.push(false);
1646 }
1647 Ok(Reading::NullHandle) => {
1648 states.push(None);
1649 null_state.push(true);
1650 }
1651 Err(o) => return o,
1652 }
1653 }
1654 for &i in &inactive_ids {
1655 let v = &program.vars[i];
1656 match read(var_owner[i], v.slot, v.window, v.ty) {
1657 Ok(x) => states.push(x),
1658 Err(o) => return o,
1659 }
1660 null_state.push(false);
1661 }
1662 let mut arrays = Vec::with_capacity(program.arrays.len());
1663 for (a, &o) in program.arrays.iter().zip(&array_owner) {
1664 let on = a.rand
1665 && match a.prop_index {
1666 Some(i) => match rand_mode.get(i) {
1667 Some(&b) => b,
1668 None => return Outcome::Failed,
1669 },
1670 None => true,
1671 };
1672 match read_array(a, o, on) {
1673 Ok(s) => arrays.push(s),
1674 Err(o) => return o,
1675 }
1676 }
1677 for t in 0..arrays.len() {
1682 let Some(mut o) = arrays[t].template_of else {
1683 continue;
1684 };
1685 let mut level = 0;
1686 while let Some(up) = arrays.get(o as usize).and_then(|a| a.template_of) {
1687 o = up;
1688 level += 1;
1689 }
1690 if let Some(rows) = arrays.get_mut(o as usize).and_then(|a| a.rows.as_mut()) {
1691 rows.set_template(level, t as u32);
1692 }
1693 }
1694
1695 let mut dropped = vec![false; program.conjuncts.len()];
1698 for &i in &inactive_ids {
1699 if let Some(d) = program.vars[i].domain_conjunct {
1700 match dropped.get_mut(d) {
1701 Some(slot) => *slot = true,
1702 None => return Outcome::Failed,
1703 }
1704 }
1705 }
1706 let call = Call {
1707 program,
1708 h,
1709 rand_mode,
1710 statics,
1711 var_owner,
1712 var_new,
1713 var_tys,
1714 active_ids,
1715 arrays,
1716 array_owner,
1717 states,
1718 null_state,
1719 real_unsat: std::cell::Cell::new(false),
1720 };
1721
1722 let has_softs = !program.softs.is_empty();
1730 let prepare = |discard: &[bool]| -> Result<Vec<Expr>, Outcome> {
1731 let mut out = Vec::with_capacity(program.conjuncts.len());
1732 for (i, c) in program.conjuncts.iter().enumerate() {
1733 if dropped[i] {
1734 continue;
1735 }
1736 let resolved;
1737 let c = if has_softs {
1738 resolved = priority::resolve_softs(c, discard).map_err(|_| Outcome::Failed)?;
1739 &resolved
1740 } else {
1741 c
1742 };
1743 let c = c.substitute_vars(&mut |v, t| {
1744 if v >= SELECTOR_BASE {
1745 return Expr::Var(v, t);
1746 }
1747 match (call.var_new[v as usize], inactive_state[v as usize]) {
1748 (Some(n), _) => Expr::Var(n, t),
1749 (None, Some(s)) => Expr::State(s, t),
1750 (None, None) => unreachable!("every variable is active or inactive"),
1751 }
1752 });
1753 out.push(if program.funcs.is_empty() {
1754 c
1755 } else {
1756 func::fold_ground_calls(&c, &program.funcs, &call.states)
1757 .map_err(|e| outcome_of_func(e, &call.null_state))?
1758 });
1759 }
1760 Ok(out)
1761 };
1762
1763 let mut seed = seed0;
1764 let solved = if has_softs {
1765 priority::solve_relaxing(
1768 &program.softs,
1769 &mut seed,
1770 |discard, seed| solve_all(&call, &prepare(discard)?, seed),
1771 |o| matches!(o, Outcome::Unsatisfiable | Outcome::ArraySizeSearch),
1772 )
1773 } else {
1774 prepare(&[]).and_then(|c| solve_all(&call, &c, &mut seed))
1775 };
1776 let (solution, elem_vars, sizes, row_bases) = match solved {
1777 Ok(s) => s,
1778 Err(o) => return o,
1779 };
1780
1781 let mut writes = solution.real_writes;
1783 writes.reserve(call.active_ids.len());
1784 for (k, &i) in call.active_ids.iter().enumerate() {
1785 let v = &program.vars[i];
1786 match Write::new(call.var_owner[i], v.slot, v.window, &solution.values[k]) {
1787 Some(w) => writes.push(w),
1788 None => return Outcome::Failed,
1789 }
1790 }
1791 let mut new_arrays: Vec<(usize, Vec<u64>)> = Vec::new();
1793 for (ai, a) in call.arrays.iter().enumerate() {
1794 if !a.active {
1795 continue;
1796 }
1797 let ew = program.arrays[ai].elem_words;
1798 if program.arrays[ai].nested {
1799 let mut out = RowWriter {
1802 pa: &program.arrays[ai],
1803 elem_vars: &elem_vars,
1804 values: &solution.values[call.active_ids.len()..],
1805 bases: &row_bases,
1806 sizes: &sizes,
1807 made: Vec::new(),
1808 };
1809 match out.elements(ai as u32, program.arrays[ai].row_depth.max(1)) {
1810 Some(flat) => new_arrays.push((ai, flat)),
1811 None => {
1812 out.made.iter().for_each(|&h| collections::release(h));
1813 return Outcome::Failed;
1814 }
1815 }
1816 continue;
1817 }
1818 let mut flat = Vec::with_capacity(sizes[ai] * ew);
1819 for (e, &(arr, _)) in elem_vars.iter().enumerate() {
1820 if arr as usize != ai {
1821 continue;
1822 }
1823 match pack(&solution.values[call.active_ids.len() + e], ew) {
1824 Some(w) => flat.extend(w),
1825 None => return Outcome::Failed,
1826 }
1827 }
1828 new_arrays.push((ai, flat));
1829 }
1830 for w in writes {
1831 if !w.apply() {
1832 return Outcome::DeadHandle;
1833 }
1834 }
1835 for (ai, flat) in new_arrays {
1836 let pa = &program.arrays[ai];
1837 let o = call.array_owner[ai];
1838 let (old_h, _) = match read_unknown(o, pa.slot, pa.window, 64) {
1839 Ok(x) => x,
1840 Err(o) => return o,
1841 };
1842 let new = if pa.nested {
1843 let shape = collections::Shape {
1847 elem_words: row_elem_words(pa, pa.row_depth.max(1)),
1848 assoc: false,
1849 key: collections::KeyKind::Int {
1850 width: 32,
1851 signed: false,
1852 },
1853 obj: false,
1854 nested: collections::Nested::NONE,
1855 };
1856 collections::from_words(&shape, &flat)
1857 } else if pa.assoc {
1858 match collections::map_with_values(old_h.to_u64().unwrap_or(0), pa.elem_words, &flat) {
1860 Some(h) => h,
1861 None => return Outcome::Failed,
1862 }
1863 } else {
1864 let shape = collections::Shape {
1865 elem_words: pa.elem_words,
1866 assoc: false,
1867 key: collections::KeyKind::Int {
1868 width: 32,
1869 signed: false,
1870 },
1871 obj: false,
1872 nested: collections::Nested::NONE,
1873 };
1874 collections::from_words(&shape, &flat)
1875 };
1876 let Some(w) = Write::new(o, pa.slot, pa.window, &BigUint::from(new)) else {
1877 collections::release(new);
1878 return Outcome::Failed;
1879 };
1880 if !w.apply() {
1881 collections::release(new);
1882 return Outcome::DeadHandle;
1883 }
1884 collections::release(old_h.to_u64().unwrap_or(0));
1885 }
1886 for ((o, offset), st) in solution.randc_keys.into_iter().zip(solution.randc_work) {
1887 if !objects::set_randc_states(o, vec![(offset, st)]) {
1888 return Outcome::DeadHandle;
1889 }
1890 }
1891 objects::set_prng_seed(h, seed);
1892 Outcome::Solved
1893}
1894
1895fn row_elem_words(pa: &ProgArray, levels: u32) -> usize {
1900 if levels == 1 && pa.inner_fixed > 0 {
1901 (2 * pa.inner_fixed as usize * pa.elem_ty.width as usize)
1902 .div_ceil(64)
1903 .max(1)
1904 } else {
1905 2
1906 }
1907}
1908
1909struct RowWriter<'a> {
1911 pa: &'a ProgArray,
1912 elem_vars: &'a [(u32, usize)],
1913 values: &'a [BigUint],
1915 bases: &'a [(u32, u32)],
1917 sizes: &'a [usize],
1918 made: Vec<u64>,
1920}
1921
1922impl RowWriter<'_> {
1923 fn base(&self, arr: u32) -> Option<u32> {
1924 self.bases.iter().find(|&&(o, _)| o == arr).map(|&(_, b)| b)
1925 }
1926
1927 fn shape(elem_words: usize) -> collections::Shape {
1928 collections::Shape {
1929 elem_words,
1930 assoc: false,
1931 key: collections::KeyKind::Int {
1932 width: 32,
1933 signed: false,
1934 },
1935 obj: false,
1936 nested: collections::Nested::NONE,
1937 }
1938 }
1939
1940 fn elements(&mut self, arr: u32, levels: u32) -> Option<Vec<u64>> {
1943 let base = self.base(arr)?;
1944 let mut out = Vec::new();
1945 for k in 0..*self.sizes.get(arr as usize)? {
1946 let row = base + k as u32;
1947 if levels > 1 {
1948 let words = self.elements(row, levels - 1)?;
1949 let h = collections::from_words(
1950 &Self::shape(row_elem_words(self.pa, levels - 1)),
1951 &words,
1952 );
1953 self.made.push(h);
1954 out.extend(pack(&BigUint::from(h), 2)?);
1955 } else if self.pa.inner_fixed > 0 {
1956 let n = self.pa.inner_fixed as usize;
1960 let w = self.pa.elem_ty.width as usize;
1961 let mut val = BigUint::zero();
1962 let mut count = 0usize;
1963 for (e, &(a, _)) in self.elem_vars.iter().enumerate() {
1964 if a == row {
1965 val |= &self.values[e] << (count * w);
1966 count += 1;
1967 }
1968 }
1969 if count != n {
1970 return None;
1971 }
1972 out.extend(pack(&val, row_elem_words(self.pa, 1))?);
1973 } else {
1974 let ew = self.pa.elem_words;
1975 let mut flat = Vec::new();
1976 for (e, &(a, _)) in self.elem_vars.iter().enumerate() {
1977 if a == row {
1978 flat.extend(pack(&self.values[e], ew)?);
1979 }
1980 }
1981 let h = collections::from_words(&Self::shape(ew), &flat);
1982 self.made.push(h);
1983 out.extend(pack(&BigUint::from(h), 2)?);
1984 }
1985 }
1986 Some(out)
1987 }
1988}
1989
1990#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
1993pub struct Capture {
1994 pub state: u32,
1996 pub width: u32,
1998}
1999
2000impl Capture {
2001 pub fn words(&self) -> usize {
2004 (2 * self.width as usize).div_ceil(64).max(1)
2005 }
2006}
2007
2008#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
2011pub struct Target {
2012 pub home: Home,
2014 pub slot: FieldSlot,
2016 pub window: Option<Window>,
2019}
2020
2021#[derive(Clone, Debug, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
2027pub struct Spec {
2028 pub frag: Program,
2034 pub captures: Vec<Capture>,
2037 pub only: Option<Vec<Target>>,
2042}
2043
2044impl Spec {
2045 pub fn capture_words(&self) -> usize {
2047 self.captures.iter().map(Capture::words).sum()
2048 }
2049
2050 pub fn scope_words(&self) -> usize {
2054 let ints = self.frag.vars.iter().map(|v| v.slot.offset + v.slot.words);
2055 let reals = self
2056 .frag
2057 .real_vars
2058 .iter()
2059 .map(|v| v.slot.offset + v.slot.words);
2060 let arrays = self
2062 .frag
2063 .arrays
2064 .iter()
2065 .map(|v| v.slot.offset + v.slot.words);
2066 ints.chain(reals).chain(arrays).max().unwrap_or(0)
2067 }
2068}
2069
2070type SpecTable = HashMap<u32, Arc<Spec>>;
2071
2072fn specs() -> &'static Mutex<SpecTable> {
2073 static T: OnceLock<Mutex<SpecTable>> = OnceLock::new();
2074 T.get_or_init(|| Mutex::new(HashMap::new()))
2075}
2076
2077static NEXT_SPEC: AtomicU32 = AtomicU32::new(1);
2079
2080pub fn register_spec(spec: Spec) -> u32 {
2083 let id = NEXT_SPEC.fetch_add(1, Ordering::Relaxed);
2084 specs()
2085 .lock()
2086 .unwrap_or_else(|e| e.into_inner())
2087 .insert(id, Arc::new(spec));
2088 id
2089}
2090
2091pub(super) fn register_spec_at(id: u32, spec: Spec) {
2096 NEXT_SPEC.fetch_max(id + 1, Ordering::Relaxed);
2097 specs()
2098 .lock()
2099 .unwrap_or_else(|e| e.into_inner())
2100 .insert(id, Arc::new(spec));
2101}
2102
2103pub(super) fn all_specs() -> Vec<(u32, Arc<Spec>)> {
2106 let mut v: Vec<_> = specs()
2107 .lock()
2108 .unwrap_or_else(|e| e.into_inner())
2109 .iter()
2110 .map(|(&id, s)| (id, Arc::clone(s)))
2111 .collect();
2112 v.sort_by_key(|(id, _)| *id);
2113 v
2114}
2115
2116pub fn spec(id: u32) -> Option<Arc<Spec>> {
2118 specs()
2119 .lock()
2120 .unwrap_or_else(|e| e.into_inner())
2121 .get(&id)
2122 .cloned()
2123}
2124
2125pub fn spec_count() -> usize {
2128 specs().lock().unwrap_or_else(|e| e.into_inner()).len()
2129}
2130
2131static OBJECT_SITES: AtomicU32 = AtomicU32::new(0);
2132
2133pub fn note_object_site() {
2137 OBJECT_SITES.fetch_add(1, Ordering::Relaxed);
2138}
2139
2140pub fn object_site_count() -> u32 {
2143 OBJECT_SITES.load(Ordering::Relaxed)
2144}
2145
2146pub fn spec_capture_words(id: u32) -> usize {
2149 spec(id).map_or(0, |s| s.capture_words())
2150}
2151
2152fn capture_consts(spec: &Spec, words: &[u64]) -> Result<Vec<Option<BigUint>>, Outcome> {
2157 let mut consts: Vec<Option<BigUint>> = vec![None; spec.frag.states.len()];
2158 let mut at = 0;
2159 for c in &spec.captures {
2160 let n = c.words();
2161 let chunk = words.get(at..at + n).ok_or(Outcome::Failed)?;
2162 at += n;
2163 let (val, unk) = unpack(chunk, c.width);
2164 if !unk.is_zero() {
2165 return Err(Outcome::StateUnknown);
2166 }
2167 *consts.get_mut(c.state as usize).ok_or(Outcome::Failed)? = Some(val);
2168 }
2169 Ok(consts)
2170}
2171
2172pub fn set_all_constraint_modes(h: u64, on: bool) -> Outcome {
2178 let Some(template) = objects::dynamic_template(h) else {
2179 return Outcome::DeadHandle;
2180 };
2181 let Some(entry) = program(template) else {
2182 return Outcome::NoProgram;
2183 };
2184 let Ok(prog) = entry.as_ref() else {
2185 return Outcome::NoProgram;
2186 };
2187 for b in prog.blocks.iter().filter(|b| b.home == Home::Instance) {
2188 if !objects::set_constraint_mode_of(h, b.ordinal, on) {
2189 return Outcome::DeadHandle;
2190 }
2191 }
2192 Outcome::Solved
2193}
2194
2195pub fn randomize(h: u64, statics: &[(u32, u64)]) -> Outcome {
2199 randomize_spec(h, statics, 0, &[])
2200}
2201
2202pub fn randomize_spec(h: u64, statics: &[(u32, u64)], spec_id: u32, captures: &[u64]) -> Outcome {
2206 let Some(template) = objects::dynamic_template(h) else {
2207 return Outcome::DeadHandle;
2208 };
2209 let Some(base) = program(template) else {
2210 return Outcome::NoProgram;
2211 };
2212 let Ok(base_prog) = base.as_ref() else {
2213 return Outcome::NoProgram;
2214 };
2215 let Some(meta) = objects::rand_meta(h) else {
2216 return Outcome::DeadHandle;
2217 };
2218 let mut rand_mode = meta.rand_mode;
2219 let entry = if spec_id == 0 {
2220 base.clone()
2221 } else {
2222 let Some(spec) = spec(spec_id) else {
2223 return Outcome::Failed;
2224 };
2225 let consts = match capture_consts(&spec, captures) {
2226 Ok(c) => c,
2227 Err(o) => return o,
2228 };
2229 let mut p = match joint::overlay(base_prog, &spec.frag, &consts) {
2230 Ok(p) => p,
2231 Err(o) => return o,
2232 };
2233 if let Some(only) = &spec.only
2234 && let Err(o) = joint::restrict(&mut p, &mut rand_mode, only)
2235 {
2236 return o;
2237 }
2238 Arc::new(Ok(p))
2239 };
2240 let Ok(prog) = entry.as_ref() else {
2241 return Outcome::NoProgram;
2242 };
2243 let entry = match joint::apply_leaf_modes(prog, &mut rand_mode, &meta.leaf_off, &meta.leaf_on) {
2245 Some(p) => Arc::new(Ok(p)),
2246 None => entry,
2247 };
2248 let Ok(prog) = entry.as_ref() else {
2249 return Outcome::NoProgram;
2250 };
2251 let entry = match joint::apply_static_modes(prog, &mut rand_mode, statics) {
2254 Some(p) => Arc::new(Ok(p)),
2255 None => entry,
2256 };
2257 let Ok(prog) = entry.as_ref() else {
2258 return Outcome::NoProgram;
2259 };
2260 let closure = match joint::closure(h, &entry, &rand_mode, statics) {
2263 Ok(c) => c,
2264 Err(o) => return o,
2265 };
2266 if closure.members.len() == 1 {
2267 match joint::self_alias(&closure, prog, statics) {
2270 Ok(joint::SelfAlias::None) => return run(prog, h, &rand_mode, statics),
2271 Ok(joint::SelfAlias::Integral) => {}
2272 Err(o) => return o,
2273 }
2274 }
2275 match joint::merge(&closure, statics) {
2276 Ok(m) => run(&m.program, h, &m.rand_mode, statics),
2277 Err(o) => o,
2278 }
2279}
2280
2281const SCOPE_TEMPLATE: ClassTemplateId = ClassTemplateId(u32::MAX - 1);
2285
2286pub fn randomize_scope(spec_id: u32, captures: &[u64], out: &mut [u64], seed: u64) -> Outcome {
2296 let Some(spec) = spec(spec_id) else {
2297 return Outcome::Failed;
2298 };
2299 if out.len() != spec.scope_words() || captures.len() != spec.capture_words() {
2301 return Outcome::Failed;
2302 }
2303 let consts = match capture_consts(&spec, captures) {
2304 Ok(c) => c,
2305 Err(o) => return o,
2306 };
2307 let prog = match joint::overlay(&Program::default(), &spec.frag, &consts) {
2308 Ok(p) => p,
2309 Err(o) => return o,
2310 };
2311 let h = objects::alloc(SCOPE_TEMPLATE, out.len());
2312 objects::set_prng_seed(h, seed);
2313 let slot = FieldSlot {
2314 offset: 0,
2315 words: out.len(),
2316 };
2317 if !out.is_empty() && !objects::field_set(h, slot, out) {
2320 objects::release(h);
2321 return Outcome::Failed;
2322 }
2323 let outcome = run(&prog, h, &[], &[]);
2324 if outcome == Outcome::Solved && !out.is_empty() && !objects::field_get(h, slot, out) {
2325 objects::release(h);
2326 return Outcome::Failed;
2327 }
2328 objects::release(h);
2329 outcome
2330}
2331
2332#[cfg(test)]
2333mod tests {
2334 use super::*;
2335 use crate::constraints::ir::BinOp;
2336 use crate::objects::{RandKind, RandProp, alloc, register_rand_props, release};
2337
2338 pub(super) fn u(w: u32) -> Ty {
2339 Ty::unsigned(w)
2340 }
2341 pub(super) fn var(i: u32, t: Ty) -> Expr {
2342 Expr::Var(i, t)
2343 }
2344 pub(super) fn k(t: Ty, v: u64) -> Expr {
2345 Expr::constant(t, v)
2346 }
2347 pub(super) fn bin(op: BinOp, a: Expr, b: Expr, t: Ty) -> Expr {
2348 Expr::Binary(op, Box::new(a), Box::new(b), t)
2349 }
2350
2351 fn randomize(h: u64) -> Outcome {
2353 super::randomize(h, &[])
2354 }
2355
2356 pub(super) fn setup(id: u32, n: usize, conjuncts: Vec<Expr>) -> (ClassTemplateId, u64) {
2361 setup_with_softs(id, n, conjuncts, Vec::new())
2362 }
2363
2364 fn setup_with_softs(
2365 id: u32,
2366 n: usize,
2367 conjuncts: Vec<Expr>,
2368 softs: Vec<Soft>,
2369 ) -> (ClassTemplateId, u64) {
2370 let t = ClassTemplateId(id);
2371 let vars = (0..n)
2372 .map(|i| ProgVar {
2373 ty: u(8),
2374 slot: FieldSlot {
2375 offset: i,
2376 words: 1,
2377 },
2378 home: Home::Instance,
2379 prop_index: Some(i),
2380 domain_conjunct: None,
2381 randc: false,
2382 window: None,
2383 })
2384 .collect();
2385 register_rand_props(
2386 t,
2387 (0..n)
2388 .map(|i| RandProp {
2389 slot: FieldSlot {
2390 offset: i,
2391 words: 1,
2392 },
2393 kind: RandKind::Rand,
2394 })
2395 .collect(),
2396 );
2397 register_program(
2398 t,
2399 Ok(Program {
2400 vars,
2401 states: vec![ProgState {
2402 class_of: false,
2403 root_bit: 0,
2404 ty: u(8),
2405 slot: FieldSlot {
2406 offset: n,
2407 words: 1,
2408 },
2409 window: None,
2410 home: Home::Instance,
2411 via: Vec::new(),
2412 }],
2413 conjuncts,
2414 before: Vec::new(),
2415 dists: Vec::new(),
2416 softs,
2417 ..Program::default()
2418 }),
2419 );
2420 let h = alloc(t, n + 1);
2421 objects::set_prng_seed(h, 0x1234_5678);
2422 (t, h)
2423 }
2424
2425 fn soft_on_x0(seq: u32) -> Soft {
2427 Soft {
2428 priority: priority::Priority {
2429 object: 0,
2430 level: 0,
2431 seq,
2432 },
2433 dists: Vec::new(),
2434 keys: vec![priority::SoftKey::Var(0)],
2435 disabled: false,
2436 disabled_by: Vec::new(),
2437 }
2438 }
2439
2440 #[test]
2441 fn soft_constraints_hold_unless_a_hard_or_higher_soft_conflicts() {
2442 let eq = |v: u64| bin(BinOp::Eq, var(0, u(8)), k(u(8), v), Ty::BIT);
2444 let c = vec![priority::wrap_soft(0, eq(5)), priority::wrap_soft(1, eq(6))];
2445 let (_, h) = setup_with_softs(0x7000_0f01, 1, c, vec![soft_on_x0(0), soft_on_x0(1)]);
2446 for _ in 0..20 {
2447 assert_eq!(randomize(h), Outcome::Solved);
2448 assert_eq!(get(h, 0), 6);
2449 }
2450 release(h);
2451
2452 let c = vec![
2454 bin(BinOp::Ge, var(0, u(8)), k(u(8), 1), Ty::BIT),
2455 bin(BinOp::Le, var(0, u(8)), k(u(8), 3), Ty::BIT),
2456 priority::wrap_soft(0, eq(5)),
2457 priority::wrap_soft(1, eq(6)),
2458 ];
2459 let (_, h) = setup_with_softs(0x7000_0f02, 1, c, vec![soft_on_x0(0), soft_on_x0(1)]);
2460 let mut seen = std::collections::BTreeSet::new();
2461 for _ in 0..100 {
2462 assert_eq!(randomize(h), Outcome::Solved);
2463 seen.insert(get(h, 0));
2464 }
2465 assert_eq!(seen.len(), 3, "{seen:?}");
2466 release(h);
2467
2468 let c = vec![
2470 bin(BinOp::Gt, var(0, u(8)), k(u(8), 5), Ty::BIT),
2471 bin(BinOp::Lt, var(0, u(8)), k(u(8), 3), Ty::BIT),
2472 priority::wrap_soft(0, eq(4)),
2473 ];
2474 let (_, h) = setup_with_softs(0x7000_0f03, 1, c, vec![soft_on_x0(0)]);
2475 set(h, 0, 0x42);
2476 assert_eq!(randomize(h), Outcome::Unsatisfiable);
2477 assert_eq!(get(h, 0), 0x42, "a failed randomize writes nothing");
2478 release(h);
2479 }
2480
2481 pub(super) fn get(h: u64, off: usize) -> u64 {
2482 let mut b = [0u64];
2483 assert!(objects::field_get(
2484 h,
2485 FieldSlot {
2486 offset: off,
2487 words: 1
2488 },
2489 &mut b
2490 ));
2491 b[0]
2492 }
2493 fn set(h: u64, off: usize, v: u64) {
2494 assert!(objects::field_set(
2495 h,
2496 FieldSlot {
2497 offset: off,
2498 words: 1
2499 },
2500 &[v]
2501 ));
2502 }
2503
2504 #[test]
2505 fn solves_writes_two_state_values_and_advances_the_seed() {
2506 let c = vec![
2508 bin(BinOp::Ge, var(0, u(8)), k(u(8), 10), Ty::BIT),
2509 bin(BinOp::Le, var(0, u(8)), k(u(8), 12), Ty::BIT),
2510 ];
2511 let (_, h) = setup(0x7000_0001, 1, c);
2512 let before = objects::prng_seed(h);
2513 let mut seen = std::collections::BTreeSet::new();
2514 for _ in 0..200 {
2515 assert_eq!(randomize(h), Outcome::Solved);
2516 let v = get(h, 0);
2517 assert!((10..=12).contains(&v), "{v}");
2518 seen.insert(v);
2519 }
2520 assert_eq!(seen.len(), 3, "all three legal values are reachable");
2521 assert_ne!(objects::prng_seed(h), before);
2522 release(h);
2523 }
2524
2525 #[test]
2526 fn failure_writes_nothing_and_consumes_no_draws() {
2527 let c = vec![
2529 bin(BinOp::Gt, var(0, u(8)), k(u(8), 5), Ty::BIT),
2530 bin(BinOp::Lt, var(0, u(8)), k(u(8), 3), Ty::BIT),
2531 ];
2532 let (_, h) = setup(0x7000_0002, 2, c);
2533 set(h, 0, 0x77);
2534 set(h, 1, 0x88);
2535 let seed = objects::prng_seed(h);
2536 assert_eq!(randomize(h), Outcome::Unsatisfiable);
2537 assert_eq!((get(h, 0), get(h, 1)), (0x77, 0x88), "values unchanged");
2538 assert_eq!(objects::prng_seed(h), seed, "stream not perturbed");
2539 release(h);
2540 }
2541
2542 #[test]
2543 fn determinism_by_seed() {
2544 let (_, a) = setup(0x7000_0003, 2, vec![]);
2545 let b = alloc(ClassTemplateId(0x7000_0003), 3);
2546 objects::set_prng_seed(b, 0x1234_5678);
2547 for _ in 0..5 {
2548 assert_eq!(randomize(a), Outcome::Solved);
2549 assert_eq!(randomize(b), Outcome::Solved);
2550 assert_eq!((get(a, 0), get(a, 1)), (get(b, 0), get(b, 1)));
2551 }
2552 objects::set_prng_seed(b, 99);
2553 let mut diff = false;
2554 for _ in 0..8 {
2555 randomize(a);
2556 randomize(b);
2557 diff |= get(a, 0) != get(b, 0);
2558 }
2559 assert!(diff, "different seeds give different streams");
2560 release(a);
2561 release(b);
2562 }
2563
2564 #[test]
2565 fn rand_mode_off_variable_is_state_not_solved() {
2566 let c = vec![bin(
2568 BinOp::Eq,
2569 bin(BinOp::Add, var(0, u(8)), var(1, u(8)), u(8)),
2570 k(u(8), 100),
2571 Ty::BIT,
2572 )];
2573 let (_, h) = setup(0x7000_0004, 2, c);
2574 set(h, 0, 30);
2575 let mut m = objects::rand_meta(h).unwrap();
2576 m.rand_mode[0] = false;
2577 objects::set_rand_meta(h, m);
2578 for _ in 0..20 {
2579 assert_eq!(randomize(h), Outcome::Solved);
2580 assert_eq!((get(h, 0), get(h, 1)), (30, 70));
2581 }
2582 let c2 = vec![bin(BinOp::Lt, var(0, u(8)), k(u(8), 10), Ty::BIT)];
2585 let (_, h2) = setup(0x7000_0005, 1, c2);
2586 set(h2, 0, 200);
2587 let mut m = objects::rand_meta(h2).unwrap();
2588 m.rand_mode[0] = false;
2589 objects::set_rand_meta(h2, m);
2590 assert_eq!(randomize(h2), Outcome::Unsatisfiable, "acts as a checker");
2591 assert_eq!(get(h2, 0), 200);
2592 release(h);
2593 release(h2);
2594 }
2595
2596 #[test]
2597 fn all_variables_inactive_checks_the_constraints() {
2598 let c = vec![bin(BinOp::Eq, var(0, u(8)), k(u(8), 5), Ty::BIT)];
2599 let (_, h) = setup(0x7000_0006, 1, c);
2600 let mut m = objects::rand_meta(h).unwrap();
2601 m.rand_mode[0] = false;
2602 objects::set_rand_meta(h, m);
2603 set(h, 0, 5);
2604 assert_eq!(randomize(h), Outcome::Solved);
2605 set(h, 0, 6);
2606 assert_eq!(randomize(h), Outcome::Unsatisfiable);
2607 release(h);
2608 }
2609
2610 fn windowed(id: u32, randc_hi: bool) -> (ClassTemplateId, u64) {
2615 let t = ClassTemplateId(id);
2616 let slot = FieldSlot {
2617 offset: 0,
2618 words: 1,
2619 };
2620 register_rand_props(
2621 t,
2622 vec![RandProp {
2623 slot,
2624 kind: RandKind::Rand,
2625 }],
2626 );
2627 let leaf = |bit| {
2628 Some(Window {
2629 bit,
2630 width: 8,
2631 field_width: 24,
2632 })
2633 };
2634 let c = vec![
2635 bin(BinOp::Ge, var(0, u(8)), k(u(8), 10), Ty::BIT),
2636 bin(BinOp::Le, var(0, u(8)), k(u(8), 12), Ty::BIT),
2637 bin(
2638 BinOp::Eq,
2639 var(1, u(8)),
2640 bin(BinOp::Add, var(0, u(8)), k(u(8), 1), u(8)),
2641 Ty::BIT,
2642 ),
2643 ];
2644 let mk = |bit, randc| ProgVar {
2645 ty: u(8),
2646 slot,
2647 home: Home::Instance,
2648 prop_index: Some(0),
2649 domain_conjunct: None,
2650 randc,
2651 window: leaf(bit),
2652 };
2653 register_program(
2654 t,
2655 Ok(Program {
2656 vars: vec![mk(0, false), mk(16, randc_hi)],
2657 conjuncts: c,
2658 ..Program::default()
2659 }),
2660 );
2661 let h = alloc(t, 2);
2662 set(h, 0, (0x5au64 << 8) | (0xffu64 << (24 + 8)));
2664 set(h, 1, 0xdead);
2665 (t, h)
2666 }
2667
2668 #[test]
2669 fn leaf_windows_of_one_property_are_solved_and_written_in_place() {
2670 let (_, h) = windowed(0x7000_0f10, false);
2671 let mut seen = std::collections::BTreeSet::new();
2672 for _ in 0..60 {
2673 assert_eq!(randomize(h), Outcome::Solved);
2674 let w = get(h, 0);
2675 let (lo, mid, hi) = (w & 0xff, (w >> 8) & 0xff, (w >> 16) & 0xff);
2676 assert!((10..=12).contains(&lo), "{lo}");
2677 assert_eq!(hi, lo + 1);
2678 assert_eq!(mid, 0x5a, "the neighbouring leaf keeps its value");
2679 assert_eq!(w >> 24, 0xff << 8, "the neighbour's unknown bits survive");
2680 assert_eq!(get(h, 1), 0xdead, "the next property is untouched");
2681 seen.insert(lo);
2682 }
2683 assert_eq!(seen.len(), 3, "all three legal values are reachable");
2684 release(h);
2685 }
2686
2687 #[test]
2690 fn an_inactive_leaf_is_read_through_its_window() {
2691 let (_, h) = windowed(0x7000_0f11, false);
2692 let mut m = objects::rand_meta(h).unwrap();
2693 m.rand_mode[0] = false;
2694 objects::set_rand_meta(h, m);
2695 set(h, 0, 11 | (0x5 << 8) | (12 << 16));
2697 assert_eq!(randomize(h), Outcome::Solved);
2698 assert_eq!(get(h, 0), 11 | (0x5 << 8) | (12 << 16));
2699 set(h, 0, 11 | (13 << 16));
2701 assert_eq!(randomize(h), Outcome::Unsatisfiable);
2702 set(h, 0, 11 | (12 << 16) | (1u64 << (24 + 16)));
2705 assert_eq!(randomize(h), Outcome::StateUnknown);
2706 set(h, 0, 11 | (12 << 16) | (1u64 << (24 + 8)));
2707 assert_eq!(randomize(h), Outcome::Solved);
2708 release(h);
2709 }
2710
2711 #[test]
2713 fn randc_cycles_of_two_leaves_of_one_property_are_independent() {
2714 let (_, h) = windowed(0x7000_0f12, true);
2715 assert_eq!(randomize(h), Outcome::Solved);
2716 let hi_key = randc_key(
2717 FieldSlot {
2718 offset: 0,
2719 words: 1,
2720 },
2721 Some(Window {
2722 bit: 16,
2723 width: 8,
2724 field_width: 24,
2725 }),
2726 );
2727 assert_ne!(hi_key, 0, "a leaf's key differs from its property's");
2728 assert!(objects::randc_state(h, hi_key).unwrap().live);
2729 assert!(!objects::randc_state(h, 0).unwrap().live);
2730 let mut cycle = vec![get(h, 0) >> 16 & 0xff];
2732 for _ in 0..2 {
2733 assert_eq!(randomize(h), Outcome::Solved);
2734 cycle.push(get(h, 0) >> 16 & 0xff);
2735 }
2736 cycle.sort_unstable();
2737 assert_eq!(cycle, vec![11, 12, 13]);
2738 release(h);
2739 }
2740
2741 #[test]
2742 fn enum_domain_dropped_only_while_inactive() {
2743 let dom = bin(
2745 BinOp::LogOr,
2746 bin(BinOp::Eq, var(0, u(8)), k(u(8), 1), Ty::BIT),
2747 bin(BinOp::Eq, var(0, u(8)), k(u(8), 3), Ty::BIT),
2748 Ty::BIT,
2749 );
2750 let (t, h) = setup(0x7000_0007, 1, vec![dom]);
2751 let mut p = program(t).unwrap().as_ref().clone().unwrap();
2752 p.vars[0].domain_conjunct = Some(0);
2753 register_program(t, Ok(p));
2754 for _ in 0..20 {
2755 assert_eq!(randomize(h), Outcome::Solved);
2756 assert!(matches!(get(h, 0), 1 | 3));
2757 }
2758 set(h, 0, 2);
2760 let mut m = objects::rand_meta(h).unwrap();
2761 m.rand_mode[0] = false;
2762 objects::set_rand_meta(h, m);
2763 assert_eq!(randomize(h), Outcome::Solved);
2764 assert_eq!(get(h, 0), 2);
2765 release(h);
2766 }
2767
2768 #[test]
2769 fn x_state_is_a_loud_error_and_changes_nothing() {
2770 let c = vec![bin(BinOp::Eq, var(0, u(8)), Expr::State(0, u(8)), Ty::BIT)];
2772 let (_, h) = setup(0x7000_0008, 1, c);
2773 set(h, 0, 9);
2774 set(h, 1, 1 << 8);
2775 let seed = objects::prng_seed(h);
2776 assert_eq!(randomize(h), Outcome::StateUnknown);
2777 assert_eq!(get(h, 0), 9);
2778 assert_eq!(objects::prng_seed(h), seed);
2779 set(h, 1, 0x42);
2781 assert_eq!(randomize(h), Outcome::Solved);
2782 assert_eq!(get(h, 0), 0x42);
2783 release(h);
2784 }
2785
2786 #[test]
2787 fn unreferenced_x_state_is_not_an_error() {
2788 let (_, h) = setup(0x7000_0009, 1, vec![]);
2789 set(h, 1, 1 << 8);
2790 assert_eq!(randomize(h), Outcome::Solved);
2791 release(h);
2792 }
2793
2794 #[test]
2795 fn dead_handle_missing_program_and_failed_program() {
2796 assert_eq!(randomize(0), Outcome::DeadHandle);
2797 let t = ClassTemplateId(0x7000_000a);
2798 let h = alloc(t, 1);
2799 assert_eq!(randomize(h), Outcome::NoProgram, "nothing registered");
2800 register_program(t, Err("unsupported thing".into()));
2801 assert_eq!(randomize(h), Outcome::NoProgram, "build failure");
2802 assert_eq!(
2803 program(t).unwrap().as_ref().clone(),
2804 Err("unsupported thing".to_string())
2805 );
2806 release(h);
2807 assert_eq!(randomize(h), Outcome::DeadHandle, "stale handle");
2808 }
2809
2810 #[test]
2811 fn four_state_field_round_trip_packs_value_low_unknown_high() {
2812 let (v, x) = unpack(&[0xde_adbe_ef12, 0], 40);
2814 assert_eq!(v, BigUint::from(0xde_adbe_ef12u64));
2815 assert!(x.is_zero());
2816 let (_, x) = unpack(&[0, 1 << 8], 40);
2817 assert!(!x.is_zero(), "bit 72 of the packed value is unknown bit 32");
2818 assert_eq!(pack(&BigUint::from(7u8), 2), Some(vec![7, 0]));
2819 assert_eq!(pack(&(BigUint::from(1u8) << 70), 1), None);
2820 }
2821
2822 fn skewed(id: u32, before: Vec<(u32, u32)>) -> (ClassTemplateId, u64) {
2825 let c = vec![
2826 bin(BinOp::Lt, var(0, u(8)), k(u(8), 2), Ty::BIT),
2827 bin(
2828 BinOp::LogImpl,
2829 bin(BinOp::Eq, var(0, u(8)), k(u(8), 1), Ty::BIT),
2830 bin(BinOp::Eq, var(1, u(8)), k(u(8), 0), Ty::BIT),
2831 Ty::BIT,
2832 ),
2833 ];
2834 let (t, h) = setup(id, 2, c);
2835 let mut p = program(t).unwrap().as_ref().clone().unwrap();
2836 p.before = before;
2837 register_program(t, Ok(p));
2838 (t, h)
2839 }
2840
2841 fn count_x0_is_one(h: u64, n: u32) -> u32 {
2842 let mut ones = 0;
2843 for _ in 0..n {
2844 assert_eq!(randomize(h), Outcome::Solved);
2845 ones += u32::from(get(h, 0) == 1);
2846 }
2847 ones
2848 }
2849
2850 #[test]
2851 fn solve_before_orders_the_solve_but_not_the_solution_space() {
2852 let (_, h) = skewed(0x7000_0020, vec![]);
2857 let ones = count_x0_is_one(h, 3000);
2858 assert!(ones < 40, "unordered: {ones}/3000");
2859 let (_, h) = skewed(0x7000_0021, vec![(0, 1)]);
2860 let ones = count_x0_is_one(h, 3000);
2861 assert!((1350..1650).contains(&ones), "x0 before x1: {ones}/3000");
2862 let (_, h) = skewed(0x7000_0022, vec![(1, 0)]);
2863 let ones = count_x0_is_one(h, 3000);
2864 assert!(ones < 40, "x1 before x0: {ones}/3000");
2865 }
2866
2867 #[test]
2868 fn an_edge_through_an_inactive_variable_is_ignored() {
2869 let (_, h) = skewed(0x7000_0023, vec![(0, 1)]);
2870 set(h, 0, 1);
2871 let mut m = objects::rand_meta(h).unwrap();
2872 m.rand_mode[0] = false;
2873 objects::set_rand_meta(h, m);
2874 for _ in 0..20 {
2875 assert_eq!(randomize(h), Outcome::Solved);
2876 assert_eq!(
2877 (get(h, 0), get(h, 1)),
2878 (1, 0),
2879 "x0 is state, x1 forced to 0"
2880 );
2881 }
2882 }
2883
2884 #[test]
2885 fn a_circular_or_out_of_range_order_is_a_failed_program_not_a_guess() {
2886 let (_, h) = skewed(0x7000_0024, vec![(0, 1), (1, 0)]);
2887 let seed = objects::prng_seed(h);
2888 assert_eq!(randomize(h), Outcome::Failed);
2889 assert_eq!(objects::prng_seed(h), seed);
2890 let (_, h) = skewed(0x7000_0025, vec![(0, 9)]);
2891 assert_eq!(randomize(h), Outcome::Failed);
2892 }
2893
2894 #[test]
2895 fn outcome_codes_round_trip_and_every_error_has_a_message() {
2896 for c in 0..=20u64 {
2897 let o = Outcome::from_code(c).unwrap();
2898 assert_eq!(o.code(), c);
2899 assert_eq!(o.error_message().is_some(), c >= 2, "{o:?}");
2900 }
2901 assert_eq!(Outcome::from_code(21), None);
2902 }
2903
2904 #[test]
2905 fn every_sat_fallback_failure_has_its_own_outcome() {
2906 use crate::constraints::bdd::BddBuildError;
2907 let none: &[bool] = &[];
2908 for (e, want) in [
2909 (
2910 SolveError::Build(BddBuildError::TooLarge {
2911 budget: 1,
2912 during: "x",
2913 }),
2914 Outcome::TooLarge,
2915 ),
2916 (
2917 SolveError::Build(BddBuildError::CnfTooLarge {
2918 limit: 1,
2919 during: "x",
2920 }),
2921 Outcome::TooLarge,
2922 ),
2923 (SolveError::SatTimeout, Outcome::SatTimeout),
2924 (
2925 SolveError::SatUnsupported { what: "x" },
2926 Outcome::SatUnsupported,
2927 ),
2928 (SolveError::SatModelRejected, Outcome::Failed),
2929 (SolveError::SatSolverFailure { what: "x" }, Outcome::Failed),
2930 ] {
2931 assert_eq!(outcome_of(e.clone(), none), want, "{e}");
2932 }
2933 assert!(
2935 Outcome::TooLarge
2936 .error_message()
2937 .unwrap()
2938 .contains("SAT fallback")
2939 );
2940 assert!(
2941 Outcome::SatTimeout
2942 .error_message()
2943 .unwrap()
2944 .contains("time limit")
2945 );
2946 assert!(
2947 Outcome::SatUnsupported
2948 .error_message()
2949 .unwrap()
2950 .contains("does not support")
2951 );
2952 }
2953
2954 fn dist_program(id: u32) -> (ClassTemplateId, u64) {
2957 use crate::constraints::dist::{Dist, Item, SELECTOR_BASE, Span};
2958 let x = var(0, u(8));
2959 let item = |v: u64, w: Expr| Item {
2960 member: bin(BinOp::Eq, x.clone(), k(u(8), v), Ty::BIT),
2961 ty: u(8),
2962 span: Span::Point(k(u(8), v)),
2963 weight: w,
2964 per_range: false,
2965 };
2966 let d = Dist {
2967 subject_ty: u(8),
2968 plain_var: Some(0),
2969 vars: vec![0],
2970 items: vec![
2971 item(
2972 0,
2973 Expr::Resize(Box::new(Expr::State(0, u(8))), Ty::signed(8)),
2974 ),
2975 item(1, k(Ty::signed(8), 1)),
2976 ],
2977 default: None,
2978 subject: None,
2979 };
2980 let (t, h) = setup(id, 1, vec![d.conjunct(SELECTOR_BASE)]);
2981 let mut p = program(t).unwrap().as_ref().clone().unwrap();
2982 p.dists = vec![d];
2983 register_program(t, Ok(p));
2984 (t, h)
2985 }
2986
2987 #[test]
2988 fn a_dist_weight_is_read_from_the_state_at_each_call() {
2989 let (_, h) = dist_program(0x7000_0040);
2990 set(h, 1, 3);
2992 let mut zeros = 0;
2993 for _ in 0..2000 {
2994 assert_eq!(randomize(h), Outcome::Solved);
2995 let x = get(h, 0);
2996 assert!(x <= 1, "x = {x}");
2997 zeros += u32::from(x == 0);
2998 }
2999 assert!(
3000 (1400..1600).contains(&zeros),
3001 "x == 0: {zeros}/2000 (want 1500)"
3002 );
3003 set(h, 1, 0);
3005 for _ in 0..50 {
3006 assert_eq!(randomize(h), Outcome::Solved);
3007 assert_eq!(get(h, 0), 1);
3008 }
3009 }
3010
3011 #[test]
3012 fn a_negative_dist_weight_fails_the_call_and_changes_nothing() {
3013 let (_, h) = dist_program(0x7000_0041);
3014 set(h, 1, 3);
3015 assert_eq!(randomize(h), Outcome::Solved);
3016 let (before, seed) = (get(h, 0), objects::prng_seed(h));
3017 set(h, 1, 200); assert_eq!(randomize(h), Outcome::BadDistWeight);
3019 assert_eq!((get(h, 0), objects::prng_seed(h)), (before, seed));
3020 set(h, 1, 1 << 8);
3022 assert_eq!(randomize(h), Outcome::StateUnknown);
3023 }
3024
3025 #[test]
3026 fn a_dist_on_an_inactive_variable_is_a_membership_test_on_its_value() {
3027 let (_, h) = dist_program(0x7000_0042);
3028 set(h, 1, 3);
3029 let mut m = objects::rand_meta(h).unwrap();
3030 m.rand_mode[0] = false;
3031 objects::set_rand_meta(h, m);
3032 set(h, 0, 1);
3033 assert_eq!(randomize(h), Outcome::Solved);
3034 assert_eq!(get(h, 0), 1);
3035 set(h, 0, 7);
3037 assert_eq!(randomize(h), Outcome::Unsatisfiable);
3038 set(h, 0, 0);
3039 set(h, 1, 0);
3040 assert_eq!(randomize(h), Outcome::Unsatisfiable);
3041 }
3042
3043 #[test]
3047 fn a_dist_default_spreads_its_weight_over_the_values_no_item_names() {
3048 use crate::constraints::dist::{DefaultWeight, Dist, Item, SELECTOR_BASE, Span};
3049 let x = var(0, u(8));
3050 let d = Dist {
3051 subject_ty: u(8),
3052 plain_var: Some(0),
3053 vars: vec![0],
3054 items: vec![Item {
3055 member: bin(BinOp::Le, x.clone(), k(u(8), 3), Ty::BIT),
3056 ty: u(8),
3057 span: Span::Range {
3058 lo: Some(k(u(8), 0)),
3059 hi: Some(k(u(8), 3)),
3060 },
3061 weight: k(u(32), 8),
3062 per_range: true,
3063 }],
3064 default: Some(DefaultWeight {
3065 weight: k(u(32), 8),
3066 per_range: true,
3067 }),
3068 subject: None,
3069 };
3070 let (t, h) = setup(0x7000_0043, 1, vec![d.conjunct(SELECTOR_BASE)]);
3071 let mut p = program(t).unwrap().as_ref().clone().unwrap();
3072 p.dists = vec![d];
3073 register_program(t, Ok(p));
3074 let (mut low, mut distinct) = (0u32, std::collections::BTreeSet::new());
3075 for _ in 0..4000 {
3076 assert_eq!(randomize(h), Outcome::Solved);
3077 let v = get(h, 0);
3078 if v <= 3 {
3079 low += 1;
3080 } else {
3081 distinct.insert(v);
3082 }
3083 }
3084 assert!(
3085 (1850..2150).contains(&low),
3086 "x <= 3: {low}/4000 (want 2000)"
3087 );
3088 assert!(
3089 distinct.len() > 100,
3090 "the default values are spread: {}",
3091 distinct.len()
3092 );
3093 }
3094
3095 fn randc_program(id: u32, limit: u64) -> (ClassTemplateId, u64) {
3098 let c = vec![
3099 bin(BinOp::Lt, var(0, u(8)), k(u(8), limit), Ty::BIT),
3100 bin(BinOp::Ge, var(0, u(8)), Expr::State(0, u(8)), Ty::BIT),
3101 bin(BinOp::Gt, var(1, u(8)), var(0, u(8)), Ty::BIT),
3102 ];
3103 let (t, h) = setup(id, 2, c);
3104 let mut p = program(t).unwrap().as_ref().clone().unwrap();
3105 p.vars[0].randc = true;
3106 register_program(t, Ok(p));
3107 (t, h)
3108 }
3109
3110 fn draw_cycle(h: u64, n: usize) -> Vec<u64> {
3111 (0..n)
3112 .map(|_| {
3113 assert_eq!(randomize(h), Outcome::Solved);
3114 get(h, 0)
3115 })
3116 .collect()
3117 }
3118
3119 #[test]
3120 fn randc_visits_every_legal_value_once_per_cycle() {
3121 let (_, h) = randc_program(0x7000_0030, 6);
3124 let mut orders = std::collections::BTreeSet::new();
3125 for _ in 0..40 {
3126 let mut cyc = draw_cycle(h, 6);
3127 orders.insert(cyc.clone());
3128 cyc.sort_unstable();
3129 assert_eq!(cyc, vec![0, 1, 2, 3, 4, 5]);
3130 }
3131 assert!(orders.len() > 10, "cycles are shuffled, not a fixed order");
3132 release(h);
3133 }
3134
3135 #[test]
3136 fn randc_state_is_committed_only_when_the_call_succeeds() {
3137 let (_, h) = randc_program(0x7000_0031, 4);
3138 let first = draw_cycle(h, 2);
3139 let saved = objects::randc_state(h, 0).unwrap();
3142 set(h, 2, 200);
3143 let seed = objects::prng_seed(h);
3144 assert_eq!(randomize(h), Outcome::Unsatisfiable);
3145 assert_eq!(objects::randc_state(h, 0).unwrap(), saved);
3146 assert_eq!(objects::prng_seed(h), seed);
3147 set(h, 2, 0);
3148 let rest = draw_cycle(h, 2);
3149 let mut all = [first, rest].concat();
3150 all.sort_unstable();
3151 assert_eq!(all, vec![0, 1, 2, 3], "the interrupted cycle completes");
3152 release(h);
3153 }
3154
3155 #[test]
3156 fn randc_constraint_change_starts_a_new_cycle() {
3157 let (_, h) = randc_program(0x7000_0032, 8);
3159 draw_cycle(h, 3);
3160 set(h, 2, 6); let next = draw_cycle(h, 2);
3162 let mut sorted = next.clone();
3163 sorted.sort_unstable();
3164 assert_eq!(sorted, vec![6, 7], "{next:?}: a fresh cycle of the new set");
3165 release(h);
3166 }
3167
3168 #[test]
3169 fn randc_interacts_with_rand_without_failing() {
3170 let (_, h) = randc_program(0x7000_0033, 6);
3173 for _ in 0..10 {
3174 let mut cyc = Vec::new();
3175 for _ in 0..6 {
3176 assert_eq!(randomize(h), Outcome::Solved);
3177 assert!(get(h, 1) > get(h, 0));
3178 cyc.push(get(h, 0));
3179 }
3180 cyc.sort_unstable();
3181 assert_eq!(cyc, vec![0, 1, 2, 3, 4, 5]);
3182 }
3183 release(h);
3184 }
3185
3186 #[test]
3187 fn an_inactive_randc_variable_is_state_and_keeps_its_cycle() {
3188 let (_, h) = randc_program(0x7000_0034, 4);
3189 draw_cycle(h, 2);
3190 let saved = objects::randc_state(h, 0).unwrap();
3191 set(h, 0, 2);
3192 let mut m = objects::rand_meta(h).unwrap();
3193 m.rand_mode[0] = false;
3194 objects::set_rand_meta(h, m);
3195 for _ in 0..5 {
3196 assert_eq!(randomize(h), Outcome::Solved);
3197 assert_eq!(get(h, 0), 2, "not redrawn");
3198 assert!(get(h, 1) > 2);
3199 }
3200 assert_eq!(objects::randc_state(h, 0).unwrap(), saved);
3201 release(h);
3202 }
3203
3204 fn static_randc(id: u32) -> (u64, u64, u64) {
3207 let (t, a) = randc_program(id, 4);
3208 let mut p = program(t).unwrap().as_ref().clone().unwrap();
3209 p.vars[0].home = Home::Static(7);
3210 p.vars[0].prop_index = None;
3211 register_program(t, Ok(p));
3212 let b = alloc(t, 3);
3213 objects::set_prng_seed(b, 0x9999);
3214 let st = alloc(ClassTemplateId(id + 0x100), 1);
3215 (a, b, st)
3216 }
3217
3218 #[test]
3219 fn a_static_randc_cycles_across_all_instances() {
3220 let (a, b, st) = static_randc(0x7000_0035);
3224 let pairs = [(7u32, st)];
3225 for _ in 0..10 {
3226 let mut cyc = Vec::new();
3227 for i in 0..4 {
3228 let h = if i % 2 == 0 { a } else { b };
3229 assert_eq!(super::randomize(h, &pairs), Outcome::Solved);
3230 let mut w = [0u64];
3231 assert!(objects::field_get(
3232 st,
3233 FieldSlot {
3234 offset: 0,
3235 words: 1
3236 },
3237 &mut w
3238 ));
3239 cyc.push(w[0]);
3240 assert_eq!(get(h, 0), 0, "the instance's own word is untouched");
3241 }
3242 cyc.sort_unstable();
3243 assert_eq!(cyc, vec![0, 1, 2, 3]);
3244 }
3245 assert_eq!(
3246 objects::randc_state(a, 0).unwrap(),
3247 RandcState::default(),
3248 "the cycle lives in the static object, not the instance"
3249 );
3250 assert_ne!(objects::randc_state(st, 0).unwrap(), RandcState::default());
3251 release(a);
3252 release(b);
3253 release(st);
3254 }
3255
3256 #[test]
3257 fn instance_randc_cycles_are_per_object() {
3258 let (_, a) = randc_program(0x7000_0036, 4);
3260 let b = alloc(ClassTemplateId(0x7000_0036), 3);
3261 objects::set_prng_seed(b, 0x4242);
3262 for _ in 0..6 {
3263 let mut ca = Vec::new();
3264 let mut cb = Vec::new();
3265 for _ in 0..4 {
3266 assert_eq!(randomize(a), Outcome::Solved);
3267 ca.push(get(a, 0));
3268 assert_eq!(randomize(b), Outcome::Solved);
3269 cb.push(get(b, 0));
3270 }
3271 ca.sort_unstable();
3272 cb.sort_unstable();
3273 assert_eq!((ca, cb), (vec![0, 1, 2, 3], vec![0, 1, 2, 3]));
3274 }
3275 release(a);
3276 release(b);
3277 }
3278
3279 #[test]
3280 fn a_missing_static_object_is_a_loud_error_that_changes_nothing() {
3281 let (a, _, st) = static_randc(0x7000_0037);
3282 let seed = objects::prng_seed(a);
3283 assert_eq!(super::randomize(a, &[]), Outcome::StaticUnavailable);
3284 assert_eq!(super::randomize(a, &[(8, st)]), Outcome::StaticUnavailable);
3285 assert_eq!(objects::prng_seed(a), seed);
3286 assert_eq!(super::randomize(a, &[(7, st)]), Outcome::Solved);
3287 release(a);
3288 release(st);
3289 }
3290
3291 const HANDLE_A: usize = 2;
3298 const HANDLE_B: usize = 4;
3299
3300 fn handle_slot(offset: usize) -> FieldSlot {
3301 FieldSlot { offset, words: 2 }
3302 }
3303
3304 fn d_object(id: u32, x: u64) -> u64 {
3306 let d = alloc(ClassTemplateId(id), 1);
3307 set(d, 0, x);
3308 d
3309 }
3310
3311 fn set_handle(h: u64, offset: usize, target: u64) {
3312 assert!(objects::field_set(h, handle_slot(offset), &[target, 0]));
3313 }
3314
3315 fn guard_class(id: u32, conjuncts: Vec<Expr>, a_x: Option<u64>, b_x: Option<u64>) -> u64 {
3319 let t = ClassTemplateId(id);
3320 let rand_slot = |i: usize| FieldSlot {
3321 offset: i,
3322 words: 1,
3323 };
3324 register_rand_props(
3325 t,
3326 (0..2)
3327 .map(|i| RandProp {
3328 slot: rand_slot(i),
3329 kind: RandKind::Rand,
3330 })
3331 .collect(),
3332 );
3333 let state = |root: usize| ProgState {
3334 class_of: false,
3335 root_bit: 0,
3336 ty: u(8),
3337 slot: handle_slot(root),
3338 window: None,
3339 home: Home::Instance,
3340 via: vec![rand_slot(0)],
3341 };
3342 register_program(
3343 t,
3344 Ok(Program {
3345 vars: (0..2)
3346 .map(|i| ProgVar {
3347 ty: u(8),
3348 slot: rand_slot(i),
3349 home: Home::Instance,
3350 prop_index: Some(i),
3351 domain_conjunct: None,
3352 randc: false,
3353 window: None,
3354 })
3355 .collect(),
3356 states: vec![state(HANDLE_A), state(HANDLE_B)],
3357 conjuncts,
3358 before: Vec::new(),
3359 dists: Vec::new(),
3360 ..Program::default()
3361 }),
3362 );
3363 let h = alloc(t, 6);
3364 objects::set_prng_seed(h, 0x5eed_0018);
3365 if let Some(x) = a_x {
3366 set_handle(h, HANDLE_A, d_object(id + 0x100, x));
3367 }
3368 if let Some(x) = b_x {
3369 set_handle(h, HANDLE_B, d_object(id + 0x200, x));
3370 }
3371 h
3372 }
3373
3374 fn x() -> Expr {
3375 var(0, u(8))
3376 }
3377 fn y() -> Expr {
3378 var(1, u(8))
3379 }
3380 fn a_x() -> Expr {
3381 Expr::State(0, u(8))
3382 }
3383 fn b_x() -> Expr {
3384 Expr::State(1, u(8))
3385 }
3386 fn lt_xy() -> Expr {
3387 bin(BinOp::Lt, x(), y(), Ty::BIT)
3388 }
3389 fn a_gt_b() -> Expr {
3390 bin(BinOp::Gt, a_x(), b_x(), Ty::BIT)
3391 }
3392 fn a_is_5() -> Expr {
3393 bin(BinOp::Eq, a_x(), k(u(8), 5), Ty::BIT)
3394 }
3395 fn or(l: Expr, r: Expr) -> Expr {
3396 bin(BinOp::LogOr, l, r, Ty::BIT)
3397 }
3398 fn and(l: Expr, r: Expr) -> Expr {
3399 bin(BinOp::LogAnd, l, r, Ty::BIT)
3400 }
3401 fn guard_sum(pred: Expr) -> Vec<Expr> {
3403 vec![Expr::Guard(
3404 Box::new(pred),
3405 Box::new(bin(
3406 BinOp::Eq,
3407 bin(BinOp::Add, x(), y(), u(8)),
3408 k(u(8), 10),
3409 Ty::BIT,
3410 )),
3411 Box::new(k(Ty::BIT, 1)),
3412 Ty::BIT,
3413 )]
3414 }
3415
3416 fn sum_draws(h: u64) -> (u32, u32, bool) {
3419 let (mut sums, mut lts, mut implied) = (0, 0, true);
3420 for _ in 0..400 {
3421 assert_eq!(randomize(h), Outcome::Solved);
3422 let (x, y) = (get(h, 0) as u8, get(h, 1) as u8);
3423 let sum = x.wrapping_add(y) == 10;
3424 sums += u32::from(sum);
3425 lts += u32::from(x < y);
3426 implied &= x >= y || sum;
3427 }
3428 (sums, lts, implied)
3429 }
3430
3431 fn assert_body_unconditional(h: u64) {
3433 let (sums, _, _) = sum_draws(h);
3434 assert_eq!(sums, 400, "an unconditional `x + y == 10`");
3435 }
3436
3437 fn assert_body_conditional(h: u64) {
3440 let (sums, lts, implied) = sum_draws(h);
3441 assert!(implied, "(x < y) -> (x + y == 10) always holds");
3442 assert!(lts < 400 && sums < 400, "not unconditional: {sums}/{lts}");
3443 }
3444
3445 fn assert_eliminated(h: u64) {
3447 let (sums, lts, _) = sum_draws(h);
3448 assert!(sums < 20, "x + y == 10 is a 1/256 accident: {sums}");
3449 assert!((120..280).contains(<s), "x and y are free: {lts}/400");
3450 }
3451
3452 fn assert_error(h: u64) {
3453 let (seed, x0) = (objects::prng_seed(h), get(h, 0));
3454 assert_eq!(randomize(h), Outcome::GuardError);
3455 assert_eq!((objects::prng_seed(h), get(h, 0)), (seed, x0), "no change");
3456 }
3457
3458 #[test]
3460 fn lrm_example_1_or_guard() {
3461 let c = || guard_sum(or(or(lt_xy(), a_gt_b()), a_is_5()));
3462 assert_body_unconditional(guard_class(0x7100_0001, c(), Some(5), None));
3465 assert_error(guard_class(0x7100_0002, c(), None, Some(1)));
3467 assert_body_conditional(guard_class(0x7100_0003, c(), Some(10), Some(20)));
3469 }
3470
3471 #[test]
3473 fn lrm_example_2_and_guard() {
3474 let c = || guard_sum(and(and(lt_xy(), a_gt_b()), a_is_5()));
3475 assert_eliminated(guard_class(0x7100_0011, c(), Some(6), None));
3478 assert_error(guard_class(0x7100_0012, c(), None, Some(1)));
3480 assert_body_conditional(guard_class(0x7100_0013, c(), Some(5), Some(2)));
3482 }
3483
3484 #[test]
3486 fn lrm_example_3_nested_guard() {
3487 let c = || guard_sum(and(lt_xy(), or(a_gt_b(), a_is_5())));
3488 assert_body_conditional(guard_class(0x7100_0021, c(), Some(5), None));
3490 assert_error(guard_class(0x7100_0022, c(), Some(8), None));
3492 assert_error(guard_class(0x7100_0023, c(), None, Some(1)));
3494 assert_body_conditional(guard_class(0x7100_0024, c(), Some(5), Some(2)));
3496 }
3497
3498 #[test]
3501 fn a_false_guard_selects_the_else_arm_and_never_examines_the_other() {
3502 let inner = Expr::Guard(
3504 Box::new(bin(BinOp::Eq, b_x(), k(u(8), 1), Ty::BIT)),
3505 Box::new(bin(BinOp::Eq, x(), k(u(8), 9), Ty::BIT)),
3506 Box::new(k(Ty::BIT, 1)),
3507 Ty::BIT,
3508 );
3509 let c = vec![Expr::Guard(
3510 Box::new(a_is_5()),
3511 Box::new(inner),
3512 Box::new(bin(BinOp::Eq, y(), k(u(8), 3), Ty::BIT)),
3513 Ty::BIT,
3514 )];
3515 let h = guard_class(0x7100_0031, c.clone(), Some(6), None);
3517 for _ in 0..20 {
3518 assert_eq!(randomize(h), Outcome::Solved);
3519 assert_eq!(get(h, 1), 3);
3520 }
3521 assert_error(guard_class(0x7100_0032, c.clone(), Some(5), None));
3523 let h = guard_class(0x7100_0033, c, Some(5), Some(1));
3525 assert_eq!(randomize(h), Outcome::Solved);
3526 assert_eq!(get(h, 0), 9);
3527 }
3528
3529 #[test]
3532 fn a_null_handle_outside_a_guard_fails_the_call_with_its_own_outcome() {
3533 let c = vec![bin(BinOp::Eq, x(), a_x(), Ty::BIT)];
3534 let h = guard_class(0x7100_0041, c.clone(), None, None);
3535 let seed = objects::prng_seed(h);
3536 assert_eq!(randomize(h), Outcome::NullHandle);
3537 assert_eq!(objects::prng_seed(h), seed);
3538 let h = guard_class(0x7100_0042, c.clone(), Some(77), None);
3540 assert_eq!(randomize(h), Outcome::Solved);
3541 assert_eq!(get(h, 0), 77);
3542 let h = guard_class(0x7100_0043, c.clone(), Some(77), None);
3544 let mut buf = [0u64; 2];
3545 assert!(objects::field_get(h, handle_slot(HANDLE_A), &mut buf));
3546 release(buf[0]);
3547 assert_eq!(randomize(h), Outcome::NullHandle);
3548 let h = guard_class(0x7100_0044, c, Some(77), None);
3550 assert!(objects::field_set(h, handle_slot(HANDLE_A), &[1, 1]));
3551 assert_eq!(randomize(h), Outcome::StateUnknown);
3552 }
3553
3554 #[test]
3557 fn an_unknown_value_in_a_guard_is_an_error() {
3558 let h = guard_class(0x7100_0051, guard_sum(a_is_5()), Some(5), None);
3559 let mut buf = [0u64; 2];
3561 assert!(objects::field_get(h, handle_slot(HANDLE_A), &mut buf));
3562 assert!(objects::field_set(
3563 buf[0],
3564 FieldSlot {
3565 offset: 0,
3566 words: 1
3567 },
3568 &[5 | (1 << 8)]
3569 ));
3570 assert_error(h);
3571 }
3572
3573 #[test]
3576 fn a_two_hop_handle_chain_reads_the_far_member_and_fails_on_a_null_link() {
3577 let node = |id: u32, v: u64| {
3579 let o = alloc(ClassTemplateId(id), 3);
3580 set(o, 2, v);
3581 o
3582 };
3583 let t = ClassTemplateId(0x7100_0061);
3584 register_rand_props(
3585 t,
3586 vec![RandProp {
3587 slot: FieldSlot {
3588 offset: 0,
3589 words: 1,
3590 },
3591 kind: RandKind::Rand,
3592 }],
3593 );
3594 let root = |offset| ProgState {
3595 class_of: false,
3596 root_bit: 0,
3597 ty: u(8),
3598 slot: handle_slot(offset),
3599 window: None,
3600 home: Home::Instance,
3601 via: vec![
3602 handle_slot(0),
3603 FieldSlot {
3604 offset: 2,
3605 words: 1,
3606 },
3607 ],
3608 };
3609 register_program(
3611 t,
3612 Ok(Program {
3613 vars: vec![ProgVar {
3614 ty: u(8),
3615 slot: FieldSlot {
3616 offset: 0,
3617 words: 1,
3618 },
3619 home: Home::Instance,
3620 prop_index: Some(0),
3621 domain_conjunct: None,
3622 randc: false,
3623 window: None,
3624 }],
3625 states: vec![root(1)],
3626 conjuncts: vec![bin(BinOp::Eq, x(), Expr::State(0, u(8)), Ty::BIT)],
3627 before: Vec::new(),
3628 dists: Vec::new(),
3629 ..Program::default()
3630 }),
3631 );
3632 let h = alloc(t, 3);
3633 objects::set_prng_seed(h, 1);
3634 let (n0, n1) = (node(0x7100_0062, 1), node(0x7100_0063, 42));
3635 set_handle(h, 1, n0);
3636 set_handle(n0, 0, n1);
3637 assert_eq!(randomize(h), Outcome::Solved);
3638 assert_eq!(get(h, 0), 42);
3639 set_handle(n0, 0, 0);
3640 assert_eq!(randomize(h), Outcome::NullHandle);
3641 }
3642
3643 use crate::constraints::real_domain::{
3646 CmpOp, RealCond, RealDist, RealDistItem, RealExpr, RealSpan,
3647 };
3648
3649 fn rslot(offset: usize) -> FieldSlot {
3650 FieldSlot { offset, words: 2 }
3651 }
3652 fn rk(x: f64) -> RealExpr {
3653 RealExpr::Const(real_domain::Real::new(x), FloatKind::F64)
3654 }
3655 fn rcmp(op: CmpOp, a: RealExpr, b: RealExpr) -> RealCond {
3656 RealCond::Cmp(op, a, b)
3657 }
3658 fn rget(h: u64, off: usize) -> f64 {
3659 let mut b = [0u64; 2];
3660 assert!(objects::field_get(h, rslot(off), &mut b));
3661 assert_eq!(b[1], 0, "a solved real has no unknown bits");
3662 f64::from_bits(b[0])
3663 }
3664 fn rset_bits(h: u64, off: usize, bits: u64, unk: u64) {
3665 assert!(objects::field_set(h, rslot(off), &[bits, unk]));
3666 }
3667
3668 fn setup_real(id: u32, real: RealProblem, conjuncts: Vec<Expr>) -> u64 {
3672 let t = ClassTemplateId(id);
3673 register_rand_props(
3674 t,
3675 vec![
3676 RandProp {
3677 slot: rslot(0),
3678 kind: RandKind::Rand,
3679 },
3680 RandProp {
3681 slot: FieldSlot {
3682 offset: 2,
3683 words: 1,
3684 },
3685 kind: RandKind::Rand,
3686 },
3687 ],
3688 );
3689 register_program(
3690 t,
3691 Ok(Program {
3692 vars: vec![ProgVar {
3693 ty: u(8),
3694 slot: FieldSlot {
3695 offset: 2,
3696 words: 1,
3697 },
3698 home: Home::Instance,
3699 prop_index: Some(1),
3700 domain_conjunct: None,
3701 randc: false,
3702 window: None,
3703 }],
3704 states: vec![ProgState {
3705 class_of: false,
3706 root_bit: 0,
3707 ty: u(64),
3708 slot: rslot(3),
3709 window: None,
3710 home: Home::Instance,
3711 via: Vec::new(),
3712 }],
3713 conjuncts,
3714 real_vars: vec![ProgRealVar {
3715 kind: FloatKind::F64,
3716 slot: rslot(0),
3717 home: Home::Instance,
3718 prop_index: Some(0),
3719 window: None,
3720 }],
3721 real,
3722 ..Program::default()
3723 }),
3724 );
3725 let h = alloc(t, 5);
3726 objects::set_prng_seed(h, 0x5eed_0c09);
3727 h
3728 }
3729
3730 fn unit_interval() -> RealProblem {
3731 RealProblem {
3732 kinds: vec![FloatKind::F64],
3733 conds: vec![
3734 rcmp(CmpOp::Ge, RealExpr::Var(0), rk(1.0)),
3735 rcmp(CmpOp::Lt, RealExpr::Var(0), rk(2.0)),
3736 ],
3737 ..RealProblem::default()
3738 }
3739 }
3740
3741 #[test]
3742 fn real_variable_is_solved_with_the_integral_ones_and_the_seed_advances() {
3743 let c = vec![bin(BinOp::Eq, var(0, u(8)), k(u(8), 7), Ty::BIT)];
3745 let h = setup_real(0x7000_0c01, unit_interval(), c);
3746 let before = objects::prng_seed(h);
3747 let mut bins = [0u32; 4];
3748 for _ in 0..400 {
3749 assert_eq!(randomize(h), Outcome::Solved);
3750 let r = rget(h, 0);
3751 assert!((1.0..2.0).contains(&r), "{r}");
3752 assert_eq!(get(h, 2), 7);
3753 bins[((r - 1.0) * 4.0) as usize] += 1;
3754 }
3755 assert!(
3756 bins.iter().all(|&n| n > 60),
3757 "uniform over the interval: {bins:?}"
3758 );
3759 assert_ne!(objects::prng_seed(h), before);
3760 release(h);
3761 }
3762
3763 #[test]
3764 fn unsatisfiable_real_constraint_writes_nothing_and_keeps_the_seed() {
3765 let mut p = unit_interval();
3766 p.conds.push(rcmp(CmpOp::Gt, RealExpr::Var(0), rk(5.0)));
3767 let h = setup_real(0x7000_0c02, p, Vec::new());
3768 rset_bits(h, 0, 3.25f64.to_bits(), 0);
3769 set(h, 2, 99);
3770 let seed = objects::prng_seed(h);
3771 assert_eq!(randomize(h), Outcome::Unsatisfiable);
3772 assert_eq!(rget(h, 0), 3.25);
3773 assert_eq!(get(h, 2), 99, "the integral variable is untouched too");
3774 assert_eq!(objects::prng_seed(h), seed);
3775 release(h);
3776 }
3777
3778 #[test]
3779 fn inactive_real_variable_is_a_constant_and_is_not_written() {
3780 let h = setup_real(0x7000_0c03, unit_interval(), Vec::new());
3781 let mut m = objects::rand_meta(h).unwrap();
3782 m.rand_mode[0] = false;
3783 objects::set_rand_meta(h, m);
3784 rset_bits(h, 0, 1.5f64.to_bits(), 0);
3785 for _ in 0..10 {
3786 assert_eq!(randomize(h), Outcome::Solved);
3787 assert_eq!(rget(h, 0), 1.5);
3788 }
3789 rset_bits(h, 0, 2.0f64.to_bits(), 0);
3791 assert_eq!(randomize(h), Outcome::Unsatisfiable);
3792 assert_eq!(rget(h, 0), 2.0);
3793 rset_bits(h, 0, 0, 1);
3795 assert_eq!(randomize(h), Outcome::StateUnknown);
3796 release(h);
3797 }
3798
3799 #[test]
3800 fn real_state_variable_bounds_the_solution_and_division_by_zero_is_a_loud_error() {
3801 let p = RealProblem {
3803 kinds: vec![FloatKind::F64],
3804 conds: vec![
3805 rcmp(CmpOp::Ge, RealExpr::Var(0), rk(0.0)),
3806 rcmp(
3807 CmpOp::Lt,
3808 RealExpr::Var(0),
3809 RealExpr::State(0, FloatKind::F64),
3810 ),
3811 ],
3812 ..RealProblem::default()
3813 };
3814 let h = setup_real(0x7000_0c04, p, Vec::new());
3815 rset_bits(h, 3, 0.5f64.to_bits(), 0);
3816 for _ in 0..50 {
3817 assert_eq!(randomize(h), Outcome::Solved);
3818 assert!((0.0..0.5).contains(&rget(h, 0)));
3819 }
3820 rset_bits(h, 3, f64::NAN.to_bits(), 0);
3823 assert_eq!(randomize(h), Outcome::Unsatisfiable);
3824 rset_bits(h, 3, 0, 1);
3825 assert_eq!(randomize(h), Outcome::StateUnknown);
3826 release(h);
3827
3828 let p = RealProblem {
3830 kinds: vec![FloatKind::F64],
3831 conds: vec![rcmp(
3832 CmpOp::Lt,
3833 RealExpr::Div(
3834 Box::new(RealExpr::Var(0)),
3835 Box::new(RealExpr::State(0, FloatKind::F64)),
3836 ),
3837 rk(1.0),
3838 )],
3839 ..RealProblem::default()
3840 };
3841 let h = setup_real(0x7000_0c08, p, Vec::new());
3842 rset_bits(h, 3, 2.0f64.to_bits(), 0);
3843 assert_eq!(randomize(h), Outcome::Solved);
3844 assert!(rget(h, 0) < 2.0);
3845 rset_bits(h, 3, 0.0f64.to_bits(), 0);
3846 assert_eq!(randomize(h), Outcome::RealDomain);
3847 release(h);
3848 }
3849
3850 #[test]
3851 fn real_dist_follows_its_weights() {
3852 let p = RealProblem {
3855 kinds: vec![FloatKind::F64],
3856 dists: vec![RealDist {
3857 var: 0,
3858 items: vec![
3859 RealDistItem {
3860 span: RealSpan::Point(rk(1.0)),
3861 weight: k(u(32), 3),
3862 },
3863 RealDistItem {
3864 span: RealSpan::Range {
3865 lo: Some(rk(10.0)),
3866 hi: Some(rk(20.0)),
3867 },
3868 weight: k(u(32), 1),
3869 },
3870 ],
3871 default: None,
3872 }],
3873 ..RealProblem::default()
3874 };
3875 let h = setup_real(0x7000_0c05, p, Vec::new());
3876 let (mut point, mut range) = (0u32, 0u32);
3877 for _ in 0..2000 {
3878 assert_eq!(randomize(h), Outcome::Solved);
3879 let r = rget(h, 0);
3880 if r == 1.0 {
3881 point += 1;
3882 } else {
3883 assert!((10.0..=20.0).contains(&r), "{r}");
3884 range += 1;
3885 }
3886 }
3887 assert!((1400..=1600).contains(&point), "{point} / {range}");
3889 release(h);
3890 }
3891
3892 #[test]
3893 fn the_integral_solution_is_drawn_before_the_real_one() {
3894 let c = vec![bin(BinOp::Ge, var(0, u(8)), k(u(8), 3), Ty::BIT)];
3898 let (_, plain) = setup(0x7000_0c06, 1, c.clone());
3899 let h = setup_real(0x7000_0c07, unit_interval(), c);
3900 objects::set_prng_seed(h, objects::prng_seed(plain).unwrap());
3901 let seed = objects::prng_seed(plain).unwrap();
3902 assert_eq!(randomize(plain), Outcome::Solved);
3903 assert_eq!(randomize(h), Outcome::Solved);
3904 assert_eq!(get(plain, 0), get(h, 2));
3906 assert_ne!(
3907 objects::prng_seed(h),
3908 objects::prng_seed(plain),
3909 "real draws follow"
3910 );
3911 let mut m = objects::rand_meta(h).unwrap();
3913 m.rand_mode[0] = false;
3914 objects::set_rand_meta(h, m);
3915 rset_bits(h, 0, 1.5f64.to_bits(), 0);
3916 objects::set_prng_seed(h, seed);
3917 assert_eq!(randomize(h), Outcome::Solved);
3918 assert_eq!(objects::prng_seed(h), objects::prng_seed(plain));
3919 release(plain);
3920 release(h);
3921 }
3922}
3923
3924#[cfg(test)]
3926mod array_tests {
3927 use super::*;
3928 use crate::collections::{KeyKind, Shape};
3929 use crate::constraints::ir::{ArrayNode, BinOp, LoopId, ReduceOp};
3930 use crate::objects::{RandKind, RandProp, alloc, register_rand_props, release};
3931
3932 fn u8t() -> Ty {
3933 Ty::unsigned(8)
3934 }
3935 fn s32() -> Ty {
3936 Ty::signed(32)
3937 }
3938 fn bin(op: BinOp, a: Expr, b: Expr, t: Ty) -> Expr {
3939 Expr::Binary(op, Box::new(a), Box::new(b), t)
3940 }
3941 fn k(t: Ty, v: u64) -> Expr {
3942 Expr::constant(t, v)
3943 }
3944 fn node(n: ArrayNode) -> Expr {
3945 Expr::Array(n)
3946 }
3947 fn lp(id: LoopId) -> Expr {
3948 node(ArrayNode::Loop(id, s32()))
3949 }
3950 fn elem(a: u32, i: Expr) -> Expr {
3951 node(ArrayNode::Elem {
3952 array: a,
3953 index: Box::new(i),
3954 ty: u8t(),
3955 })
3956 }
3957 fn shape() -> Shape {
3958 Shape {
3959 elem_words: 1,
3960 assoc: false,
3961 key: KeyKind::Int {
3962 width: 32,
3963 signed: false,
3964 },
3965 obj: false,
3966 nested: collections::Nested::NONE,
3967 }
3968 }
3969 fn slot(o: usize) -> FieldSlot {
3970 FieldSlot {
3971 offset: o,
3972 words: 1,
3973 }
3974 }
3975 fn contents(h: u64) -> Vec<u64> {
3976 let mut b = [0u64];
3977 assert!(objects::field_get(h, slot(0), &mut b));
3978 collections::elements(b[0])
3979 .into_iter()
3980 .map(|e| e[0])
3981 .collect()
3982 }
3983
3984 fn setup(id: u32, conjuncts: Vec<Expr>, initial: &[u64]) -> u64 {
3987 let t = ClassTemplateId(id);
3988 register_rand_props(
3989 t,
3990 vec![
3991 RandProp {
3992 slot: slot(0),
3993 kind: RandKind::Rand,
3994 },
3995 RandProp {
3996 slot: slot(1),
3997 kind: RandKind::Rand,
3998 },
3999 ],
4000 );
4001 register_program(
4002 t,
4003 Ok(Program {
4004 vars: vec![ProgVar {
4005 ty: u8t(),
4006 slot: slot(1),
4007 home: Home::Instance,
4008 prop_index: Some(1),
4009 domain_conjunct: None,
4010 randc: false,
4011 window: None,
4012 }],
4013 conjuncts,
4014 arrays: vec![ProgArray {
4015 elem_ty: u8t(),
4016 elem_words: 1,
4017 slot: slot(0),
4018 home: Home::Instance,
4019 rand: true,
4020 prop_index: Some(0),
4021 elem_domain: None,
4022 assoc: false,
4023 nested: false,
4024 template_of: None,
4025 inner_fixed: 0,
4026 row_depth: 0,
4027 window: None,
4028 }],
4029 ..Program::default()
4030 }),
4031 );
4032 let h = alloc(t, 2);
4033 objects::set_prng_seed(h, 0xA11A_5EED);
4034 let c = collections::from_words(&shape(), initial);
4035 assert!(objects::field_set(h, slot(0), &[c]));
4036 h
4037 }
4038
4039 #[test]
4042 fn a_size_constraint_resizes_and_randomizes_every_element() {
4043 let size_eq = bin(
4044 BinOp::Eq,
4045 node(ArrayNode::Size(0)),
4046 Expr::Resize(Box::new(Expr::Var(0, u8t())), s32()),
4047 Ty::BIT,
4048 );
4049 let lo = bin(BinOp::Ge, Expr::Var(0, u8t()), k(u8t(), 2), Ty::BIT);
4050 let hi = bin(BinOp::Le, Expr::Var(0, u8t()), k(u8t(), 4), Ty::BIT);
4051 let each = node(ArrayNode::Foreach {
4052 array: 0,
4053 var: 0,
4054 body: Box::new(bin(
4055 BinOp::Gt,
4056 Expr::Resize(Box::new(elem(0, lp(0))), s32()),
4057 lp(0),
4058 Ty::BIT,
4059 )),
4060 });
4061 let h = setup(0x7a00_0001, vec![size_eq, lo, hi, each], &[9]);
4062 let mut sizes = std::collections::BTreeSet::new();
4063 for _ in 0..60 {
4064 assert_eq!(randomize(h, &[]), Outcome::Solved);
4065 let a = contents(h);
4066 let mut len = [0u64];
4067 assert!(objects::field_get(h, slot(1), &mut len));
4068 assert_eq!(a.len() as u64, len[0], "size == len");
4069 assert!((2..=4).contains(&a.len()));
4070 for (i, v) in a.iter().enumerate() {
4071 assert!(*v > i as u64, "element {i} = {v}");
4072 }
4073 sizes.insert(a.len());
4074 }
4075 assert_eq!(sizes.len(), 3, "every size 2..=4 is reached: {sizes:?}");
4076 release(h);
4077 }
4078
4079 #[test]
4082 fn an_unsized_array_keeps_its_length_but_gets_new_elements() {
4083 let each = node(ArrayNode::Foreach {
4084 array: 0,
4085 var: 0,
4086 body: Box::new(bin(BinOp::Ge, elem(0, lp(0)), k(u8t(), 200), Ty::BIT)),
4087 });
4088 let h = setup(0x7a00_0002, vec![each], &[1, 2, 3]);
4089 assert_eq!(randomize(h, &[]), Outcome::Solved);
4090 let a = contents(h);
4091 assert_eq!(a.len(), 3);
4092 assert!(a.iter().all(|v| *v >= 200), "{a:?}");
4093 release(h);
4094 }
4095
4096 #[test]
4099 fn an_inactive_array_is_state_and_unchanged() {
4100 let sum = node(ArrayNode::Reduce {
4101 op: ReduceOp::Sum,
4102 array: 0,
4103 var: 0,
4104 index_var: 1,
4105 body: Box::new(Expr::Resize(
4106 Box::new(node(ArrayNode::Loop(0, u8t()))),
4107 s32(),
4108 )),
4109 });
4110 let c = bin(
4112 BinOp::Eq,
4113 Expr::Resize(Box::new(Expr::Var(0, u8t())), s32()),
4114 sum,
4115 Ty::BIT,
4116 );
4117 let h = setup(0x7a00_0003, vec![c], &[3, 4, 5]);
4118 let mut m = objects::rand_meta(h).unwrap();
4119 m.rand_mode[0] = false;
4120 objects::set_rand_meta(h, m);
4121 assert_eq!(randomize(h, &[]), Outcome::Solved);
4122 assert_eq!(contents(h), vec![3, 4, 5]);
4123 let mut len = [0u64];
4124 assert!(objects::field_get(h, slot(1), &mut len));
4125 assert_eq!(len[0], 12);
4126 release(h);
4127 }
4128
4129 #[test]
4132 fn an_out_of_range_element_fails_loudly_and_changes_nothing() {
4133 let c = bin(BinOp::Eq, elem(0, k(s32(), 7)), k(u8t(), 1), Ty::BIT);
4134 let h = setup(0x7a00_0004, vec![c], &[1, 2]);
4135 let seed = objects::prng_seed(h);
4136 assert_eq!(randomize(h, &[]), Outcome::ArrayIndexOutOfRange);
4137 assert_eq!(contents(h), vec![1, 2]);
4138 assert_eq!(objects::prng_seed(h), seed);
4139 release(h);
4140 }
4141
4142 #[test]
4145 fn a_size_that_fails_the_element_constraints_is_retried() {
4146 let lo = bin(BinOp::Ge, node(ArrayNode::Size(0)), k(s32(), 1), Ty::BIT);
4147 let hi = bin(BinOp::Le, node(ArrayNode::Size(0)), k(s32(), 3), Ty::BIT);
4148 let guarded = Expr::Guard(
4151 Box::new(bin(
4152 BinOp::Gt,
4153 node(ArrayNode::Size(0)),
4154 k(s32(), 2),
4155 Ty::BIT,
4156 )),
4157 Box::new(bin(BinOp::Eq, elem(0, k(s32(), 2)), k(u8t(), 5), Ty::BIT)),
4158 Box::new(k(Ty::BIT, 0)),
4159 Ty::BIT,
4160 );
4161 let h = setup(0x7a00_0005, vec![lo, hi, guarded], &[]);
4162 for _ in 0..20 {
4163 assert_eq!(randomize(h, &[]), Outcome::Solved);
4164 let a = contents(h);
4165 assert_eq!(a.len(), 3);
4166 assert_eq!(a[2], 5);
4167 }
4168 release(h);
4169 }
4170
4171 #[test]
4172 fn an_impossible_size_is_unsatisfiable_not_an_error() {
4173 let c = bin(BinOp::Lt, node(ArrayNode::Size(0)), k(s32(), 0), Ty::BIT);
4174 let h = setup(0x7a00_0006, vec![c], &[1]);
4175 assert_eq!(randomize(h, &[]), Outcome::Unsatisfiable);
4176 assert_eq!(contents(h), vec![1]);
4177 release(h);
4178 }
4179
4180 #[test]
4184 fn a_rand_associative_array_keeps_its_keys_and_redraws_its_values() {
4185 use crate::collections::Key;
4186 let t = ClassTemplateId(0x7a00_0007);
4187 register_rand_props(
4188 t,
4189 vec![RandProp {
4190 slot: slot(0),
4191 kind: RandKind::Rand,
4192 }],
4193 );
4194 register_program(
4195 t,
4196 Ok(Program {
4197 arrays: vec![ProgArray {
4198 elem_ty: u8t(),
4199 elem_words: 1,
4200 slot: slot(0),
4201 home: Home::Instance,
4202 rand: true,
4203 prop_index: Some(0),
4204 elem_domain: None,
4205 assoc: true,
4206 nested: false,
4207 template_of: None,
4208 inner_fixed: 0,
4209 row_depth: 0,
4210 window: None,
4211 }],
4212 ..Program::default()
4213 }),
4214 );
4215 let h = alloc(t, 1);
4216 objects::set_prng_seed(h, 0xA550_0001);
4217 let src = collections::frozen_map(
4218 vec![
4219 (Key::Int(7), vec![1]),
4220 (Key::Int(3), vec![1]),
4221 (Key::Int(40), vec![1]),
4222 ],
4223 Some(vec![9]),
4224 );
4225 let c = collections::clone(src);
4226 assert!(objects::field_set(h, slot(0), &[c]));
4227 let mut seen = std::collections::BTreeSet::new();
4228 for _ in 0..30 {
4229 assert_eq!(randomize(h, &[]), Outcome::Solved);
4230 let mut b = [0u64];
4231 assert!(objects::field_get(h, slot(0), &mut b));
4232 assert_eq!(
4233 collections::keys(b[0]),
4234 vec![Key::Int(3), Key::Int(7), Key::Int(40)],
4235 "the keys never change"
4236 );
4237 assert_eq!(collections::size(b[0]), 3);
4238 let mut dflt = [0u64];
4239 assert!(collections::lookup(b[0], &Key::Int(1000), &mut dflt));
4240 assert_eq!(dflt[0], 9, "the default survives");
4241 for e in collections::elements(b[0]) {
4242 seen.insert(e[0]);
4243 }
4244 }
4245 assert!(seen.len() > 20, "the values are redrawn: {seen:?}");
4246 release(h);
4247 }
4248}
4249
4250#[cfg(test)]
4253mod mode_tests {
4254 use super::tests::{bin, get, k, setup, u, var};
4255 use super::*;
4256 use crate::constraints::ir::BinOp;
4257 use crate::objects::{alloc, register_rand_props, release};
4258
4259 fn slot(offset: usize) -> FieldSlot {
4260 FieldSlot { offset, words: 1 }
4261 }
4262
4263 fn randomize(h: u64) -> Outcome {
4264 super::randomize(h, &[])
4265 }
4266
4267 fn with_blocks(t: ClassTemplateId, blocks: Vec<ProgBlock>) {
4269 let mut p = program(t).unwrap().as_ref().clone().unwrap();
4270 p.blocks = blocks;
4271 register_program(t, Ok(p));
4272 }
4273
4274 fn block(home: Home, ordinal: u32, conjuncts: std::ops::Range<usize>) -> ProgBlock {
4275 ProgBlock {
4276 home,
4277 ordinal,
4278 conjuncts,
4279 items: BlockItems::default(),
4280 }
4281 }
4282
4283 #[test]
4286 fn a_constraint_block_switched_off_is_left_out_of_the_solve() {
4287 let c = vec![
4289 bin(BinOp::Gt, var(0, u(8)), k(u(8), 200), Ty::BIT),
4290 bin(BinOp::Lt, var(0, u(8)), k(u(8), 100), Ty::BIT),
4291 ];
4292 let (t, h) = setup(0x7c00_0001, 1, c);
4293 with_blocks(
4294 t,
4295 vec![
4296 block(Home::Instance, 0, 0..1),
4297 block(Home::Instance, 1, 1..2),
4298 ],
4299 );
4300 assert_eq!(randomize(h), Outcome::Unsatisfiable);
4301 assert!(objects::set_constraint_mode_of(h, 0, false));
4302 for _ in 0..20 {
4303 assert_eq!(randomize(h), Outcome::Solved);
4304 assert!(get(h, 0) < 100, "block 1 still holds");
4305 }
4306 assert!(objects::set_constraint_mode_of(h, 0, true));
4307 assert!(objects::set_constraint_mode_of(h, 1, false));
4308 for _ in 0..20 {
4309 assert_eq!(randomize(h), Outcome::Solved);
4310 assert!(get(h, 0) > 200, "block 0 holds again, block 1 is off");
4311 }
4312 assert!(objects::set_constraint_mode_of(h, 1, true));
4313 assert_eq!(randomize(h), Outcome::Unsatisfiable);
4314 release(h);
4315 }
4316
4317 #[test]
4320 fn constraint_mode_is_per_object() {
4321 let c = vec![
4322 bin(BinOp::Gt, var(0, u(8)), k(u(8), 200), Ty::BIT),
4323 bin(BinOp::Lt, var(0, u(8)), k(u(8), 100), Ty::BIT),
4324 ];
4325 let (t, a) = setup(0x7c00_0002, 1, c);
4326 with_blocks(
4327 t,
4328 vec![
4329 block(Home::Instance, 0, 0..1),
4330 block(Home::Instance, 1, 1..2),
4331 ],
4332 );
4333 let b = alloc(t, 2);
4334 objects::set_prng_seed(b, 7);
4335 assert!(objects::set_constraint_mode_of(a, 0, false));
4336 assert_eq!(randomize(a), Outcome::Solved);
4337 assert_eq!(randomize(b), Outcome::Unsatisfiable);
4338 release(a);
4339 release(b);
4340 }
4341
4342 #[test]
4345 fn a_static_blocks_mode_is_shared_by_every_instance() {
4346 let c = vec![
4347 bin(BinOp::Gt, var(0, u(8)), k(u(8), 200), Ty::BIT),
4348 bin(BinOp::Lt, var(0, u(8)), k(u(8), 100), Ty::BIT),
4349 ];
4350 let (t, a) = setup(0x7c00_0004, 1, c);
4351 with_blocks(
4352 t,
4353 vec![
4354 block(Home::Static(9), 0, 0..1),
4355 block(Home::Instance, 1, 1..2),
4356 ],
4357 );
4358 let b = alloc(t, 2);
4359 objects::set_prng_seed(b, 11);
4360 let st = alloc(ClassTemplateId(0x7c00_0104), 1);
4361 let pairs = [(9u32, st)];
4362 assert_eq!(super::randomize(a, &pairs), Outcome::Unsatisfiable);
4363 assert!(objects::set_constraint_mode_of(st, 0, false));
4364 assert_eq!(super::randomize(a, &pairs), Outcome::Solved);
4365 assert_eq!(super::randomize(b, &pairs), Outcome::Solved);
4366 assert_eq!(super::randomize(a, &[]), Outcome::Unsatisfiable);
4368 release(a);
4369 release(b);
4370 release(st);
4371 }
4372
4373 #[test]
4376 fn a_static_rand_mode_is_shared_and_makes_the_variable_state() {
4377 let t = ClassTemplateId(0x7c00_0005);
4378 register_rand_props(t, Vec::new());
4379 register_program(
4380 t,
4381 Ok(Program {
4382 vars: vec![ProgVar {
4383 ty: u(8),
4384 slot: slot(0),
4385 home: Home::Static(5),
4386 prop_index: None,
4387 domain_conjunct: None,
4388 randc: false,
4389 window: None,
4390 }],
4391 conjuncts: vec![bin(BinOp::Gt, var(0, u(8)), k(u(8), 200), Ty::BIT)],
4392 ..Program::default()
4393 }),
4394 );
4395 let a = alloc(t, 1);
4396 objects::set_prng_seed(a, 3);
4397 let st = alloc(ClassTemplateId(0x7c00_0105), 1);
4398 let pairs = [(5u32, st)];
4399 assert!(objects::field_set(st, slot(0), &[7]));
4400 assert_eq!(super::randomize(a, &pairs), Outcome::Solved);
4402 assert!(get(st, 0) > 200);
4403 assert!(objects::field_set(st, slot(0), &[7]));
4405 assert!(objects::set_static_rand_mode(st, 0, false));
4406 assert_eq!(super::randomize(a, &pairs), Outcome::Unsatisfiable);
4407 assert_eq!(get(st, 0), 7);
4408 assert!(objects::set_static_rand_mode(st, 0, true));
4409 assert_eq!(super::randomize(a, &pairs), Outcome::Solved);
4410 release(a);
4411 release(st);
4412 }
4413}
4414
4415#[cfg(test)]
4417mod func_tests {
4418 use super::*;
4419 use crate::constraints::func::{Stmt, tests::count_ones};
4420 use crate::constraints::ir::{ArrayNode, BinOp};
4421 use crate::objects::{RandKind, RandProp, alloc, register_rand_props, release};
4422
4423 fn u(w: u32) -> Ty {
4424 Ty::unsigned(w)
4425 }
4426 fn s32() -> Ty {
4427 Ty::signed(32)
4428 }
4429 fn var(i: u32, t: Ty) -> Expr {
4430 Expr::Var(i, t)
4431 }
4432 fn k(t: Ty, v: u64) -> Expr {
4433 Expr::constant(t, v)
4434 }
4435 fn bin(op: BinOp, a: Expr, b: Expr, t: Ty) -> Expr {
4436 Expr::Binary(op, Box::new(a), Box::new(b), t)
4437 }
4438 fn widen(x: Expr, t: Ty) -> Expr {
4439 Expr::Resize(Box::new(x), t)
4440 }
4441 fn call(func: u32, args: Vec<Expr>, ty: Ty) -> Expr {
4442 Expr::Array(ArrayNode::Call { func, args, ty })
4443 }
4444 fn slot(o: usize) -> FieldSlot {
4445 FieldSlot {
4446 offset: o,
4447 words: 1,
4448 }
4449 }
4450
4451 fn twice() -> Func {
4453 Func {
4454 name: "twice".into(),
4455 slots: vec![u(8), u(8)],
4456 n_params: 1,
4457 ret: 1,
4458 body: vec![Stmt::Assign(
4459 1,
4460 bin(BinOp::Add, var(0, u(8)), var(0, u(8)), u(8)),
4461 )],
4462 }
4463 }
4464
4465 fn zero() -> Func {
4467 Func {
4468 name: "zero".into(),
4469 slots: vec![u(8), u(8)],
4470 n_params: 1,
4471 ret: 1,
4472 body: vec![Stmt::Assign(1, k(u(8), 0))],
4473 }
4474 }
4475
4476 fn setup(id: u32, n: usize, funcs: Vec<Func>, conjuncts: Vec<Expr>) -> u64 {
4479 let t = ClassTemplateId(id);
4480 register_rand_props(
4481 t,
4482 (0..n)
4483 .map(|i| RandProp {
4484 slot: slot(i),
4485 kind: RandKind::Rand,
4486 })
4487 .collect(),
4488 );
4489 register_program(
4490 t,
4491 Ok(Program {
4492 vars: (0..n)
4493 .map(|i| ProgVar {
4494 ty: u(8),
4495 slot: slot(i),
4496 home: Home::Instance,
4497 prop_index: Some(i),
4498 domain_conjunct: None,
4499 randc: false,
4500 window: None,
4501 })
4502 .collect(),
4503 conjuncts,
4504 funcs,
4505 ..Program::default()
4506 }),
4507 );
4508 let h = alloc(t, n);
4509 objects::set_prng_seed(h, 0xF00D_0001);
4510 h
4511 }
4512
4513 fn get(h: u64, off: usize) -> u64 {
4514 let mut b = [0u64];
4515 assert!(objects::field_get(h, slot(off), &mut b));
4516 b[0]
4517 }
4518
4519 #[test]
4523 fn a_return_value_follows_its_argument_which_is_solved_first() {
4524 let c = bin(
4526 BinOp::Eq,
4527 widen(var(1, u(8)), s32()),
4528 call(0, vec![widen(var(0, u(8)), u(10))], s32()),
4529 Ty::BIT,
4530 );
4531 let h = setup(0x7a00_0008, 2, vec![count_ones()], vec![c]);
4532 let mut by_len = [0u32; 9];
4533 for _ in 0..400 {
4534 assert_eq!(randomize(h, &[]), Outcome::Solved);
4535 let (v, len) = (get(h, 0), get(h, 1));
4536 assert_eq!(len, u64::from(v.count_ones()), "len == count_ones({v})");
4537 by_len[len as usize] += 1;
4538 }
4539 assert!(by_len[4] > 60, "len 4 is the likeliest: {by_len:?}");
4540 assert!(by_len[0] < 10, "len 0 needs v == 0: {by_len:?}");
4541 release(h);
4542 }
4543
4544 #[test]
4548 fn the_argument_is_drawn_uniformly_before_the_variable_it_bounds() {
4549 let le = bin(
4551 BinOp::Le,
4552 widen(var(0, u(8)), u(8)),
4553 call(0, vec![var(1, u(8))], u(8)),
4554 Ty::BIT,
4555 );
4556 let y = var(1, u(8));
4557 let inside = [2u64, 4, 8]
4558 .into_iter()
4559 .map(|v| bin(BinOp::Eq, y.clone(), k(u(8), v), Ty::BIT))
4560 .reduce(|a, b| bin(BinOp::LogOr, a, b, Ty::BIT))
4561 .unwrap();
4562 let h = setup(0x7a00_0009, 2, vec![twice()], vec![le, inside]);
4563 let mut hits = [0u32; 3];
4564 for _ in 0..300 {
4565 assert_eq!(randomize(h, &[]), Outcome::Solved);
4566 let (x, y) = (get(h, 0), get(h, 1));
4567 assert!(x <= 2 * y, "x={x} y={y}");
4568 hits[match y {
4569 2 => 0,
4570 4 => 1,
4571 8 => 2,
4572 other => panic!("y = {other}"),
4573 }] += 1;
4574 }
4575 assert!(
4576 hits.iter().all(|&n| n > 75),
4577 "each y about a third of the time: {hits:?}"
4578 );
4579 release(h);
4580 }
4581
4582 #[test]
4584 fn a_circular_implicit_order_is_a_loud_outcome() {
4585 let cs = vec![
4586 bin(
4587 BinOp::Eq,
4588 var(0, u(8)),
4589 call(0, vec![var(1, u(8))], u(8)),
4590 Ty::BIT,
4591 ),
4592 bin(
4593 BinOp::Eq,
4594 var(1, u(8)),
4595 call(0, vec![var(0, u(8))], u(8)),
4596 Ty::BIT,
4597 ),
4598 ];
4599 let h = setup(0x7a00_000a, 2, vec![twice()], cs);
4600 let seed = objects::prng_seed(h);
4601 assert_eq!(randomize(h, &[]), Outcome::ImplicitOrderCycle);
4602 assert_eq!(objects::prng_seed(h), seed, "a failed call leaves the seed");
4603 release(h);
4604 }
4605
4606 #[test]
4609 fn an_unsatisfiable_check_is_function_search_not_a_wrong_value() {
4610 let c = bin(
4611 BinOp::Eq,
4612 call(0, vec![var(0, u(8))], u(8)),
4613 k(u(8), 1),
4614 Ty::BIT,
4615 );
4616 let h = setup(0x7a00_000b, 1, vec![zero()], vec![c]);
4617 assert_eq!(randomize(h, &[]), Outcome::FunctionSearch);
4618 release(h);
4619 }
4620
4621 #[test]
4623 fn a_satisfiable_check_is_found_by_sampling() {
4624 let c = bin(
4626 BinOp::Eq,
4627 call(0, vec![var(0, u(8))], u(8)),
4628 k(u(8), 6),
4629 Ty::BIT,
4630 );
4631 let h = setup(0x7a00_000c, 1, vec![twice()], vec![c]);
4632 for _ in 0..10 {
4633 assert_eq!(randomize(h, &[]), Outcome::Solved);
4634 assert!(matches!(get(h, 0), 3 | 131));
4635 }
4636 release(h);
4637 }
4638}
4639
4640#[cfg(test)]
4642mod scope_tests {
4643 use super::tests::{bin, k, u, var};
4644 use super::*;
4645 use crate::constraints::ir::BinOp;
4646
4647 fn scope_var(offset: usize, width: u32) -> ProgVar {
4648 ProgVar {
4649 ty: u(width),
4650 slot: FieldSlot {
4651 offset,
4652 words: (2 * width as usize).div_ceil(64).max(1),
4653 },
4654 home: Home::Instance,
4655 prop_index: None,
4656 domain_conjunct: None,
4657 randc: false,
4658 window: None,
4659 }
4660 }
4661
4662 fn captured_state(width: u32) -> ProgState {
4663 ProgState {
4664 class_of: false,
4665 root_bit: 0,
4666 ty: u(width),
4667 slot: FieldSlot {
4668 offset: 0,
4669 words: 1,
4670 },
4671 home: Home::Instance,
4672 via: Vec::new(),
4673 window: None,
4674 }
4675 }
4676
4677 fn register(vars: Vec<ProgVar>, states: Vec<ProgState>, conjuncts: Vec<Expr>) -> u32 {
4678 let captures = states
4679 .iter()
4680 .enumerate()
4681 .map(|(i, s)| Capture {
4682 state: i as u32,
4683 width: s.ty.width,
4684 })
4685 .collect();
4686 register_spec(Spec {
4687 frag: Program {
4688 vars,
4689 states,
4690 conjuncts,
4691 ..Program::default()
4692 },
4693 captures,
4694 only: None,
4695 })
4696 }
4697
4698 fn lt(a: Expr, b: Expr) -> Expr {
4699 bin(BinOp::Lt, a, b, Ty::BIT)
4700 }
4701
4702 #[test]
4703 fn a_scope_randomize_solves_without_any_object_and_returns_the_values() {
4704 let id = register(
4706 vec![scope_var(0, 8), scope_var(1, 8)],
4707 Vec::new(),
4708 vec![
4709 lt(var(0, u(8)), var(1, u(8))),
4710 bin(BinOp::Le, var(1, u(8)), k(u(8), 10), Ty::BIT),
4711 ],
4712 );
4713 let mut distinct = std::collections::BTreeSet::new();
4714 for seed in 1..60u64 {
4715 let mut out = [0xffu64; 2];
4716 assert_eq!(randomize_scope(id, &[], &mut out, seed), Outcome::Solved);
4717 assert!(out[0] < out[1] && out[1] <= 10, "{out:?}");
4718 distinct.insert((out[0], out[1]));
4719 }
4720 assert!(
4721 distinct.len() > 10,
4722 "the draw follows the seed: {distinct:?}"
4723 );
4724 }
4725
4726 #[test]
4727 fn the_same_seed_draws_the_same_values() {
4728 let id = register(
4729 vec![scope_var(0, 16)],
4730 Vec::new(),
4731 vec![lt(var(0, u(16)), k(u(16), 1000))],
4732 );
4733 let (mut a, mut b) = ([0u64], [0u64]);
4734 assert_eq!(randomize_scope(id, &[], &mut a, 77), Outcome::Solved);
4735 assert_eq!(randomize_scope(id, &[], &mut b, 77), Outcome::Solved);
4736 assert_eq!(a, b);
4737 }
4738
4739 #[test]
4740 fn captured_locals_are_constants_of_the_solve() {
4741 let id = register(
4743 vec![scope_var(0, 8)],
4744 vec![captured_state(8)],
4745 vec![bin(
4746 BinOp::Eq,
4747 var(0, u(8)),
4748 bin(BinOp::Add, Expr::State(0, u(8)), k(u(8), 1), u(8)),
4749 Ty::BIT,
4750 )],
4751 );
4752 for s in [0u64, 41, 200] {
4753 let mut out = [0u64];
4754 assert_eq!(randomize_scope(id, &[s], &mut out, 5), Outcome::Solved);
4755 assert_eq!(out[0], s + 1);
4756 }
4757 }
4758
4759 #[test]
4760 fn an_unsatisfiable_scope_randomize_writes_nothing() {
4761 let id = register(
4762 vec![scope_var(0, 8)],
4763 Vec::new(),
4764 vec![
4765 bin(BinOp::Gt, var(0, u(8)), k(u(8), 5), Ty::BIT),
4766 lt(var(0, u(8)), k(u(8), 3)),
4767 ],
4768 );
4769 let mut out = [0x77u64];
4770 assert_eq!(
4771 randomize_scope(id, &[], &mut out, 1),
4772 Outcome::Unsatisfiable
4773 );
4774 assert_eq!(out, [0x77], "a failed randomize changes nothing (18.6.1)");
4775 }
4776
4777 #[test]
4778 fn with_no_variable_the_call_is_a_checker() {
4779 let id = register(
4781 Vec::new(),
4782 vec![captured_state(8), captured_state(8)],
4783 vec![lt(Expr::State(0, u(8)), Expr::State(1, u(8)))],
4784 );
4785 let mut none: [u64; 0] = [];
4786 assert_eq!(randomize_scope(id, &[3, 9], &mut none, 1), Outcome::Solved);
4787 assert_eq!(
4788 randomize_scope(id, &[9, 3], &mut none, 1),
4789 Outcome::Unsatisfiable
4790 );
4791 assert_eq!(
4792 randomize_scope(id, &[4, 4], &mut none, 1),
4793 Outcome::Unsatisfiable
4794 );
4795 }
4796
4797 #[test]
4798 fn an_unknown_bit_in_a_captured_local_is_an_error_not_a_value() {
4799 let id = register(
4800 Vec::new(),
4801 vec![captured_state(8)],
4802 vec![lt(Expr::State(0, u(8)), k(u(8), 9))],
4803 );
4804 let mut none: [u64; 0] = [];
4805 let x_bit = 1u64 << 8;
4807 assert_eq!(
4808 randomize_scope(id, &[x_bit], &mut none, 1),
4809 Outcome::StateUnknown
4810 );
4811 }
4812
4813 #[test]
4814 fn a_wide_variable_spans_words_and_keeps_its_pinned_bits() {
4815 let wide = scope_var(0, 70);
4817 assert_eq!(wide.slot.words, 3);
4818 let top = bin(
4819 BinOp::Eq,
4820 Expr::Slice(Box::new(var(0, u(70))), 64, u(6)),
4821 k(u(6), 0x2a),
4822 Ty::BIT,
4823 );
4824 let id = register(vec![wide], Vec::new(), vec![top]);
4825 let mut seen_low = std::collections::BTreeSet::new();
4826 for seed in 1..20u64 {
4827 let mut out = [0u64; 3];
4828 assert_eq!(randomize_scope(id, &[], &mut out, seed), Outcome::Solved);
4829 assert_eq!(out[1] & 0x3f, 0x2a, "{out:x?}");
4830 assert_eq!(out[2], 0, "no unknown bits: {out:x?}");
4831 seen_low.insert(out[0]);
4832 }
4833 assert!(seen_low.len() > 10, "the other bits are random");
4834 }
4835
4836 #[test]
4837 fn a_call_that_does_not_match_its_spec_fails_loudly() {
4838 let id = register(
4839 vec![scope_var(0, 8)],
4840 vec![captured_state(8)],
4841 vec![lt(var(0, u(8)), Expr::State(0, u(8)))],
4842 );
4843 let spec = spec(id).expect("registered");
4844 assert_eq!((spec.scope_words(), spec.capture_words()), (1, 1));
4845 let mut one = [0u64];
4846 let mut two = [0u64; 2];
4847 assert_eq!(randomize_scope(id, &[], &mut one, 1), Outcome::Failed);
4848 assert_eq!(randomize_scope(id, &[5], &mut two, 1), Outcome::Failed);
4849 assert_eq!(randomize_scope(0, &[], &mut one, 1), Outcome::Failed);
4850 assert_eq!(randomize_scope(u32::MAX, &[], &mut one, 1), Outcome::Failed);
4851 }
4852}