diff --git a/ase/atomics.cc b/ase/atomics.cc index 68f2f66d..85c184b1 100644 --- a/ase/atomics.cc +++ b/ase/atomics.cc @@ -65,9 +65,9 @@ atomic_next_ptrref (AisNode *node) return node->next; } -TEST_INTEGRITY (atomic_stack_test); +TEST_INTEGRITY (atomic_mpmcstack_test); static void -atomic_stack_test() +atomic_mpmcstack_test() { bool was_empty; AtomicIntrusiveStack stack; @@ -158,4 +158,29 @@ mpmc_stack_test() assert (number_totals == COUNTING_THREADS * (NUMBER_NODES_PER_THREAD * (NUMBER_NODES_PER_THREAD + 1ull)) / 2); } +// == AtomicStack<> test == +TEST_INTEGRITY (atomic_valuestack_test); +static void +atomic_valuestack_test() +{ + AtomicStack sstack; + TASSERT (sstack.empty()); + bool was_empty, had; + std::string s = "foo"; + was_empty = sstack.push (s); + TASSERT (was_empty); + TASSERT (!sstack.empty()); + was_empty = sstack.push (std::string ("bar")); + TASSERT (!was_empty); + TASSERT (!sstack.empty()); + had = sstack.pop (s); + TASSERT (had && s == "bar"); + TASSERT (!sstack.empty()); + had = sstack.pop (s); + TASSERT (had && s == "foo"); + had = sstack.pop (s); + TASSERT (!had); + TASSERT (sstack.empty()); +} + } // Anon diff --git a/ase/atomics.hh b/ase/atomics.hh index ed7ee2fc..060e6bba 100644 --- a/ase/atomics.hh +++ b/ase/atomics.hh @@ -5,6 +5,7 @@ #include #include #include // Needed for gcc to emit CMPXCHG16B +#include namespace Ase { @@ -13,7 +14,6 @@ namespace Ase { template using Atomic = boost::atomic; // == AtomicIntrusiveStack == - /** Lock-free stack with atomic `push()` and `pop_all` operations. * Relies on unqualified calls to `atomic& atomic_next_ptrref(T*)`. */ @@ -172,6 +172,59 @@ private: static constexpr uintptr_t TAIL_ = ~uintptr_t (0); }; +// == AtomicStack == +/** Thread-safe, lock-free stack based on MpmcStack and an Allocator with `allows_read_after_free`. + */ +template class GrowOnlyAllocator = Loft::Allocator> +struct AtomicStack { + /// Return `true` if the stack holds no items. + bool empty() const { return stack_.empty(); } + /// Add `value` to top of the stack, returns if the stack was empty (true). + bool + push (Value &&value) + { + Node *node = node_realloc (nullptr); + std::construct_at (node, std::move (value)); + return stack_.push (node); + } + /// Add a copy of `value` to top of the stack, returns if the stack was empty (true). + bool + push (const Value &value) + { + return push (std::move (Value (value))); + } + /// Pop `value` from top of the stack, returns if `value` was reassigned (true), otherwise the stack was empty. + bool + pop (Value &value) + { + Node *node = stack_.pop(); + if (!node) + return false; + value = std::move (node->value); + std::destroy_at (node); + node_realloc (node); + return true; + } +private: + struct Node { + Value value = {}; + Atomic intr_ptr_ = nullptr; // atomic intrusive pointer + }; + MpmcStack stack_; + Node* + node_realloc (Node *node) + { + static constexpr GrowOnlyAllocator grow_only_alloc; + static_assert (GrowOnlyAllocator::allows_read_after_free == true); + if (node) { + grow_only_alloc.deallocate (node, 1); + node = nullptr; + } else + node = grow_only_alloc.allocate (1); + return node; + } +}; + // == AtomicBits == using AtomicU64 = std::atomic; diff --git a/ase/loft.hh b/ase/loft.hh index ab41433d..8420c0c4 100644 --- a/ase/loft.hh +++ b/ase/loft.hh @@ -2,8 +2,7 @@ #ifndef __ASE_LOFT_HH__ #define __ASE_LOFT_HH__ -#include -#include +#include namespace Ase { @@ -90,23 +89,20 @@ template class Allocator : AllocatorBase { public: using value_type = T; - Allocator () noexcept = default; - template - Allocator(const Allocator& other) noexcept {} - bool - operator== (const Allocator &o) const noexcept { return true; } - bool - operator!= (const Allocator &o) const noexcept { return !this->operator== (o); } - constexpr void - deallocate (T *p, size_t n) noexcept { loft_btfree (p, n); } + /**/ Allocator () noexcept = default; + template Allocator (const Allocator& other) noexcept {} + bool operator== (const Allocator &o) const noexcept { return true; } + bool operator!= (const Allocator &o) const noexcept { return !this->operator== (o); } + constexpr void deallocate (T *p, size_t n) const noexcept { loft_btfree (p, n); } [[nodiscard]] constexpr T* - allocate (std::size_t n) + allocate (std::size_t n) const { void *const mem = loft_btalloc (n * sizeof (T), alignof (T)); if (!mem) throw std::bad_alloc(); return reinterpret_cast (mem); } + static constexpr bool allows_read_after_free = true; }; } // Loft diff --git a/misc/synsmell.py b/misc/synsmell.py index 26063ec6..86134d9a 100755 --- a/misc/synsmell.py +++ b/misc/synsmell.py @@ -29,21 +29,22 @@ def line_matcher (code, text, has_comment, orig, filename): m = m.end() - 2 warning = "missing whitespace after parenthesis" # ban-fixme - elif m := checks['ban-fixme'] and has_comment and re.search (r'\bFIXME\b', text, re.IGNORECASE): + elif m := checks['ban-fixme'] and has_comment and re.search (r'\bFI[X]ME\b', text, re.IGNORECASE): error = "comment indicates unfinished code" # ban-todo elif m := checks['ban-todo'] and has_comment and re.search (r'\bTODO\b', text, re.IGNORECASE): error = "comment indicates open issues" # separate-body - elif m := checks['separate-body'] and re.match (r'\s*[\w <:,>]+\s*\(.*\)[\s\w]*{\s*(/[/*].*)?$', code): + elif m := checks['separate-body'] and re.match (r'\s*[\w <:,>]+\s*\((.+\s+.+)?\)[\s\w]*{\s*(/[/*].*)?$', code): m = m.start() + m.group().find ('{') - bad = not re.search (r'\]\s*\(', code) # ignore lambda - if bad: + ignore = bool (re.search (r'\]\s*\(', code)) # ignore lambda + ignore |= bool (re.search (r'\balignas\s*\(', code)) # ignore alignas() + if not ignore: for w in ('do', 'switch', 'while', 'for', 'if', 'namespace'): if re.search (r'\b' + w + r'\b', code): - bad = False + ignore = True break - if bad: + if not ignore: warning = "missing newline before function body" # print diagnostics if error or warning: