diff --git a/.github/workflows/build-and-test.yaml b/.github/workflows/build-and-test.yaml new file mode 100644 index 0000000..82dea33 --- /dev/null +++ b/.github/workflows/build-and-test.yaml @@ -0,0 +1,31 @@ +name: Build/Test + +on: + workflow_call: + workflow_dispatch: + inputs: + part: + required: false + default: '' + +jobs: + build: + name: Build the library and run tests + strategy: + matrix: + os: [ubuntu-latest, ubuntu-24.04, ubuntu-22.04, ubuntu-24.04-arm] + fail-fast: false + runs-on: ${{ matrix.os }} + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Build and install library + run: | + ./configure --enable-static --enable-shared --enable-debug + make + sudo make install + + - name: Run tests + run: | + make check diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml new file mode 100644 index 0000000..0689df6 --- /dev/null +++ b/.github/workflows/pr.yaml @@ -0,0 +1,8 @@ +name: PR + +on: + pull_request: + +jobs: + build: + uses: ./.github/workflows/build-and-test.yaml diff --git a/.gitignore b/.gitignore index dae939b..31d7455 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -.* +.git *.o *.lo *.out diff --git a/Changes b/Changes index 1d82048..d3549e7 100644 --- a/Changes +++ b/Changes @@ -2,3 +2,6 @@ Revision history for XRCU 0.1 19/03/2019 - First public version + +0.2 15/10/2025 + - Allow custom allocators for all containers diff --git a/Makefile b/Makefile index 9fd2629..cab7cab 100644 --- a/Makefile +++ b/Makefile @@ -11,10 +11,26 @@ endif STATIC_LIBS = libxrcu.$(STATIC_EXT) SHARED_LIBS = libxrcu.$(DYNAMIC_EXT) -HEADERS = xrcu.hpp stack.hpp hash_table.hpp skip_list.hpp \ - xatomic.hpp lwlock.hpp optional.hpp queue.hpp +ROOT_DIR := $(dir $(realpath $(lastword $(MAKEFILE_LIST)))) + +I = $(ROOT_DIR) +S = $(ROOT_DIR)src + +HEADERS = $(I)xrcu/xrcu.hpp \ + $(I)xrcu/stack.hpp \ + $(I)xrcu/hash_table.hpp \ + $(I)xrcu/skip_list.hpp \ + $(I)xrcu/xatomic.hpp \ + $(I)xrcu/lwlock.hpp \ + $(I)xrcu/queue.hpp + +OBJS = $(S)/xrcu.o \ + $(S)/hash_table.o \ + $(S)/queue.o \ + $(S)/stack.o \ + $(S)/lwlock.o \ + $(S)/utils.o -OBJS = xrcu.o hash_table.o stack.o lwlock.o skip_list.o queue.o utils.o LOBJS = $(OBJS:.o=.lo) TEST_OBJS = $(LOBJS) @@ -25,7 +41,7 @@ ALL_LIBS = $(STATIC_LIBS) $(SHARED_LIBS) AR = $(CROSS_COMPILE)ar RANLIB = $(CROSS_COMPILE)ranlib -CXXFLAGS += $(CXXFLAGS_AUTO) +CXXFLAGS += $(CXXFLAGS_AUTO) -I$(I) -D_DEFAULT_SOURCE all: $(ALL_LIBS) @@ -53,5 +69,5 @@ install: $(ALL_LIBS) cp $(HEADERS) $(includedir)/xrcu clean: - rm -rf *.o *.lo libxrcu.* tst + rm -rf $(S)/*.o $(S)/*.lo libxrcu.* tst diff --git a/configure b/configure index 3175556..46faecf 100755 --- a/configure +++ b/configure @@ -176,7 +176,7 @@ tryflag CXXFLAGS_TRY -Werror=ignored-optimization-argument tryflag CXXFLAGS_EXTRA -Wa,--noexecstack # See if the compiler accepts explicit standard versioning -tryflag CXXFLAGS -std=c++11 +tryflag CXXFLAGS -std=c++17 # Enable optimizations tryflag CXXFLAGS -O2 diff --git a/docs/xrcu.html b/docs/xrcu.html index d58166e..52d7e0e 100644 --- a/docs/xrcu.html +++ b/docs/xrcu.html @@ -1,153 +1,17 @@ + + -XRCU documentation +XRCU is a library that provides efficient lock-less synchronization for read-mostly tasks and structures + - + + + + -
  • Interlude: optionals - -
  • Stacks
  • Queues
  • Skip lists
  • Hash tables
  • @@ -243,7 +101,7 @@

    API design

    RCU API

    -
      #include <xrcu/xrcu.hpp>
    +
    #include <xrcu/xrcu.hpp>

    The following section documents the RCU API that is exposed in XRCU. It's used internally quite a bit by other parts of the library, yet it's very useful by itself when implementing other concurrent algorithms or data structures.

    @@ -293,11 +151,11 @@

    RCU finalizable objects

    As it was mentioned before, XRCU defines a type called finalizable that is specifically designed to make its destruction safe (i.e: Only once all threads are outside a critical section). This type defines the following interface:

    -
      struct finalizable
    -    {
    -      virtual void safe_destroy ();
    -      virtual ~finalizable ();
    -    };
    +
    struct finalizable
    +  {
    +    virtual void safe_destroy ();
    +    virtual ~finalizable ();
    +  };

    Under most circumstances, it's enough for a user-defined type to derive from finalizable and leave it at that. However, the above 2 methods are provided as virtual for customization's sake. When a finalizable object is reclaimed by the RCU subsystem, it will call the safe_destroy method. The default implementation simply calls the object's destructor and frees the memory associated to it. If, for whatever reason, a user wants to override this behaviour, they may do so by extending either of those methods.

    @@ -327,12 +185,12 @@

    Miscellaneous functions

    These functions don't really belong anywhere else, but they are included in this file for convenience's sake:

    -
        struct atfork
    -      {
    -        void (*prepare) (void);
    -        void (*parent) (void);
    -        void (*child) (void);
    -      };
    +
    struct atfork
    +  {
    +    void (*prepare) (void);
    +    void (*parent) (void);
    +    void (*child) (void);
    +  };
    @@ -362,134 +220,9 @@

    Implementation details

    In this implementation, the most expensive operation is undoubtedly sync. It works by locking the global registry, then checking if any thread is in a critical section, and sleeping for short periods of time in case there are. The overhead associated to sync is the main reason why critical sections should be short, and also why finalizable objects are accumulated instead of being reclaimed right away.

    -

    Interlude: optionals

    - -
        #include <xrcu/optional.hpp>
    - -

    Before moving on to the topic of containers, we need to take a look at an auxiliary structure implemented to make things easier and more convenient for users. This is the optional type.

    - -

    An optional is a template type that can hold either an object of type T, or be in an uninitialized state. In other words, the value may or may not be present. Unlike the usage of a pointer which may be null, an optional never performs dynamic memory allocation, as the space required to hold the value is always there, even if it's not being used.

    - -

    Optional types have been standardized in C++17, and are present in many other libraries as well. However, since XRCU aims to work with the base minimum of C++11 compliant compilers, and because it's designed not to depend on other libraries or frameworks, it contains its own (lightweight) implementation.

    - -

    Optional API

    - -

    An optional is defined as such:

    - -
        template <class T>
    -    struct optional
    -      {
    -      };
    - -

    And its public interface is the following:

    - -
    - -
    optional ();
    -
    - -

    Default constructor. Initializes the optional without a value.

    - -
    -
    optional (const T& value);
    -
    - -

    Initializes the optional to contain value.

    - -
    -
    optional (const optional<T>& other);
    -
    - -

    Copy constructor. If other has a value, then it initializes the optional to contain that same value. Otherwise, the optional will contain no value.

    - -
    -
    optional (T&& value);
    -
    - -

    Move constructor. Initializes the optional by taking ownership of value.

    - -
    -
    optional (optional<T>&& other);
    -
    - -

    Move constructor. If other has a value, then the optional is initialized by taken ownership of it. Otherwise, the optional will have no value.

    - -
    -
    T& operator* ();
    -
    - -
    -
    const T& operator* () const;
    -
    - -

    Returns a (possibly const) reference to the optional's value. The results are undefined if the optional does not contain one.

    - -
    -
    T* operator-> ();
    -
    - -
    -
    const T* operator-> () const;
    -
    - -

    Returns a (possibly const) pointer to the optional's value. The results are undefined if the optional does not contain one.

    - -
    -
    bool has_value () const;
    -
    - -

    Returns true if the optional has a value.

    - -
    -
    void reset () const;
    -
    - -

    If the optional contains a value, call its destructor and make the optional contain no value afterwards. Otherwise, there are no effects.

    - -
    -
    optional& operator= (const optional<T>& other);
    -
    - -

    If other has a value, assigns it to the optional. Otherwise, calls reset on the optional and leaves it without a value. Returns *this.

    - -
    -
    optional& operator= (const T& value);
    -
    - -

    Assigns value to the optional. Returns *this.

    - -
    -
    optional& operator= (optional<T>&& other);
    -
    - -

    If other has a value, assigns it to the optional by moving it. Otherwise, calls reset on the optional and leaves it without a value. Returns *this.

    - -
    -
    optional& operator= (T&& value);
    -
    - -

    Assigns value to the optional by moving it. Returns *this.

    - -
    -
    ~optional ();
    -
    - -

    Destroys the value associated to the optional, if it had any.

    - -
    -
    - -

    Implementation details

    - -

    Optionals are rather simple: They are implemented by using a flat buffer on which placement new is called. Once an object has been constructed, a pointer is cached for the buffer. This pointer will be used when fetching the value, or destroying the object (it's null for empty optionals).

    - -

    Optionals are very useful when performing lookups in mapped containers, since they allow us to bypass the need of additional output parameters to determine if a search was successful in an atomic way.

    - -

    Another reason as to why optional values are used so extensively in XRCU is because we consider them to be the best way to signal both a valid object, as well as an error. When using lock-less data structures, it's impossible to determine beforehand whether an error will occur when calling a method (because multithreading makes things non deterministic). Throwing an exception was considered a bit too "punishing" - it's not precisely a programming error, after all.

    -

    Stacks

    -
      #include <xrcu/stack.hpp>
    +
    #include <xrcu/stack.hpp>

    Stacks are the simplest of the lock-free data structures: They represent a basic LIFO container on which you can push and pop items at the back of the stack. Although their functionality is a bit more limited than other structures, they are still very useful to implement things like atomic free lists.

    @@ -499,20 +232,20 @@

    Stack API

    Stacks are templated types that can be instantiated with a type T:

    -
      template <class T>
    -  struct stack
    -    {
    -      typedef T value_type;
    -      typedef T& reference;
    -      typedef const T& const_reference;
    -      typedef T* pointer;
    -      typedef const T* const_pointer;
    -      typedef ptrdiff_t difference_type;
    -      typedef size_t size_type;
    -
    -      struct iterator;
    -      struct const_iterator;
    -    };
    +
    template <typename T, typename Alloc = std::allocator<T>>
    +struct stack
    +  {
    +    typedef T value_type;
    +    typedef T& reference;
    +    typedef const T& const_reference;
    +    typedef T* pointer;
    +    typedef const T* const_pointer;
    +    typedef ptrdiff_t difference_type;
    +    typedef size_t size_type;
    +
    +    struct iterator;
    +    struct const_iterator;
    +  };

    In C++'s parlance, a stack iterator is a forward iterator, and it may be used in any function that accepts them.

    @@ -528,13 +261,13 @@

    Stack API

    Default constructor. Initializes a stack to be empty.

    -
    template <class Integer> stack (Integer n, T value);
    +
    template <typename Integer> stack (Integer n, T value);

    Initializes a stack to contain n times value.

    -
    template <class Iter> stack (Iter first, Iter last);
    +
    template <typename Iter> stack (Iter first, Iter last);

    Initializes a stack with the values in the range [first, last)

    @@ -564,31 +297,31 @@

    Stack API

    Pushes value to the top of the stack.

    -
    template <class Iter> void push (Iter first, Iter last)
    +
    template <typename Iter> void push (Iter first, Iter last)

    Pushes the values in the range [first, last) to the stack.

    -
    template <class Integer> void push (Integer n, T value)
    +
    template <typename Integer> void push (Integer n, T value)

    Pushes n times value to the stack.

    -
    template<class ...Args> void emplace (Args&& ...args)
    +
    template<typename ...Args> void emplace (Args&& ...args)

    Same as push, only it constructs a new item by calling the move constructor with args....

    -
    optional<T> pop ();
    +
    std::optional<T> pop ();

    Removes the item at the top of the stack, and returns an optional with that value. If the stack is empty, the method returns an optional with no value.

    -
    optional<T> top ();
    +
    std::optional<T> top ();

    Fetches the current item at the top of the stack, and returns an optional with it as its value. If the stack is empty, returns an optional with no value.

    @@ -612,49 +345,49 @@

    Stack API

    Returns true if the stack is empty (equivalent to size () == 0).

    -
    void swap (stack<T>& other);
    +
    void swap (stack<T, Alloc>& other);

    Swaps the contents of the stack with other, but only if other is not the same object.

    -
    stack<T>& operator= (const stack<T>& other);
    +
    stack& operator= (const stack& other);

    Assigns the contents of other to the stack and returns *this.

    -
    stack<T>& operator= (stack<T>&& other);
    +
    stack& operator= (stack&& other);

    Move assignment. Takes ownership of the elements in other. Returns *this.

    -
    bool operator== (const stack<T>& other) const;
    +
    bool operator== (const stack& other) const;

    Compares the elements of the stack with the ones in other. Returns true if they are all equal.

    -
    bool operator!= (const stack<T>& other) const;
    +
    bool operator!= (const stack& other) const;

    Compares the elements of the stack with the ones in other. Returns true if any two of them are not equal.

    -
    bool operator< (const stack<T>& other) const;
    +
    bool operator< (const stack& other) const;
    -
    bool operator> (const stack<T>& other) const;
    +
    bool operator> (const stack& other) const;
    -
    bool operator<= (const stack<T>& other) const;
    +
    bool operator<= (const stack& other) const;
    -
    bool operator>= (const stack<T>& other) const;
    +
    bool operator>= (const stack& other) const;

    Lexicographically compares the elements of the stack with the ones in other, in a way that is equivalent to calling std::lexicographical_compare.

    @@ -666,7 +399,7 @@

    Stack API

    Removes every element from the stack.

    -
    template <class T1, class T2> void assign (T1 x, T2 y);
    +
    template <typename T1, typename T2> void assign (T1 x, T2 y);

    Assigns to the stack the elements described by (x, y). They could be an iterator range, or a pair of (integer, value), as is the case with the stack's constructor.

    @@ -754,17 +487,17 @@

    Stack API

    -

    Implementation details

    +

    Implementation details

    There really isn't much to say about the internal details of the stack. It's probably the easiest lock free structure to implement. As with most other implementations, the one in XRCU simply consists of an atomic pointer to the top node. The use of RCU prevents the biggest issue with this design, that is, the ABA problem.

    -

    The only noteworthy thing to point out is that the swap method is safe to call from multiple threads as well. In order to achive this, the library uses a special sentinel bit, that is temporarily set as the head node when a swap is undergoing. During a swap, push and pop cannot proceed, since they check against that the special bit is not set before modifying the stack.

    +

    The only noteworthy thing to point out is that the swap method is safe to call from multiple threads as well. In order to achive this, the library uses a special sentinel bit that is temporarily set at the head node when a swap is undergoing. During a swap, push and pop cannot proceed, since they check that this special bit is not set before modifying the stack.

    Note that because stack iterators are implicit critical sections, and because of stacks' implementation, iterating a stack will always be safe, even in the presence of operations like push and pop. The only way for an iterator to be invalidated is it's at the beggining of the stack, and a call to pop is made. Even then, dereferencing the iterator is valid, but advancing it will end prematurely, since the object was unlinked from the stack.

    Queues

    -
        #include <xrcu/queue.hpp>
    +
    #include <xrcu/queue.hpp>

    This is a multi-producer, multi-consumer, FIFO queue. Much like the stack, elements can be pushed or popped from it, but the order is different: They will be retrieved in the same order they were inserted.

    @@ -774,20 +507,20 @@

    Queue API

    Queues are templated types, and they may be instantiated with a type T:

    -
      template <class T>
    -  struct queue
    -    {
    -      typedef T value_type;
    -      typedef T& reference;
    -      typedef const T& const_reference;
    -      typedef T* pointer;
    -      typedef const T* const_pointer;
    -      typedef ptrdiff_t difference_type;
    -      typedef size_t size_type;
    -
    -      struct iterator;
    -      struct const_iterator;
    -    };
    +
    template <typename T, typename Alloc = std::allocator<T>>
    +struct queue
    +  {
    +    typedef T value_type;
    +    typedef T& reference;
    +    typedef const T& const_reference;
    +    typedef T* pointer;
    +    typedef const T* const_pointer;
    +    typedef ptrdiff_t difference_type;
    +    typedef size_t size_type;
    +
    +    struct iterator;
    +    struct const_iterator;
    +  };

    As with the stack, a queue's iterator can be considered a forward iterator, and it's also a cs_guard, so that a queue may be examined by an iterator, at any time.

    @@ -801,13 +534,13 @@

    Queue API

    Default constructor. Initializes a queue to be empty.

    -
    template <class Integer> queue (Integer n, T value);
    +
    template <typename Integer> queue (Integer n, T value);

    Initializes a queue to contain n times value.

    -
    template <class Iter> queue (Iter first, Iter last);
    +
    template <typename Iter> queue (Iter first, Iter last);

    Initializes a queue with the values in the range [first, last).

    @@ -819,13 +552,13 @@

    Queue API

    Initializes a queue with the values in lst.

    -
    queue (const queue<T>& other);
    +
    queue (const queue& other);

    Copy constructor. Initializes a queue with the values in other.

    -
    queue (queue<T>&& other);
    +
    queue (queue&& other);

    Move constructor. Takes ownership of the values in other.

    @@ -837,25 +570,25 @@

    Queue API

    Pushes value to the top of the queue.

    -
    template <class ...Args> void emplace (Args&& ...args)
    +
    template <typename ...Args> void emplace (Args&& ...args)

    Same as push, only the new item is constructed by calling the move constructor with args....

    -
    optional<T> pop ();
    +
    std::optional<T> pop ();

    Removes the first item from the queue, and returns an optional with that value. If the queue is empty, the method returns an optional with no value.

    -
    optional<T> front () const;
    +
    std::optional<T> front () const;

    Returns an optional with the first value from the queue. If the queue is empty, an optional with no value is returned instead.

    -
    optional<T> back () const;
    +
    std::optional<T> back () const;

    Returns an optional with the last value from the queue. If the queue is empty, an optional with no value is returned instead.

    @@ -873,49 +606,49 @@

    Queue API

    Returns the maximum allowed size for the queue.

    -
    void swap (queue<T>& other);
    +
    void swap (queue& other);

    Swaps the contents of the queue with other, but only if other is not the same object.

    -
    queue<T>& operator= (const queue<T>& other);
    +
    queue& operator= (const queue& other);

    Assigns the contents of other to the queue and returns *this.

    -
    queue<T>& operator= (queue<T>&& other);
    +
    queue& operator= (queue&& other);

    Move assignment. Takes ownership of the elements in other. Returns *this.

    -
    bool operator== (const queue<T>& other);
    +
    bool operator== (const queue& other);

    Compares the elements of the queue with the ones in other. Returns true if they are all equal.

    -
    bool operator!= (const queue<T>& other);
    +
    bool operator!= (const queue& other);

    Compares the elements of the queue with the ones in other. Returns true if any two of them are not equal.

    -
    bool operator< (const queue<T>& other) const;
    +
    bool operator< (const queue& other) const;
    -
    bool operator> (const queue<T>& other) const;
    +
    bool operator> (const queue& other) const;
    -
    bool operator<= (const queue<T>& other) const;
    +
    bool operator<= (const queue& other) const;
    -
    bool operator>= (const queue<T>& other) const;
    +
    bool operator>= (const queue& other) const;

    Lexicographically compares the elements of the queue with the ones in other, in a way that is equivalent to calling std::lexicographical_compare.

    @@ -927,7 +660,7 @@

    Queue API

    Removes every element from the queue.

    -
    template <class T1, class T2> void assign (T1 x, T2 y);
    +
    template <typename T1, typename T2> void assign (T1 x, T2 y);

    Assigns to the queue the elements described by (x, y). They can be an iterator range or a pair of (integer, value), as is the case with the queue's constructor.

    @@ -1005,7 +738,7 @@

    Queue API

    -

    Implementation details

    +

    Implementation details

    The design of the multi-producer, multi-consumer queue in XRCU is rather simple and different from other implementations. It consists of a single array that holds either the elements themselvers, or pointers to them, a decision taken at compile time via type traits.

    @@ -1017,7 +750,7 @@

    Implementation details

    Skip lists

    -
        #include <xrcu/skip_list.hpp>
    +
    #include <xrcu/skip_list.hpp>

    A skip list is an associative container that holds a sorted set of unique objects of a particular type. In addition to the Key type, skip lists are instantiated with a comparator type that allows them to determine ordering.

    @@ -1027,23 +760,24 @@

    Skip list API

    Skip lists are templated types, defined in the following way:

    -
        template <class T, class Cmp = std::less<T> >
    -    struct skip_list
    -      {
    -        typedef T value_type;
    -        typedef T key_type;
    -        typedef Cmp key_compare;
    -        typedef Cmp value_compare;
    -        typedef T& reference;
    -        typedef const T& const_reference;
    -        typedef T* pointer;
    -        typedef const T* const_pointer;
    -        typedef ptrdiff_t difference_type;
    -        typedef size_t size_type;
    -
    -        struct iterator;
    -        struct const_iterator;
    -      };
    +
    template <typename T, typename Cmp = std::less<T>,
    +          typename Alloc = std::allocator<T>>
    +struct skip_list
    +  {
    +    typedef T value_type;
    +    typedef T key_type;
    +    typedef Cmp key_compare;
    +    typedef Cmp value_compare;
    +    typedef T& reference;
    +    typedef const T& const_reference;
    +    typedef T* pointer;
    +    typedef const T* const_pointer;
    +    typedef ptrdiff_t difference_type;
    +    typedef size_t size_type;
    +
    +    struct iterator;
    +    struct const_iterator;
    +  };

    The template parameter T refers to the key type, whereas Cmp is the comparator type, which defaults to std::less. Under most circumstances, that is usually enough, but users may instantiate with any other type that defines the operator() which returns a boolean.

    @@ -1059,7 +793,7 @@

    Skip list API

    Initializes the skip list with comparator c, which defaults to a default constructed value. Also takes a parameter indicating the maximum depth a skip list node may have, which defaults to an implementation-specified value. Users shouldn't need to change the latter, but it can be useful when tuning the application for performance. As a general rule, a higher value implies more memory usage, but better performance.

    -
    template <class Iter> skip_list (Iter first, Iter last, Cmp c = Cmp (), unsigned int depth = ...);
    +
    template <typename Iter> skip_list (Iter first, Iter last, Cmp c = Cmp (), unsigned int depth = ...);

    Initializes the skip list to the values in [first, last). The comparator and depth parameters are the same as explained above.

    @@ -1071,19 +805,19 @@

    Skip list API

    Initializes the skip list to the values in lst. The comparator and depth parameters are the same as explained above.

    -
    skip_list (const skip_list<T, Cmp>& other);
    +
    skip_list (const skip_list& other);

    Copy constructor. Initializes the skip list to hold the values in other.

    -
    skip_list (skip_list<T, Cmp>&& other);
    +
    skip_list (skip_list&& other);

    Move constructor. Takes ownership of the values in other.

    -
    optional<T> find (const T& key) const;
    +
    std::optional<T> find (const T& key) const;

    Searches for an element equivalent to key in the skip list, and returns an optional with it as its value. If the key couldn't be found, the returned optional has no value.

    @@ -1107,7 +841,7 @@

    Skip list API

    Erases key from the skip list. Returns true if the key was present previous to this call.

    -
    optional<T> remove (const T& key);
    +
    std::optional<T> remove (const T& key);

    Erases key from the skip list and returns an optional with that element as its value, if the key was present. Otherwise, the returned optional has no value.

    @@ -1151,7 +885,7 @@

    Skip list API

    Returns true if the skip list is empty (i.e: its size is 0).

    -
    template <class Iter> void assign (Iter first, Iter last);
    +
    template <typename Iter> void assign (Iter first, Iter last);

    Assigns to the skip list the elements in [first, last).

    @@ -1163,19 +897,19 @@

    Skip list API

    Assigns to the skip list the elements in lst.

    -
    skip_list& operator= (const skip_list<T, Cmp>& other);
    +
    skip_list& operator= (const skip_list& other);

    Assigns the elements in other to the skip list. Returns *this.

    -
    skip_list& operator= (skip_list<T, Cmp>&& other);
    +
    skip_list& operator= (skip_list&& other);

    Move assignment. Takes ownership of the elements in other. Returns *this.

    -
    void swap (skip_list<T, Cmp>& other);
    +
    void swap (skip_list& other);

    Swaps the contents of the skip list with other, but only if other is a different object.

    @@ -1189,7 +923,7 @@

    Skip list API

    -

    Implementation details

    +

    Implementation details

    In XRCU, skip lists are implemented as described in any piece of literature that talks about them. Basically, every skip list of depth D has a head node that can be linked with up to other D nodes. When performing a lookup for an element, we start at the head node, and move horizontally until the current element is equal or greater. If it's equal, we the lookup succeeded. Otherwise, we move vertically to the next node, until we either find the element, or we exhausted every node.

    @@ -1201,7 +935,7 @@

    Implementation details

    Hash tables

    -
        #include <xrcu/hash_table.hpp>
    +
    #include <xrcu/hash_table.hpp>

    Hash tables are associative containers that map unique keys to values. Ordering is unspecified for both keys and values. In addition to the key and value types, hash tables are instantiated with a hashing type and an equality type; callables that compute a hash value for a given key, and one that tests for equality, given two keys, respectively.

    @@ -1213,26 +947,27 @@

    Hash table API

    As mentioned above, hash tables are template types, defined like this:

    -
        template <class Key, class Val,
    -              class Equal = std::equal<Key>,
    -              class Hash = std::hash<Key> >
    -    struct hash_table
    -      {
    -        typedef Val mapped_type;
    -        typedef Key key_type;
    -        typedef std::pair<Key, Val> value_type;
    -        typedef Equal key_equal;
    -        typedef Hash hasher;
    -        typedef value_type& reference;
    -        typedef const value_type& const_reference;
    -        typedef value_type* pointer;
    -        typedef const value_type* const_pointer;
    -        typedef ptrdiff_t difference_type;
    -        typedef size_t size_type;
    -
    -        struct iterator;
    -        struct const_iterator;
    -      };
    +
    template <typename Key, typename Val,
    +          typename Equal = std::equal<Key>,
    +          typename Hash = std::hash<Key>,
    +          typename Alloc = std::allocator<std::pair<Key, Val>>>
    +struct hash_table
    +  {
    +    typedef Val mapped_type;
    +    typedef Key key_type;
    +    typedef std::pair<Key, Val> value_type;
    +    typedef Equal key_equal;
    +    typedef Hash hasher;
    +    typedef value_type& reference;
    +    typedef const value_type& const_reference;
    +    typedef value_type* pointer;
    +    typedef const value_type* const_pointer;
    +    typedef ptrdiff_t difference_type;
    +    typedef size_t size_type;
    +
    +    struct iterator;
    +    struct const_iterator;
    +  };

    The template parameters should be pretty self explanatory: They refer to the key, value, equality and hashing types, in that order. The Equal type has to operate on two keys and return a boolean value that determines their equality, whereas the Hash type has to operate on keys and return unsigned integers. Both are allowed to throw exceptions, although it is not really wise to do so.

    @@ -1248,25 +983,25 @@

    Hash table API

    Initializes the hash table to hold size elements, with a load factor of lf, using the equality predicate e and the hasher h. The load factor must be in the range [0.4, 0.9]; if it's not, then it will silently be set to the default of 0.85.

    -
    template <class It> hash_table (It first, It last, float lf = 0.85, Equal e = Equal (), Hash h = Hash ())
    +
    template <typename It> hash_table (It first, It last, float lf = 0.85, Equal e = Equal (), Hash h = Hash ())

    Initializes the hash table with the values between (first, last]. The elements in that range must have two public members defined: first and second, referring to the key and value of each element, respectively, and in a similar fashion to what std::pair does. The other parameters work as with the previous constructor.

    -
    hash_table (std::initializer_list<std::pair<Key, Val> >, float lf = 0.85, Equal e = Equal (), Hash h = Hash ())
    +
    hash_table (std::initializer_list<std::pair<Key, Val>>, float lf = 0.85, Equal e = Equal (), Hash h = Hash ())

    Initializes the hash table with the values in lst. The rest of the parameters work as with the previous constructors.

    -
    hash_table (const hash_table<Key, Val, Hash, Equal>& other);
    +
    hash_table (const hash_table& other);

    Copy constructor. Initializes the hash table with the values in other.

    -
    hash_table (hash_table<Key, Val, Hash, Equal>&& other);
    +
    hash_table (hash_table&& other);

    Move constructor. Takes ownership of the values in other.

    @@ -1290,7 +1025,7 @@

    Hash table API

    Returns true if the hash table is empty.

    -
    optional<Val> find (const Key& key) const;
    +
    std::optional<Val> find (const Key& key) const;
    @@ -1312,12 +1047,12 @@

    Hash table API

    Associates key with val in the hash table. Returns true if the value was not present. Otherwise, the former value is replaced.

    -
    template <class Fn, class ...Args> bool update (const Key& key, Fn f, Args... args);
    +
    template <typename Fn, typename ...Args> bool update (const Key& key, Fn f, Args... args);

    Updates the value associated to key by calling f with it and the rest of the arguments. In other words, if val is the value associated to key, calls f (val, args...) and updates the value with the result. If no value was present for key, the function is called with a default constructed value instead (as if by calling Val ()).

    -

    Note that the function takes a reference to the value, and so it's possible to modify it in place. Additionally, if the function returns the very same object that was passed (i.e: the same reference that it received), there won't be any updates made. The function can also return a freshly made object, and update will work the same. The function may be called more than once if the atomic updates fail.

    +

    Note that the function takes a reference to the value, and so it's possible to modify it in place. Additionally, if the function returns the very same object that was passed (i.e: the same reference that it received), there won't be any modifications made to the value in the table. If, however, the function returns a different object, this call will need to atomically update the value. The passed function may be called more than once if the atomic updates fail.

    Returns true if key was not present in the hash table before the call.

    @@ -1340,7 +1075,7 @@

    Hash table API

    Removes every element from the hash table.

    -
    template <class Iter> void assign (Iter first, Iter last);
    +
    template <typename Iter> void assign (Iter first, Iter last);

    Assigns the elements in [first, last) to the hash table. The same restrictions as the range constructor apply here.

    @@ -1352,19 +1087,19 @@

    Hash table API

    Assigns the elements in lst to the hash table.

    -
    hash_table<Key, Val>& operator= (const hash_table<Key, Val>& other);
    +
    hash_table& operator= (const hash_table& other);

    Assigns the elements in other to the hash table. Returns *this.

    -
    hash_table<Key, Val>& operator= (hash_table<Key, Val>&& other);
    +
    hash_table& operator= (hash_table&& other);

    Move assignment. Takes ownership of the elements in other. Returns *this.

    -
    void swap (hash_table<Key, Val>& other);
    +
    void swap (hash_table& other);

    Swaps the contents of the hash table with other, but only if other is a different object.

    @@ -1444,7 +1179,7 @@

    Hash table API

    -

    Implementation details

    +

    Implementation details

    Hash tables are somewhat complex, because the atomicity requirements force us to do some rather convoluted things. To start off, a hash table is essentially a vector of consecutive key and value pairs, with some special values indicating free and deleted entries. However, since we can only operate atomically on integers, we wrap any other type that is not integral into a dynamically allocated pointer. This is done based on the template instantiation and is figured out at compile time.

    diff --git a/docs/xrcu.pod b/docs/xrcu.pod index e7c7bef..7cfe474 100644 --- a/docs/xrcu.pod +++ b/docs/xrcu.pod @@ -262,126 +262,6 @@ The overhead associated to C is the main reason why critical sections should be short, and also why C objects are accumulated instead of being reclaimed right away. -=head2 Interlude: optionals - - #include - -Before moving on to the topic of containers, we need to take a look at an -auxiliary structure implemented to make things easier and more convenient -for users. This is the C type. - -An optional is a template type that can hold either an object of type I, or -be in an uninitialized state. In other words, the value may or may not be -present. Unlike the usage of a pointer which may be null, an C never -performs dynamic memory allocation, as the space required to hold the value is -always there, even if it's not being used. - -Optional types have been standardized in C++17, and are present in many other -libraries as well. However, since XRCU aims to work with the base minimum of -C++11 compliant compilers, and because it's designed not to depend on other -libraries or frameworks, it contains its own (lightweight) implementation. - -=head3 Optional API - -An optional is defined as such: - - template - struct optional - { - }; - -And its public interface is the following: - -=over 4 - -=item optional (); - -Default constructor. Initializes the optional without a value. - -=item optional (const T& value); - -Initializes the optional to contain C. - -=item optional (const optional& other); - -Copy constructor. If C has a value, then it initializes the optional to -contain that same value. Otherwise, the optional will contain no value. - -=item optional (T&& value); - -Move constructor. Initializes the optional by taking ownership of C. - -=item optional (optional&& other); - -Move constructor. If C has a value, then the optional is initialized -by taking ownership of it. Otherwise, the optional will have no value. - -=item T& operator* (); - -=item const T& operator* () const; - -Returns a (possibly const) reference to the optional's value. The results are -undefined if the optional does not contain one. - -=item T* operator-> (); - -=item const T* operator-> () const; - -Returns a (possibly const) pointer to the optional's value. The results are -undefined if the optional does not contain one. - -=item bool has_value () const; - -Returns true if the optional has a value. - -=item void reset () const; - -If the optional contains a value, call its destructor and make the optional -contain no value afterwards. Otherwise, there are no effects. - -=item optional& operator= (const optional& other); - -If C has a value, assigns it to the optional. Otherwise, calls C -on the optional and leaves it without a value. Returns C<*this>. - -=item optional& operator= (const T& value); - -Assigns C to the optional. Returns C<*this>. - -=item optional& operator= (optional&& other); - -If C has a value, assigns it to the optional by moving it. Otherwise, -calls C on the optional and leaves it without a value. Returns C<*this>. - -=item optional& operator= (T&& value); - -Assigns C to the optional by moving it. Returns C<*this>. - -=item ~optional (); - -Destroys the value associated to the optional, if it had any. - -=back - -=head3 Implementation details - -Optionals are rather simple: They are implemented by using a flat buffer -on which placement new is called. Once an object has been constructed, -a pointer is cached for the buffer. This pointer will be used when fetching -the value, or destroying the object (it's null for empty optionals). - -Optionals are very useful when performing lookups in mapped containers, since -they allow us to bypass the need of additional output parameters to determine -if a search was successful in an atomic way. - -Another reason as to why optional values are used so extensively in XRCU is -because we consider them to be the best way to signal both a valid object, -as well as an error. When using lock-less data structures, it's impossible to -determine beforehand whether an error will occur when calling a method (because -multithreading makes things non deterministic). Throwing an exception was -considered a bit too "punishing" - it's not precisely a programming error, -after all. - =head2 Stacks #include @@ -397,7 +277,7 @@ In XRCU, stacks meet the requirements for the C++ concept of I. Stacks are templated types that can be instantiated with a type T: - template + template > struct stack { typedef T value_type; @@ -426,11 +306,11 @@ The following describes the public interface for C> Default constructor. Initializes a stack to be empty. -=item template stack (Integer n, T value); +=item template stack (Integer n, T value); Initializes a stack to contain C times C. -=item template stack (Iter first, Iter last); +=item template stack (Iter first, Iter last); Initializes a stack with the values in the range [C, C) @@ -450,25 +330,25 @@ Move constructor. Takes ownership of the values in C. Pushes C to the top of the stack. -=item template void push (Iter first, Iter last) +=item template void push (Iter first, Iter last) Pushes the values in the range [C, C) to the stack. -=item template void push (Integer n, T value) +=item template void push (Integer n, T value) Pushes C times C to the stack. -=item template void emplace (Args&& ...args) +=item template void emplace (Args&& ...args) Same as C, only it constructs a new item by calling the move constructor with C. -=item optional pop (); +=item std::optional pop (); Removes the item at the top of the stack, and returns an optional with that value. If the stack is empty, the method returns an optional with no value. -=item optional top (); +=item std::optional top (); Fetches the current item at the top of the stack, and returns an optional with it as its value. If the stack is empty, returns an optional with no value. @@ -485,36 +365,36 @@ Returns the maximum allowed size for the stack. Returns true if the stack is empty (equivalent to C). -=item void swap (stack& other); +=item void swap (stack& other); Swaps the contents of the stack with C, but only if C is not the same object. -=item stack& operator= (const stack& other); +=item stack& operator= (const stack& other); Assigns the contents of C to the stack and returns C<*this>. -=item stack& operator= (stack&& other); +=item stack& operator= (stack&& other); Move assignment. Takes ownership of the elements in C. Returns C<*this>. -=item bool operator== (const stack& other) const; +=item bool operator== (const stack& other) const; Compares the elements of the stack with the ones in C. Returns true if they are all equal. -=item bool operator!= (const stack& other) const; +=item bool operator!= (const stack& other) const; Compares the elements of the stack with the ones in C. Returns true if any two of them are not equal. -=item bool operator< (const stack& other) const; +=item bool operator< (const stack& other) const; -=item bool operator> (const stack& other) const; +=item bool operator> (const stack& other) const; -=item bool operator<= (const stack& other) const; +=item bool operator<= (const stack& other) const; -=item bool operator>= (const stack& other) const; +=item bool operator>= (const stack& other) const; Lexicographically compares the elements of the stack with the ones in C, in a way that is equivalent to calling C. @@ -523,7 +403,7 @@ in a way that is equivalent to calling C. Removes every element from the stack. -=item template void assign (T1 x, T2 y); +=item template void assign (T1 x, T2 y); Assigns to the stack the elements described by (C, C). They could be an iterator range, or a pair of (integer, value), as is the case with the stack's @@ -620,7 +500,7 @@ In XRCU, queues meet the requirements for the C++ concept of I. Queues are templated types, and they may be instantiated with a type T: - template + template > struct queue { typedef T value_type; @@ -647,11 +527,11 @@ The following describes the public interface for C> Default constructor. Initializes a queue to be empty. -=item template queue (Integer n, T value); +=item template queue (Integer n, T value); Initializes a queue to contain C times C. -=item template queue (Iter first, Iter last); +=item template queue (Iter first, Iter last); Initializes a queue with the values in the range [C, C). @@ -659,11 +539,11 @@ Initializes a queue with the values in the range [C, C). Initializes a queue with the values in C. -=item queue (const queue& other); +=item queue (const queue& other); Copy constructor. Initializes a queue with the values in C. -=item queue (queue&& other); +=item queue (queue&& other); Move constructor. Takes ownership of the values in C. @@ -671,22 +551,22 @@ Move constructor. Takes ownership of the values in C. Pushes C to the top of the queue. -=item template void emplace (Args&& ...args) +=item template void emplace (Args&& ...args) Same as C, only the new item is constructed by calling the move constructor with C. -=item optional pop (); +=item std::optional pop (); Removes the first item from the queue, and returns an optional with that value. If the queue is empty, the method returns an optional with no value. -=item optional front () const; +=item std::optional front () const; Returns an optional with the first value from the queue. If the queue is empty, an optional with no value is returned instead. -=item optional back () const; +=item std::optional back () const; Returns an optional with the last value from the queue. If the queue is empty, an optional with no value is returned instead. @@ -699,36 +579,36 @@ Returns the size of the queue. Returns the maximum allowed size for the queue. -=item void swap (queue& other); +=item void swap (queue& other); Swaps the contents of the queue with C, but only if C is not the same object. -=item queue& operator= (const queue& other); +=item queue& operator= (const queue& other); Assigns the contents of C to the queue and returns C<*this>. -=item queue& operator= (queue&& other); +=item queue& operator= (queue&& other); Move assignment. Takes ownership of the elements in C. Returns C<*this>. -=item bool operator== (const queue& other); +=item bool operator== (const queue& other); Compares the elements of the queue with the ones in C. Returns true if they are all equal. -=item bool operator!= (const queue& other); +=item bool operator!= (const queue& other); Compares the elements of the queue with the ones in C. Returns true if any two of them are not equal. -=item bool operator< (const queue& other) const; +=item bool operator< (const queue& other) const; -=item bool operator> (const queue& other) const; +=item bool operator> (const queue& other) const; -=item bool operator<= (const queue& other) const; +=item bool operator<= (const queue& other) const; -=item bool operator>= (const queue& other) const; +=item bool operator>= (const queue& other) const; Lexicographically compares the elements of the queue with the ones in C, in a way that is equivalent to calling C. @@ -737,7 +617,7 @@ in a way that is equivalent to calling C. Removes every element from the queue. -=item template void assign (T1 x, T2 y); +=item template void assign (T1 x, T2 y); Assigns to the queue the elements described by (C, C). They can be an iterator range or a pair of (integer, value), as is the case with the queue's @@ -834,7 +714,8 @@ and I. Skip lists are templated types, defined in the following way: - template > + template , + typename Alloc = std::allocator> struct skip_list { typedef T value_type; @@ -874,7 +755,7 @@ shouldn't need to change the latter, but it can be useful when tuning the application for performance. As a general rule, a higher value implies more memory usage, but better performance. -=item template skip_list (Iter first, Iter last, Cmp c = Cmp (), unsigned int depth = ...); +=item template skip_list (Iter first, Iter last, Cmp c = Cmp (), unsigned int depth = ...); Initializes the skip list to the values in [C, C). The comparator and depth parameters are the same as explained above. @@ -884,15 +765,15 @@ and depth parameters are the same as explained above. Initializes the skip list to the values in C. The comparator and depth parameters are the same as explained above. -=item skip_list (const skip_list& other); +=item skip_list (const skip_list& other); Copy constructor. Initializes the skip list to hold the values in C. -=item skip_list (skip_list&& other); +=item skip_list (skip_list&& other); Move constructor. Takes ownership of the values in C. -=item optional find (const T& key) const; +=item std::optional find (const T& key) const; Searches for an element equivalent to C in the skip list, and returns an optional with it as its value. If the key couldn't be found, the returned @@ -912,7 +793,7 @@ previous to this call. Erases C from the skip list. Returns true if the key was present previous to this call. -=item optional remove (const T& key); +=item std::optional remove (const T& key); Erases C from the skip list and returns an optional with that element as its value, if the key was present. Otherwise, the returned optional has no @@ -942,7 +823,7 @@ Returns the maximum allowed size for a skip list. Returns true if the skip list is empty (i.e: its size is 0). -=item template void assign (Iter first, Iter last); +=item template void assign (Iter first, Iter last); Assigns to the skip list the elements in [C, C). @@ -950,15 +831,15 @@ Assigns to the skip list the elements in [C, C). Assigns to the skip list the elements in C. -=item skip_list& operator= (const skip_list& other); +=item skip_list& operator= (const skip_list& other); Assigns the elements in C to the skip list. Returns C<*this>. -=item skip_list& operator= (skip_list&& other); +=item skip_list& operator= (skip_list&& other); Move assignment. Takes ownership of the elements in C. Returns C<*this>. -=item void swap (skip_list& other); +=item void swap (skip_list& other); Swaps the contents of the skip list with C, but only if C is a different object. @@ -1016,9 +897,10 @@ and I. As mentioned above, hash tables are template types, defined like this: - template , - class Hash = std::hash > + template , + typename Hash = std::hash, + typename Alloc = std::allocator>> struct hash_table { typedef Val mapped_type; @@ -1057,7 +939,7 @@ C, using the equality predicate C and the hasher C. The load factor must be in the range [0.4, 0.9]; if it's not, then it will silently be set to the default of 0.85. -=item template hash_table (It first, It last, float lf = 0.85, Equal e = Equal (), Hash h = Hash ()) +=item template hash_table (It first, It last, float lf = 0.85, Equal e = Equal (), Hash h = Hash ()) Initializes the hash table with the values between (C, C]. The elements in that range must have two public members defined: C and @@ -1065,16 +947,16 @@ C, referring to the key and value of each element, respectively, and in a similar fashion to what C does. The other parameters work as with the previous constructor. -=item hash_table (std::initializer_list >, float lf = 0.85, Equal e = Equal (), Hash h = Hash ()) +=item hash_table (std::initializer_list>, float lf = 0.85, Equal e = Equal (), Hash h = Hash ()) Initializes the hash table with the values in C. The rest of the parameters work as with the previous constructors. -=item hash_table (const hash_table& other); +=item hash_table (const hash_table& other); Copy constructor. Initializes the hash table with the values in C. -=item hash_table (hash_table&& other); +=item hash_table (hash_table&& other); Move constructor. Takes ownership of the values in C. @@ -1090,7 +972,7 @@ Returns the maximum allowed size for a hash table. Returns true if the hash table is empty. -=item optional find (const Key& key) const; +=item std::optional find (const Key& key) const; =item Val find (const Key& key, const Val& defl) const; @@ -1107,7 +989,7 @@ Returns true if the key is present in the hash table. Associates C with C in the hash table. Returns true if the value was not present. Otherwise, the former value is replaced. -=item template bool update (const Key& key, Fn f, Args... args); +=item template bool update (const Key& key, Fn f, Args... args); Updates the value associated to C by calling C with it and the rest of the arguments. In other words, if C is the value associated to C, @@ -1138,7 +1020,7 @@ to if, if it was present. Removes every element from the hash table. -=item template void assign (Iter first, Iter last); +=item template void assign (Iter first, Iter last); Assigns the elements in [C, C) to the hash table. The same restrictions as the range constructor apply here. @@ -1147,15 +1029,15 @@ restrictions as the range constructor apply here. Assigns the elements in C to the hash table. -=item hash_table& operator= (const hash_table& other); +=item hash_table& operator= (const hash_table& other); Assigns the elements in C to the hash table. Returns C<*this>. -=item hash_table& operator= (hash_table&& other); +=item hash_table& operator= (hash_table&& other); Move assignment. Takes ownership of the elements in C. Returns C<*this>. -=item void swap (hash_table& other); +=item void swap (hash_table& other); Swaps the contents of the hash table with C, but only if C is a different object. diff --git a/optional.hpp b/optional.hpp deleted file mode 100644 index d56eef2..0000000 --- a/optional.hpp +++ /dev/null @@ -1,175 +0,0 @@ -/* Declarations for the optional template type. - - This file is part of xrcu. - - xrcu 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. - - This program 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 this program. If not, see . */ - -#ifndef __XRCU_OPTIONAL_HPP__ -#define __XRCU_OPTIONAL_HPP__ 1 - -#include - -namespace xrcu -{ - -template -struct optional -{ - T *xptr = nullptr; - alignas (alignof (T)) char buf[sizeof (T)]; - - T* _Ptr () - { - return ((T *)&this->buf[0]); - } - - const T* _Ptr () const - { - return ((const T *)&this->buf[0]); - } - - void _Init (const T& value) - { - new (this->_Ptr ()) T (value); - this->xptr = this->_Ptr (); - } - - void _Init (T&& value) - { - new (this->_Ptr ()) T (std::forward (value)); - this->xptr = this->_Ptr (); - } - - optional () - { - } - - optional (const T& value) - { - this->_Init (value); - } - - optional (const optional& right) - { - if (right.xptr) - this->_Init (*right); - } - - optional (T&& value) - { - this->_Init (std::forward (value)); - } - - optional (optional&& right) - { - if (right.xptr) - { - this->_Init (std::forward (*right)); - right.xptr = nullptr; - } - } - - T& operator* () - { - return (*this->xptr); - } - - const T& operator* () const - { - return (*this->xptr); - } - - T* operator-> () - { - return (this->xptr); - } - - const T* operator-> () const - { - return (this->xptr); - } - - bool has_value () const - { - return (this->xptr != nullptr); - } - - void reset () - { - if (!this->xptr) - return; - - this->xptr->~T (); - this->xptr = nullptr; - } - - optional& operator= (const optional& right) - { - if (&right == this) - ; - else if (!right.xptr) - this->reset (); - else if (!this->xptr) - this->_Init (*right); - else - **this = *right; - - return (*this); - } - - optional& operator= (optional&& right) - { - if (&right == this) - return (*this); - - if (!right.xptr) - this->reset (); - else if (!this->xptr) - this->_Init (std::forward (*right)); - else - **this = std::forward (*right); - - right.xptr = nullptr; - return (*this); - } - - optional& operator= (const T& value) - { - if (!this->xptr) - this->_Init (value); - else - **this = value; - - return (*this); - } - - optional& operator= (T&& value) - { - if (!this->xptr) - this->_Init (std::forward (value)); - else - **this = std::forward (value); - - return (*this); - } - - ~optional () - { - this->reset (); - } -}; - -} // namespace xrcu - -#endif diff --git a/skip_list.cpp b/skip_list.cpp deleted file mode 100644 index a470b83..0000000 --- a/skip_list.cpp +++ /dev/null @@ -1,43 +0,0 @@ -/* Definitions for the skip list template type. - - This file is part of xrcu. - - xrcu 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. - - This program 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 this program. If not, see . */ - -#include "skip_list.hpp" -#include -#include - -namespace xrcu -{ - -namespace detail -{ - -sl_node_base* sl_alloc_node (unsigned int lvl, size_t size, uintptr_t **outpp) -{ - void *p = ::operator new (size + lvl * sizeof (uintptr_t)); - *outpp = (uintptr_t *)((char *)p + size); - memset (*outpp, 0, lvl * sizeof (uintptr_t)); - return ((sl_node_base *)p); -} - -void sl_dealloc_node (void *ptr) -{ - ::operator delete (ptr); -} - -} // namespace detail - -} // namespace xrcu diff --git a/hash_table.cpp b/src/hash_table.cpp similarity index 68% rename from hash_table.cpp rename to src/hash_table.cpp index b191325..33fc55c 100644 --- a/hash_table.cpp +++ b/src/hash_table.cpp @@ -15,7 +15,7 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ -#include "hash_table.hpp" +#include "xrcu/hash_table.hpp" #include #include #include @@ -26,17 +26,6 @@ namespace xrcu namespace detail { -inline ht_vector* ht_vector_make (size_t n) -{ - void *p = ::operator new (sizeof (ht_vector) + n * sizeof (uintptr_t)); - return (new (p) ht_vector ((uintptr_t *)((char *)p + sizeof (ht_vector)))); -} - -void ht_vector::safe_destroy () -{ - ::operator delete (this); -} - static const size_t PRIMES[] = { 0xb, 0x25, 0x71, 0x15b, 0x419, 0xc4d, 0x24f5, 0x6ee3, 0x14cb3, 0x3e61d, @@ -52,6 +41,11 @@ static const size_t PRIMES[] = #endif }; +size_t vec_psize (size_t pidx) +{ + return (PRIMES[pidx]); +} + size_t find_hsize (size_t size, float mvr, size_t& pidx) { intptr_t i1 = 0, i2 = sizeof (PRIMES) / sizeof (PRIMES[0]); @@ -68,28 +62,6 @@ size_t find_hsize (size_t size, float mvr, size_t& pidx) return ((size_t)(PRIMES[i1] * mvr)); } -ht_vector* make_htvec (size_t pidx, uintptr_t key, uintptr_t val) -{ - size_t entries = PRIMES[pidx], tsize = table_idx (entries); -#ifdef XRCU_HAVE_XATOMIC_DCAS - auto ret = ht_vector_make (tsize + 1); - - // Ensure correct alignment for double-width CAS. - if ((uintptr_t)ret->data % (2 * sizeof (uintptr_t)) != 0) - ++ret->data; -#else - auto ret = ht_vector_make (tsize); -#endif - - for (size_t i = 0; i < tsize; i += 2) - ret->data[i] = key, ret->data[i + 1] = val; - - ret->entries = entries; - ret->pidx = pidx; - - return (ret); -} - } // namespace detail } // namespace xrcu diff --git a/lwlock.cpp b/src/lwlock.cpp similarity index 97% rename from lwlock.cpp rename to src/lwlock.cpp index 03fe5f2..c47492d 100644 --- a/lwlock.cpp +++ b/src/lwlock.cpp @@ -15,8 +15,8 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ -#include "lwlock.hpp" -#include "xatomic.hpp" +#include "xrcu/lwlock.hpp" +#include "xrcu/xatomic.hpp" #if (defined (linux) || defined (__linux) || defined (__linux__)) && \ defined (__BYTE_ORDER__) diff --git a/src/queue.cpp b/src/queue.cpp new file mode 100644 index 0000000..898cdc3 --- /dev/null +++ b/src/queue.cpp @@ -0,0 +1,91 @@ +/* Definitions for the queue template type. + + This file is part of xrcu. + + xrcu 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. + + This program 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 this program. If not, see . */ + +#include "xrcu/queue.hpp" + +namespace xrcu +{ + +namespace detail +{ + +bool q_base::push (uintptr_t val, uintptr_t xbit, uintptr_t empty) +{ + while (true) + { + size_t curr = this->_Wridx (); + if (curr >= this->cap) + return (false); + + uintptr_t xv = this->ptrs[curr]; + if ((xv & xbit) != 0) + return (false); + else if (xv == empty && + xatomic_cas_bool (&this->ptrs[curr], xv, val)) + { + this->wr_idx.fetch_add (1, std::memory_order_acq_rel); + return (true); + } + + xatomic_spin_nop (); + } +} + +uintptr_t q_base::pop (uintptr_t xbit, uintptr_t dfl) +{ + while (true) + { + size_t curr = this->_Rdidx (); + if (curr >= this->_Wridx ()) + return (dfl); + + uintptr_t rv = this->ptrs[curr]; + if (rv & xbit) + return (xbit); + else if (rv != dfl && xatomic_cas_bool (&this->ptrs[curr], rv, dfl)) + { + this->rd_idx.fetch_add (1, std::memory_order_relaxed); + return (rv); + } + + xatomic_spin_nop (); + } +} + +void q_base::replace (std::atomic& ptr, q_base *old, + q_base *nq, uintptr_t) +{ + finalize (old); + ptr.store (nq, std::memory_order_relaxed); +} + +void q_base::clear (std::atomic&, q_base *old, + q_base *, uintptr_t empty) +{ + old->wr_idx.store (old->cap, std::memory_order_relaxed); + old->rd_idx.store (old->cap, std::memory_order_relaxed); + + for (size_t i = 0; i < old->cap; ++i) + old->ptrs[i] = empty; + + old->rd_idx.store (0, std::memory_order_release); + old->wr_idx.store (0, std::memory_order_release); +} + +} // namespace detail + +} // namespace xrcu diff --git a/stack.cpp b/src/stack.cpp similarity index 98% rename from stack.cpp rename to src/stack.cpp index 1ad3a91..f918be2 100644 --- a/stack.cpp +++ b/src/stack.cpp @@ -15,8 +15,8 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ -#include "stack.hpp" -#include "xatomic.hpp" +#include "xrcu/stack.hpp" +#include "xrcu/xatomic.hpp" #include namespace xrcu diff --git a/utils.cpp b/src/utils.cpp similarity index 97% rename from utils.cpp rename to src/utils.cpp index f30eb5a..f7e0f8a 100644 --- a/utils.cpp +++ b/src/utils.cpp @@ -15,7 +15,7 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ -#include "utils.hpp" +#include "xrcu/utils.hpp" #include namespace xrcu diff --git a/xrcu.cpp b/src/xrcu.cpp similarity index 99% rename from xrcu.cpp rename to src/xrcu.cpp index c89b632..a13a67b 100644 --- a/xrcu.cpp +++ b/src/xrcu.cpp @@ -15,9 +15,9 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ -#include "xrcu.hpp" -#include "xatomic.hpp" -#include "version.hpp" +#include "xrcu/xrcu.hpp" +#include "xrcu/xatomic.hpp" +#include "xrcu/version.hpp" #include #include #include diff --git a/tests/hash.hpp b/tests/hash.hpp index ae39631..b515ebd 100644 --- a/tests/hash.hpp +++ b/tests/hash.hpp @@ -1,13 +1,14 @@ #ifndef __XRCU_TESTS_HASH__ #define __XRCU_TESTS_HASH__ 1 -#include "../hash_table.hpp" +#include "xrcu/hash_table.hpp" #include "utils.hpp" #include #include #include -typedef xrcu::hash_table table_t; +typedef xrcu::hash_table, + std::hash, test_allocator> table_t; namespace ht_test { diff --git a/tests/opt.hpp b/tests/opt.hpp deleted file mode 100644 index 0fd598a..0000000 --- a/tests/opt.hpp +++ /dev/null @@ -1,54 +0,0 @@ -#ifndef __XRCU_TESTS_OPT__ -#define __XRCU_TESTS_OPT__ 1 - -#include "../optional.hpp" -#include "utils.hpp" - -namespace opt_test -{ - -void test_optional () -{ - xrcu::optional opt; - - ASSERT (!opt.has_value ()); - opt.reset (); - ASSERT (!opt.has_value ()); - opt = mkstr (100); - ASSERT (opt.has_value ()); - - *opt += mkstr (200); - ASSERT (*opt == mkstr (100) + mkstr (200)); - - opt->clear (); - ASSERT (opt.has_value ()); - ASSERT (opt->empty ()); - - opt.reset (); - ASSERT (!opt.has_value ()); - - std::string s = mkstr (300); - opt = s; - ASSERT (s == *opt); - - xrcu::optional o2 { opt }; - ASSERT (*o2 == *opt); - - o2.reset (); - ASSERT (opt.has_value ()); - - xrcu::optional o3 { mkstr (400) }; - ASSERT (o3.has_value ()); -} - -test_module optional_tests -{ - "optional", - { - { "API", test_optional } - } -}; - -} // namespace opt_test - -#endif diff --git a/tests/queue.hpp b/tests/queue.hpp index f81603a..3d74d91 100644 --- a/tests/queue.hpp +++ b/tests/queue.hpp @@ -1,20 +1,21 @@ #ifndef __XRCU_TESTS_QUEUE__ #define __XRCU_TESTS_QUEUE__ 1 -#include "../queue.hpp" +#include "xrcu/queue.hpp" #include "utils.hpp" #include namespace q_test { -typedef xrcu::queue queue_t; +typedef xrcu::queue> queue_t; void test_single_threaded () { { queue_t q; ASSERT (q.empty ()); + ASSERT (!q.front().has_value ()); ASSERT (!q.back().has_value ()); } diff --git a/tests/sl.hpp b/tests/sl.hpp index 3442bd7..1e3bff1 100644 --- a/tests/sl.hpp +++ b/tests/sl.hpp @@ -1,13 +1,14 @@ #ifndef __XRCU_TESTS_SL__ #define __XRCU_TESTS_SL__ 1 -#include "../skip_list.hpp" -#include +#include "xrcu/skip_list.hpp" #include #include +#include #include "utils.hpp" -typedef xrcu::skip_list sklist_t; +typedef xrcu::skip_list, + test_allocator> sklist_t; namespace sl_test { diff --git a/tests/stack.hpp b/tests/stack.hpp index 92029c0..3f914f1 100644 --- a/tests/stack.hpp +++ b/tests/stack.hpp @@ -1,14 +1,14 @@ #ifndef __XRCU_TESTS_STACK__ #define __XRCU_TESTS_STACK__ 1 -#include "../stack.hpp" +#include "xrcu/stack.hpp" #include "utils.hpp" #include namespace stk_test { -typedef xrcu::stack stack_t; +typedef xrcu::stack> stack_t; void test_single_threaded () { diff --git a/tests/test.cpp b/tests/test.cpp index d02f2d7..d1723f0 100644 --- a/tests/test.cpp +++ b/tests/test.cpp @@ -1,10 +1,28 @@ +/* Testing entry point. + + This file is part of xrcu. + + xrcu 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. + + This program 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 this program. If not, see . */ + #include "xrcu.hpp" -#include "opt.hpp" #include "sl.hpp" #include "hash.hpp" #include "stack.hpp" #include "queue.hpp" +std::atomic alloc_size; + int main () { run_tests (); diff --git a/tests/utils.hpp b/tests/utils.hpp index 85e346e..36260ee 100644 --- a/tests/utils.hpp +++ b/tests/utils.hpp @@ -1,13 +1,31 @@ +/* Common definitions for testing. + + This file is part of xrcu. + + xrcu 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. + + This program 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 this program. If not, see . */ + #ifndef __XRCU_TESTS_UTILS__ #define __XRCU_TESTS_UTILS__ 1 +#include +#include +#include +#include #include -#include #include #include -#include -#include -#include +#include #define ASSERT(Cond) \ do \ @@ -21,6 +39,45 @@ } \ while (0) +extern std::atomic alloc_size; + +template +struct test_allocator +{ + typedef std::true_type is_always_equal; + typedef T value_type; + typedef T* pointer; + typedef const T* const_pointer; + typedef T& reference; + typedef const T& const_reference; + typedef size_t size_type; + typedef ptrdiff_t difference_type; + + test_allocator () + { + } + + T* allocate (size_t n, const void * = nullptr) + { + T *ret = (T *)malloc (n * sizeof (T)); + if (! ret) + throw std::bad_alloc (); + + alloc_size.fetch_add (n * sizeof (T)); + return (ret); + } + + void deallocate (T *ptr, size_t n) + { + free (ptr); + alloc_size.fetch_sub (n * sizeof (T)); + } + + ~test_allocator () + { + } +}; + struct test_fn { std::string msg; @@ -59,9 +116,14 @@ struct test_module test_module (const char *name, std::initializer_list tests) { std::string nm = std::string (" (") + name + ") "; + alloc_size.store (0, std::memory_order_release); for (auto pair : tests) test_suite().push_back (test_fn (pair.first + nm, pair.second)); + + size_t bytes = alloc_size.load (std::memory_order_acquire); + if (bytes) + std::cout << "Warning: " << bytes << " bytes were not freed\n"; } }; diff --git a/tests/xrcu.hpp b/tests/xrcu.hpp index 5d1b4b5..08b548b 100644 --- a/tests/xrcu.hpp +++ b/tests/xrcu.hpp @@ -1,7 +1,7 @@ #ifndef __XRCU_TESTS_XRCU__ #define __XRCU_TESTS_XRCU__ 1 -#include "../xrcu.hpp" +#include "xrcu/xrcu.hpp" #include "utils.hpp" #include #include diff --git a/version.hpp b/version.hpp deleted file mode 100644 index 8e9426d..0000000 --- a/version.hpp +++ /dev/null @@ -1 +0,0 @@ -const int MAJOR = 0; const int MINOR = 1; diff --git a/hash_table.hpp b/xrcu/hash_table.hpp similarity index 78% rename from hash_table.hpp rename to xrcu/hash_table.hpp index 5ee56fd..78f6584 100644 --- a/hash_table.hpp +++ b/xrcu/hash_table.hpp @@ -20,14 +20,16 @@ #include "xrcu.hpp" #include "xatomic.hpp" -#include "optional.hpp" #include "lwlock.hpp" +#include "memory.hpp" #include "utils.hpp" + #include +#include #include -#include #include -#include +#include +#include namespace std { @@ -45,6 +47,9 @@ inline constexpr size_t table_idx (size_t idx) return (idx * 2); } +extern size_t vec_psize (size_t pidx); + +template struct alignas (uintptr_t) ht_vector : public finalizable { uintptr_t *data; @@ -54,7 +59,33 @@ struct alignas (uintptr_t) ht_vector : public finalizable ht_vector (uintptr_t *ep) : data (ep) {} - void safe_destroy (); + static ht_vector* make (size_t pidx, uintptr_t key, uintptr_t val) + { + size_t entries = vec_psize (pidx), tsize = table_idx (entries); +#ifdef XRCU_HAVE_XATOMIC_DCAS + auto raw = alloc_uptrs (sizeof (ht_vector), tsize + 1); + uintptr_t *p = (uintptr_t *)((char *)raw + sizeof (ht_vector)); + + // Ensure correct alignment for double-width CAS. + if ((uintptr_t)p % (2 * sizeof (uintptr_t)) != 0) + ++p; +#else + auto raw = alloc_uptrs (sizeof (ht_vector), tsize); + uintptr_t *p = (uintptr_t *)(ret + 1); +#endif + auto ret = new ((ht_vector *)raw) ht_vector (p); + for (size_t i = 0; i < tsize; i += 2) + ret->data[i] = key, ret->data[i + 1] = val; + + ret->entries = entries; + ret->pidx = pidx; + return (ret); + } + + void safe_destroy () + { + dealloc_uptrs (this, this->data + this->size ()); + } size_t size () const { @@ -71,13 +102,12 @@ secondary_hash (size_t hval) extern size_t find_hsize (size_t size, float ldf, size_t& pidx); -extern ht_vector* make_htvec (size_t pidx, uintptr_t key, uintptr_t val); - +template struct ht_sentry { lwlock *lock; uintptr_t xbit; - ht_vector *vector = nullptr; + ht_vector *vector = nullptr; ht_sentry (lwlock *lp, uintptr_t xb) : lock (lp), xbit (~xb) { @@ -94,10 +124,10 @@ struct ht_sentry } }; -template +template struct ht_iter : public cs_guard { - const ht_vector *vec = nullptr; + const ht_vector *vec = nullptr; size_t idx = 0; uintptr_t c_key; uintptr_t c_val; @@ -105,7 +135,7 @@ struct ht_iter : public cs_guard typedef std::forward_iterator_tag iterator_category; - void _Init (const ht_vector *vp) + void _Init (const ht_vector *vp) { this->vec = vp; this->_Adv (); @@ -115,13 +145,13 @@ struct ht_iter : public cs_guard { } - ht_iter (const ht_iter& right) : + ht_iter (const ht_iter& right) : vec (right.vec), idx (right.idx), c_key (right.c_key), c_val (right.c_val), valid (right.valid) { } - ht_iter (ht_iter&& right) : + ht_iter (ht_iter&& right) : vec (right.vec), idx (right.idx), c_key (right.c_key), c_val (right.c_val), valid (right.valid) { @@ -148,13 +178,13 @@ struct ht_iter : public cs_guard } } - bool operator== (const ht_iter& right) const + bool operator== (const ht_iter& right) const { return ((!this->valid && !right.valid) || - (this->vec == right.vec && this->idx == right.idx)); + (this->vec == right.vec && this->idx == right.idx)); } - bool operator!= (const ht_iter& right) + bool operator!= (const ht_iter& right) { return (!(*this == right)); } @@ -178,22 +208,32 @@ struct ht_inserter void free (uintptr_t) {} }; +inline intptr_t +compute_fsize (float loadf, size_t entries) +{ + return ((intptr_t)(loadf * entries)); +} + } // namespace detail -template , - class HashFn = std::hash > +template , + typename HashFn = std::hash, + typename Alloc = std::allocator>> struct hash_table { - typedef detail::wrapped_traits<(sizeof (KeyT) < sizeof (uintptr_t) && - std::is_integral::value) || (std::is_pointer::value && - alignof (KeyT) >= 8), KeyT> key_traits; + typedef detail::wrapped_traits<( + sizeof (KeyT) < sizeof (uintptr_t) && + std::is_integral::value), KeyT> key_traits; - typedef detail::wrapped_traits<(sizeof (ValT) < sizeof (uintptr_t) && - std::is_integral::value) || (std::is_pointer::value && - alignof (ValT) >= 8), ValT> val_traits; + typedef detail::wrapped_traits<( + sizeof (ValT) < sizeof (uintptr_t) && + std::is_integral::value), ValT> val_traits; - typedef hash_table self_type; + using Nalloc = typename std::allocator_traits::template + rebind_alloc; + + typedef hash_table self_type; typedef KeyT key_type; typedef ValT mapped_type; typedef std::pair value_type; @@ -206,7 +246,7 @@ struct hash_table typedef ptrdiff_t difference_type; typedef size_t size_type; - detail::ht_vector *vec; + detail::ht_vector *vec; EqFn eqfn; HashFn hashfn; float loadf = 0.85f; @@ -237,9 +277,8 @@ struct hash_table { this->_Set_loadf (ldf); size_t pidx, gt = detail::find_hsize (size, this->loadf, pidx); - - this->vec = detail::make_htvec (pidx, - key_traits::FREE, val_traits::FREE); + this->vec = detail::ht_vector::make (pidx, key_traits::FREE, + val_traits::FREE); this->eqfn = e; this->hashfn = h; this->grow_limit.store (gt, std::memory_order_relaxed); @@ -247,14 +286,14 @@ struct hash_table } hash_table (size_t size = 0, float ldf = 0.85f, - EqFn e = EqFn (), HashFn h = HashFn ()) + EqFn e = EqFn (), HashFn h = HashFn ()) { this->_Init (size, ldf, e, h); } - template + template hash_table (Iter first, Iter last, float ldf = 0.85f, - EqFn e = EqFn (), HashFn h = HashFn ()) + EqFn e = EqFn (), HashFn h = HashFn ()) { this->_Init (0, ldf, e, h); for (; first != last; ++first) @@ -262,7 +301,7 @@ struct hash_table } hash_table (std::initializer_list > lst, - float ldf = 0.85f, EqFn e = EqFn (), HashFn h = HashFn ()) : + float ldf = 0.85f, EqFn e = EqFn (), HashFn h = HashFn ()) : hash_table (lst.begin (), lst.end (), ldf, e, h) { } @@ -301,8 +340,8 @@ struct hash_table return (this->size () == 0); } - size_t _Probe (const KeyT& key, const detail::ht_vector *vp, - bool put_p, bool& found) const + size_t _Probe (const KeyT& key, const detail::ht_vector *vp, + bool put_p, bool& found) const { size_t code = this->hashfn (key); size_t entries = vp->entries; @@ -337,13 +376,14 @@ struct hash_table } } - size_t _Probe (const KeyT& key, const detail::ht_vector *vp, bool put_p) const + size_t _Probe (const KeyT& key, const detail::ht_vector *vp, + bool put_p) const { bool unused; return (this->_Probe (key, vp, put_p, unused)); } - size_t _Gprobe (uintptr_t key, detail::ht_vector *vp) + size_t _Gprobe (uintptr_t key, detail::ht_vector *vp) { size_t code = this->hashfn (key_traits::get (key)); size_t entries = vp->entries; @@ -366,14 +406,15 @@ struct hash_table void _Rehash () { - detail::ht_sentry s (&this->lock, val_traits::XBIT); + detail::ht_sentry s (&this->lock, val_traits::XBIT); if (this->grow_limit.load (std::memory_order_relaxed) <= 0) { auto old = this->vec; - auto np = detail::make_htvec (old->pidx + 1, - key_traits::FREE, val_traits::FREE); size_t nelem = 0; + auto np = detail::ht_vector::make (old->pidx + 1, + key_traits::FREE, + val_traits::FREE); s.vector = old; @@ -399,9 +440,11 @@ struct hash_table nelem, std::memory_order_relaxed); std::atomic_thread_fence (std::memory_order_release); - /* At this point, another thread may decrement the growth limit + /* + * At this point, another thread may decrement the growth limit * from the wrong vector. That's fine, it just means we'll move - * the table sooner than necessary. */ + * the table sooner than necessary. + */ this->vec = np; finalize (old); } @@ -415,12 +458,13 @@ struct hash_table vp->data[idx + 1] & ~val_traits::XBIT); } - optional find (const KeyT& key) const + std::optional find (const KeyT& key) const { cs_guard g; uintptr_t val = this->_Find (key); - return (val == val_traits::DELT ? optional () : - optional (val_traits::get (val))); + return (val == val_traits::DELT ? + std::optional () : + std::optional (val_traits::get (val))); } ValT find (const KeyT& key, const ValT& dfl) const @@ -443,15 +487,16 @@ struct hash_table auto limit = this->grow_limit.load (std::memory_order_relaxed); if (limit <= 0) return (false); - else if (this->grow_limit.compare_exchange_weak (limit, limit - 1, - std::memory_order_acq_rel, std::memory_order_relaxed)) + else if (this->grow_limit.compare_exchange_weak ( + limit, limit - 1, std::memory_order_acq_rel, + std::memory_order_relaxed)) return (true); xatomic_spin_nop (); } } - template + template bool _Upsert (uintptr_t k, const KeyT& key, Fn f, Args... args) { cs_guard g; @@ -533,14 +578,14 @@ struct hash_table } } - template + template struct _Updater { Fn fct; _Updater (Fn f) : fct (f) {} - template + template uintptr_t call0 (Args ...args) { // Call function with default-constructed value and arguments. auto tmp = (typename Vtraits::value_type ()); @@ -548,13 +593,13 @@ struct hash_table return (Vtraits::make (rv)); } - template + template uintptr_t call1 (uintptr_t x, Args ...args) { // Call function with stored value and arguments. auto&& tmp = Vtraits::get (x); auto&& rv = this->fct (tmp, args...); - return (Vtraits::XBIT == 1 && - &rv == &tmp ? x : Vtraits::make (rv)); + return (Vtraits::XBIT == 1 && &rv == &tmp ? + x : Vtraits::make (rv)); } void free (uintptr_t x) @@ -563,7 +608,7 @@ struct hash_table } }; - template + template bool update (const KeyT& key, Fn f, Args... args) { uintptr_t k = key_traits::make (key); @@ -580,7 +625,7 @@ struct hash_table } } - bool _Erase (const KeyT& key, optional *outp = nullptr) + bool _Erase (const KeyT& key, std::optional *outp = nullptr) { cs_guard g; @@ -626,16 +671,16 @@ struct hash_table return (this->_Erase (key)); } - optional remove (const KeyT& key) + std::optional remove (const KeyT& key) { - optional ret; + std::optional ret; this->_Erase (key, &ret); return (ret); } - struct iterator : public detail::ht_iter + struct iterator : public detail::ht_iter { - typedef detail::ht_iter base_type; + typedef detail::ht_iter base_type; iterator () : base_type () { @@ -705,7 +750,7 @@ struct hash_table return (this->end ()); } - void _Assign_vector (detail::ht_vector *nv, intptr_t gt) + void _Assign_vector (detail::ht_vector *nv, intptr_t gt) { // First step: Lock the table. this->lock.acquire (); @@ -751,22 +796,23 @@ struct hash_table val_traits::destroy (v); } - this->grow_limit.store ((intptr_t)(this->loadf * this->size ()), + this->grow_limit.store (detail::compute_fsize (this->loadf, + this->size ()), std::memory_order_relaxed); this->vec->nelems.store (0, std::memory_order_release); this->lock.release (); } - template + template void assign (Iter first, Iter last) { self_type tmp (first, last, 0, this->loadf); this->_Assign_vector (tmp.vec, - tmp.grow_limit.load (std::memory_order_relaxed)); + tmp.grow_limit.load (std::memory_order_relaxed)); tmp.vec = nullptr; } - void assign (std::initializer_list > lst) + void assign (std::initializer_list> lst) { this->assign (lst.begin (), lst.end ()); } @@ -780,7 +826,7 @@ struct hash_table self_type& operator= (self_type&& right) { this->_Assign_vector (right.vec, - right.grow_limit.load (std::memory_order_relaxed)); + right.grow_limit.load (std::memory_order_relaxed)); this->loadf = right.loadf; right.vec = nullptr; return (*this); @@ -791,8 +837,8 @@ struct hash_table if (this == &right) return; - detail::ht_sentry s1 (&this->lock, ~val_traits::XBIT); - detail::ht_sentry s2 (&right.lock, ~val_traits::XBIT); + detail::ht_sentry s1 (&this->lock, ~val_traits::XBIT); + detail::ht_sentry s2 (&right.lock, ~val_traits::XBIT); // Prevent further insertions (still allows deletions). this->grow_limit.store (0, std::memory_order_release); @@ -803,10 +849,12 @@ struct hash_table std::swap (this->hashfn, right.hashfn); std::swap (this->loadf, right.loadf); - this->grow_limit.store ((intptr_t)(this->loadf * - this->vec->entries) - this->size (), std::memory_order_release); - right.grow_limit.store ((intptr_t)(right.loadf * - right.vec->entries) - right.size (), std::memory_order_release); + this->grow_limit.store (detail::compute_fsize (this->loadf, + this->vec->entries), + std::memory_order_release); + right.grow_limit.store (detail::compute_fsize (right.loadf, + right.vec->entries), + std::memory_order_release); } ~hash_table () @@ -834,7 +882,7 @@ struct hash_table namespace std { -template +template void swap (xrcu::hash_table& left, xrcu::hash_table& right) { diff --git a/lwlock.hpp b/xrcu/lwlock.hpp similarity index 100% rename from lwlock.hpp rename to xrcu/lwlock.hpp diff --git a/queue.cpp b/xrcu/memory.hpp similarity index 51% rename from queue.cpp rename to xrcu/memory.hpp index 9096386..02c85f2 100644 --- a/queue.cpp +++ b/xrcu/memory.hpp @@ -1,4 +1,4 @@ -/* Definitions for the queue template type. +/* Declarations for memory-related interfaces. This file is part of xrcu. @@ -15,38 +15,35 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . */ -#include "queue.hpp" -#include +#ifndef __XRCU_MEMORY_HPP__ +#define __XRCU_MEMORY_HPP__ 1 -namespace xrcu -{ +#include +#include -namespace detail +namespace xrcu { -q_data* q_data::make (size_t cnt, uintptr_t empty) +template +uintptr_t* alloc_uptrs (size_t tsize, size_t nr_uptrs, + size_t *upp = nullptr) { - size_t ns = cnt + 1; - q_data *ret = (q_data *)::operator new (sizeof (*ret) + - ns * sizeof (uintptr_t)); - - new (ret) q_data (); - ret->ptrs = (uintptr_t *)(ret + 1); - ret->cap = cnt; + size_t total = tsize + nr_uptrs * sizeof (uintptr_t); + size_t uptrs = (total / sizeof (uintptr_t)) + + ((total % sizeof (uintptr_t)) != 0); + if (upp) + *upp = uptrs; - for (uintptr_t *p = ret->ptrs; p != ret->ptrs + ns; ) - *p++ = empty; - - ret->wr_idx.store (0, std::memory_order_relaxed); - ret->rd_idx.store (0, std::memory_order_relaxed); - return (ret); + return (Alloc().allocate (uptrs)); } -void q_data::safe_destroy () +template +void dealloc_uptrs (void *base, void *end) { - ::operator delete (this); + size_t total = (char *)end - (char *)base; + Alloc().deallocate ((uintptr_t *)base, total / sizeof (uintptr_t)); } -} // namespace detail - } // namespace xrcu + +#endif diff --git a/queue.hpp b/xrcu/queue.hpp similarity index 74% rename from queue.hpp rename to xrcu/queue.hpp index 4395029..01ebdf4 100644 --- a/queue.hpp +++ b/xrcu/queue.hpp @@ -18,12 +18,14 @@ #ifndef __XRCU_QUEUE_HPP__ #define __XRCU_QUEUE_HPP__ 1 +#include "memory.hpp" #include "xrcu.hpp" -#include "optional.hpp" #include "xatomic.hpp" #include "utils.hpp" -#include #include +#include +#include +#include #include #include @@ -59,14 +61,18 @@ static inline uint64_t upsize (uint64_t x) return (x + 1); } -struct alignas (uintptr_t) q_data : public finalizable +struct alignas (uintptr_t) q_base : public finalizable { uintptr_t *ptrs; size_t cap; std::atomic wr_idx; std::atomic rd_idx; - static q_data* make (size_t cnt, uintptr_t empty); + q_base () + { + this->wr_idx.store (0, std::memory_order_relaxed); + this->rd_idx.store (0, std::memory_order_relaxed); + } size_t _Wridx () const { @@ -78,48 +84,8 @@ struct alignas (uintptr_t) q_data : public finalizable return (this->rd_idx.load (std::memory_order_relaxed)); } - bool push (uintptr_t val, uintptr_t xbit, uintptr_t empty) - { - while (true) - { - size_t curr = this->_Wridx (); - if (curr >= this->cap) - return (false); - - uintptr_t xv = this->ptrs[curr]; - if ((xv & xbit) != 0) - return (false); - else if (xv == empty && - xatomic_cas_bool (&this->ptrs[curr], xv, val)) - { - this->wr_idx.fetch_add (1, std::memory_order_acq_rel); - return (true); - } - - xatomic_spin_nop (); - } - } - - uintptr_t pop (uintptr_t xbit, uintptr_t dfl) - { - while (true) - { - size_t curr = this->_Rdidx (); - if (curr >= this->_Wridx ()) - return (dfl); - - uintptr_t rv = this->ptrs[curr]; - if (rv & xbit) - return (xbit); - else if (rv != dfl && xatomic_cas_bool (&this->ptrs[curr], rv, dfl)) - { - this->rd_idx.fetch_add (1, std::memory_order_relaxed); - return (rv); - } - - xatomic_spin_nop (); - } - } + bool push (uintptr_t val, uintptr_t xbit, uintptr_t empty); + uintptr_t pop (uintptr_t xbit, uintptr_t dfl); uintptr_t front () const { @@ -135,44 +101,53 @@ struct alignas (uintptr_t) q_data : public finalizable return (this->ptrs[idx - 1]); } + static void replace (std::atomic&, q_base *, + q_base *, uintptr_t); + static void clear (std::atomic&, q_base *, + q_base *, uintptr_t); + size_t size () const { return (this->_Wridx () - this->_Rdidx ()); } - - void safe_destroy (); }; -inline void -q_replace_cb (std::atomic& ptr, q_data *old, q_data *nq, uintptr_t) -{ - finalize (old); - ptr.store (nq, std::memory_order_relaxed); -} +} // namespace detail -inline void -q_clear_cb (std::atomic&, q_data *old, q_data *, uintptr_t empty) +template > +struct queue { - old->wr_idx.store (old->cap, std::memory_order_relaxed); - old->rd_idx.store (old->cap, std::memory_order_relaxed); + typedef detail::wrapped_traits<(sizeof (T) < sizeof (uintptr_t) && + std::is_integral::value), T> val_traits; + using Nalloc = typename std::allocator_traits::template + rebind_alloc; + + struct q_data : public detail::q_base + { + q_data () + { + } - for (size_t i = 0; i < old->cap; ++i) - old->ptrs[i] = empty; + static q_data* make (size_t cnt) + { + auto ret = (q_data *)alloc_uptrs (sizeof (q_data), cnt + 1); + new (ret) q_data (); + ret->ptrs = (uintptr_t *)(ret + 1); + ret->cap = cnt; - old->rd_idx.store (0, std::memory_order_release); - old->wr_idx.store (0, std::memory_order_release); -} + for (uintptr_t *p = ret->ptrs; p != ret->ptrs + cnt + 1; ) + *p++ = val_traits::FREE; -} // namespace detail + return (ret); + } -template -struct queue -{ - typedef detail::wrapped_traits<(sizeof (T) < sizeof (uintptr_t) && - std::is_integral::value) || (std::is_pointer::value && - alignof (T) >= 8), T> val_traits; + void safe_destroy () + { + dealloc_uptrs (this, this->ptrs + this->cap + 1); + } + }; - std::atomic impl; + std::atomic impl; typedef T value_type; typedef T& reference; @@ -184,33 +159,33 @@ struct queue void _Init (size_t size) { - this->impl = detail::q_data::make (size, val_traits::FREE); + this->impl = q_data::make (size); } - detail::q_data* _Data () + q_data* _Data () { - return (this->impl.load (std::memory_order_relaxed)); + return ((q_data *)this->impl.load (std::memory_order_relaxed)); } - const detail::q_data* _Data () const + const q_data* _Data () const { - return (this->impl.load (std::memory_order_relaxed)); + return ((const q_data *)this->impl.load (std::memory_order_relaxed)); } - void _Set_data (detail::q_data *qdp) + void _Set_data (q_data *qdp) { this->impl.store (qdp, std::memory_order_relaxed); } struct iterator : public cs_guard { - const detail::q_data *qdp; + const q_data *qdp; size_t idx; uintptr_t c_val; typedef std::forward_iterator_tag iterator_category; - iterator (const detail::q_data *q, size_t s) : qdp (q), idx (s) + iterator (const q_data *q, size_t s) : qdp (q), idx (s) { if (this->qdp) this->_Adv (); @@ -277,11 +252,11 @@ struct queue this->_Init (8); } - template + template void _Init (T1 n, const T2& val, std::true_type) { size_t ns = (size_t)n; - auto qdp = detail::q_data::make (detail::upsize (ns), val_traits::FREE); + auto qdp = q_data::make (detail::upsize (ns)); try { @@ -300,10 +275,10 @@ struct queue this->_Set_data (qdp); } - template + template void _Init (It first, It last, std::false_type) { - auto qdp = detail::q_data::make (8, val_traits::FREE); + auto qdp = q_data::make (8); try { for (size_t i = 0; first != last; ++first) @@ -313,7 +288,7 @@ struct queue if (i == qdp->cap) { - auto q2 = detail::q_data::make (i * 2, val_traits::FREE); + auto q2 = q_data::make (i * 2); for (size_t j = 0; j < i; ++j) q2->ptrs[j] = qdp->ptrs[j]; @@ -331,17 +306,17 @@ struct queue this->_Set_data (qdp); } - template + template queue (T1 first, T2 last) { this->_Init (first, last, typename std::is_integral::type ()); } - queue (const queue& right) : queue (right.begin (), right.end ()) + queue (const queue& right) : queue (right.begin (), right.end ()) { } - queue (queue&& right) + queue (queue&& right) { this->_Set_data (right._Data ()); right._Set_data (nullptr); @@ -351,7 +326,7 @@ struct queue { } - bool _Rearm (uintptr_t elem, detail::q_data *qdp) + bool _Rearm (uintptr_t elem, q_data *qdp) { size_t ix; uintptr_t prev; @@ -372,21 +347,23 @@ struct queue while (true) { if (qdp != this->_Data ()) - // Impl pointer has been installed - Return. + // The pointer has been installed - Return. return (false); else if ((qdp->ptrs[ix] & val_traits::XBIT) == 0) - /* The thread rearming the queue raised an exception. - * Recurse and see if we can pick up the slack. */ + /* + * The thread rearming the queue raised an exception. + * Recurse and see if we can pick up the slack. + */ return (this->_Rearm (elem, qdp)); xatomic_spin_nop (); } } - detail::q_data *nq = nullptr; + q_data *nq = nullptr; try { - nq = detail::q_data::make (qdp->cap * 2, val_traits::FREE); + nq = q_data::make (qdp->cap * 2); } catch (...) { @@ -424,14 +401,14 @@ struct queue this->_Push (val_traits::make (elem)); } - template + template void emplace (Args&& ...args) { cs_guard g; this->_Push (val_traits::make (std::forward(args)...)); } - optional pop () + std::optional pop () { cs_guard g; @@ -442,12 +419,11 @@ struct queue if (val == val_traits::DELT) // Queue is empty. - return (optional ()); + return (std::optional ()); else if (val != val_traits::XBIT) { val_traits::destroy (val); - optional rv (val_traits::get (val)); - return (rv); + return (std::optional (val_traits::get (val))); } while (qdp == this->_Data ()) @@ -455,7 +431,7 @@ struct queue } } - optional front () const + std::optional front () const { cs_guard g; while (true) @@ -467,12 +443,12 @@ struct queue continue; } else if (rv == val_traits::FREE) - return (optional ()); - return (optional (val_traits::get (rv))); + return (std::optional ()); + return (std::optional (val_traits::get (rv))); } } - optional back () const + std::optional back () const { cs_guard g; while (true) @@ -484,8 +460,8 @@ struct queue continue; } else if (rv == val_traits::FREE) - return (optional ()); - return (optional (val_traits::get (rv))); + return (std::optional ()); + return (std::optional (val_traits::get (rv))); } } @@ -527,9 +503,9 @@ struct queue return (this->end ()); } - void _Call_cb (detail::q_data *nq, uintptr_t xv, - void (*f) (std::atomic&, - detail::q_data *, detail::q_data *, uintptr_t)) + void _Call_cb (q_data *nq, uintptr_t xv, + void (*f) (std::atomic&, detail::q_base *, + detail::q_base *, uintptr_t)) { while (true) { @@ -566,16 +542,16 @@ struct queue } } - void _Assign (detail::q_data *nq) + void _Assign (q_data *nq) { - this->_Call_cb (nq, 0, detail::q_replace_cb); + this->_Call_cb (nq, 0, detail::q_base::replace); } - template + template void assign (T1 first, T2 last) { cs_guard g; - auto tmp = queue (first, last); + auto tmp = queue (first, last); this->_Assign (tmp._Data ()); tmp._Set_data (nullptr); } @@ -585,7 +561,7 @@ struct queue this->assign (lst.begin (), lst.end ()); } - queue& operator= (const queue& right) + queue& operator= (const queue& right) { if (this == &right) return (*this); @@ -594,7 +570,7 @@ struct queue return (*this); } - bool operator== (const queue& right) const + bool operator== (const queue& right) const { auto x1 = this->cbegin (), x2 = this->cend (); auto y1 = right.cbegin (), y2 = right.cend (); @@ -606,12 +582,12 @@ struct queue return (x1 == x2 && y1 == y2); } - bool operator!= (const queue& right) const + bool operator!= (const queue& right) const { return (!(*this == right)); } - bool operator< (const queue& right) const + bool operator< (const queue& right) const { auto x1 = this->cbegin (), x2 = this->cend (); auto y1 = right.cbegin (), y2 = right.cend (); @@ -627,22 +603,22 @@ struct queue return (y1 != y2); } - bool operator> (const queue& right) const + bool operator> (const queue& right) const { return (right < *this); } - bool operator<= (const queue& right) const + bool operator<= (const queue& right) const { return (!(right < *this)); } - bool operator>= (const queue& right) const + bool operator>= (const queue& right) const { return (!(*this < right)); } - queue& operator= (queue&& right) + queue& operator= (queue&& right) { auto prev = this->impl.exchange (right._Data (), std::memory_order_acq_rel); @@ -651,7 +627,7 @@ struct queue return (*this); } - static void _Destroy (detail::q_data *qdp) + static void _Destroy (q_data *qdp) { for (size_t i = qdp->_Rdidx (); i < qdp->cap; ++i) { @@ -666,7 +642,7 @@ struct queue void clear () { cs_guard g; - this->_Call_cb (nullptr, val_traits::FREE, detail::q_clear_cb); + this->_Call_cb (nullptr, val_traits::FREE, detail::q_base::clear); } size_t _Lock () @@ -687,7 +663,7 @@ struct queue } } - void swap (queue& right) + void swap (queue& right) { if (this == &right) return; @@ -725,8 +701,8 @@ struct queue namespace std { -template -void swap (xrcu::queue& left, xrcu::queue& right) +template +void swap (xrcu::queue& left, xrcu::queue& right) { left.swap (right); } diff --git a/skip_list.hpp b/xrcu/skip_list.hpp similarity index 85% rename from skip_list.hpp rename to xrcu/skip_list.hpp index 60402e2..370c4bf 100644 --- a/skip_list.hpp +++ b/xrcu/skip_list.hpp @@ -18,14 +18,17 @@ #ifndef __SKIP_LIST_HPP__ #define __SKIP_LIST_HPP__ 1 +#include "memory.hpp" #include "xrcu.hpp" #include "xatomic.hpp" -#include "optional.hpp" #include "utils.hpp" -#include #include +#include +#include #include #include +#include +#include namespace std { @@ -38,11 +41,6 @@ namespace xrcu namespace detail { -struct sl_node_base; - -void sl_dealloc_node (void *base); -sl_node_base* sl_alloc_node (unsigned int lvl, size_t size, uintptr_t **outpp); - static const uintptr_t SL_XBIT = 1; static const unsigned int SL_MAX_DEPTH = 24; @@ -51,7 +49,8 @@ static const int SL_UNLINK_NONE = 0; static const int SL_UNLINK_ASSIST = 1; static const int SL_UNLINK_FORCE = 2; -struct sl_node_base : public finalizable +template +struct alignas (uintptr_t) sl_node_base : public finalizable { unsigned int nlvl; uintptr_t *next; @@ -82,9 +81,13 @@ struct sl_node_base : public finalizable static sl_node_base* make_root (unsigned int depth) { - uintptr_t *np; - auto ret = sl_alloc_node (depth + 1, sizeof (sl_node_base), &np); - return (new (ret) sl_node_base (depth, np + 1)); + size_t uptrs; + uintptr_t *raw = alloc_uptrs (sizeof (sl_node_base), + depth + 1, &uptrs); + memset (raw, 0, uptrs * sizeof (uintptr_t)); + auto ret = (sl_node_base *)raw; + uintptr_t *endp = (uintptr_t *)(ret + 1); + return (new (ret) sl_node_base (depth, endp + 1)); } static unsigned int @@ -100,8 +103,9 @@ struct sl_node_base : public finalizable if (lvl <= prev) return (lvl); else if (prev == SL_MAX_DEPTH || - hw.compare_exchange_weak (prev, prev + 1, - std::memory_order_acq_rel, std::memory_order_relaxed)) + hw.compare_exchange_weak (prev, prev + 1, + std::memory_order_acq_rel, + std::memory_order_relaxed)) return (prev); xatomic_spin_nop (); @@ -110,54 +114,57 @@ struct sl_node_base : public finalizable void safe_destroy () { - sl_dealloc_node (this); + dealloc_uptrs (this, this->next + this->nlvl); } }; -template -struct sl_node : public sl_node_base +template +struct sl_node : public sl_node_base { T key; + typedef sl_node _Self; sl_node (unsigned int lvl, uintptr_t *np, const T& kv) : - sl_node_base (lvl, np), key (kv) + sl_node_base (lvl, np), key (kv) { } - template + template sl_node (unsigned int lvl, uintptr_t *np, Args... args) : - sl_node_base (lvl, np), key (std::forward(args)...) + sl_node_base (lvl, np), key (std::forward(args)...) { } - static sl_node* copy (unsigned int lvl, const T& k) + static sl_node* copy (unsigned int lvl, const T& k) { - uintptr_t *np; - auto self = (sl_node *)sl_alloc_node (lvl, sizeof (sl_node), &np); - + size_t uptrs; + uintptr_t *raw = alloc_uptrs (sizeof (_Self), lvl, &uptrs); + auto ret = (sl_node *)raw; try { - return (new (self) sl_node (lvl, np, k)); + uintptr_t *endp = (uintptr_t *)(ret + 1); + return (new (ret) sl_node (lvl, endp, k)); } catch (...) { - sl_dealloc_node (self); + dealloc_uptrs (raw, raw + uptrs); throw; } } - template - static sl_node* move (unsigned int lvl, Args... args) + template + static sl_node* move (unsigned int lvl, Args... args) { - uintptr_t *np; - auto self = (sl_node *)sl_alloc_node (lvl, sizeof (sl_node), &np); - return (new (self) sl_node (lvl, np, std::forward(args)...)); + auto ret = (_Self *)alloc_uptrs (sizeof (_Self), lvl); + uintptr_t *np = (uintptr_t *)(ret + 1); + return (new (ret) sl_node (lvl, np, + std::forward(args)...)); } void safe_destroy () { destroy (&this->key); - sl_dealloc_node (this); + dealloc_uptrs (this, this->next + this->nlvl); } }; @@ -170,10 +177,14 @@ init_preds_succs (uintptr_t *p1, uintptr_t *p2) } // namespace detail -template > +template , + typename Alloc = std::allocator> struct skip_list { - std::atomic head; + using Nalloc = typename std::allocator_traits::template + rebind_alloc; + + std::atomic *> head; Cmp cmpfn; unsigned int max_depth; std::atomic hi_water { 1 }; @@ -188,8 +199,8 @@ struct skip_list typedef const T& const_reference; typedef T* pointer; typedef const T* const_pointer; - typedef skip_list _Self; - typedef detail::sl_node _Node; + typedef skip_list _Self; + typedef detail::sl_node _Node; uintptr_t _Head () const { @@ -216,7 +227,7 @@ struct skip_list this->_Init (c, depth); } - template + template skip_list (Iter first, Iter last, Cmp c = Cmp (), unsigned int depth = detail::SL_MAX_DEPTH) { @@ -277,7 +288,7 @@ struct skip_list if (this->node == 0) break; - this->node = detail::sl_node_base::at (this->node, 0); + this->node = _Node::at (this->node, 0); if ((this->node & detail::SL_XBIT) == 0) break; } @@ -398,11 +409,11 @@ struct skip_list return (got || unlink == detail::SL_UNLINK_SKIP ? it : 0); } - optional find (const T& key) const + std::optional find (const T& key) const { cs_guard g; uintptr_t rv = this->_Find_preds (0, key, detail::SL_UNLINK_NONE); - return (rv ? optional (this->_Getk (rv)) : optional ()); + return (rv ? std::optional (this->_Getk (rv)) : std::optional ()); } bool contains (const T& key) const @@ -510,7 +521,7 @@ struct skip_list if (it == 0) return (it); - detail::sl_node_base *nodep = _Node::get (it); + detail::sl_node_base *nodep = _Node::get (it); uintptr_t qx = 0, next = 0; for (int lvl = nodep->nlvl - 1; lvl >= 0; --lvl) @@ -545,11 +556,11 @@ struct skip_list return (this->_Erase (key) != 0); } - optional remove (const T& key) + std::optional remove (const T& key) { cs_guard g; uintptr_t it = this->_Erase (key); - return (it ? optional (_Self::_Getk (it)) : optional ()); + return (it ? std::optional (_Self::_Getk (it)) : std::optional ()); } const_iterator cbegin () const @@ -600,7 +611,7 @@ struct skip_list } template - void _Fini_root (detail::sl_node_base *xroot) + void _Fini_root (detail::sl_node_base *xroot) { for (uintptr_t run = (uintptr_t)xroot; run != 0; ) { @@ -631,7 +642,7 @@ struct skip_list } } - template + template void assign (Iter first, Iter last) { _Self tmp (first, last); @@ -699,7 +710,7 @@ struct skip_list namespace std { -template +template void swap (xrcu::skip_list& left, xrcu::skip_list& right) { diff --git a/stack.hpp b/xrcu/stack.hpp similarity index 76% rename from stack.hpp rename to xrcu/stack.hpp index 525efdb..bf3d2fd 100644 --- a/stack.hpp +++ b/xrcu/stack.hpp @@ -19,11 +19,12 @@ #define __XRCU_STACK_HPP__ 1 #include "xrcu.hpp" -#include "optional.hpp" #include #include -#include #include +#include +#include +#include namespace std { @@ -96,11 +97,9 @@ struct stack_iter_base : public cs_guard } // namespace detail. -template +template > struct stack { - std::atomic hnode; - struct _Stknode : public detail::stack_node_base, finalizable { T value; @@ -113,19 +112,46 @@ struct stack { } - template + template _Stknode (Args&&... args) : value (std::forward(args)...) { } + static _Stknode* alloc (const T& val) + { + auto ret = Nalloc().allocate (1); + try + { + new (ret) _Stknode (val); + return (ret); + } + catch (...) + { + Nalloc().deallocate (ret, 1); + throw; + } + } + + template + static _Stknode* move (Args... args) + { + auto ret = Nalloc().allocate (1); + new (ret) _Stknode (std::forward(args)...); + return (ret); + } + void safe_destroy () { auto runp = this->next; - delete this; - stack::_Clean_nodes (runp); + stack::_Destroy (this); + stack::_Clean_nodes (runp); } }; + std::atomic hnode; + using Nalloc = typename std::allocator_traits::template + rebind_alloc<_Stknode>; + typedef _Stknode node_type; typedef T value_type; typedef T& reference; @@ -140,17 +166,23 @@ struct stack this->hnode.store (ptr, std::memory_order_relaxed); } + static void _Destroy (_Stknode *node) + { + node->value.~T (); + Nalloc().deallocate (node, 1); + } + static void _Clean_nodes (detail::stack_node_base *runp) { while (runp) { auto tmp = runp->next; - delete (node_type *)runp; + stack::_Destroy ((_Stknode *)runp); runp = tmp; } } - template + template void _Init (T1 first, T1 last, std::false_type) { detail::stack_node_base *runp = nullptr, **outp = &runp; @@ -158,7 +190,7 @@ struct stack { for (; first != last; ++first) { - *outp = new node_type (*first); + *outp = _Stknode::alloc (*first); outp = &(*outp)->next; } } @@ -171,7 +203,7 @@ struct stack this->_Reset (runp); } - template + template void _Init (T1 n, const T2& value, std::true_type) { detail::stack_node_base *runp = nullptr, **outp = &runp; @@ -180,7 +212,7 @@ struct stack { for (T1 x = 0; x != n; ++x) { - *outp = new node_type (value); + *outp = _Stknode::alloc (value); outp = &(*outp)->next; } } @@ -198,7 +230,7 @@ struct stack this->_Reset (nullptr); } - template + template stack (T1 first, T2 last) { this->_Reset (nullptr); @@ -209,11 +241,11 @@ struct stack { } - stack (const stack& right) : stack (right.begin (), right.end ()) + stack (const stack& right) : stack (right.begin (), right.end ()) { } - stack (stack&& right) + stack (stack&& right) { this->_Reset (right._Root ()); right._Reset (nullptr); @@ -222,32 +254,32 @@ struct stack void push (const T& value) { cs_guard g; - detail::stack_node_base::push (this->hnode, new node_type (value)); + detail::stack_node_base::push (this->hnode, _Stknode::alloc (value)); } void push (T&& value) { cs_guard g; detail::stack_node_base::push (this->hnode, - new node_type (std::move (value))); + _Stknode::move (std::move (value))); } - template + template void _Push (Iter first, Iter last, std::false_type) { if (first == last) return; - node_type *np; + node_type *np = nullptr; try { - np = new node_type (*first); + np = _Stknode::alloc (*first); auto **outp = &np->next; for (++first; first != last; ++first) { - node_type *tmp = new node_type (*first); + node_type *tmp = _Stknode::alloc (*first); *outp = tmp, outp = &tmp->next; } @@ -260,22 +292,22 @@ struct stack } } - template + template void _Push (T1 n, const T2& value, std::true_type) { if (n == 0) return; - node_type *np; + node_type *np = nullptr; try { - np = new node_type (value); + np = _Stknode::alloc (value); auto **outp = &np->next; for (; n != 0; --n) { - node_type *tmp = new node_type (value); + node_type *tmp = _Stknode::alloc (value); *outp = tmp, outp = &tmp->next; } @@ -288,28 +320,28 @@ struct stack } } - template + template void push (T1 first, T2 last) { this->_Push (first, last, typename std::is_integral::type ()); } - template + template void emplace (Args&& ...args) { - detail::stack_node_base::push (this->hnode, - new node_type (std::forward(args)...)); + auto np = _Stknode::move (std::forward(args)...); + detail::stack_node_base::push (this->hnode, np); } - optional pop () + std::optional pop () { cs_guard g; auto node = detail::stack_node_base::pop (this->hnode); if (!node) - return (optional ()); + return (std::optional ()); - optional ret { ((node_type *)node)->value }; + std::optional ret { ((node_type *)node)->value }; finalize ((node_type *)node); return (ret); } @@ -324,11 +356,11 @@ struct stack return ((node_type *)detail::stack_node_base::root (this->hnode)); } - optional top () + std::optional top () { cs_guard g; auto node = this->_Root (); - return (node ? optional { node->value } : optional ()); + return (node ? std::optional { node->value } : std::optional ()); } struct iterator : public detail::stack_iter_base @@ -432,7 +464,7 @@ struct stack return (this->_Root () == nullptr); } - void swap (stack& right) + void swap (stack& right) { cs_guard g; @@ -440,7 +472,7 @@ struct stack detail::stack_node_base::swap (this->hnode, right.hnode); } - stack& operator= (const stack& right) + stack& operator= (const stack& right) { cs_guard g; @@ -450,7 +482,7 @@ struct stack return (*this); } - stack& operator= (stack&& right) + stack& operator= (stack&& right) { this->swap (right); finalize (right._Root ()); @@ -458,7 +490,7 @@ struct stack return (*this); } - bool operator== (const stack& right) const + bool operator== (const stack& right) const { auto x1 = this->cbegin (), x2 = this->cend (); auto y1 = right.cbegin (), y2 = right.cend (); @@ -470,12 +502,12 @@ struct stack return (x1 == x2 && y1 == y2); } - bool operator!= (const stack& right) const + bool operator!= (const stack& right) const { return (!(*this == right)); } - bool operator< (const stack& right) const + bool operator< (const stack& right) const { auto x1 = this->cbegin (), x2 = this->cend (); auto y1 = right.cbegin (), y2 = right.cend (); @@ -491,17 +523,17 @@ struct stack return (y1 != y2); } - bool operator> (const stack& right) const + bool operator> (const stack& right) const { return (right < *this); } - bool operator<= (const stack& right) const + bool operator<= (const stack& right) const { return (!(right < *this)); } - bool operator>= (const stack& right) const + bool operator>= (const stack& right) const { return (!(*this < right)); } @@ -512,10 +544,10 @@ struct stack finalize ((node_type *)prev); } - template + template void assign (T1 first, T2 last) { - auto tmp = stack (first, last); + auto tmp = stack (first, last); this->swap (tmp); finalize (tmp._Root ()); tmp._Reset (nullptr); @@ -542,8 +574,8 @@ struct stack namespace std { -template -void swap (xrcu::stack& left, xrcu::stack& right) +template +void swap (xrcu::stack& left, xrcu::stack& right) { return (left.swap (right)); } diff --git a/utils.hpp b/xrcu/utils.hpp similarity index 100% rename from utils.hpp rename to xrcu/utils.hpp diff --git a/xrcu/version.hpp b/xrcu/version.hpp new file mode 100644 index 0000000..1fe648a --- /dev/null +++ b/xrcu/version.hpp @@ -0,0 +1 @@ +const int MAJOR = 0; const int MINOR = 2; diff --git a/xatomic.hpp b/xrcu/xatomic.hpp similarity index 82% rename from xatomic.hpp rename to xrcu/xatomic.hpp index 4064f5a..51b96fb 100644 --- a/xatomic.hpp +++ b/xrcu/xatomic.hpp @@ -48,7 +48,7 @@ inline uintptr_t xatomic_cas (uintptr_t *ptr, uintptr_t exp, uintptr_t nval) { __atomic_compare_exchange_n (ptr, &exp, nval, 0, - __ATOMIC_ACQ_REL, __ATOMIC_RELAXED); + __ATOMIC_ACQ_REL, __ATOMIC_RELAXED); return (exp); } @@ -58,10 +58,10 @@ xatomic_or (uintptr_t *ptr, uintptr_t val) return (__atomic_fetch_or (ptr, val, __ATOMIC_ACQ_REL)); } -inline void +inline uintptr_t xatomic_and (uintptr_t *ptr, uintptr_t val) { - (void)__atomic_and_fetch (ptr, val, __ATOMIC_ACQ_REL); + return (__atomic_fetch_and (ptr, val, __ATOMIC_ACQ_REL)); } inline uintptr_t @@ -81,8 +81,8 @@ xatomic_add (uintptr_t *ptr, intptr_t val) #include static_assert (sizeof (uintptr_t) == sizeof (std::atomic_uintptr_t) && - alignof (uintptr_t) == alignof (std::atomic_uintptr_t), - "unsupported compiler (uintptr_t and atomic_uintptr_t mismatch)"); + alignof (uintptr_t) == alignof (std::atomic_uintptr_t), + "uintptr_t and atomic_uintptr_t must match in size"); namespace xrcu { @@ -92,8 +92,8 @@ namespace xrcu inline uintptr_t xatomic_cas (uintptr_t *ptr, uintptr_t exp, uintptr_t nval) { - AS_ATOMIC(ptr)->compare_exchange_weak (exp, nval, - std::memory_order_acq_rel, std::memory_order_relaxed); + AS_ATOMIC(ptr)->compare_exchange_weak (exp, nval, std::memory_order_acq_rel, + std::memory_order_relaxed); return (exp); } @@ -124,14 +124,14 @@ xatomic_or (uintptr_t *ptr, uintptr_t val) } } -inline void +inline uintptr_t xatomic_and (uintptr_t *ptr, uintptr_t val) { while (true) { uintptr_t ret = *ptr; if (xatomic_cas (ptr, ret, ret & val) == ret) - return; + return (ret); xatomic_spin_nop (); } @@ -195,24 +195,24 @@ xatomic_cas_bool (uintptr_t *ptr, uintptr_t exp, uintptr_t nval) # if defined (_ILP32) || defined (__ILP32__) inline bool -xatomic_dcas_bool (uintptr_t *ptr, uintptr_t elo, - uintptr_t ehi, uintptr_t nlo, uintptr_t nhi) +xatomic_dcas_bool (uintptr_t *ptr, uintptr_t elo, uintptr_t ehi, + uintptr_t nlo, uintptr_t nhi) { uint64_t exp = ((uint64_t)ehi << 32) | elo; uint64_t nval = ((uint64_t)nhi << 32) | nlo; - return (__atomic_compare_exchange_n ((uint64_t *)ptr, - &exp, nval, 0, __ATOMIC_ACQ_REL, __ATOMIC_RELAXED)); + return (__atomic_compare_exchange_n ((uint64_t *)ptr, &exp, nval, 0, + __ATOMIC_ACQ_REL, __ATOMIC_RELAXED)); } # else inline bool -xatomic_dcas_bool (uintptr_t *ptr, uintptr_t elo, - uintptr_t ehi, uintptr_t nlo, uintptr_t nhi) +xatomic_dcas_bool (uintptr_t *ptr, uintptr_t elo, uintptr_t ehi, + uintptr_t nlo, uintptr_t nhi) { char r; - __asm__ __volatile__ + asm volatile ( "lock; cmpxchg16b %0\n\t" "setz %1" @@ -234,13 +234,13 @@ xatomic_dcas_bool (uintptr_t *ptr, uintptr_t elo, # if defined (__PIC__) && __GNUC__ < 5 inline bool -xatomic_dcas_bool (uintptr_t *ptr, uintptr_t elo, - uintptr_t ehi, uintptr_t nlo, uintptr_t nhi) +xatomic_dcas_bool (uintptr_t *ptr, uintptr_t elo, uintptr_t ehi, + uintptr_t nlo, uintptr_t nhi) { uintptr_t s; char r; - __asm__ __volatile__ + asm volatile ( "movl %%ebx, %2\n\t" "leal %0, %%edi\n\t" @@ -260,11 +260,11 @@ xatomic_dcas_bool (uintptr_t *ptr, uintptr_t elo, # else inline bool -xatomic_dcas_bool (uintptr_t *ptr, uintptr_t elo, - uintptr_t ehi, uintptr_t nlo, uintptr_t nhi) +xatomic_dcas_bool (uintptr_t *ptr, uintptr_t elo, uintptr_t ehi, + uintptr_t nlo, uintptr_t nhi) { char r; - __asm__ __volatile__ + asm volatile ( "lock; cmpxchg8b %0\n\t" "setz %1" @@ -288,8 +288,8 @@ xatomic_dcas_bool (uintptr_t *ptr, uintptr_t elo, # define XRCU_HAVE_XATOMIC_DCAS inline bool -xatomic_dcas_bool (uintptr_t *ptr, uintptr_t elo, - uintptr_t ehi, uintptr_t nlo, uintptr_t nhi) +xatomic_dcas_bool (uintptr_t *ptr, uintptr_t elo, uintptr_t ehi, + uintptr_t nlo, uintptr_t nhi) { uint64_t qv = ((uint64_t)ehi << 32) | elo; uint64_t nv = ((uint64_t)nhi << 32) | nlo; @@ -297,7 +297,7 @@ xatomic_dcas_bool (uintptr_t *ptr, uintptr_t elo, while (true) { uint64_t tmp; - __asm__ __volatile__ + asm volatile ( "ldrexd %0, %H0, [%1]" : "=&r" (tmp) : "r" (ptr) @@ -307,7 +307,7 @@ xatomic_dcas_bool (uintptr_t *ptr, uintptr_t elo, return (false); int r; - __asm__ __volatile__ + asm volatile ( "strexd %0, %3, %H3, [%2]" : "=&r" (r), "+m" (*ptr) @@ -325,13 +325,13 @@ xatomic_dcas_bool (uintptr_t *ptr, uintptr_t elo, # define XRCU_HAVE_XATOMIC_DCAS inline bool -xatomic_dcas_bool (uintptr_t *ptr, uintptr_t elo, - uintptr_t ehi, uintptr_t nlo, uintptr_t nhi) +xatomic_dcas_bool (uintptr_t *ptr, uintptr_t elo, uintptr_t ehi, + uintptr_t nlo, uintptr_t nhi) { while (true) { uintptr_t t1, t2; - __asm__ __volatile__ + asm volatile ( "ldaxp %0, %1, %2" : "=&r" (t1), "=&r" (t2) @@ -342,7 +342,7 @@ xatomic_dcas_bool (uintptr_t *ptr, uintptr_t elo, return (false); int r; - __asm__ __volatile__ + asm volatile ( "stxp %w0, %2, %3, %1" : "=&r" (r), "=Q" (*ptr) diff --git a/xrcu.hpp b/xrcu/xrcu.hpp similarity index 90% rename from xrcu.hpp rename to xrcu/xrcu.hpp index f89d29e..54154d2 100644 --- a/xrcu.hpp +++ b/xrcu/xrcu.hpp @@ -30,8 +30,10 @@ extern void exit_cs (); // Test if the calling thread is in a read-side critical section. extern bool in_cs (); -/* Wait until all readers have entered a quiescent state. - * Returns false if a deadlock is detected, true otherwise. */ +/* + * Wait until all readers have entered a quiescent state. + * Returns false if a deadlock is detected, true otherwise. + */ extern bool sync (); // Base type for finalizable objects. @@ -50,8 +52,10 @@ struct finalizable // Add FINP to the list of objects to be finalized after a grace period. extern void finalize (finalizable *finp); -/* Force destruction of pending finalizable objects. - * Returns true if they were destroyed. */ +/* + * Force destruction of pending finalizable objects. + * Returns true if they were destroyed, false otherwise. + */ extern bool flush_finalizers (); struct cs_guard