Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -91,3 +91,27 @@ jobs:

- name: Test
run: ctest --test-dir build --output-on-failure -C Release

build-and-test-scalar:
name: Linux x64 GCC (scalar)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Configure
env:
CC: gcc
CXX: g++
run: |
cmake -S . -B build \
-DCMAKE_BUILD_TYPE=Release \
-DBITCAL_BUILD_TESTS=ON \
-DBITCAL_BUILD_EXAMPLES=ON \
-DBITCAL_NATIVE_ARCH=OFF \
-DCMAKE_CXX_FLAGS="-mno-avx2"

- name: Build
run: cmake --build build --config Release -j"$(nproc)"

- name: Test
run: ctest --test-dir build --output-on-failure -C Release
4 changes: 2 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,10 @@ BitCal 是一个 C++23、header-only、实验性质的 SIMD 位运算练习库

### 4.3 工程化

- BitCal 是 header-only 实验库;CI 只保留最小可信路径(format-check + GCC/Clang 两个 build/test job)
- BitCal 是 header-only 实验库;CI 只保留最小可信路径(format-check + GCC/Clang/scalar 三个 build/test job)
- 不引入分发库基础设施(install/export/LTO/hardening/cmake config 包)
- 版本号单一事实源:`include/bitcal/config.hpp`(`CMakeLists.txt` 的 `project()` 版本独立硬编码,仅为 CMake 元数据)
- scalar 路径与 sanitizer 验证在本地手动执行,不进 CI 矩阵
- sanitizer 验证在本地手动执行,不进 CI 矩阵(scalar 路径已由 CI scalar job 覆盖)
- `.clang-format` 的 `Standard` 设为 `Latest`,确保与项目语言基线(C++23)一致

### 4.4 注释与风格
Expand Down
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@
- `benchmarks/CMakeLists.txt` 删除冗余 `cmake_minimum_required`(子目录无需重复声明)
- 删除 benchmark 文件的 Doxygen `@file`/`@brief` 头(项目无 Doxygen 工具链)
- 删除 `examples/basic_usage.cpp` 与 `benchmarks/benchmark_compare.cpp` 中静态 span 改造后遗留的未使用 `#include <span>`(`from_words` 改为接收 `std::array` 隐式转换,调用方不再显式使用 span)
- CI 新增 scalar build/test job(`-mno-avx2` + `BITCAL_NATIVE_ARCH=OFF`),覆盖 `BITCAL_HAS_AVX2 == 0` 分支;此前 CI 两个 job 均走 AVX2 路径,scalar 分支从未被编译和测试
- `README.md` 与 `AGENTS.md` §4.3 scalar 路径描述同步("本地手动执行,不进 CI" -> "已进 CI";sanitizer 验证仍为本地手动)

### ♻️ 重构

Expand All @@ -43,6 +45,7 @@
### 🧪 测试

- 补 `equals` 的 AVX2 路径回归覆盖:256/512 位确定性用例(块内部分相等、跨 vec 块不等)+ 256 位随机对照参考模型。此前 `equals` 的"不等 -> 提前返回"分支仅在 ≤192 位(走 scalar 尾部)或自比(恒真)下被覆盖,AVX2 路径(≥4 字)无断言保护
- 补 `shift_left` / `shift_right` 的 256 位随机对照测试:随机 count ∈ [0, Bits] 对照逐 bit 参考模型,覆盖此前手工用例未碰的中间 shift 值

## [4.1.0] - 2026-07-20

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ cmake --build build --config Release -j"$(nproc)"
ctest --test-dir build --output-on-failure -C Release
```

Scalar 路径(验证 `BITCAL_HAS_AVX2 == 0` 分支,本地手动执行,不进 CI):
Scalar 路径(验证 `BITCAL_HAS_AVX2 == 0` 分支,已进 CI;以下本地命令供调试复用):

```bash
cmake -S . -B build-scalar -DCMAKE_BUILD_TYPE=Release -DBITCAL_BUILD_TESTS=ON -DBITCAL_NATIVE_ARCH=OFF -DCMAKE_CXX_FLAGS="-mno-avx2"
Expand Down
27 changes: 27 additions & 0 deletions tests/support/random_cases.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -67,4 +67,31 @@ std::vector<random_unary_case<Bits>> make_random_unary_cases(const std::uint64_t
return cases;
}

template <std::size_t Bits>
struct random_shift_case {
bitcal::bit_block<Bits> block;
std::array<std::uint64_t, Bits / 64> words;
std::size_t count;
};

template <std::size_t Bits>
std::vector<random_shift_case<Bits>> make_random_shift_cases(const std::uint64_t seed, const std::size_t count) {
std::mt19937_64 rng(seed);
std::uniform_int_distribution<std::size_t> count_dist(0, Bits);
std::vector<random_shift_case<Bits>> cases;
cases.reserve(count);

for (std::size_t i = 0; i < count; ++i) {
random_shift_case<Bits> tc{};
for (std::size_t word = 0; word < Bits / 64; ++word) {
tc.words[word] = rng();
}
tc.block = bitcal::bit_block<Bits>::from_words(tc.words);
tc.count = count_dist(rng);
cases.push_back(tc);
}

return cases;
}

} // namespace bitcal::test
27 changes: 27 additions & 0 deletions tests/support/reference_model.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -87,4 +87,31 @@ bool reference_equals(const word_array<Bits>& lhs, const word_array<Bits>& rhs)
return true;
}

// Bit-by-bit reference shifts: obviously correct, O(Bits) per call.
template <std::size_t Bits>
word_array<Bits> reference_shift_left(const word_array<Bits>& words, std::size_t count) {
word_array<Bits> out{};
if (count >= Bits)
return out;
for (std::size_t i = 0; i + count < Bits; ++i) {
if ((words[i / 64] >> (i % 64)) & 1ULL) {
out[(i + count) / 64] |= 1ULL << ((i + count) % 64);
}
}
return out;
}

template <std::size_t Bits>
word_array<Bits> reference_shift_right(const word_array<Bits>& words, std::size_t count) {
word_array<Bits> out{};
if (count >= Bits)
return out;
for (std::size_t i = count; i < Bits; ++i) {
if ((words[i / 64] >> (i % 64)) & 1ULL) {
out[(i - count) / 64] |= 1ULL << ((i - count) % 64);
}
}
return out;
}

} // namespace bitcal::test
30 changes: 30 additions & 0 deletions tests/test_bitcal.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -778,6 +778,32 @@ bool test_ctad_bit_block_overload_runtime() {
return true;
}

bool test_random_shift_left_matches_reference_model_256() {
for (const auto& tc : bitcal::test::make_random_shift_cases<256>(0x51F7ULL, 64)) {
const auto actual = bitcal::shift_left<256>(tc.block.view(), tc.count);
const auto expected = bitcal::test::reference_shift_left<256>(tc.words, tc.count);

for (std::size_t i = 0; i < bitcal::bit_block<256>::word_count; ++i) {
BITCAL_ASSERT_EQ(actual.word(i), expected[i]);
}
}

return true;
}

bool test_random_shift_right_matches_reference_model_256() {
for (const auto& tc : bitcal::test::make_random_shift_cases<256>(0x5B17ULL, 64)) {
const auto actual = bitcal::shift_right<256>(tc.block.view(), tc.count);
const auto expected = bitcal::test::reference_shift_right<256>(tc.words, tc.count);

for (std::size_t i = 0; i < bitcal::bit_block<256>::word_count; ++i) {
BITCAL_ASSERT_EQ(actual.word(i), expected[i]);
}
}

return true;
}

int main() {
std::cout << "=== BitCal test suite ===" << std::endl;

Expand Down Expand Up @@ -856,6 +882,10 @@ int main() {
bitcal::test::run_case(g_counters, "test_ctad_bit_block_overload_runtime", test_ctad_bit_block_overload_runtime);
bitcal::test::run_case(g_counters, "test_ctad_bit_block_overload_matches_view_form",
test_ctad_bit_block_overload_matches_view_form);
bitcal::test::run_case(g_counters, "test_random_shift_left_matches_reference_model_256",
test_random_shift_left_matches_reference_model_256);
bitcal::test::run_case(g_counters, "test_random_shift_right_matches_reference_model_256",
test_random_shift_right_matches_reference_model_256);

std::cout << std::endl;
std::cout << "Passed: " << g_counters.pass << std::endl;
Expand Down
Loading