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