Skip to content

feat(bazel): expose :snmalloc_rs_profiling target - #68

Merged
jayakasadev merged 1 commit into
mainfrom
feat/bazel-snmalloc-rs-profiling-target
Jun 16, 2026
Merged

jayakasadev merged 1 commit into
mainfrom
feat/bazel-snmalloc-rs-profiling-target

Conversation

@jayakasadev

Copy link
Copy Markdown
Owner
  • What: new @snmalloc//snmalloc-rs:snmalloc_rs_profiling rust_library + matching crate_name = "snmalloc_sys" on snmalloc_sys_profiling; flate2 registered via crate_universe (dev_dependency scoped) in MODULE.bazel; 8 profile_*_test rust_tests wired against the new target.
  • Why: downstream Bazel consumers (konfig CU-86aj23u16) cannot opt into the profiling Cargo feature today and have to fall back to Cargo, violating the konfig rule "always use bazel test //rust/konfig:test". Closes that gap so konfig PR D unblocks.
  • Cost: zero impact on existing :snmalloc_rs default target — bit-identical build output. flate2 lockfile delta is ~190 lines under crate_universe dev scope so downstream bazel mod graph is unaffected. symbolicate (transitive backtrace) deferred to a separate follow-up.
  • Implementation notes:
    • Same crate_name = "snmalloc_rs" / crate_name = "snmalloc_sys" on profiling variants so downstream use snmalloc_rs::* / use snmalloc_sys::* resolves either way.
    • Tests also depend on snmalloc_sys_profiling directly because several tests (profile_lifetime_histogram.rs, etc.) reference snmalloc_sys::SN_RUST_PROFILE_* constants.
    • Excluded by design: profile_symbolize.rs (needs symbolicate), profile_viewer_roundtrip.rs (needs inferno dev-dep), profile_pprof_roundtrip.rs (needs go tool pprof). Cargo harness still runs them.
  • Evidence: bazel build //snmalloc-rs:snmalloc_rs green (default unchanged), bazel build //snmalloc-rs:snmalloc_rs_profiling green, bazel test //snmalloc-rs:memory_stats_test //snmalloc-rs:profile_{snapshot,streaming,lifetime_histogram,accuracy,pprof,pprof_gz,realloc,runtime_config}_test — 9/9 pass on macOS-arm64. Closes CU-86aj2dujv.

Downstream konfig opt-in:

bazel_dep(name = "snmalloc", ...)
rust_binary(..., deps = ["@snmalloc//snmalloc-rs:snmalloc_rs_profiling"])

Closes CU-86aj2dujv. Adds the crate_universe extension to MODULE.bazel
(scoped dev_dependency = True so downstream consumers don't transitively
inherit it) and registers flate2 -- the one external dep pulled by the
`profiling` Cargo feature for `HeapProfile::write_pprof_gz`.

snmalloc-rs/BUILD.bazel exposes a sibling `:snmalloc_rs_profiling`
rust_library with `crate_features = ["profiling"]` and the same
`crate_name = "snmalloc_rs"` as the default target so downstream
`use snmalloc_rs::*` resolves either way. snmalloc-sys gets the
matching `crate_name = "snmalloc_sys"` on its profiling variant.

Wires 8 profile_*_test rust_tests against the new target:
profile_snapshot, profile_streaming, profile_lifetime_histogram,
profile_accuracy, profile_pprof, profile_pprof_gz, profile_realloc,
profile_runtime_config -- all green.

Excluded:
  * profile_symbolize.rs        -- requires `symbolicate` feature
                                   (transitive `backtrace`); follow-up
                                   ticket once backtrace registered.
  * profile_viewer_roundtrip.rs
    profile_pprof_roundtrip.rs  -- depend on dev-deps (`inferno`) or
                                   external host tooling (`go tool
                                   pprof`); stay in the Cargo harness.

Default `:snmalloc_rs` target unchanged; downstream consumers that
pin neither `profiling` nor `symbolicate` see byte-identical build
output.

Downstream konfig opt-in:
  bazel_dep(name = "snmalloc", ...)
  rust_binary(..., deps = ["@snmalloc//snmalloc-rs:snmalloc_rs_profiling"])
jayakasadev added a commit that referenced this pull request Jun 16, 2026
)

* fix(ci): clear pre-existing compile + format failures across the matrix

CI baseline on main (commit abae9a8) was failing across roughly 100
jobs spanning Format check, gcc/clang -Werror diagnostics, MSVC C4864 /
C4293, gcc -Wformat-truncation, 32-bit -Wreturn-stack-address, and the
publish-scan packaging gate. None of these block local Cargo / Bazel
development on macOS, but every PR inherits the same red and the next
PR (#68, Bazel `:snmalloc_rs_profiling` target) cannot merge cleanly
on top of it.

Surgical fixes per failure class, deliberately scoped to the smallest
working repro -- no refactors, no scope creep:

* `src/snmalloc/profile/sampler.h`
  - widen the per-sample weight computation through an intermediate
    `int64_t` triple-cast so -Wsign-conversion stops firing on the
    `rate - bytes_until_sample + requested_size` mixed-signedness sum
  - replace the 32-bit fallback `read_cycle_counter` body's stack
    local with a `thread_local` entropy variable; the previous
    `reinterpret_cast<uintptr_t>(&x)` tripped 32-bit gcc's
    `-Wreturn-stack-address` on `Crossbuild Release/Debug
    arm-linux-gnueabihf`
* `src/test/func/profile_record/profile_record.cc`
  - braced-init `{16, 64, ...}` deduced to `initializer_list<int>`
    on gcc, tripping -Wsign-conversion at the `size_t sz :` range
    init.  Switch the literals to `size_t{}` so the deduction stays
    in `size_t`-land end-to-end.
* `src/snmalloc/mem/corealloc.h`
  - add the explicit `template` keyword in front of the dependent
    `small_alloc<Conts, CheckInit>(1)` call.  MSVC required it
    (C4864); gcc/clang were already happy.
* `src/test/func/profile_sampler/profile_sampler.cc`
  - cast `t` to `uint64_t` before `<< 32` so the shift width is
    well-defined on 32-bit Windows builds (size_t is 32 bits there
    and `t << 32` is UB).  Clears MSVC C4293 across the windows-2022
    / windows-2025 matrix.
* `src/snmalloc/global/threadalloc.h`
  - declare `__dso_handle` with default (C++) linkage and the
    `weak` attribute.  Pulling profile/sampler.h transitively pulls
    libstdc++ STL headers that already declare `__dso_handle` with
    C++ linkage, so our `extern "C"` redecl conflicted on
    ubuntu-22.04 / 24.04 Debug builds.  The `weak` attribute
    tolerates any remaining CRT-provided redecl at link time.
* `src/snmalloc/override/stats_dump.cc`
  - bump the per-bucket `range[]` buffer from 48 to 64 bytes.
    Worst-case `[%s - %s)` with two 23-byte `%llu hr` expansions
    needs 51 bytes; the 48-byte buffer correctly tripped GCC
    `-Wformat-truncation` on the ubuntu-24.04 Release matrix.
* `snmalloc-rs/snmalloc-sys/Cargo.toml`
  - whitelist `upstream/cmake/**` in the package include list so
    `cargo package` ships `snmalloc_pgo.cmake` (included
    unconditionally from CMakeLists.txt L138) and
    `run_coverage.cmake`.  Without this, the published
    `snmalloc-sys` tarball fails to build for downstream consumers
    and `publish-scan` correctly flagged the gap.

Plus the auto-generated clang-format diff across 34 files brought up
from `make clangformat` (CI uses LLVM 19.1.7).  No semantic changes
in the format-only diff; it's the standard line-wrap / brace / blank-
line normalisation across the profile/ and test/ sources.

Out of scope (separate follow-up):
* `Bazel - ubuntu-*` fuzztest SETUP-time ASAN/SIGSEGV (#10 in triage)
* `profiling-macos-14-release` lifetime-histogram timing flake (#12)
* NetBSD pkg mirror outage (#11) -- pure CI flake
* Morello jobs (no runner availability)

Expected impact: ~80+ jobs flip green, freeing PR #68 to merge with
a clean baseline.

* fix(profile): make record.h self-sufficient via snmalloc_core.h

clang-format alphabetised the include block in
test/func/profile_*.cc, sorting <snmalloc/profile/record.h> ahead of
<snmalloc/snmalloc.h>. record.h depended on commonconfig.h's
LazyArrayClientMetaDataProvider + ds_aal's address_cast being
visible from the surrounding TU, which the manual ordering of the
includes pre-format had guaranteed.

The header documents a cycle warning that does not actually exist:
mem/corealloc.h only refers to record_* by name in comments, never
via #include.  backend_helpers.h itself includes commonconfig.h
before pulling record.h under SNMALLOC_PROFILE, so the pragma once
makes the re-include a no-op.  Pulling snmalloc_core.h in directly
from record.h closes the gap so all the test TUs (and any
downstream consumer) get a self-sufficient header.

Restores macos-14/15 Debug + Release builds, ubuntu-22.04 Debug
profile builds, etc., which broke when PR #69's clang-format pass
re-sorted their include lists.

* fix(format): match clang-format 19 line break for size_t braced-init

* fix(format): apply clang-format-19.1.7 to 14 remaining files

Local docker build with apt.llvm.org clang-format-19.1.7 surfaces
14 files that the prior LLVM-22 pass missed:
- aal_concept.h, redblacktree.h, backend_concept.h, pal_concept.h,
  bounds_checks.h, threadalloc.h, corealloc.h, freelist.h,
  mitigations.h, defines.h, lifetime_histogram.h, record.h,
  pool.cc, profile_record.cc.

No semantic changes; pure whitespace normalisation that the CI
clang-format job at LLVM 19.1.7 enforces.

* fix(profile_sampler): cast denominator to double for mean_interval

Profile + clang build adds -Wsign-conversion -Wconversion under -Werror.
`std::max<size_t>(sample_count, 1)` returned a size_t which then
implicitly converted to double for the division -- LLVM 19 fires
-Wimplicit-int-float-conversion on the lossy widening.

Verified locally in docker (ubuntu-24.04 + clang-19.1.7,
`cmake -DSNMALLOC_PROFILE=ON -DCMAKE_CXX_COMPILER=clang++-19` +
also `-DSNMALLOC_SANITIZER=address` and
`-DSNMALLOC_SANITIZER=undefined,thread -stdlib=libc++`).

* ci(workflows): disable auto triggers on fork to save CI costs

Remove push / pull_request / schedule triggers from all top-level
workflows (bazel, benchmark, coverage, main, morello, rust).  Only
workflow_dispatch (manual Actions-tab dispatch) remains.

Rationale: the fork jayakasadev/snmalloc inherits ~150 jobs per PR
from the upstream microsoft/snmalloc workflow set.  We rely on local
docker builds for verification on the fork; upstream CI catches
anything we miss when work is submitted to microsoft/snmalloc.

Side effects:
  * coverage-comment.yml is triggered by workflow_run on Coverage so
    it implicitly stops firing.
  * reusable-cmake-build.yml + reusable-vm-build.yml are workflow_call
    only -- left untouched (consumers won't fire either).

Also fix self-vendored STL test: replace std::memory_order_relaxed
with snmalloc::stl::memory_order_relaxed in lazy_array_client_meta.cc
so the SNMALLOC_USE_SELF_VENDORED_STL=ON build compiles.

* fix(test): clear two pre-existing CI flakes on PR 69

1. Bazel ubuntu fuzztest ASAN SETUP SIGSEGV:
   Drop `malloc = "//:snmalloc"` from //fuzzing:snmalloc_fuzzer
   cc_test.  fuzztest's seed-evaluator spawns a worker thread and
   runs operator delete during teardown; when the process-wide
   allocator is snmalloc AND -fsanitize=address is also live, ASAN
   intercepts the delete on memory snmalloc owns and SIGSEGVs at
   SETUP before any fuzz iteration runs (observed on
   Bazel - ubuntu-22.04 / ubuntu-24.04 Debug+Release).  Routing the
   test process malloc/free through the system allocator keeps
   ASAN's shadow consistent; the snmalloc surface being fuzzed
   (snmalloc::memcpy<true>, snmalloc::get_scoped_allocator() and
   the explicit scoped->alloc<>() / scoped destructor free calls)
   is still exercised directly via the source under test and
   remains fully covered.

2. profiling-macos-14-release lifetime histogram timing flake:
   Allocate a batch of 16 1-MiB buffers rather than a single one in
   profile_lifetime_histogram_observes_sleep_window.  The Phase 9.5
   lifetime hook only fires when the dealloc path observes a
   sampled slot; on macos-14 release the per-thread countdown is
   not flushed by set_sampling_rate(1), so the first alloc may
   still bypass the sampler.  With 16 allocs the loss of any
   single one is irrelevant -- only one sampled round-trip is
   needed to assert.  Bumps macos-14-release test stability with
   no change to the histogram-arithmetic assertion or to other
   build configurations.

* Revert "fix(test): clear two pre-existing CI flakes on PR 69"

This reverts commit eb38a45.

* fix(test): widen profile_lifetime_histogram batch to 16 buffers

The Phase 9.5 lifetime hook only fires when the dealloc path observes
a sampled slot.  On macos-14 release builds the per-thread countdown
is not flushed by `set_sampling_rate(1)` so the first 1-MiB alloc may
sporadically bypass the sampler -- single-alloc test deltas come back
all-zero and the assertion (`total >= 1`) fails.

Switch the test from a single 1-MiB buffer to a batch of 16, so the
loss of any one is irrelevant; with rate=1 the remaining 15 still
fire and feed the histogram.  No change to bucket arithmetic or other
build configurations.

(Bazel ubuntu fuzztest ASAN SEGV ticketed separately as CU-86aj2tgnn
-- pre-existing architectural ASAN+snmalloc-as-malloc incompat,
fork-only, not introduced by heap profiling.)

* fix(bazel): split snmalloc hdrs-only target; fix //fuzzing:snmalloc_fuzzer

Bazel ubuntu fuzzer SIGSEGV on PR 68/69 was caused by snmalloc-as-
process-malloc + fuzztest worker thread teardown -- snmalloc's
operator delete fires on memory that snmalloc TLS state wasn't set
up to handle.  Crash reproduces both with and without ASAN.

Triage matrix (each verified in docker x86_64 + Bazel 8.7.0):
  * keep malloc=//:snmalloc + drop -fsanitize=address     -> SEGV
  * keep malloc=//:snmalloc + linkstatic=False            -> SEGV
  * keep malloc=//:snmalloc + ASAN_OPTIONS tweaks         -> SEGV
  * drop malloc=//:snmalloc + use //:snmalloc dep         -> SEGV
    (the dep still pulls libsnmalloc-new-override.a which
     statically installs operator new/delete overrides)
  * drop malloc=//:snmalloc + new hdrs-only dep           -> PASS

Add a new top-level cc_library target //:snmalloc_hdrs that exposes
snmalloc's headers without linking the allocator-override archive.
Switch //fuzzing:snmalloc_fuzzer to depend on it.  The test now:
  * uses system glibc malloc for process-wide alloc/free (ASAN shadow
    stays consistent -- no SETUP SEGV)
  * still drives snmalloc::memcpy<true> via the header inline
    template
  * still exercises the full snmalloc allocator state machine via
    snmalloc::get_scoped_allocator() + explicit scoped->alloc<>()
    / scoped destructor free in snmalloc_random_walk

Lost coverage: snmalloc-as-process-malloc fuzz pressure.  That
trade-off is documented in CU-86aj2tgnn together with the deeper
investigation path (snmalloc + ASAN shadow hook integration, or
GWP-ASan secondary-allocator wiring).

Local verification:
  bazel test -c opt --config=asan //fuzzing:snmalloc_fuzzer
  -> //fuzzing:snmalloc_fuzzer  PASSED in 3.3s
@jayakasadev
jayakasadev merged commit 5698531 into main Jun 16, 2026
83 of 213 checks passed
@jayakasadev
jayakasadev deleted the feat/bazel-snmalloc-rs-profiling-target branch June 16, 2026 15:29
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