AtomicQueue.h 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. #pragma once
  2. #include "UnityPlatformConfigure.h"
  3. #include "ExtendedAtomicTypes.h"
  4. #include "AtomicNode.h"
  5. UNITY_PLATFORM_BEGIN_NAMESPACE;
  6. #if ATOMIC_HAS_QUEUE
  7. // clang has arm-specific bultins to implement LL/SC
  8. // NB the common pattern in implementation *there* seems to be chain of ifdef-s (not if-s!). So keep it same
  9. // if you compare arm+clang specific impl to generic cpp impl, you will notice that stricter memory ordering is used.
  10. // It was conscious decision. Performance-wise it is on-par (or faster) than generic cpp code anyway
  11. // some whining about DCAS define and its usage: AtomicQueue/AtomicStack do actually use DCAS
  12. // as in second "word" in atomic_word2 is for counter to avoid ABA problem
  13. // at the same time, in AtomicList atomic_word2 is used NOT for DCAS
  14. // as its second word is "tag" which has some meaning to the outside code (AtomicQueue),
  15. // so it is actually "please use two words atomically"
  16. // as opposed to "please use one word atomically and use second as implementation detail to fight ABA problem"
  17. // that means that for AtomicQueue/AtomicStack we can easily do specific impl with LL/SC
  18. // but for AtomicList we need to use atomic_word2
  19. #if (defined(__arm__) || defined(__arm64__) || defined(__aarch64__)) && UNITY_ATOMIC_USE_CLANG_ATOMICS
  20. #define UNITY_ATOMICQUEUE_USE_CLANG_ARM_ATOMICS 1
  21. #endif
  22. // A generic lockfree stack.
  23. // Any thread can Push / Pop nodes to the stack.
  24. // The stack is lockfree and highly optimized. It has different implementations for different architectures.
  25. // On intel / arm it is built with double CAS:
  26. // http://en.wikipedia.org/wiki/Double_compare-and-swap
  27. // On PPC it is built on LL/SC:
  28. // http://en.wikipedia.org/wiki/Load-link/store-conditional
  29. class AtomicStack
  30. {
  31. #if defined(ATOMIC_HAS_DCAS)
  32. volatile atomic_word2 _top;
  33. #else
  34. volatile atomic_word _top;
  35. #endif
  36. public:
  37. AtomicStack();
  38. ~AtomicStack();
  39. int IsEmpty() const;
  40. void Push(AtomicNode *node);
  41. void PushAll(AtomicNode *first, AtomicNode *last);
  42. AtomicNode *Pop();
  43. AtomicNode *PopAll();
  44. };
  45. AtomicStack* CreateAtomicStack();
  46. void DestroyAtomicStack(AtomicStack* s);
  47. // A generic lockfree queue FIFO queue.
  48. // Any thread can Enqueue / Dequeue in parallel.
  49. // We do guarantee that all 3 data pointer are the same after dequeuing.
  50. //
  51. // But when pushing / popping a node there is no guarantee that the pointer to the AtomicNode is the same.
  52. // Enqueue adds node to the head, and Dequeue pops it from the tail.
  53. // Implementation relies on dummy head node which allow to modify next pointer atomically.
  54. // Thus Dequeue pops not the enqueued node, but the next one.
  55. // Empty: [ head ] [next] [ tail ]
  56. // dummy 0 dummy
  57. // Enqueue: [ head ] [next] [next] [ tail ]
  58. // node1 dummy 0 dummy
  59. // Dequeue: [ head ] [next] [ tail ] -> dummy dequeued, but with node1 data[3]
  60. // node1 0 node1
  61. // Make sure to destroy nodes consistently.
  62. // The queue is lockfree and highly optimized. It has different implementations for different architectures.
  63. // On intel / arm it is built with double CAS:
  64. // http://en.wikipedia.org/wiki/Double_compare-and-swap
  65. // On PPC it is built on LL/SC:
  66. // http://en.wikipedia.org/wiki/Load-link/store-conditional
  67. class AtomicQueue
  68. {
  69. #if defined(ATOMIC_HAS_DCAS)
  70. volatile atomic_word2 _tail;
  71. #else
  72. volatile atomic_word _tail;
  73. #endif
  74. volatile atomic_word _head;
  75. public:
  76. AtomicQueue();
  77. ~AtomicQueue();
  78. int IsEmpty() const;
  79. void Enqueue(AtomicNode *node);
  80. void EnqueueAll(AtomicNode *first, AtomicNode *last);
  81. AtomicNode *Dequeue();
  82. };
  83. AtomicQueue* CreateAtomicQueue();
  84. void DestroyAtomicQueue(AtomicQueue* s);
  85. #elif IL2CPP_SUPPORT_THREADS
  86. #if IL2CPP_TARGET_JAVASCRIPT
  87. // Provide a dummy implementation for Emscripten that lets us build, but won't
  88. // work at runtime.
  89. class AtomicStack
  90. {
  91. public:
  92. AtomicStack() {}
  93. int IsEmpty() const
  94. {
  95. return 1;
  96. }
  97. void Push(AtomicNode *node)
  98. {
  99. }
  100. void PushAll(AtomicNode *first, AtomicNode *last)
  101. {
  102. }
  103. AtomicNode *Pop()
  104. {
  105. return NULL;
  106. }
  107. AtomicNode *PopAll()
  108. {
  109. return NULL;
  110. }
  111. };
  112. #else
  113. #error Platform is missing atomic queue implementation
  114. #endif // IL2CPP_TARGET_JAVASCRIPT
  115. #endif
  116. //
  117. // Special concurrent list for JobQueue
  118. // This code is not meant to be general purpose and should not be used outside of the job queue.
  119. class AtomicList
  120. {
  121. #if defined(ATOMIC_HAS_DCAS) || defined(UNITY_ATOMICQUEUE_USE_CLANG_ARM_ATOMICS)
  122. volatile atomic_word2 _top;
  123. #else
  124. volatile atomic_word _top;
  125. volatile atomic_word _ver;
  126. #endif
  127. public:
  128. void Init();
  129. atomic_word Tag();
  130. AtomicNode *Peek();
  131. AtomicNode *Load(atomic_word &tag);
  132. AtomicNode *Clear(AtomicNode *old, atomic_word tag);
  133. bool Add(AtomicNode *first, AtomicNode *last, atomic_word tag);
  134. AtomicNode* Touch(atomic_word tag);
  135. void Reset(AtomicNode *node, atomic_word tag);
  136. };
  137. UNITY_PLATFORM_END_NAMESPACE;