#pragma once #include "UnityPlatformConfigure.h" #include "ExtendedAtomicTypes.h" #include "AtomicNode.h" UNITY_PLATFORM_BEGIN_NAMESPACE; #if ATOMIC_HAS_QUEUE // clang has arm-specific bultins to implement LL/SC // NB the common pattern in implementation *there* seems to be chain of ifdef-s (not if-s!). So keep it same // if you compare arm+clang specific impl to generic cpp impl, you will notice that stricter memory ordering is used. // It was conscious decision. Performance-wise it is on-par (or faster) than generic cpp code anyway // some whining about DCAS define and its usage: AtomicQueue/AtomicStack do actually use DCAS // as in second "word" in atomic_word2 is for counter to avoid ABA problem // at the same time, in AtomicList atomic_word2 is used NOT for DCAS // as its second word is "tag" which has some meaning to the outside code (AtomicQueue), // so it is actually "please use two words atomically" // as opposed to "please use one word atomically and use second as implementation detail to fight ABA problem" // that means that for AtomicQueue/AtomicStack we can easily do specific impl with LL/SC // but for AtomicList we need to use atomic_word2 #if (defined(__arm__) || defined(__arm64__) || defined(__aarch64__)) && UNITY_ATOMIC_USE_CLANG_ATOMICS #define UNITY_ATOMICQUEUE_USE_CLANG_ARM_ATOMICS 1 #endif // A generic lockfree stack. // Any thread can Push / Pop nodes to the stack. // The stack is lockfree and highly optimized. It has different implementations for different architectures. // On intel / arm it is built with double CAS: // http://en.wikipedia.org/wiki/Double_compare-and-swap // On PPC it is built on LL/SC: // http://en.wikipedia.org/wiki/Load-link/store-conditional class AtomicStack { #if defined(ATOMIC_HAS_DCAS) volatile atomic_word2 _top; #else volatile atomic_word _top; #endif public: AtomicStack(); ~AtomicStack(); int IsEmpty() const; void Push(AtomicNode *node); void PushAll(AtomicNode *first, AtomicNode *last); AtomicNode *Pop(); AtomicNode *PopAll(); }; AtomicStack* CreateAtomicStack(); void DestroyAtomicStack(AtomicStack* s); // A generic lockfree queue FIFO queue. // Any thread can Enqueue / Dequeue in parallel. // We do guarantee that all 3 data pointer are the same after dequeuing. // // But when pushing / popping a node there is no guarantee that the pointer to the AtomicNode is the same. // Enqueue adds node to the head, and Dequeue pops it from the tail. // Implementation relies on dummy head node which allow to modify next pointer atomically. // Thus Dequeue pops not the enqueued node, but the next one. // Empty: [ head ] [next] [ tail ] // dummy 0 dummy // Enqueue: [ head ] [next] [next] [ tail ] // node1 dummy 0 dummy // Dequeue: [ head ] [next] [ tail ] -> dummy dequeued, but with node1 data[3] // node1 0 node1 // Make sure to destroy nodes consistently. // The queue is lockfree and highly optimized. It has different implementations for different architectures. // On intel / arm it is built with double CAS: // http://en.wikipedia.org/wiki/Double_compare-and-swap // On PPC it is built on LL/SC: // http://en.wikipedia.org/wiki/Load-link/store-conditional class AtomicQueue { #if defined(ATOMIC_HAS_DCAS) volatile atomic_word2 _tail; #else volatile atomic_word _tail; #endif volatile atomic_word _head; public: AtomicQueue(); ~AtomicQueue(); int IsEmpty() const; void Enqueue(AtomicNode *node); void EnqueueAll(AtomicNode *first, AtomicNode *last); AtomicNode *Dequeue(); }; AtomicQueue* CreateAtomicQueue(); void DestroyAtomicQueue(AtomicQueue* s); #elif IL2CPP_SUPPORT_THREADS #if IL2CPP_TARGET_JAVASCRIPT // Provide a dummy implementation for Emscripten that lets us build, but won't // work at runtime. class AtomicStack { public: AtomicStack() {} int IsEmpty() const { return 1; } void Push(AtomicNode *node) { } void PushAll(AtomicNode *first, AtomicNode *last) { } AtomicNode *Pop() { return NULL; } AtomicNode *PopAll() { return NULL; } }; #else #error Platform is missing atomic queue implementation #endif // IL2CPP_TARGET_JAVASCRIPT #endif // // Special concurrent list for JobQueue // This code is not meant to be general purpose and should not be used outside of the job queue. class AtomicList { #if defined(ATOMIC_HAS_DCAS) || defined(UNITY_ATOMICQUEUE_USE_CLANG_ARM_ATOMICS) volatile atomic_word2 _top; #else volatile atomic_word _top; volatile atomic_word _ver; #endif public: void Init(); atomic_word Tag(); AtomicNode *Peek(); AtomicNode *Load(atomic_word &tag); AtomicNode *Clear(AtomicNode *old, atomic_word tag); bool Add(AtomicNode *first, AtomicNode *last, atomic_word tag); AtomicNode* Touch(atomic_word tag); void Reset(AtomicNode *node, atomic_word tag); }; UNITY_PLATFORM_END_NAMESPACE;