Skip to content

Commit a187e32

Browse files
committed
Add no_std and WASM support
This commit adds comprehensive no_std compatibility while maintaining 100% backward compatibility with existing std-based code. Key changes: - Add optional `std` feature (enabled by default) - Add `alloc` and `cache` features for heap allocation in no_std - Use spin crate for no_std synchronization (Once, RwLock, Mutex) - Use hashbrown for no_std HashMap when cache feature enabled - Fix UB in algorithm.rs (potential out-of-bounds read) - Fix Miri memory leaks in software fallback caching - Add dual-path feature detection (runtime with std, compile-time without) - Add comprehensive test coverage (tests/no_std_tests.rs, tests/wasm_tests.rs) - Add CI workflows for embedded (ARM, RISC-V) and WASM targets - Update all modules to use core/alloc primitives where appropriate Tested on: - Embedded: thumbv7em, thumbv8m, riscv32 - WASM: wasm32-unknown-unknown, wasm32-wasip1, wasm32-wasip2 - All existing x86/x86_64/aarch64 targets - Miri validation passes (no memory leaks)
1 parent f44f8d0 commit a187e32

36 files changed

+1580
-290
lines changed

.github/workflows/tests.yml

Lines changed: 80 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,4 +146,83 @@ jobs:
146146
run: cargo miri nextest run --all-features -j${{ steps.cores.outputs.count }}
147147
- name: Run Miri tests (serial)
148148
if: steps.cores.outputs.use_nextest == 'false'
149-
run: cargo miri test --all-features
149+
run: cargo miri test --all-features
150+
151+
test-no-std:
152+
name: Test no_std
153+
runs-on: ubuntu-latest
154+
strategy:
155+
matrix:
156+
target:
157+
- thumbv7em-none-eabihf # ARM Cortex-M4F/M7F
158+
- thumbv8m.main-none-eabihf # ARM Cortex-M33/M35P
159+
- riscv32imac-unknown-none-elf # RISC-V 32-bit
160+
rust-toolchain:
161+
- "1.81" # minimum for this crate
162+
- "stable"
163+
- "nightly"
164+
steps:
165+
- uses: actions/checkout@v4 # not pinning to commit hash since this is a GitHub action, which we trust
166+
- uses: actions-rust-lang/setup-rust-toolchain@9d7e65c320fdb52dcd45ffaa68deb6c02c8754d9 # v1.12.0
167+
with:
168+
toolchain: ${{ matrix.rust-toolchain }}
169+
target: ${{ matrix.target }}
170+
components: rustfmt, clippy
171+
cache-key: ${{ matrix.target }}-${{ matrix.rust-toolchain }}
172+
- name: Check no_std (no features)
173+
run: cargo check --target ${{ matrix.target }} --no-default-features --features panic-handler --lib
174+
- name: Check no_std with alloc
175+
run: cargo check --target ${{ matrix.target }} --no-default-features --features alloc,panic-handler --lib
176+
- name: Check no_std with cache
177+
run: cargo check --target ${{ matrix.target }} --no-default-features --features cache,panic-handler --lib
178+
- name: Run no_std tests (on host with std test harness)
179+
run: cargo test --test no_std_tests
180+
181+
test-wasm:
182+
name: Test WASM
183+
runs-on: ubuntu-latest
184+
strategy:
185+
matrix:
186+
include:
187+
# WASM 1.0/2.0 (32-bit) - all toolchains
188+
- target: wasm32-unknown-unknown
189+
rust-toolchain: "1.81"
190+
- target: wasm32-unknown-unknown
191+
rust-toolchain: "stable"
192+
- target: wasm32-unknown-unknown
193+
rust-toolchain: "nightly"
194+
# WASI preview 1 (32-bit) - all toolchains
195+
- target: wasm32-wasip1
196+
rust-toolchain: "1.81"
197+
- target: wasm32-wasip1
198+
rust-toolchain: "stable"
199+
- target: wasm32-wasip1
200+
rust-toolchain: "nightly"
201+
# WASI preview 2 (32-bit) - nightly only (experimental)
202+
- target: wasm32-wasip2
203+
rust-toolchain: "nightly"
204+
# Note: wasm64-unknown-unknown removed - not consistently available in nightly
205+
steps:
206+
- uses: actions/checkout@v4 # not pinning to commit hash since this is a GitHub action, which we trust
207+
- uses: actions-rust-lang/setup-rust-toolchain@9d7e65c320fdb52dcd45ffaa68deb6c02c8754d9 # v1.12.0
208+
with:
209+
toolchain: ${{ matrix.rust-toolchain }}
210+
target: ${{ matrix.target }}
211+
components: rustfmt, clippy
212+
cache-key: ${{ matrix.target }}-${{ matrix.rust-toolchain }}
213+
- name: Check WASM (no features)
214+
run: cargo check --target ${{ matrix.target }} --no-default-features --features panic-handler --lib
215+
- name: Check WASM with alloc
216+
run: cargo check --target ${{ matrix.target }} --no-default-features --features alloc,panic-handler --lib
217+
- name: Check WASM with cache
218+
run: cargo check --target ${{ matrix.target }} --no-default-features --features cache,panic-handler --lib
219+
- name: Build WASM release
220+
run: cargo build --target ${{ matrix.target }} --no-default-features --features alloc,panic-handler --lib --release
221+
- name: Run WASM tests (on host with std test harness)
222+
run: cargo test --test wasm_tests
223+
- if: ${{ matrix.target == 'wasm32-unknown-unknown' && matrix.rust-toolchain == 'stable' }}
224+
name: Install wasm-pack
225+
run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
226+
- if: ${{ matrix.target == 'wasm32-unknown-unknown' && matrix.rust-toolchain == 'stable' }}
227+
name: Build WASM package with wasm-pack
228+
run: wasm-pack build --target web --no-default-features --features alloc,panic-handler

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,4 @@
44
.idea
55
.DS_Store
66
.git
7-
.vscode
7+
.vscode

Cargo.lock

Lines changed: 95 additions & 10 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,19 +23,26 @@ bench = true
2323

2424
[dependencies]
2525
crc = "3"
26-
digest = { version = "0.10", features = ["alloc"] }
26+
digest = { version = "0.10", optional = true, default-features = false, features = ["alloc"] }
2727

2828
# will be removed once Rust 1.89 is the minimum supported version
2929
rustversion = "1.0"
3030

3131
# constrain indexmap (transitive) to a version compatible with Rust 1.81.0
3232
indexmap = { version = ">=2.11.0, <2.12.0", optional = true }
3333

34+
# no_std support
35+
# spin is always required for no_std builds (feature detection synchronization)
36+
spin = { version = "0.10.0", default-features = false, features = ["once", "rwlock", "mutex", "spin_mutex"] }
37+
# hashbrown is only needed when caching is enabled in no_std
38+
hashbrown = { version = "0.16.0", optional = true }
39+
3440
[dev-dependencies]
3541
criterion = "0.7"
3642
cbindgen = "0.29"
3743
rand = "0.9"
3844
regex = "1.12"
45+
wasm-bindgen-test = "0.3"
3946

4047
# lto=true has a big improvement in performance
4148
[profile.release]
@@ -62,12 +69,16 @@ required-features = ["cli"]
6269
[[bench]]
6370
name = "benchmark"
6471
harness = false
72+
required-features = ["std"]
6573

6674
[features]
67-
default = ["std"]
68-
std = []
75+
default = ["std", "panic-handler"]
76+
std = ["alloc"] # std implies alloc is available
6977
cli = ["std"]
70-
alloc = []
78+
alloc = ["digest"] # marker feature for heap allocation support
79+
cache = ["alloc", "hashbrown"] # caching requires alloc + hashbrown HashMap
80+
ffi = ["std"]
81+
panic-handler = [] # Provides panic handler for no_std library checks (disable in binaries)
7182

7283
# the features below are deprecated, aren't in use, and will be removed in the next MAJOR version (v2)
7384
vpclmulqdq = [] # deprecated, VPCLMULQDQ stabilized in Rust 1.89.0
@@ -87,4 +98,4 @@ rustdoc-args = ["--cfg", "docsrs"]
8798
[[test]]
8899
name = "checksum_integration_tests"
89100
path = "tests/checksum_integration_tests.rs"
90-
required-features = ["cli"]
101+
required-features = ["cli"]

benches/benchmark.rs

Lines changed: 12 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -59,11 +59,8 @@ fn create_aligned_data(input: &[u8]) -> Vec<u8> {
5959
// Size of our target alignment structure
6060
let align_size = std::mem::size_of::<[[u64; 4]; 2]>(); // 64 bytes
6161

62-
// Create a vector with padding to ensure we can find a properly aligned position
63-
let mut padded = Vec::with_capacity(input.len() + align_size);
64-
65-
// Fill with zeros initially to reach needed capacity
66-
padded.resize(input.len() + align_size, 0);
62+
// Create a zero-filled vector with padding to ensure we can find a properly aligned position
63+
let mut padded = vec![0; input.len() + align_size];
6764

6865
// Find the first address that satisfies our alignment
6966
let start_addr = padded.as_ptr() as usize;
@@ -87,7 +84,7 @@ fn bench_crc32(c: &mut Criterion) {
8784
);
8885

8986
for (size_name, size) in SIZES {
90-
let buf = create_aligned_data(&*random_data(*size));
87+
let buf = create_aligned_data(&random_data(*size));
9188

9289
let (part1, rest) = buf.split_at(buf.len() / 4);
9390
let (part2, rest) = rest.split_at(rest.len() / 3);
@@ -115,10 +112,10 @@ fn bench_crc32(c: &mut Criterion) {
115112
b.iter(|| {
116113
black_box({
117114
let mut digest = crc_fast::Digest::new(*algorithm);
118-
digest.update(&part1);
119-
digest.update(&part2);
120-
digest.update(&part3);
121-
digest.update(&part4);
115+
digest.update(part1);
116+
digest.update(part2);
117+
digest.update(part3);
118+
digest.update(part4);
122119
digest.finalize()
123120
})
124121
})
@@ -137,7 +134,7 @@ fn bench_crc64(c: &mut Criterion) {
137134
let mut group = c.benchmark_group("CRC-64");
138135

139136
for (size_name, size) in SIZES {
140-
let buf = create_aligned_data(&*random_data(*size));
137+
let buf = create_aligned_data(&random_data(*size));
141138

142139
let (part1, rest) = buf.split_at(buf.len() / 4);
143140
let (part2, rest) = rest.split_at(rest.len() / 3);
@@ -165,10 +162,10 @@ fn bench_crc64(c: &mut Criterion) {
165162
b.iter(|| {
166163
black_box({
167164
let mut digest = crc_fast::Digest::new(*algorithm);
168-
digest.update(&part1);
169-
digest.update(&part2);
170-
digest.update(&part3);
171-
digest.update(&part4);
165+
digest.update(part1);
166+
digest.update(part2);
167+
digest.update(part3);
168+
digest.update(part4);
172169
digest.finalize()
173170
})
174171
})

0 commit comments

Comments
 (0)