diff --git a/.github/actions/benchmark_epilogue/action.yml b/.github/actions/benchmark_epilogue/action.yml index c7222878..1b9b0d2e 100644 --- a/.github/actions/benchmark_epilogue/action.yml +++ b/.github/actions/benchmark_epilogue/action.yml @@ -1,6 +1,9 @@ name: "Benchmark Epilogue" description: "Processes coverage information with lcov and uploads it to coveralls/codecov" inputs: + workspace: + required: True + description: "Source code / workspace" compiler: required: True description: 'Compiler used to build benchmark' @@ -13,29 +16,64 @@ inputs: build-output-dir: required: true description: 'Build output directory' + base_ref: + required: true + description: 'Base ref for PR/comparison' + cpp_compiler: + required: true + description: 'Cpp compiler to use' + c_compiler: + required: true + description: 'C compiler to use' + build_type: + required: true + description: 'Build type (release/debug)' + runs: using: "composite" steps: - - name: Archive benchmark results - uses: actions/upload-artifact@v4 + - name: Checkout target branch (BASE) + uses: actions/checkout@v4 with: - name: benchmark_${{ inputs.os }}_${{ inputs.compiler }}_${{ inputs.stdlib }}_json - path: ${{ inputs.build-output-dir }}/benchmark/benchmark_result.json + ref: ${{ inputs.base_ref }} - # Download previous benchmark result from cache (if exists) - - name: Download previous benchmark data - uses: actions/cache@v4 + - name: Setup Python + uses: actions/setup-python@v5 with: - path: ./cache - key: benchmark_${{ inputs.os }}_${{ inputs.compiler }}_${{ inputs.stdlib }} + python-version: '3.11' + + - name: Install benchmark compare.py requirements + shell: bash + run: | + pip install --upgrade setuptools + pip install -r ${{ inputs.build-output-dir}}/_deps/benchmark-src/tools/requirements.txt + + - name: Benchmark Workflow + shell: bash + run: | + mv ${{ inputs.build-output-dir }}/benchmark/benchmark_result.json ${{ inputs.build-output-dir }}/benchmark/benchmark_result_new.json + cmake -B ${{ inputs.build-output-dir}} -S ${{ inputs.workspace }} \ + --preset benchmark_${{ inputs.os }}_${{ inputs.compiler }}_${{ inputs.stdlib }} \ + -DCMAKE_CXX_COMPILER=${{ inputs.cpp_compiler }} \ + -DCMAKE_C_COMPILER=${{ inputs.c_compiler }} \ + -DCMAKE_BUILD_TYPE=${{ inputs.build_type }} + cmake --build ${{ inputs.build-output-dir}} --target benchmark --config ${{ inputs.build_type }} --parallel + ctest --test-dir ${{ inputs.build-output-dir}} --build-config ${{ inputs.build_type }} --output-on-failure --parallel + mv ${{ inputs.build-output-dir }}/benchmark/benchmark_result.json ${{ inputs.build-output-dir }}/benchmark/benchmark_result_ref.json + + - name: Compare Benchmarks + shell: bash + run: | + python3 ${{ inputs.build-output-dir}}/_deps/benchmark-src/tools/compare.py benchmarks \ + ${{ inputs.build-output-dir }}/benchmark/benchmark_result_ref.json \ + ${{ inputs.build-output-dir }}/benchmark/benchmark_result_new.json \ + | tee ${{ inputs.build-output-dir }}/benchmark/comparison.txt - - name: Store benchmark result - uses: benchmark-action/github-action-benchmark@v1 + - name: Archive benchmark results + uses: actions/upload-artifact@v4 with: - tool: 'googlecpp' - # Where the output from the benchmark tool is stored - output-file-path: ${{ inputs.build-output-dir }}/benchmark/benchmark_result.json - # Where the previous data file is stored - external-data-json-path: ./cache/benchmark_result.json - # Workflow will fail when an alert happens - fail-on-alert: true + name: benchmark_${{ inputs.os }}_${{ inputs.compiler }}_${{ inputs.stdlib }}_json + path: | + ${{ inputs.build-output-dir }}/benchmark/benchmark_result_ref.json + ${{ inputs.build-output-dir }}/benchmark/benchmark_result_new.json + ${{ inputs.build-output-dir }}/benchmark/comparison.txt diff --git a/.github/workflows/cmake-multi-platform.yml b/.github/workflows/cmake-multi-platform.yml index b73281b4..ce816881 100644 --- a/.github/workflows/cmake-multi-platform.yml +++ b/.github/workflows/cmake-multi-platform.yml @@ -175,6 +175,11 @@ jobs: - uses: ./.github/actions/benchmark_epilogue if: matrix.preset == 'benchmark' with: + workspace: ${{ github.workspace }} + cpp_compiler: ${{ matrix.cpp_compiler }} + c_compiler: ${{ matrix.c_compiler }} + build_type: ${{ matrix.build_type }} + base_ref: ${{ github.event.pull_request.base.ref }} os: ${{ matrix.os }} compiler: ${{ matrix.compiler }} stdlib: ${{ matrix.stdlib }} diff --git a/benchmark/CMakeLists.txt b/benchmark/CMakeLists.txt index 3a09d660..709c3b21 100644 --- a/benchmark/CMakeLists.txt +++ b/benchmark/CMakeLists.txt @@ -97,5 +97,5 @@ target_link_libraries(bitlib-bench PRIVATE add_test( NAME BenchmarkTest - COMMAND $ --benchmark_format=json --benchmark_out=benchmark_result.json + COMMAND $ --benchmark_format=json --benchmark_out=benchmark_result.json --benchmark_min_warmup_time=0.001 --benchmark_min_time=0.01s ) diff --git a/include/bitlib/bit-algorithms/bit_algorithm_details.hpp b/include/bitlib/bit-algorithms/bit_algorithm_details.hpp index ab30bc3f..57e2fb87 100644 --- a/include/bitlib/bit-algorithms/bit_algorithm_details.hpp +++ b/include/bitlib/bit-algorithms/bit_algorithm_details.hpp @@ -105,17 +105,17 @@ constexpr bool is_within( template T get_word(bit_iterator first, size_t len=binary_digits::value) { - using native_word_type = typename bit_iterator::word_type; - constexpr T digits = binary_digits::value; - assert(digits >= len); - using non_const_T = std::remove_cv_t; - non_const_T offset = digits - first.position(); - non_const_T ret_word = *first.base() >> first.position(); - - // We've already assigned enough bits - if (len <= offset) { - return ret_word; - } + using native_word_type = typename bit_iterator::word_type; + constexpr T digits = binary_digits::value; + assert(digits >= len); + using non_const_T = std::remove_cv_t; + non_const_T offset = digits - first.position(); + non_const_T ret_word = lsr(*first.base(), first.position()); + + // We've already assigned enough bits + if (len <= offset) { + return ret_word; + } InputIt it = std::next(first.base()); len -= offset; @@ -214,63 +214,57 @@ void write_word(src_type src, bit_iterator dst_bit_it, src_type len=binary_digits::value ) { - using dst_type = typename bit_iterator::word_type; - constexpr dst_type dst_digits = binary_digits::value; - constexpr dst_type src_digits = binary_digits::value; - - if constexpr (dst_digits >= src_digits) { - if (dst_bit_it.position() == 0 && len == dst_digits) { - *dst_bit_it.base() = src; - } - else { - *dst_bit_it.base() = _bitblend( - *dst_bit_it.base(), - src << dst_bit_it.position(), - dst_bit_it.position(), - std::min( - dst_digits - dst_bit_it.position(), - len - ) - ); - if (len > dst_digits - dst_bit_it.position()) { - OutputIt overflow_dst = std::next(dst_bit_it.base()); - *overflow_dst = _bitblend( - *overflow_dst, - src >> (dst_digits - dst_bit_it.position()), - 0, - len - (dst_digits - dst_bit_it.position()) - ); - } - } + using dst_type = typename bit_iterator::word_type; + constexpr dst_type dst_digits = binary_digits::value; + constexpr dst_type src_digits = binary_digits::value; + + if constexpr (dst_digits >= src_digits) { + if (dst_bit_it.position() == 0 && len == dst_digits) { + *dst_bit_it.base() = src; } else { - OutputIt it = dst_bit_it.base(); - if (dst_bit_it.position() != 0) { - *it = _bitblend( - *it, - static_cast(src), - static_cast(-1) << dst_bit_it.position() - ); - len -= dst_digits - dst_bit_it.position(); - // TODO would it be faster to jsut shift src every time it is - // passed as an argument and keep track of how much we need to - // shift? - src >>= dst_digits - dst_bit_it.position(); - ++it; - } - while (len >= dst_digits) { - *it = static_cast(src); - src >>= dst_digits; - len -= dst_digits; - ++it; - } - if (len > 0 ) { - *it = _bitblend( - *it, - static_cast(src), - (1 << len) - 1 - ); - } + *dst_bit_it.base() = _bitblend( + *dst_bit_it.base(), + src << dst_bit_it.position(), + dst_bit_it.position(), + std::min( + dst_digits - dst_bit_it.position(), + len)); + if (len > dst_digits - dst_bit_it.position()) { + OutputIt overflow_dst = std::next(dst_bit_it.base()); + *overflow_dst = _bitblend( + *overflow_dst, + lsr(src, (dst_digits - dst_bit_it.position())), + 0, + len - (dst_digits - dst_bit_it.position())); + } + } + } else { + OutputIt it = dst_bit_it.base(); + if (dst_bit_it.position() != 0) { + *it = _bitblend( + *it, + static_cast(src), + static_cast(-1) << dst_bit_it.position()); + len -= dst_digits - dst_bit_it.position(); + // TODO would it be faster to jsut shift src every time it is + // passed as an argument and keep track of how much we need to + // shift? + src = lsr(src, dst_digits - dst_bit_it.position()); + ++it; } + while (len >= dst_digits) { + *it = static_cast(src); + src = lsr(src, dst_digits); + len -= dst_digits; + ++it; + } + if (len > 0) { + *it = _bitblend( + *it, + static_cast(src), + _mask(len)); + } + } return; } @@ -367,56 +361,56 @@ WordType _shift_towards_msb(WordType word, std::size_t n) { * is undefined */ template +[[deprecated("Unused")]] typename bit_iterator::word_type _padded_read(bit_iterator first, - bit_iterator last, const bit::bit_value bv) { - - using word_type = typename bit_iterator::word_type; - - constexpr std::size_t num_digits = binary_digits::value; - const std::size_t first_position = first.position(); - const std::size_t last_position = last.position(); - const word_type read = *(first.base()); - constexpr word_type all_ones = _all_ones(); - - word_type mask; - - if (_is_aligned_lsb(first)) { - if (_in_same_word(first, last)) { - // Case 1 - if (bv == bit0) { - mask = _shift_towards_lsb(all_ones, num_digits - last_position); - return read & mask; - } else { - mask = _shift_towards_msb(all_ones, last_position); - return read | mask; - } - } else { - // Case 0 - return read; - } + bit_iterator last, const bit::bit_value bv) { + using word_type = typename bit_iterator::word_type; + + constexpr std::size_t num_digits = binary_digits::value; + const std::size_t first_position = first.position(); + const std::size_t last_position = last.position(); + const word_type read = *(first.base()); + constexpr word_type all_ones = _all_ones(); + + word_type mask; + + if (_is_aligned_lsb(first)) { + if (_in_same_word(first, last)) { + // Case 1 + if (bv == bit0) { + mask = _shift_towards_lsb(all_ones, num_digits - last_position); + return read & mask; + } else { + mask = _shift_towards_msb(all_ones, last_position); + return read | mask; + } + } else { + // Case 0 + return read; + } + } else { + if (!_in_same_word(first, last)) { + // Case 2 + if (bv == bit0) { + mask = _shift_towards_msb(all_ones, first_position); + return read & mask; + } else { + mask = _shift_towards_lsb(all_ones, num_digits - first_position); + return read | mask; + } } else { - if (!_in_same_word(first, last)) { - // Case 2 - if (bv == bit0) { - mask = _shift_towards_msb(all_ones, first_position); - return read & mask; - } else { - mask = _shift_towards_lsb(all_ones, num_digits - first_position); - return read | mask; - } - } else { - // Case 3 - if (bv == bit0) { - mask = _shift_towards_msb(all_ones, first_position); - mask &= _shift_towards_lsb(all_ones, num_digits - last_position); - return read & mask; - } else { - mask = _shift_towards_lsb(all_ones, num_digits - first_position); - mask |= _shift_towards_msb(all_ones, last_position); - return read | mask; - } - } + // Case 3 + if (bv == bit0) { + mask = _shift_towards_msb(all_ones, first_position); + mask &= _shift_towards_lsb(all_ones, num_digits - last_position); + return read & mask; + } else { + mask = _shift_towards_lsb(all_ones, num_digits - first_position); + mask |= _shift_towards_msb(all_ones, last_position); + return read | mask; + } } + } } // -------------------------------------------------------------------------- // diff --git a/include/bitlib/bit-algorithms/copy.hpp b/include/bitlib/bit-algorithms/copy.hpp index d4846026..9ecb336a 100644 --- a/include/bitlib/bit-algorithms/copy.hpp +++ b/include/bitlib/bit-algorithms/copy.hpp @@ -101,12 +101,10 @@ constexpr bit_iterator copy(bit_iterator first } } if (remaining_bits_to_copy > 0) { - *it = _bitblend( - *it, - get_word(first, remaining_bits_to_copy), - static_cast( - (static_cast(1) << remaining_bits_to_copy) - 1) - ); + *it = _bitblend( + *it, + get_word(first, remaining_bits_to_copy), + _mask(remaining_bits_to_copy)); } } return d_first + total_bits_to_copy; diff --git a/include/bitlib/bit-algorithms/count.hpp b/include/bitlib/bit-algorithms/count.hpp index 3f02be01..485a7750 100644 --- a/include/bitlib/bit-algorithms/count.hpp +++ b/include/bitlib/bit-algorithms/count.hpp @@ -55,7 +55,7 @@ count( iterator_type it = first.base(); if (first.position() != 0) { - word_type first_value = *first.base() >> first.position(); + word_type first_value = lsr(*first.base(), first.position()); result = _popcnt(first_value); ++it; } diff --git a/include/bitlib/bit-algorithms/equal.hpp b/include/bitlib/bit-algorithms/equal.hpp index 004c12bf..a733a625 100644 --- a/include/bitlib/bit-algorithms/equal.hpp +++ b/include/bitlib/bit-algorithms/equal.hpp @@ -56,9 +56,7 @@ constexpr bool equal( const size_type partial_bits_to_check = ::std::min( remaining_bits_to_check, digits - d_first.position()); - const word_type mask = static_cast( - (static_cast(1) << partial_bits_to_check) - 1 - ) << d_first.position(); + const word_type mask = _mask(partial_bits_to_check) << d_first.position(); const word_type comp = static_cast( get_word(first, partial_bits_to_check) << d_first.position()); @@ -88,11 +86,11 @@ constexpr bool equal( } } if (remaining_bits_to_check > 0) { - const word_type mask = static_cast( - (static_cast(1) << remaining_bits_to_check) - 1 - ); - const word_type comp = get_word(first, remaining_bits_to_check); - if ((mask & *it) != (mask & comp)) { return false; } + const word_type mask = _mask(remaining_bits_to_check); + const word_type comp = get_word(first, remaining_bits_to_check); + if ((mask & *it) != (mask & comp)) { + return false; + } } } return true; diff --git a/include/bitlib/bit-algorithms/find.hpp b/include/bitlib/bit-algorithms/find.hpp index b35583c7..0227d3d0 100644 --- a/include/bitlib/bit-algorithms/find.hpp +++ b/include/bitlib/bit-algorithms/find.hpp @@ -46,17 +46,17 @@ constexpr bit_iterator find( if (!is_first_aligned) { - word_type shifted_first = *first.base() >> first.position(); - size_type num_trailing_complementary_bits = (bv == bit0) - ? _tzcnt(static_cast(~shifted_first)) - : _tzcnt(static_cast(shifted_first)); - if (std::next(first.base(), is_last_aligned) == last.base()) { - return first + std::min(num_trailing_complementary_bits, (size_type) distance(first, last)); - } else if (num_trailing_complementary_bits + first.position() < digits) { - return first + num_trailing_complementary_bits; - } else { - first += digits - first.position(); - } + word_type shifted_first = lsr(*first.base(), first.position()); + size_type num_trailing_complementary_bits = (bv == bit0) + ? _tzcnt(static_cast(~shifted_first)) + : _tzcnt(static_cast(shifted_first)); + if (std::next(first.base(), is_last_aligned) == last.base()) { + return first + std::min(num_trailing_complementary_bits, static_cast(distance(first, last))); + } else if (num_trailing_complementary_bits + first.position() < digits) { + return first + num_trailing_complementary_bits; + } else { + first += digits - first.position(); + } } // Initialization @@ -109,7 +109,7 @@ constexpr bit_iterator find( size_type num_trailing_complementary_bits = (bv == bit0) ? _tzcnt(static_cast(~*it)) : _tzcnt(static_cast(*it)); - return bit_iterator(it, (size_type) num_trailing_complementary_bits); + return bit_iterator(it, static_cast(num_trailing_complementary_bits)); } // Deal with any unaligned boundaries @@ -117,7 +117,7 @@ constexpr bit_iterator find( size_type num_trailing_complementary_bits = (bv == bit0) ? _tzcnt(static_cast(~*it)) : _tzcnt(static_cast(*it)); - return bit_iterator(it, (size_type) std::min(num_trailing_complementary_bits, last.position())); + return bit_iterator(it, static_cast(std::min(num_trailing_complementary_bits, last.position()))); } return last; } diff --git a/include/bitlib/bit-algorithms/move.hpp b/include/bitlib/bit-algorithms/move.hpp index 0f0e09e4..aa31813d 100644 --- a/include/bitlib/bit-algorithms/move.hpp +++ b/include/bitlib/bit-algorithms/move.hpp @@ -87,12 +87,10 @@ constexpr bit_iterator move(bit_iterator first } } if (remaining_bits_to_move > 0) { - *it = _bitblend( - *it, - get_word(first, remaining_bits_to_move), - static_cast( - (static_cast(1) << remaining_bits_to_move) - 1) - ); + *it = _bitblend( + *it, + get_word(first, remaining_bits_to_move), + _mask(remaining_bits_to_move)); } } return d_first + total_bits_to_move; diff --git a/include/bitlib/bit-algorithms/reverse.hpp b/include/bitlib/bit-algorithms/reverse.hpp index 37dacb7c..582b24c0 100644 --- a/include/bitlib/bit-algorithms/reverse.hpp +++ b/include/bitlib/bit-algorithms/reverse.hpp @@ -101,12 +101,11 @@ constexpr void reverse( } // Reverse when bit iterators belong to the same underlying word } else { - *it = _bitblend( - *it, - _bitswap(*it >> first.position()) >> gap, - first.position(), - last.position() - first.position() - ); + *it = _bitblend( + *it, + lsr(_bitswap(lsr(*it, first.position())), gap), + first.position(), + last.position() - first.position()); } } diff --git a/include/bitlib/bit-algorithms/rotate.hpp b/include/bitlib/bit-algorithms/rotate.hpp index cbcb038a..1d5ecb28 100644 --- a/include/bitlib/bit-algorithms/rotate.hpp +++ b/include/bitlib/bit-algorithms/rotate.hpp @@ -208,23 +208,20 @@ bit_iterator rotate( // Within the same word if (std::next(first.base(), is_last_aligned) == last.base()) { if (is_first_aligned && is_last_aligned) { - *first.base() = - (*first.base() >> n_first.position()) - | - static_cast( - *first.base() << (digits - n_first.position()) - ); - return std::next(first, digits - n_first.position()); + *first.base() = + (lsr(*first.base(), n_first.position())) | + static_cast( + *first.base() << (digits - n_first.position())); + return std::next(first, digits - n_first.position()); } else { size_type last_pos = is_last_aligned ? digits : last.position(); size_type k = n_first.position() - first.position(); size_type p = last_pos - n_first.position(); size_type d = last_pos - first.position(); - word_type mask = ((1ULL << d) - 1) << first.position(); + word_type mask = _mask(d) << first.position(); word_type rotated = *first.base() & mask; - rotated = static_cast(rotated >> k) - | static_cast(rotated << p); + rotated = static_cast(lsr(rotated, k)) | static_cast(rotated << p); *first.base() = _bitblend( *first.base(), rotated, diff --git a/include/bitlib/bit-algorithms/shift.hpp b/include/bitlib/bit-algorithms/shift.hpp index 57c5308d..1d1bf3a6 100644 --- a/include/bitlib/bit-algorithms/shift.hpp +++ b/include/bitlib/bit-algorithms/shift.hpp @@ -73,21 +73,23 @@ bit_iterator shift_left( // Single word case // Triggered if all relevant bits are in first.base() + // clang-format off if (std::next(first.base(), is_last_aligned) == last.base()) { *first.base() = _bitblend( *first.base(), - (( + lsr( *first.base() & ( - static_cast(-1) >> ( + lsr(static_cast(-1), ( digits - (is_last_aligned ? digits : last.position()) - ) + )) ) - )) >> n, + , n), first.position(), (is_last_aligned ? digits : last.position()) - first.position() ); return first + d - n; } + // clang-format on // Triggered if all remaining bits can fit in a word if (d - n <= digits) @@ -113,7 +115,7 @@ bit_iterator shift_left( const int n2 = digits - first.position() - n1; *first.base() = _bitblend( *first.base(), - (*middle.base()) >> (middle.position() - first.position()), + lsr(*middle.base(), (middle.position() - first.position())), first.position(), n1); *first.base() = _bitblend( @@ -131,13 +133,12 @@ bit_iterator shift_left( const int bits_left = last.position() - middle.position(); if (bits_left > 0) { - *first.base() = _bitblend( - *first.base(), - *middle.base() >> middle.position(), - 0, - bits_left - ); - first += bits_left; + *first.base() = _bitblend( + *first.base(), + lsr(*middle.base(), middle.position()), + 0, + bits_left); + first += bits_left; } // https://en.cppreference.com/w/cpp/algorithm/shift // "Elements that are in the original range but not the new range @@ -205,14 +206,13 @@ bit_iterator shift_left( // If middle is now penultimate word if (std::next(middle.base()) == last.base()) { - *first.base() = _bitblend( - *first.base(), - *middle.base() >> offset, - 0, - digits - offset - ); - first += digits - offset; - middle += digits - offset; + *first.base() = _bitblend( + *first.base(), + lsr(*middle.base(), offset), + 0, + digits - offset); + first += digits - offset; + middle += digits - offset; } if (!is_last_aligned) diff --git a/include/bitlib/bit-algorithms/transform.hpp b/include/bitlib/bit-algorithms/transform.hpp index 88577882..ac3de218 100644 --- a/include/bitlib/bit-algorithms/transform.hpp +++ b/include/bitlib/bit-algorithms/transform.hpp @@ -116,12 +116,10 @@ constexpr bit_iterator transform( } } if (remaining_bits_to_op > 0) { - *it = _bitblend( - *it, - unary_op(get_word(first, remaining_bits_to_op)), - static_cast( - (static_cast(1) << remaining_bits_to_op) - 1) - ); + *it = _bitblend( + *it, + unary_op(get_word(first, remaining_bits_to_op)), + _mask(remaining_bits_to_op)); } } return d_first + total_bits_to_op; @@ -197,15 +195,12 @@ constexpr bit_iterator transform( } } if (remaining_bits_to_op > 0) { - *it = _bitblend( - *it, - binary_op( - get_word(first1, remaining_bits_to_op), - get_word(first2, remaining_bits_to_op) - ), - static_cast( - (static_cast(1) << remaining_bits_to_op) - 1) - ); + *it = _bitblend( + *it, + binary_op( + get_word(first1, remaining_bits_to_op), + get_word(first2, remaining_bits_to_op)), + _mask(remaining_bits_to_op)); } } return d_first + total_bits_to_op; diff --git a/include/bitlib/bit-algorithms/transform_compare.hpp b/include/bitlib/bit-algorithms/transform_compare.hpp new file mode 100644 index 00000000..fb0e177d --- /dev/null +++ b/include/bitlib/bit-algorithms/transform_compare.hpp @@ -0,0 +1,134 @@ +// ================================= EQUAL =================================== // +// Project: The Experimental Bit Algorithms Library +// Name: equal.hpp +// Contributor: Bryce Kille [2019] +// License: BSD 3-Clause License +// ========================================================================== // +#ifndef _EQUAL_HPP_INCLUDED +#define _EQUAL_HPP_INCLUDED +// ========================================================================== // + + + +// ================================ PREAMBLE ================================ // +// C++ standard library +#include +#include +// Project sources +#include "bitlib/bit-iterator/bit.hpp" +// Third-party libraries +// Miscellaneous +namespace bit { +// ========================================================================== // + + + +// ---------------------------- Equal Algorithms ----------------------------- // + +// Status: Does not work for Input/Output iterators due to distance call +template +constexpr bool transform_compare( + bit_iterator first, + bit_iterator last, + bit_iterator first2, + BinaryComparison binary_cmp +) +{ + // Types and constants + using dst_word_type = typename bit_iterator::word_type; + using src_word_type = typename bit_iterator::word_type; + using word_type = dst_word_type; + using size_type = typename bit_iterator::size_type; + constexpr size_type digits = binary_digits::value; + + // Assertions + _assert_range_viability(first, last); + static_assert(::std::is_same::value, "Underlying word types must be equal"); + if (first == last) return true; + + // Initialization + size_type total_bits_to_check = distance(first, last); + size_type remaining_bits_to_check = total_bits_to_check; + + if (remaining_bits_to_check < digits) { + + } + + auto it1 = first1.base() + total_bits_to_check - 1 - digits; + auto it2 = first2.base() + total_bits_to_check - 1 - digits; + const bool is_d_first_aligned = it1.position() == 0; + + + bitsof< + // d_first is not aligned. + if (!is_d_first_aligned) { + + size_type partial_bits_to_op = ::std::min( + remaining_bits_to_op, + digits - d_first.position() + ); + + if (!binary_cmp( + static_cast( + get_word(it1, partial_bits_to_op) + << static_cast(d_first.position()) + ), + static_cast( + get_word(first2, partial_bits_to_op) + << static_cast(d_first.position()) + ) + )) { return false;} + remaining_bits_to_op -= partial_bits_to_op; + advance(first1, partial_bits_to_op); + advance(first2, partial_bits_to_op); + it++; + const size_type partial_bits_to_check = ::std::min( + remaining_bits_to_check, + digits - d_first.position()); + const word_type mask = _mask(partial_bits_to_check) << d_first.position(); + const word_type comp = static_cast( + get_word(first, partial_bits_to_check) + << d_first.position()); + if ((mask & *it) != (mask & comp)) { return false; } + remaining_bits_to_check -= partial_bits_to_check; + advance(first, partial_bits_to_check); + it++; + } + + if (remaining_bits_to_check > 0) { + const bool is_first_aligned = first.position() == 0; + // d_first will be aligned at this point + if (is_first_aligned && remaining_bits_to_check >= digits) { + auto N = ::std::distance(first.base(), last.base()); + bool found_mismatch = !::std::equal(first.base(), last.base(), it); + if (found_mismatch) {return false;} + it += N; + first += digits * N; + remaining_bits_to_check -= digits * N; + } else { + // TODO benchmark if its faster to ::std::check the entire range then shift + while (remaining_bits_to_check >= digits) { + if (*it != get_word(first, digits)) {return false;} + remaining_bits_to_check -= digits; + it++; + advance(first, digits); + } + } + if (remaining_bits_to_check > 0) { + const word_type mask = _mask(remaining_bits_to_check); + const word_type comp = get_word(first, remaining_bits_to_check); + if ((mask & *it) != (mask & comp)) { + return false; + } + } + } + return true; +} +// -------------------------------------------------------------------------- // + + + +// ========================================================================== // +} // namespace bit +#endif // _EQUAL_HPP_INCLUDED +// ========================================================================== // diff --git a/include/bitlib/bit-iterator/bit_details.hpp b/include/bitlib/bit-iterator/bit_details.hpp index e1cc9385..7b2f79e1 100644 --- a/include/bitlib/bit-iterator/bit_details.hpp +++ b/include/bitlib/bit-iterator/bit_details.hpp @@ -28,6 +28,7 @@ #include #include +#include "bitlib/bit-containers/bit_bitsof.hpp" #include "bitlib/bit_concepts.hpp" // Project sources @@ -47,12 +48,11 @@ using bit_pointer = bit_iterator; // Binary digits structure definition // Implementation template: only instantiates static_asserts for non-byte types. template ::value> -struct binary_digits_impl : std::integral_constant::digits> -{ - static_assert(std::is_integral::value, "Type must be integral"); - static_assert(std::is_unsigned::value, "Type must be unsigned"); - static_assert(!std::is_same::value, "Type must not be bool"); - static_assert(!std::is_same::value, "Type must not be char"); +struct binary_digits_impl : std::integral_constant>::digits> { + static_assert(std::is_integral::value, "Type must be integral"); + //static_assert(std::is_unsigned::value, "Type must be unsigned"); + static_assert(!std::is_same::value, "Type must not be bool"); + static_assert(!std::is_same::value, "Type must not be char"); }; // Specialization for std::byte. @@ -395,440 +395,411 @@ template constexpr T _mulx(T src0, T src1, T* hi, X...) noexcept; /* ************************************************************************** */ +/* +Logical shift right +*/ +template +constexpr T lsr(const T& val, const size_type shift) { + return static_cast(static_cast>(val) >> shift); +} +enum class _mask_len { + unknown, + in_range +}; + +template +constexpr T _mask(const size_type len) { + constexpr std::make_unsigned_t one = std::make_unsigned_t(1); + if constexpr (len_in_range != _mask_len::unknown) { + return static_cast((one << len) - one); + } else { + // The digits_mask is solely here to prevent Undefined Sanitizer + // complaining about shift of len >= digits + // Note: on -O1 the (len & digits_mask) is optimized to simply (len) + constexpr std::make_unsigned_t digits_mask = bitsof() - one; + return static_cast((one << (len & digits_mask)) * (len < bitsof()) - one); + } +} // ------------- IMPLEMENTATION DETAILS: UTILITIES: ASSERTIONS -------------- // // If the range allows multipass iteration, checks if last - first >= 0 template -constexpr bool _assert_range_viability(Iterator first, Iterator last) -{ - using traits_t = std::iterator_traits; - using category_t = typename traits_t::iterator_category; - using multi_t = std::forward_iterator_tag; - constexpr bool is_multipass = std::is_base_of::value; - const bool is_viable = !is_multipass || std::distance(first, last) >= 0; - assert(is_viable); - return is_viable; +constexpr bool _assert_range_viability(Iterator first, Iterator last) { + using traits_t = std::iterator_traits; + using category_t = typename traits_t::iterator_category; + using multi_t = std::forward_iterator_tag; + constexpr bool is_multipass = std::is_base_of::value; + const bool is_viable = !is_multipass || std::distance(first, last) >= 0; + assert(is_viable); + return is_viable; } // -------------------------------------------------------------------------- // - - // --------- IMPLEMENTATION DETAILS: INSTRUCTIONS: POPULATION COUNT --------- // // Counts the number of bits set to 1 with compiler intrinsics template -constexpr T _popcnt(T src) noexcept -{ - static_assert(binary_digits::value, ""); - constexpr T digits = binary_digits::value; - if (digits <= std::numeric_limits::digits) { - src = __builtin_popcount(src); - } else if (digits <= std::numeric_limits::digits) { - src = __builtin_popcountl(src); - } else if (digits <= std::numeric_limits::digits) { - src = __builtin_popcountll(src); - } else { - src = _popcnt(src, std::ignore); - } - return src; +constexpr T _popcnt(T src) noexcept { + static_assert(binary_digits::value, ""); + constexpr T digits = binary_digits::value; + if (digits <= std::numeric_limits::digits) { + src = __builtin_popcount(static_cast>(src)); + } else if (digits <= std::numeric_limits::digits) { + src = __builtin_popcountl(static_cast>(src)); + } else if (digits <= std::numeric_limits::digits) { + src = __builtin_popcountll(static_cast>(src)); + } else { + src = _popcnt(src, std::ignore); + } + return src; } // Counts the number of bits set to 1 without compiler intrinsics template -constexpr T _popcnt(T src, X...) noexcept -{ - static_assert(binary_digits::value, ""); - T dst = T(); - for (dst = T(); src; src >>= 1) { - dst += src & 1; - } - return dst; +constexpr T _popcnt(T src, X...) noexcept { + static_assert(binary_digits::value, ""); + T dst = T(); + for (dst = T(); src; src = lsr(src, 1)) { + dst += src & 1; + } + return dst; } // -------------------------------------------------------------------------- // - - // ------- IMPLEMENTATION DETAILS: INSTRUCTIONS: LEADING ZEROS COUNT -------- // // Counts the number of leading zeros with compiler intrinsics template -constexpr T _lzcnt(T src) noexcept -{ - static_assert(binary_digits::value, ""); - constexpr T digits = binary_digits::value; - T dst = T(); - if (digits < std::numeric_limits::digits) { - dst = src ? __builtin_clz(src) - - (std::numeric_limits::digits - - digits) - : digits; - } else if (digits == std::numeric_limits::digits) { - dst = src ? __builtin_clz(src) : digits; - } else if (digits < std::numeric_limits::digits) { - dst = src ? __builtin_clzl(src) - - (std::numeric_limits::digits - - digits) - : digits; - } else if (digits == std::numeric_limits::digits) { - dst = src ? __builtin_clzl(src) : digits; - } else if (digits < std::numeric_limits::digits) { - dst = src ? __builtin_clzll(src) - - (std::numeric_limits::digits - - digits) - : digits; - } else if (digits == std::numeric_limits::digits) { - dst = src ? __builtin_clzll(src) : digits; - } else { - dst = _lzcnt(src, std::ignore); - } - return dst; +constexpr T _lzcnt(T src) noexcept { + static_assert(binary_digits::value, ""); + constexpr T digits = binary_digits::value; + T dst = T(); + if (digits < std::numeric_limits::digits) { + dst = src ? __builtin_clz(src) - (std::numeric_limits::digits - digits) + : digits; + } else if (digits == std::numeric_limits::digits) { + dst = src ? __builtin_clz(src) : digits; + } else if (digits < std::numeric_limits::digits) { + dst = src ? __builtin_clzl(src) - (std::numeric_limits::digits - digits) + : digits; + } else if (digits == std::numeric_limits::digits) { + dst = src ? __builtin_clzl(src) : digits; + } else if (digits < std::numeric_limits::digits) { + dst = src ? __builtin_clzll(src) - (std::numeric_limits::digits - digits) + : digits; + } else if (digits == std::numeric_limits::digits) { + dst = src ? __builtin_clzll(src) : digits; + } else { + dst = _lzcnt(src, std::ignore); + } + return dst; } // Counts the number of leading zeros without compiler intrinsics template -constexpr T _lzcnt(T src, X...) noexcept -{ - static_assert(binary_digits::value, ""); - constexpr T digits = binary_digits::value; - T dst = src != T(); - while (src >>= 1) { - ++dst; - } - return digits - dst; +constexpr T _lzcnt(T src, X...) noexcept { + static_assert(binary_digits::value, ""); + constexpr T digits = binary_digits::value; + T dst = src != T(); + while ((src = lsr(src, 1))) { + ++dst; + } + return digits - dst; } // -------------------------------------------------------------------------- // - - // ------- IMPLEMENTATION DETAILS: INSTRUCTIONS: TRAILING ZEROS COUNT ------- // // Counts the number of trailing zeros with compiler intrinsics template -constexpr T _tzcnt(T src) noexcept -{ - static_assert(binary_digits::value, ""); - constexpr T digits = binary_digits::value; - T dst = T(); - if (digits <= std::numeric_limits::digits) { - dst = src ? __builtin_ctz(src) : digits; - } else if (digits <= std::numeric_limits::digits) { - dst = src ? __builtin_ctzl(src) : digits; - } else if (digits <= std::numeric_limits::digits) { - dst = src ? __builtin_ctzll(src) : digits; - } else { - dst = _tzcnt(src, std::ignore); - } - return dst; +constexpr T _tzcnt(T src) noexcept { + static_assert(binary_digits::value, ""); + constexpr T digits = binary_digits::value; + T dst = T(); + if (digits <= std::numeric_limits::digits) { + dst = src ? __builtin_ctz(src) : digits; + } else if (digits <= std::numeric_limits::digits) { + dst = src ? __builtin_ctzl(src) : digits; + } else if (digits <= std::numeric_limits::digits) { + dst = src ? __builtin_ctzll(src) : digits; + } else { + dst = _tzcnt(src, std::ignore); + } + return dst; } // Counts the number of trailing zeros without compiler intrinsics template -constexpr T _tzcnt(T src, X...) noexcept -{ - static_assert(binary_digits::value, ""); - constexpr T digits = binary_digits::value; - T dst = digits; - if (src) { - src = (src ^ (src - 1)) >> 1; - for (dst = T(); src; dst++) { - src >>= 1; - } +constexpr T _tzcnt(T src, X...) noexcept { + static_assert(binary_digits::value, ""); + constexpr T digits = binary_digits::value; + T dst = digits; + if (src) { + src = lsr((src ^ (src - 1)), 1); + for (dst = T(); src; dst++) { + src = lsr(src, 1); } - return dst; + } + return dst; } // -------------------------------------------------------------------------- // - - // ------- IMPLEMENTATION DETAILS: INSTRUCTIONS: BIT FIELD EXTRACTION ------- // // Extacts to lsbs a field of contiguous bits with compiler intrinsics template -constexpr T _bextr(T src, T start, T len) noexcept -{ - static_assert(binary_digits::value, ""); - constexpr T digits = binary_digits::value; - T dst = T(); - if (digits <= std::numeric_limits::digits) { - dst = __builtin_ia32_bextr_u32(src, start, len); - } else if (digits <= std::numeric_limits::digits) { - dst = __builtin_ia32_bextr_u64(src, start, len); - } else { - dst = _bextr(src, start, len, std::ignore); - } - return dst; +constexpr T _bextr(T src, T start, T len) noexcept { + static_assert(binary_digits::value, ""); + constexpr T digits = binary_digits::value; + T dst = T(); + if (digits <= std::numeric_limits::digits) { + dst = __builtin_ia32_bextr_u32(src, start, len); + } else if (digits <= std::numeric_limits::digits) { + dst = __builtin_ia32_bextr_u64(src, start, len); + } else { + dst = _bextr(src, start, len, std::ignore); + } + return dst; } // Extacts to lsbs a field of contiguous bits without compiler intrinsics template -constexpr T _bextr(T src, T start, T len, X...) noexcept -{ - static_assert(binary_digits::value, ""); - constexpr T digits = binary_digits::value; - constexpr T one = 1; - const T msk = (one << len) * (len < digits) - one; - return (src >> start) & msk * (start < digits); +constexpr T _bextr(T src, T start, T len, X...) noexcept { + static_assert(binary_digits::value, ""); + constexpr T digits = binary_digits::value; + constexpr T one = 1; + const T msk = (one << len) * (len < digits) - one; + return (lsr(src, start)) & msk * (start < digits); } // -------------------------------------------------------------------------- // - - // ------- IMPLEMENTATION DETAILS: INSTRUCTIONS: PARALLEL BIT DEPOSIT ------- // // Deposits bits according to a mask with compiler instrinsics template -constexpr T _pdep(T src, T msk) noexcept -{ - static_assert(binary_digits::value, ""); - constexpr T digits = binary_digits::value; - T dst = T(); - if (digits <= std::numeric_limits::digits) { - dst = _pdep_u32(src, msk); - } else if (digits <= std::numeric_limits::digits) { - dst = _pdep_u64(src, msk); - } else { - dst = _pdep(src, msk, std::ignore); - } - return dst; +constexpr T _pdep(T src, T msk) noexcept { + static_assert(binary_digits::value, ""); + constexpr T digits = binary_digits::value; + T dst = T(); + if (digits <= std::numeric_limits::digits) { + dst = _pdep_u32(src, msk); + } else if (digits <= std::numeric_limits::digits) { + dst = _pdep_u64(src, msk); + } else { + dst = _pdep(src, msk, std::ignore); + } + return dst; } // Deposits bits according to a mask without compiler instrinsics template -constexpr T _pdep(T src, T msk, X...) noexcept -{ - static_assert(binary_digits::value, ""); - constexpr T digits = binary_digits::value; - T dst = T(); - T cnt = T(); - while (msk) { - dst >>= 1; - if (msk & 1) { - dst |= src << (digits - 1); - src >>= 1; - } - msk >>= 1; - ++cnt; +constexpr T _pdep(T src, T msk, X...) noexcept { + static_assert(binary_digits::value, ""); + constexpr T digits = binary_digits::value; + T dst = T(); + T cnt = T(); + while (msk) { + dst = lsr(dst, 1); + if (msk & 1) { + dst |= src << (digits - 1); + src = lsr(src, 1); } - dst >>= (digits - cnt) * (cnt > 0); - return dst; + msk = lsr(msk, 1); + ++cnt; + } + dst = lsr(dst, (digits - cnt) * (cnt > 0)); + return dst; } // -------------------------------------------------------------------------- // - - // ------- IMPLEMENTATION DETAILS: INSTRUCTIONS: PARALLEL BIT EXTRACT ------- // // Extracts bits according to a mask with compiler instrinsics template -constexpr T _pext(T src, T msk) noexcept -{ - static_assert(binary_digits::value, ""); - constexpr T digits = binary_digits::value; - T dst = T(); - if (digits <= std::numeric_limits::digits) { - dst = _pext_u32(src, msk); - } else if (digits <= std::numeric_limits::digits) { - dst = _pext_u64(src, msk); - } else { - dst = _pext(src, msk, std::ignore); - } - return dst; +constexpr T _pext(T src, T msk) noexcept { + static_assert(binary_digits::value, ""); + constexpr T digits = binary_digits::value; + T dst = T(); + if (digits <= std::numeric_limits::digits) { + dst = _pext_u32(src, msk); + } else if (digits <= std::numeric_limits::digits) { + dst = _pext_u64(src, msk); + } else { + dst = _pext(src, msk, std::ignore); + } + return dst; } // Extracts bits according to a mask without compiler instrinsics template -constexpr T _pext(T src, T msk, X...) noexcept -{ - static_assert(binary_digits::value, ""); - constexpr T digits = binary_digits::value; - T dst = T(); - T cnt = T(); - while (msk) { - if (msk & 1) { - dst >>= 1; - dst |= src << (digits - 1); - ++cnt; - } - src >>= 1; - msk >>= 1; +constexpr T _pext(T src, T msk, X...) noexcept { + static_assert(binary_digits::value, ""); + constexpr T digits = binary_digits::value; + T dst = T(); + T cnt = T(); + while (msk) { + if (msk & 1) { + dst = lsr(dst, 1); + dst |= src << (digits - 1); + ++cnt; } - dst >>= (digits - cnt) * (cnt > 0); - return dst; + src = lsr(src, 1); + msk = lsr(msk, 1); + } + dst = lsr(dst, (digits - cnt) * (cnt > 0)); + return dst; } // -------------------------------------------------------------------------- // - - // ------------ IMPLEMENTATION DETAILS: INSTRUCTIONS: BYTE SWAP ------------- // // Reverses the order of the underlying bytes with compiler intrinsics template -constexpr T _byteswap(T src) noexcept -{ - static_assert(binary_digits::value, ""); - using byte_t = unsigned char; - constexpr T digits = sizeof(T) * std::numeric_limits::digits; - std::uint64_t tmp64 = 0; - std::uint64_t* ptr64 = nullptr; - if (std::is_same::value) { - ptr64 = reinterpret_cast(&src); - tmp64 = __builtin_bswap64(*ptr64); - *ptr64 = __builtin_bswap64(*(ptr64 + 1)); - *(ptr64 + 1) = tmp64; - } else if (digits == std::numeric_limits::digits) { - src = __builtin_bswap16(src); - } else if (digits == std::numeric_limits::digits) { - src = __builtin_bswap32(src); - } else if (digits == std::numeric_limits::digits) { - src = __builtin_bswap64(src); - } else if (digits > std::numeric_limits::digits) { - src = _byteswap(src, std::ignore); - } - return src; +constexpr T _byteswap(T src) noexcept { + static_assert(binary_digits::value, ""); + using byte_t = unsigned char; + constexpr T digits = sizeof(T) * std::numeric_limits::digits; + std::uint64_t tmp64 = 0; + std::uint64_t* ptr64 = nullptr; + if (std::is_same::value) { + ptr64 = reinterpret_cast(&src); + tmp64 = __builtin_bswap64(*ptr64); + *ptr64 = __builtin_bswap64(*(ptr64 + 1)); + *(ptr64 + 1) = tmp64; + } else if (digits == std::numeric_limits::digits) { + src = __builtin_bswap16(src); + } else if (digits == std::numeric_limits::digits) { + src = __builtin_bswap32(src); + } else if (digits == std::numeric_limits::digits) { + src = __builtin_bswap64(src); + } else if (digits > std::numeric_limits::digits) { + src = _byteswap(src, std::ignore); + } + return src; } // Reverses the order of the underlying bytes without compiler intrinsics template -constexpr T _byteswap(T src, X...) noexcept -{ - static_assert(binary_digits::value, ""); - using byte_t = unsigned char; - constexpr T half = sizeof(T) / 2; - constexpr T end = sizeof(T) - 1; - unsigned char* bytes = reinterpret_cast(&src); - unsigned char byte = 0; - for (T i = T(); i < half; ++i) { - byte = bytes[i]; - bytes[i] = bytes[end - i]; - bytes[end - i] = byte; - } - return src; +constexpr T _byteswap(T src, X...) noexcept { + static_assert(binary_digits::value, ""); + using byte_t = unsigned char; + constexpr T half = sizeof(T) / 2; + constexpr T end = sizeof(T) - 1; + unsigned char* bytes = reinterpret_cast(&src); + unsigned char byte = 0; + for (T i = T(); i < half; ++i) { + byte = bytes[i]; + bytes[i] = bytes[end - i]; + bytes[end - i] = byte; + } + return src; } // -------------------------------------------------------------------------- // - - // ------------- IMPLEMENTATION DETAILS: INSTRUCTIONS: BIT SWAP ------------- // // Reverses the order of the bits with or without of compiler intrinsics template -constexpr T _bitswap(T src) noexcept -{ - static_assert(binary_digits::value, ""); - using byte_t = unsigned char; - constexpr auto ignore = nullptr; - constexpr T digits = binary_digits::value; - constexpr unsigned long long int first = 0x80200802ULL; - constexpr unsigned long long int second = 0x0884422110ULL; - constexpr unsigned long long int third = 0x0101010101ULL; - constexpr unsigned long long int fourth = 32; - constexpr bool is_size1 = sizeof(T) == 1; - constexpr bool is_byte = digits == std::numeric_limits::digits; - constexpr bool is_octet = std::numeric_limits::digits == 8; - constexpr bool is_pow2 = _popcnt(digits, ignore) == 1; - T dst = src; - T i = digits - 1; - if (is_size1 && is_byte && is_octet) { - dst = ((src * first) & second) * third >> fourth; - } else if (is_pow2) { - dst = _bitswap(src); - } else { - for (src >>= 1; src; src >>= 1) { - dst <<= 1; - dst |= src & 1; - i--; - } - dst <<= i; +constexpr T _bitswap(T src) noexcept { + static_assert(binary_digits::value, ""); + using byte_t = unsigned char; + constexpr auto ignore = nullptr; + constexpr T digits = binary_digits::value; + constexpr unsigned long long int first = 0x80200802ULL; + constexpr unsigned long long int second = 0x0884422110ULL; + constexpr unsigned long long int third = 0x0101010101ULL; + constexpr unsigned long long int fourth = 32; + constexpr bool is_size1 = sizeof(T) == 1; + constexpr bool is_byte = digits == std::numeric_limits::digits; + constexpr bool is_octet = std::numeric_limits::digits == 8; + constexpr bool is_pow2 = _popcnt(digits, ignore) == 1; + T dst = src; + T i = digits - 1; + if (is_size1 && is_byte && is_octet) { + dst = static_cast(lsr(((static_cast>(src) * first) & second) * third, fourth)); + } else if (is_pow2) { + dst = _bitswap(src); + } else { + for (src = lsr(src, 1); src; src = lsr(src, 1)) { + dst <<= 1; + dst |= src & 1; + i--; } - return dst; + dst <<= i; + } + return dst; } // Reverses the order of the bits: recursive metafunction template -constexpr T _bitswap(T src) noexcept -{ - static_assert(binary_digits::value, ""); - constexpr T cnt = N >> 1; - constexpr T msk = _bitswap(); - src = ((src >> cnt) & msk) | ((src << cnt) & ~msk); - return cnt > 1 ? _bitswap(src) : src; +constexpr T _bitswap(T src) noexcept { + static_assert(binary_digits::value, ""); + constexpr T cnt = N >> 1; + constexpr T msk = _bitswap(); + src = ((lsr(src, cnt)) & msk) | ((src << cnt) & ~msk); + return cnt > 1 ? _bitswap(src) : src; } // Reverses the order of the bits: mask for the recursive metafunction template -constexpr T _bitswap() noexcept -{ - static_assert(binary_digits::value, ""); - constexpr T digits = binary_digits::value; - T cnt = digits; - T msk = ~T(); - while (cnt != N) { - cnt >>= 1; - msk ^= (msk << cnt); - } - return msk; +constexpr T _bitswap() noexcept { + static_assert(binary_digits::value, ""); + constexpr T digits = binary_digits::value; + T cnt = digits; + T msk = ~T(); + while (cnt != N) { + cnt = lsr(cnt, 1); + msk ^= (msk << cnt); + } + return msk; } // -------------------------------------------------------------------------- // - - // ------------ IMPLEMENTATION DETAILS: INSTRUCTIONS: BIT BLEND ------------- // // Replaces bits of src0 by the ones of src1 where the mask is true template -constexpr T _bitblend(T src0, T src1, T msk) noexcept -{ - static_assert(binary_digits::value, ""); - return src0 ^ ((src0 ^ src1) & msk); +constexpr T _bitblend(T src0, T src1, T msk) noexcept { + static_assert(binary_digits::value, ""); + return src0 ^ ((src0 ^ src1) & msk); } // Replaces len bits of src0 by the ones of src1 starting at start template -constexpr T _bitblend(T src0, T src1, T start, T len) noexcept -{ - static_assert(binary_digits::value, ""); - constexpr T digits = binary_digits::value; - constexpr T one = 1; - // The digits_mask is solely here to prevent Undefined Sanitizer - // complaining about shift of len >= digits - // Note: on -O1 the (len & digits_mask) is optimized to simply (len) - constexpr T digits_mask = digits - one; - const T msk = ((one << (len & digits_mask)) * (len < digits) - one) << start; - return src0 ^ ((src0 ^ src1) & msk * (start < digits)); +constexpr T _bitblend(T src0, T src1, T start, T len) noexcept { + static_assert(binary_digits::value, ""); + constexpr T digits = binary_digits::value; + const T msk = _mask(len) << start; + return src0 ^ ((src0 ^ src1) & msk * (start < digits)); } // -------------------------------------------------------------------------- // - - // ---------- IMPLEMENTATION DETAILS: INSTRUCTIONS: BIT EXCHANGE ------------ // // Exchanges/swaps bits of src0 by the ones of src1 where the mask is true template -constexpr void _bitexch(T& src0, T& src1, T msk) noexcept -{ - src0 = src0 ^ static_cast(src1 & msk); - src1 = src1 ^ static_cast(src0 & msk); - src0 = src0 ^ static_cast(src1 & msk); - return; +constexpr void _bitexch(T& src0, T& src1, T msk) noexcept { + src0 = src0 ^ static_cast(src1 & msk); + src1 = src1 ^ static_cast(src0 & msk); + src0 = src0 ^ static_cast(src1 & msk); + return; } // Replaces len bits of src0 by the ones of src1 starting at start template -constexpr void _bitexch(T& src0, T& src1, S start, S len) noexcept -{ - static_assert(binary_digits::value, ""); - constexpr auto digits = binary_digits::value; - constexpr T one = 1; - const T msk = (len < digits) - ? ((one << len) - one) << start : -1; - src0 = src0 ^ static_cast(src1 & msk); - src1 = src1 ^ static_cast(src0 & msk); - src0 = src0 ^ static_cast(src1 & msk); - return; +constexpr void _bitexch(T& src0, T& src1, S start, S len) noexcept { + static_assert(binary_digits::value, ""); + constexpr auto digits = binary_digits::value; + const T msk = (len < digits) + ? _mask(len) << start + : -1; // TODO: What if start > 0 here? + src0 = src0 ^ static_cast(src1 & msk); + src1 = src1 ^ static_cast(src0 & msk); + src0 = src0 ^ static_cast(src1 & msk); + return; } // Replaces len bits of src0 by the ones of src1 starting at start0 // in src0 and start1 in src1. // len <= digits-max(start0, start1) +// clang-format off template constexpr void _bitexch(T& src0, T& src1, S start0, S start1, S len) noexcept { static_assert(binary_digits::value, ""); constexpr auto digits = binary_digits::value; - constexpr T one = 1; - const T msk = (len < digits) ? - ((one << len) - one) : -1; + const T msk = _mask(len); if (start0 >= start1) { src0 = src0 ^ ( static_cast(src1 << (start0 - start1)) @@ -836,7 +807,7 @@ constexpr void _bitexch(T& src0, T& src1, S start0, S start1, S len) noexcept static_cast(msk << start0) ); src1 = src1 ^ ( - static_cast(src0 >> (start0 - start1)) + static_cast(lsr(src0, (start0 - start1))) & static_cast(msk << start1) ); @@ -847,7 +818,7 @@ constexpr void _bitexch(T& src0, T& src1, S start0, S start1, S len) noexcept ); } else { src0 = src0 ^ ( - static_cast(src1 >> (start1 - start0)) + static_cast(lsr(src1, (start1 - start0))) & static_cast(msk << start0) ); @@ -857,173 +828,154 @@ constexpr void _bitexch(T& src0, T& src1, S start0, S start1, S len) noexcept static_cast(msk << start1) ); src0 = src0 ^ ( - static_cast(src1 >> (start1 - start0)) + static_cast(lsr(src1, (start1 - start0))) & static_cast(msk << start0) ); } return; } +// clang-format on // -------------------------------------------------------------------------- // - - // ----------- IMPLEMENTATION DETAILS: INSTRUCTIONS: BIT COMPARE ------------ // // Compares a subsequence of bits within src0 and src1 and returns 0 if equal template -constexpr T _bitcmp(T src0, T src1, T start0, T start1, T len) noexcept -{ - static_assert(binary_digits::value, ""); - return _bextr(src0, start0, len) == _bextr(src1, start1, len); +constexpr T _bitcmp(T src0, T src1, T start0, T start1, T len) noexcept { + static_assert(binary_digits::value, ""); + return _bextr(src0, start0, len) == _bextr(src1, start1, len); } // -------------------------------------------------------------------------- // - - // --- IMPLEMENTATION DETAILS: INSTRUCTIONS: DOUBLE PRECISION SHIFT LEFT ---- // // Left shifts dst by cnt bits, filling the lsbs of dst by the msbs of src template -constexpr T _shld(T dst, T src, T cnt) noexcept -{ - static_assert(binary_digits::value, ""); - constexpr T digits = binary_digits::value; - if (cnt < digits) { - dst = (dst << cnt) | (src >> (digits - cnt)); - } else { - dst = (src << (cnt - digits)) * (cnt < digits + digits); - } - return dst; +constexpr T _shld(T dst, T src, T cnt) noexcept { + static_assert(binary_digits::value, ""); + constexpr T digits = binary_digits::value; + if (cnt < digits) { + dst = (dst << cnt) | (lsr(src, (digits - cnt))); + } else { + dst = (src << (cnt - digits)) * (cnt < digits + digits); + } + return dst; } // -------------------------------------------------------------------------- // - - // --- IMPLEMENTATION DETAILS: INSTRUCTIONS: DOUBLE PRECISION SHIFT RIGHT --- // // Right shifts dst by cnt bits, filling the msbs of dst by the lsbs of src template -constexpr T _shrd(T dst, T src, T cnt) noexcept -{ - static_assert(binary_digits::value, ""); - constexpr T digits = binary_digits::value; - if (cnt < digits) { - dst = (dst >> cnt) | (src << (digits - cnt)); - } else { - dst = (src >> (cnt - digits)) * (cnt < digits + digits); - } - return dst; +constexpr T _shrd(T dst, T src, T cnt) noexcept { + static_assert(binary_digits::value, ""); + constexpr T digits = binary_digits::value; + if (cnt < digits) { + dst = (lsr(dst, cnt)) | (src << (digits - cnt)); + } else { + dst = (lsr(src, (cnt - digits))) * (cnt < digits + digits); + } + return dst; } // -------------------------------------------------------------------------- // - - // ------------ IMPLEMENTATION DETAILS: INSTRUCTIONS: ADD CARRY ------------- // // Adds src0 and src1 and returns the new carry bit with intrinsics template -constexpr C _addcarry(C carry, T src0, T src1, T* dst) noexcept -{ - static_assert(binary_digits::value, ""); - using wider_t = typename _wider_type::type; - constexpr T digits = binary_digits::value; - wider_t tmp = 0; - unsigned int udst = 0; - unsigned long long int ulldst = 0; - if (digits == std::numeric_limits::digits) { - carry = __builtin_ia32_addcarryx_u32(carry, src0, src1, &udst); - *dst = udst; - } else if (digits == std::numeric_limits::digits) { - carry = __builtin_ia32_addcarryx_u64(carry, src0, src1, &ulldst); - *dst = ulldst; - } else if (digits < binary_digits::value) { - tmp = static_cast(src0) + static_cast(src1); - tmp += static_cast(static_cast(carry)); - *dst = tmp; - carry = static_cast(tmp >> digits); - } else { - carry = _addcarry(carry, src0, src1, dst, std::ignore); - } - return carry; +constexpr C _addcarry(C carry, T src0, T src1, T* dst) noexcept { + static_assert(binary_digits::value, ""); + using wider_t = typename _wider_type::type; + constexpr T digits = binary_digits::value; + wider_t tmp = 0; + unsigned int udst = 0; + unsigned long long int ulldst = 0; + if (digits == std::numeric_limits::digits) { + carry = __builtin_ia32_addcarryx_u32(carry, src0, src1, &udst); + *dst = udst; + } else if (digits == std::numeric_limits::digits) { + carry = __builtin_ia32_addcarryx_u64(carry, src0, src1, &ulldst); + *dst = ulldst; + } else if (digits < binary_digits::value) { + tmp = static_cast(src0) + static_cast(src1); + tmp += static_cast(static_cast(carry)); + *dst = tmp; + carry = static_cast(tmp >> digits); + } else { + carry = _addcarry(carry, src0, src1, dst, std::ignore); + } + return carry; } // Adds src0 and src1 and returns the new carry bit without intrinsics template -constexpr C _addcarry(C carry, T src0, T src1, T* dst, X...) noexcept -{ - static_assert(binary_digits::value, ""); - *dst = src0 + src1 + static_cast(static_cast(carry)); - return carry ? *dst <= src0 || *dst <= src1 : *dst < src0 || *dst < src1; +constexpr C _addcarry(C carry, T src0, T src1, T* dst, X...) noexcept { + static_assert(binary_digits::value, ""); + *dst = src0 + src1 + static_cast(static_cast(carry)); + return carry ? *dst <= src0 || *dst <= src1 : *dst < src0 || *dst < src1; } // -------------------------------------------------------------------------- // - - // ------------ IMPLEMENTATION DETAILS: INSTRUCTIONS: SUB BORROW ------------ // // Subtracts src1 to src0 and returns the new borrow bit with intrinsics template -constexpr B _subborrow(B borrow, T src0, T src1, T* dst) noexcept -{ - static_assert(binary_digits::value, ""); - using wider_t = typename _wider_type::type; - constexpr T digits = binary_digits::value; - wider_t tmp = 0; - unsigned int udst = 0; - unsigned long long int ulldst = 0; - if (digits == std::numeric_limits::digits) { - borrow = __builtin_ia32_sbb_u32(borrow, src0, src1, &udst); - *dst = udst; - } else if (digits == std::numeric_limits::digits) { - borrow = __builtin_ia32_sbb_u64(borrow, src0, src1, &ulldst); - *dst = ulldst; - } else if (digits < binary_digits::value) { - tmp = static_cast(src1); - tmp += static_cast(static_cast(borrow)); - borrow = tmp > static_cast(src0); - *dst = static_cast(src0) - tmp; - } else { - borrow = _subborrow(borrow, src0, src1, dst, std::ignore); - } - return borrow; +constexpr B _subborrow(B borrow, T src0, T src1, T* dst) noexcept { + static_assert(binary_digits::value, ""); + using wider_t = typename _wider_type::type; + constexpr T digits = binary_digits::value; + wider_t tmp = 0; + unsigned int udst = 0; + unsigned long long int ulldst = 0; + if (digits == std::numeric_limits::digits) { + borrow = __builtin_ia32_sbb_u32(borrow, src0, src1, &udst); + *dst = udst; + } else if (digits == std::numeric_limits::digits) { + borrow = __builtin_ia32_sbb_u64(borrow, src0, src1, &ulldst); + *dst = ulldst; + } else if (digits < binary_digits::value) { + tmp = static_cast(src1); + tmp += static_cast(static_cast(borrow)); + borrow = tmp > static_cast(src0); + *dst = static_cast(src0) - tmp; + } else { + borrow = _subborrow(borrow, src0, src1, dst, std::ignore); + } + return borrow; } // Subtracts src1 to src0 and returns the new borrow bit with other intrinsics template -constexpr B _subborrow(const B& borrow, T src0, T src1, T* dst) noexcept -{ - static_assert(binary_digits::value, ""); - using wider_t = typename _wider_type::type; - constexpr T digits = binary_digits::value; - wider_t tmp = 0; - unsigned int udst = 0; - unsigned long long int ulldst = 0; - B flag = borrow; - if (digits == std::numeric_limits::digits) { - flag = __builtin_ia32_subborrow_u32(borrow, src0, src1, &udst); - *dst = udst; - } else if (digits == std::numeric_limits::digits) { - flag = __builtin_ia32_subborrow_u64(borrow, src0, src1, &ulldst); - *dst = ulldst; - } else if (digits < binary_digits::value) { - tmp = static_cast(src1); - tmp += static_cast(static_cast(borrow)); - flag = tmp > static_cast(src0); - *dst = static_cast(src0) - tmp; - } else { - flag = _subborrow(borrow, src0, src1, dst, std::ignore); - } - return flag; +constexpr B _subborrow(const B& borrow, T src0, T src1, T* dst) noexcept { + static_assert(binary_digits::value, ""); + using wider_t = typename _wider_type::type; + constexpr T digits = binary_digits::value; + wider_t tmp = 0; + unsigned int udst = 0; + unsigned long long int ulldst = 0; + B flag = borrow; + if (digits == std::numeric_limits::digits) { + flag = __builtin_ia32_subborrow_u32(borrow, src0, src1, &udst); + *dst = udst; + } else if (digits == std::numeric_limits::digits) { + flag = __builtin_ia32_subborrow_u64(borrow, src0, src1, &ulldst); + *dst = ulldst; + } else if (digits < binary_digits::value) { + tmp = static_cast(src1); + tmp += static_cast(static_cast(borrow)); + flag = tmp > static_cast(src0); + *dst = static_cast(src0) - tmp; + } else { + flag = _subborrow(borrow, src0, src1, dst, std::ignore); + } + return flag; } // Subtracts src1 to src0 and returns the new borrow bit without intrinsics template -constexpr B _subborrow(B borrow, T src0, T src1, T* dst, X...) noexcept -{ - static_assert(binary_digits::value, ""); - *dst = src0 - (src1 + static_cast(static_cast(borrow))); - return borrow ? src1 >= src0 : src1 > src0; +constexpr B _subborrow(B borrow, T src0, T src1, T* dst, X...) noexcept { + static_assert(binary_digits::value, ""); + *dst = src0 - (src1 + static_cast(static_cast(borrow))); + return borrow ? src1 >= src0 : src1 > src0; } // -------------------------------------------------------------------------- // - - // -------- IMPLEMENTATION DETAILS: INSTRUCTIONS: MULTIWORD MULTIPLY -------- // // Multiplies src0 and src1 and gets the full result with compiler intrinsics template @@ -1057,10 +1009,10 @@ constexpr T _mulx(T src0, T src1, T* hi, X...) noexcept constexpr T digits = binary_digits::value; constexpr T offset = digits / 2; constexpr T ones = ~static_cast(0); - const T lsbs0 = src0 & static_cast(ones >> (digits - offset)); - const T msbs0 = src0 >> offset; - const T lsbs1 = src1 & static_cast(ones >> (digits - offset)); - const T msbs1 = src1 >> offset; + const T lsbs0 = src0 & static_cast(lsr(ones, (digits - offset))); + const T msbs0 = lsr(src0, offset); + const T lsbs1 = src1 & static_cast(lsr(ones, (digits - offset))); + const T msbs1 = lsr(src1, offset); const T llsbs = lsbs0 * lsbs1; const T mlsbs = msbs0 * lsbs1; const T lmsbs = lsbs0 * msbs1; @@ -1068,7 +1020,7 @@ constexpr T _mulx(T src0, T src1, T* hi, X...) noexcept const T lo = llsbs + static_cast(mi << offset); const T lcarry = lo < llsbs || lo < static_cast(mi << offset); const T mcarry = static_cast(mi < mlsbs || mi < lmsbs) << offset; - *hi = static_cast(mi >> offset) + msbs0 * msbs1 + mcarry + lcarry; + *hi = static_cast(lsr(mi, offset)) + msbs0 * msbs1 + mcarry + lcarry; return lo; } // -------------------------------------------------------------------------- // diff --git a/include/bitlib/bit-iterator/bit_reference.hpp b/include/bitlib/bit-iterator/bit_reference.hpp index 59d7cbdc..ee4327af 100644 --- a/include/bitlib/bit-iterator/bit_reference.hpp +++ b/include/bitlib/bit-iterator/bit_reference.hpp @@ -182,7 +182,7 @@ constexpr bit_reference& bit_reference::assign(word_type val template constexpr bit_reference& bit_reference::assign(word_type val, size_type pos) const { assert(pos < binary_digits::value); - val >> pos & 1 ? set() : reset(); + ((val >> pos) & 1) ? set() : reset(); return const_cast&>(*this); } // -------------------------------------------------------------------------- // diff --git a/test/src/fixtures.hpp b/test/src/fixtures.hpp index b8cd5942..f5b672f1 100644 --- a/test/src/fixtures.hpp +++ b/test/src/fixtures.hpp @@ -1,14 +1,13 @@ // =============================== FIXTURES ================================= // // Project: The Experimental Bit Algorithms Library -// Description: Fixtures for testing -// Contributor(s): Bryce Kille +// Description: Fixtures for testing +// Contributor(s): Bryce Kille // License: BSD 3-Clause License // ========================================================================== // #ifndef _FIXTURES_HPP_INCLUDED #define _FIXTURES_HPP_INCLUDED // ========================================================================== // - // ============================== PREAMBLE ================================== // // C++ standard library #include @@ -32,51 +31,49 @@ //TODO tests need a lot of cleanup. We should only copy what we need from random_vec //and also refactor the vec generation to reduce duplication -using BaseTypes = ::testing::Types; - +using BaseTypes = ::testing::Types; -template +template class VectorTest : public testing::Test { - protected: - - using base_type = WordType; - using vec_type = bit::bit_vector; - vec_type empty_vec; - std::vector empty_vec_bool; - vec_type v2_ = vec_type(18); - vec_type v3_ = vec_type("010111111"); - - std::vector random_bitvecs; - std::vector> random_boolvecs; - std::vector random_vec; - const size_t word_size = 4; - const size_t digits = bit::binary_digits::value; - const size_t bit_size = word_size*digits; - - void SetUp() override { - empty_vec = vec_type(); - random_vec = get_random_vec(word_size); - for (size_t cont_size = 1; cont_size < bit_size; ++cont_size) { - auto bitvec = vec_type(bit_size); - std::memcpy(&(*bitvec.begin().base()), &(random_vec[0]), word_size); - bitvec.resize(cont_size); - - auto boolvec = boolvec_from_bitvec(bitvec); - random_bitvecs.push_back(bitvec); - random_boolvecs.push_back(boolvec); - } - size_t big_size = 64*64*10; - for (int i = -4; i < 4; ++i) { - size_t cont_size = big_size + i; - auto bitvec = vec_type(bit_size); - std::memcpy(&(*bitvec.begin().base()), &(random_vec[0]), word_size); - bitvec.resize(cont_size); - - auto boolvec = boolvec_from_bitvec(bitvec); - random_bitvecs.push_back(bitvec); - random_boolvecs.push_back(boolvec); - } + protected: + using base_type = WordType; + using vec_type = bit::bit_vector; + vec_type empty_vec; + std::vector empty_vec_bool; + vec_type v2_ = vec_type(18); + vec_type v3_ = vec_type("010111111"); + + std::vector random_bitvecs; + std::vector> random_boolvecs; + std::vector random_vec; + const size_t word_size = 4; + const size_t digits = bit::binary_digits::value; + const size_t bit_size = word_size * digits; + + void SetUp() override { + empty_vec = vec_type(); + random_vec = get_random_vec(word_size); + for (size_t cont_size = 1; cont_size < bit_size; ++cont_size) { + auto bitvec = vec_type(bit_size); + std::memcpy(&(*bitvec.begin().base()), &(random_vec[0]), word_size); + bitvec.resize(cont_size); + + auto boolvec = boolvec_from_bitvec(bitvec); + random_bitvecs.push_back(bitvec); + random_boolvecs.push_back(boolvec); + } + size_t big_size = 64 * 64 * 10; + for (int i = -4; i < 4; ++i) { + size_t cont_size = big_size + i; + auto bitvec = vec_type(bit_size); + std::memcpy(&(*bitvec.begin().base()), &(random_vec[0]), word_size); + bitvec.resize(cont_size); + + auto boolvec = boolvec_from_bitvec(bitvec); + random_bitvecs.push_back(bitvec); + random_boolvecs.push_back(boolvec); } + } }; TYPED_TEST_SUITE(VectorTest, BaseTypes);