Skip to content

Support 32-bit machines - #128

Merged
kampersanda merged 2 commits into
mainfrom
claude/64bit-restriction-removal-1b069a
Aug 31, 2026
Merged

kampersanda merged 2 commits into
mainfrom
claude/64bit-restriction-removal-1b069a

Conversation

@kampersanda

Copy link
Copy Markdown
Owner

What

Removes the 64-bit-only restriction: the compile_error! in lib.rs and the #![cfg(target_pointer_width = \"64\")] attributes on 14 modules are gone, and the handful of real dependencies on the pointer width are fixed.

Why

The restriction turned out to be almost entirely incidental. Deleting the cfg gates and building for a 32-bit target produced only three compile errors, all of them broadword constants. Everything else was already stored in explicit widths (Vec<u64>, Vec<u16>, Vec<u8>), with usize used for lengths and positions as it should be.

What actually depended on the pointer width

  • broadword — the 64-bit step constants (ONES_STEP_8, MSBS_STEP_9, …) were typed usize, so they overflowed at compile time on a 32-bit target. Now u64, along with the helpers that consume them. On a 64-bit machine the two types are identical, so this is a no-op there.
  • Rank9SelIndex::block_rank_pairs — packs seven 9-bit subranks into one word, which does not fit in a 32-bit usize. Now Vec<u64>.
  • DArrayIndex::block_inventory — bit positions stored as isize; beyond 2^31 bits on a 32-bit machine they would wrap into the negative overflow marker and silently corrupt select. Now Vec<i64>.
  • EliasFanoBuilder::push — val as usize >> low_len cast before shifting, truncating the value. Now shifts before casting. (Found by an actual 32-bit test run, not by reading.)
  • EliasFano::delta — computed the delta in usize, which overflows once low_len >= 32. Now computes in u64. (Also found by running.)

Serialization

usize and isize are now serialized as fixed 64-bit little-endian integers instead of native-width ones, so the format no longer depends on the machine.

  • Backward compatible. The bytes are unchanged from what 0.9.1 writes on a 64-bit machine, so existing serialized data stay readable.
  • Portable. A file written on a 64-bit machine reads on a 32-bit one and vice versa.
  • Deserializing a value that does not fit in usize of the machine fails with SucdsError::InvalidArgument rather than truncating silently.

Verification

Runtime behavior was checked on wasm32-wasip1, a genuine 32-bit target, run under Node's WASI.

Check Result
Unit tests on wasm32-wasip1 (32-bit) 90/90 pass
Tests + doctests on 64-bit (default / intrinsics / no-default-features) 90 + 101 pass each
cargo check --target i686-unknown-linux-gnu --all-targets (stable and 1.62.0) pass
cargo build --no-default-features --target thumbv7em-none-eabi (stable and 1.62.0) pass
x86_64-unknown-none (existing no_std job) pass
MSRV 1.62.0 pass
cargo fmt / clippy nursery -D warnings / cargo doc -D warnings clean

Serialized bytes and query results were compared across three builds — 0.9.1 on 64-bit, this branch on 64-bit, and this branch on 32-bit — over BitVector, Rank9Sel, DArray, SArray, CompactVector, DacsByte, DacsOpt, PrefixSummedEliasFano, EliasFano, and WaveletMatrix. All three outputs are byte-identical.

CI

  • New test-32bit job: builds and runs the tests for i686-unknown-linux-gnu. i686 binaries execute natively on the x86_64 runner, so the tests really run with a 32-bit usize rather than only cross-compiling. -C target-cpu=native is deliberately not set for the intrinsics run, since the host is x86_64. Stable only — the MSRV is covered by the 64-bit test job, and nothing here depends on the language version.
  • The no-std job also builds for thumbv7em-none-eabi, covering no_std and a 32-bit pointer width at once.

Note: the i686 job itself has not been executed yet — it was developed on macOS, where no Linux runner is available. It is compile-checked on both toolchains, and the 32-bit runtime behavior is covered by the wasm32 run above. This PR's CI is its first real execution.

Remaining limitations (documented in the README and crate docs)

  • On a 32-bit machine each structure is bounded by usize, i.e. fewer than 2^32 stored bits or integers.
  • Broadword operations on 64-bit words are emulated on 32-bit machines and are therefore slower.
  • No 64-bit performance regression is expected: usize and u64 are the same type there and the memory layout is unchanged. Benchmarks have not been run.

🤖 Generated with Claude Code

kampersanda and others added 2 commits August 31, 2026 15:31
The crate refused to compile unless `target_pointer_width` was 64, via a
`compile_error!` in `lib.rs` and `#![cfg(target_pointer_width = "64")]` on
most modules. Removing them exposed only a handful of real dependencies on
the pointer width, all of which are fixed here.

- `broadword`: the 64-bit step constants were typed `usize`, so they
  overflowed at compile time on a 32-bit target. They are now `u64`, along
  with the helpers that consume them. On a 64-bit machine the types are
  identical, so nothing changes there.
- `Rank9SelIndex::block_rank_pairs`: it packs seven 9-bit subranks into one
  word, which does not fit in a 32-bit `usize`. It is now `Vec<u64>`.
- `DArrayIndex::block_inventory`: bit positions stored as `isize` would wrap
  into the negative overflow marker beyond 2^31 bits on a 32-bit machine.
  It is now `Vec<i64>`.
- `EliasFanoBuilder::push`: `val as usize >> low_len` cast before shifting
  and thus truncated the value. It now shifts before casting.
- `EliasFano::delta`: it computed the delta in `usize`, which overflows when
  `low_len >= 32`. It now computes in `u64`.

`usize` and `isize` are serialized as fixed 64-bit little-endian integers
instead of native-width ones, so the format no longer depends on the machine.
The bytes are unchanged from those written by 0.9.1 on a 64-bit machine, so
existing data stay readable; deserializing a value that does not fit in
`usize` of the machine now fails with `SucdsError::InvalidArgument`.

Verified that the serialized bytes and the query results are identical
between 0.9.1 on 64-bit, this commit on 64-bit, and this commit on a 32-bit
target, and that all unit tests pass on wasm32-wasip1.

CI gains a `test-32bit` job that builds and runs the tests for
i686-unknown-linux-gnu (i686 binaries run natively on the x86_64 runner, so
the tests really execute with a 32-bit `usize`), and the no_std job also
builds for thumbv7em-none-eabi to cover no_std and a 32-bit pointer width at
once.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Say the same in two short paragraphs instead of eight lines of prose.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@kampersanda
kampersanda merged commit e9e6ffc into main Aug 31, 2026
7 checks passed
@kampersanda
kampersanda deleted the claude/64bit-restriction-removal-1b069a branch August 31, 2026 06:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant