refactor: swap Goldilocks re-export to the Felt type unified for off-chain and on-chain code#819
refactor: swap Goldilocks re-export to the Felt type unified for off-chain and on-chain code#819
Goldilocks re-export to the Felt type unified for off-chain and on-chain code#819Conversation
Felt type unified for off-chain and on-chain codeGoldilocks re-export to the Felt type unified for off-chain and on-chain code
f38f76c to
bff706b
Compare
make `Word` type to have the WIT-compatible shape
bff706b to
ba5aa2b
Compare
huitseeker
left a comment
There was a problem hiding this comment.
Some concerns with unsafe conversions, and questions inline.
miden-crypto/src/word/mod.rs
Outdated
| /// | ||
| /// # Safety | ||
| /// This assumes `(Felt, Felt, Felt, Felt)` has the same memory layout as `[Felt; 4]`. | ||
| fn as_elements_array(&self) -> &[Felt; WORD_SIZE_FELT] { |
There was a problem hiding this comment.
The cast from &(Felt, Felt, Felt, Felt) to &[Felt; 4] indeed assumes identical memory layout, but Rust does not guarantee tuple layout. This could lead to UB if the compiler changes layout.
I'd suggest adding compile-time assertions like const_assert!(size_of::<(Felt, Felt, Felt, Felt)>() == size_of::<[Felt; 4]>()); at least (and a proptest) to verify the assumption of equivalence at build (/test) time.
There was a problem hiding this comment.
Thank you! I converted the Word type to have named fields instead of a tuple and added a test with a roundtrip in 0e06cae.
| @@ -161,14 +208,13 @@ impl Word { | |||
| where | |||
| I: Iterator<Item = &'a Self>, | |||
| { | |||
| words.flat_map(|d| d.0.iter()) | |||
| words.flat_map(|d| d.as_elements().iter()) | |||
| } | |||
|
|
|||
| /// Returns all elements of multiple words as a slice. | |||
| pub fn words_as_elements(words: &[Self]) -> &[Felt] { | |||
There was a problem hiding this comment.
words_as_elements casts &[Word] to &[Felt] via raw pointer. With #[repr(C, align(16))] on Word, this assumes no padding between elements and specific field ordering. Suggest adding const_assert! checks for size and alignment, or using the safe words_as_elements_iter instead where performance allows.
| @@ -0,0 +1,20 @@ | |||
| //! A unified `Felt` for Miden Rust code. | |||
There was a problem hiding this comment.
The new miden-field crate has no tests. Since this is cryptographic code used throughout the codebase, consider adding unit tests for both the native and WASM implementations to ensure correctness of field operations.
There was a problem hiding this comment.
I'm not sure adding tests for miden target would be possible here since implementation of these methods would be delegated to compiler intrinsics. IIUC, this would require the whole compiler pipeline to be present. So, maybe a better places for these is in the compiler repo?
For native implementation, I think the main thing we'd be testing is that we didn't mix up delegated function calls. Maybe there is a way to add a couple of tests which would run all (most?) operations on some random elements and make sure we get the same result for Felt and native Goldilocks?
There was a problem hiding this comment.
The miden target tests for Felt are living in the compiler repo since they are implemented in the compiler intrinsics which mostly delegate to the VM ops. We have a proptest test suite where we test individual ops and they are also covered in the integration test suite where we are compiling and running some contracts.
For native implementation, the only thing worth testing is that we're delegating properly to the underlying Goldilocks implementation. I'll come up with something.
| // We cannot define this type as `Word([Felt;4])` since there is no struct tuple support | ||
| // and fixed array support is not complete in WIT. For the type remapping to work the | ||
| // bindings are expecting the remapped type to be the same shape as the one generated from | ||
| // WIT. |
There was a problem hiding this comment.
Would it not be possible to use:
#[repr(C)]
pub struct Word {
a: Felt,
b: Felt,
c: Felt,
d: Felt,
}from a WIT PoV, or do you need the tuple?
Because that is layout-compatible with [Felt; 4].
There was a problem hiding this comment.
Thank you! From the WIT it does not have to be a tuple, so I changed it to your suggestion.
… `build.yml` To test building for the wasm32+miden (on-chain) target.
bobbinth
left a comment
There was a problem hiding this comment.
Looks good! Thank you! Not a full review yet, but I left some comments inline.
miden-field/src/word/tests.rs
Outdated
| pub fn rand_u64() -> u64 { | ||
| let mut bytes = [0u8; 8]; | ||
| let rng = &mut rand::rng(); | ||
| rng.fill(&mut bytes[..]); | ||
| let bytes = bytes[..8].try_into().unwrap(); | ||
| u64::from_le_bytes(bytes) | ||
| } |
There was a problem hiding this comment.
Could this not be just:
pub fn rand_u64() -> u64 {
rand::random()
}And if so, maybe we can just use rand::random() instead of this function directly?
There was a problem hiding this comment.
Sure. I also planned to convert the existing tests to proptest in this PR as well. Although it might be better to do it in the new PR.
There was a problem hiding this comment.
I did a very cursory review of this file.
split internal vs external dependencies.
|
Thank you for the review! I've gone through all the review notes and implemented some of them right away and put the rest in the TODO in the PR desc. I'll be going through the TODO and will ping you when I'm done. The notable change that I made after the @huitseeker review was to move the I organized the commits addressing the review notes to make it easier to review so I suggest reviewing the changes on a per-commit basis. |
huitseeker
left a comment
There was a problem hiding this comment.
On wasm+miden, word! still expands to Word::parse, but parse is behind cfg(not(all(target_family = "wasm", miden))). I think that breaks compile for on-chain users. Please gate word! for this target or add a Miden-safe parse path.
| self.as_elements().to_vec() | ||
| } | ||
|
|
||
| pub fn reversed(&self) -> Self { |
There was a problem hiding this comment.
Small: Word::reversed() has no direct test. A short round-trip test would lock the behavior.
88c1e88 to
21199a3
Compare
Thank you! Done in 04f5af6 |
|
I addressed all the review notes and this PR is ready. Since my last comment at #819 (comment) I did (notable):
I tried to structure the commits for reviewing so I suggest reviewing the changes on a per-commit basis. |
huitseeker
left a comment
There was a problem hiding this comment.
Most everything looks good, I think we're just missing the NEG_ONE, thank you!
|
|
||
| /// Field element representing two. | ||
| pub const TWO: Self = Self { inner: f32::from_bits(2) }; | ||
|
|
There was a problem hiding this comment.
The native implementation has NEG_ONE constant but this WASM implementation is missing it. This could cause compilation failures for code that expects it to be available. Should we add it for API parity?
There was a problem hiding this comment.
Good catch! But since Felt for miden target is a wrapper on f32 there is no way to encode the 2^64 - 2^32 using the Rust const because it'd involve a call to the compiler intrinsics.
miden-field/build.rs
Outdated
| // `cargo-miden` compiles Rust to Wasm which will then be compiled to Miden VM code by `midenc`. | ||
| // When targeting a "real" Wasm runtime (e.g. `wasm32-unknown-unknown` for a web SDK), we want a | ||
| // regular felt representation instead. | ||
| if env::var_os("MIDENC_TARGET_IS_MIDEN_VM").is_some() { |
There was a problem hiding this comment.
nit: This accepts any value (even empty string) for MIDENC_TARGET_IS_MIDEN_VM. Consider checking for a non-empty value, true or 1 to avoid accidentally triggering the miden target when the variable is set but empty.
| /// This assumes the four fields of [`Word`] are laid out contiguously with no padding, in | ||
| /// the same order as `[Felt; 4]`. | ||
| fn as_elements_array(&self) -> &[Felt; WORD_SIZE_FELTS] { | ||
| unsafe { &*(&self.a as *const Felt as *const [Felt; WORD_SIZE_FELTS]) } |
There was a problem hiding this comment.
nit: Consider adding compile-time assertions to verify that size_of::<Word>() == 4 * size_of::<Felt>() and that fields have no padding. This would catch layout changes at compile time rather than risking UB. The static_assertions crate or a simple const _: () = assert!(...) could work here.
Thank you! I addressed all your notes. |
bobbinth
left a comment
There was a problem hiding this comment.
Looks good! Thank you! I left one question inline.
Close #771
This PR swaps the
p3_goldilocks::Goldilocks as Feltre-export to the wrapperFelt(p3_goldilocks::Goldilocks)type for non-Miden targets. For the Miden targetFelt(f32)is compiled (see explanation in #771).Besides that
Wordtype is changed to have the WIT-compatible shape (structwith a named field).All
Feltmethods and implemented traits are delegating everything to the underlyingGoldilockstype and its trait implementations. So theFeltshould be 100% API equivalent to theGoldilockstype and can be used as a drop-in replacement. This allows us to release it as av0.22.xpatch version so that it can be included in VM v0.21 release. That's why I made this PR against themainbranch.The corresponding PR in the VM repo that updates VM to the version of
miden-cryptofrom this branch is 0xMiden/miden-vm#2649.The corresponding PR in the compiler repo migrating to the
miden-fieldcrate from this PR is 0xMiden/compiler#923In the follow-up PRs:
Serializable/Deserializableimpl forGoldilocksin themiden-serde-utilscrate (via refactor: swapGoldilocksre-export to theFelttype unified for off-chain and on-chain code #819 (comment))