| /* Copyright (C) 2011-2022 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 |