diff --git a/include/duckhandy/alignment.hpp b/include/duckhandy/alignment.hpp
new file mode 100644
index 0000000..aa7f610
--- /dev/null
+++ b/include/duckhandy/alignment.hpp
@@ -0,0 +1,48 @@
+/* Copyright 2016-2025 Michele Santullo
+ * This file is part of "duckhandy".
+ *
+ * "duckhandy" is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * "duckhandy" is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with "duckhandy". If not, see .
+ */
+
+#ifndef id99DC0F782D0F4907A7768E8743A8BE74
+#define id99DC0F782D0F4907A7768E8743A8BE74
+
+#include
+#include
+#include
+
+namespace dhandy {
+template
+concept is_pow_of_two = static_cast(V and not(V bitand (V - 1u)));
+
+template
+requires is_pow_of_two
+constexpr T align_to (T v) {
+ return (v + V-1) & ~(T{V-1});
+}
+
+template
+requires is_pow_of_two
+constexpr T padding_to (T v) {
+ return (-v) & (V-1);
+}
+
+template
+requires is_pow_of_two
+constexpr T* align_ptr_to (T* ptr, std::size_t add) {
+ return reinterpret_cast(align_to(reinterpret_cast(ptr) + add));
+}
+
+} //namespace dhandy
+#endif
diff --git a/include/duckhandy/small_object_allocator.hpp b/include/duckhandy/small_object_allocator.hpp
new file mode 100644
index 0000000..b3fe28b
--- /dev/null
+++ b/include/duckhandy/small_object_allocator.hpp
@@ -0,0 +1,268 @@
+/* Copyright 2016-2024 Michele Santullo
+ * This file is part of "duckhandy".
+ *
+ * "duckhandy" is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * "duckhandy" is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with "duckhandy". If not, see .
+ */
+
+#ifndef idE77208CAFC79452DA12757DD0F6692D3
+#define idE77208CAFC79452DA12757DD0F6692D3
+
+#include "alignment.hpp"
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#if !defined(NDEBUG) && !defined(DEBUG_SMALL_OBJECT_ALLOCATOR)
+# define DEBUG_SMALL_OBJECT_ALLOCATOR
+#endif
+
+#if defined(DEBUG_SMALL_OBJECT_ALLOCATOR)
+# include "tiger_bt.hpp"
+# include "lengthof.h"
+#endif
+
+namespace dhandy {
+namespace implem {
+[[gnu::pure,gnu::always_inline]]
+unsigned int ffs (unsigned int val) { return static_cast(::ffs(static_cast(val))); }
+
+[[gnu::pure,gnu::always_inline]]
+unsigned int ffs (unsigned long val) { return static_cast(::ffsl(static_cast(val))); }
+
+[[gnu::pure,gnu::always_inline]]
+unsigned int ffs (unsigned long long val) { return static_cast(::ffsll(static_cast(val))); }
+
+#if defined(DEBUG_SMALL_OBJECT_ALLOCATOR)
+template typename A>
+consteval std::uint32_t make_signature() {
+ return static_cast(
+ bt::tiger(__PRETTY_FUNCTION__, lengthof(__PRETTY_FUNCTION__), bt::TigerPaddingV2).a & 0xFFFFFFFF
+ );
+}
+#endif
+} //namespace implem
+
+template
+struct AllocatorFunction {
+ std::unique_ptr operator()() {
+ return std::make_unique();
+ }
+};
+
+template typename A=AllocatorFunction>
+class SmallObjectAllocator {
+public:
+ typedef T value_type;
+ typedef T* pointer;
+ typedef const T* const_pointer;
+ typedef T& reference;
+ typedef const T& const_reference;
+ typedef std::size_t size_type;
+ typedef std::ptrdiff_t difference_type;
+ typedef std::true_type propagate_on_container_move_assignment;
+
+private:
+ typedef std::uint_fast32_t uint_freelist_t;
+ typedef std::uint32_t index_t;
+
+ struct TAndPtrFakeStruct { //only used for sizeof()
+ TAndPtrFakeStruct() = delete;
+ T t;
+#if defined(DEBUG_SMALL_OBJECT_ALLOCATOR)
+ std::uint32_t signature;
+#endif
+ index_t block_index; //index in a lookup table so that lookup[index-1]->next == owner
+ };
+
+ static constexpr std::uint32_t signature = implem::make_signature();
+ static constexpr size_type size = sizeof(TAndPtrFakeStruct);
+ static constexpr size_type align = alignof(TAndPtrFakeStruct);
+ static constexpr size_type object_size = sizeof(T); //(size < sizeof(T*) ? sizeof(T*) : size);
+ static constexpr size_type object_align = alignof(T); //(align < alignof(T*) ? alignof(T*) : align);
+ static constexpr size_type block_ptr_offset = offsetof(TAndPtrFakeStruct, block);
+
+public:
+ static constexpr size_type objects_per_block = CHAR_BIT * sizeof(uint_freelist_t);
+
+private:
+ struct Block {
+ typedef typename std::aligned_storage::type raw_t;
+
+ raw_t data[objects_per_block];
+ std::unique_ptr next;
+ uint_freelist_t freelist{0};
+ };
+
+public:
+
+ SmallObjectAllocator() = default;
+ ~SmallObjectAllocator() noexcept = default;
+
+ [[nodiscard]] constexpr pointer allocate (size_type size);
+ //[[nodiscard]] constexpr std::allocation_result allocate_at_least (size_type size);
+ constexpr void deallocate (pointer ptr, size_type size);
+
+ //template
+ //constexpr bool operator== (const allocator& rhs) noexcept;
+
+
+private:
+ static index_t* fetch_block_index (Block::raw_t* in_ptr);
+ static void set_block_indices (Block* block, index_t new_index);
+ static void set_debug_signatures (Block* block);
+
+ std::vector m_block_list;
+ std::unique_ptr m_head;
+ index_t m_prev_index;
+ index_t m_curr_index{0};
+};
+
+template typename A>
+constexpr auto SmallObjectAllocator::allocate (size_type size) -> pointer {
+ assert(object_size == size);
+#if defined(NDEBUG)
+ static_cast(size);
+#endif
+
+ assert(0 == m_curr_index or m_curr_index <= m_block_list.size());
+ assert(m_head or m_block_list.empty());
+ if (!m_curr_index or !~m_block_list[m_curr_index-1]->freelist) {
+ A alloc_function{};
+ auto new_block = alloc_function();
+ set_debug_signatures(new_block.get());
+ new_block->next = std::move(m_head);
+ m_head.swap(new_block);
+ m_block_list.push_back(m_head.get());
+ m_prev_index = m_curr_index;
+ m_curr_index = static_cast(m_block_list.size());
+
+ if (m_head->next)
+ set_block_indices(m_head->next.get(), m_curr_index);
+ }
+
+ Block*const block = m_block_list[m_curr_index-1];
+ assert(block != nullptr);
+ const uint_freelist_t neg_freelist = static_cast(~block->freelist);
+ assert(neg_freelist != 0);
+
+ const unsigned int object_num = implem::ffs(neg_freelist);
+ assert(object_num);
+ const unsigned int object_index = object_num - 1;
+ assert(object_index < objects_per_block);
+ constexpr uint_freelist_t one = 1;
+ block->freelist |= one << object_index;
+
+ index_t* const index_ptr = fetch_block_index(block->data + object_index);
+ assert(index_ptr);
+ *index_ptr = m_prev_index;
+ return reinterpret_cast(block->data + object_index);
+}
+
+template typename A>
+constexpr void SmallObjectAllocator::deallocate (pointer ptr, size_type size) {
+ assert(object_size == size);
+ auto* const block_ptr = reinterpret_cast(ptr);
+ const index_t owner_prev_index = *fetch_block_index(block_ptr);
+ Block* const owner = (0 == owner_prev_index ? m_head.get() : m_block_list[owner_prev_index-1]->next.get());
+
+ assert(owner);
+ const auto flag_index = std::distance(owner->data, block_ptr);
+ assert(flag_index >= 0);
+ assert(flag_index < objects_per_block);
+
+ constexpr uint_freelist_t one = 1;
+ const uint_freelist_t mask = one << flag_index;
+ assert((owner->freelist & mask) != 0);
+ owner->freelist &= ~mask;
+
+ if (0 == owner->freelist) {
+ if (owner_prev_index) {
+ Block& prev = *m_block_list[owner_prev_index-1];
+ auto found = std::find(m_block_list.begin(), m_block_list.end(), owner);
+ assert(m_block_list.end() != found);
+ it's all fucked up here, I can't delete the item because that would
+ invalidate all the subsequent indices. It means all blocks following
+ the one being deleted would have to get set_block_indices() invoked
+ on them which is insane so this whole approach is fucke'
+
+ auto empty_block = std::move(prev.next);
+ assert(!prev.next);
+ prev.next = std::move(empty_block->next);
+ set_block_indices(prev.next.get(), owner_prev_index);
+
+ }
+ else {
+ assert(m_block_list.empty());
+ m_head.reset();
+ m_curr_index = 0;
+ }
+ }
+}
+
+template typename A>
+auto SmallObjectAllocator::fetch_block_index (Block::raw_t* in_ptr) -> index_t* {
+ assert(in_ptr);
+
+ char* ptr = reinterpret_cast(in_ptr);
+#if defined(DEBUG_SMALL_OBJECT_ALLOCATOR)
+ ptr = align_ptr_to(ptr, object_size);
+ {
+ std::uint32_t read_signature;
+ std::memcpy(&read_signature, ptr, sizeof(signature));
+ assert(signature == read_signature);
+ }
+ ptr = align_ptr_to(ptr, sizeof(signature));
+#else
+ ptr = align_ptr_to(ptr, object_size);
+#endif
+
+ index_t* const retval = reinterpret_cast(ptr);
+ return retval;
+}
+
+template typename A>
+void SmallObjectAllocator::set_block_indices (Block* block, index_t new_index) {
+ uint_freelist_t flag = 1;
+ for (unsigned int z = 0; z < objects_per_block; ++z, flag<<=1) {
+ if (flag & block->freelist) {
+ index_t* const dst_index = fetch_block_index(block->data + z);
+ assert(dst_index);
+ *dst_index = new_index;
+ }
+ }
+}
+
+template typename A>
+void SmallObjectAllocator::set_debug_signatures (Block* block) {
+#if defined(DEBUG_SMALL_OBJECT_ALLOCATOR)
+ const auto sig = signature;
+ for (unsigned int z = 0; z < objects_per_block; ++z) {
+ std::memcpy(align_ptr_to(block->data + z, object_size), &sig, sizeof(signature));
+ }
+#else
+ static_cast(block);
+#endif
+}
+} //namespace dhandy
+
+#endif