From c968e9f5c2a263fa323f37a40e8536a857ac8287 Mon Sep 17 00:00:00 2001 From: Mikhail Volkhov Date: Mon, 20 May 2024 12:21:24 +0100 Subject: [PATCH 1/3] [#2231] Add array allocators/helpers & clean up code --- ivc/src/ivc/interpreter.rs | 87 ++++++++++---------------- ivc/src/ivc/mod.rs | 34 +++++------ utils/src/array.rs | 122 +++++++++++++++++++++++++++++++++++++ utils/src/lib.rs | 1 + 4 files changed, 171 insertions(+), 73 deletions(-) create mode 100644 utils/src/array.rs diff --git a/ivc/src/ivc/interpreter.rs b/ivc/src/ivc/interpreter.rs index 899bbb55c2..b17cfa96e2 100644 --- a/ivc/src/ivc/interpreter.rs +++ b/ivc/src/ivc/interpreter.rs @@ -257,8 +257,9 @@ where Ff: PrimeField, Env: MultiRowReadCap + LookupCap>, { - let mut comms_limbs: [[Vec>; 3]; 3] = - std::array::from_fn(|_| std::array::from_fn(|_| vec![])); + let mut comms_limbs_s: [Vec>; 3] = std::array::from_fn(|_| vec![]); + let mut comms_limbs_l: [Vec>; 3] = std::array::from_fn(|_| vec![]); + let mut comms_limbs_xl: [Vec>; 3] = std::array::from_fn(|_| vec![]); for _block_row_i in 0..(3 * N_COL_TOTAL) { let row_num = env.curr_row(); @@ -274,41 +275,19 @@ where let (limbs_small, limbs_large, limbs_xlarge) = write_inputs_row(env, target_comms, row_num_local); - comms_limbs[0][comtype].push(limbs_small); - comms_limbs[1][comtype].push(limbs_large); - comms_limbs[2][comtype].push(limbs_xlarge); + comms_limbs_s[comtype].push(limbs_small); + comms_limbs_l[comtype].push(limbs_large); + comms_limbs_xl[comtype].push(limbs_xlarge); constrain_inputs(env); env.next_row(); } - // Transforms nested Vec> into fixed-size arrays. Returns - // Left-Right-Output for a given limb size. - fn repack_output( - input: [Vec>; 3], - ) -> Box<[[[F; TWO_LIMB_SIZE]; N_COL_TOTAL]; 3]> { - Box::new( - input - .into_iter() - .map(|vector: Vec>| { - vector - .into_iter() - .map(|subvec: Vec<_>| subvec.try_into().unwrap()) - .collect::>() - .try_into() - .unwrap() - }) - .collect::>() - .try_into() - .unwrap(), - ) - } - ( - repack_output(comms_limbs[0].clone()), - repack_output(comms_limbs[1].clone()), - repack_output(comms_limbs[2].clone()), + o1_utils::array::vec_to_boxed_array3(comms_limbs_s.to_vec()), + o1_utils::array::vec_to_boxed_array3(comms_limbs_l.to_vec()), + o1_utils::array::vec_to_boxed_array3(comms_limbs_xl.to_vec()), ) } @@ -637,32 +616,28 @@ pub fn process_ecadds( let r_hat_large: Box<[[F; 2 * N_LIMBS_LARGE]; N_COL_TOTAL]> = Box::new(comms_large[1]); // Compute error and t terms limbs. - let error_terms_large: [[F; 2 * N_LIMBS_LARGE]; 3] = error_terms - .iter() - .map(|(x, y)| { - limb_decompose_ff::(x) - .into_iter() - .chain(limb_decompose_ff::(y)) - .collect::>() - .try_into() - .unwrap() - }) - .collect::>() - .try_into() - .unwrap(); - let t_terms_large: [[F; 2 * N_LIMBS_LARGE]; 2] = t_terms - .iter() - .map(|(x, y)| { - limb_decompose_ff::(x) - .into_iter() - .chain(limb_decompose_ff::(y)) - .collect::>() - .try_into() - .unwrap() - }) - .collect::>() - .try_into() - .unwrap(); + let error_terms_large: Box<[[F; 2 * N_LIMBS_LARGE]; 3]> = o1_utils::array::vec_to_boxed_array2( + error_terms + .iter() + .map(|(x, y)| { + limb_decompose_ff::(x) + .into_iter() + .chain(limb_decompose_ff::(y)) + .collect() + }) + .collect(), + ); + let t_terms_large: Box<[[F; 2 * N_LIMBS_LARGE]; 2]> = o1_utils::array::vec_to_boxed_array2( + t_terms + .iter() + .map(|(x, y)| { + limb_decompose_ff::(x) + .into_iter() + .chain(limb_decompose_ff::(y)) + .collect() + }) + .collect(), + ); // E_R' = r·T_0 + r^2·T_1 + r^3·E_R // FIXME for now stubbed and just equal to E_L diff --git a/ivc/src/ivc/mod.rs b/ivc/src/ivc/mod.rs index 2776e04e73..eb09a2f852 100644 --- a/ivc/src/ivc/mod.rs +++ b/ivc/src/ivc/mod.rs @@ -29,6 +29,7 @@ mod tests { witness::Witness, BaseSponge, Ff1, Fp, OpeningProof, ScalarSponge, BN254, }; + use o1_utils::box_array; use poly_commitment::pairing_proof::PairingSRS; use rand::{CryptoRng, RngCore}; @@ -50,8 +51,6 @@ mod tests { 0, LT, >; - //type IVCWitnessBuilderEnv = IVCWitnessBuilderEnvRaw>; - //type IVCWitnessBuilderEnvDummy = IVCWitnessBuilderEnvRaw; impl PoseidonParams for PoseidonBN254Parameters @@ -78,32 +77,31 @@ mod tests { ) -> IVCWitnessBuilderEnvRaw { let mut witness_env = IVCWitnessBuilderEnvRaw::::create(); - // To support less rows than domain_size we need to have selectors. - //let row_num = rng.gen_range(0..domain_size); + let mut comms_left: Box<_> = box_array![(Ff1::zero(),Ff1::zero()); TEST_N_COL_TOTAL]; + let mut comms_right: Box<_> = box_array![(Ff1::zero(),Ff1::zero()); TEST_N_COL_TOTAL]; + let mut comms_output: Box<_> = box_array![(Ff1::zero(),Ff1::zero()); TEST_N_COL_TOTAL]; - let comms_left: Box<[_; TEST_N_COL_TOTAL]> = Box::new(core::array::from_fn(|_i| { - ( + for i in 0..TEST_N_COL_TOTAL { + comms_left[i] = ( ::rand(rng), ::rand(rng), - ) - })); - let comms_right: Box<[_; TEST_N_COL_TOTAL]> = Box::new(core::array::from_fn(|_i| { - ( + ); + comms_right[i] = ( ::rand(rng), ::rand(rng), - ) - })); - let comms_output: Box<[_; TEST_N_COL_TOTAL]> = Box::new(core::array::from_fn(|_i| { - ( + ); + comms_output[i] = ( ::rand(rng), ::rand(rng), - ) - })); + ); + } + println!("Building fixed selectors"); let fixed_selectors: Vec> = build_selectors::<_, TEST_N_COL_TOTAL, TEST_N_CHALS>(domain_size); witness_env.set_fixed_selectors(fixed_selectors.to_vec()); + println!("Calling the IVC circuit"); // TODO add nonzero E/T values. ivc_circuit::<_, _, _, _, TEST_N_COL_TOTAL>( &mut SubEnvLookup::new(&mut witness_env, lt_lens), @@ -113,7 +111,7 @@ mod tests { [(Ff1::zero(), Ff1::zero()); 3], [(Ff1::zero(), Ff1::zero()); 2], Fp::zero(), - vec![Fp::zero(); 200], + vec![Fp::zero(); TEST_N_CHALS], &PoseidonBN254Parameters, TEST_DOMAIN_SIZE, ); @@ -136,6 +134,8 @@ mod tests { fn test_completeness_ivc() { let mut rng = o1_utils::tests::make_test_rng(); + //let mut comms_left: Box<_> = box_array2![(Fp::zero(),Fp::zero()); TEST_N_COL_TOTAL; 3]; + let domain_size = 1 << 15; let domain = EvaluationDomains::::create(domain_size).unwrap(); diff --git a/utils/src/array.rs b/utils/src/array.rs new file mode 100644 index 0000000000..2ee48bfafe --- /dev/null +++ b/utils/src/array.rs @@ -0,0 +1,122 @@ +//! This module provides different helpers in creating constant sized +//! arrays and converting them to different formats. + +// @volhovm It could potentially be more efficient with unsafe tricks. +/// Converts a two-dimensional vector to a constant sized two-dimensional array. +pub fn vec_to_boxed_array2( + vec: Vec>, +) -> Box<[[T; N]; M]> { + let vec_of_slices2: Vec<[T; N]> = vec + .into_iter() + .map(|x: Vec| { + let y: Box<[T]> = x.into_boxed_slice(); + let z: Box<[T; N]> = y + .try_into() + .unwrap_or_else(|_| panic!("vec_to_boxed_array2: length mismatch inner array")); + *z + }) + .collect(); + let array: Box<[[T; N]; M]> = vec_of_slices2 + .into_boxed_slice() + .try_into() + .unwrap_or_else(|_| panic!("vec_to_boxed_array2: length mismatch outer array")); + + array +} + +/// Converts a three-dimensional vector to a constant sized two-dimensional array. +pub fn vec_to_boxed_array3( + vec: Vec>>, +) -> Box<[[[T; N]; M]; K]> { + let vec_of_slices2: Vec<[[T; N]; M]> = + vec.into_iter().map(|v| *vec_to_boxed_array2(v)).collect(); + vec_of_slices2 + .into_boxed_slice() + .try_into() + .unwrap_or_else(|_| panic!("vec_to_boxed_array3: length mismatch outer array")) +} + +/// A macro similar to `vec![$elem; $size]` which returns a boxed +/// array, allocated directly on the heap (via a vector). +/// +/// ```rustc +/// let _: Box<[u8; 1024]> = box_array![0; 1024]; +/// ``` +/// +/// See +/// +#[macro_export] +macro_rules! box_array { + ($val:expr ; $len:expr) => {{ + // Use a generic function so that the pointer cast remains type-safe + fn vec_to_boxed_array(vec: Vec) -> Box<[T; $len]> { + let boxed_slice = vec.into_boxed_slice(); + + let ptr = ::std::boxed::Box::into_raw(boxed_slice) as *mut [T; $len]; + + unsafe { Box::from_raw(ptr) } + } + + vec_to_boxed_array(vec![$val; $len]) + }}; +} + +/// A macro similar to `vec![vec![$elem; $size1]; $size2]` which +/// returns a two-dimensional boxed array, allocated directly on the +/// heap (via a vector). +/// +/// ```rustc +/// let _: Box<[[u8; 1024]; 512]> = box_array![0; 1024; 512]; +/// ``` +/// +#[macro_export] +macro_rules! box_array2 { + ($val:expr; $len1:expr; $len2:expr) => {{ + pub fn vec_to_boxed_array2(vec: Vec>) -> Box<[[T; $len1]; $len2]> { + let vec_of_slices2: Vec<[T; $len1]> = vec + .into_iter() + .map(|x: Vec| { + let y: Box<[T]> = x.into_boxed_slice(); + let z: Box<[T; $len1]> = y + .try_into() + .unwrap_or_else(|_| panic!("box_array2: length mismatch inner array")); + *z + }) + .collect(); + let array: Box<[[T; $len1]; $len2]> = vec_of_slices2 + .into_boxed_slice() + .try_into() + .unwrap_or_else(|_| panic!("box_array2: length mismatch outer array")); + + array + } + + vec_to_boxed_array2(vec![vec![$val; $len1]; $len2]) + }}; +} + +#[cfg(test)] +mod tests { + use super::*; + + use ark_ec::AffineCurve; + use ark_ff::Zero; + use mina_curves::pasta::Pallas as CurvePoint; + + pub type BaseField = ::BaseField; + + #[test] + /// Tests whether initialising different arrays creates a stack + /// overflow. The usual default size of the stack is 128kB. + fn test_boxed_stack_overflow() { + // Each point is assumed to be 256 bits, so 512 points is + // 16MB. This often overflows the stack if created as an + // array. + let _boxed: Box<[[BaseField; 256]; 1]> = + vec_to_boxed_array2(vec![vec![BaseField::zero(); 256]; 1]); + let _boxed: Box<[[BaseField; 64]; 4]> = + vec_to_boxed_array2(vec![vec![BaseField::zero(); 64]; 4]); + let _boxed: Box<[BaseField; 256]> = box_array![BaseField::zero(); 256]; + let _boxed: Box<[[BaseField; 256]; 1]> = box_array2![BaseField::zero(); 256; 1]; + } +} diff --git a/utils/src/lib.rs b/utils/src/lib.rs index 8fbc727ac7..14479656be 100644 --- a/utils/src/lib.rs +++ b/utils/src/lib.rs @@ -3,6 +3,7 @@ //! A collection of utility functions and constants that can be reused from multiple projects pub mod adjacent_pairs; +pub mod array; pub mod biguint_helpers; pub mod bitwise_operations; pub mod chunked_evaluations; From 42162093fed463cb87ef5aee5517c27090ea388e Mon Sep 17 00:00:00 2001 From: Danny Willems Date: Tue, 28 May 2024 16:21:07 +0200 Subject: [PATCH 2/3] Remove old comment --- ivc/src/ivc/mod.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/ivc/src/ivc/mod.rs b/ivc/src/ivc/mod.rs index eb09a2f852..69cb3219db 100644 --- a/ivc/src/ivc/mod.rs +++ b/ivc/src/ivc/mod.rs @@ -134,8 +134,6 @@ mod tests { fn test_completeness_ivc() { let mut rng = o1_utils::tests::make_test_rng(); - //let mut comms_left: Box<_> = box_array2![(Fp::zero(),Fp::zero()); TEST_N_COL_TOTAL; 3]; - let domain_size = 1 << 15; let domain = EvaluationDomains::::create(domain_size).unwrap(); From a942e9131e277c56c36a6c1de6bb3a6a14ac1889 Mon Sep 17 00:00:00 2001 From: Mikhail Volkhov Date: Tue, 28 May 2024 16:31:19 +0200 Subject: [PATCH 3/3] [#2231] Add comments and suggested changes --- utils/src/array.rs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/utils/src/array.rs b/utils/src/array.rs index 2ee48bfafe..502ff9f8ce 100644 --- a/utils/src/array.rs +++ b/utils/src/array.rs @@ -1,7 +1,11 @@ //! This module provides different helpers in creating constant sized //! arrays and converting them to different formats. +//! +//! Functions in this module are not necessarily optimal in terms of +//! allocations, as they tend to create intermediate vectors. For +//! better performance, either optimise this code, or use +//! (non-fixed-sized) vectors. -// @volhovm It could potentially be more efficient with unsafe tricks. /// Converts a two-dimensional vector to a constant sized two-dimensional array. pub fn vec_to_boxed_array2( vec: Vec>, @@ -37,7 +41,7 @@ pub fn vec_to_boxed_array3( } /// A macro similar to `vec![$elem; $size]` which returns a boxed -/// array, allocated directly on the heap (via a vector). +/// array, allocated directly on the heap (via a vector, with reallocations). /// /// ```rustc /// let _: Box<[u8; 1024]> = box_array![0; 1024]; @@ -50,11 +54,9 @@ macro_rules! box_array { ($val:expr ; $len:expr) => {{ // Use a generic function so that the pointer cast remains type-safe fn vec_to_boxed_array(vec: Vec) -> Box<[T; $len]> { - let boxed_slice = vec.into_boxed_slice(); - - let ptr = ::std::boxed::Box::into_raw(boxed_slice) as *mut [T; $len]; - - unsafe { Box::from_raw(ptr) } + (vec.into_boxed_slice()) + .try_into() + .unwrap_or_else(|_| panic!("box_array: length mismatch")) } vec_to_boxed_array(vec![$val; $len]) @@ -63,7 +65,7 @@ macro_rules! box_array { /// A macro similar to `vec![vec![$elem; $size1]; $size2]` which /// returns a two-dimensional boxed array, allocated directly on the -/// heap (via a vector). +/// heap (via a vector, with reallocations). /// /// ```rustc /// let _: Box<[[u8; 1024]; 512]> = box_array![0; 1024; 512];