Skip to content

Commit

Permalink
Merge branch 'ATOMIC-STACK' - simple atomic stack based on Loft
Browse files Browse the repository at this point in the history
* ATOMIC-STACK:
  ase/atomics.cc: add atomic_valuestack_test()
  ase/atomics.hh: add a simple AtomicStack<> with just push, pop, empty
  ase/loft.hh: reduce includes
  ase/loft.hh: allow constexpr Loft::Allocator and export allows_read_after_free
  misc/synsmell.py: escape FI[X]ME
  misc/synsmell.py: reduce false positives for the separate-body check

Signed-off-by: Tim Janik <[email protected]>
  • Loading branch information
tim-janik committed Feb 8, 2024
2 parents 789a493 + 83dabce commit 765600a
Show file tree
Hide file tree
Showing 4 changed files with 96 additions and 21 deletions.
29 changes: 27 additions & 2 deletions ase/atomics.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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<AisNode> stack;
Expand Down Expand Up @@ -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<std::string> 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
55 changes: 54 additions & 1 deletion ase/atomics.hh
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include <ase/platform.hh>
#include <atomic>
#include <boost/atomic/atomic.hpp> // Needed for gcc to emit CMPXCHG16B
#include <ase/loft.hh>

namespace Ase {

Expand All @@ -13,7 +14,6 @@ namespace Ase {
template<class T> using Atomic = boost::atomic<T>;

// == AtomicIntrusiveStack ==

/** Lock-free stack with atomic `push()` and `pop_all` operations.
* Relies on unqualified calls to `atomic<T*>& atomic_next_ptrref(T*)`.
*/
Expand Down Expand Up @@ -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<typename Value, template<class> 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<Node*> intr_ptr_ = nullptr; // atomic intrusive pointer
};
MpmcStack<Node> stack_;
Node*
node_realloc (Node *node)
{
static constexpr GrowOnlyAllocator<Node> grow_only_alloc;
static_assert (GrowOnlyAllocator<Node>::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<uint64>;

Expand Down
20 changes: 8 additions & 12 deletions ase/loft.hh
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@
#ifndef __ASE_LOFT_HH__
#define __ASE_LOFT_HH__

#include <ase/defs.hh>
#include <utility>
#include <ase/cxxaux.hh>

namespace Ase {

Expand Down Expand Up @@ -90,23 +89,20 @@ template<typename T>
class Allocator : AllocatorBase {
public:
using value_type = T;
Allocator () noexcept = default;
template<typename U>
Allocator(const Allocator<U>& other) noexcept {}
bool
operator== (const Allocator<T> &o) const noexcept { return true; }
bool
operator!= (const Allocator<T> &o) const noexcept { return !this->operator== (o); }
constexpr void
deallocate (T *p, size_t n) noexcept { loft_btfree (p, n); }
/**/ Allocator () noexcept = default;
template<typename U> Allocator (const Allocator<U>& other) noexcept {}
bool operator== (const Allocator<T> &o) const noexcept { return true; }
bool operator!= (const Allocator<T> &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<T*> (mem);
}
static constexpr bool allows_read_after_free = true;
};

} // Loft
Expand Down
13 changes: 7 additions & 6 deletions misc/synsmell.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down

0 comments on commit 765600a

Please sign in to comment.