// Copyright 2022 The Centipede Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // This library defines the concepts "fuzzing feature" and "feature domain". // It is used by Centipede, and it can be used by fuzz runners to // define their features in a way most friendly to Centipede. // Fuzz runners do not have to use this file nor to obey the rules defined here. // But using this file and following its rules is the simplest way if you want // Centipede to understand the details about the features generated by the // runner. // // This library must not depend on anything other than libc so that fuzz targets // using it doesn't gain redundant coverage. For the same reason this library // uses raw __builtin_trap instead of CHECKs. // We make an exception for for std::sort/std::unique, // since is very lightweight. // This library is also header-only, with all functions defined as inline. #ifndef THIRD_PARTY_CENTIPEDE_CONCURRENT_BITSET_H_ #define THIRD_PARTY_CENTIPEDE_CONCURRENT_BITSET_H_ #include #include // WARNING!!!: Be very careful with what STL headers or other dependencies you // add here. This header needs to remain mostly bare-bones so that we can // include it into runner. #include #include #include #include "absl/base/const_init.h" #include "./centipede/concurrent_byteset.h" namespace fuzztest::internal { // A fixed-size bitset with a lossy concurrent set() function. // kSize (in bits) must be a multiple of 2**16. // // IMPORTANT!!! Objects of this class should only be constructed with static // storage duration. This is because the class has intentionally uninitialized // direct and transitive data members that rely on static initialization in the // compiled process image. template class ConcurrentBitSet { public: static_assert((kSizeInBits % (1<<16)) == 0); // Creates a ConcurrentBitSet with static storage duration. explicit constexpr ConcurrentBitSet(absl::ConstInitType) : lines_{absl::kConstInit} {} // Clears the bit set. void clear() { memset(words_, 0, sizeof(words_)); lines_.clear(); } // Sets the bit `idx % kSizeInBits`. // set() can be called concurrently with another set(). // If several threads race to update adjacent bits, // the update may be lost (i.e. set() is lossy). // We could use atomic set-bit instructions to make it non-lossy, // but it is going to be too expensive. void set(size_t idx) { idx %= kSizeInBits; size_t word_idx = idx / kBitsInWord; size_t bit_idx = idx % kBitsInWord; size_t line_idx = word_idx / kWordsInLine; lines_.Set(line_idx, 1); word_t mask = 1ULL << bit_idx; word_t word = __atomic_load_n(&words_[word_idx], __ATOMIC_RELAXED); if (!(word & mask)) { word |= mask; __atomic_store_n(&words_[word_idx], word, __ATOMIC_RELAXED); } } // Gets the bit at `idx % kSizeInBits`. uint8_t get(size_t idx) { idx %= kSizeInBits; size_t word_idx = idx / kBitsInWord; size_t bit_idx = idx % kBitsInWord; word_t word = __atomic_load_n(&words_[word_idx], __ATOMIC_RELAXED); word_t mask = 1ULL << bit_idx; return (word & mask) != 0; } // Calls `action(index)` for every index of a non-zero bit in the set, // then sets all those bits to zero. __attribute__((noinline)) void ForEachNonZeroBit( const std::function &action) { // Iterates over all non-empty lines. lines_.ForEachNonZeroByte([&](size_t idx, uint8_t value) { size_t word_idx_beg = idx * kWordsInLine; size_t word_idx_end = word_idx_beg + kWordsInLine; ForEachNonZeroBit(action, word_idx_beg, word_idx_end); }); } private: // Iterates over the range of words [`word_idx_beg`, `word_idx_end`). void ForEachNonZeroBit(const std::function &action, size_t word_idx_beg, size_t word_idx_end) { for (size_t word_idx = word_idx_beg; word_idx < word_idx_end; ++word_idx) { if (word_t word = words_[word_idx]) { words_[word_idx] = 0; do { size_t bit_idx = __builtin_ctzll(word); action(word_idx * kBitsInWord + bit_idx); word_t mask = 1ULL << bit_idx; word &= ~mask; } while (word); } } } // A word is the largest integer type convenient for bitwise operations. using word_t = uintptr_t; static constexpr size_t kBytesInWord = sizeof(word_t); static constexpr size_t kBitsInWord = CHAR_BIT * kBytesInWord; static constexpr size_t kSizeInWords = kSizeInBits / kBitsInWord; // All words are logically split into lines. // When `set()` is called, we set the corresponding element of `lines_` to 1, // so that we now know that at least 1 bit in that line is set. Then, in // `ForEachNonZeroBit()`, we iterate only those lines that have non-zero bits. static constexpr size_t kBytesInLine = 64 * 8; static constexpr size_t kWordsInLine = kBytesInLine / kBytesInWord; static constexpr size_t kSizeInLines = kSizeInWords / kWordsInLine; ConcurrentByteSet lines_; // NOTE: No initializer for performance (`kSizeInWords` can be quite large). // Relies on static initialization in the process image (see the class // comment). word_t words_[kSizeInWords]; }; } // namespace fuzztest::internal #endif // THIRD_PARTY_CENTIPEDE_CONCURRENT_BITSET_H_