diff --git a/prover/src/tables/types.rs b/prover/src/tables/types.rs index 3581dc8ec..85eaca17a 100644 --- a/prover/src/tables/types.rs +++ b/prover/src/tables/types.rs @@ -57,10 +57,22 @@ pub fn zeroed_fe_vec(len: usize) -> Vec { const _: () = assert!(core::mem::size_of::() == core::mem::size_of::()); const _: () = assert!(core::mem::align_of::() == core::mem::align_of::()); let zeros: Vec = vec![0u64; len]; + // Reinterpret the buffer as `Vec` via its raw parts rather than + // `mem::transmute::, Vec>`. `Vec`'s field layout is unspecified + // and may depend on its element type, so transmuting one `Vec` to another + // relies on that unspecified layout (the std `mem::transmute` docs call this + // out and recommend `from_raw_parts`). Rebuilding from `(ptr, len, cap)` + // reuses the same allocation and carries no `Vec`-layout assumption. + let mut zeros = core::mem::ManuallyDrop::new(zeros); // SAFETY: `FE` is `#[repr(transparent)]` over `u64` with identical size and // alignment (asserted above), and `0u64` is exactly `FE::zero()`'s bit - // pattern (no Montgomery form), so reinterpreting the buffer is valid. - unsafe { core::mem::transmute::, Vec>(zeros) } + // pattern (Goldilocks has no Montgomery form), so the zeroed `u64` buffer is + // a valid `[FE]` of all `FE::zero()`. `len`/`capacity` are element counts and + // the element sizes are equal, so they carry over unchanged; the eventual + // dealloc uses the same `size * capacity` and alignment as the original + // allocation. `ManuallyDrop` stops the source `Vec` from freeing the buffer + // that the returned `Vec` now owns. + unsafe { Vec::from_raw_parts(zeros.as_mut_ptr() as *mut FE, zeros.len(), zeros.capacity()) } } #[cfg(test)]