Skip to content

fix(alloc): request 16-byte alignment for small bemo_alloc sizes - #50

Merged
Sam Gammon (sgammon) merged 2 commits into
mainfrom
detail/bug-fix/fix-alloc-request-16-byte-alignment-for-small-bemo-63e77d
Oct 14, 2026
Merged

Sam Gammon (sgammon) merged 2 commits into
mainfrom
detail/bug-fix/fix-alloc-request-16-byte-alignment-for-small-bemo-63e77d

Conversation

@detail-app

@detail-app detail-app Bot commented Oct 14, 2026

Copy link
Copy Markdown
Contributor

Detail bug report: View on Detail

Closes #45

Change

bemo_alloc / alloc::allocate document "aligned to at least 16", but called plain mi_malloc(size). On bundled-mimalloc builds (non-Apple: Linux/musl) sizes 1–8 are served from mimalloc's 8-byte block class, so roughly half of returned pointers were congruent to 8 mod 16. This is reachable from C embedders, the JVM FFM binding, GraalVM Native Image, and Netty's NativeDirectByteBufAllocator (which forwards capacity verbatim). Apple / miri / rust-allocator were unaffected — there mi_malloc resolves to a 16-aligned Rust-allocator wrapper.

  • allocate() now calls mi_malloc_aligned(size, 16), still paired with the existing mi_free.
  • Exposed mi_malloc_aligned across all three allocator resolution paths in buffer.rs: bundled mimalloc (libmimalloc-sys re-export), embedder-supplied mimalloc (extern "C"), and the rust_alloc shim (delegates to mi_malloc for any alignment ≤ 16 — unchanged behavior, since its MI_ALIGN header already yields 16-aligned payloads).
  • The transport's internal Buffer allocation path is untouched.

No new ABI symbols, no version bump, no layout/ownership/lifetime change — mi_malloc_aligned is a pub(crate) alias, and bemo_alloc/bemo_free/bemo_alloc_live are unchanged. The fix makes the implementation honor a contract already published in include/bemo.h.

Validation

Platform: Linux x86-64 (glibc, static musl, and the Elide toolchain).

  • Routine: workspace build (debug + release), cargo check -p bemo --no-default-features, cargo clippy --workspace --all-targets --locked -- -D warnings, and cargo fmt --all --check all clean.
  • Rust unit tests (alloc module): pass on default bundled-mimalloc, under --profile ci, under --features rust-allocator, and on the static x86_64-unknown-linux-musl target — the production config where the bug reproduced. The new every_size_class_returns_16_aligned_storage holds 32 allocations of each size 1–64 at once and asserts 16-byte alignment + first/last-byte writability; it fails on the buggy mi_malloc (size 1 returned a pointer not aligned to 16, left: 8) and passes with the fix.
  • Integration: crates/bemo/tests/abi.rs (14/14) and bemo-ffi (1/1) pass. Full bemo suite: 378/380 — the 2 failures (persistent_keepalive_reuses_receive_registration_and_fixed_send_state, arm_receive_failure_leaves_socket_dormant) are pre-existing driver/http socket flakes that reproduce identically on a clean checkout (git stash), unrelated to this change.
  • C ABI: strengthened tests/static.c to allocate sizes 1–16 through the published bemo_alloc and assert 16-alignment + per-byte writability + bemo_alloc_live accounting + bemo_free; tests/abi.c (4-thread cross-thread alloc/free, the Netty release pattern) — both pass linked against the debug static lib, and tests/static.c also passes under the ASAN and TSAN native stages.
  • Miri: cargo miri test -p bemo --no-default-features --features rust-allocator --lib --test abi --test buffers --test pool --test workload — clean, no UB on the rust_alloc header path.
  • Sanitizers: ASAN and TSAN rust stages run all alloc tests with no violations; the instrumented tests/static.c native stage passes under both. (Stock ASAN/TSAN use rust-allocator, so they cover the rust_alloc path; the bundled-mimalloc path is covered by the musl + unit tests.)
  • Embedder toolchain (make test-embedder): all 4 cases pass — gnu-mimalloc, gnu-rust-allocator, musl-libc-mimalloc (static), musl-rust-allocator (static) — each linking tests/embedder against the embedder's allocator with the Elide clang and checking all 97 header exports are defined exactly once.
  • Musl static suite (make test-musl): 379/381 (same 2 pre-existing flakes); alloc tests pass on the production musl + bundled-mimalloc target.
  • FFM (per template): make test-jvm — FfmContract and CAbiContract (tests/abi.c vs libbemo_ffi.so) both failures=0, and Netty NativeDirectAllocatorTest (forwards capacities 1–8 to bemo_alloc) failures=0. One unrelated JVM contract, NativeSslInteropTest, failed only because the sandbox denies exec of /usr/bin/openssl.
  • Native Image (per template): could not be fully verified here. native-image was not on PATH — I located it, made it executable, and set JAVA_HOME; all Java modules and the CapiContract test sources compiled, but GraalVM native-image image generation then failed because the JVM cannot posix_spawn /usr/bin/gcc in this sandbox (posix_spawn failed, error: 13 (Permission denied)) — the same sandbox restriction that blocks NativeSslInteropTest. gcc itself works from the shell, so this is an environment limitation, not a code issue. Since this change adds no symbols/layouts and does not alter ownership/lifetime, the Native Image C API contract is unaffected by the fix.

Automatic Fixes PRs can be configured here.

@codecov

codecov Bot commented Oct 14, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 86.79%. Comparing base (da8dbb6) to head (2700e92).
✅ All tests successful. No failed tests found.

Additional details and impacted files
@@            Coverage Diff            @@
##               main      #50   +/-   ##
=========================================
  Coverage     86.78%   86.79%           
+ Complexity      616      615    -1     
=========================================
  Files            60       60           
  Lines         16912    16930   +18     
  Branches        490      490           
=========================================
+ Hits          14677    14694   +17     
- Misses         1911     1913    +2     
+ Partials        324      323    -1     
Flag Coverage Δ
jvm 57.47% <ø> (-0.04%) ⬇️
rust 93.01% <100.00%> (+<0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

@codspeed

codspeed Bot commented Oct 14, 2026 •

Copy link
Copy Markdown

Merging this PR will not alter performance

⚠️ Unknown Walltime execution environment detected

Using the Walltime instrument on standard Hosted Runners will lead to inconsistent data.

For the most accurate results, we recommend using CodSpeed Macro Runners: bare-metal machines fine-tuned for performance measurement consistency.

⚠️ 6 benchmarks spent significant time in system calls

System calls cannot be consistently instrumented, so they are not included in the measure, which understates the real cost. Please switch to the Walltime instrument to accurately measure system calls.

Measurement and system calls

✅ 117 untouched benchmarks
⏩ 65 skipped benchmarks1


Comparing detail/bug-fix/fix-alloc-request-16-byte-alignment-for-small-bemo-63e77d (2700e92) with main (da8dbb6)

Open in CodSpeed

Footnotes

  1. 65 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports. ↩

clippy::undocumented_unsafe_blocks requires a SAFETY comment on the
unsafe { mi_malloc(size) } block in mi_malloc_aligned; the other unsafe
blocks in buffer.rs already have them. Fixes the macOS CI failures.
@sgammon
Sam Gammon (sgammon) merged commit 26fb123 into main Oct 14, 2026
25 checks passed
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.

[Detail Bug] FFI: bemo_alloc returns only 8-byte-aligned pointers for small allocations on mimalloc builds

1 participant