|  | /* Copyright (C) 2011-2021 Free Software Foundation, Inc. | 
|  | Contributed by Torvald Riegel <triegel@redhat.com>. | 
|  |  | 
|  | This file is part of the GNU Transactional Memory Library (libitm). | 
|  |  | 
|  | Libitm is free software; you can redistribute it and/or modify it | 
|  | under the terms of the GNU General Public License as published by | 
|  | the Free Software Foundation; either version 3 of the License, or | 
|  | (at your option) any later version. | 
|  |  | 
|  | Libitm is distributed in the hope that it will be useful, but WITHOUT ANY | 
|  | WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS | 
|  | FOR A PARTICULAR PURPOSE.  See the GNU General Public License for | 
|  | more details. | 
|  |  | 
|  | Under Section 7 of GPL version 3, you are granted additional | 
|  | permissions described in the GCC Runtime Library Exception, version | 
|  | 3.1, as published by the Free Software Foundation. | 
|  |  | 
|  | You should have received a copy of the GNU General Public License and | 
|  | a copy of the GCC Runtime Library Exception along with this program; | 
|  | see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see | 
|  | <http://www.gnu.org/licenses/>.  */ | 
|  |  | 
|  | #include "libitm_i.h" | 
|  | #include "futex.h" | 
|  | #include <limits.h> | 
|  |  | 
|  | namespace GTM HIDDEN { | 
|  |  | 
|  | // Acquire a RW lock for reading. | 
|  |  | 
|  | void | 
|  | gtm_rwlock::read_lock (gtm_thread *tx) | 
|  | { | 
|  | for (;;) | 
|  | { | 
|  | // Fast path: first announce our intent to read, then check for | 
|  | // conflicting intents to write.  The fence ensures that this happens | 
|  | // in exactly this order. | 
|  | tx->shared_state.store (0, memory_order_relaxed); | 
|  | atomic_thread_fence (memory_order_seq_cst); | 
|  | if (likely (writers.load (memory_order_relaxed) == 0)) | 
|  | return; | 
|  |  | 
|  | // There seems to be an active, waiting, or confirmed writer, so enter | 
|  | // the futex-based slow path. | 
|  |  | 
|  | // Before waiting, we clear our read intent check whether there are any | 
|  | // writers that might potentially wait for readers. If so, wake them. | 
|  | // We need the barrier here for the same reason that we need it in | 
|  | // read_unlock(). | 
|  | // TODO Potentially too many wake-ups. See comments in read_unlock(). | 
|  | tx->shared_state.store (-1, memory_order_relaxed); | 
|  | atomic_thread_fence (memory_order_seq_cst); | 
|  | if (writer_readers.load (memory_order_relaxed) > 0) | 
|  | { | 
|  | writer_readers.store (0, memory_order_relaxed); | 
|  | futex_wake(&writer_readers, 1); | 
|  | } | 
|  |  | 
|  | // Signal that there are waiting readers and wait until there is no | 
|  | // writer anymore. | 
|  | // TODO Spin here on writers for a while. Consider whether we woke | 
|  | // any writers before? | 
|  | while (writers.load (memory_order_relaxed)) | 
|  | { | 
|  | // An active writer. Wait until it has finished. To avoid lost | 
|  | // wake-ups, we need to use Dekker-like synchronization. | 
|  | // Note that we cannot reset readers to zero when we see that there | 
|  | // are no writers anymore after the barrier because this pending | 
|  | // store could then lead to lost wake-ups at other readers. | 
|  | readers.store (1, memory_order_relaxed); | 
|  | atomic_thread_fence (memory_order_seq_cst); | 
|  | if (writers.load (memory_order_relaxed)) | 
|  | futex_wait(&readers, 1); | 
|  | else | 
|  | { | 
|  | // There is no writer, actually.  However, we can have enabled | 
|  | // a futex_wait in other readers by previously setting readers | 
|  | // to 1, so we have to wake them up because there is no writer | 
|  | // that will do that.  We don't know whether the wake-up is | 
|  | // really necessary, but we can get lost wake-up situations | 
|  | // otherwise. | 
|  | // No additional barrier nor a nonrelaxed load is required due | 
|  | // to coherency constraints.  write_unlock() checks readers to | 
|  | // see if any wake-up is necessary, but it is not possible that | 
|  | // a reader's store prevents a required later writer wake-up; | 
|  | // If the waking reader's store (value 0) is in modification | 
|  | // order after the waiting readers store (value 1), then the | 
|  | // latter will have to read 0 in the futex due to coherency | 
|  | // constraints and the happens-before enforced by the futex | 
|  | // (paragraph 6.10 in the standard, 6.19.4 in the Batty et al | 
|  | // TR); second, the writer will be forced to read in | 
|  | // modification order too due to Dekker-style synchronization | 
|  | // with the waiting reader (see write_unlock()). | 
|  | // ??? Can we avoid the wake-up if readers is zero (like in | 
|  | // write_unlock())?  Anyway, this might happen too infrequently | 
|  | // to improve performance significantly. | 
|  | readers.store (0, memory_order_relaxed); | 
|  | futex_wake(&readers, INT_MAX); | 
|  | } | 
|  | } | 
|  |  | 
|  | // And we try again to acquire a read lock. | 
|  | } | 
|  | } | 
|  |  | 
|  |  | 
|  | // Acquire a RW lock for writing. Generic version that also works for | 
|  | // upgrades. | 
|  | // Note that an upgrade might fail (and thus waste previous work done during | 
|  | // this transaction) if there is another thread that tried to go into serial | 
|  | // mode earlier (i.e., upgrades do not have higher priority than pure writers). | 
|  | // However, this seems rare enough to not consider it further as we need both | 
|  | // a non-upgrade writer and a writer to happen to switch to serial mode | 
|  | // concurrently. If we'd want to handle this, a writer waiting for readers | 
|  | // would have to coordinate with later arriving upgrades and hand over the | 
|  | // lock to them, including the the reader-waiting state. We can try to support | 
|  | // this if this will actually happen often enough in real workloads. | 
|  |  | 
|  | bool | 
|  | gtm_rwlock::write_lock_generic (gtm_thread *tx) | 
|  | { | 
|  | // Try to acquire the write lock.  Relaxed MO is fine because of the | 
|  | // additional fence below. | 
|  | int w = 0; | 
|  | if (unlikely (!writers.compare_exchange_strong (w, 1, memory_order_relaxed))) | 
|  | { | 
|  | // If this is an upgrade, we must not wait for other writers or | 
|  | // upgrades. | 
|  | if (tx != 0) | 
|  | return false; | 
|  |  | 
|  | // There is already a writer. If there are no other waiting writers, | 
|  | // switch to contended mode.  We need seq_cst memory order to make the | 
|  | // Dekker-style synchronization work. | 
|  | if (w != 2) | 
|  | w = writers.exchange (2, memory_order_relaxed); | 
|  | while (w != 0) | 
|  | { | 
|  | futex_wait(&writers, 2); | 
|  | w = writers.exchange (2, memory_order_relaxed); | 
|  | } | 
|  | } | 
|  | // This fence is both required for the Dekker-like synchronization we do | 
|  | // here and is the acquire MO required to make us synchronize-with prior | 
|  | // writers. | 
|  | atomic_thread_fence (memory_order_seq_cst); | 
|  |  | 
|  | // We have acquired the writer side of the R/W lock. Now wait for any | 
|  | // readers that might still be active. | 
|  | // TODO In the worst case, this requires one wait/wake pair for each | 
|  | // active reader. Reduce this! | 
|  | for (gtm_thread *it = gtm_thread::list_of_threads; it != 0; | 
|  | it = it->next_thread) | 
|  | { | 
|  | if (it == tx) | 
|  | continue; | 
|  | // Use a loop here to check reader flags again after waiting. | 
|  | while (it->shared_state.load (memory_order_relaxed) | 
|  | != ~(typeof it->shared_state)0) | 
|  | { | 
|  | // If this is an upgrade, we have to break deadlocks with | 
|  | // privatization safety.  This may fail on our side, in which | 
|  | // case we need to cancel our attempt to upgrade.  Also, we do not | 
|  | // block but just spin so that we never have to be woken. | 
|  | if (tx != 0) | 
|  | { | 
|  | if (!abi_disp()->snapshot_most_recent ()) | 
|  | { | 
|  | write_unlock (); | 
|  | return false; | 
|  | } | 
|  | continue; | 
|  | } | 
|  | // An active reader. Wait until it has finished. To avoid lost | 
|  | // wake-ups, we need to use Dekker-like synchronization. | 
|  | // Note that we can reset writer_readers to zero when we see after | 
|  | // the barrier that the reader has finished in the meantime; | 
|  | // however, this is only possible because we are the only writer. | 
|  | // TODO Spin for a while on this reader flag. | 
|  | writer_readers.store (1, memory_order_relaxed); | 
|  | atomic_thread_fence (memory_order_seq_cst); | 
|  | if (it->shared_state.load (memory_order_relaxed) | 
|  | != ~(typeof it->shared_state)0) | 
|  | futex_wait(&writer_readers, 1); | 
|  | else | 
|  | writer_readers.store (0, memory_order_relaxed); | 
|  | } | 
|  | } | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | // Acquire a RW lock for writing. | 
|  |  | 
|  | void | 
|  | gtm_rwlock::write_lock () | 
|  | { | 
|  | write_lock_generic (0); | 
|  | } | 
|  |  | 
|  |  | 
|  | // Upgrade a RW lock that has been locked for reading to a writing lock. | 
|  | // Do this without possibility of another writer incoming.  Return false | 
|  | // if this attempt fails (i.e. another thread also upgraded). | 
|  |  | 
|  | bool | 
|  | gtm_rwlock::write_upgrade (gtm_thread *tx) | 
|  | { | 
|  | return write_lock_generic (tx); | 
|  | } | 
|  |  | 
|  |  | 
|  | // Has to be called iff the previous upgrade was successful and after it is | 
|  | // safe for the transaction to not be marked as a reader anymore. | 
|  |  | 
|  | void | 
|  | gtm_rwlock::write_upgrade_finish (gtm_thread *tx) | 
|  | { | 
|  | // We are not a reader anymore.  This is only safe to do after we have | 
|  | // acquired the writer lock. | 
|  | tx->shared_state.store (-1, memory_order_release); | 
|  | } | 
|  |  | 
|  |  | 
|  | // Release a RW lock from reading. | 
|  |  | 
|  | void | 
|  | gtm_rwlock::read_unlock (gtm_thread *tx) | 
|  | { | 
|  | // We only need release memory order here because of privatization safety | 
|  | // (this ensures that marking the transaction as inactive happens after | 
|  | // any prior data accesses by this transaction, and that neither the | 
|  | // compiler nor the hardware order this store earlier). | 
|  | // ??? We might be able to avoid this release here if the compiler can't | 
|  | // merge the release fence with the subsequent seq_cst fence. | 
|  | tx->shared_state.store (-1, memory_order_release); | 
|  |  | 
|  | // If there is a writer waiting for readers, wake it up.  We need the fence | 
|  | // to avoid lost wake-ups.  Furthermore, the privatization safety | 
|  | // implementation in gtm_thread::try_commit() relies on the existence of | 
|  | // this seq_cst fence. | 
|  | // ??? We might not be the last active reader, so the wake-up might happen | 
|  | // too early. How do we avoid this without slowing down readers too much? | 
|  | // Each reader could scan the list of txns for other active readers but | 
|  | // this can result in many cache misses. Use combining instead? | 
|  | // TODO Sends out one wake-up for each reader in the worst case. | 
|  | atomic_thread_fence (memory_order_seq_cst); | 
|  | if (unlikely (writer_readers.load (memory_order_relaxed) > 0)) | 
|  | { | 
|  | // No additional barrier needed here (see write_unlock()). | 
|  | writer_readers.store (0, memory_order_relaxed); | 
|  | futex_wake(&writer_readers, 1); | 
|  | } | 
|  | } | 
|  |  | 
|  |  | 
|  | // Release a RW lock from writing. | 
|  |  | 
|  | void | 
|  | gtm_rwlock::write_unlock () | 
|  | { | 
|  | // Release MO so that we synchronize with subsequent writers. | 
|  | if (writers.exchange (0, memory_order_release) == 2) | 
|  | { | 
|  | // There might be waiting writers, so wake them.  If we woke any thread, | 
|  | // we assume it to indeed be a writer; waiting writers will never give | 
|  | // up, so we can assume that they will take care of anything else such | 
|  | // as waking readers. | 
|  | if (futex_wake(&writers, 1) > 0) | 
|  | return; | 
|  | // If we did not wake any waiting writers, we might indeed be the last | 
|  | // writer (this can happen because write_lock_generic() exchanges 0 or 1 | 
|  | // to 2 and thus might go to contended mode even if no other thread | 
|  | // holds the write lock currently). Therefore, we have to fall through | 
|  | // to the normal reader wake-up code. | 
|  | } | 
|  | // This fence is required because we do Dekker-like synchronization here. | 
|  | atomic_thread_fence (memory_order_seq_cst); | 
|  | // No waiting writers, so wake up all waiting readers. | 
|  | if (readers.load (memory_order_relaxed) > 0) | 
|  | { | 
|  | // No additional barrier needed here.  The previous load must be in | 
|  | // modification order because of the coherency constraints.  Late stores | 
|  | // by a reader are not a problem because readers do Dekker-style | 
|  | // synchronization on writers. | 
|  | readers.store (0, memory_order_relaxed); | 
|  | futex_wake(&readers, INT_MAX); | 
|  | } | 
|  | } | 
|  |  | 
|  | } // namespace GTM |