Skip to content

Commit 5edead2

Browse files
Nashtarehuitseeker
andauthored
perf: make field operations for Goldilocks faster (#926)
* perf: make some operations for Goldilocks faster * chore: update CHANGELOG.md * apply review * chore: address review nits after rebasing --------- Co-authored-by: François Garillot <francois@garillot.net>
1 parent 940d57b commit 5edead2

File tree

4 files changed

+54
-3
lines changed

4 files changed

+54
-3
lines changed

CHANGELOG.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,13 @@
55
- Adds `LargeSmtForest::add_lineages` which provides an efficient means of adding multiple new lineages at once ([#910](https://github.com/0xMiden/crypto/pull/910)).
66
- [BREAKING] Removed `LexicographicWord` as `Word` itself now implements the correct comparison behavior. Any place where the former is used should be able to seamlessly swap to the latter.
77
- Added `Serializable` and `Deserializable` instances for `Arc<str>`.
8-
- [BREAKING] Removed `WORD_SIZE_FELTS` and `WORD_SIZE_BYTES` from `miden-field` in favor of `Word::NUM_FELTS` and `Word::SERIALIZED_SIZE`, respectively. The values remain the same.
9-
- [BREAKING] `WORD_SIZE` has been removed from `miden-crypto` in favor of `Word::NUM_FELTS`. Clients will need to update references to the constant, but `Word` will already be in scope as it is re-exported from `miden-crypto`.
8+
- [BREAKING] Removed `WORD_SIZE_FELTS` and `WORD_SIZE_BYTES` from `miden-field` in favor of `Word::NUM_ELEMENTS` and `Word::SERIALIZED_SIZE`, respectively. The values remain the same.
9+
- [BREAKING] `WORD_SIZE` has been removed from `miden-crypto` in favor of `Word::NUM_ELEMENTS`. Clients will need to update references to the constant, but `Word` will already be in scope as it is re-exported from `miden-crypto`.
1010
- [BREAKING] Removed implementations of `Deref` and `DerefMut` for `Felt`.
1111
- [BREAKING] Update `Poseidon2` instance to match Plonky3 one ([#905](https://github.com/0xMiden/crypto/pull/905)).
1212
- Use per-chunk scratch space for batch inversion ([#933](https://github.com/0xMiden/crypto/pull/933)).
1313
- [BREAKING] Changed the signature of `Felt::new` to perform reduction, and raise an error if the input is invalid. Retained the old behavior as `Felt::new_unchecked`, as its usage may lead to incorrect results.
14+
- Optimize field operations for `Goldilocks` ([#926](https://github.com/0xMiden/crypto/pull/926)).
1415

1516
## 0.23.0 (2026-03-11)
1617

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

miden-field/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ num-bigint = { default-features = false, version = "0.4" }
2626
p3-challenger.workspace = true
2727
p3-field.workspace = true
2828
p3-goldilocks.workspace = true
29+
p3-util.workspace = true
2930
paste = { version = "1.0.15" }
3031
proptest = { default-features = false, features = ["alloc", "std"], optional = true, version = "1.7" }
3132
rand = { default-features = false, version = "0.10" }

miden-field/src/native/mod.rs

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//! Off-chain implementation of [`crate::Felt`].
22
3-
use alloc::format;
3+
use alloc::{format, vec, vec::Vec};
44
use core::{
55
array, fmt,
66
hash::{Hash, Hasher},
@@ -22,6 +22,7 @@ use p3_field::{
2222
quotient_map_large_iint, quotient_map_large_uint, quotient_map_small_int,
2323
};
2424
use p3_goldilocks::Goldilocks;
25+
use p3_util::flatten_to_base;
2526
use rand::{
2627
Rng,
2728
distr::{Distribution, StandardUniform},
@@ -137,6 +138,28 @@ fn raw_felt_u64(value: &Felt) -> u64 {
137138
unsafe { core::mem::transmute_copy(value) }
138139
}
139140

141+
/// Reinterprets a `Felt` slice as `Goldilocks`.
142+
///
143+
/// # Safety
144+
///
145+
/// `Felt` is `#[repr(transparent)]` over `Goldilocks`, so the element layout matches.
146+
#[inline]
147+
fn felts_as_goldilocks_slice(s: &[Felt]) -> &[Goldilocks] {
148+
// SAFETY: `Felt` is `#[repr(transparent)]` over `Goldilocks`, so the element layout matches.
149+
unsafe { core::slice::from_raw_parts(s.as_ptr().cast::<Goldilocks>(), s.len()) }
150+
}
151+
152+
/// Reinterprets a `Felt` array as `Goldilocks`.
153+
///
154+
/// # Safety
155+
///
156+
/// `Felt` is `#[repr(transparent)]` over `Goldilocks`, so `[Felt; N]` matches `[Goldilocks; N]`.
157+
#[inline]
158+
fn felts_as_goldilocks_array<const N: usize>(a: &[Felt; N]) -> &[Goldilocks; N] {
159+
// SAFETY: same layout as `felts_as_goldilocks_slice`, for a fixed `N`.
160+
unsafe { &*(a as *const [Felt; N] as *const [Goldilocks; N]) }
161+
}
162+
140163
impl fmt::Display for Felt {
141164
#[inline]
142165
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
@@ -162,6 +185,8 @@ impl Hash for Felt {
162185
// ================================================================================================
163186

164187
impl Field for Felt {
188+
// TODO: This should only be the case for WASM targets.
189+
// Native targets should be able to leverage AVX2 / NEON optimizations from Plonky3.
165190
type Packing = Self;
166191

167192
const GENERATOR: Self = Self(Goldilocks::GENERATOR);
@@ -221,6 +246,29 @@ impl PrimeCharacteristicRing for Felt {
221246
fn exp_u64(&self, power: u64) -> Self {
222247
self.0.exp_u64(power).into()
223248
}
249+
250+
#[inline]
251+
fn sum_array<const N: usize>(input: &[Self]) -> Self {
252+
assert_eq!(N, input.len());
253+
let g = felts_as_goldilocks_slice(input);
254+
Self(Goldilocks::sum_array::<N>(g))
255+
}
256+
257+
#[inline]
258+
fn dot_product<const N: usize>(lhs: &[Self; N], rhs: &[Self; N]) -> Self {
259+
let lhs_g = felts_as_goldilocks_array(lhs);
260+
let rhs_g = felts_as_goldilocks_array(rhs);
261+
Self(Goldilocks::dot_product(lhs_g, rhs_g))
262+
}
263+
264+
#[inline]
265+
fn zero_vec(len: usize) -> Vec<Self> {
266+
// SAFETY:
267+
// Due to `#[repr(transparent)]`, Felt, Goldilocks and u64 have the same size,
268+
// alignment and memory layout making `flatten_to_base` safe.
269+
// This will create a vector of Felt elements with value set to 0.
270+
unsafe { flatten_to_base(vec![0u64; len]) }
271+
}
224272
}
225273

226274
quotient_map_small_int!(Felt, u64, [u8, u16, u32]);

0 commit comments

Comments
 (0)