-
Notifications
You must be signed in to change notification settings - Fork 31
wasm32 support for twenty-first #265
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
makes twenty-first "wasm-friendly" such that it builds when target_arch is wasm32. This enables twenty-first to be used inside a web-browser's web-assembly environment. Tests do not presently build for wasm32 target. Addressing that is more complicated and will be the focus of subsequent commits.
This adds the necessary Cargo.toml magic for `cargo test` to build tests correctly, both #[test] and #[proptest]. However the wasm test harness ignores #[test] and #[proptest] so tests are not actually run in that environment. The source files will need to be modified to make the tests visible to wasm test harness in a future commit.
annotates tests to indicate they should be compiled for wasm32 target by decorating with #[wasm_bindgen_test] when target_arch = wasm32.
It remains 32 on 64 bit systems. This avoids isize::MAX overflow for 32 bit systems and fixes a failing test case
The test uses assert_snapshot() which performs wasm32 disallowed operation(s)
9caacb6 to
d960e06
Compare
|
👀 Very interesting! Would be really cool if we could get our dependencies to all run on wasm-32. What I've seen looks good, with minimal changes/decorations to get this to work. I'll let others weigh in though, as devops is not my forté. |
jan-ferdinand
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for taking this on. I've left a number of comments inline. Additionally:
- I can't
wasm-pack buildbecause apparently, “crate-type must be cdylib to compile towasm32-unknown-unknown”. The suggestion is to addcrate-type = ["cdylib", "rlib"]to the Cargo.toml's[lib]section. I don't know what the implications of this are. Do you? - Running
wasm-pack test --node(which works) results in one test failure, in particular,ntt_on_empty_input. Runningcargo t ntt_on_empty_inputresults in a pass. Seems like we need to do some debugging. - Before merging, I'd like to have a CI script that builds twenty-first for a wasm target and executes the full test suite successfully.
| #[cfg(target_pointer_width = "32")] | ||
| const NUM_DOMAINS: usize = 29; // On 32-bit, up to length 2^28 to avoid isize::MAX overflow. | ||
|
|
||
| #[cfg(not(target_pointer_width = "32"))] | ||
| const NUM_DOMAINS: usize = 32; // Default for 64-bit and other architectures. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In order to not lose the docstring on #[cfg(not(target_pointer_width = "32"))] architectures, I suggest to merge these.
| #[cfg(target_pointer_width = "32")] | |
| const NUM_DOMAINS: usize = 29; // On 32-bit, up to length 2^28 to avoid isize::MAX overflow. | |
| #[cfg(not(target_pointer_width = "32"))] | |
| const NUM_DOMAINS: usize = 32; // Default for 64-bit and other architectures. | |
| const NUM_DOMAINS: usize = { | |
| #[cfg(target_pointer_width = "16")] | |
| compile_error!("pointer width 16 is not supported"); | |
| #[cfg(target_pointer_width = "32")] | |
| { | |
| 29 // On 32-bit, up to length 2^28 to avoid isize::MAX overflow. | |
| } | |
| #[cfg(target_pointer_width = "64")] | |
| { | |
| 32 // Default for 64-bit and other architectures. | |
| } | |
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can also
#[cfg(target_pointer_width = "16")]
const NUM_DOMAINS: usize = compile_error!("pointer width 16 is not supported");
/// The number of different domains over which this library can compute (i)NTT.
///
/// In particular, the maximum slice length for both [NTT][ntt] and [iNTT][intt]
/// supported by this library is 2^28. All domains of length some power of 2
/// smaller than this, plus the empty domain, are supported as well.
//
// only up to length 2^28: avoid isize::MAX overflow
#[cfg(target_pointer_width = "32")]
const NUM_DOMAINS: usize = 29;
/// The number of different domains over which this library can compute (i)NTT.
///
/// In particular, the maximum slice length for both [NTT][ntt] and [iNTT][intt]
/// supported by this library is 2^31. All domains of length some power of 2
/// smaller than this, plus the empty domain, are supported as well.
#[cfg(not(target_pointer_width = "64"))]
const NUM_DOMAINS: usize = 32;There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it seems the const NUM_DOMAINS: usize = gets removed in the first suggestion. Is it implied to be above the suggested code? Can conditional compilation be applied to expressions in a statement? It seems I am not fully understanding the suggestion.
The second suggestion seems more straight-forward.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're right, I botched the first suggestion. I've edited to unbotch.
Now, I'm also realizing that this const is not pub, so it's probably not particularly important anyway.
| /// supported by this library is 2^31 on 64 bit systems and 2^28 on 32 bit systems. | ||
| /// All domains of length some power of 2 smaller than this, plus the empty domain, | ||
| /// are supported as well. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: my spellchecker suggests “64-bit system” over “64 bit system”
Pedant: comment width of 80
| /// supported by this library is 2^31 on 64 bit systems and 2^28 on 32 bit systems. | |
| /// All domains of length some power of 2 smaller than this, plus the empty domain, | |
| /// are supported as well. | |
| /// supported by this library is 2^31 on 64-bit systems and 2^28 on 32-bit | |
| /// systems. All domains of length some power of 2 smaller than this, plus the | |
| /// empty domain, are supported as well. |
| #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)] | ||
| #[test] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Once frozenlib/test-strategy#18 is resolved, I suggest to reduce the attribute duplication by doing the following instead:
/// A crate-specific replacement of the `#[test]` attribute for tests that
/// should also be executed on `wasm` targets (which is almost all tests).
///
/// If you specifically want to exclude a test from `wasm` targets, use the
/// usual `#[test]` attribute instead.
///
/// # Usage
///
/// ```
/// #[macro_rules_attr::apply(test)]
/// fn foo() {
/// assert_eq!(4, 2 + 2);
/// }
/// ```
macro_rules! test {
($item:item) => {
#[test]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
$item
};
}
pub(crate) use test;
/// A crate-specific replacement of the `#[test_strategy::proptest]`
/// attribute for tests that should also be executed on `wasm` targets
/// (which is almost all tests).
///
/// If you specifically want to exclude a test from `wasm` targets, use the
/// usual `#[test_strategy::proptest]` attribute instead.
///
/// # Usage
///
/// ```
/// #[macro_rules_attr::apply(proptest)]
/// fn foo(#[strategy(0..=42)] x: i32) {
/// assert_eq!(2 * x, x + x);
/// }
/// ```
macro_rules! proptest {
($item:item) => {
#[test_strategy::proptest]
#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
$item
};
}
pub(crate) use proptest;Pros
- Less code duplication
- I think it's easier to see whether a new test has
#[macro_rules_attr::apply(test)]over#[test]than it is to see whether the line#[cfg_attr(target_arch = "wasm32", wasm_bindgen_test)]is missing
Cons
macro_rules_attrbecomes a new dev-dependency
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems unfortunate that the wasm ecosystem has to use a different attribute for tests. And that it can be such a headache to get them working at the Cargo.toml level. The end result (according to an LLM) seems to be that many/most crates that support building wasm32 target do not bother to make the test suite build and run for wasm32 target. In that sense, this PR is going the extra mile.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I both appreciate the extra mile and deem it important. I don't really trust that things will “just work” on a completely different architecture.
| "-C", "debuginfo=0", | ||
| "-C", "strip=symbols", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you add explainer comments why the two -C flags are here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sure. in a nutshell without these the generated debug binary is so huge that the wasm test harness barfs on it and emits huge stack traces without any decipherable error message.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is a great summary to ease the PR process; I don't think it should make its way into the repository. If the list of changes is relevant in the future, we can query git. If a specific thing needs to be justified, that justification should live close to the thing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I were maintainer I would include them, for future developers reference. But its your call; I can certainly remove the file.
| proptest-arbitrary-interop = "0.1" | ||
| test-strategy = "0.4" | ||
| trybuild = "1.0" | ||
| proptest-arbitrary-interop = {git="https://github.com/dan-da/proptest-arbitrary-interop.git", version = "0.1", rev="d9fcf5b"} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's hope that your PR on the upstream crate gets merged, but I'm not holding my breath. In order to publish twenty-first on crates.io, we must not have git dependencies per their policy. There are two obvious ways forward, and maybe there are more:
- We (or you) publish your fork as a new crate to crates.io, twenty-first then depends on that crate.
- We copy the entire content of your fork into twenty-first and remove the dependency. (We should publicly expose this for downstream usage.)
I don't like either method too much. I think I slightly favor the latter approach. Perhaps you see a better option still?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah, I don't really like either option. I just added a comment to that PR asking if its likely to be merged soon. If no reply by end of week, I suppose we should move forward with one of the two options.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is very helpful. I think we might as well merge this with the README.md. Thoughts?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think wasm32 is not relevant for a lot of people and would just clutter up the main README for them. I would suggest we keep the files separate, but add a link from the main readme.
| MmrAccumulator::new_from_leafs(vec![]).bag_peaks(); | ||
| } | ||
|
|
||
| // note: snapshot not supported for wasm32 target. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Too bad. I think it's better to keep the test and to remove the dependency on insta. To be frank, we don't really need the functionality provided by insta anyway, as we're only asserting on u64s and the occasional Digest with it. To me, it's much more important to have the snapshot tests also in wasm than to communicate that the assertion is a snapshot test.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've removed insta as a dev-dependency in 17a5683. Feel free to rebase.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i will, thx.
|
|
||
| ## 3\. Build and Test Commands | ||
|
|
||
| With the environment configured, you can now build and test the crate. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
“Make sure your current working directory is /path/to/twenty-first/twenty-first before executing any of the commands below.”
well, I just learned:
Apparently adding Hopefully we can make this specific to the wasm32 build.
I observed same/similar. Though I was using However I also saw multiple test failures running
This is novel behavior and I concluded that perhaps there is some shared state happening between tests. But since it was happening on master I put it firmly in the "not my problem" box. If anything, the wasm32 tests behaved better under the wasm test harness, because only one of them failed when run with Regardless, my position is that:
I consider CI scripts to be outside scope of this PR. Anyway, it is not a skill I possess. I would suggest that could be done in a followup and should not block merging this PR. |
Adds wasm32 support. See included README files:
This PR is a series of commits. The very first commit only changes Cargo.toml and is sufficient to enable wasm32 support for
cargo buildmeaning that crates that depend on twenty-first can use it in their wasm32 builds. So it can be considered the "bare minimum" for wasm32 support.The remainder of the commits mainly tackle the challenge of getting our unit tests and especially proptests working in the wasm32 environment, which is substantially more complicated.
There are also a couple of commits that address failing test cases. One of these failing tests found a problem with twenty-first when executing in a 32-bit environment. This is addressed in 19a11a4, and should be carefully reviewed for correctness. This is the only change that was made to non-test library code in any of the commits, afaik.
This PR may be easiest to grok by:
note: the README files were generated by Gemini LLM, with coaching and minor edits. Also the LLM was very helpful for navigating through the complex issues getting the tests to build and execute.