diff --git a/Cargo.lock b/Cargo.lock index 46c2a87482a9..dd169b358026 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -633,6 +633,7 @@ name = "cranelift-control" version = "0.96.0" dependencies = [ "arbitrary", + "yoke", ] [[package]] @@ -4585,6 +4586,23 @@ dependencies = [ "zeroize", ] +[[package]] +name = "yoke" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1848075a23a28f9773498ee9a0f2cf58fcbad4f8c0ccf84a210ab33c6ae495de" +dependencies = [ + "serde", + "stable_deref_trait", + "zerofrom", +] + +[[package]] +name = "zerofrom" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df54d76c3251de27615dfcce21e636c172dafb2549cd7fd93e21c66f6ca6bea2" + [[package]] name = "zeroize" version = "1.4.3" diff --git a/cranelift/control/Cargo.toml b/cranelift/control/Cargo.toml index a830864594b3..b29996384e82 100644 --- a/cranelift/control/Cargo.toml +++ b/cranelift/control/Cargo.toml @@ -11,6 +11,7 @@ edition.workspace = true [dependencies] arbitrary = { version = "1.3.0" } +yoke = "0.7.1" [features] diff --git a/cranelift/control/src/chaos.rs b/cranelift/control/src/chaos.rs index 0fda3304f6ee..90bbdceee602 100644 --- a/cranelift/control/src/chaos.rs +++ b/cranelift/control/src/chaos.rs @@ -1,26 +1,47 @@ use arbitrary::{Arbitrary, Unstructured}; +use yoke::Yoke; /// The control plane of chaos mode. /// Please see the [crate-level documentation](crate). -#[derive(Debug, Clone, Default)] pub struct ControlPlane { - data: Vec, - /// This is used as a little optimization to avoid additional heap - /// allocations when using `Unstructured` internally. See the source of - /// [`ControlPlane::shuffle`] for an example. - tmp: Vec, + // The cart is the vector of pseudo-random data generated by a fuzzer. + // The yoke is the slice of unused bytes in that vector. + data: Yoke<&'static [u8], Vec>, +} + +impl Default for ControlPlane { + fn default() -> Self { + Self::new(Vec::new()) + } +} + +impl Clone for ControlPlane { + fn clone(&self) -> Self { + Self::new(self.data.get().to_vec()) + } +} + +impl std::fmt::Debug for ControlPlane { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("ControlPlane") + .field("unused_bytes", &self.data.get()) + .field("backing_vector", self.data.backing_cart()) + .finish() + } } impl Arbitrary<'_> for ControlPlane { fn arbitrary<'a>(u: &mut arbitrary::Unstructured<'a>) -> arbitrary::Result { - Ok(Self { - data: u.arbitrary()?, - tmp: Vec::new(), - }) + u.arbitrary().map(Self::new) } } impl ControlPlane { + fn new(data: Vec) -> Self { + let data = Yoke::attach_to_cart(data, |slice| slice); + Self { data } + } + /// Returns a pseudo-random boolean if the control plane was constructed /// with `arbitrary`. /// @@ -28,7 +49,14 @@ impl ControlPlane { /// pseudo-random data is exhausted or the control plane was constructed /// with `default`. pub fn get_decision(&mut self) -> bool { - self.data.pop().unwrap_or_default() & 1 == 1 + let mut res = false; + let data = std::mem::take(self).data.map_project(|unused_bytes, _| { + let mut u = Unstructured::new(unused_bytes); + res = u.arbitrary().unwrap_or_default(); + u.take_rest() + }); + *self = Self { data }; + res } /// Shuffles the items in the slice into a pseudo-random permutation if @@ -38,25 +66,23 @@ impl ControlPlane { /// performed if the pseudo-random data is exhausted or the control /// plane was constructed with `default`. pub fn shuffle(&mut self, slice: &mut [T]) { - let mut u = Unstructured::new(&self.data); - - // adapted from: - // https://docs.rs/arbitrary/1.3.0/arbitrary/struct.Unstructured.html#examples-1 - let mut to_permute = &mut slice[..]; - - while to_permute.len() > 1 { - if let Ok(idx) = u.choose_index(to_permute.len()) { - to_permute.swap(0, idx); - to_permute = &mut to_permute[1..]; - } else { - break; - } - } + let data = std::mem::take(self).data.map_project(|unused_bytes, _| { + let mut u = Unstructured::new(unused_bytes); + + // adapted from: + // https://docs.rs/arbitrary/1.3.0/arbitrary/struct.Unstructured.html#examples-1 + let mut to_permute = &mut slice[..]; - // take remaining bytes - let rest = u.take_rest(); - self.tmp.resize(rest.len(), 0); // allocates once per control plane - self.tmp.copy_from_slice(rest); - std::mem::swap(&mut self.data, &mut self.tmp); + while to_permute.len() > 1 { + if let Ok(idx) = u.choose_index(to_permute.len()) { + to_permute.swap(0, idx); + to_permute = &mut to_permute[1..]; + } else { + break; + } + } + u.take_rest() + }); + *self = Self { data }; } }