1 /*
2     Copyright (c) 2005-2021 Intel Corporation
3 
4     Licensed under the Apache License, Version 2.0 (the "License");
5     you may not use this file except in compliance with the License.
6     You may obtain a copy of the License at
7 
8         http://www.apache.org/licenses/LICENSE-2.0
9 
10     Unless required by applicable law or agreed to in writing, software
11     distributed under the License is distributed on an "AS IS" BASIS,
12     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13     See the License for the specific language governing permissions and
14     limitations under the License.
15 */
16 
17 /** Before making any changes in the implementation, please emulate algorithmic changes
18     with SPIN tool using <TBB directory>/tools/spin_models/ReaderWriterMutex.pml.
19     There could be some code looking as "can be restructured" but its structure does matter! */
20 
21 #include "oneapi/tbb/queuing_rw_mutex.h"
22 #include "oneapi/tbb/detail/_assert.h"
23 #include "oneapi/tbb/detail/_utils.h"
24 #include "itt_notify.h"
25 
26 namespace tbb {
27 namespace detail {
28 namespace r1 {
29 
30 #if defined(_MSC_VER) && !defined(__INTEL_COMPILER)
31     // Workaround for overzealous compiler warnings
32     #pragma warning (push)
33     #pragma warning (disable: 4311 4312)
34 #endif
35 
36 //! A view of a T* with additional functionality for twiddling low-order bits.
37 template<typename T>
38 class tricky_atomic_pointer {
39 public:
40     using word = uintptr_t;
41 
fetch_add(std::atomic<word> & location,word addend,std::memory_order memory_order)42     static T* fetch_add( std::atomic<word>& location, word addend, std::memory_order memory_order ) {
43         return reinterpret_cast<T*>(location.fetch_add(addend, memory_order));
44     }
45 
exchange(std::atomic<word> & location,T * value,std::memory_order memory_order)46     static T* exchange( std::atomic<word>& location, T* value, std::memory_order memory_order ) {
47         return reinterpret_cast<T*>(location.exchange(reinterpret_cast<word>(value), memory_order));
48     }
49 
compare_exchange_strong(std::atomic<word> & obj,const T * expected,const T * desired,std::memory_order memory_order)50     static T* compare_exchange_strong( std::atomic<word>& obj, const T* expected, const T* desired, std::memory_order memory_order ) {
51         word expd = reinterpret_cast<word>(expected);
52         obj.compare_exchange_strong(expd, reinterpret_cast<word>(desired), memory_order);
53         return reinterpret_cast<T*>(expd);
54     }
55 
store(std::atomic<word> & location,const T * value,std::memory_order memory_order)56     static void store( std::atomic<word>& location, const T* value, std::memory_order memory_order ) {
57         location.store(reinterpret_cast<word>(value), memory_order);
58     }
59 
load(std::atomic<word> & location,std::memory_order memory_order)60     static T* load( std::atomic<word>& location, std::memory_order memory_order ) {
61         return reinterpret_cast<T*>(location.load(memory_order));
62     }
63 
spin_wait_while_eq(const std::atomic<word> & location,const T * value)64     static void spin_wait_while_eq(const std::atomic<word>& location, const T* value) {
65         tbb::detail::d0::spin_wait_while_eq(location, reinterpret_cast<word>(value) );
66     }
67 
68     T* & ref;
tricky_atomic_pointer(T * & original)69     tricky_atomic_pointer( T*& original ) : ref(original) {};
70     tricky_atomic_pointer(const tricky_atomic_pointer&) = delete;
71     tricky_atomic_pointer& operator=(const tricky_atomic_pointer&) = delete;
operator &(const word operand2) const72     T* operator&( const word operand2 ) const {
73         return reinterpret_cast<T*>( reinterpret_cast<word>(ref) & operand2 );
74     }
operator |(const word operand2) const75     T* operator|( const word operand2 ) const {
76         return reinterpret_cast<T*>( reinterpret_cast<word>(ref) | operand2 );
77     }
78 };
79 
80 using tricky_pointer = tricky_atomic_pointer<queuing_rw_mutex::scoped_lock>;
81 
82 #if defined(_MSC_VER) && !defined(__INTEL_COMPILER)
83     // Workaround for overzealous compiler warnings
84     #pragma warning (pop)
85 #endif
86 
87 //! Flag bits in a state_t that specify information about a locking request.
88 enum state_t_flags : unsigned char {
89     STATE_NONE                   = 0,
90     STATE_WRITER                 = 1<<0,
91     STATE_READER                 = 1<<1,
92     STATE_READER_UNBLOCKNEXT     = 1<<2,
93     STATE_ACTIVEREADER           = 1<<3,
94     STATE_UPGRADE_REQUESTED      = 1<<4,
95     STATE_UPGRADE_WAITING        = 1<<5,
96     STATE_UPGRADE_LOSER          = 1<<6,
97     STATE_COMBINED_WAITINGREADER = STATE_READER | STATE_READER_UNBLOCKNEXT,
98     STATE_COMBINED_READER        = STATE_COMBINED_WAITINGREADER | STATE_ACTIVEREADER,
99     STATE_COMBINED_UPGRADING     = STATE_UPGRADE_WAITING | STATE_UPGRADE_LOSER
100 };
101 
102 static const unsigned char RELEASED = 0;
103 static const unsigned char ACQUIRED = 1;
104 
105 struct queuing_rw_mutex_impl {
106     //! Try to acquire the internal lock
107     /** Returns true if lock was successfully acquired. */
try_acquire_internal_locktbb::detail::r1::queuing_rw_mutex_impl108     static bool try_acquire_internal_lock(d1::queuing_rw_mutex::scoped_lock& s)
109     {
110         auto expected = RELEASED;
111         return s.my_internal_lock.compare_exchange_strong(expected, ACQUIRED);
112     }
113 
114     //! Acquire the internal lock
acquire_internal_locktbb::detail::r1::queuing_rw_mutex_impl115     static void acquire_internal_lock(d1::queuing_rw_mutex::scoped_lock& s)
116     {
117         // Usually, we would use the test-test-and-set idiom here, with exponential backoff.
118         // But so far, experiments indicate there is no value in doing so here.
119         while( !try_acquire_internal_lock(s) ) {
120             machine_pause(1);
121         }
122     }
123 
124     //! Release the internal lock
release_internal_locktbb::detail::r1::queuing_rw_mutex_impl125     static void release_internal_lock(d1::queuing_rw_mutex::scoped_lock& s)
126     {
127         s.my_internal_lock.store(RELEASED, std::memory_order_release);
128     }
129 
130     //! Wait for internal lock to be released
wait_for_release_of_internal_locktbb::detail::r1::queuing_rw_mutex_impl131     static void wait_for_release_of_internal_lock(d1::queuing_rw_mutex::scoped_lock& s)
132     {
133         spin_wait_until_eq(s.my_internal_lock, RELEASED);
134     }
135 
136     //! A helper function
unblock_or_wait_on_internal_locktbb::detail::r1::queuing_rw_mutex_impl137     static void unblock_or_wait_on_internal_lock(d1::queuing_rw_mutex::scoped_lock& s, uintptr_t flag ) {
138         if( flag ) {
139             wait_for_release_of_internal_lock(s);
140         }
141         else {
142             release_internal_lock(s);
143         }
144     }
145 
146     //! Mask for low order bit of a pointer.
147     static const tricky_pointer::word FLAG = 0x1;
148 
get_flagtbb::detail::r1::queuing_rw_mutex_impl149     static uintptr_t get_flag( d1::queuing_rw_mutex::scoped_lock* ptr ) {
150         return reinterpret_cast<uintptr_t>(ptr) & FLAG;
151     }
152 
153     //------------------------------------------------------------------------
154     // Methods of queuing_rw_mutex::scoped_lock
155     //------------------------------------------------------------------------
156 
157     //! A method to acquire queuing_rw_mutex lock
acquiretbb::detail::r1::queuing_rw_mutex_impl158     static void acquire(d1::queuing_rw_mutex& m, d1::queuing_rw_mutex::scoped_lock& s, bool write)
159     {
160         __TBB_ASSERT( !s.my_mutex, "scoped_lock is already holding a mutex");
161 
162         // Must set all fields before the exchange, because once the
163         // exchange executes, *this becomes accessible to other threads.
164         s.my_mutex = &m;
165         s.my_prev.store(0U, std::memory_order_relaxed);
166         s.my_next.store(0U, std::memory_order_relaxed);
167         s.my_going.store(0U, std::memory_order_relaxed);
168         s.my_state.store(d1::queuing_rw_mutex::scoped_lock::state_t(write ? STATE_WRITER : STATE_READER), std::memory_order_relaxed);
169         s.my_internal_lock.store(RELEASED, std::memory_order_relaxed);
170 
171         queuing_rw_mutex::scoped_lock* predecessor = m.q_tail.exchange(&s, std::memory_order_release);
172 
173         if( write ) {       // Acquiring for write
174 
175             if( predecessor ) {
176                 ITT_NOTIFY(sync_prepare, s.my_mutex);
177                 predecessor = tricky_pointer(predecessor) & ~FLAG;
178                 __TBB_ASSERT( !predecessor->my_next, "the predecessor has another successor!");
179                 tricky_pointer::store(predecessor->my_next, &s, std::memory_order_release);
180                 spin_wait_until_eq(s.my_going, 1U);
181             }
182 
183         } else {            // Acquiring for read
184     #if __TBB_USE_ITT_NOTIFY
185             bool sync_prepare_done = false;
186     #endif
187             if( predecessor ) {
188                 unsigned char pred_state{};
189                 __TBB_ASSERT( !s.my_prev.load(std::memory_order_relaxed), "the predecessor is already set" );
190                 if( tricky_pointer(predecessor) & FLAG ) {
191                     /* this is only possible if predecessor is an upgrading reader and it signals us to wait */
192                     pred_state = STATE_UPGRADE_WAITING;
193                     predecessor = tricky_pointer(predecessor) & ~FLAG;
194                 } else {
195                     // Load predecessor->my_state now, because once predecessor->my_next becomes
196                     // non-NULL, we must assume that *predecessor might be destroyed.
197                     pred_state = STATE_READER;
198                     predecessor->my_state.compare_exchange_strong(pred_state, STATE_READER_UNBLOCKNEXT, std::memory_order_acq_rel);
199                 }
200                 tricky_pointer::store(s.my_prev, predecessor, std::memory_order_relaxed); // TODO: order?
201                 __TBB_ASSERT( !( tricky_pointer(predecessor) & FLAG ), "use of corrupted pointer!" );
202                 __TBB_ASSERT( !predecessor->my_next.load(std::memory_order_relaxed), "the predecessor has another successor!");
203                 tricky_pointer::store(predecessor->my_next, &s, std::memory_order_release);
204                 if( pred_state != STATE_ACTIVEREADER ) {
205     #if __TBB_USE_ITT_NOTIFY
206                     sync_prepare_done = true;
207                     ITT_NOTIFY(sync_prepare, s.my_mutex);
208     #endif
209                     spin_wait_until_eq(s.my_going, 1U, std::memory_order_acquire);
210                 }
211             }
212 
213             // The protected state must have been acquired here before it can be further released to any other reader(s):
214             unsigned char old_state = STATE_READER;
215             s.my_state.compare_exchange_strong(old_state, STATE_ACTIVEREADER, std::memory_order_acq_rel);
216             if( old_state!=STATE_READER ) {
217 #if __TBB_USE_ITT_NOTIFY
218                 if( !sync_prepare_done )
219                     ITT_NOTIFY(sync_prepare, s.my_mutex);
220 #endif
221                 // Failed to become active reader -> need to unblock the next waiting reader first
222                 __TBB_ASSERT( s.my_state.load(std::memory_order_relaxed)==STATE_READER_UNBLOCKNEXT, "unexpected state" );
223                 spin_wait_while_eq(s.my_next, 0U);
224                 /* my_state should be changed before unblocking the next otherwise it might finish
225                    and another thread can get our old state and left blocked */
226                 s.my_state.store(STATE_ACTIVEREADER, std::memory_order_relaxed);
227                 tricky_pointer::load(s.my_next, std::memory_order_relaxed)->my_going.store(1U, std::memory_order_release);
228             }
229             __TBB_ASSERT( s.my_state.load(std::memory_order_relaxed) ==STATE_ACTIVEREADER, "unlocked reader is active reader" );
230         }
231 
232         ITT_NOTIFY(sync_acquired, s.my_mutex);
233 
234         // Force acquire so that user's critical section receives correct values
235         // from processor that was previously in the user's critical section.
236         atomic_fence(std::memory_order_acquire); // TODO: consider exchange on line 171
237     }
238 
239     //! A method to acquire queuing_rw_mutex if it is free
try_acquiretbb::detail::r1::queuing_rw_mutex_impl240     static bool try_acquire(d1::queuing_rw_mutex& m, d1::queuing_rw_mutex::scoped_lock& s, bool write)
241     {
242         __TBB_ASSERT( !s.my_mutex, "scoped_lock is already holding a mutex");
243 
244         if( m.q_tail.load(std::memory_order_relaxed) )
245             return false; // Someone already took the lock
246 
247         // Must set all fields before the exchange, because once the
248         // exchange executes, *this becomes accessible to other threads.
249         s.my_prev.store(0U, std::memory_order_relaxed);
250         s.my_next.store(0U, std::memory_order_relaxed);
251         s.my_going.store(0U, std::memory_order_relaxed); // TODO: remove dead assignment?
252         s.my_state.store(d1::queuing_rw_mutex::scoped_lock::state_t(write ? STATE_WRITER : STATE_ACTIVEREADER), std::memory_order_relaxed);
253         s.my_internal_lock.store(RELEASED, std::memory_order_relaxed);
254 
255         // The CAS must have release semantics, because we are
256         // "sending" the fields initialized above to other processors.
257         d1::queuing_rw_mutex::scoped_lock* expected = nullptr;
258         if (!m.q_tail.compare_exchange_strong(expected, &s, std::memory_order_release))
259             return false; // Someone already took the lock
260         // Force acquire so that user's critical section receives correct values
261         // from processor that was previously in the user's critical section.
262         atomic_fence(std::memory_order_acquire);
263         s.my_mutex = &m;
264         ITT_NOTIFY(sync_acquired, s.my_mutex);
265         return true;
266     }
267 
268     //! A method to release queuing_rw_mutex lock
releasetbb::detail::r1::queuing_rw_mutex_impl269     static void release(d1::queuing_rw_mutex::scoped_lock& s) {
270         __TBB_ASSERT(s.my_mutex!=nullptr, "no lock acquired");
271 
272         ITT_NOTIFY(sync_releasing, s.my_mutex);
273 
274         if( s.my_state.load(std::memory_order_relaxed) == STATE_WRITER ) { // Acquired for write
275 
276             // The logic below is the same as "writerUnlock", but elides
277             // "return" from the middle of the routine.
278             // In the statement below, acquire semantics of reading my_next is required
279             // so that following operations with fields of my_next are safe.
280             d1::queuing_rw_mutex::scoped_lock* next = tricky_pointer::load(s.my_next, std::memory_order_acquire);
281             if( !next ) {
282                 d1::queuing_rw_mutex::scoped_lock* expected = &s;
283                 if( s.my_mutex->q_tail.compare_exchange_strong(expected, nullptr, std::memory_order_release) ) {
284                     // this was the only item in the queue, and the queue is now empty.
285                     goto done;
286                 }
287                 spin_wait_while_eq(s.my_next, 0U);
288                 next = tricky_pointer::load(s.my_next, std::memory_order_acquire);
289             }
290             next->my_going.store(2U, std::memory_order_relaxed); // protect next queue node from being destroyed too early
291             if( next->my_state.load()==STATE_UPGRADE_WAITING ) {
292                 // the next waiting for upgrade means this writer was upgraded before.
293                 acquire_internal_lock(s);
294                 // Responsibility transition, the one who reads uncorrupted my_prev will do release.
295                 d1::queuing_rw_mutex::scoped_lock* tmp = tricky_pointer::exchange(next->my_prev, nullptr, std::memory_order_release);
296                 next->my_state.store(STATE_UPGRADE_LOSER, std::memory_order_relaxed);
297                 next->my_going.store(1U, std::memory_order_release);
298                 unblock_or_wait_on_internal_lock(s, get_flag(tmp));
299             } else {
300                 // next->state cannot be STATE_UPGRADE_REQUESTED
301                 __TBB_ASSERT( next->my_state.load(std::memory_order_relaxed) & (STATE_COMBINED_WAITINGREADER | STATE_WRITER), "unexpected state" );
302                 __TBB_ASSERT( !( next->my_prev.load(std::memory_order_relaxed) & FLAG ), "use of corrupted pointer!" );
303                 tricky_pointer::store(next->my_prev, nullptr, std::memory_order_relaxed);
304                 next->my_going.store(1U, std::memory_order_release);
305             }
306 
307         } else { // Acquired for read
308 
309             queuing_rw_mutex::scoped_lock *tmp = nullptr;
310     retry:
311             // Addition to the original paper: Mark my_prev as in use
312             queuing_rw_mutex::scoped_lock *predecessor = tricky_pointer::fetch_add(s.my_prev, FLAG, std::memory_order_acquire);
313 
314             if( predecessor ) {
315                 if( !(try_acquire_internal_lock(*predecessor)) )
316                 {
317                     // Failed to acquire the lock on predecessor. The predecessor either unlinks or upgrades.
318                     // In the second case, it could or could not know my "in use" flag - need to check
319                     // Responsibility transition, the one who reads uncorrupted my_prev will do release.
320                     tmp = tricky_pointer::compare_exchange_strong(s.my_prev, tricky_pointer(predecessor) | FLAG, predecessor, std::memory_order_release);
321                     if( !(tricky_pointer(tmp) & FLAG) ) {
322                         // Wait for the predecessor to change my_prev (e.g. during unlink)
323                         // TODO: spin_wait condition seems never reachable
324                         __TBB_ASSERT(tricky_pointer::load(s.my_prev, std::memory_order_relaxed) != (tricky_pointer(predecessor) | FLAG), nullptr);
325                         tricky_pointer::spin_wait_while_eq( s.my_prev, tricky_pointer(predecessor)|FLAG );
326                         // Now owner of predecessor is waiting for _us_ to release its lock
327                         release_internal_lock(*predecessor);
328                     }
329                     // else the "in use" flag is back -> the predecessor didn't get it and will release itself; nothing to do
330 
331                     tmp = nullptr;
332                     goto retry;
333                 }
334                 __TBB_ASSERT(predecessor && predecessor->my_internal_lock.load(std::memory_order_relaxed)==ACQUIRED, "predecessor's lock is not acquired");
335                 tricky_pointer::store(s.my_prev, predecessor, std::memory_order_relaxed);
336                 acquire_internal_lock(s);
337 
338                 tricky_pointer::store(predecessor->my_next, nullptr, std::memory_order_release);
339 
340                 d1::queuing_rw_mutex::scoped_lock* expected = &s;
341                 if( !tricky_pointer::load(s.my_next, std::memory_order_relaxed) && !s.my_mutex->q_tail.compare_exchange_strong(expected, predecessor, std::memory_order_release) ) {
342                     spin_wait_while_eq( s.my_next, 0U, std::memory_order_acquire );
343                 }
344                 __TBB_ASSERT( !(s.my_next.load(std::memory_order_relaxed) & FLAG), "use of corrupted pointer" );
345 
346                 // ensure acquire semantics of reading 'my_next'
347                 if(d1::queuing_rw_mutex::scoped_lock *const l_next = tricky_pointer::load(s.my_next, std::memory_order_acquire) ) { // I->next != nil, TODO: rename to next after clearing up and adapting the n in the comment two lines below
348                     // Equivalent to I->next->prev = I->prev but protected against (prev[n]&FLAG)!=0
349                     tmp = tricky_pointer::exchange(l_next->my_prev, predecessor, std::memory_order_release);
350                     // I->prev->next = I->next;
351                     __TBB_ASSERT(tricky_pointer::load(s.my_prev, std::memory_order_relaxed)==predecessor, nullptr);
352                     predecessor->my_next.store(s.my_next.load(std::memory_order_relaxed), std::memory_order_release);
353                 }
354                 // Safe to release in the order opposite to acquiring which makes the code simpler
355                 release_internal_lock(*predecessor);
356 
357             } else { // No predecessor when we looked
358                 acquire_internal_lock(s);  // "exclusiveLock(&I->EL)"
359                 d1::queuing_rw_mutex::scoped_lock* next = tricky_pointer::load(s.my_next, std::memory_order_acquire);
360                 if( !next ) {
361                     d1::queuing_rw_mutex::scoped_lock* expected = &s;
362                     if( !s.my_mutex->q_tail.compare_exchange_strong(expected, nullptr, std::memory_order_release) ) {
363                         spin_wait_while_eq( s.my_next, 0U );
364                         next = tricky_pointer::load(s.my_next, std::memory_order_relaxed);
365                     } else {
366                         goto unlock_self;
367                     }
368                 }
369                 next->my_going.store(2U, std::memory_order_relaxed);
370                 // Responsibility transition, the one who reads uncorrupted my_prev will do release.
371                 tmp = tricky_pointer::exchange(next->my_prev, nullptr, std::memory_order_release);
372                 next->my_going.store(1U, std::memory_order_release);
373             }
374     unlock_self:
375             unblock_or_wait_on_internal_lock(s, get_flag(tmp));
376         }
377     done:
378         spin_wait_while_eq( s.my_going, 2U );
379 
380         s.initialize();
381     }
382 
downgrade_to_readertbb::detail::r1::queuing_rw_mutex_impl383     static bool downgrade_to_reader(d1::queuing_rw_mutex::scoped_lock& s) {
384         if ( s.my_state.load(std::memory_order_relaxed) == STATE_ACTIVEREADER ) return true; // Already a reader
385 
386         ITT_NOTIFY(sync_releasing, s.my_mutex);
387         s.my_state.store(STATE_READER, std::memory_order_relaxed);
388         if( ! tricky_pointer::load(s.my_next, std::memory_order_relaxed)) {
389             // the following load of q_tail must not be reordered with setting STATE_READER above
390             if( &s==s.my_mutex->q_tail.load() ) {
391                 unsigned char old_state = STATE_READER;
392                 s.my_state.compare_exchange_strong(old_state, STATE_ACTIVEREADER, std::memory_order_release);
393                 if( old_state==STATE_READER )
394                     return true; // Downgrade completed
395             }
396             /* wait for the next to register */
397             spin_wait_while_eq( s.my_next, 0U );
398         }
399         d1::queuing_rw_mutex::scoped_lock *const next = tricky_pointer::load(s.my_next, std::memory_order_acquire);
400         __TBB_ASSERT( next, "still no successor at this point!" );
401         if( next->my_state & STATE_COMBINED_WAITINGREADER )
402             next->my_going.store(1U, std::memory_order_release);
403         else if( next->my_state==STATE_UPGRADE_WAITING )
404             // the next waiting for upgrade means this writer was upgraded before.
405             next->my_state.store(STATE_UPGRADE_LOSER, std::memory_order_relaxed);
406         s.my_state.store(STATE_ACTIVEREADER, std::memory_order_relaxed);
407         return true;
408     }
409 
upgrade_to_writertbb::detail::r1::queuing_rw_mutex_impl410     static bool upgrade_to_writer(d1::queuing_rw_mutex::scoped_lock& s) {
411         if (s.my_state.load(std::memory_order_relaxed) == STATE_WRITER) {
412             // Already a writer
413             return true;
414         }
415 
416         __TBB_ASSERT(s.my_state.load(std::memory_order_relaxed) == STATE_ACTIVEREADER, "only active reader can be updated");
417 
418         queuing_rw_mutex::scoped_lock* tmp{};
419         queuing_rw_mutex::scoped_lock* me = &s;
420 
421         ITT_NOTIFY(sync_releasing, s.my_mutex);
422         s.my_state.store(STATE_UPGRADE_REQUESTED, std::memory_order_relaxed);
423     requested:
424         __TBB_ASSERT( !(s.my_next.load(std::memory_order_relaxed) & FLAG), "use of corrupted pointer!" );
425         acquire_internal_lock(s);
426         d1::queuing_rw_mutex::scoped_lock* expected = &s;
427         if( !s.my_mutex->q_tail.compare_exchange_strong(expected, tricky_pointer(me)|FLAG, std::memory_order_release) ) {
428             spin_wait_while_eq( s.my_next, 0U );
429             queuing_rw_mutex::scoped_lock * next;
430             next = tricky_pointer::fetch_add(s.my_next, FLAG, std::memory_order_acquire);
431             unsigned short n_state = next->my_state;
432             /* the next reader can be blocked by our state. the best thing to do is to unblock it */
433             if( n_state & STATE_COMBINED_WAITINGREADER )
434                 next->my_going.store(1U, std::memory_order_release);
435             // Responsibility transition, the one who reads uncorrupted my_prev will do release.
436             tmp = tricky_pointer::exchange(next->my_prev, &s, std::memory_order_release);
437             unblock_or_wait_on_internal_lock(s, get_flag(tmp));
438             if( n_state & (STATE_COMBINED_READER | STATE_UPGRADE_REQUESTED) ) {
439                 // save next|FLAG for simplicity of following comparisons
440                 tmp = tricky_pointer(next)|FLAG;
441                 for( atomic_backoff b; tricky_pointer::load(s.my_next, std::memory_order_relaxed)==tmp; b.pause() ) {
442                     if( s.my_state & STATE_COMBINED_UPGRADING ) {
443                         if( tricky_pointer::load(s.my_next, std::memory_order_acquire)==tmp )
444                             tricky_pointer::store(s.my_next, next, std::memory_order_relaxed);
445                         goto waiting;
446                     }
447                 }
448                 __TBB_ASSERT(tricky_pointer::load(s.my_next, std::memory_order_relaxed) != (tricky_pointer(next)|FLAG), nullptr);
449                 goto requested;
450             } else {
451                 __TBB_ASSERT( n_state & (STATE_WRITER | STATE_UPGRADE_WAITING), "unexpected state");
452                 __TBB_ASSERT( (tricky_pointer(next)|FLAG) == tricky_pointer::load(s.my_next, std::memory_order_relaxed), nullptr);
453                 tricky_pointer::store(s.my_next, next, std::memory_order_relaxed);
454             }
455         } else {
456             /* We are in the tail; whoever comes next is blocked by q_tail&FLAG */
457             release_internal_lock(s);
458         } // if( this != my_mutex->q_tail... )
459         {
460             unsigned char old_state = STATE_UPGRADE_REQUESTED;
461             s.my_state.compare_exchange_strong(old_state, STATE_UPGRADE_WAITING, std::memory_order_acquire);
462         }
463     waiting:
464         __TBB_ASSERT( !( s.my_next.load(std::memory_order_relaxed) & FLAG ), "use of corrupted pointer!" );
465         __TBB_ASSERT( s.my_state & STATE_COMBINED_UPGRADING, "wrong state at upgrade waiting_retry" );
466         __TBB_ASSERT( me==&s, nullptr );
467         ITT_NOTIFY(sync_prepare, s.my_mutex);
468         /* if no one was blocked by the "corrupted" q_tail, turn it back */
469         expected = tricky_pointer(me)|FLAG;
470         s.my_mutex->q_tail.compare_exchange_strong(expected, &s, std::memory_order_release);
471         queuing_rw_mutex::scoped_lock * predecessor;
472         // Mark my_prev as 'in use' to prevent predecessor from releasing
473         predecessor = tricky_pointer::fetch_add(s.my_prev, FLAG, std::memory_order_acquire);
474         if( predecessor ) {
475             bool success = try_acquire_internal_lock(*predecessor);
476             {
477                 // While the predecessor pointer (my_prev) is in use (FLAG is set), we can safely update the node`s state.
478                 // Corrupted pointer transitions responsibility to release the predecessor`s node on us.
479                 unsigned char old_state = STATE_UPGRADE_REQUESTED;
480                 predecessor->my_state.compare_exchange_strong(old_state, STATE_UPGRADE_WAITING, std::memory_order_release);
481             }
482             if( !success ) {
483                 // Responsibility transition, the one who reads uncorrupted my_prev will do release.
484                 tmp = tricky_pointer::compare_exchange_strong(s.my_prev, tricky_pointer(predecessor)|FLAG, predecessor, std::memory_order_release);
485                 if( tricky_pointer(tmp) & FLAG ) {
486                     tricky_pointer::spin_wait_while_eq(s.my_prev, predecessor);
487                     predecessor = tricky_pointer::load(s.my_prev, std::memory_order_relaxed);
488                 } else {
489                     // TODO: spin_wait condition seems never reachable
490                     tricky_pointer::spin_wait_while_eq(s.my_prev, tricky_pointer(predecessor)|FLAG);
491                     release_internal_lock(*predecessor);
492                 }
493             } else {
494                 tricky_pointer::store(s.my_prev, predecessor, std::memory_order_relaxed);
495                 release_internal_lock(*predecessor);
496                 tricky_pointer::spin_wait_while_eq(s.my_prev, predecessor);
497                 predecessor = tricky_pointer::load(s.my_prev, std::memory_order_relaxed);
498             }
499             if( predecessor )
500                 goto waiting;
501         } else {
502             tricky_pointer::store(s.my_prev, nullptr, std::memory_order_relaxed);
503         }
504         __TBB_ASSERT( !predecessor && !s.my_prev, nullptr );
505 
506         // additional lifetime issue prevention checks
507         // wait for the successor to finish working with my fields
508         wait_for_release_of_internal_lock(s);
509         // now wait for the predecessor to finish working with my fields
510         spin_wait_while_eq( s.my_going, 2U );
511 
512         // Acquire critical section indirectly from previous owner or directly from predecessor (TODO: not clear).
513         atomic_fence(std::memory_order_acquire); // on either "my_mutex->q_tail" or "my_going" (TODO: not clear)
514 
515         bool result = ( s.my_state != STATE_UPGRADE_LOSER );
516         s.my_state.store(STATE_WRITER, std::memory_order_relaxed);
517         s.my_going.store(1U, std::memory_order_relaxed);
518 
519         ITT_NOTIFY(sync_acquired, s.my_mutex);
520         return result;
521     }
522 
is_writertbb::detail::r1::queuing_rw_mutex_impl523     static bool is_writer(const d1::queuing_rw_mutex::scoped_lock& m) {
524         return m.my_state.load(std::memory_order_relaxed) == STATE_WRITER;
525     }
526 
constructtbb::detail::r1::queuing_rw_mutex_impl527     static void construct(d1::queuing_rw_mutex& m) {
528         suppress_unused_warning(m);
529         ITT_SYNC_CREATE(&m, _T("tbb::queuing_rw_mutex"), _T(""));
530     }
531 };
532 
acquire(d1::queuing_rw_mutex & m,d1::queuing_rw_mutex::scoped_lock & s,bool write)533 void __TBB_EXPORTED_FUNC acquire(d1::queuing_rw_mutex& m, d1::queuing_rw_mutex::scoped_lock& s, bool write) {
534     queuing_rw_mutex_impl::acquire(m, s, write);
535 }
536 
try_acquire(d1::queuing_rw_mutex & m,d1::queuing_rw_mutex::scoped_lock & s,bool write)537 bool __TBB_EXPORTED_FUNC try_acquire(d1::queuing_rw_mutex& m, d1::queuing_rw_mutex::scoped_lock& s, bool write) {
538     return queuing_rw_mutex_impl::try_acquire(m, s, write);
539 }
540 
release(d1::queuing_rw_mutex::scoped_lock & s)541 void __TBB_EXPORTED_FUNC release(d1::queuing_rw_mutex::scoped_lock& s) {
542     queuing_rw_mutex_impl::release(s);
543 }
544 
upgrade_to_writer(d1::queuing_rw_mutex::scoped_lock & s)545 bool __TBB_EXPORTED_FUNC upgrade_to_writer(d1::queuing_rw_mutex::scoped_lock& s) {
546     return queuing_rw_mutex_impl::upgrade_to_writer(s);
547 }
548 
is_writer(const d1::queuing_rw_mutex::scoped_lock & s)549 bool __TBB_EXPORTED_FUNC is_writer(const d1::queuing_rw_mutex::scoped_lock& s) {
550     return queuing_rw_mutex_impl::is_writer(s);
551 }
552 
downgrade_to_reader(d1::queuing_rw_mutex::scoped_lock & s)553 bool __TBB_EXPORTED_FUNC downgrade_to_reader(d1::queuing_rw_mutex::scoped_lock& s) {
554     return queuing_rw_mutex_impl::downgrade_to_reader(s);
555 }
556 
construct(d1::queuing_rw_mutex & m)557 void __TBB_EXPORTED_FUNC construct(d1::queuing_rw_mutex& m) {
558     queuing_rw_mutex_impl::construct(m);
559 }
560 
561 } // namespace r1
562 } // namespace detail
563 } // namespace tbb
564