about summary refs log tree commit diff homepage
path: root/include
diff options
context:
space:
mode:
authorDaniel Schemmel <daniel@schemmel.net>2022-10-13 14:22:52 +0100
committerFrank Busse <f.busse@imperial.ac.uk>2023-03-16 11:57:59 +0000
commit5b49bd5999aabf51016a34acaefe905c313185c1 (patch)
tree554049ef578652424847071f03167bc7c0505c24 /include
parent6bc91e58dc9d286cbb50c9e48057c8037229023b (diff)
downloadklee-5b49bd5999aabf51016a34acaefe905c313185c1.tar.gz
The KDAlloc slot allocator is useful for small sized allocations
Diffstat (limited to 'include')
-rw-r--r--include/klee/ADT/Bits.h95
-rw-r--r--include/klee/KDAlloc/define.h17
-rw-r--r--include/klee/KDAlloc/location_info.h71
-rw-r--r--include/klee/KDAlloc/suballocators/slot_allocator.h538
-rw-r--r--include/klee/KDAlloc/tagged_logger.h43
5 files changed, 755 insertions, 9 deletions
diff --git a/include/klee/ADT/Bits.h b/include/klee/ADT/Bits.h
index 5f64e244..ba25f94f 100644
--- a/include/klee/ADT/Bits.h
+++ b/include/klee/ADT/Bits.h
@@ -11,8 +11,14 @@
 #define KLEE_BITS_H
 
 #include "klee/Config/Version.h"
+
 #include "llvm/Support/DataTypes.h"
-#include <assert.h>
+
+#include <cassert>
+#include <cstddef>
+#include <cstdint>
+#include <limits>
+#include <type_traits>
 
 namespace klee {
   namespace bits32 {
@@ -57,10 +63,6 @@ namespace klee {
       assert(res < 32);
       assert((UINT32_C(1) << res) == x);
       return res;
-    } 
-
-    inline unsigned indexOfRightmostBit(unsigned x) {
-      return indexOfSingleBit(isolateRightmostBit(x));
     }
   }
 
@@ -103,12 +105,87 @@ namespace klee {
       assert(res < 64);
       assert((UINT64_C(1) << res) == x);
       return res;
-    } 
-
-    inline uint64_t indexOfRightmostBit(uint64_t x) {
-      return indexOfSingleBit(isolateRightmostBit(x));
     }
   }
+
+  template <typename T>
+  [[nodiscard]] static constexpr inline auto countLeadingZeroes(T &&x) noexcept
+      -> std::enable_if_t<!std::numeric_limits<std::decay_t<T>>::is_signed &&
+                              std::numeric_limits<std::decay_t<T>>::digits ==
+                                  std::numeric_limits<unsigned>::digits,
+                          int> {
+      assert(x > 0);
+      return __builtin_clz(static_cast<unsigned>(x));
+  }
+
+  template <typename T>
+  [[nodiscard]] static constexpr inline auto countLeadingZeroes(T &&x) noexcept
+      -> std::enable_if_t<!std::numeric_limits<std::decay_t<T>>::is_signed &&
+                              std::numeric_limits<std::decay_t<T>>::digits ==
+                                  std::numeric_limits<unsigned long>::digits &&
+                              std::numeric_limits<unsigned>::digits !=
+                                  std::numeric_limits<unsigned long>::digits,
+                          int> {
+      assert(x > 0);
+      return __builtin_clzl(static_cast<unsigned long>(x));
+  }
+
+  template <typename T>
+  [[nodiscard]] static constexpr inline auto countLeadingZeroes(T &&x) noexcept
+      -> std::enable_if_t<
+          !std::numeric_limits<std::decay_t<T>>::is_signed &&
+              std::numeric_limits<std::decay_t<T>>::digits ==
+                  std::numeric_limits<unsigned long long>::digits &&
+              std::numeric_limits<unsigned>::digits !=
+                  std::numeric_limits<unsigned long long>::digits &&
+              std::numeric_limits<unsigned long>::digits !=
+                  std::numeric_limits<unsigned long long>::digits,
+          int> {
+      assert(x > 0);
+      return __builtin_clzll(static_cast<unsigned long long>(x));
+  }
+
+  template <typename T>
+  [[nodiscard]] static constexpr inline auto countTrailingZeroes(T &&x) noexcept
+      -> std::enable_if_t<!std::numeric_limits<std::decay_t<T>>::is_signed &&
+                              std::numeric_limits<std::decay_t<T>>::digits ==
+                                  std::numeric_limits<unsigned>::digits,
+                          int> {
+      assert(x > 0);
+      return __builtin_ctz(static_cast<unsigned>(x));
+  }
+
+  template <typename T>
+  [[nodiscard]] static constexpr inline auto countTrailingZeroes(T &&x) noexcept
+      -> std::enable_if_t<!std::numeric_limits<std::decay_t<T>>::is_signed &&
+                              std::numeric_limits<std::decay_t<T>>::digits ==
+                                  std::numeric_limits<unsigned long>::digits &&
+                              std::numeric_limits<unsigned>::digits !=
+                                  std::numeric_limits<unsigned long>::digits,
+                          int> {
+      assert(x > 0);
+      return __builtin_ctzl(static_cast<unsigned long>(x));
+  }
+
+  template <typename T>
+  [[nodiscard]] static constexpr inline auto countTrailingZeroes(T &&x) noexcept
+      -> std::enable_if_t<
+          !std::numeric_limits<std::decay_t<T>>::is_signed &&
+              std::numeric_limits<std::decay_t<T>>::digits ==
+                  std::numeric_limits<unsigned long long>::digits &&
+              std::numeric_limits<unsigned>::digits !=
+                  std::numeric_limits<unsigned long long>::digits &&
+              std::numeric_limits<unsigned long>::digits !=
+                  std::numeric_limits<unsigned long long>::digits,
+          int> {
+      assert(x > 0);
+      return __builtin_ctzll(static_cast<unsigned long long>(x));
+  }
+
+  [[nodiscard]] static constexpr inline std::size_t
+  roundUpToMultipleOf4096(std::size_t const x) {
+      return ((x - 1) | static_cast<std::size_t>(4096 - 1)) + 1;
+  }
 } // End klee namespace
 
 #endif /* KLEE_BITS_H */
diff --git a/include/klee/KDAlloc/define.h b/include/klee/KDAlloc/define.h
new file mode 100644
index 00000000..b20bf13e
--- /dev/null
+++ b/include/klee/KDAlloc/define.h
@@ -0,0 +1,17 @@
+//===-- define.h ------------------------------------------------*- C++ -*-===//
+//
+//                     The KLEE Symbolic Virtual Machine
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef KDALLOC_DEFINE_H
+#define KDALLOC_DEFINE_H
+
+#ifndef KDALLOC_TRACE
+#define KDALLOC_TRACE 0
+#endif
+
+#endif
diff --git a/include/klee/KDAlloc/location_info.h b/include/klee/KDAlloc/location_info.h
new file mode 100644
index 00000000..d9852e7e
--- /dev/null
+++ b/include/klee/KDAlloc/location_info.h
@@ -0,0 +1,71 @@
+//===-- location_info.h -----------------------------------------*- C++ -*-===//
+//
+//                     The KLEE Symbolic Virtual Machine
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef KDALLOC_LOCATION_INFO_H
+#define KDALLOC_LOCATION_INFO_H
+
+#include <cassert>
+
+namespace klee::kdalloc {
+class LocationInfo {
+public:
+  enum Enum {
+    /// refers to the null page
+    LI_NullPage,
+    /// location is not inside the mapping (but not on the null page)
+    LI_NonNullOutsideMapping,
+    /// location spans suballocator mapping boundary
+    LI_SpansSuballocators,
+    /// area spans object alignment boundary
+    LI_Unaligned,
+    /// location always refers to a redzone
+    LI_AlwaysRedzone,
+    /// location currently refers to a redzone
+    LI_CurrentRedzone,
+    /// location is inside an object that is either currently allocated or in
+    /// quarantine
+    LI_AllocatedOrQuarantined,
+    /// location is potentially valid, but not currently allocated
+    LI_Unallocated,
+  };
+
+private:
+  Enum value;
+  void *address;
+
+public:
+  constexpr LocationInfo(Enum value, void *address = nullptr) noexcept
+      : value(value), address(address) {}
+  constexpr operator Enum() noexcept { return value; }
+
+  /// location is (partially) outside the mapping
+  constexpr bool isOutsideMapping() const noexcept {
+    return value != Enum::LI_NullPage &&
+           value != Enum::LI_NonNullOutsideMapping;
+  }
+
+  /// location is potentially valid
+  constexpr bool isValid() const noexcept {
+    return value == Enum::LI_AllocatedOrQuarantined ||
+           value == Enum::LI_Unallocated || value == Enum::LI_CurrentRedzone;
+  }
+
+  /// location (partially) refers to a redzone
+  constexpr bool isRedzone() const noexcept {
+    return value == Enum::LI_AlwaysRedzone || value == Enum::LI_CurrentRedzone;
+  }
+
+  constexpr void *getBaseAddress() const noexcept {
+    assert(value == Enum::LI_AllocatedOrQuarantined);
+    return address;
+  }
+};
+} // namespace klee::kdalloc
+
+#endif
diff --git a/include/klee/KDAlloc/suballocators/slot_allocator.h b/include/klee/KDAlloc/suballocators/slot_allocator.h
new file mode 100644
index 00000000..be8393d7
--- /dev/null
+++ b/include/klee/KDAlloc/suballocators/slot_allocator.h
@@ -0,0 +1,538 @@
+//===-- slot_allocator.h ----------------------------------------*- C++ -*-===//
+//
+//                     The KLEE Symbolic Virtual Machine
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef KDALLOC_SUBALLOCATORS_SLOT_ALLOCATOR_H
+#define KDALLOC_SUBALLOCATORS_SLOT_ALLOCATOR_H
+
+#include "../define.h"
+#include "../location_info.h"
+#include "../tagged_logger.h"
+
+#include "klee/ADT/Bits.h"
+
+#include <cassert>
+#include <cstddef>
+#include <cstring>
+#include <limits>
+#include <ostream>
+#include <type_traits>
+
+namespace klee::kdalloc::suballocators {
+class SlotAllocator final : public TaggedLogger<SlotAllocator> {
+  static_assert(static_cast<std::size_t>(-1) == ~static_cast<std::size_t>(0),
+                "-1 must be ~0 for size_t");
+  struct Data final {
+    /// The reference count.
+    std::size_t referenceCount; // initial value is 1 as soon as this member
+                                // is actually allocated
+
+    /// The number of allocated words. Always non-negative.
+    std::ptrdiff_t capacity; // initial value is 0 (changes as soon as this
+                             // member is actually allocated)
+
+    /// Always less than or equal to the first word that contains a one bit.
+    /// Less than or equal to _capacity. Always non-negative.
+    std::ptrdiff_t firstFreeFinger; // initial value is 0
+
+    /// Always greater than or equal to the last word that contains a zero bit.
+    /// Less than _capacity. May be negative (exactly -1).
+    std::ptrdiff_t lastUsedFinger; // initial value is -1
+
+    /// position in the quarantine, followed by the quarantine ring buffer,
+    /// followed by the bitmap
+    std::size_t quarantineAndBitmap[];
+  };
+
+  static_assert(std::is_pod<Data>::value, "Data must be POD");
+
+public:
+  class Control final : public TaggedLogger<Control> {
+    friend class SlotAllocator;
+
+    /// pointer to the start of the range managed by this allocator
+    char *baseAddress = nullptr;
+
+    /// size in bytes of the range managed by this allocator
+    std::size_t size = 0;
+
+    /// size in bytes of the slots that are managed in this slot allocator
+    std::size_t slotSize = 0;
+
+    /// number of bytes before the start of the bitmap (includes ordinary
+    /// members and quarantine)
+    std::size_t prefixSize = -1;
+
+    /// quarantine size *including* the position (=> is never 1)
+    std::uint32_t quarantineSize = 0;
+
+    /// true iff the quarantine is unlimited (=> _qurantine_size == 0)
+    bool unlimitedQuarantine = false;
+
+    [[nodiscard]] inline std::size_t
+    convertIndexToPosition(std::size_t index) const noexcept {
+      index += 1;
+      int const layer =
+          std::numeric_limits<std::size_t>::digits - countLeadingZeroes(index);
+      auto const layerSlots = static_cast<std::size_t>(1) << (layer - 1);
+      auto const currentSlotSize = (size >> layer);
+      assert(currentSlotSize > slotSize && "Zero (or below) red zone size!");
+
+      auto const highBit = static_cast<std::size_t>(1) << (layer - 1);
+      assert((index & highBit) != 0 && "Failed to compute high bit");
+      assert((index ^ highBit) < highBit && "Failed to compute high bit");
+
+      auto layerIndex = index ^ highBit;
+      if (layerIndex % 2 == 0) {
+        layerIndex /= 2; // drop trailing 0
+      } else {
+        layerIndex /= 2; // drop trailing 1
+        layerIndex = layerSlots - 1 - layerIndex;
+      }
+      assert(layerIndex < highBit && "Invalid tempering");
+      auto const pos = layerIndex * 2 + 1;
+      return currentSlotSize * pos;
+    }
+
+    [[nodiscard]] inline std::size_t
+    convertPositionToIndex(std::size_t const Position) const noexcept {
+      int const trailingZeroes = countTrailingZeroes(Position);
+      auto const layer = countTrailingZeroes(size) - trailingZeroes;
+      auto const layerSlots = static_cast<std::size_t>(1) << (layer - 1);
+
+      auto const highBit = static_cast<std::size_t>(1) << (layer - 1);
+      auto layerIndex = Position >> (trailingZeroes + 1);
+      assert(layerIndex < highBit &&
+             "Tempered value was not restored correctly");
+
+      if (layerIndex < (layerSlots + 1) / 2) {
+        layerIndex *= 2; // add trailing 0
+      } else {
+        layerIndex = layerSlots - 1 - layerIndex;
+        layerIndex = layerIndex * 2 + 1; // add trailing 1
+      }
+      assert(layerIndex < highBit && "Invalid reverse tempering");
+
+      auto const index = highBit ^ layerIndex;
+      return index - 1;
+    }
+
+  public:
+    Control() = default;
+    Control(Control const &) = delete;
+    Control &operator=(Control const &) = delete;
+    Control(Control &&) = delete;
+    Control &operator=(Control &&) = delete;
+
+    void initialize(void *const baseAddress, std::size_t const size,
+                    std::size_t const slotSize, bool const unlimitedQuarantine,
+                    std::uint32_t const quarantineSize) noexcept {
+      assert(size > 0 && (size & (size - 1)) == 0 &&
+             "Sizes of sized bins must be powers of two");
+
+      this->baseAddress = static_cast<char *>(baseAddress);
+      this->size = size;
+      this->slotSize = slotSize;
+      if (unlimitedQuarantine) {
+        this->quarantineSize = 0;
+        this->unlimitedQuarantine = true;
+      } else {
+        this->quarantineSize = quarantineSize == 0 ? 0 : quarantineSize + 1;
+        this->unlimitedQuarantine = false;
+      }
+      this->prefixSize =
+          sizeof(Data) + this->quarantineSize * sizeof(std::size_t);
+
+      traceLine("Initialization complete");
+    }
+
+    inline std::ostream &logTag(std::ostream &out) const noexcept {
+      return out << "[slot " << slotSize << " Control] ";
+    }
+
+    constexpr void *mapping_begin() const noexcept { return baseAddress; }
+    constexpr void *mapping_end() const noexcept {
+      return static_cast<void *>(static_cast<char *>(baseAddress) + size);
+    }
+  };
+
+private:
+  Data *data = nullptr;
+
+  inline void releaseData() noexcept {
+    if (data) {
+      --data->referenceCount;
+      if (data->referenceCount == 0) {
+        std::free(data);
+      }
+      data = nullptr;
+    }
+  }
+
+  inline void acquireData(Control const &Control) noexcept {
+    assert(!!data);
+    if (data->referenceCount > 1) {
+      auto newCapacity = computeNextCapacity(
+          getLastUsed(Control) +
+          1); // one more, since `getLastUsed` is an index, not a size
+      auto objectSize = Control.prefixSize + newCapacity * sizeof(std::size_t);
+      auto newData = static_cast<Data *>(std::malloc(objectSize));
+      assert(newData && "allocation failure");
+
+      std::memcpy(newData, data,
+                  static_cast<std::size_t>(
+                      reinterpret_cast<char *>(
+                          &data->quarantineAndBitmap[Control.quarantineSize +
+                                                     data->lastUsedFinger] +
+                          1) -
+                      reinterpret_cast<char *>(data)));
+      newData->referenceCount = 1;
+      newData->capacity = newCapacity;
+      std::fill(
+          &newData->quarantineAndBitmap[Control.quarantineSize +
+                                        newData->lastUsedFinger + 1],
+          &newData->quarantineAndBitmap[Control.quarantineSize + newCapacity],
+          ~static_cast<std::size_t>(0));
+
+      --data->referenceCount;
+      assert(data->referenceCount > 0);
+      data = newData;
+    }
+    assert(data->referenceCount == 1);
+  }
+
+  std::size_t quarantine(Control const &control, std::size_t const index) {
+    assert(!!data &&
+           "Deallocations can only happen if allocations happened beforehand");
+    assert(
+        !control.unlimitedQuarantine &&
+        "Please check for unlimited quarantine before calling this function");
+
+    if (control.quarantineSize == 0) {
+      return index;
+    }
+
+    assert(data->referenceCount == 1 &&
+           "Must hold CoW ownership to quarantine a new index");
+
+    auto const pos = data->quarantineAndBitmap[0];
+    if (pos + 1 == control.quarantineSize) {
+      data->quarantineAndBitmap[0] = 1;
+    } else {
+      data->quarantineAndBitmap[0] = pos + 1;
+    }
+
+    return std::exchange(data->quarantineAndBitmap[pos], index);
+  }
+
+  inline static constexpr std::ptrdiff_t
+  computeNextCapacity(std::ptrdiff_t oldCapacity) noexcept {
+    assert(oldCapacity >= 0);
+    assert(oldCapacity <
+           (std::numeric_limits<std::ptrdiff_t>::max)() /
+               std::max<std::ptrdiff_t>(8, sizeof(std::uint64_t)));
+    return std::max<std::ptrdiff_t>(8, (oldCapacity * 7) / 4);
+  }
+
+  /// Get index of first word that contains a one bit. (Internally update the
+  /// associated finger.)
+  [[nodiscard]] std::ptrdiff_t
+  getFirstFree(Control const &control) const noexcept {
+    if (!data) {
+      return 0;
+    }
+
+    assert(data->firstFreeFinger >= 0);
+    assert(data->firstFreeFinger <= data->capacity);
+
+    while (data->firstFreeFinger < data->capacity &&
+           data->quarantineAndBitmap[control.quarantineSize +
+                                     data->firstFreeFinger] == 0) {
+      ++data->firstFreeFinger;
+    }
+    assert(data->firstFreeFinger <= data->capacity);
+
+    return data->firstFreeFinger;
+  }
+
+  /// Get index of last word that contains a zero bit. (Internally update the
+  /// associated finger.)
+  [[nodiscard]] inline std::ptrdiff_t
+  getLastUsed(Control const &control) const noexcept {
+    if (!data) {
+      return -1;
+    }
+
+    assert(data->lastUsedFinger >= -1);
+    assert(data->lastUsedFinger < data->capacity);
+
+    while (data->lastUsedFinger >= 0 &&
+           ~data->quarantineAndBitmap[control.quarantineSize +
+                                      data->lastUsedFinger] == 0) {
+      --data->lastUsedFinger;
+    }
+    assert(data->lastUsedFinger >= -1);
+
+    return data->lastUsedFinger;
+  }
+
+  /// Returns true iff the bit at `index` is used
+  [[nodiscard]] bool isUsed(Control const &control,
+                            std::size_t const index) const noexcept {
+    if (!data) {
+      return false;
+    }
+
+    auto const loc = static_cast<std::ptrdiff_t>(
+        index / std::numeric_limits<std::size_t>::digits);
+    auto const shift =
+        static_cast<int>(index % std::numeric_limits<std::size_t>::digits);
+
+    if (loc <= data->lastUsedFinger) {
+      return (data->quarantineAndBitmap[control.quarantineSize + loc] &
+              (static_cast<std::uint64_t>(1) << shift)) == 0;
+    } else {
+      return false;
+    }
+  }
+
+  void setFree(Control const &control, std::size_t const index) noexcept {
+    auto const loc = static_cast<std::ptrdiff_t>(
+        index / std::numeric_limits<std::size_t>::digits);
+    auto const shift =
+        static_cast<int>(index % std::numeric_limits<std::size_t>::digits);
+
+    assert(!!data);
+    assert(loc <= data->lastUsedFinger);
+    assert(data->lastUsedFinger < data->capacity);
+    // 0 <= loc <= _last_used_finger < _capacity
+
+    acquireData(control);
+
+    auto word = data->quarantineAndBitmap[control.quarantineSize + loc];
+    auto const mask = static_cast<std::size_t>(1) << shift;
+    assert((word & mask) == 0);
+    word ^= mask;
+    data->quarantineAndBitmap[control.quarantineSize + loc] = word;
+
+    if (~word == 0 && loc == data->lastUsedFinger) {
+      data->lastUsedFinger = loc - 1;
+    }
+
+    if (loc < data->firstFreeFinger) {
+      data->firstFreeFinger = loc;
+    }
+  }
+
+  /// Toggles the first free bit to allocated and returns its index
+  [[nodiscard]] std::size_t
+  setFirstFreeToUsed(Control const &control) noexcept {
+    auto const loc = getFirstFree(control);
+    if (!data) {
+      auto newCapacity = computeNextCapacity(0);
+      auto objectSize = control.prefixSize + newCapacity * sizeof(std::size_t);
+      data = static_cast<Data *>(std::malloc(objectSize));
+      assert(data && "allocation failure");
+      data->referenceCount = 1;
+      data->capacity = newCapacity;
+      data->firstFreeFinger = 0;
+      data->lastUsedFinger = -1;
+
+      if (control.quarantineSize == 0) {
+        std::fill(&data->quarantineAndBitmap[0],
+                  &data->quarantineAndBitmap[newCapacity],
+                  ~static_cast<std::size_t>(0));
+      } else {
+        data->quarantineAndBitmap[0] = 1;
+        std::fill(
+            &data->quarantineAndBitmap[1],
+            &data->quarantineAndBitmap[control.quarantineSize + newCapacity],
+            ~static_cast<std::size_t>(0));
+      }
+    } else {
+      if (loc == data->capacity && data->referenceCount == 1) {
+        auto newCapacity = computeNextCapacity(data->capacity);
+        auto objectSize =
+            control.prefixSize + newCapacity * sizeof(std::size_t);
+        data = static_cast<Data *>(std::realloc(data, objectSize));
+        assert(data && "allocation failure");
+        std::fill(
+            &data->quarantineAndBitmap[control.quarantineSize + data->capacity],
+            &data->quarantineAndBitmap[control.quarantineSize + newCapacity],
+            ~static_cast<std::size_t>(0));
+        data->capacity = newCapacity;
+      } else {
+        acquireData(control);
+        assert(loc < data->capacity &&
+               "acquire_data performs a growth step in the sense of "
+               "`computeNextCapacity`");
+      }
+    }
+
+    assert(!!data);
+    assert(data->referenceCount == 1);
+    assert(loc < data->capacity);
+
+    auto word = data->quarantineAndBitmap[control.quarantineSize + loc];
+    auto const shift = countTrailingZeroes(word);
+    auto const mask = static_cast<std::size_t>(1) << shift;
+    assert((word & mask) == mask);
+    word ^= mask;
+    data->quarantineAndBitmap[control.quarantineSize + loc] = word;
+
+    if (word == 0 && data->firstFreeFinger == loc) {
+      data->firstFreeFinger = loc + 1;
+    }
+
+    if (loc > data->lastUsedFinger) {
+      data->lastUsedFinger = loc;
+    }
+
+    return static_cast<std::size_t>(
+        loc * std::numeric_limits<std::size_t>::digits + shift);
+  }
+
+public:
+  constexpr SlotAllocator() = default;
+
+  SlotAllocator(SlotAllocator const &rhs) noexcept : data(rhs.data) {
+    if (data) {
+      ++data->referenceCount;
+      assert(data->referenceCount > 1);
+    }
+  }
+
+  SlotAllocator &operator=(SlotAllocator const &rhs) noexcept {
+    if (data != rhs.data) {
+      releaseData();
+      data = rhs.data;
+      if (data) {
+        ++data->referenceCount;
+        assert(data->referenceCount > 1);
+      }
+    }
+    return *this;
+  }
+
+  SlotAllocator(SlotAllocator &&rhs) noexcept
+      : data(std::exchange(rhs.data, nullptr)) {
+    assert(data == nullptr || data->referenceCount > 0);
+  }
+
+  SlotAllocator &operator=(SlotAllocator &&rhs) noexcept {
+    if (data != rhs.data) {
+      releaseData();
+      data = std::exchange(rhs.data, nullptr);
+    }
+    return *this;
+  }
+
+  ~SlotAllocator() noexcept { releaseData(); }
+
+  inline std::ostream &logTag(std::ostream &out) const noexcept {
+    return out << "[slot] ";
+  }
+
+  LocationInfo getLocationInfo(Control const &control, void const *const ptr,
+                               std::size_t const size) const noexcept {
+    assert(control.mapping_begin() <= ptr &&
+           reinterpret_cast<char const *>(ptr) + size < control.mapping_end() &&
+           "This property should have been ensured by the caller");
+
+    auto const begin = static_cast<std::size_t>(static_cast<char const *>(ptr) -
+                                                control.baseAddress);
+    auto const end = static_cast<std::size_t>(static_cast<char const *>(ptr) +
+                                              size - control.baseAddress);
+    assert(control.slotSize > 0 && "Uninitialized Control structure");
+    auto const pos = begin - begin % control.slotSize;
+    if (pos != (end - 1) - (end - 1) % control.slotSize) {
+      return LocationInfo::LI_Unaligned;
+    }
+
+    auto const index = control.convertPositionToIndex(pos);
+    auto const loc = static_cast<std::ptrdiff_t>(
+        index / std::numeric_limits<std::size_t>::digits);
+    auto const shift =
+        static_cast<int>(index % std::numeric_limits<std::size_t>::digits);
+
+    if (!data || loc > data->lastUsedFinger) {
+      return LocationInfo::LI_Unallocated;
+    }
+    assert(data->lastUsedFinger < data->capacity);
+
+    auto word = data->quarantineAndBitmap[control.quarantineSize + loc];
+    auto const mask = static_cast<std::size_t>(1) << shift;
+    if ((word & mask) == 0) {
+      return {LocationInfo::LI_AllocatedOrQuarantined,
+              control.baseAddress + pos};
+    } else {
+      return LocationInfo::LI_Unallocated;
+    }
+  }
+
+  [[nodiscard]] void *allocate(Control const &control) noexcept {
+    traceLine("Allocating ", control.slotSize, " bytes");
+    traceContents(control);
+
+    auto const index = setFirstFreeToUsed(control);
+    return control.baseAddress + control.convertIndexToPosition(index);
+  }
+
+  void deallocate(Control const &control, void *const ptr) {
+    assert(!!data &&
+           "Deallocations can only happen if allocations happened beforehand");
+
+    if (control.unlimitedQuarantine) {
+      traceLine("Quarantining ", ptr, " for ever");
+    } else {
+      auto pos = static_cast<std::size_t>(static_cast<char *>(ptr) -
+                                          control.baseAddress);
+      acquireData(control); // we will need quarantine and/or bitmap ownership
+
+      traceLine("Quarantining ", ptr, " as ", pos, " for ",
+                control.quarantineSize, " deallocations");
+      pos = quarantine(control, pos);
+
+      if (pos == static_cast<std::size_t>(-1)) {
+        traceLine("Nothing to deallocate");
+      } else {
+        traceLine("Deallocating ", pos);
+        traceContents(control);
+        assert(pos < control.size);
+
+        setFree(control, control.convertPositionToIndex(pos));
+      }
+    }
+  }
+
+  void traceContents(Control const &control) const noexcept {
+    static_cast<void>(control);
+
+#if KDALLOC_TRACE >= 2
+    traceLine("bitmap:");
+    bool isEmpty = true;
+    for (std::size_t i = 0; i < data->capacity; ++i) {
+      if (is_used(Control, i)) {
+        isEmpty = false;
+        traceLine("  ", i, " ",
+                  static_cast<void *>(control.baseAddress +
+                                      control.convertIndexToPosition(i)));
+      }
+    }
+    if (isEmpty) {
+      traceLine("  <empty>");
+    }
+#else
+    traceLine("bitmap has a capacity of ", data ? data->capacity : 0,
+              " entries");
+#endif
+  }
+};
+} // namespace klee::kdalloc::suballocators
+
+#endif
diff --git a/include/klee/KDAlloc/tagged_logger.h b/include/klee/KDAlloc/tagged_logger.h
new file mode 100644
index 00000000..e0c9c194
--- /dev/null
+++ b/include/klee/KDAlloc/tagged_logger.h
@@ -0,0 +1,43 @@
+//===-- tagged_logger.h -----------------------------------------*- C++ -*-===//
+//
+//                     The KLEE Symbolic Virtual Machine
+//
+// This file is distributed under the University of Illinois Open Source
+// License. See LICENSE.TXT for details.
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef KDALLOC_UTIL_TAGGED_LOGGER_H
+#define KDALLOC_UTIL_TAGGED_LOGGER_H
+
+#include "define.h"
+
+#include <utility>
+
+#if KDALLOC_TRACE >= 1
+#include <iostream>
+#endif
+
+namespace klee::kdalloc {
+template <typename C> class TaggedLogger {
+  template <typename O> inline void traceLineImpl(O &out) const noexcept {
+    out << "\n";
+  }
+
+  template <typename O, typename T, typename... V>
+  inline void traceLineImpl(O &out, T &&head, V &&...tail) const noexcept {
+    out << head;
+    traceLineImpl(out, std::forward<V>(tail)...);
+  }
+
+protected:
+  template <typename... V> inline void traceLine(V &&...args) const noexcept {
+#if KDALLOC_TRACE >= 1
+    traceLineImpl(static_cast<C const *>(this)->logTag(std::cout),
+                  std::forward<T>(args));
+#endif
+  }
+};
+} // namespace klee::kdalloc
+
+#endif