-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add LatchingBitSet and some better diagnostics. (#141)
* Add LatchingBitSet. * Use BATT_CHECK_OK for done_status (ioring_log_driver.ipp). * Update docker image for build pipelines.
- Loading branch information
1 parent
514519d
commit 2a19883
Showing
5 changed files
with
385 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,164 @@ | ||
//#=##=##=#==#=#==#===#+==#+==========+==+=+=+=+=+=++=+++=+++++=-++++=-+++++++++++ | ||
// | ||
// Part of the LLFS Project, under Apache License v2.0. | ||
// See https://www.apache.org/licenses/LICENSE-2.0 for license information. | ||
// SPDX short identifier: Apache-2.0 | ||
// | ||
//+++++++++++-+-+--+----- --- -- - - - - | ||
|
||
#include <llfs/latching_bit_set.hpp> | ||
// | ||
|
||
namespace llfs { | ||
|
||
//==#==========+==+=+=++=+++++++++++-+-+--+----- --- -- - - - - | ||
// | ||
/*explicit*/ LatchingBitSet::LatchingBitSet(usize n) noexcept : upper_bound_{n} | ||
{ | ||
usize total_size = 0; | ||
usize level_size = this->upper_bound_; | ||
|
||
this->start_of_level_.emplace_back(0); | ||
|
||
batt::SmallVec<u64, 12> last_word; | ||
|
||
for (;;) { | ||
const usize leftover_bits = level_size % 64; | ||
if (leftover_bits == 0) { | ||
last_word.emplace_back(0); | ||
} else { | ||
last_word.emplace_back(~((u64{1} << leftover_bits) - 1)); | ||
} | ||
|
||
level_size = (level_size + 63) / 64; | ||
total_size += level_size; | ||
if (level_size <= 1) { | ||
break; | ||
} | ||
this->start_of_level_.emplace_back(this->start_of_level_.back() + level_size); | ||
} | ||
|
||
if (total_size > 0) { | ||
this->data_.reset(new u64[total_size]); | ||
std::memset(this->data_.get(), 0, sizeof(u64) * total_size); | ||
|
||
// Set any leftover bits at the end of the last word in each level to 1, to simplify the code | ||
// to check for a block containing all 1's. | ||
// | ||
for (usize i = 0; i < this->start_of_level_.size() - 1; ++i) { | ||
const usize last_word_offset = this->start_of_level_[i + 1] - 1; | ||
this->data_.get()[last_word_offset] = last_word[i]; | ||
} | ||
this->data_.get()[this->start_of_level_.back()] = last_word.back(); | ||
} | ||
} | ||
|
||
//==#==========+==+=+=++=+++++++++++-+-+--+----- --- -- - - - - | ||
// | ||
usize LatchingBitSet::upper_bound() const noexcept | ||
{ | ||
return this->upper_bound_; | ||
} | ||
|
||
//==#==========+==+=+=++=+++++++++++-+-+--+----- --- -- - - - - | ||
// | ||
usize LatchingBitSet::first_missing() const noexcept | ||
{ | ||
if (this->is_full()) { | ||
return this->upper_bound(); | ||
} | ||
|
||
usize depth = this->start_of_level_.size(); | ||
usize lower_bound = 0; | ||
while (depth > 0) { | ||
--depth; | ||
|
||
const usize index = this->start_of_level_[depth] + lower_bound; | ||
|
||
// Even if the set wasn't full | ||
// | ||
if (depth + 1 < this->start_of_level_.size() && index >= this->start_of_level_[depth + 1]) { | ||
return this->upper_bound(); | ||
} | ||
|
||
const u64 value = this->data()[index].load(); | ||
i32 first_zero_bit = [&] { | ||
if (value == 0) { | ||
return 0; | ||
} | ||
return __builtin_ctzll(~value); | ||
}(); | ||
|
||
lower_bound = lower_bound * 64 + first_zero_bit; | ||
} | ||
|
||
return lower_bound; | ||
} | ||
|
||
//==#==========+==+=+=++=+++++++++++-+-+--+----- --- -- - - - - | ||
// | ||
bool LatchingBitSet::contains(usize i) noexcept | ||
{ | ||
BATT_CHECK_LT(i, this->upper_bound()); | ||
|
||
const usize word_i = i / 64; | ||
const usize bit_i = i % 64; | ||
const u64 mask = u64{1} << bit_i; | ||
|
||
return (this->data()[word_i].load() & mask) != 0; | ||
} | ||
|
||
//==#==========+==+=+=++=+++++++++++-+-+--+----- --- -- - - - - | ||
// | ||
bool LatchingBitSet::insert(usize i) noexcept | ||
{ | ||
BATT_CHECK_LT(i, this->upper_bound()); | ||
|
||
bool changed = false; | ||
|
||
usize depth = 0; | ||
|
||
for (;;) { | ||
const usize word_i = i / 64; | ||
const usize bit_i = i % 64; | ||
const u64 mask = u64{1} << bit_i; | ||
|
||
const u64 old_value = this->data()[word_i + this->start_of_level_[depth]].fetch_or(mask); | ||
const u64 new_value = old_value | mask; | ||
|
||
changed = changed || (old_value != new_value); | ||
|
||
if (!changed || batt::bit_count(new_value) < 64) { | ||
break; | ||
} | ||
|
||
++depth; | ||
if (depth == this->start_of_level_.size()) { | ||
break; | ||
} | ||
i = word_i; | ||
} | ||
|
||
return changed; | ||
} | ||
|
||
//==#==========+==+=+=++=+++++++++++-+-+--+----- --- -- - - - - | ||
// | ||
bool LatchingBitSet::is_full() const noexcept | ||
{ | ||
if (this->upper_bound() == 0u) { | ||
return true; | ||
} | ||
return this->data()[this->start_of_level_.back()].load() == ~u64{0}; | ||
} | ||
|
||
//==#==========+==+=+=++=+++++++++++-+-+--+----- --- -- - - - - | ||
// | ||
std::atomic<u64>* LatchingBitSet::data() const noexcept | ||
{ | ||
static_assert(sizeof(std::atomic<u64>) == sizeof(u64)); | ||
|
||
return reinterpret_cast<std::atomic<u64>*>(this->data_.get()); | ||
} | ||
|
||
} //namespace llfs |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,89 @@ | ||
//#=##=##=#==#=#==#===#+==#+==========+==+=+=+=+=+=++=+++=+++++=-++++=-+++++++++++ | ||
// | ||
// Part of the LLFS Project, under Apache License v2.0. | ||
// See https://www.apache.org/licenses/LICENSE-2.0 for license information. | ||
// SPDX short identifier: Apache-2.0 | ||
// | ||
//+++++++++++-+-+--+----- --- -- - - - - | ||
|
||
#pragma once | ||
#ifndef LLFS_LATCHING_BIT_SET_HPP | ||
#define LLFS_LATCHING_BIT_SET_HPP | ||
|
||
#include <llfs/config.hpp> | ||
// | ||
#include <llfs/int_types.hpp> | ||
|
||
#include <batteries/math.hpp> | ||
#include <batteries/small_vec.hpp> | ||
|
||
namespace llfs { | ||
|
||
/** \brief A lock-free concurrent set of integers with a fixed upper bound, which supports insertion | ||
* but not erasure of items. | ||
*/ | ||
class LatchingBitSet | ||
{ | ||
public: | ||
/** \brief Creates a new set with space for `n_bits` bits, allowing integers from 0 to n_bits-1 to | ||
* be inserted. | ||
*/ | ||
explicit LatchingBitSet(usize n_bits) noexcept; | ||
|
||
//+++++++++++-+-+--+----- --- -- - - - - | ||
|
||
/** \brief Returns one past the maximum integer that can be inserted into the set. | ||
* | ||
* Note: this is a property of the container that is fixed at construction time; it does not | ||
* report the current actual largest value in the set. | ||
*/ | ||
usize upper_bound() const noexcept; | ||
|
||
/** \brief Returns the index of the first unset (0) bit. | ||
*/ | ||
usize first_missing() const noexcept; | ||
|
||
/** \brief Returns true iff the set contains the given integer. | ||
* | ||
* \param i The integer to test for; MUST be less than this->upper_bound(), or we PANIC. | ||
*/ | ||
bool contains(usize i) noexcept; | ||
|
||
/** \brief Inserts the passed integer `i`, returning true iff it was not previously contained by | ||
* the set. | ||
*/ | ||
bool insert(usize i) noexcept; | ||
|
||
/** \brief Returns true iff all integers from 0 to this->upper_bound() - 1 (inclusive) are present | ||
* in the set. | ||
*/ | ||
bool is_full() const noexcept; | ||
|
||
//+++++++++++-+-+--+----- --- -- - - - - | ||
private: | ||
/** \brief Returns the backing storage memory as an array of std::atomic<u64>. | ||
*/ | ||
std::atomic<u64>* data() const noexcept; | ||
|
||
//+++++++++++-+-+--+----- --- -- - - - - | ||
|
||
/** \brief The number of bits stored in this set. | ||
*/ | ||
usize upper_bound_; | ||
|
||
/** \brief Precomputed/cached starts of the levels of the data structure, in index offsets into | ||
* this->data_; level 0 is the flat array of all per-integer bits, the next level is a 1/64-sized | ||
* summary of those (where a 1 bit means all 64 corresponding bits from the previous level are | ||
* also 1; 0 otherwise); the last level is always a single u64. | ||
*/ | ||
batt::SmallVec<usize, 12> start_of_level_; | ||
|
||
/** \brief The backing memory for the bit set. This is allocated as u64 and then cast to | ||
* std::atomic<u64> so we can speed up initialization by just memset-ing the array to all zeros. | ||
*/ | ||
std::unique_ptr<u64[]> data_; | ||
}; | ||
|
||
} //namespace llfs | ||
|
||
#endif // LLFS_LATCHING_BIT_SET_HPP |
Oops, something went wrong.