diff --git a/ipa-core/src/secret_sharing/mod.rs b/ipa-core/src/secret_sharing/mod.rs index 7fa77a89f..64365bd51 100644 --- a/ipa-core/src/secret_sharing/mod.rs +++ b/ipa-core/src/secret_sharing/mod.rs @@ -1,65 +1,15 @@ -//! # Vectorization -//! -//! Vectorization refers to adapting an implementation that previously operated on one value at a -//! time, to instead operate on `N` values at a time. Vectorization improves performance in two ways: -//! -//! 1. Vectorized code can make use of special CPU instructions (Intel AVX, ARM NEON) that operate -//! on multiple values at a time. This reduces the CPU time required to perform computations. -//! We also use vectorization to refer to "bit packing" of boolean values, i.e., packing -//! 64 boolean values into a single u64 rather than using a byte (or even a word) for each -//! value. -//! 2. Aside from the core arithmetic operations that are involved in our MPC, a substantial -//! amount of other code is needed to send values between helpers, schedule futures for -//! execution, etc. Vectorization can result in a greater amount of arithmetic work being -//! performed for a given amount of overhead work, thus increasing the efficiency of the -//! implementation. -//! -//! ## Vectorization traits -//! -//! There are two sets of traits related to vectorization. -//! -//! If you are writing protocols, the trait of interest is `FieldSimd`, which can be specified in -//! a trait bound, something like `F: Field + FieldSimd`. -//! -//! The other traits are `Vectorizable` (for `SharedValue`s) and `FieldVectorizable`. These traits -//! are needed to work around a limitation in the rust type system. In most cases, you do not need -//! to reference the `Vectorizable` or `FieldVectorizable` traits directly when implementing -//! protocols. Usually the vector type is hidden within `AdditiveShare`, but if you are writing a -//! vectorized low-level primitive, you may need to refer to it directly, as `>::Array`. It is even more rare to need to use `FieldVectorizable`; see its -//! documentation and the documentation of `FieldSimd` for details. -//! -//! We require that each supported vectorization configuration (i.e. combination of data type and -//! width) be explicitly identified, by implementing the `Vectorizable` and/or `FieldVectorizable` -//! traits for base data type (e.g. `Fp32BitPrime`). This is for two reasons: -//! 1. Rust doesn't yet support evaluating expressions involving const parameters at compile time, -//! which makes it difficult or impossible to write generic serialization routines for -//! arbitrary widths. -//! 2. As a measure of protection against inadvertently using a configuration that will not be -//! efficient (i.e. an excessive vector width). -//! -//! ## Adding a new supported vectorization -//! -//! To add a new supported vectorization: -//! -//! 1. Add `FieldSimd` impl (in `secret_sharing/mod.rs`) -//! 2. Add `FromRandom` impl (in `array.rs` or `boolean_array.rs`) -//! 3. Add `Serializable` impl (in `array.rs` or `boolean_array.rs`) -//! 4. Add `Vectorizable` and `FieldVectorizable` impls (with the primitive type def in e.g. `galois_field.rs` - pub mod replicated; -mod array; mod decomposed; mod into_shares; mod scheme; +mod vector; use std::{ fmt::Debug, - ops::{Add, AddAssign, Mul, MulAssign, Neg, Sub, SubAssign}, + ops::{Mul, MulAssign, Neg}, }; -pub use array::StdArray; pub(crate) use decomposed::BitDecomposed; use generic_array::ArrayLength; pub use into_shares::IntoShares; @@ -69,16 +19,15 @@ use rand::{ Rng, }; pub use scheme::{Bitwise, Linear, LinearRefOps, SecretSharing}; +pub use vector::{ + FieldArray, FieldSimd, FieldVectorizable, SharedValueArray, StdArray, Vectorizable, +}; +#[cfg(any(test, feature = "test-fixture", feature = "cli"))] +use crate::secret_sharing::replicated::semi_honest::AdditiveShare; use crate::{ - error::LengthError, - ff::{ - boolean::Boolean, - boolean_array::{BA20, BA256, BA3, BA32, BA5, BA64, BA8}, - AddSub, AddSubAssign, Field, Fp32BitPrime, Serializable, - }, - protocol::prss::FromRandom, - secret_sharing::replicated::{semi_honest::AdditiveShare, ReplicatedSecretSharing}, + ff::{AddSub, AddSubAssign, Serializable}, + secret_sharing::replicated::ReplicatedSecretSharing, }; /// Operations supported for weak shared values. @@ -170,148 +119,6 @@ macro_rules! impl_shared_value_common { }; } -// Note that we can either make `trait Vectorizable: SharedValue`, or we can make `trait -// SharedValue: Vectorizable<1>`, but doing both creates a cycle. (Similarly for -// `FieldVectorizable` / `Field`.) -// -// Although it is somewhat unnatural, we choose to do the latter, because it allows existing -// high-level protocols unaware of vectorization to call vectorized versions of core protocols (with -// width of 1) without updating all of the trait bounds. This does mean that the trait definitions -// do not prevent implementing `Vectorizable` for something that is not a `SharedValue`, but please -// don't do that. - -/// Trait for `SharedValue`s supporting operations on `N`-wide vectors. -pub trait Vectorizable: Sized { - type Array: SharedValueArray; -} - -/// Trait for `Field`s supporting operations on `N`-wide vectors. -/// -/// We would like `F` to be `FieldVectorizable` if it satisfies all of the following: -/// 1. `F: Field`. -/// 2. `>::Array: FieldArray`. Rust does not support expressing a -/// constraint on a super-trait's associated type directly. Instead, this effect is achieved -/// by constraining the `ArrayAlias` associated type and then constraining that -/// `Vectorizable::Array == FieldVectorizable::ArrayAlias` where necessary (e.g. in the -/// definition and blanket impl of the `FieldSimd` trait. We call it `ArrayAlias` instead of -/// `Array` so that references to the `Array` associated type do not require qualification -/// with a trait name. -/// 3. `F: Vectorizable`. This is implied by the previous two, because `FieldArray` -/// is a sub-trait of `SharedValueArray`. (See the `FieldSimd` documentation for another -/// important consequence of this sub-trait relationship.) -pub trait FieldVectorizable: SharedValue + Sized { - type ArrayAlias: FieldArray; -} - -// Convenience alias to express a supported vectorization when writing protocols. -// -// Typically appears like this: `F: Field + FieldSimd`. -// -// We could define a `SharedValueSimd` trait that is the analog of this for `SharedValue`s, but -// there are not currently any protocols that need it. -// -// Because we have constrained the associated types Vectorizable::Array and -// FieldVectorizable::ArrayAlias to be equal, the type they refer to must satisfy the union of all -// trait bounds applicable to either. However, in some cases the compiler has trouble proving -// properties related to this. (See rust issues [41118] and [60471].) A typical workaround for -// problems of this sort is to redundantly list a trait bound on both associated types, but for us -// that is not necessary in most cases because `FieldArray` is a sub-trait of `SharedValueArray`. -// -// Another consequence of this limitation of the compiler is that if you write the bound `F: Field + -// FieldSimd + Vectorizable`, you will get the error ``type annotations needed: -// cannot satisfy `>::Array == >::ArrayAlias```. The compiler is not smart enough to -// coalesce the constraints and see that `S`, `::Array`, and `::ArrayAlias` must all to refer to the same type. -// -// [41118](https://github.com/rust-lang/rust/issues/41118) -// [60471](https://github.com/rust-lang/rust/issues/60471) -pub trait FieldSimd: - Field + Vectorizable>::ArrayAlias> + FieldVectorizable -{ -} - -// Portions of the implementation treat non-vectorized operations as a vector with `N = 1`. This -// blanket impl (and the fact that `F: Field` is the only trait bound) is important in allowing code -// that writes `F: Field` to continue working without modification. -impl FieldSimd<1> for F {} - -// Supported vectorizations - -impl FieldSimd<32> for Fp32BitPrime {} - -macro_rules! boolean_vector { - ($dim:expr, $vec:ty) => { - impl Vectorizable<$dim> for Boolean { - type Array = $vec; - } - - impl FieldVectorizable<$dim> for Boolean { - type ArrayAlias = $vec; - } - - impl FieldSimd<$dim> for Boolean {} - - impl From> for AdditiveShare { - fn from(value: AdditiveShare<$vec>) -> Self { - AdditiveShare::new_arr(value.left(), value.right()) - } - } - - impl From> for AdditiveShare<$vec> { - fn from(value: AdditiveShare) -> Self { - AdditiveShare::new(*value.left_arr(), *value.right_arr()) - } - } - }; -} - -boolean_vector!(3, BA3); -boolean_vector!(5, BA5); -boolean_vector!(8, BA8); -boolean_vector!(20, BA20); -boolean_vector!(32, BA32); -boolean_vector!(64, BA64); -boolean_vector!(256, BA256); - -pub trait SharedValueArray: - Clone - + Eq - + Debug - + Send - + Sync - + Sized - + Sendable - + TryFrom, Error = LengthError> - + FromIterator - + IntoIterator - + Add - + for<'a> Add<&'a Self, Output = Self> - + AddAssign - + for<'a> AddAssign<&'a Self> - + Neg - + Sub - + for<'a> Sub<&'a Self, Output = Self> - + SubAssign - + for<'a> SubAssign<&'a Self> -{ - const ZERO_ARRAY: Self; - - fn from_fn V>(f: F) -> Self; -} - -// Some `SharedValue` types (and thus their arrays) implement `FromRandom`, but `RP25519` does not. -// We overload this distinction on `FieldArray` instead of creating a separate `ArrayFromRandom` trait, -// to avoid making the `Vectorizable` / `FieldVectorizable` situation that much more complicated. -pub trait FieldArray: - SharedValueArray - + FromRandom - + for<'a> Mul - + for<'a> Mul<&'a F, Output = Self> - + for<'a> Mul<&'a Self, Output = Self> -{ -} - #[cfg(any(test, feature = "test-fixture", feature = "cli"))] impl IntoShares> for V where diff --git a/ipa-core/src/secret_sharing/array.rs b/ipa-core/src/secret_sharing/vector/array.rs similarity index 99% rename from ipa-core/src/secret_sharing/array.rs rename to ipa-core/src/secret_sharing/vector/array.rs index f38e608c0..13f476b2e 100644 --- a/ipa-core/src/secret_sharing/array.rs +++ b/ipa-core/src/secret_sharing/vector/array.rs @@ -23,7 +23,7 @@ use crate::{ /// * It disables by-index access to individual elements of the array, which /// should never be necessary in properly vectorized code. #[derive(Clone, Debug, Eq, PartialEq)] -pub struct StdArray([V; N]); +pub struct StdArray(pub(super) [V; N]); impl PartialEq for StdArray where diff --git a/ipa-core/src/secret_sharing/vector/impls.rs b/ipa-core/src/secret_sharing/vector/impls.rs new file mode 100644 index 000000000..e29d8712b --- /dev/null +++ b/ipa-core/src/secret_sharing/vector/impls.rs @@ -0,0 +1,49 @@ +//! Supported vectorizations + +use crate::{ + ff::{ + boolean::Boolean, + boolean_array::{BA20, BA256, BA3, BA32, BA5, BA64, BA8}, + Fp32BitPrime, + }, + secret_sharing::{ + replicated::semi_honest::AdditiveShare, FieldSimd, FieldVectorizable, + ReplicatedSecretSharing, Vectorizable, + }, +}; + +impl FieldSimd<32> for Fp32BitPrime {} + +macro_rules! boolean_vector { + ($dim:expr, $vec:ty) => { + impl Vectorizable<$dim> for Boolean { + type Array = $vec; + } + + impl FieldVectorizable<$dim> for Boolean { + type ArrayAlias = $vec; + } + + impl FieldSimd<$dim> for Boolean {} + + impl From> for AdditiveShare { + fn from(value: AdditiveShare<$vec>) -> Self { + AdditiveShare::new_arr(value.left(), value.right()) + } + } + + impl From> for AdditiveShare<$vec> { + fn from(value: AdditiveShare) -> Self { + AdditiveShare::new(*value.left_arr(), *value.right_arr()) + } + } + }; +} + +boolean_vector!(3, BA3); +boolean_vector!(5, BA5); +boolean_vector!(8, BA8); +boolean_vector!(20, BA20); +boolean_vector!(32, BA32); +boolean_vector!(64, BA64); +boolean_vector!(256, BA256); diff --git a/ipa-core/src/secret_sharing/vector/mod.rs b/ipa-core/src/secret_sharing/vector/mod.rs new file mode 100644 index 000000000..e276d7d22 --- /dev/null +++ b/ipa-core/src/secret_sharing/vector/mod.rs @@ -0,0 +1,56 @@ +//! Vectorized secret shares +//! +//! Vectorization refers to adapting an implementation that previously operated on one value at a +//! time, to instead operate on `N` values at a time. Vectorization improves performance in two ways: +//! +//! 1. Vectorized code can make use of special CPU instructions (Intel AVX, ARM NEON) that operate +//! on multiple values at a time. This reduces the CPU time required to perform computations. +//! We also use vectorization to refer to "bit packing" of boolean values, i.e., packing +//! 64 boolean values into a single u64 rather than using a byte (or even a word) for each +//! value. +//! 2. Aside from the core arithmetic operations that are involved in our MPC, a substantial +//! amount of other code is needed to send values between helpers, schedule futures for +//! execution, etc. Vectorization can result in a greater amount of arithmetic work being +//! performed for a given amount of overhead work, thus increasing the efficiency of the +//! implementation. +//! +//! ## Vectorization traits +//! +//! There are two sets of traits related to vectorization. +//! +//! If you are writing protocols, the trait of interest is `FieldSimd`, which can be specified in +//! a trait bound, something like `F: Field + FieldSimd`. +//! +//! The other traits are `Vectorizable` (for `SharedValue`s) and `FieldVectorizable`. These traits +//! are needed to work around a limitation in the rust type system. In most cases, you do not need +//! to reference the `Vectorizable` or `FieldVectorizable` traits directly when implementing +//! protocols. Usually the vector type is hidden within `AdditiveShare`, but if you are writing a +//! vectorized low-level primitive, you may need to refer to it directly, as `>::Array`. It is even more rare to need to use `FieldVectorizable`; see its +//! documentation and the documentation of `FieldSimd` for details. +//! +//! We require that each supported vectorization configuration (i.e. combination of data type and +//! width) be explicitly identified, by implementing the `Vectorizable` and/or `FieldVectorizable` +//! traits for base data type (e.g. `Fp32BitPrime`). This is for two reasons: +//! 1. Rust doesn't yet support evaluating expressions involving const parameters at compile time, +//! which makes it difficult or impossible to write generic serialization routines for +//! arbitrary widths. +//! 2. As a measure of protection against inadvertently using a configuration that will not be +//! efficient (i.e. an excessive vector width). +//! +//! ## Adding a new supported vectorization +//! +//! To add a new supported vectorization: +//! +//! 1. Add `FromRandom` impl (in `array.rs` or `boolean_array.rs`) +//! 2. Add `Serializable` impl (in `array.rs` or `boolean_array.rs`) +//! 3. Add `FieldSimd` impl (in `secret_sharing/vector/impls.rs`) +//! 4. Add `Vectorizable` and `FieldVectorizable` impls (either with the primitive type def in +//! e.g. `galois_field.rs`, or in `vector/impls.rs`) + +mod array; +mod impls; +mod traits; + +pub use array::StdArray; +pub use traits::{FieldArray, FieldSimd, FieldVectorizable, SharedValueArray, Vectorizable}; diff --git a/ipa-core/src/secret_sharing/vector/traits.rs b/ipa-core/src/secret_sharing/vector/traits.rs new file mode 100644 index 000000000..b44316b70 --- /dev/null +++ b/ipa-core/src/secret_sharing/vector/traits.rs @@ -0,0 +1,115 @@ +use std::{ + fmt::Debug, + ops::{Add, AddAssign, Mul, Neg, Sub, SubAssign}, +}; + +use crate::{ + error::LengthError, + ff::Field, + protocol::prss::FromRandom, + secret_sharing::{Sendable, SharedValue}, +}; + +// Note that we can either make `trait Vectorizable: SharedValue`, or we can make `trait +// SharedValue: Vectorizable<1>`, but doing both creates a cycle. (Similarly for +// `FieldVectorizable` / `Field`.) +// +// Although it is somewhat unnatural, we choose to do the latter, because it allows existing +// high-level protocols unaware of vectorization to call vectorized versions of core protocols (with +// width of 1) without updating all of the trait bounds. This does mean that the trait definitions +// do not prevent implementing `Vectorizable` for something that is not a `SharedValue`, but please +// don't do that. + +/// Trait for `SharedValue`s supporting operations on `N`-wide vectors. +pub trait Vectorizable: Sized { + type Array: SharedValueArray; +} + +/// Trait for `Field`s supporting operations on `N`-wide vectors. +/// +/// We would like `F` to be `FieldVectorizable` if it satisfies all of the following: +/// 1. `F: Field`. +/// 2. `>::Array: FieldArray`. Rust does not support expressing a +/// constraint on a super-trait's associated type directly. Instead, this effect is achieved +/// by constraining the `ArrayAlias` associated type and then constraining that +/// `Vectorizable::Array == FieldVectorizable::ArrayAlias` where necessary (e.g. in the +/// definition and blanket impl of the `FieldSimd` trait. We call it `ArrayAlias` instead of +/// `Array` so that references to the `Array` associated type do not require qualification +/// with a trait name. +/// 3. `F: Vectorizable`. This is implied by the previous two, because `FieldArray` +/// is a sub-trait of `SharedValueArray`. (See the `FieldSimd` documentation for another +/// important consequence of this sub-trait relationship.) +pub trait FieldVectorizable: SharedValue + Sized { + type ArrayAlias: FieldArray; +} + +// Convenience alias to express a supported vectorization when writing protocols. +// +// Typically appears like this: `F: Field + FieldSimd`. +// +// We could define a `SharedValueSimd` trait that is the analog of this for `SharedValue`s, but +// there are not currently any protocols that need it. +// +// Because we have constrained the associated types Vectorizable::Array and +// FieldVectorizable::ArrayAlias to be equal, the type they refer to must satisfy the union of all +// trait bounds applicable to either. However, in some cases the compiler has trouble proving +// properties related to this. (See rust issues [41118] and [60471].) A typical workaround for +// problems of this sort is to redundantly list a trait bound on both associated types, but for us +// that is not necessary in most cases because `FieldArray` is a sub-trait of `SharedValueArray`. +// +// Another consequence of this limitation of the compiler is that if you write the bound `F: Field + +// FieldSimd + Vectorizable`, you will get the error ``type annotations needed: +// cannot satisfy `>::Array == >::ArrayAlias```. The compiler is not smart enough to +// coalesce the constraints and see that `S`, `::Array`, and `::ArrayAlias` must all to refer to the same type. +// +// [41118](https://github.com/rust-lang/rust/issues/41118) +// [60471](https://github.com/rust-lang/rust/issues/60471) +pub trait FieldSimd: + Field + Vectorizable>::ArrayAlias> + FieldVectorizable +{ +} + +// Portions of the implementation treat non-vectorized operations as a vector with `N = 1`. This +// blanket impl (and the fact that `F: Field` is the only trait bound) is important in allowing code +// that writes `F: Field` to continue working without modification. +impl FieldSimd<1> for F {} + +pub trait SharedValueArray: + Clone + + Eq + + Debug + + Send + + Sync + + Sized + + Sendable + + TryFrom, Error = LengthError> + + FromIterator + + IntoIterator + + Add + + for<'a> Add<&'a Self, Output = Self> + + AddAssign + + for<'a> AddAssign<&'a Self> + + Neg + + Sub + + for<'a> Sub<&'a Self, Output = Self> + + SubAssign + + for<'a> SubAssign<&'a Self> +{ + const ZERO_ARRAY: Self; + + fn from_fn V>(f: F) -> Self; +} + +// Some `SharedValue` types (and thus their arrays) implement `FromRandom`, but `RP25519` does not. +// We overload this distinction on `FieldArray` instead of creating a separate `ArrayFromRandom` trait, +// to avoid making the `Vectorizable` / `FieldVectorizable` situation that much more complicated. +pub trait FieldArray: + SharedValueArray + + FromRandom + + for<'a> Mul + + for<'a> Mul<&'a F, Output = Self> + + for<'a> Mul<&'a Self, Output = Self> +{ +}