xref: /oneTBB/src/tbb/rtm_rw_mutex.cpp (revision c21e688a)
151c0b2f7Stbbdev /*
2*c21e688aSSergey Zheltov     Copyright (c) 2005-2022 Intel Corporation
351c0b2f7Stbbdev 
451c0b2f7Stbbdev     Licensed under the Apache License, Version 2.0 (the "License");
551c0b2f7Stbbdev     you may not use this file except in compliance with the License.
651c0b2f7Stbbdev     You may obtain a copy of the License at
751c0b2f7Stbbdev 
851c0b2f7Stbbdev         http://www.apache.org/licenses/LICENSE-2.0
951c0b2f7Stbbdev 
1051c0b2f7Stbbdev     Unless required by applicable law or agreed to in writing, software
1151c0b2f7Stbbdev     distributed under the License is distributed on an "AS IS" BASIS,
1251c0b2f7Stbbdev     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1351c0b2f7Stbbdev     See the License for the specific language governing permissions and
1451c0b2f7Stbbdev     limitations under the License.
1551c0b2f7Stbbdev */
1651c0b2f7Stbbdev 
1749e08aacStbbdev #include "oneapi/tbb/detail/_assert.h"
1849e08aacStbbdev #include "oneapi/tbb/detail/_rtm_rw_mutex.h"
1951c0b2f7Stbbdev #include "itt_notify.h"
2051c0b2f7Stbbdev #include "governor.h"
2151c0b2f7Stbbdev #include "misc.h"
2251c0b2f7Stbbdev 
2351c0b2f7Stbbdev #include <atomic>
2451c0b2f7Stbbdev 
2551c0b2f7Stbbdev namespace tbb {
2651c0b2f7Stbbdev namespace detail {
2751c0b2f7Stbbdev namespace r1 {
2851c0b2f7Stbbdev 
2951c0b2f7Stbbdev struct rtm_rw_mutex_impl {
3051c0b2f7Stbbdev     // maximum number of times to retry
3151c0b2f7Stbbdev     // TODO: experiment on retry values.
3251c0b2f7Stbbdev     static constexpr int retry_threshold_read = 10;
3351c0b2f7Stbbdev     static constexpr int retry_threshold_write = 10;
34478de5b1Stbbdev     using transaction_result_type = decltype(begin_transaction());
3551c0b2f7Stbbdev 
3651c0b2f7Stbbdev     //! Release speculative mutex
releasetbb::detail::r1::rtm_rw_mutex_impl3751c0b2f7Stbbdev     static void release(d1::rtm_rw_mutex::scoped_lock& s) {
3851c0b2f7Stbbdev         switch(s.m_transaction_state) {
3951c0b2f7Stbbdev         case d1::rtm_rw_mutex::rtm_type::rtm_transacting_writer:
4051c0b2f7Stbbdev         case d1::rtm_rw_mutex::rtm_type::rtm_transacting_reader:
4151c0b2f7Stbbdev             __TBB_ASSERT(is_in_transaction(), "m_transaction_state && not speculating");
4251c0b2f7Stbbdev             end_transaction();
4351c0b2f7Stbbdev             s.m_mutex = nullptr;
4451c0b2f7Stbbdev             break;
4551c0b2f7Stbbdev         case d1::rtm_rw_mutex::rtm_type::rtm_real_reader:
4651c0b2f7Stbbdev             __TBB_ASSERT(!s.m_mutex->write_flag.load(std::memory_order_relaxed), "write_flag set but read lock acquired");
4751c0b2f7Stbbdev             s.m_mutex->unlock_shared();
4851c0b2f7Stbbdev             s.m_mutex = nullptr;
4951c0b2f7Stbbdev             break;
5051c0b2f7Stbbdev         case d1::rtm_rw_mutex::rtm_type::rtm_real_writer:
5151c0b2f7Stbbdev             __TBB_ASSERT(s.m_mutex->write_flag.load(std::memory_order_relaxed), "write_flag unset but write lock acquired");
5251c0b2f7Stbbdev             s.m_mutex->write_flag.store(false, std::memory_order_relaxed);
5351c0b2f7Stbbdev             s.m_mutex->unlock();
5451c0b2f7Stbbdev             s.m_mutex = nullptr;
5551c0b2f7Stbbdev             break;
5651c0b2f7Stbbdev         case d1::rtm_rw_mutex::rtm_type::rtm_not_in_mutex:
5751c0b2f7Stbbdev             __TBB_ASSERT(false, "rtm_not_in_mutex, but in release");
5851c0b2f7Stbbdev             break;
5951c0b2f7Stbbdev         default:
6051c0b2f7Stbbdev             __TBB_ASSERT(false, "invalid m_transaction_state");
6151c0b2f7Stbbdev         }
6251c0b2f7Stbbdev         s.m_transaction_state = d1::rtm_rw_mutex::rtm_type::rtm_not_in_mutex;
6351c0b2f7Stbbdev     }
6451c0b2f7Stbbdev 
6551c0b2f7Stbbdev     //! Acquire write lock on the given mutex.
acquire_writertbb::detail::r1::rtm_rw_mutex_impl6651c0b2f7Stbbdev     static void acquire_writer(d1::rtm_rw_mutex& m, d1::rtm_rw_mutex::scoped_lock& s, bool only_speculate) {
6751c0b2f7Stbbdev         __TBB_ASSERT(s.m_transaction_state == d1::rtm_rw_mutex::rtm_type::rtm_not_in_mutex, "scoped_lock already in transaction");
6851c0b2f7Stbbdev         if(governor::speculation_enabled()) {
6951c0b2f7Stbbdev             int num_retries = 0;
70478de5b1Stbbdev             transaction_result_type abort_code = 0;
7151c0b2f7Stbbdev             do {
7251c0b2f7Stbbdev                 if(m.m_state.load(std::memory_order_acquire)) {
7351c0b2f7Stbbdev                     if(only_speculate) return;
7451c0b2f7Stbbdev                     spin_wait_until_eq(m.m_state, d1::rtm_rw_mutex::state_type(0));
7551c0b2f7Stbbdev                 }
7651c0b2f7Stbbdev                 // _xbegin returns -1 on success or the abort code, so capture it
77478de5b1Stbbdev                 if((abort_code = begin_transaction()) == transaction_result_type(speculation_successful_begin))
7851c0b2f7Stbbdev                 {
7951c0b2f7Stbbdev                     // started speculation
8051c0b2f7Stbbdev                     if(m.m_state.load(std::memory_order_relaxed)) {  // add spin_rw_mutex to read-set.
8151c0b2f7Stbbdev                         // reader or writer grabbed the lock, so abort.
8251c0b2f7Stbbdev                         abort_transaction();
8351c0b2f7Stbbdev                     }
8451c0b2f7Stbbdev                     s.m_transaction_state = d1::rtm_rw_mutex::rtm_type::rtm_transacting_writer;
8551c0b2f7Stbbdev                     // Don not wrap the following assignment to a function,
8651c0b2f7Stbbdev                     // because it can abort the transaction in debug. Need mutex for release().
8751c0b2f7Stbbdev                     s.m_mutex = &m;
8851c0b2f7Stbbdev                     return;  // successfully started speculation
8951c0b2f7Stbbdev                 }
9051c0b2f7Stbbdev                 ++num_retries;
9151c0b2f7Stbbdev             } while((abort_code & speculation_retry) != 0 && (num_retries < retry_threshold_write));
9251c0b2f7Stbbdev         }
9351c0b2f7Stbbdev 
9451c0b2f7Stbbdev         if(only_speculate) return;
9551c0b2f7Stbbdev         s.m_mutex = &m;                                                          // should apply a real try_lock...
9651c0b2f7Stbbdev         s.m_mutex->lock();                                                       // kill transactional writers
9751c0b2f7Stbbdev         __TBB_ASSERT(!m.write_flag.load(std::memory_order_relaxed), "After acquire for write, write_flag already true");
9851c0b2f7Stbbdev         m.write_flag.store(true, std::memory_order_relaxed);                       // kill transactional readers
9951c0b2f7Stbbdev         s.m_transaction_state = d1::rtm_rw_mutex::rtm_type::rtm_real_writer;
10051c0b2f7Stbbdev     }
10151c0b2f7Stbbdev 
10251c0b2f7Stbbdev     //! Acquire read lock on given mutex.
10351c0b2f7Stbbdev     //  only_speculate : true if we are doing a try_acquire.  If true and we fail to speculate, don't
10451c0b2f7Stbbdev     //     really acquire the lock, return and do a try_acquire on the contained spin_rw_mutex.  If
10551c0b2f7Stbbdev     //     the lock is already held by a writer, just return.
acquire_readertbb::detail::r1::rtm_rw_mutex_impl10651c0b2f7Stbbdev     static void acquire_reader(d1::rtm_rw_mutex& m, d1::rtm_rw_mutex::scoped_lock& s, bool only_speculate) {
10751c0b2f7Stbbdev         __TBB_ASSERT(s.m_transaction_state == d1::rtm_rw_mutex::rtm_type::rtm_not_in_mutex, "scoped_lock already in transaction");
10851c0b2f7Stbbdev         if(governor::speculation_enabled()) {
10951c0b2f7Stbbdev             int num_retries = 0;
110478de5b1Stbbdev             transaction_result_type abort_code = 0;
11151c0b2f7Stbbdev             do {
11251c0b2f7Stbbdev                 // if in try_acquire, and lock is held as writer, don't attempt to speculate.
11351c0b2f7Stbbdev                 if(m.write_flag.load(std::memory_order_acquire)) {
11451c0b2f7Stbbdev                     if(only_speculate) return;
11551c0b2f7Stbbdev                     spin_wait_while_eq(m.write_flag, true);
11651c0b2f7Stbbdev                 }
11751c0b2f7Stbbdev                 // _xbegin returns -1 on success or the abort code, so capture it
118478de5b1Stbbdev                 if((abort_code = begin_transaction()) == transaction_result_type(speculation_successful_begin))
11951c0b2f7Stbbdev                 {
12051c0b2f7Stbbdev                     // started speculation
12151c0b2f7Stbbdev                     if(m.write_flag.load(std::memory_order_relaxed)) {  // add write_flag to read-set.
12251c0b2f7Stbbdev                         abort_transaction();  // writer grabbed the lock, so abort.
12351c0b2f7Stbbdev                     }
12451c0b2f7Stbbdev                     s.m_transaction_state = d1::rtm_rw_mutex::rtm_type::rtm_transacting_reader;
12551c0b2f7Stbbdev                     // Don not wrap the following assignment to a function,
12651c0b2f7Stbbdev                     // because it can abort the transaction in debug. Need mutex for release().
12751c0b2f7Stbbdev                     s.m_mutex = &m;
12851c0b2f7Stbbdev                     return;  // successfully started speculation
12951c0b2f7Stbbdev                 }
13051c0b2f7Stbbdev                 // fallback path
13151c0b2f7Stbbdev                 // retry only if there is any hope of getting into a transaction soon
13251c0b2f7Stbbdev                 // Retry in the following cases (from Section 8.3.5 of
13351c0b2f7Stbbdev                 // Intel(R) Architecture Instruction Set Extensions Programming Reference):
13451c0b2f7Stbbdev                 // 1. abort caused by XABORT instruction (bit 0 of EAX register is set)
13551c0b2f7Stbbdev                 // 2. the transaction may succeed on a retry (bit 1 of EAX register is set)
13651c0b2f7Stbbdev                 // 3. if another logical processor conflicted with a memory address
13751c0b2f7Stbbdev                 //    that was part of the transaction that aborted (bit 2 of EAX register is set)
13851c0b2f7Stbbdev                 // That is, retry if (abort_code & 0x7) is non-zero
13951c0b2f7Stbbdev                 ++num_retries;
14051c0b2f7Stbbdev             } while((abort_code & speculation_retry) != 0 && (num_retries < retry_threshold_read));
14151c0b2f7Stbbdev         }
14251c0b2f7Stbbdev 
14351c0b2f7Stbbdev         if(only_speculate) return;
14451c0b2f7Stbbdev         s.m_mutex = &m;
14551c0b2f7Stbbdev         s.m_mutex->lock_shared();
14651c0b2f7Stbbdev         s.m_transaction_state = d1::rtm_rw_mutex::rtm_type::rtm_real_reader;
14751c0b2f7Stbbdev     }
14851c0b2f7Stbbdev 
14951c0b2f7Stbbdev     //! Upgrade reader to become a writer.
15051c0b2f7Stbbdev     /** Returns whether the upgrade happened without releasing and re-acquiring the lock */
upgradetbb::detail::r1::rtm_rw_mutex_impl15151c0b2f7Stbbdev     static bool upgrade(d1::rtm_rw_mutex::scoped_lock& s) {
15251c0b2f7Stbbdev         switch(s.m_transaction_state) {
15351c0b2f7Stbbdev         case d1::rtm_rw_mutex::rtm_type::rtm_real_reader: {
15451c0b2f7Stbbdev             s.m_transaction_state = d1::rtm_rw_mutex::rtm_type::rtm_real_writer;
15551c0b2f7Stbbdev             bool no_release = s.m_mutex->upgrade();
15651c0b2f7Stbbdev             __TBB_ASSERT(!s.m_mutex->write_flag.load(std::memory_order_relaxed), "After upgrade, write_flag already true");
15751c0b2f7Stbbdev             s.m_mutex->write_flag.store(true, std::memory_order_relaxed);
15851c0b2f7Stbbdev             return no_release;
15951c0b2f7Stbbdev         }
16051c0b2f7Stbbdev         case d1::rtm_rw_mutex::rtm_type::rtm_transacting_reader: {
16151c0b2f7Stbbdev             d1::rtm_rw_mutex& m = *s.m_mutex;
16251c0b2f7Stbbdev             if(m.m_state.load(std::memory_order_acquire)) {  // add spin_rw_mutex to read-set.
16351c0b2f7Stbbdev                 // Real reader or writer holds the lock; so commit the read and re-acquire for write.
16451c0b2f7Stbbdev                 release(s);
16551c0b2f7Stbbdev                 acquire_writer(m, s, false);
16651c0b2f7Stbbdev                 return false;
16751c0b2f7Stbbdev             } else
16851c0b2f7Stbbdev             {
16951c0b2f7Stbbdev                 s.m_transaction_state = d1::rtm_rw_mutex::rtm_type::rtm_transacting_writer;
17051c0b2f7Stbbdev                 return true;
17151c0b2f7Stbbdev             }
17251c0b2f7Stbbdev         }
17351c0b2f7Stbbdev         default:
17451c0b2f7Stbbdev             __TBB_ASSERT(false, "Invalid state for upgrade");
17551c0b2f7Stbbdev             return false;
17651c0b2f7Stbbdev         }
17751c0b2f7Stbbdev     }
17851c0b2f7Stbbdev 
17951c0b2f7Stbbdev     //! Downgrade writer to a reader.
downgradetbb::detail::r1::rtm_rw_mutex_impl18051c0b2f7Stbbdev     static bool downgrade(d1::rtm_rw_mutex::scoped_lock& s) {
18151c0b2f7Stbbdev         switch (s.m_transaction_state) {
18251c0b2f7Stbbdev         case d1::rtm_rw_mutex::rtm_type::rtm_real_writer:
18351c0b2f7Stbbdev             s.m_transaction_state = d1::rtm_rw_mutex::rtm_type::rtm_real_reader;
18451c0b2f7Stbbdev             __TBB_ASSERT(s.m_mutex->write_flag.load(std::memory_order_relaxed), "Before downgrade write_flag not true");
18551c0b2f7Stbbdev             s.m_mutex->write_flag.store(false, std::memory_order_relaxed);
18651c0b2f7Stbbdev             s.m_mutex->downgrade();
18751c0b2f7Stbbdev             return true;
18851c0b2f7Stbbdev         case d1::rtm_rw_mutex::rtm_type::rtm_transacting_writer:
18951c0b2f7Stbbdev             s.m_transaction_state = d1::rtm_rw_mutex::rtm_type::rtm_transacting_reader;
19051c0b2f7Stbbdev             return true;
19151c0b2f7Stbbdev         default:
19251c0b2f7Stbbdev             __TBB_ASSERT(false, "Invalid state for downgrade");
19351c0b2f7Stbbdev             return false;
19451c0b2f7Stbbdev         }
19551c0b2f7Stbbdev     }
19651c0b2f7Stbbdev 
19751c0b2f7Stbbdev     //! Try to acquire write lock on the given mutex.
19851c0b2f7Stbbdev     //  There may be reader(s) which acquired the spin_rw_mutex, as well as possibly
19951c0b2f7Stbbdev     //  transactional reader(s).  If this is the case, the acquire will fail, and assigning
20051c0b2f7Stbbdev     //  write_flag will kill the transactors.  So we only assign write_flag if we have successfully
20151c0b2f7Stbbdev     //  acquired the lock.
try_acquire_writertbb::detail::r1::rtm_rw_mutex_impl20251c0b2f7Stbbdev     static bool try_acquire_writer(d1::rtm_rw_mutex& m, d1::rtm_rw_mutex::scoped_lock& s) {
20351c0b2f7Stbbdev         acquire_writer(m, s, /*only_speculate=*/true);
20451c0b2f7Stbbdev         if (s.m_transaction_state == d1::rtm_rw_mutex::rtm_type::rtm_transacting_writer) {
20551c0b2f7Stbbdev             return true;
20651c0b2f7Stbbdev         }
20757f524caSIlya Isaev         __TBB_ASSERT(s.m_transaction_state == d1::rtm_rw_mutex::rtm_type::rtm_not_in_mutex, nullptr);
20851c0b2f7Stbbdev         // transacting write acquire failed. try_lock the real mutex
20951c0b2f7Stbbdev         if (m.try_lock()) {
21051c0b2f7Stbbdev             s.m_mutex = &m;
21151c0b2f7Stbbdev             // only shoot down readers if we're not transacting ourselves
21251c0b2f7Stbbdev             __TBB_ASSERT(!m.write_flag.load(std::memory_order_relaxed), "After try_acquire_writer, write_flag already true");
21351c0b2f7Stbbdev             m.write_flag.store(true, std::memory_order_relaxed);
21451c0b2f7Stbbdev             s.m_transaction_state = d1::rtm_rw_mutex::rtm_type::rtm_real_writer;
21551c0b2f7Stbbdev             return true;
21651c0b2f7Stbbdev         }
21751c0b2f7Stbbdev         return false;
21851c0b2f7Stbbdev     }
21951c0b2f7Stbbdev 
22051c0b2f7Stbbdev     //! Try to acquire read lock on the given mutex.
try_acquire_readertbb::detail::r1::rtm_rw_mutex_impl22151c0b2f7Stbbdev     static bool try_acquire_reader(d1::rtm_rw_mutex& m, d1::rtm_rw_mutex::scoped_lock& s) {
22251c0b2f7Stbbdev         // speculatively acquire the lock. If this fails, do try_lock_shared on the spin_rw_mutex.
22351c0b2f7Stbbdev         acquire_reader(m, s, /*only_speculate=*/true);
22451c0b2f7Stbbdev         if (s.m_transaction_state == d1::rtm_rw_mutex::rtm_type::rtm_transacting_reader) {
22551c0b2f7Stbbdev             return true;
22651c0b2f7Stbbdev         }
22757f524caSIlya Isaev         __TBB_ASSERT(s.m_transaction_state == d1::rtm_rw_mutex::rtm_type::rtm_not_in_mutex, nullptr);
22851c0b2f7Stbbdev         // transacting read acquire failed. try_lock_shared the real mutex
22951c0b2f7Stbbdev         if (m.try_lock_shared()) {
23051c0b2f7Stbbdev             s.m_mutex = &m;
23151c0b2f7Stbbdev             s.m_transaction_state = d1::rtm_rw_mutex::rtm_type::rtm_real_reader;
23251c0b2f7Stbbdev             return true;
23351c0b2f7Stbbdev         }
23451c0b2f7Stbbdev         return false;
23551c0b2f7Stbbdev     }
23651c0b2f7Stbbdev };
23751c0b2f7Stbbdev 
acquire_writer(d1::rtm_rw_mutex & m,d1::rtm_rw_mutex::scoped_lock & s,bool only_speculate)23851c0b2f7Stbbdev void __TBB_EXPORTED_FUNC acquire_writer(d1::rtm_rw_mutex& m, d1::rtm_rw_mutex::scoped_lock& s, bool only_speculate) {
23951c0b2f7Stbbdev     rtm_rw_mutex_impl::acquire_writer(m, s, only_speculate);
24051c0b2f7Stbbdev }
24151c0b2f7Stbbdev //! Internal acquire read lock.
24251c0b2f7Stbbdev // only_speculate == true if we're doing a try_lock, else false.
acquire_reader(d1::rtm_rw_mutex & m,d1::rtm_rw_mutex::scoped_lock & s,bool only_speculate)24351c0b2f7Stbbdev void __TBB_EXPORTED_FUNC acquire_reader(d1::rtm_rw_mutex& m, d1::rtm_rw_mutex::scoped_lock& s, bool only_speculate) {
24451c0b2f7Stbbdev     rtm_rw_mutex_impl::acquire_reader(m, s, only_speculate);
24551c0b2f7Stbbdev }
24651c0b2f7Stbbdev //! Internal upgrade reader to become a writer.
upgrade(d1::rtm_rw_mutex::scoped_lock & s)24751c0b2f7Stbbdev bool __TBB_EXPORTED_FUNC upgrade(d1::rtm_rw_mutex::scoped_lock& s) {
24851c0b2f7Stbbdev     return rtm_rw_mutex_impl::upgrade(s);
24951c0b2f7Stbbdev }
25051c0b2f7Stbbdev //! Internal downgrade writer to become a reader.
downgrade(d1::rtm_rw_mutex::scoped_lock & s)25151c0b2f7Stbbdev bool __TBB_EXPORTED_FUNC downgrade(d1::rtm_rw_mutex::scoped_lock& s) {
25251c0b2f7Stbbdev     return rtm_rw_mutex_impl::downgrade(s);
25351c0b2f7Stbbdev }
25451c0b2f7Stbbdev //! Internal try_acquire write lock.
try_acquire_writer(d1::rtm_rw_mutex & m,d1::rtm_rw_mutex::scoped_lock & s)25551c0b2f7Stbbdev bool __TBB_EXPORTED_FUNC try_acquire_writer(d1::rtm_rw_mutex& m, d1::rtm_rw_mutex::scoped_lock& s) {
25651c0b2f7Stbbdev     return rtm_rw_mutex_impl::try_acquire_writer(m, s);
25751c0b2f7Stbbdev }
25851c0b2f7Stbbdev //! Internal try_acquire read lock.
try_acquire_reader(d1::rtm_rw_mutex & m,d1::rtm_rw_mutex::scoped_lock & s)25951c0b2f7Stbbdev bool __TBB_EXPORTED_FUNC try_acquire_reader(d1::rtm_rw_mutex& m, d1::rtm_rw_mutex::scoped_lock& s) {
26051c0b2f7Stbbdev     return rtm_rw_mutex_impl::try_acquire_reader(m, s);
26151c0b2f7Stbbdev }
26251c0b2f7Stbbdev //! Internal release lock.
release(d1::rtm_rw_mutex::scoped_lock & s)26351c0b2f7Stbbdev void __TBB_EXPORTED_FUNC release(d1::rtm_rw_mutex::scoped_lock& s) {
26451c0b2f7Stbbdev     rtm_rw_mutex_impl::release(s);
26551c0b2f7Stbbdev }
26651c0b2f7Stbbdev 
26751c0b2f7Stbbdev } // namespace r1
26851c0b2f7Stbbdev } // namespace detail
26951c0b2f7Stbbdev } // namespace tbb
27051c0b2f7Stbbdev 
27151c0b2f7Stbbdev 
272