• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 //  lock-free freelist
2 //
3 //  Copyright (C) 2008-2016 Tim Blechmann
4 //
5 //  Distributed under the Boost Software License, Version 1.0. (See
6 //  accompanying file LICENSE_1_0.txt or copy at
7 //  http://www.boost.org/LICENSE_1_0.txt)
8 
9 #ifndef BOOST_LOCKFREE_FREELIST_HPP_INCLUDED
10 #define BOOST_LOCKFREE_FREELIST_HPP_INCLUDED
11 
12 #include <cstring>
13 #include <limits>
14 #include <memory>
15 
16 #include <boost/array.hpp>
17 #include <boost/config.hpp>
18 #include <boost/cstdint.hpp>
19 #include <boost/noncopyable.hpp>
20 #include <boost/static_assert.hpp>
21 
22 #include <boost/align/align_up.hpp>
23 #include <boost/align/aligned_allocator_adaptor.hpp>
24 
25 #include <boost/lockfree/detail/atomic.hpp>
26 #include <boost/lockfree/detail/parameter.hpp>
27 #include <boost/lockfree/detail/tagged_ptr.hpp>
28 
29 #if defined(_MSC_VER)
30 #pragma warning(push)
31 #pragma warning(disable: 4100) // unreferenced formal parameter
32 #pragma warning(disable: 4127) // conditional expression is constant
33 #endif
34 
35 namespace boost    {
36 namespace lockfree {
37 namespace detail   {
38 
39 template <typename T,
40           typename Alloc = std::allocator<T>
41          >
42 class freelist_stack:
43     Alloc
44 {
45     struct freelist_node
46     {
47         tagged_ptr<freelist_node> next;
48     };
49 
50     typedef tagged_ptr<freelist_node> tagged_node_ptr;
51 
52 public:
53     typedef T *           index_t;
54     typedef tagged_ptr<T> tagged_node_handle;
55 
56     template <typename Allocator>
freelist_stack(Allocator const & alloc,std::size_t n=0)57     freelist_stack (Allocator const & alloc, std::size_t n = 0):
58         Alloc(alloc),
59         pool_(tagged_node_ptr(NULL))
60     {
61         for (std::size_t i = 0; i != n; ++i) {
62             T * node = Alloc::allocate(1);
63             std::memset(node, 0, sizeof(T));
64 #ifdef BOOST_LOCKFREE_FREELIST_INIT_RUNS_DTOR
65             destruct<false>(node);
66 #else
67             deallocate<false>(node);
68 #endif
69         }
70     }
71 
72     template <bool ThreadSafe>
reserve(std::size_t count)73     void reserve (std::size_t count)
74     {
75         for (std::size_t i = 0; i != count; ++i) {
76             T * node = Alloc::allocate(1);
77             std::memset(node, 0, sizeof(T));
78             deallocate<ThreadSafe>(node);
79         }
80     }
81 
82     template <bool ThreadSafe, bool Bounded>
construct(void)83     T * construct (void)
84     {
85         T * node = allocate<ThreadSafe, Bounded>();
86         if (node)
87             new(node) T();
88         return node;
89     }
90 
91     template <bool ThreadSafe, bool Bounded, typename ArgumentType>
construct(ArgumentType const & arg)92     T * construct (ArgumentType const & arg)
93     {
94         T * node = allocate<ThreadSafe, Bounded>();
95         if (node)
96             new(node) T(arg);
97         return node;
98     }
99 
100     template <bool ThreadSafe, bool Bounded, typename ArgumentType1, typename ArgumentType2>
construct(ArgumentType1 const & arg1,ArgumentType2 const & arg2)101     T * construct (ArgumentType1 const & arg1, ArgumentType2 const & arg2)
102     {
103         T * node = allocate<ThreadSafe, Bounded>();
104         if (node)
105             new(node) T(arg1, arg2);
106         return node;
107     }
108 
109     template <bool ThreadSafe>
destruct(tagged_node_handle const & tagged_ptr)110     void destruct (tagged_node_handle const & tagged_ptr)
111     {
112         T * n = tagged_ptr.get_ptr();
113         n->~T();
114         deallocate<ThreadSafe>(n);
115     }
116 
117     template <bool ThreadSafe>
destruct(T * n)118     void destruct (T * n)
119     {
120         n->~T();
121         deallocate<ThreadSafe>(n);
122     }
123 
~freelist_stack(void)124     ~freelist_stack(void)
125     {
126         tagged_node_ptr current = pool_.load();
127 
128         while (current) {
129             freelist_node * current_ptr = current.get_ptr();
130             if (current_ptr)
131                 current = current_ptr->next;
132             Alloc::deallocate((T*)current_ptr, 1);
133         }
134     }
135 
is_lock_free(void) const136     bool is_lock_free(void) const
137     {
138         return pool_.is_lock_free();
139     }
140 
get_handle(T * pointer) const141     T * get_handle(T * pointer) const
142     {
143         return pointer;
144     }
145 
get_handle(tagged_node_handle const & handle) const146     T * get_handle(tagged_node_handle const & handle) const
147     {
148         return get_pointer(handle);
149     }
150 
get_pointer(tagged_node_handle const & tptr) const151     T * get_pointer(tagged_node_handle const & tptr) const
152     {
153         return tptr.get_ptr();
154     }
155 
get_pointer(T * pointer) const156     T * get_pointer(T * pointer) const
157     {
158         return pointer;
159     }
160 
null_handle(void) const161     T * null_handle(void) const
162     {
163         return NULL;
164     }
165 
166 protected: // allow use from subclasses
167     template <bool ThreadSafe, bool Bounded>
allocate(void)168     T * allocate (void)
169     {
170         if (ThreadSafe)
171             return allocate_impl<Bounded>();
172         else
173             return allocate_impl_unsafe<Bounded>();
174     }
175 
176 private:
177     template <bool Bounded>
allocate_impl(void)178     T * allocate_impl (void)
179     {
180         tagged_node_ptr old_pool = pool_.load(memory_order_consume);
181 
182         for(;;) {
183             if (!old_pool.get_ptr()) {
184                 if (!Bounded) {
185                     T *ptr = Alloc::allocate(1);
186                     std::memset(ptr, 0, sizeof(T));
187                     return ptr;
188                 }
189                 else
190                     return 0;
191             }
192 
193             freelist_node * new_pool_ptr = old_pool->next.get_ptr();
194             tagged_node_ptr new_pool (new_pool_ptr, old_pool.get_next_tag());
195 
196             if (pool_.compare_exchange_weak(old_pool, new_pool)) {
197                 void * ptr = old_pool.get_ptr();
198                 return reinterpret_cast<T*>(ptr);
199             }
200         }
201     }
202 
203     template <bool Bounded>
allocate_impl_unsafe(void)204     T * allocate_impl_unsafe (void)
205     {
206         tagged_node_ptr old_pool = pool_.load(memory_order_relaxed);
207 
208         if (!old_pool.get_ptr()) {
209             if (!Bounded) {
210                 T *ptr = Alloc::allocate(1);
211                 std::memset(ptr, 0, sizeof(T));
212                 return ptr;
213             }
214             else
215                 return 0;
216         }
217 
218         freelist_node * new_pool_ptr = old_pool->next.get_ptr();
219         tagged_node_ptr new_pool (new_pool_ptr, old_pool.get_next_tag());
220 
221         pool_.store(new_pool, memory_order_relaxed);
222         void * ptr = old_pool.get_ptr();
223         return reinterpret_cast<T*>(ptr);
224     }
225 
226 protected:
227     template <bool ThreadSafe>
deallocate(T * n)228     void deallocate (T * n)
229     {
230         if (ThreadSafe)
231             deallocate_impl(n);
232         else
233             deallocate_impl_unsafe(n);
234     }
235 
236 private:
deallocate_impl(T * n)237     void deallocate_impl (T * n)
238     {
239         void * node = n;
240         tagged_node_ptr old_pool = pool_.load(memory_order_consume);
241         freelist_node * new_pool_ptr = reinterpret_cast<freelist_node*>(node);
242 
243         for(;;) {
244             tagged_node_ptr new_pool (new_pool_ptr, old_pool.get_tag());
245             new_pool->next.set_ptr(old_pool.get_ptr());
246 
247             if (pool_.compare_exchange_weak(old_pool, new_pool))
248                 return;
249         }
250     }
251 
deallocate_impl_unsafe(T * n)252     void deallocate_impl_unsafe (T * n)
253     {
254         void * node = n;
255         tagged_node_ptr old_pool = pool_.load(memory_order_relaxed);
256         freelist_node * new_pool_ptr = reinterpret_cast<freelist_node*>(node);
257 
258         tagged_node_ptr new_pool (new_pool_ptr, old_pool.get_tag());
259         new_pool->next.set_ptr(old_pool.get_ptr());
260 
261         pool_.store(new_pool, memory_order_relaxed);
262     }
263 
264     atomic<tagged_node_ptr> pool_;
265 };
266 
267 class
268 BOOST_ALIGNMENT( 4 ) // workaround for bugs in MSVC
269 tagged_index
270 {
271 public:
272     typedef boost::uint16_t tag_t;
273     typedef boost::uint16_t index_t;
274 
275     /** uninitialized constructor */
tagged_index(void)276     tagged_index(void) BOOST_NOEXCEPT //: index(0), tag(0)
277     {}
278 
279     /** copy constructor */
280 #ifdef BOOST_NO_CXX11_DEFAULTED_FUNCTIONS
tagged_index(tagged_index const & rhs)281     tagged_index(tagged_index const & rhs):
282         index(rhs.index), tag(rhs.tag)
283     {}
284 #else
285     tagged_index(tagged_index const & rhs) = default;
286 #endif
287 
tagged_index(index_t i,tag_t t=0)288     explicit tagged_index(index_t i, tag_t t = 0):
289         index(i), tag(t)
290     {}
291 
292     /** index access */
293     /* @{ */
get_index() const294     index_t get_index() const
295     {
296         return index;
297     }
298 
set_index(index_t i)299     void set_index(index_t i)
300     {
301         index = i;
302     }
303     /* @} */
304 
305     /** tag access */
306     /* @{ */
get_tag() const307     tag_t get_tag() const
308     {
309         return tag;
310     }
311 
get_next_tag() const312     tag_t get_next_tag() const
313     {
314         tag_t next = (get_tag() + 1u) & (std::numeric_limits<tag_t>::max)();
315         return next;
316     }
317 
set_tag(tag_t t)318     void set_tag(tag_t t)
319     {
320         tag = t;
321     }
322     /* @} */
323 
operator ==(tagged_index const & rhs) const324     bool operator==(tagged_index const & rhs) const
325     {
326         return (index == rhs.index) && (tag == rhs.tag);
327     }
328 
operator !=(tagged_index const & rhs) const329     bool operator!=(tagged_index const & rhs) const
330     {
331         return !operator==(rhs);
332     }
333 
334 protected:
335     index_t index;
336     tag_t tag;
337 };
338 
339 template <typename T,
340           std::size_t size>
341 struct compiletime_sized_freelist_storage
342 {
343     // array-based freelists only support a 16bit address space.
344     BOOST_STATIC_ASSERT(size < 65536);
345 
346     boost::array<char, size * sizeof(T) + 64> data;
347 
348     // unused ... only for API purposes
349     template <typename Allocator>
compiletime_sized_freelist_storageboost::lockfree::detail::compiletime_sized_freelist_storage350     compiletime_sized_freelist_storage(Allocator const & /* alloc */, std::size_t /* count */)
351     {
352         data.fill(0);
353     }
354 
nodesboost::lockfree::detail::compiletime_sized_freelist_storage355     T * nodes(void) const
356     {
357         char * data_pointer = const_cast<char*>(data.data());
358         return reinterpret_cast<T*>( boost::alignment::align_up( data_pointer, BOOST_LOCKFREE_CACHELINE_BYTES ) );
359     }
360 
node_countboost::lockfree::detail::compiletime_sized_freelist_storage361     std::size_t node_count(void) const
362     {
363         return size;
364     }
365 };
366 
367 template <typename T,
368           typename Alloc = std::allocator<T> >
369 struct runtime_sized_freelist_storage:
370     boost::alignment::aligned_allocator_adaptor<Alloc, BOOST_LOCKFREE_CACHELINE_BYTES >
371 {
372     typedef boost::alignment::aligned_allocator_adaptor<Alloc, BOOST_LOCKFREE_CACHELINE_BYTES > allocator_type;
373     T * nodes_;
374     std::size_t node_count_;
375 
376     template <typename Allocator>
runtime_sized_freelist_storageboost::lockfree::detail::runtime_sized_freelist_storage377     runtime_sized_freelist_storage(Allocator const & alloc, std::size_t count):
378         allocator_type(alloc), node_count_(count)
379     {
380         if (count > 65535)
381             boost::throw_exception(std::runtime_error("boost.lockfree: freelist size is limited to a maximum of 65535 objects"));
382         nodes_ = allocator_type::allocate(count);
383         std::memset(nodes_, 0, sizeof(T) * count);
384     }
385 
~runtime_sized_freelist_storageboost::lockfree::detail::runtime_sized_freelist_storage386     ~runtime_sized_freelist_storage(void)
387     {
388         allocator_type::deallocate(nodes_, node_count_);
389     }
390 
nodesboost::lockfree::detail::runtime_sized_freelist_storage391     T * nodes(void) const
392     {
393         return nodes_;
394     }
395 
node_countboost::lockfree::detail::runtime_sized_freelist_storage396     std::size_t node_count(void) const
397     {
398         return node_count_;
399     }
400 };
401 
402 
403 template <typename T,
404           typename NodeStorage = runtime_sized_freelist_storage<T>
405          >
406 class fixed_size_freelist:
407     NodeStorage
408 {
409     struct freelist_node
410     {
411         tagged_index next;
412     };
413 
initialize(void)414     void initialize(void)
415     {
416         T * nodes = NodeStorage::nodes();
417         for (std::size_t i = 0; i != NodeStorage::node_count(); ++i) {
418             tagged_index * next_index = reinterpret_cast<tagged_index*>(nodes + i);
419             next_index->set_index(null_handle());
420 
421 #ifdef BOOST_LOCKFREE_FREELIST_INIT_RUNS_DTOR
422             destruct<false>(nodes + i);
423 #else
424             deallocate<false>(static_cast<index_t>(i));
425 #endif
426         }
427     }
428 
429 public:
430     typedef tagged_index tagged_node_handle;
431     typedef tagged_index::index_t index_t;
432 
433     template <typename Allocator>
fixed_size_freelist(Allocator const & alloc,std::size_t count)434     fixed_size_freelist (Allocator const & alloc, std::size_t count):
435         NodeStorage(alloc, count),
436         pool_(tagged_index(static_cast<index_t>(count), 0))
437     {
438         initialize();
439     }
440 
fixed_size_freelist(void)441     fixed_size_freelist (void):
442         pool_(tagged_index(NodeStorage::node_count(), 0))
443     {
444         initialize();
445     }
446 
447     template <bool ThreadSafe, bool Bounded>
construct(void)448     T * construct (void)
449     {
450         index_t node_index = allocate<ThreadSafe>();
451         if (node_index == null_handle())
452             return NULL;
453 
454         T * node = NodeStorage::nodes() + node_index;
455         new(node) T();
456         return node;
457     }
458 
459     template <bool ThreadSafe, bool Bounded, typename ArgumentType>
construct(ArgumentType const & arg)460     T * construct (ArgumentType const & arg)
461     {
462         index_t node_index = allocate<ThreadSafe>();
463         if (node_index == null_handle())
464             return NULL;
465 
466         T * node = NodeStorage::nodes() + node_index;
467         new(node) T(arg);
468         return node;
469     }
470 
471     template <bool ThreadSafe, bool Bounded, typename ArgumentType1, typename ArgumentType2>
construct(ArgumentType1 const & arg1,ArgumentType2 const & arg2)472     T * construct (ArgumentType1 const & arg1, ArgumentType2 const & arg2)
473     {
474         index_t node_index = allocate<ThreadSafe>();
475         if (node_index == null_handle())
476             return NULL;
477 
478         T * node = NodeStorage::nodes() + node_index;
479         new(node) T(arg1, arg2);
480         return node;
481     }
482 
483     template <bool ThreadSafe>
destruct(tagged_node_handle tagged_index)484     void destruct (tagged_node_handle tagged_index)
485     {
486         index_t index = tagged_index.get_index();
487         T * n = NodeStorage::nodes() + index;
488         (void)n; // silence msvc warning
489         n->~T();
490         deallocate<ThreadSafe>(index);
491     }
492 
493     template <bool ThreadSafe>
destruct(T * n)494     void destruct (T * n)
495     {
496         n->~T();
497         deallocate<ThreadSafe>(static_cast<index_t>(n - NodeStorage::nodes()));
498     }
499 
is_lock_free(void) const500     bool is_lock_free(void) const
501     {
502         return pool_.is_lock_free();
503     }
504 
null_handle(void) const505     index_t null_handle(void) const
506     {
507         return static_cast<index_t>(NodeStorage::node_count());
508     }
509 
get_handle(T * pointer) const510     index_t get_handle(T * pointer) const
511     {
512         if (pointer == NULL)
513             return null_handle();
514         else
515             return static_cast<index_t>(pointer - NodeStorage::nodes());
516     }
517 
get_handle(tagged_node_handle const & handle) const518     index_t get_handle(tagged_node_handle const & handle) const
519     {
520         return handle.get_index();
521     }
522 
get_pointer(tagged_node_handle const & tptr) const523     T * get_pointer(tagged_node_handle const & tptr) const
524     {
525         return get_pointer(tptr.get_index());
526     }
527 
get_pointer(index_t index) const528     T * get_pointer(index_t index) const
529     {
530         if (index == null_handle())
531             return 0;
532         else
533             return NodeStorage::nodes() + index;
534     }
535 
get_pointer(T * ptr) const536     T * get_pointer(T * ptr) const
537     {
538         return ptr;
539     }
540 
541 protected: // allow use from subclasses
542     template <bool ThreadSafe>
allocate(void)543     index_t allocate (void)
544     {
545         if (ThreadSafe)
546             return allocate_impl();
547         else
548             return allocate_impl_unsafe();
549     }
550 
551 private:
allocate_impl(void)552     index_t allocate_impl (void)
553     {
554         tagged_index old_pool = pool_.load(memory_order_consume);
555 
556         for(;;) {
557             index_t index = old_pool.get_index();
558             if (index == null_handle())
559                 return index;
560 
561             T * old_node = NodeStorage::nodes() + index;
562             tagged_index * next_index = reinterpret_cast<tagged_index*>(old_node);
563 
564             tagged_index new_pool(next_index->get_index(), old_pool.get_next_tag());
565 
566             if (pool_.compare_exchange_weak(old_pool, new_pool))
567                 return old_pool.get_index();
568         }
569     }
570 
allocate_impl_unsafe(void)571     index_t allocate_impl_unsafe (void)
572     {
573         tagged_index old_pool = pool_.load(memory_order_consume);
574 
575         index_t index = old_pool.get_index();
576         if (index == null_handle())
577             return index;
578 
579         T * old_node = NodeStorage::nodes() + index;
580         tagged_index * next_index = reinterpret_cast<tagged_index*>(old_node);
581 
582         tagged_index new_pool(next_index->get_index(), old_pool.get_next_tag());
583 
584         pool_.store(new_pool, memory_order_relaxed);
585         return old_pool.get_index();
586     }
587 
588     template <bool ThreadSafe>
deallocate(index_t index)589     void deallocate (index_t index)
590     {
591         if (ThreadSafe)
592             deallocate_impl(index);
593         else
594             deallocate_impl_unsafe(index);
595     }
596 
deallocate_impl(index_t index)597     void deallocate_impl (index_t index)
598     {
599         freelist_node * new_pool_node = reinterpret_cast<freelist_node*>(NodeStorage::nodes() + index);
600         tagged_index old_pool = pool_.load(memory_order_consume);
601 
602         for(;;) {
603             tagged_index new_pool (index, old_pool.get_tag());
604             new_pool_node->next.set_index(old_pool.get_index());
605 
606             if (pool_.compare_exchange_weak(old_pool, new_pool))
607                 return;
608         }
609     }
610 
deallocate_impl_unsafe(index_t index)611     void deallocate_impl_unsafe (index_t index)
612     {
613         freelist_node * new_pool_node = reinterpret_cast<freelist_node*>(NodeStorage::nodes() + index);
614         tagged_index old_pool = pool_.load(memory_order_consume);
615 
616         tagged_index new_pool (index, old_pool.get_tag());
617         new_pool_node->next.set_index(old_pool.get_index());
618 
619         pool_.store(new_pool);
620     }
621 
622     atomic<tagged_index> pool_;
623 };
624 
625 template <typename T,
626           typename Alloc,
627           bool IsCompileTimeSized,
628           bool IsFixedSize,
629           std::size_t Capacity
630           >
631 struct select_freelist
632 {
633     typedef typename mpl::if_c<IsCompileTimeSized,
634                                compiletime_sized_freelist_storage<T, Capacity>,
635                                runtime_sized_freelist_storage<T, Alloc>
636                               >::type fixed_sized_storage_type;
637 
638     typedef typename mpl::if_c<IsCompileTimeSized || IsFixedSize,
639                                fixed_size_freelist<T, fixed_sized_storage_type>,
640                                freelist_stack<T, Alloc>
641                               >::type type;
642 };
643 
644 template <typename T, bool IsNodeBased>
645 struct select_tagged_handle
646 {
647     typedef typename mpl::if_c<IsNodeBased,
648                                tagged_ptr<T>,
649                                tagged_index
650                               >::type tagged_handle_type;
651 
652     typedef typename mpl::if_c<IsNodeBased,
653                                T*,
654                                typename tagged_index::index_t
655                               >::type handle_type;
656 };
657 
658 
659 } /* namespace detail */
660 } /* namespace lockfree */
661 } /* namespace boost */
662 
663 #if defined(_MSC_VER)
664 #pragma warning(pop)
665 #endif
666 
667 
668 #endif /* BOOST_LOCKFREE_FREELIST_HPP_INCLUDED */
669