blob: 171c0cea4c403e1c242182cde5a57b96395e0a22 [file] [log] [blame]
// Copyright 2023 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.
#ifndef THIRD_PARTY_CENTIPEDE_CONCURRENT_BYTESET_H_
#define THIRD_PARTY_CENTIPEDE_CONCURRENT_BYTESET_H_
#include <climits>
#include <cstddef>
#include <cstdint>
#include <functional>
// 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 "absl/base/const_init.h"
namespace centipede {
// TODO(kcc): replace the standalone ForEachNonZeroByte with code from here.
// TODO(kcc): ConcurrentByteSet is an unoptimized single-layer byte set.
// Implement multi-layer byte set(s).
// A fixed-size byte set containing kSize bytes, kSize must be a multiple of 64.
// Set() can be called concurrently with another Set(), other uses should be
// synchronized externally.
// Intended usage is to call ForEachNonZeroByte() from one thread.
// Should only be constructed with static storage duration.
template <size_t kSize>
class ConcurrentByteSet {
public:
static constexpr size_t kSizeInBytes = kSize;
// kSize must be multiple of this.
static constexpr size_t kSizeMultiple = 64;
static_assert((kSize % kSizeMultiple) == 0);
// Creates a ConcurrentByteSet with static storage duration.
explicit constexpr ConcurrentByteSet(absl::ConstInitType) {}
// Clears the set.
void clear() { memset(bytes_, 0, sizeof(bytes_)); }
// Sets element `idx` to `value`. `idx` must be <= kSize.
// Can be called concurrently.
void Set(size_t idx, uint8_t value) {
if (idx >= kSize) __builtin_trap();
__atomic_store_n(&bytes_[idx], value, __ATOMIC_RELAXED);
}
// Performs a saturated increment of element `idx`.
void SaturatedIncrement(size_t idx) {
if (idx >= kSize) __builtin_trap();
uint8_t counter = __atomic_load_n(&bytes_[idx], __ATOMIC_RELAXED);
if (counter != 255)
__atomic_store_n(&bytes_[idx], counter + 1, __ATOMIC_RELAXED);
}
// Calls `action(index, value)` for every {index,value} of a non-zero byte in
// the set, then sets all those bytes to zero.
// `from` and `to` set the range of elements to iterate, both must be
// multiples of kSizeMultiple.
void ForEachNonZeroByte(const std::function<void(size_t, uint8_t)> &action,
size_t from = 0, size_t to = kSize) {
using word_t = uintptr_t;
constexpr size_t kWordSize = sizeof(word_t);
if (from % kSizeMultiple) __builtin_trap();
if (to % kSizeMultiple) __builtin_trap();
if (to > kSize) __builtin_trap();
// Iterate one word at a time.
for (uint8_t *ptr = &bytes_[from], *end = &bytes_[to]; ptr < end;
ptr += kWordSize) {
word_t word;
__builtin_memcpy(&word, ptr, kWordSize);
if (!word) continue;
__builtin_memset(ptr, 0, kWordSize);
// This loop assumes little-endianness. (Tests will break on big-endian).
for (size_t pos = 0; pos < kWordSize; pos++) {
uint8_t value = word >> (pos * CHAR_BIT); // lowest byte is taken.
if (value) action(ptr - &bytes_[0] + pos, value);
}
}
}
private:
uint8_t bytes_[kSize] __attribute__((aligned(64))); // No initializer.
};
// Similar to ConcurrentByteSet, but consists of two layers, upper and lower.
// The size of the lower layer is a multiple of the size of the upper layer.
// Set() writes 1 to an element in the upper layer and then writes `value` to an
// element of the lower value. This allows ForEachNonZeroByte() to
// skip sub-regions of lower layer that were not written to. Otherwise, the
// interface and the behaviour is equivalent to ConcurrentByteSet.
template <size_t kSize, typename Upper,
typename Lower = ConcurrentByteSet<kSize>>
class LayeredConcurrentByteSet {
public:
static constexpr size_t kSizeInBytes = kSize;
static constexpr size_t kSizeMultiple =
Lower::kSizeMultiple * Upper::kSizeMultiple;
static_assert(kSize == Lower::kSizeInBytes);
LayeredConcurrentByteSet() = default;
// Creates a LayeredConcurrentByteSet with static storage duration.
explicit constexpr LayeredConcurrentByteSet(absl::ConstInitType)
: upper_layer_(absl::kConstInit), lower_layer_(absl::kConstInit) {}
void clear() {
upper_layer_.clear();
lower_layer_.clear();
}
void Set(size_t idx, uint8_t value) {
if (idx >= kSize) __builtin_trap();
upper_layer_.Set(idx / kLayerRatio, 1);
lower_layer_.Set(idx, value);
}
void SaturatedIncrement(size_t idx) {
if (idx >= kSize) __builtin_trap();
upper_layer_.Set(idx / kLayerRatio, 1);
lower_layer_.SaturatedIncrement(idx);
}
void ForEachNonZeroByte(const std::function<void(size_t, uint8_t)> &action,
size_t from = 0, size_t to = kSize) {
if (to > kSize) __builtin_trap();
if (from % kSizeMultiple) __builtin_trap();
if (to % kSizeMultiple) __builtin_trap();
size_t upper_from = from / kLayerRatio;
size_t upper_to = to / kLayerRatio;
upper_layer_.ForEachNonZeroByte(
[&](size_t idx, uint8_t value) {
size_t lower_from = idx * kLayerRatio;
size_t lower_to = lower_from + kLayerRatio;
lower_layer_.ForEachNonZeroByte(action, lower_from, lower_to);
},
upper_from, upper_to);
}
private:
Upper upper_layer_;
Lower lower_layer_;
static constexpr size_t kLayerRatio =
Lower::kSizeInBytes / Upper::kSizeInBytes;
static_assert((Lower::kSizeInBytes % Upper::kSizeInBytes) == 0);
};
// Two-layer ConcurrentByteSet() with upper layer 64x smaller than the lower.
template <size_t kSize>
class TwoLayerConcurrentByteSet
: public LayeredConcurrentByteSet<kSize, ConcurrentByteSet<kSize / 64>> {
public:
// Creates a TwoLayerConcurrentByteSet with static storage duration.
explicit constexpr TwoLayerConcurrentByteSet(absl::ConstInitType)
: LayeredConcurrentByteSet<kSize, ConcurrentByteSet<kSize / 64>>(
absl::kConstInit) {}
};
} // namespace centipede
#endif // THIRD_PARTY_CENTIPEDE_CONCURRENT_BYTESET_H_