1use crate::compile::ModuleTranslation;
28use crate::component::ExportItem;
29use crate::component::dfg::{
30 AdapterId, AdapterModuleId, ComponentDfg, CoreDef, Export, Instance, InstanceId, SideEffect,
31};
32use crate::prelude::*;
33use crate::union_find::UnionFind;
34use crate::{EntityIndex, EntityRef, FuncIndex, PrimaryMap, SecondaryMap, StaticModuleIndex};
35use core::mem;
36use std::collections::HashMap;
37use std::collections::hash_map::Entry;
38
39#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
43enum VmctxKey {
44 Component,
49
50 CoreInstance(InstanceId),
52
53 AdapterModule(AdapterModuleId),
58}
59
60#[derive(Clone, Debug, Default)]
63enum SameVmctxPartition {
64 #[default]
68 Top,
69
70 Known(UnionFind<FuncIndex>),
75}
76
77impl SameVmctxPartition {
78 fn bottom() -> Self {
81 SameVmctxPartition::Known(UnionFind::new())
82 }
83
84 fn from_keys(first: &mut HashMap<VmctxKey, FuncIndex>, keys: &[Option<VmctxKey>]) -> Self {
93 first.clear();
94 let mut sets = UnionFind::new();
95 for (i, key) in keys.iter().enumerate() {
96 let Some(key) = *key else { continue };
97 let func = FuncIndex::new(i);
98 match first.entry(key) {
99 Entry::Occupied(e) => {
100 sets.union(*e.get(), func);
101 }
102 Entry::Vacant(e) => {
103 e.insert(func);
104 }
105 }
106 }
107 SameVmctxPartition::Known(sets)
108 }
109
110 fn meet(groups: &mut HashMap<(FuncIndex, FuncIndex), FuncIndex>, a: Self, b: Self) -> Self {
116 use SameVmctxPartition::*;
117 match (a, b) {
118 (Top, p) | (p, Top) => p,
120
121 (Known(ref a), Known(ref b)) => {
122 let (small, large) = if a.len() <= b.len() { (a, b) } else { (b, a) };
130 let mut sets = UnionFind::new();
131
132 groups.clear();
135 for func in small.elems() {
136 if !large.contains(func) {
137 continue;
138 }
139 let group = (
140 a.find_without_path_compression(func),
141 b.find_without_path_compression(func),
142 );
143 match groups.entry(group) {
144 Entry::Occupied(e) => {
145 sets.union(*e.get(), func);
146 }
147 Entry::Vacant(e) => {
148 e.insert(func);
149 }
150 }
151 }
152
153 Known(sets)
154 }
155 }
156 }
157
158 fn representative(&self, func: FuncIndex) -> FuncIndex {
165 match self {
166 SameVmctxPartition::Top => FuncIndex::new(0),
168
169 SameVmctxPartition::Known(sets) => sets.set_min(func),
173 }
174 }
175}
176
177#[derive(Default)]
179struct SameVmctxBuilder {
180 partitions: SecondaryMap<StaticModuleIndex, SameVmctxPartition>,
184
185 scratch_first: HashMap<VmctxKey, FuncIndex>,
188
189 scratch_groups: HashMap<(FuncIndex, FuncIndex), FuncIndex>,
192}
193
194impl SameVmctxBuilder {
195 fn observe_instantiation(&mut self, module: StaticModuleIndex, keys: &[Option<VmctxKey>]) {
198 let partition = SameVmctxPartition::from_keys(&mut self.scratch_first, keys);
199 self.observe(module, partition);
200 }
201
202 fn observe_unknown_instantiation(&mut self, module: StaticModuleIndex) {
205 self.observe(module, SameVmctxPartition::bottom());
206 }
207
208 fn observe(&mut self, module: StaticModuleIndex, partition: SameVmctxPartition) {
209 let current = mem::take(&mut self.partitions[module]);
210 let met = SameVmctxPartition::meet(&mut self.scratch_groups, current, partition);
211 self.partitions[module] = met;
212 }
213
214 fn finish(self) -> SameVmctxImports {
216 SameVmctxImports {
217 partitions: self.partitions,
218 }
219 }
220}
221
222struct SameVmctxImports {
224 partitions: SecondaryMap<StaticModuleIndex, SameVmctxPartition>,
225}
226
227impl SameVmctxImports {
228 fn representative(&self, module: StaticModuleIndex, func: FuncIndex) -> FuncIndex {
231 self.partitions[module].representative(func)
232 }
233}
234
235pub fn analyze_same_vmctx_imports(
238 dfg: &ComponentDfg,
239 static_modules: &mut PrimaryMap<StaticModuleIndex, ModuleTranslation<'_>>,
240) {
241 let mut builder = SameVmctxBuilder::default();
242
243 let mut keys = Vec::new();
245 let mut stack = Vec::new();
246
247 for effect in dfg.side_effects.iter() {
250 let SideEffect::Instance(id, _) = effect else {
251 continue;
252 };
253
254 let Instance::Static(module, args) = &dfg.instances[*id] else {
255 continue;
257 };
258
259 observe_instantiation(&mut keys, &mut builder, dfg, static_modules, *module, args);
260 }
261
262 for (_, (module, args)) in dfg.adapter_modules.iter() {
265 observe_instantiation(&mut keys, &mut builder, dfg, static_modules, *module, args);
266 }
267
268 for (_, (export, _)) in dfg.exports.iter() {
270 observe_exported_modules(&mut stack, &mut builder, export);
271 }
272
273 let analysis = builder.finish();
274
275 for (module, translation) in static_modules.iter_mut() {
276 for i in 0..translation.module.num_imported_funcs {
277 let func = FuncIndex::new(i);
278 let representative = analysis.representative(module, func);
279 if representative != func {
280 translation.imported_func_vmctx_representative[func] = representative.into();
281 }
282 }
283 }
284}
285
286fn observe_instantiation(
291 keys: &mut Vec<Option<VmctxKey>>,
292 builder: &mut SameVmctxBuilder,
293 dfg: &ComponentDfg,
294 static_modules: &PrimaryMap<StaticModuleIndex, ModuleTranslation<'_>>,
295 module: StaticModuleIndex,
296 args: &[CoreDef],
297) {
298 let translation = &static_modules[module];
299 keys.clear();
300 keys.resize(translation.module.num_imported_funcs, None);
301
302 for (position, arg) in args.iter().enumerate() {
303 let Some(EntityIndex::Function(func)) = translation.module.import_index(position) else {
304 continue;
305 };
306 keys[func.index()] = vmctx_key(dfg, static_modules, arg);
307 }
308
309 builder.observe_instantiation(module, keys);
310}
311
312fn observe_exported_modules<'a>(
318 stack: &mut Vec<&'a Export>,
319 builder: &mut SameVmctxBuilder,
320 export: &'a Export,
321) {
322 stack.clear();
323 stack.push(export);
324
325 while let Some(export) = stack.pop() {
326 match export {
327 Export::ModuleStatic { index, .. } => builder.observe_unknown_instantiation(*index),
328
329 Export::Instance { exports, .. } => {
330 stack.extend(exports.iter().map(|(_, (export, _))| export));
331 }
332
333 Export::LiftedFunction { .. } | Export::ModuleImport { .. } | Export::Type(_) => {}
334 }
335 }
336}
337
338fn vmctx_key(
341 dfg: &ComponentDfg,
342 static_modules: &PrimaryMap<StaticModuleIndex, ModuleTranslation<'_>>,
343 def: &CoreDef,
344) -> Option<VmctxKey> {
345 let mut def = def;
350
351 let mut previous: Option<InstanceId> = None;
354
355 loop {
356 let export = match def {
359 CoreDef::Trampoline(_) | CoreDef::UnsafeIntrinsic(..) => {
362 return Some(VmctxKey::Component);
363 }
364
365 CoreDef::Adapter(id) => return adapter_vmctx_key(dfg, static_modules, *id),
366
367 CoreDef::InstanceFlags(_) => return None,
370
371 CoreDef::Export(export) => export,
372 };
373
374 if previous.is_some_and(|p| export.instance.index() >= p.index()) {
375 return None;
378 }
379 previous = Some(export.instance);
380
381 let Instance::Static(module, args) = &dfg.instances[export.instance] else {
382 return None;
384 };
385
386 let ExportItem::Index(index) = &export.item else {
387 return None;
390 };
391
392 let module = &static_modules[*module].module;
393
394 if !module.is_imported(*index) {
397 return Some(VmctxKey::CoreInstance(export.instance));
398 }
399
400 let position = module
403 .import_position(*index)
404 .expect("imported entities always have an associated import initializer");
405 def = &args[position];
406 }
407}
408
409fn adapter_vmctx_key(
412 dfg: &ComponentDfg,
413 static_modules: &PrimaryMap<StaticModuleIndex, ModuleTranslation<'_>>,
414 id: AdapterId,
415) -> Option<VmctxKey> {
416 let (adapter_module, index) = *dfg.adapter_partitionings.get(id)?;
417 let (static_module, _) = dfg.adapter_modules[adapter_module];
418
419 debug_assert!(
420 !static_modules[static_module].module.is_imported(index),
421 "adapter modules always define their exported adapters",
422 );
423
424 Some(VmctxKey::AdapterModule(adapter_module))
425}
426
427#[cfg(test)]
428mod tests {
429 use super::*;
430 use crate::property_check;
431 use mutatis::{Mutate, check::CheckResult, mutators as m};
432
433 impl SameVmctxPartition {
434 fn same_block(&self, a: FuncIndex, b: FuncIndex) -> bool {
436 self.representative(a) == self.representative(b)
438 }
439
440 fn blocks(&self, n: u32) -> Vec<Vec<u32>> {
444 let mut blocks = std::collections::BTreeMap::<u32, Vec<u32>>::new();
445 for i in 0..n {
446 let rep = self.representative(FuncIndex::from_u32(i)).as_u32();
447 blocks.entry(rep).or_default().push(i);
448 }
449 blocks.into_values().collect()
450 }
451
452 fn refines(&self, other: &Self, n: u32) -> bool {
455 (0..n).all(|i| {
456 (0..n).all(|j| {
457 let (a, b) = (FuncIndex::from_u32(i), FuncIndex::from_u32(j));
458 !self.same_block(a, b) || other.same_block(a, b)
459 })
460 })
461 }
462 }
463
464 fn key(i: u32) -> Option<VmctxKey> {
466 Some(VmctxKey::CoreInstance(InstanceId::from_u32(i)))
467 }
468
469 fn unknown() -> Option<VmctxKey> {
471 None
472 }
473
474 fn analyze(instantiations: &[&[Option<VmctxKey>]]) -> SameVmctxPartition {
477 let module = StaticModuleIndex::from_u32(0);
478 let mut builder = SameVmctxBuilder::default();
479 for keys in instantiations {
480 builder.observe_instantiation(module, keys);
481 }
482 builder.finish().partitions[module].clone()
483 }
484
485 fn blocks(n: u32, instantiations: &[&[Option<VmctxKey>]]) -> Vec<Vec<u32>> {
488 analyze(instantiations).blocks(n)
489 }
490
491 #[test]
492 fn a_module_that_is_never_instantiated_stays_at_top() {
493 let p = analyze(&[]);
496 assert!(matches!(p, SameVmctxPartition::Top));
497 assert_eq!(p.blocks(4), vec![vec![0, 1, 2, 3]]);
498
499 for i in 0..4 {
500 assert_eq!(
501 p.representative(FuncIndex::from_u32(i)),
502 FuncIndex::from_u32(0),
503 "import {i} should use import 0's `vmctx` slot",
504 );
505 }
506 }
507
508 #[test]
509 fn one_instantiation_with_all_imports_from_one_instance() {
510 assert_eq!(
511 blocks(4, &[&[key(0), key(0), key(0), key(0)]]),
512 vec![vec![0, 1, 2, 3]],
513 );
514 }
515
516 #[test]
517 fn one_instantiation_with_all_imports_from_distinct_instances() {
518 assert_eq!(
519 blocks(4, &[&[key(0), key(1), key(2), key(3)]]),
520 vec![vec![0], vec![1], vec![2], vec![3]],
521 );
522 }
523
524 #[test]
525 fn one_instantiation_split_across_two_instances() {
526 assert_eq!(
527 blocks(4, &[&[key(9), key(9), key(5), key(5)]]),
528 vec![vec![0, 1], vec![2, 3]],
529 );
530 }
531
532 #[test]
533 fn one_instantiation_with_interleaved_instances() {
534 assert_eq!(
536 blocks(4, &[&[key(0), key(1), key(0), key(1)]]),
537 vec![vec![0, 2], vec![1, 3]],
538 );
539 }
540
541 #[test]
542 fn unknown_keys_are_singletons() {
543 assert_eq!(
546 blocks(5, &[&[key(0), unknown(), key(0), unknown(), key(0)]]),
547 vec![vec![0, 2, 4], vec![1], vec![3]],
548 );
549 }
550
551 #[test]
552 fn distinct_key_variants_are_distinct_keys() {
553 let component = Some(VmctxKey::Component);
556 let instance = Some(VmctxKey::CoreInstance(InstanceId::from_u32(0)));
557 let adapter = Some(VmctxKey::AdapterModule(AdapterModuleId::from_u32(0)));
558 assert_eq!(
559 blocks(
560 6,
561 &[&[component, instance, adapter, component, instance, adapter]]
562 ),
563 vec![vec![0, 3], vec![1, 4], vec![2, 5]],
564 );
565 }
566
567 #[test]
568 fn two_agreeing_instantiations_change_nothing() {
569 assert_eq!(
571 blocks(
572 4,
573 &[
574 &[key(0), key(0), key(1), key(1)],
575 &[key(7), key(7), key(8), key(8)],
576 ],
577 ),
578 vec![vec![0, 1], vec![2, 3]],
579 );
580 }
581
582 #[test]
583 fn a_disagreeing_instantiation_breaks_everything_apart() {
584 assert_eq!(
585 blocks(
586 4,
587 &[
588 &[key(0), key(0), key(0), key(0)],
589 &[key(0), key(1), key(2), key(3)],
590 ],
591 ),
592 vec![vec![0], vec![1], vec![2], vec![3]],
593 );
594 }
595
596 #[test]
597 fn a_partially_disagreeing_instantiation_keeps_what_it_agrees_on() {
598 assert_eq!(
599 blocks(
600 4,
601 &[
602 &[key(0), key(0), key(0), key(0)],
603 &[key(0), key(0), key(1), key(1)],
604 ],
605 ),
606 vec![vec![0, 1], vec![2, 3]],
607 );
608 }
609
610 #[test]
611 fn crossing_splits_meet_to_all_singletons() {
612 assert_eq!(
615 blocks(
616 4,
617 &[
618 &[key(0), key(0), key(1), key(1)],
619 &[key(0), key(1), key(0), key(1)],
620 ],
621 ),
622 vec![vec![0], vec![1], vec![2], vec![3]],
623 );
624 }
625
626 #[test]
627 fn partially_crossing_splits_keep_their_common_refinement() {
628 assert_eq!(
630 blocks(
631 6,
632 &[
633 &[key(0), key(0), key(0), key(1), key(1), key(1)],
634 &[key(0), key(0), key(1), key(1), key(2), key(2)],
635 ],
636 ),
637 vec![vec![0, 1], vec![2], vec![3], vec![4, 5]],
638 );
639 }
640
641 #[test]
642 fn progressive_refinement_is_order_independent() {
643 let a: &[Option<VmctxKey>] = &[key(0), key(0), key(0), key(0), key(0), key(0)];
644 let b: &[Option<VmctxKey>] = &[key(0), key(0), key(0), key(1), key(1), key(1)];
645 let c: &[Option<VmctxKey>] = &[key(0), key(0), key(1), key(1), key(2), key(2)];
646
647 let expected = vec![vec![0, 1], vec![2], vec![3], vec![4, 5]];
648 for order in [
649 [a, b, c],
650 [a, c, b],
651 [b, a, c],
652 [b, c, a],
653 [c, a, b],
654 [c, b, a],
655 ] {
656 assert_eq!(blocks(6, &order), expected, "order {order:?} disagreed");
657 }
658 }
659
660 #[test]
661 fn an_unknown_instantiation_forces_bottom_and_cannot_be_undone() {
662 let module = StaticModuleIndex::from_u32(0);
663 let mut builder = SameVmctxBuilder::default();
664
665 builder.observe_instantiation(module, &[key(0), key(0), key(0)]);
666 builder.observe_unknown_instantiation(module);
667 builder.observe_instantiation(module, &[key(0), key(0), key(0)]);
669
670 let p = builder.finish().partitions[module].clone();
671 assert_eq!(p.blocks(3), SameVmctxPartition::bottom().blocks(3));
672 }
673
674 #[test]
675 fn modules_do_not_interfere_with_each_other() {
676 let a = StaticModuleIndex::from_u32(0);
677 let b = StaticModuleIndex::from_u32(3);
678 let mut builder = SameVmctxBuilder::default();
679
680 builder.observe_instantiation(a, &[key(0), key(0)]);
681 builder.observe_instantiation(b, &[key(0), key(1)]);
682
683 let analysis = builder.finish();
684 assert_eq!(analysis.partitions[a].blocks(2), vec![vec![0, 1]]);
685 assert_eq!(analysis.partitions[b].blocks(2), vec![vec![0], vec![1]]);
686 assert_eq!(
688 analysis.partitions[StaticModuleIndex::from_u32(1)].blocks(2),
689 vec![vec![0, 1]],
690 );
691 }
692
693 #[test]
694 fn degenerate_numbers_of_imports() {
695 assert_eq!(blocks(0, &[]), Vec::<Vec<u32>>::new());
698 assert_eq!(blocks(0, &[&[]]), Vec::<Vec<u32>>::new());
699
700 assert_eq!(blocks(1, &[]), vec![vec![0]]);
703 assert_eq!(blocks(1, &[&[key(0)]]), vec![vec![0]]);
704 assert_eq!(blocks(1, &[&[unknown()]]), vec![vec![0]]);
705 assert_eq!(blocks(1, &[&[key(0)], &[key(1)]]), vec![vec![0]]);
706 }
707
708 #[test]
709 fn a_representative_is_always_its_blocks_least_member() {
710 let p = analyze(&[&[key(1), key(0), key(1), key(0), key(1)]]);
711 let rep = |i| p.representative(FuncIndex::from_u32(i)).as_u32();
712 assert_eq!(rep(0), 0);
713 assert_eq!(rep(2), 0);
714 assert_eq!(rep(4), 0);
715 assert_eq!(rep(1), 1);
716 assert_eq!(rep(3), 1);
717
718 let p = analyze(&[&[unknown(), key(0), key(1), key(0), key(1)]]);
720 let rep = |i| p.representative(FuncIndex::from_u32(i)).as_u32();
721 assert_eq!(rep(0), 0);
722 assert_eq!(rep(1), 1);
723 assert_eq!(rep(3), 1);
724 assert_eq!(rep(2), 2);
725 assert_eq!(rep(4), 2);
726 }
727
728 #[test]
729 fn many_imports_in_many_blocks() {
730 let keys = (0..64).map(|i| key(i % 8)).collect::<Vec<_>>();
733 let expected = (0..8)
734 .map(|b| (0..8).map(|i| b + i * 8).collect::<Vec<u32>>())
735 .collect::<Vec<_>>();
736 assert_eq!(blocks(64, &[&keys]), expected);
737
738 let contiguous = (0..64).map(|i| key(i / 8)).collect::<Vec<_>>();
742 assert_eq!(
743 blocks(64, &[&keys, &contiguous]),
744 (0..64).map(|i| vec![i]).collect::<Vec<_>>(),
745 );
746 }
747
748 const N: u32 = 6;
752
753 fn partition(labels: &[Option<u32>]) -> SameVmctxPartition {
756 let keys = labels
757 .iter()
758 .map(|l| match l {
759 Some(l) => key(*l),
760 None => unknown(),
761 })
762 .collect::<Vec<_>>();
763 SameVmctxPartition::from_keys(&mut HashMap::new(), &keys)
764 }
765
766 fn decode(bytes: &[u8]) -> SameVmctxPartition {
773 let byte = |i: usize| bytes.get(i).copied().unwrap_or(0);
774 match byte(0) % 8 {
775 0 => SameVmctxPartition::Top,
776 1 => SameVmctxPartition::bottom(),
777 _ => {
778 let labels = (0..N)
779 .map(|i| match byte(1 + i as usize) % 5 {
780 4 => None,
782 l => Some(u32::from(l)),
783 })
784 .collect::<Vec<_>>();
785 partition(&labels)
786 }
787 }
788 }
789
790 fn decode3(bytes: &[u8]) -> (SameVmctxPartition, SameVmctxPartition, SameVmctxPartition) {
792 let chunk = 1 + N as usize;
793 let at = |i: usize| decode(bytes.get(i * chunk..).unwrap_or(&[]));
794 (at(0), at(1), at(2))
795 }
796
797 fn meet(a: &SameVmctxPartition, b: &SameVmctxPartition) -> SameVmctxPartition {
800 SameVmctxPartition::meet(&mut HashMap::new(), a.clone(), b.clone())
801 }
802
803 fn reference_meet(a: &SameVmctxPartition, b: &SameVmctxPartition) -> Vec<Vec<u32>> {
807 let mut assigned = vec![false; N as usize];
808 let mut blocks = Vec::new();
809 for i in 0..N {
810 if assigned[i as usize] {
811 continue;
812 }
813 let mut block = Vec::new();
814 for j in i..N {
815 let (x, y) = (FuncIndex::from_u32(i), FuncIndex::from_u32(j));
816 if !assigned[j as usize] && a.same_block(x, y) && b.same_block(x, y) {
817 assigned[j as usize] = true;
818 block.push(j);
819 }
820 }
821 blocks.push(block);
822 }
823 blocks
824 }
825
826 #[test]
827 fn top_and_bottom_are_the_lattice_bounds() {
828 let top = SameVmctxPartition::Top;
829 let bottom = SameVmctxPartition::bottom();
830 let p = partition(&[Some(0), Some(0), Some(1), Some(1), None, None]);
831
832 assert_eq!(meet(&top, &p).blocks(N), p.blocks(N));
833 assert_eq!(meet(&p, &top).blocks(N), p.blocks(N));
834 assert_eq!(meet(&bottom, &p).blocks(N), bottom.blocks(N));
835 assert_eq!(meet(&p, &bottom).blocks(N), bottom.blocks(N));
836
837 assert!(p.refines(&top, N));
838 assert!(bottom.refines(&p, N));
839 assert!(!top.refines(&p, N));
840 assert!(!p.refines(&bottom, N));
841 }
842
843 #[test]
844 fn meet_is_idempotent() {
845 for p in [
846 SameVmctxPartition::Top,
847 SameVmctxPartition::bottom(),
848 partition(&[Some(0), Some(0), Some(1), Some(1), Some(2), None]),
849 partition(&[Some(3), Some(3), Some(3), Some(3), Some(3), Some(3)]),
850 ] {
851 assert_eq!(meet(&p, &p).blocks(N), p.blocks(N));
852 }
853 }
854
855 #[test]
856 fn meet_laws_hold_on_random_partitions() -> CheckResult<Vec<u8>> {
857 let mutator = m::default::<Vec<u8>>().map(|_ctx, bytes| {
858 bytes.truncate(3 * (1 + N as usize));
859 Ok(())
860 });
861
862 property_check().run_with(mutator, [Vec::new()], |bytes| {
863 let (a, b, c) = decode3(bytes);
864
865 let ab = meet(&a, &b);
867 let ba = meet(&b, &a);
868 assert_eq!(ab.blocks(N), ba.blocks(N), "meet is not commutative");
869
870 let ab_c = meet(&ab, &c);
872 let a_bc = meet(&a, &meet(&b, &c));
873 assert_eq!(ab_c.blocks(N), a_bc.blocks(N), "meet is not associative");
874
875 assert_eq!(
877 meet(&a, &a).blocks(N),
878 a.blocks(N),
879 "meet is not idempotent"
880 );
881
882 assert_eq!(
884 ab.blocks(N),
885 reference_meet(&a, &b),
886 "meet disagrees with the reference meet",
887 );
888
889 assert!(ab.refines(&a, N), "meet does not refine its left operand");
891 assert!(ab.refines(&b, N), "meet does not refine its right operand");
892
893 if c.refines(&a, N) && c.refines(&b, N) {
895 assert!(
896 c.refines(&ab, N),
897 "meet is not the greatest lower bound of its operands",
898 );
899 }
900
901 assert!(
904 meet(&ab, &c).refines(&meet(&a, &c), N),
905 "meet is not monotone in its left argument",
906 );
907 assert!(
908 meet(&c, &ab).refines(&meet(&c, &a), N),
909 "meet is not monotone in its right argument",
910 );
911
912 assert_eq!(
914 a.refines(&b, N),
915 ab.blocks(N) == a.blocks(N),
916 "`a <= b` and `a /\\ b == a` disagree",
917 );
918
919 Ok::<_, String>(())
920 })
921 }
922
923 #[test]
924 fn representatives_are_least_members_on_random_partitions() -> CheckResult<Vec<u8>> {
925 let mutator = m::default::<Vec<u8>>().map(|_ctx, bytes| {
926 bytes.truncate(1 + N as usize);
927 Ok(())
928 });
929
930 property_check().run_with(mutator, [Vec::new()], |bytes| {
931 let p = decode(bytes);
932 for block in p.blocks(N) {
933 let least = *block.first().unwrap();
934 for i in block {
935 assert_eq!(
936 p.representative(FuncIndex::from_u32(i)).as_u32(),
937 least,
938 "every member of a block must name the same representative",
939 );
940 }
941 }
942 Ok::<_, String>(())
943 })
944 }
945}