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