From cfb1bdeb6bc4b6fb946b4fec3740ac3bfbcba787 Mon Sep 17 00:00:00 2001 From: e1turin Date: Wed, 14 Oct 2026 00:42:02 +0300 Subject: [PATCH 1/5] reorganize project structure --- .bazelrc | 2 + src/.bazelversion => .bazelversion | 0 .github/workflows/bazel.yaml | 7 +-- MODULE.bazel | 8 +++ README.md | 72 ++++++++++++++++++++------- benchmarks/memory/BUILD.bazel | 10 ++++ benchmarks/memory/memcpy_benchmark.cc | 41 +++++++++++++++ lib/memory/BUILD.bazel | 18 +++++++ lib/memory/memcpy.cc | 11 ++++ lib/memory/memcpy.h | 10 ++++ lib/memory/memcpy_test.cc | 36 ++++++++++++++ reports/.gitkeep | 1 + results/.gitkeep | 1 + src/.bazelrc | 1 - src/MODULE.bazel | 8 --- src/lib/BUILD | 14 ------ src/lib/memcpy_bench.cc | 54 -------------------- src/lib/memcpy_bench.h | 13 ----- src/main/BUILD | 9 ---- src/main/main.cc | 6 --- src/test/BUILD | 11 ---- src/test/memcpy_bench_test.cc | 15 ------ tests/.gitkeep | 1 + tools/.gitkeep | 1 + 24 files changed, 194 insertions(+), 156 deletions(-) create mode 100644 .bazelrc rename src/.bazelversion => .bazelversion (100%) create mode 100644 MODULE.bazel create mode 100644 benchmarks/memory/BUILD.bazel create mode 100644 benchmarks/memory/memcpy_benchmark.cc create mode 100644 lib/memory/BUILD.bazel create mode 100644 lib/memory/memcpy.cc create mode 100644 lib/memory/memcpy.h create mode 100644 lib/memory/memcpy_test.cc create mode 100644 reports/.gitkeep create mode 100644 results/.gitkeep delete mode 100644 src/.bazelrc delete mode 100644 src/MODULE.bazel delete mode 100644 src/lib/BUILD delete mode 100644 src/lib/memcpy_bench.cc delete mode 100644 src/lib/memcpy_bench.h delete mode 100644 src/main/BUILD delete mode 100644 src/main/main.cc delete mode 100644 src/test/BUILD delete mode 100644 src/test/memcpy_bench_test.cc create mode 100644 tests/.gitkeep create mode 100644 tools/.gitkeep diff --git a/.bazelrc b/.bazelrc new file mode 100644 index 0000000..1877fa4 --- /dev/null +++ b/.bazelrc @@ -0,0 +1,2 @@ +# Performance measurements should use optimized builds by default. +build -c opt diff --git a/src/.bazelversion b/.bazelversion similarity index 100% rename from src/.bazelversion rename to .bazelversion diff --git a/.github/workflows/bazel.yaml b/.github/workflows/bazel.yaml index 50f2d12..86bedd0 100644 --- a/.github/workflows/bazel.yaml +++ b/.github/workflows/bazel.yaml @@ -12,11 +12,6 @@ jobs: test: runs-on: ubuntu-latest - defaults: - run: - # MODULE.bazel and .bazelversion live in src/ - working-directory: src - steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 @@ -25,7 +20,7 @@ jobs: bazelisk-cache: true disk-cache: ${{ github.workflow }} repository-cache: true - # Pull requests only restore caches, pushes to main refresh them + # Pull requests only restore caches, pushes to main refresh them. cache-save: ${{ github.event_name == 'push' }} bazelrc: | common --announce_rc diff --git a/MODULE.bazel b/MODULE.bazel new file mode 100644 index 0000000..f24f9d2 --- /dev/null +++ b/MODULE.bazel @@ -0,0 +1,8 @@ +# C/C++ rules for the reusable frame-processing libraries and executables. +bazel_dep(name = "rules_cc", version = "0.2.26") + +# Test-only dependency; production libraries in lib/ do not depend on it. +bazel_dep(name = "googletest", version = "1.18.0.bcr.1", dev_dependency = True) + +# Benchmark executables depend on this package, never libraries in lib/. +bazel_dep(name = "google_benchmark", version = "1.9.5") diff --git a/README.md b/README.md index 99b5b71..8811a56 100644 --- a/README.md +++ b/README.md @@ -1,34 +1,68 @@ # memcopy-bench -DevTools 2026 Research Project. Violet Team -# Setup +A C/C++ research framework for evaluating data movement and frame-processing +techniques in Telecom L2 scenarios. The initial focus is PDCP and GTP-U frame +processing on x86, while reusable library code avoids unnecessary +architecture-specific assumptions. -We use [Bazel](https://bazel.build/install) to build, run and test our project. -The Bazel version is pinned in `src/.bazelversion`, use -[Bazelisk](https://github.com/bazelbuild/bazelisk) to get the right one. +## Layout -All commands below are run from `src/`: -```bash -cd src/ -``` +- `lib/` — reusable frame, memory, transport, and scenario implementations. + Libraries do not depend on benchmarking or reporting infrastructure. +- `benchmarks/` — Google Benchmark executables. `memory/` holds operation + microbenchmarks; `scenarios/` will hold complete workflow benchmarks. +- `tests/` — integration and cross-component tests. Unit tests are colocated + with the library code they verify. +- `tools/` — experiment automation and analysis utilities. +- `results/` — raw benchmark output and experiment metadata (kept separate + from analysis). +- `reports/` — research findings and comparative analysis. + +## Prerequisites -Optimized build mode (`-c opt`) is enabled by default in `src/.bazelrc`. +Install [Bazelisk](https://github.com/bazelbuild/bazelisk). It selects the +Bazel version pinned in `.bazelversion`. Commands are run from the repository +root. Optimized mode is enabled by default in `.bazelrc`. + +## Build and test -**Build:** ```bash -bazel build //lib:memcpy_bench # build benchmark lib only -bazel build //main:main # build main binary and library +bazel build //... +bazel test //... ``` -**Test:** +For example, the initial reusable algorithm and its colocated test are: + ```bash -bazel test //test:memcpy_bench_test +bazel build //lib/memory:memcpy +bazel test //lib/memory:memcpy_test ``` -**Run benchmarks:** +## Run a benchmark + ```bash -bazel run //main:main +bazel run //benchmarks/memory:memcpy_benchmark -# Google Benchmark flags are passed after `--` -bazel run //main:main -- --benchmark_filter='BM_Memcpy/4096' --benchmark_format=json +# Google Benchmark options follow `--`. +bazel run //benchmarks/memory:memcpy_benchmark -- \ + --benchmark_filter='BM_Memcpy/4096' \ + --benchmark_format=json \ + --benchmark_out=results/memcpy-4096.json ``` + +Store the matching experiment metadata (scenario, algorithm, input size, +compiler configuration, CPU information, and source revision) alongside raw +output in `results/`. Use `reports/` for the derived analysis. + +## Development workflow + +1. Add an algorithm or explicit frame-processing scenario under `lib/`. +2. Add colocated functional tests and run `bazel test`. +3. Add or extend the corresponding executable under `benchmarks/`. +4. Run controlled experiments and record raw measurements plus metadata in + `results/`. +5. Analyze results in `reports/`. + +A scenario describes *what* happens to a frame; algorithms describe *how* an +individual operation is performed. Keep those concerns separate so algorithms +can be compared across independent research cases. diff --git a/benchmarks/memory/BUILD.bazel b/benchmarks/memory/BUILD.bazel new file mode 100644 index 0000000..48a086b --- /dev/null +++ b/benchmarks/memory/BUILD.bazel @@ -0,0 +1,10 @@ +load("@rules_cc//cc:cc_binary.bzl", "cc_binary") + +cc_binary( + name = "memcpy_benchmark", + srcs = ["memcpy_benchmark.cc"], + deps = [ + "//lib/memory:memcpy", + "@google_benchmark//:benchmark_main", + ], +) diff --git a/benchmarks/memory/memcpy_benchmark.cc b/benchmarks/memory/memcpy_benchmark.cc new file mode 100644 index 0000000..49d976e --- /dev/null +++ b/benchmarks/memory/memcpy_benchmark.cc @@ -0,0 +1,41 @@ +#include "lib/memory/memcpy.h" + +#include +#include +#include + +#include + +namespace { + +std::vector MakeInput(std::size_t size) { + std::vector input(size); + std::uint32_t state = 0xC0FFEEu; + for (std::byte& value : input) { + state = state * 1664525u + 1013904223u; + value = static_cast(state >> 24); + } + return input; +} + +void BM_Memcpy(benchmark::State& state) { + const auto size = static_cast(state.range(0)); + const std::vector source = MakeInput(size); + std::vector destination(size); + + for (auto _ : state) { + memcopy_bench::memory::CopyWithMemcpy( + destination.data(), source.data(), size); + benchmark::DoNotOptimize(destination.data()); + benchmark::ClobberMemory(); + } + + state.SetBytesProcessed(static_cast(state.iterations()) * + static_cast(size)); +} + +BENCHMARK(BM_Memcpy)->RangeMultiplier(4)->Range(64, 64 << 20); + +} // namespace + +BENCHMARK_MAIN(); diff --git a/lib/memory/BUILD.bazel b/lib/memory/BUILD.bazel new file mode 100644 index 0000000..726c946 --- /dev/null +++ b/lib/memory/BUILD.bazel @@ -0,0 +1,18 @@ +load("@rules_cc//cc:cc_library.bzl", "cc_library") +load("@rules_cc//cc:cc_test.bzl", "cc_test") + +cc_library( + name = "memcpy", + srcs = ["memcpy.cc"], + hdrs = ["memcpy.h"], + visibility = ["//visibility:public"], +) + +cc_test( + name = "memcpy_test", + srcs = ["memcpy_test.cc"], + deps = [ + ":memcpy", + "@googletest//:gtest_main", + ], +) diff --git a/lib/memory/memcpy.cc b/lib/memory/memcpy.cc new file mode 100644 index 0000000..24fe8e7 --- /dev/null +++ b/lib/memory/memcpy.cc @@ -0,0 +1,11 @@ +#include "lib/memory/memcpy.h" + +#include + +namespace memcopy_bench::memory { + +void CopyWithMemcpy(void* destination, const void* source, std::size_t size) { + std::memcpy(destination, source, size); +} + +} // namespace memcopy_bench::memory diff --git a/lib/memory/memcpy.h b/lib/memory/memcpy.h new file mode 100644 index 0000000..5ecd2d0 --- /dev/null +++ b/lib/memory/memcpy.h @@ -0,0 +1,10 @@ +#pragma once + +#include + +namespace memcopy_bench::memory { + +// Copies exactly size bytes from source to destination using std::memcpy. +void CopyWithMemcpy(void* destination, const void* source, std::size_t size); + +} // namespace memcopy_bench::memory diff --git a/lib/memory/memcpy_test.cc b/lib/memory/memcpy_test.cc new file mode 100644 index 0000000..1dc6b78 --- /dev/null +++ b/lib/memory/memcpy_test.cc @@ -0,0 +1,36 @@ +#include "lib/memory/memcpy.h" + +#include +#include + +#include "gtest/gtest.h" + +namespace memcopy_bench::memory { +namespace { + +TEST(CopyWithMemcpyTest, CopiesRequestedBytes) { + constexpr std::array source = { + std::byte{0x01}, std::byte{0x02}, std::byte{0x03}, std::byte{0x04}}; + std::array destination{}; + + CopyWithMemcpy(destination.data(), source.data(), source.size()); + + EXPECT_EQ(destination, source); +} + +TEST(CopyWithMemcpyTest, DoesNotWritePastRequestedSize) { + constexpr std::array source = { + std::byte{0x01}, std::byte{0x02}, std::byte{0x03}, std::byte{0x04}}; + std::array destination = { + std::byte{0xFF}, std::byte{0xFF}, std::byte{0xFF}, std::byte{0xFF}}; + + CopyWithMemcpy(destination.data(), source.data(), 2); + + EXPECT_EQ(destination[0], source[0]); + EXPECT_EQ(destination[1], source[1]); + EXPECT_EQ(destination[2], std::byte{0xFF}); + EXPECT_EQ(destination[3], std::byte{0xFF}); +} + +} // namespace +} // namespace memcopy_bench::memory diff --git a/reports/.gitkeep b/reports/.gitkeep new file mode 100644 index 0000000..120d7c7 --- /dev/null +++ b/reports/.gitkeep @@ -0,0 +1 @@ +# Store research findings and comparative analysis here. diff --git a/results/.gitkeep b/results/.gitkeep new file mode 100644 index 0000000..b56801f --- /dev/null +++ b/results/.gitkeep @@ -0,0 +1 @@ +# Store raw benchmark output and matching experiment metadata here. diff --git a/src/.bazelrc b/src/.bazelrc deleted file mode 100644 index f67d8fc..0000000 --- a/src/.bazelrc +++ /dev/null @@ -1 +0,0 @@ -build -c opt diff --git a/src/MODULE.bazel b/src/MODULE.bazel deleted file mode 100644 index 009a936..0000000 --- a/src/MODULE.bazel +++ /dev/null @@ -1,8 +0,0 @@ -# https://registry.bazel.build/modules/rules_cc -bazel_dep(name = "rules_cc", version = "0.2.26") - -# https://registry.bazel.build/modules/googletest -bazel_dep(name = "googletest", version = "1.18.0.bcr.1", dev_dependency = True) - -# https://registry.bazel.build/modules/google_benchmark -bazel_dep(name = "google_benchmark", version = "1.9.5") diff --git a/src/lib/BUILD b/src/lib/BUILD deleted file mode 100644 index 32723ad..0000000 --- a/src/lib/BUILD +++ /dev/null @@ -1,14 +0,0 @@ -load("@rules_cc//cc:cc_library.bzl", "cc_library") - -cc_library( - name = "memcpy_bench", - srcs = ["memcpy_bench.cc"], - hdrs = ["memcpy_bench.h"], - deps = [ - "@google_benchmark//:benchmark", - ], - visibility = [ - "//main:__pkg__", - "//test:__pkg__", - ], -) diff --git a/src/lib/memcpy_bench.cc b/src/lib/memcpy_bench.cc deleted file mode 100644 index 46585bd..0000000 --- a/src/lib/memcpy_bench.cc +++ /dev/null @@ -1,54 +0,0 @@ -#include "lib/memcpy_bench.h" - -#include -#include - -#include - -namespace memcpy_bench { - -namespace { - -void BM_Memcpy(benchmark::State& state) { - const auto size = static_cast(state.range(0)); - - const std::vector src = RandomBytes(size); - std::vector dst(size); - - for (auto _ : state) { - std::memcpy(dst.data(), src.data(), size); - benchmark::DoNotOptimize(dst.data()); - benchmark::ClobberMemory(); - } - - state.SetBytesProcessed(static_cast(state.iterations()) * - static_cast(size)); -} - -} - -std::vector RandomBytes(std::size_t size, std::uint32_t seed) { - std::mt19937 gen(seed); - std::uniform_int_distribution dist(0, 255); - std::vector bytes(size); - for (auto& byte : bytes) { - byte = static_cast(dist(gen)); - } - return bytes; -} - -int Run(int argc, char** argv) { - benchmark::RegisterBenchmark("BM_Memcpy", BM_Memcpy) - ->RangeMultiplier(4) - ->Range(64, 64 << 20); - - benchmark::Initialize(&argc, argv); - if (benchmark::ReportUnrecognizedArguments(argc, argv)) { - return 1; - } - benchmark::RunSpecifiedBenchmarks(); - benchmark::Shutdown(); - return 0; -} - -} diff --git a/src/lib/memcpy_bench.h b/src/lib/memcpy_bench.h deleted file mode 100644 index bd827d5..0000000 --- a/src/lib/memcpy_bench.h +++ /dev/null @@ -1,13 +0,0 @@ -#pragma once - -#include -#include -#include - -namespace memcpy_bench { - -std::vector RandomBytes(std::size_t size, std::uint32_t seed = 42); - -int Run(int argc, char** argv); - -} diff --git a/src/main/BUILD b/src/main/BUILD deleted file mode 100644 index 89b4608..0000000 --- a/src/main/BUILD +++ /dev/null @@ -1,9 +0,0 @@ -load("@rules_cc//cc:cc_binary.bzl", "cc_binary") - -cc_binary( - name = "main", - srcs = glob(["*.cc"]), - deps = [ - "//lib:memcpy_bench", - ], -) diff --git a/src/main/main.cc b/src/main/main.cc deleted file mode 100644 index 0bcf91d..0000000 --- a/src/main/main.cc +++ /dev/null @@ -1,6 +0,0 @@ -#include "lib/memcpy_bench.h" - -int main(int argc, char **argv) -{ - return memcpy_bench::Run(argc, argv); -} diff --git a/src/test/BUILD b/src/test/BUILD deleted file mode 100644 index 750aa32..0000000 --- a/src/test/BUILD +++ /dev/null @@ -1,11 +0,0 @@ -load("@rules_cc//cc:cc_test.bzl", "cc_test") - - -cc_test( - name = "memcpy_bench_test", - srcs = glob(["*.cc"]), - deps = [ - "@googletest//:gtest_main", - "//lib:memcpy_bench", - ], -) diff --git a/src/test/memcpy_bench_test.cc b/src/test/memcpy_bench_test.cc deleted file mode 100644 index c7ab159..0000000 --- a/src/test/memcpy_bench_test.cc +++ /dev/null @@ -1,15 +0,0 @@ -#include "gtest/gtest.h" -#include "lib/memcpy_bench.h" - -TEST(RandomBytesTest, ReturnsRequestedSize) { - EXPECT_EQ(memcpy_bench::RandomBytes(0).size(), 0u); - EXPECT_EQ(memcpy_bench::RandomBytes(1024).size(), 1024u); -} - -TEST(RandomBytesTest, SameSeedGivesSameBytes) { - EXPECT_EQ(memcpy_bench::RandomBytes(256, 7), memcpy_bench::RandomBytes(256, 7)); -} - -TEST(RandomBytesTest, DifferentSeedsGiveDifferentBytes) { - EXPECT_NE(memcpy_bench::RandomBytes(256, 1), memcpy_bench::RandomBytes(256, 2)); -} diff --git a/tests/.gitkeep b/tests/.gitkeep new file mode 100644 index 0000000..8ea750f --- /dev/null +++ b/tests/.gitkeep @@ -0,0 +1 @@ +# Integration and cross-component tests belong in this directory. diff --git a/tools/.gitkeep b/tools/.gitkeep new file mode 100644 index 0000000..9b0b175 --- /dev/null +++ b/tools/.gitkeep @@ -0,0 +1 @@ +# Experiment automation and analysis utilities belong in this directory. From a7070e76b092c7d61ccee738276f9704149324d6 Mon Sep 17 00:00:00 2001 From: e1turin Date: Wed, 14 Oct 2026 00:54:16 +0300 Subject: [PATCH 2/5] rename files and refine structure of tests --- README.md | 6 ++++-- benchmarks/memory/BUILD.bazel | 2 +- .../{memcpy_benchmark.cc => memcpy_benchmark.cpp} | 0 lib/memory/BUILD.bazel | 4 ++-- lib/memory/{memcpy.cc => memcpy.cpp} | 0 lib/memory/{memcpy_test.cc => memcpy_test.cpp} | 0 lib/test/BUILD.bazel | 4 ++++ lib/test/README.md | 10 ++++++++++ tests/.gitkeep | 1 - 9 files changed, 21 insertions(+), 6 deletions(-) rename benchmarks/memory/{memcpy_benchmark.cc => memcpy_benchmark.cpp} (100%) rename lib/memory/{memcpy.cc => memcpy.cpp} (100%) rename lib/memory/{memcpy_test.cc => memcpy_test.cpp} (100%) create mode 100644 lib/test/BUILD.bazel create mode 100644 lib/test/README.md delete mode 100644 tests/.gitkeep diff --git a/README.md b/README.md index 8811a56..7cce5cf 100644 --- a/README.md +++ b/README.md @@ -9,10 +9,12 @@ architecture-specific assumptions. - `lib/` — reusable frame, memory, transport, and scenario implementations. Libraries do not depend on benchmarking or reporting infrastructure. + `lib/test/` is reserved for shared test-only support; component unit tests + remain colocated with their implementations. - `benchmarks/` — Google Benchmark executables. `memory/` holds operation microbenchmarks; `scenarios/` will hold complete workflow benchmarks. -- `tests/` — integration and cross-component tests. Unit tests are colocated - with the library code they verify. +- `tests/integration/` — reserved for cross-component integration tests when + they are needed. Unit tests are colocated with the library code they verify. - `tools/` — experiment automation and analysis utilities. - `results/` — raw benchmark output and experiment metadata (kept separate from analysis). diff --git a/benchmarks/memory/BUILD.bazel b/benchmarks/memory/BUILD.bazel index 48a086b..8d0c940 100644 --- a/benchmarks/memory/BUILD.bazel +++ b/benchmarks/memory/BUILD.bazel @@ -2,7 +2,7 @@ load("@rules_cc//cc:cc_binary.bzl", "cc_binary") cc_binary( name = "memcpy_benchmark", - srcs = ["memcpy_benchmark.cc"], + srcs = ["memcpy_benchmark.cpp"], deps = [ "//lib/memory:memcpy", "@google_benchmark//:benchmark_main", diff --git a/benchmarks/memory/memcpy_benchmark.cc b/benchmarks/memory/memcpy_benchmark.cpp similarity index 100% rename from benchmarks/memory/memcpy_benchmark.cc rename to benchmarks/memory/memcpy_benchmark.cpp diff --git a/lib/memory/BUILD.bazel b/lib/memory/BUILD.bazel index 726c946..df95dea 100644 --- a/lib/memory/BUILD.bazel +++ b/lib/memory/BUILD.bazel @@ -3,14 +3,14 @@ load("@rules_cc//cc:cc_test.bzl", "cc_test") cc_library( name = "memcpy", - srcs = ["memcpy.cc"], + srcs = ["memcpy.cpp"], hdrs = ["memcpy.h"], visibility = ["//visibility:public"], ) cc_test( name = "memcpy_test", - srcs = ["memcpy_test.cc"], + srcs = ["memcpy_test.cpp"], deps = [ ":memcpy", "@googletest//:gtest_main", diff --git a/lib/memory/memcpy.cc b/lib/memory/memcpy.cpp similarity index 100% rename from lib/memory/memcpy.cc rename to lib/memory/memcpy.cpp diff --git a/lib/memory/memcpy_test.cc b/lib/memory/memcpy_test.cpp similarity index 100% rename from lib/memory/memcpy_test.cc rename to lib/memory/memcpy_test.cpp diff --git a/lib/test/BUILD.bazel b/lib/test/BUILD.bazel new file mode 100644 index 0000000..3592bc7 --- /dev/null +++ b/lib/test/BUILD.bazel @@ -0,0 +1,4 @@ +# Shared test-only targets (fixtures, frame builders, and correctness helpers) +# for lib/ packages belong here. Component unit tests remain colocated with +# their implementations. +package(default_testonly = True) diff --git a/lib/test/README.md b/lib/test/README.md new file mode 100644 index 0000000..e0e33de --- /dev/null +++ b/lib/test/README.md @@ -0,0 +1,10 @@ +# Shared library test support + +This Bazel package is reserved for reusable, test-only support used by unit +tests across `lib/` components, such as deterministic frame builders, shared +correctness assertions, and test fixtures. + +Keep a component's unit test next to its implementation (for example, +`lib/memory/memcpy_test.cpp`). Add a target here only when the same test-only +code is genuinely needed by more than one component. Do not add Google +Benchmark dependencies or production implementation code to this package. diff --git a/tests/.gitkeep b/tests/.gitkeep deleted file mode 100644 index 8ea750f..0000000 --- a/tests/.gitkeep +++ /dev/null @@ -1 +0,0 @@ -# Integration and cross-component tests belong in this directory. From e1611f98e73bd8907ca915791b1c64d79bfeccb7 Mon Sep 17 00:00:00 2001 From: e1turin Date: Wed, 14 Oct 2026 01:26:19 +0300 Subject: [PATCH 3/5] add linters and sanitizers --- .bazelrc | 12 +++++++++ .clang-tidy | 14 ++++++++++ .clangd | 2 ++ .github/workflows/bazel.yaml | 50 ++++++++++++++++++++++++++++++++++++ .gitignore | 1 + MODULE.bazel | 11 ++++++++ README.md | 30 +++++++++++++++++++++- tools/run-clang-tidy.sh | 12 +++++++++ 8 files changed, 131 insertions(+), 1 deletion(-) create mode 100644 .clang-tidy create mode 100644 .clangd create mode 100644 tools/run-clang-tidy.sh diff --git a/.bazelrc b/.bazelrc index 1877fa4..0a8f30c 100644 --- a/.bazelrc +++ b/.bazelrc @@ -1,2 +1,14 @@ # Performance measurements should use optimized builds by default. build -c opt + +# Instrument test binaries and their C/C++ dependencies for memory errors. +build:asan --copt=-fsanitize=address +build:asan --copt=-fno-omit-frame-pointer +build:asan --copt=-g +build:asan --linkopt=-fsanitize=address + +# Instrument test binaries and their C/C++ dependencies for undefined behavior. +build:ubsan --copt=-fsanitize=undefined +build:ubsan --copt=-fno-omit-frame-pointer +build:ubsan --copt=-g +build:ubsan --linkopt=-fsanitize=undefined diff --git a/.clang-tidy b/.clang-tidy new file mode 100644 index 0000000..489a1e2 --- /dev/null +++ b/.clang-tidy @@ -0,0 +1,14 @@ +--- +# Apply the clang-tidy checks that enforce the Google C++ Style Guide. +Checks: 'google-*' + +# Format any clang-tidy fix-its with the corresponding clang-format style. +FormatStyle: Google + +# Analyze project headers without reporting diagnostics from external modules. +HeaderFilterRegex: '^(lib|benchmarks)/.*' + +# Keep style diagnostics as warnings; projects can opt into -warnings-as-errors +# in CI when the codebase is clean. +WarningsAsErrors: '' +... diff --git a/.clangd b/.clangd new file mode 100644 index 0000000..29788b8 --- /dev/null +++ b/.clangd @@ -0,0 +1,2 @@ +CompileFlags: + CompilationDatabase: . diff --git a/.github/workflows/bazel.yaml b/.github/workflows/bazel.yaml index 86bedd0..23a12bf 100644 --- a/.github/workflows/bazel.yaml +++ b/.github/workflows/bazel.yaml @@ -9,6 +9,29 @@ on: - main jobs: + lint: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - uses: bazel-contrib/setup-bazel@c5acdfb288317d0b5c0bbd7a396a3dc868bb0f86 # 0.19.0 + with: + bazelisk-cache: true + disk-cache: ${{ github.workflow }} + repository-cache: true + # Pull requests only restore caches, pushes to main refresh them. + cache-save: ${{ github.event_name == 'push' }} + bazelrc: | + common --announce_rc + common --color=yes + + - name: Install clang-tidy + run: sudo apt-get update && sudo apt-get install --yes clang-tidy + + - name: Lint C++ sources + run: sh tools/run-clang-tidy.sh + test: runs-on: ubuntu-latest @@ -34,3 +57,30 @@ jobs: - name: Test run: bazel test //... --test_output=errors + + sanitizers: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - uses: bazel-contrib/setup-bazel@c5acdfb288317d0b5c0bbd7a396a3dc868bb0f86 # 0.19.0 + with: + bazelisk-cache: true + disk-cache: ${{ github.workflow }} + repository-cache: true + # Pull requests only restore caches, pushes to main refresh them. + cache-save: ${{ github.event_name == 'push' }} + bazelrc: | + common --announce_rc + common --color=yes + + - name: Test with AddressSanitizer + run: >- + ASAN_OPTIONS=detect_leaks=1:halt_on_error=1 + bazel test --config=asan //... --test_output=errors + + - name: Test with UndefinedBehaviorSanitizer + run: >- + UBSAN_OPTIONS=halt_on_error=1:print_stacktrace=1 + bazel test --config=ubsan //... --test_output=errors diff --git a/.gitignore b/.gitignore index 7db0bd7..523150b 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ *~ bazel-* *MODULE.bazel.lock +compile_commands.json /.ijwb/ /.aswb/ /.clwb/ diff --git a/MODULE.bazel b/MODULE.bazel index f24f9d2..f826d4d 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -6,3 +6,14 @@ bazel_dep(name = "googletest", version = "1.18.0.bcr.1", dev_dependency = True) # Benchmark executables depend on this package, never libraries in lib/. bazel_dep(name = "google_benchmark", version = "1.9.5") + +# Generates compile_commands.json for clangd and clang-tidy. This is a +# development-only tool and is not part of production or benchmark targets. +# The pinned revision is the upstream Bazel 9 compatibility patch (PR #281), +# pending its merge into the canonical repository. +bazel_dep(name = "hedron_compile_commands", dev_dependency = True) +git_override( + module_name = "hedron_compile_commands", + remote = "https://github.com/ultimateanu/bazel-compile-commands-extractor.git", + commit = "2dc498f24587eccc2752f8a892da004d9baa8318", +) diff --git a/README.md b/README.md index 7cce5cf..2394e8c 100644 --- a/README.md +++ b/README.md @@ -26,13 +26,41 @@ Install [Bazelisk](https://github.com/bazelbuild/bazelisk). It selects the Bazel version pinned in `.bazelversion`. Commands are run from the repository root. Optimized mode is enabled by default in `.bazelrc`. -## Build and test +## C++ editor tooling + +Generate Bazel-aware compile commands for `clangd` and `clang-tidy` after +changing C++ sources, targets, dependencies, or compile settings: + +```bash +bazel run @hedron_compile_commands//:refresh_all +``` + +The generated root-level `compile_commands.json` is intentionally ignored by +Git. Restart the editor language server after refreshing it if it does not +reload the database automatically. + +## Build, lint, and test ```bash bazel build //... bazel test //... ``` +Run the same C++ lint check used by CI (requires `clang-tidy` on `PATH`): + +```bash +sh tools/run-clang-tidy.sh +``` + +Run tests with the supported sanitizers: + +```bash +ASAN_OPTIONS=detect_leaks=1:halt_on_error=1 \ + bazel test --config=asan //... --test_output=errors +UBSAN_OPTIONS=halt_on_error=1:print_stacktrace=1 \ + bazel test --config=ubsan //... --test_output=errors +``` + For example, the initial reusable algorithm and its colocated test are: ```bash diff --git a/tools/run-clang-tidy.sh b/tools/run-clang-tidy.sh new file mode 100644 index 0000000..1ff8fe7 --- /dev/null +++ b/tools/run-clang-tidy.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env sh +set -eu + +if ! command -v clang-tidy >/dev/null 2>&1; then + echo "clang-tidy is required but was not found on PATH." >&2 + exit 1 +fi + +bazel run @hedron_compile_commands//:refresh_all + +find lib benchmarks -type f \( -name '*.cc' -o -name '*.cpp' -o -name '*.cxx' \) \ + -exec clang-tidy --warnings-as-errors='*' -p . {} + From a8d715776c21953c1e6c555045b70a09d65645fb Mon Sep 17 00:00:00 2001 From: e1turin Date: Wed, 14 Oct 2026 01:45:13 +0300 Subject: [PATCH 4/5] enhnance running linters and sanitizers in CI --- .bazelrc | 12 +++++- .clang-tidy | 2 +- .github/workflows/bazel.yaml | 73 +++++++++++++++++++----------------- README.md | 6 +-- 4 files changed, 51 insertions(+), 42 deletions(-) diff --git a/.bazelrc b/.bazelrc index 0a8f30c..c6e89de 100644 --- a/.bazelrc +++ b/.bazelrc @@ -1,14 +1,22 @@ # Performance measurements should use optimized builds by default. build -c opt +# Keep CI logs readable and include failed test output. +common:ci --announce_rc +common:ci --color=yes +common:ci --curses=no +test:ci --test_output=errors + # Instrument test binaries and their C/C++ dependencies for memory errors. +build:asan -c dbg build:asan --copt=-fsanitize=address build:asan --copt=-fno-omit-frame-pointer -build:asan --copt=-g build:asan --linkopt=-fsanitize=address +test:asan --test_env=ASAN_OPTIONS=detect_leaks=1:halt_on_error=1 # Instrument test binaries and their C/C++ dependencies for undefined behavior. +build:ubsan -c dbg build:ubsan --copt=-fsanitize=undefined build:ubsan --copt=-fno-omit-frame-pointer -build:ubsan --copt=-g build:ubsan --linkopt=-fsanitize=undefined +test:ubsan --test_env=UBSAN_OPTIONS=halt_on_error=1:print_stacktrace=1 diff --git a/.clang-tidy b/.clang-tidy index 489a1e2..56d4d29 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -6,7 +6,7 @@ Checks: 'google-*' FormatStyle: Google # Analyze project headers without reporting diagnostics from external modules. -HeaderFilterRegex: '^(lib|benchmarks)/.*' +HeaderFilterRegex: '(^|.*/)(lib|benchmarks)/.*' # Keep style diagnostics as warnings; projects can opt into -warnings-as-errors # in CI when the codebase is clean. diff --git a/.github/workflows/bazel.yaml b/.github/workflows/bazel.yaml index 23a12bf..b76563d 100644 --- a/.github/workflows/bazel.yaml +++ b/.github/workflows/bazel.yaml @@ -1,4 +1,4 @@ -name: Bazel +name: CI on: pull_request: @@ -7,10 +7,20 @@ on: push: branches: - main + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: ci-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true jobs: lint: - runs-on: ubuntu-latest + name: Lint + runs-on: ubuntu-24.04 + timeout-minutes: 15 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 @@ -18,13 +28,9 @@ jobs: - uses: bazel-contrib/setup-bazel@c5acdfb288317d0b5c0bbd7a396a3dc868bb0f86 # 0.19.0 with: bazelisk-cache: true - disk-cache: ${{ github.workflow }} + disk-cache: ${{ github.workflow }}-${{ github.job }} repository-cache: true - # Pull requests only restore caches, pushes to main refresh them. cache-save: ${{ github.event_name == 'push' }} - bazelrc: | - common --announce_rc - common --color=yes - name: Install clang-tidy run: sudo apt-get update && sudo apt-get install --yes clang-tidy @@ -32,8 +38,10 @@ jobs: - name: Lint C++ sources run: sh tools/run-clang-tidy.sh - test: - runs-on: ubuntu-latest + build-and-test: + name: Build and test + runs-on: ubuntu-24.04 + timeout-minutes: 20 steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 @@ -41,25 +49,28 @@ jobs: - uses: bazel-contrib/setup-bazel@c5acdfb288317d0b5c0bbd7a396a3dc868bb0f86 # 0.19.0 with: bazelisk-cache: true - disk-cache: ${{ github.workflow }} + disk-cache: ${{ github.workflow }}-${{ github.job }} repository-cache: true - # Pull requests only restore caches, pushes to main refresh them. cache-save: ${{ github.event_name == 'push' }} - bazelrc: | - common --announce_rc - common --color=yes - - - name: Bazel version - run: bazel version - name: Build - run: bazel build //... + run: bazel build --config=ci //... - name: Test - run: bazel test //... --test_output=errors + run: bazel test --config=ci //... sanitizers: - runs-on: ubuntu-latest + name: ${{ matrix.name }} + runs-on: ubuntu-24.04 + timeout-minutes: 20 + strategy: + fail-fast: false + matrix: + include: + - name: AddressSanitizer + config: asan + - name: UndefinedBehaviorSanitizer + config: ubsan steps: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 @@ -67,20 +78,12 @@ jobs: - uses: bazel-contrib/setup-bazel@c5acdfb288317d0b5c0bbd7a396a3dc868bb0f86 # 0.19.0 with: bazelisk-cache: true - disk-cache: ${{ github.workflow }} + disk-cache: ${{ github.workflow }}-${{ github.job }}-${{ matrix.config }} repository-cache: true - # Pull requests only restore caches, pushes to main refresh them. cache-save: ${{ github.event_name == 'push' }} - bazelrc: | - common --announce_rc - common --color=yes - - - name: Test with AddressSanitizer - run: >- - ASAN_OPTIONS=detect_leaks=1:halt_on_error=1 - bazel test --config=asan //... --test_output=errors - - - name: Test with UndefinedBehaviorSanitizer - run: >- - UBSAN_OPTIONS=halt_on_error=1:print_stacktrace=1 - bazel test --config=ubsan //... --test_output=errors + + - name: Build + run: bazel build --config=ci --config=${{ matrix.config }} //... + + - name: Test + run: bazel test --config=ci --config=${{ matrix.config }} //... diff --git a/README.md b/README.md index 2394e8c..0ec06d4 100644 --- a/README.md +++ b/README.md @@ -55,10 +55,8 @@ sh tools/run-clang-tidy.sh Run tests with the supported sanitizers: ```bash -ASAN_OPTIONS=detect_leaks=1:halt_on_error=1 \ - bazel test --config=asan //... --test_output=errors -UBSAN_OPTIONS=halt_on_error=1:print_stacktrace=1 \ - bazel test --config=ubsan //... --test_output=errors +bazel test --config=asan //... --test_output=errors +bazel test --config=ubsan //... --test_output=errors ``` For example, the initial reusable algorithm and its colocated test are: From a8c4bb62aa5a90c6db21b4e63111f89512e5adac Mon Sep 17 00:00:00 2001 From: e1turin Date: Wed, 14 Oct 2026 02:35:00 +0300 Subject: [PATCH 5/5] fix clang tidy check issue for benchmarks --- .clang-tidy | 5 ++--- benchmarks/memory/.clang-tidy | 3 +++ tools/run-clang-tidy.sh | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) create mode 100644 benchmarks/memory/.clang-tidy diff --git a/.clang-tidy b/.clang-tidy index 56d4d29..26973f0 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -8,7 +8,6 @@ FormatStyle: Google # Analyze project headers without reporting diagnostics from external modules. HeaderFilterRegex: '(^|.*/)(lib|benchmarks)/.*' -# Keep style diagnostics as warnings; projects can opt into -warnings-as-errors -# in CI when the codebase is clean. -WarningsAsErrors: '' +# Treat all configured diagnostics as errors by default. +WarningsAsErrors: '*' ... diff --git a/benchmarks/memory/.clang-tidy b/benchmarks/memory/.clang-tidy new file mode 100644 index 0000000..60e0249 --- /dev/null +++ b/benchmarks/memory/.clang-tidy @@ -0,0 +1,3 @@ +# Google Benchmark range-for loops require an intentionally unused binding. +InheritParentConfig: true +Checks: '-clang-analyzer-deadcode.DeadStores' diff --git a/tools/run-clang-tidy.sh b/tools/run-clang-tidy.sh index 1ff8fe7..a05d506 100644 --- a/tools/run-clang-tidy.sh +++ b/tools/run-clang-tidy.sh @@ -9,4 +9,4 @@ fi bazel run @hedron_compile_commands//:refresh_all find lib benchmarks -type f \( -name '*.cc' -o -name '*.cpp' -o -name '*.cxx' \) \ - -exec clang-tidy --warnings-as-errors='*' -p . {} + + -exec clang-tidy -p . {} +