Squashed 'third_party/immer/' content from commit ad3e3556d
git-subtree-dir: third_party/immer git-subtree-split: ad3e3556d38bb75966dd24c61a774970a7c7957e
This commit is contained in:
commit
7f19d64164
311 changed files with 74223 additions and 0 deletions
41
immer/heap/cpp_heap.hpp
Normal file
41
immer/heap/cpp_heap.hpp
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
//
|
||||
// immer: immutable data structures for C++
|
||||
// Copyright (C) 2016, 2017, 2018 Juan Pedro Bolivar Puente
|
||||
//
|
||||
// This software is distributed under the Boost Software License, Version 1.0.
|
||||
// See accompanying file LICENSE or copy at http://boost.org/LICENSE_1_0.txt
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
namespace immer {
|
||||
|
||||
/*!
|
||||
* A heap that uses `operator new` and `operator delete`.
|
||||
*/
|
||||
struct cpp_heap
|
||||
{
|
||||
/*!
|
||||
* Returns a pointer to a memory region of size `size`, if the
|
||||
* allocation was successful, and throws otherwise.
|
||||
*/
|
||||
template <typename... Tags>
|
||||
static void* allocate(std::size_t size, Tags...)
|
||||
{
|
||||
return ::operator new(size);
|
||||
}
|
||||
|
||||
/*!
|
||||
* Releases a memory region `data` that was previously returned by
|
||||
* `allocate`. One must not use nor deallocate again a memory
|
||||
* region that once it has been deallocated.
|
||||
*/
|
||||
static void deallocate(std::size_t size, void* data)
|
||||
{
|
||||
::operator delete(data);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace immer
|
||||
69
immer/heap/debug_size_heap.hpp
Normal file
69
immer/heap/debug_size_heap.hpp
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
//
|
||||
// immer: immutable data structures for C++
|
||||
// Copyright (C) 2016, 2017, 2018 Juan Pedro Bolivar Puente
|
||||
//
|
||||
// This software is distributed under the Boost Software License, Version 1.0.
|
||||
// See accompanying file LICENSE or copy at http://boost.org/LICENSE_1_0.txt
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <immer/config.hpp>
|
||||
#include <immer/heap/identity_heap.hpp>
|
||||
|
||||
#include <cassert>
|
||||
#include <cstddef>
|
||||
#include <type_traits>
|
||||
#include <memory>
|
||||
|
||||
namespace immer {
|
||||
|
||||
#if IMMER_ENABLE_DEBUG_SIZE_HEAP
|
||||
|
||||
/*!
|
||||
* A heap that in debug mode ensures that the sizes for allocation and
|
||||
* deallocation do match.
|
||||
*/
|
||||
template <typename Base>
|
||||
struct debug_size_heap
|
||||
{
|
||||
#if defined(__MINGW32__) && !defined(__MINGW64__)
|
||||
// There is a bug in MinGW 32bit:
|
||||
// https://sourceforge.net/p/mingw-w64/bugs/778/ It causes different
|
||||
// versions of std::max_align_t to be defined, depending on inclusion order
|
||||
// of stddef.h and stdint.h. As we have no control over the inclusion order
|
||||
// here (as it might be set in stone by the outside world), we can't easily
|
||||
// pin it to one of both versions of std::max_align_t. This means, we have
|
||||
// to hardcode extra_size for MinGW 32bit builds until the mentioned bug is
|
||||
// fixed.
|
||||
constexpr static auto extra_size = 8;
|
||||
#else
|
||||
constexpr static auto extra_size = sizeof(
|
||||
std::aligned_storage_t<sizeof(std::size_t), alignof(std::max_align_t)>);
|
||||
#endif
|
||||
|
||||
template <typename... Tags>
|
||||
static void* allocate(std::size_t size, Tags... tags)
|
||||
{
|
||||
auto p = (std::size_t*) Base::allocate(size + extra_size, tags...);
|
||||
new (p) std::size_t{size};
|
||||
return ((char*) p) + extra_size;
|
||||
}
|
||||
|
||||
template <typename... Tags>
|
||||
static void deallocate(std::size_t size, void* data, Tags... tags)
|
||||
{
|
||||
auto p = (std::size_t*) (((char*) data) - extra_size);
|
||||
assert(*p == size);
|
||||
Base::deallocate(size + extra_size, p, tags...);
|
||||
}
|
||||
};
|
||||
|
||||
#else // IMMER_ENABLE_DEBUG_SIZE_HEAP
|
||||
|
||||
template <typename Base>
|
||||
using debug_size_heap = identity_heap<Base>;
|
||||
|
||||
#endif // !IMMER_ENABLE_DEBUG_SIZE_HEAP
|
||||
|
||||
} // namespace immer
|
||||
83
immer/heap/free_list_heap.hpp
Normal file
83
immer/heap/free_list_heap.hpp
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
//
|
||||
// immer: immutable data structures for C++
|
||||
// Copyright (C) 2016, 2017, 2018 Juan Pedro Bolivar Puente
|
||||
//
|
||||
// This software is distributed under the Boost Software License, Version 1.0.
|
||||
// See accompanying file LICENSE or copy at http://boost.org/LICENSE_1_0.txt
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <immer/heap/free_list_node.hpp>
|
||||
#include <immer/heap/with_data.hpp>
|
||||
|
||||
#include <atomic>
|
||||
#include <cassert>
|
||||
|
||||
namespace immer {
|
||||
|
||||
/*!
|
||||
* Adaptor that does not release the memory to the parent heap but
|
||||
* instead it keeps the memory in a thread-safe global free list. Must
|
||||
* be preceded by a `with_data<free_list_node, ...>` heap adaptor.
|
||||
*
|
||||
* @tparam Size Maximum size of the objects to be allocated.
|
||||
* @tparam Base Type of the parent heap.
|
||||
*/
|
||||
template <std::size_t Size, std::size_t Limit, typename Base>
|
||||
struct free_list_heap : Base
|
||||
{
|
||||
using base_t = Base;
|
||||
|
||||
template <typename... Tags>
|
||||
static void* allocate(std::size_t size, Tags...)
|
||||
{
|
||||
assert(size <= sizeof(free_list_node) + Size);
|
||||
assert(size >= sizeof(free_list_node));
|
||||
|
||||
free_list_node* n;
|
||||
do {
|
||||
n = head().data;
|
||||
if (!n) {
|
||||
auto p = base_t::allocate(Size + sizeof(free_list_node));
|
||||
return static_cast<free_list_node*>(p);
|
||||
}
|
||||
} while (!head().data.compare_exchange_weak(n, n->next));
|
||||
head().count.fetch_sub(1u, std::memory_order_relaxed);
|
||||
return n;
|
||||
}
|
||||
|
||||
template <typename... Tags>
|
||||
static void deallocate(std::size_t size, void* data, Tags...)
|
||||
{
|
||||
assert(size <= sizeof(free_list_node) + Size);
|
||||
assert(size >= sizeof(free_list_node));
|
||||
|
||||
// we use relaxed, because we are fine with temporarily having
|
||||
// a few more/less buffers in free list
|
||||
if (head().count.load(std::memory_order_relaxed) >= Limit) {
|
||||
base_t::deallocate(Size + sizeof(free_list_node), data);
|
||||
} else {
|
||||
auto n = static_cast<free_list_node*>(data);
|
||||
do {
|
||||
n->next = head().data;
|
||||
} while (!head().data.compare_exchange_weak(n->next, n));
|
||||
head().count.fetch_add(1u, std::memory_order_relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
struct head_t
|
||||
{
|
||||
std::atomic<free_list_node*> data;
|
||||
std::atomic<std::size_t> count;
|
||||
};
|
||||
|
||||
static head_t& head()
|
||||
{
|
||||
static head_t head_{{nullptr}, {0}};
|
||||
return head_;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace immer
|
||||
24
immer/heap/free_list_node.hpp
Normal file
24
immer/heap/free_list_node.hpp
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
//
|
||||
// immer: immutable data structures for C++
|
||||
// Copyright (C) 2016, 2017, 2018 Juan Pedro Bolivar Puente
|
||||
//
|
||||
// This software is distributed under the Boost Software License, Version 1.0.
|
||||
// See accompanying file LICENSE or copy at http://boost.org/LICENSE_1_0.txt
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <immer/heap/with_data.hpp>
|
||||
|
||||
namespace immer {
|
||||
|
||||
struct free_list_node
|
||||
{
|
||||
free_list_node* next;
|
||||
};
|
||||
|
||||
template <typename Base>
|
||||
struct with_free_list_node : with_data<free_list_node, Base>
|
||||
{};
|
||||
|
||||
} // namespace immer
|
||||
127
immer/heap/gc_heap.hpp
Normal file
127
immer/heap/gc_heap.hpp
Normal file
|
|
@ -0,0 +1,127 @@
|
|||
//
|
||||
// immer: immutable data structures for C++
|
||||
// Copyright (C) 2016, 2017, 2018 Juan Pedro Bolivar Puente
|
||||
//
|
||||
// This software is distributed under the Boost Software License, Version 1.0.
|
||||
// See accompanying file LICENSE or copy at http://boost.org/LICENSE_1_0.txt
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <immer/config.hpp>
|
||||
#include <immer/heap/tags.hpp>
|
||||
|
||||
#if IMMER_HAS_LIBGC
|
||||
#include <gc/gc.h>
|
||||
#else
|
||||
#error "Using garbage collection requires libgc"
|
||||
#endif
|
||||
|
||||
#include <cstdlib>
|
||||
#include <memory>
|
||||
|
||||
namespace immer {
|
||||
|
||||
#ifdef __APPLE__
|
||||
#define IMMER_GC_REQUIRE_INIT 1
|
||||
#else
|
||||
#define IMMER_GC_REQUIRE_INIT 0
|
||||
#endif
|
||||
|
||||
#if IMMER_GC_REQUIRE_INIT
|
||||
|
||||
namespace detail {
|
||||
|
||||
template <int Dummy = 0>
|
||||
struct gc_initializer
|
||||
{
|
||||
gc_initializer() { GC_init(); }
|
||||
static gc_initializer init;
|
||||
};
|
||||
|
||||
template <int D>
|
||||
gc_initializer<D> gc_initializer<D>::init{};
|
||||
|
||||
inline void gc_initializer_guard()
|
||||
{
|
||||
static gc_initializer<> init_ = gc_initializer<>::init;
|
||||
(void) init_;
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
#define IMMER_GC_INIT_GUARD_ ::immer::detail::gc_initializer_guard()
|
||||
|
||||
#else
|
||||
|
||||
#define IMMER_GC_INIT_GUARD_
|
||||
|
||||
#endif // IMMER_GC_REQUIRE_INIT
|
||||
|
||||
/*!
|
||||
* Heap that uses a tracing garbage collector.
|
||||
*
|
||||
* @rst
|
||||
*
|
||||
* This heap uses the `Boehm's conservative garbage collector`_ under
|
||||
* the hood. This is a tracing garbage collector that automatically
|
||||
* reclaims unused memory. Thus, it is not needed to call
|
||||
* ``deallocate()`` in order to release memory.
|
||||
*
|
||||
* .. admonition:: Dependencies
|
||||
* :class: tip
|
||||
*
|
||||
* In order to use this header file, you need to make sure that
|
||||
* Boehm's ``libgc`` is your include path and link to its binary
|
||||
* library.
|
||||
*
|
||||
* .. caution:: Memory that is allocated with the standard ``malloc``
|
||||
* and ``free`` is not visible to ``libgc`` when it is looking for
|
||||
* references. This means that if, let's say, you store a
|
||||
* :cpp:class:`immer::vector` using a ``gc_heap`` inside a
|
||||
* ``std::vector`` that uses a standard allocator, the memory of
|
||||
* the former might be released automatically at unexpected times
|
||||
* causing crashes.
|
||||
*
|
||||
* .. caution:: When using a ``gc_heap`` in combination with immutable
|
||||
* containers, the destructors of the contained objects will never
|
||||
* be called. It is ok to store containers inside containers as
|
||||
* long as all of them use a ``gc_heap`` consistently, but storing
|
||||
* other kinds of objects with relevant destructors
|
||||
* (e.g. containers with reference counting or other kinds of
|
||||
* *resource handles*) might cause memory leaks and other problems.
|
||||
*
|
||||
* .. _boehm's conservative garbage collector: https://github.com/ivmai/bdwgc
|
||||
*
|
||||
* @endrst
|
||||
*/
|
||||
class gc_heap
|
||||
{
|
||||
public:
|
||||
static void* allocate(std::size_t n)
|
||||
{
|
||||
IMMER_GC_INIT_GUARD_;
|
||||
auto p = GC_malloc(n);
|
||||
if (IMMER_UNLIKELY(!p))
|
||||
throw std::bad_alloc{};
|
||||
return p;
|
||||
}
|
||||
|
||||
static void* allocate(std::size_t n, norefs_tag)
|
||||
{
|
||||
IMMER_GC_INIT_GUARD_;
|
||||
auto p = GC_malloc_atomic(n);
|
||||
if (IMMER_UNLIKELY(!p))
|
||||
throw std::bad_alloc{};
|
||||
return p;
|
||||
}
|
||||
|
||||
static void deallocate(std::size_t, void* data) { GC_free(data); }
|
||||
|
||||
static void deallocate(std::size_t, void* data, norefs_tag)
|
||||
{
|
||||
GC_free(data);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace immer
|
||||
141
immer/heap/heap_policy.hpp
Normal file
141
immer/heap/heap_policy.hpp
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
//
|
||||
// immer: immutable data structures for C++
|
||||
// Copyright (C) 2016, 2017, 2018 Juan Pedro Bolivar Puente
|
||||
//
|
||||
// This software is distributed under the Boost Software License, Version 1.0.
|
||||
// See accompanying file LICENSE or copy at http://boost.org/LICENSE_1_0.txt
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <immer/config.hpp>
|
||||
#include <immer/heap/debug_size_heap.hpp>
|
||||
#include <immer/heap/free_list_heap.hpp>
|
||||
#include <immer/heap/split_heap.hpp>
|
||||
#include <immer/heap/thread_local_free_list_heap.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdlib>
|
||||
|
||||
namespace immer {
|
||||
|
||||
/*!
|
||||
* Heap policy that unconditionally uses its `Heap` argument.
|
||||
*/
|
||||
template <typename Heap>
|
||||
struct heap_policy
|
||||
{
|
||||
using type = Heap;
|
||||
|
||||
template <std::size_t>
|
||||
struct optimized
|
||||
{
|
||||
using type = Heap;
|
||||
};
|
||||
};
|
||||
|
||||
template <typename Deriv, typename HeapPolicy>
|
||||
struct enable_optimized_heap_policy
|
||||
{
|
||||
static void* operator new(std::size_t size)
|
||||
{
|
||||
using heap_type =
|
||||
typename HeapPolicy ::template optimized<sizeof(Deriv)>::type;
|
||||
|
||||
return heap_type::allocate(size);
|
||||
}
|
||||
|
||||
static void operator delete(void* data, std::size_t size)
|
||||
{
|
||||
using heap_type =
|
||||
typename HeapPolicy ::template optimized<sizeof(Deriv)>::type;
|
||||
|
||||
heap_type::deallocate(size, data);
|
||||
}
|
||||
};
|
||||
|
||||
/*!
|
||||
* Heap policy that returns a heap with a free list of objects
|
||||
* of `max_size = max(Sizes...)` on top an underlying `Heap`. Note
|
||||
* these two properties of the resulting heap:
|
||||
*
|
||||
* - Allocating an object that is bigger than `max_size` may trigger
|
||||
* *undefined behavior*.
|
||||
*
|
||||
* - Allocating an object of size less than `max_size` still
|
||||
* returns an object of `max_size`.
|
||||
*
|
||||
* Basically, this heap will always return objects of `max_size`.
|
||||
* When an object is freed, it does not directly invoke `std::free`,
|
||||
* but it keeps the object in a global linked list instead. When a
|
||||
* new object is requested, it does not need to call `std::malloc` but
|
||||
* it can directly pop and return the other object from the global
|
||||
* list, a much faster operation.
|
||||
*
|
||||
* This actually creates a hierarchy with two free lists:
|
||||
*
|
||||
* - A `thread_local` free list is used first. It does not need any
|
||||
* kind of synchronization and is very fast. When the thread
|
||||
* finishes, its contents are returned to the next free list.
|
||||
*
|
||||
* - A global free list using lock-free access via atomics.
|
||||
*
|
||||
* @tparam Heap Heap to be used when the free list is empty.
|
||||
*
|
||||
* @rst
|
||||
*
|
||||
* .. tip:: For many applications that use immutable data structures
|
||||
* significantly, this is actually the best heap policy, and it
|
||||
* might become the default in the future.
|
||||
*
|
||||
* Note that most our data structures internally use trees with the
|
||||
* same big branching factors. This means that all *vectors*,
|
||||
* *maps*, etc. can just allocate elements from the same free-list
|
||||
* optimized heap. Not only does this lowers the allocation time,
|
||||
* but also makes up for more efficient *cache utilization*. When
|
||||
* a new node is needed, there are high chances the allocator will
|
||||
* return a node that was just accessed. When batches of immutable
|
||||
* updates are made, this can make a significant difference.
|
||||
*
|
||||
* @endrst
|
||||
*/
|
||||
template <typename Heap, std::size_t Limit = default_free_list_size>
|
||||
struct free_list_heap_policy
|
||||
{
|
||||
using type = debug_size_heap<Heap>;
|
||||
|
||||
template <std::size_t Size>
|
||||
struct optimized
|
||||
{
|
||||
using type =
|
||||
split_heap<Size,
|
||||
with_free_list_node<thread_local_free_list_heap<
|
||||
Size,
|
||||
Limit,
|
||||
free_list_heap<Size, Limit, debug_size_heap<Heap>>>>,
|
||||
debug_size_heap<Heap>>;
|
||||
};
|
||||
};
|
||||
|
||||
/*!
|
||||
* Similar to @ref free_list_heap_policy, but it assumes no
|
||||
* multi-threading, so a single global free list with no concurrency
|
||||
* checks is used.
|
||||
*/
|
||||
template <typename Heap, std::size_t Limit = default_free_list_size>
|
||||
struct unsafe_free_list_heap_policy
|
||||
{
|
||||
using type = Heap;
|
||||
|
||||
template <std::size_t Size>
|
||||
struct optimized
|
||||
{
|
||||
using type = split_heap<
|
||||
Size,
|
||||
with_free_list_node<
|
||||
unsafe_free_list_heap<Size, Limit, debug_size_heap<Heap>>>,
|
||||
debug_size_heap<Heap>>;
|
||||
};
|
||||
};
|
||||
|
||||
} // namespace immer
|
||||
34
immer/heap/identity_heap.hpp
Normal file
34
immer/heap/identity_heap.hpp
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
//
|
||||
// immer: immutable data structures for C++
|
||||
// Copyright (C) 2016, 2017, 2018 Juan Pedro Bolivar Puente
|
||||
//
|
||||
// This software is distributed under the Boost Software License, Version 1.0.
|
||||
// See accompanying file LICENSE or copy at http://boost.org/LICENSE_1_0.txt
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdlib>
|
||||
|
||||
namespace immer {
|
||||
|
||||
/*!
|
||||
* A heap that simply passes on to the parent heap.
|
||||
*/
|
||||
template <typename Base>
|
||||
struct identity_heap : Base
|
||||
{
|
||||
template <typename... Tags>
|
||||
static void* allocate(std::size_t size, Tags... tags)
|
||||
{
|
||||
return Base::allocate(size, tags...);
|
||||
}
|
||||
|
||||
template <typename... Tags>
|
||||
static void deallocate(std::size_t size, void* data, Tags... tags)
|
||||
{
|
||||
Base::deallocate(size, data, tags...);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace immer
|
||||
44
immer/heap/malloc_heap.hpp
Normal file
44
immer/heap/malloc_heap.hpp
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
//
|
||||
// immer: immutable data structures for C++
|
||||
// Copyright (C) 2016, 2017, 2018 Juan Pedro Bolivar Puente
|
||||
//
|
||||
// This software is distributed under the Boost Software License, Version 1.0.
|
||||
// See accompanying file LICENSE or copy at http://boost.org/LICENSE_1_0.txt
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <immer/config.hpp>
|
||||
|
||||
#include <cstdlib>
|
||||
#include <memory>
|
||||
|
||||
namespace immer {
|
||||
|
||||
/*!
|
||||
* A heap that uses `std::malloc` and `std::free` to manage memory.
|
||||
*/
|
||||
struct malloc_heap
|
||||
{
|
||||
/*!
|
||||
* Returns a pointer to a memory region of size `size`, if the
|
||||
* allocation was successful and throws `std::bad_alloc` otherwise.
|
||||
*/
|
||||
template <typename... Tags>
|
||||
static void* allocate(std::size_t size, Tags...)
|
||||
{
|
||||
auto p = std::malloc(size);
|
||||
if (IMMER_UNLIKELY(!p))
|
||||
throw std::bad_alloc{};
|
||||
return p;
|
||||
}
|
||||
|
||||
/*!
|
||||
* Releases a memory region `data` that was previously returned by
|
||||
* `allocate`. One must not use nor deallocate again a memory
|
||||
* region that once it has been deallocated.
|
||||
*/
|
||||
static void deallocate(std::size_t, void* data) { std::free(data); }
|
||||
};
|
||||
|
||||
} // namespace immer
|
||||
40
immer/heap/split_heap.hpp
Normal file
40
immer/heap/split_heap.hpp
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
//
|
||||
// immer: immutable data structures for C++
|
||||
// Copyright (C) 2016, 2017, 2018 Juan Pedro Bolivar Puente
|
||||
//
|
||||
// This software is distributed under the Boost Software License, Version 1.0.
|
||||
// See accompanying file LICENSE or copy at http://boost.org/LICENSE_1_0.txt
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <atomic>
|
||||
#include <cassert>
|
||||
|
||||
namespace immer {
|
||||
|
||||
/*!
|
||||
* Adaptor that uses `SmallHeap` for allocations that are smaller or
|
||||
* equal to `Size` and `BigHeap` otherwise.
|
||||
*/
|
||||
template <std::size_t Size, typename SmallHeap, typename BigHeap>
|
||||
struct split_heap
|
||||
{
|
||||
template <typename... Tags>
|
||||
static void* allocate(std::size_t size, Tags... tags)
|
||||
{
|
||||
return size <= Size ? SmallHeap::allocate(size, tags...)
|
||||
: BigHeap::allocate(size, tags...);
|
||||
}
|
||||
|
||||
template <typename... Tags>
|
||||
static void deallocate(std::size_t size, void* data, Tags... tags)
|
||||
{
|
||||
if (size <= Size)
|
||||
SmallHeap::deallocate(size, data, tags...);
|
||||
else
|
||||
BigHeap::deallocate(size, data, tags...);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace immer
|
||||
16
immer/heap/tags.hpp
Normal file
16
immer/heap/tags.hpp
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
//
|
||||
// immer: immutable data structures for C++
|
||||
// Copyright (C) 2016, 2017, 2018 Juan Pedro Bolivar Puente
|
||||
//
|
||||
// This software is distributed under the Boost Software License, Version 1.0.
|
||||
// See accompanying file LICENSE or copy at http://boost.org/LICENSE_1_0.txt
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace immer {
|
||||
|
||||
struct norefs_tag
|
||||
{};
|
||||
|
||||
} // namespace immer
|
||||
55
immer/heap/thread_local_free_list_heap.hpp
Normal file
55
immer/heap/thread_local_free_list_heap.hpp
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
//
|
||||
// immer: immutable data structures for C++
|
||||
// Copyright (C) 2016, 2017, 2018 Juan Pedro Bolivar Puente
|
||||
//
|
||||
// This software is distributed under the Boost Software License, Version 1.0.
|
||||
// See accompanying file LICENSE or copy at http://boost.org/LICENSE_1_0.txt
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <immer/heap/unsafe_free_list_heap.hpp>
|
||||
|
||||
namespace immer {
|
||||
namespace detail {
|
||||
|
||||
template <typename Heap>
|
||||
struct thread_local_free_list_storage
|
||||
{
|
||||
struct head_t
|
||||
{
|
||||
free_list_node* data;
|
||||
std::size_t count;
|
||||
|
||||
~head_t() { Heap::clear(); }
|
||||
};
|
||||
|
||||
static head_t& head()
|
||||
{
|
||||
thread_local static head_t head_{nullptr, 0};
|
||||
return head_;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
|
||||
/*!
|
||||
* Adaptor that does not release the memory to the parent heap but
|
||||
* instead it keeps the memory in a `thread_local` global free
|
||||
* list. Must be preceded by a `with_data<free_list_node, ...>` heap
|
||||
* adaptor. When the current thread finishes, the memory is returned
|
||||
* to the parent heap.
|
||||
*
|
||||
* @tparam Size Maximum size of the objects to be allocated.
|
||||
* @tparam Limit Maximum number of elements to keep in the free list.
|
||||
* @tparam Base Type of the parent heap.
|
||||
*/
|
||||
template <std::size_t Size, std::size_t Limit, typename Base>
|
||||
struct thread_local_free_list_heap
|
||||
: detail::unsafe_free_list_heap_impl<detail::thread_local_free_list_storage,
|
||||
Size,
|
||||
Limit,
|
||||
Base>
|
||||
{};
|
||||
|
||||
} // namespace immer
|
||||
109
immer/heap/unsafe_free_list_heap.hpp
Normal file
109
immer/heap/unsafe_free_list_heap.hpp
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
//
|
||||
// immer: immutable data structures for C++
|
||||
// Copyright (C) 2016, 2017, 2018 Juan Pedro Bolivar Puente
|
||||
//
|
||||
// This software is distributed under the Boost Software License, Version 1.0.
|
||||
// See accompanying file LICENSE or copy at http://boost.org/LICENSE_1_0.txt
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cassert>
|
||||
#include <immer/config.hpp>
|
||||
#include <immer/heap/free_list_node.hpp>
|
||||
|
||||
namespace immer {
|
||||
namespace detail {
|
||||
|
||||
template <typename Heap>
|
||||
struct unsafe_free_list_storage
|
||||
{
|
||||
struct head_t
|
||||
{
|
||||
free_list_node* data;
|
||||
std::size_t count;
|
||||
};
|
||||
|
||||
static head_t& head()
|
||||
{
|
||||
static head_t head_{nullptr, 0};
|
||||
return head_;
|
||||
}
|
||||
};
|
||||
|
||||
template <template <class> class Storage,
|
||||
std::size_t Size,
|
||||
std::size_t Limit,
|
||||
typename Base>
|
||||
class unsafe_free_list_heap_impl : Base
|
||||
{
|
||||
using storage = Storage<unsafe_free_list_heap_impl>;
|
||||
|
||||
public:
|
||||
using base_t = Base;
|
||||
|
||||
template <typename... Tags>
|
||||
static void* allocate(std::size_t size, Tags...)
|
||||
{
|
||||
assert(size <= sizeof(free_list_node) + Size);
|
||||
assert(size >= sizeof(free_list_node));
|
||||
|
||||
auto n = storage::head().data;
|
||||
if (!n) {
|
||||
auto p = base_t::allocate(Size + sizeof(free_list_node));
|
||||
return static_cast<free_list_node*>(p);
|
||||
}
|
||||
--storage::head().count;
|
||||
storage::head().data = n->next;
|
||||
return n;
|
||||
}
|
||||
|
||||
template <typename... Tags>
|
||||
static void deallocate(std::size_t size, void* data, Tags...)
|
||||
{
|
||||
assert(size <= sizeof(free_list_node) + Size);
|
||||
assert(size >= sizeof(free_list_node));
|
||||
|
||||
if (storage::head().count >= Limit)
|
||||
base_t::deallocate(Size + sizeof(free_list_node), data);
|
||||
else {
|
||||
auto n = static_cast<free_list_node*>(data);
|
||||
n->next = storage::head().data;
|
||||
storage::head().data = n;
|
||||
++storage::head().count;
|
||||
}
|
||||
}
|
||||
|
||||
static void clear()
|
||||
{
|
||||
while (storage::head().data) {
|
||||
auto n = storage::head().data->next;
|
||||
base_t::deallocate(Size + sizeof(free_list_node),
|
||||
storage::head().data);
|
||||
storage::head().data = n;
|
||||
--storage::head().count;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
|
||||
/*!
|
||||
* Adaptor that does not release the memory to the parent heap but
|
||||
* instead it keeps the memory in a global free list that **is not
|
||||
* thread-safe**. Must be preceded by a `with_data<free_list_node,
|
||||
* ...>` heap adaptor.
|
||||
*
|
||||
* @tparam Size Maximum size of the objects to be allocated.
|
||||
* @tparam Limit Maximum number of elements to keep in the free list.
|
||||
* @tparam Base Type of the parent heap.
|
||||
*/
|
||||
template <std::size_t Size, std::size_t Limit, typename Base>
|
||||
struct unsafe_free_list_heap
|
||||
: detail::unsafe_free_list_heap_impl<detail::unsafe_free_list_storage,
|
||||
Size,
|
||||
Limit,
|
||||
Base>
|
||||
{};
|
||||
|
||||
} // namespace immer
|
||||
43
immer/heap/with_data.hpp
Normal file
43
immer/heap/with_data.hpp
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
//
|
||||
// immer: immutable data structures for C++
|
||||
// Copyright (C) 2016, 2017, 2018 Juan Pedro Bolivar Puente
|
||||
//
|
||||
// This software is distributed under the Boost Software License, Version 1.0.
|
||||
// See accompanying file LICENSE or copy at http://boost.org/LICENSE_1_0.txt
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdio>
|
||||
|
||||
namespace immer {
|
||||
|
||||
/*!
|
||||
* Appends a default constructed extra object of type `T` at the
|
||||
* *before* the requested region.
|
||||
*
|
||||
* @tparam T Type of the appended data.
|
||||
* @tparam Base Type of the parent heap.
|
||||
*/
|
||||
template <typename T, typename Base>
|
||||
struct with_data : Base
|
||||
{
|
||||
using base_t = Base;
|
||||
|
||||
template <typename... Tags>
|
||||
static void* allocate(std::size_t size, Tags... tags)
|
||||
{
|
||||
auto p = base_t::allocate(size + sizeof(T), tags...);
|
||||
return new (p) T{} + 1;
|
||||
}
|
||||
|
||||
template <typename... Tags>
|
||||
static void deallocate(std::size_t size, void* p, Tags... tags)
|
||||
{
|
||||
auto dp = static_cast<T*>(p) - 1;
|
||||
dp->~T();
|
||||
base_t::deallocate(size + sizeof(T), dp, tags...);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace immer
|
||||
Loading…
Add table
Add a link
Reference in a new issue