summaryrefslogtreecommitdiff
path: root/libitm/method-wbetl.cc
diff options
context:
space:
mode:
Diffstat (limited to 'libitm/method-wbetl.cc')
-rw-r--r--libitm/method-wbetl.cc628
1 files changed, 628 insertions, 0 deletions
diff --git a/libitm/method-wbetl.cc b/libitm/method-wbetl.cc
new file mode 100644
index 00000000000..093d1c769f1
--- /dev/null
+++ b/libitm/method-wbetl.cc
@@ -0,0 +1,628 @@
+/* Copyright (C) 2009, 2011 Free Software Foundation, Inc.
+ Contributed by Richard Henderson <rth@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"
+
+namespace {
+
+using namespace GTM;
+
+class wbetl_dispatch : public abi_dispatch
+{
+ private:
+ static const size_t RW_SET_SIZE = 4096;
+
+ struct r_entry
+ {
+ gtm_version version;
+ gtm_stmlock *lock;
+ };
+
+ r_entry *m_rset_entries;
+ size_t m_rset_nb_entries;
+ size_t m_rset_size;
+
+ struct w_entry
+ {
+ /* There's a hashtable where the locks are held, so multiple
+ cachelines can hash to a given bucket. This link points to the
+ possible next cacheline that also hashes to this bucket. */
+ struct w_entry *next;
+
+ /* Every entry in this bucket (accessed by NEXT) has the same LOCK
+ address below. */
+ gtm_stmlock *lock;
+
+ gtm_cacheline *addr;
+ gtm_cacheline *value;
+ gtm_version version;
+ };
+
+ w_entry *m_wset_entries;
+ size_t m_wset_nb_entries;
+ size_t m_wset_size;
+ bool m_wset_reallocate;
+
+ gtm_version m_start;
+ gtm_version m_end;
+
+ gtm_cacheline_page *m_cache_page;
+ unsigned m_n_cache_page;
+
+ private:
+ bool local_w_entry_p (w_entry *w);
+ bool has_read (gtm_stmlock *lock);
+ bool validate();
+ bool extend();
+
+ gtm_cacheline *do_write_lock(gtm_cacheline *);
+ gtm_cacheline *do_after_write_lock(gtm_cacheline *);
+ const gtm_cacheline *do_read_lock(const gtm_cacheline *, bool);
+
+ public:
+ wbetl_dispatch();
+
+ virtual const gtm_cacheline *read_lock(const gtm_cacheline *, ls_modifier);
+ virtual mask_pair write_lock(gtm_cacheline *, ls_modifier);
+
+ virtual bool trycommit();
+ virtual void rollback();
+ virtual void reinit();
+ virtual void fini();
+ virtual bool trydropreference (void *, size_t);
+};
+
+/* Check if W is one of our write locks. */
+
+inline bool
+wbetl_dispatch::local_w_entry_p (w_entry *w)
+{
+ return (m_wset_entries <= w && w < m_wset_entries + m_wset_nb_entries);
+}
+
+/* Check if stripe has been read previously. */
+
+inline bool
+wbetl_dispatch::has_read (gtm_stmlock *lock)
+{
+ // ??? Consider using an AA tree to lookup the r_set entries.
+ size_t n = m_rset_nb_entries;
+ for (size_t i = 0; i < n; ++i)
+ if (m_rset_entries[i].lock == lock)
+ return true;
+
+ return false;
+}
+
+/* Validate read set, i.e. check if all read addresses are still valid now. */
+
+bool
+wbetl_dispatch::validate ()
+{
+ __sync_synchronize ();
+
+ size_t n = m_rset_nb_entries;
+ for (size_t i = 0; i < n; ++i)
+ {
+ r_entry *r = &m_rset_entries[i];
+ gtm_stmlock l = *r->lock;
+
+ if (gtm_stmlock_owned_p (l))
+ {
+ w_entry *w = (w_entry *) gtm_stmlock_get_addr (l);
+
+ // If someone has locked us, it better be by someone in the
+ // current thread.
+ if (!local_w_entry_p (w))
+ return false;
+ }
+ else if (gtm_stmlock_get_version (l) != r->version)
+ return false;
+ }
+
+ return true;
+}
+
+/* Extend the snapshot range. */
+
+bool
+wbetl_dispatch::extend ()
+{
+ gtm_version now = gtm_get_clock ();
+
+ if (validate ())
+ {
+ m_end = now;
+ return true;
+ }
+ return false;
+}
+
+/* Acquire a write lock on ADDR. */
+
+gtm_cacheline *
+wbetl_dispatch::do_write_lock(gtm_cacheline *addr)
+{
+ gtm_stmlock *lock;
+ gtm_stmlock l, l2;
+ gtm_version version;
+ w_entry *w, *prev = NULL;
+
+ lock = gtm_get_stmlock (addr);
+ l = *lock;
+
+ restart_no_load:
+ if (gtm_stmlock_owned_p (l))
+ {
+ w = (w_entry *) gtm_stmlock_get_addr (l);
+
+ /* Did we previously write the same address? */
+ if (local_w_entry_p (w))
+ {
+ prev = w;
+ while (1)
+ {
+ if (addr == prev->addr)
+ return prev->value;
+ if (prev->next == NULL)
+ break;
+ prev = prev->next;
+ }
+
+ /* Get version from previous entry write set. */
+ version = prev->version;
+
+ /* If there's not enough entries, we must reallocate the array,
+ which invalidates all pointers to write set entries, which
+ means we have to restart the transaction. */
+ if (m_wset_nb_entries == m_wset_size)
+ {
+ m_wset_size *= 2;
+ m_wset_reallocate = true;
+ gtm_tx()->restart (RESTART_REALLOCATE);
+ }
+
+ w = &m_wset_entries[m_wset_nb_entries];
+ goto do_write;
+ }
+
+ gtm_tx()->restart (RESTART_LOCKED_WRITE);
+ }
+ else
+ {
+ version = gtm_stmlock_get_version (l);
+
+ /* We might have read an older version previously. */
+ if (version > m_end)
+ {
+ if (has_read (lock))
+ gtm_tx()->restart (RESTART_VALIDATE_WRITE);
+ }
+
+ /* Extend write set, aborting to reallocate write set entries. */
+ if (m_wset_nb_entries == m_wset_size)
+ {
+ m_wset_size *= 2;
+ m_wset_reallocate = true;
+ gtm_tx()->restart (RESTART_REALLOCATE);
+ }
+
+ /* Acquire the lock. */
+ w = &m_wset_entries[m_wset_nb_entries];
+ l2 = gtm_stmlock_set_owned (w);
+ l = __sync_val_compare_and_swap (lock, l, l2);
+ if (l != l2)
+ goto restart_no_load;
+ }
+
+ do_write:
+ m_wset_nb_entries++;
+ if (prev != NULL)
+ prev->next = w;
+ w->next = 0;
+ w->lock = lock;
+ w->addr = addr;
+ w->version = version;
+
+ gtm_cacheline_page *page = m_cache_page;
+ unsigned index = m_n_cache_page;
+
+ if (page == NULL || index == gtm_cacheline_page::LINES)
+ {
+ gtm_cacheline_page *npage = new gtm_cacheline_page;
+ npage->prev = page;
+ m_cache_page = page = npage;
+ m_n_cache_page = 1;
+ index = 0;
+ }
+ else
+ m_n_cache_page = index + 1;
+
+ gtm_cacheline *line = &page->lines[index];
+ w->value = line;
+ page->masks[index] = 0;
+ *line = *addr;
+
+ return line;
+}
+
+gtm_cacheline *
+wbetl_dispatch::do_after_write_lock (gtm_cacheline *addr)
+{
+ gtm_stmlock *lock;
+ gtm_stmlock l;
+ w_entry *w;
+
+ lock = gtm_get_stmlock (addr);
+ l = *lock;
+ assert (gtm_stmlock_owned_p (l));
+
+ w = (w_entry *) gtm_stmlock_get_addr (l);
+ assert (local_w_entry_p (w));
+
+ while (1)
+ {
+ if (addr == w->addr)
+ return w->value;
+ w = w->next;
+ }
+}
+
+/* Acquire a read lock on ADDR. */
+
+const gtm_cacheline *
+wbetl_dispatch::do_read_lock (const gtm_cacheline *addr, bool after_read)
+{
+ gtm_stmlock *lock;
+ gtm_stmlock l, l2;
+ gtm_version version;
+ w_entry *w;
+
+ lock = gtm_get_stmlock (addr);
+ l = *lock;
+
+ restart_no_load:
+ if (gtm_stmlock_owned_p (l))
+ {
+ w = (w_entry *) gtm_stmlock_get_addr (l);
+
+ /* Did we previously write the same address? */
+ if (local_w_entry_p (w))
+ {
+ while (1)
+ {
+ if (addr == w->addr)
+ return w->value;
+ if (w->next == NULL)
+ return addr;
+ w = w->next;
+ }
+ }
+
+ gtm_tx()->restart (RESTART_LOCKED_READ);
+ }
+
+ version = gtm_stmlock_get_version (l);
+
+ /* If version is no longer valid, re-validate the read set. */
+ if (version > m_end)
+ {
+ if (!extend ())
+ gtm_tx()->restart (RESTART_VALIDATE_READ);
+
+ if (!after_read)
+ {
+ // Verify that the version has not yet been overwritten. The read
+ // value has not yet been added to read set and may not have been
+ // checked during the extend.
+ //
+ // ??? This only makes sense if we're actually reading the value
+ // and returning it now -- which I believe the original TinySTM
+ // did. This doesn't make a whole lot of sense when we're
+ // manipulating cachelines as we are now. Do we need some other
+ // form of lock verification here, or is the validate call in
+ // trycommit sufficient?
+
+ __sync_synchronize ();
+ l2 = *lock;
+ if (l != l2)
+ {
+ l = l2;
+ goto restart_no_load;
+ }
+ }
+ }
+
+ if (!after_read)
+ {
+ r_entry *r;
+
+ /* Add the address and version to the read set. */
+ if (m_rset_nb_entries == m_rset_size)
+ {
+ m_rset_size *= 2;
+
+ m_rset_entries = (r_entry *)
+ xrealloc (m_rset_entries, m_rset_size * sizeof(r_entry));
+ }
+ r = &m_rset_entries[m_rset_nb_entries++];
+ r->version = version;
+ r->lock = lock;
+ }
+
+ return addr;
+}
+
+const gtm_cacheline *
+wbetl_dispatch::read_lock (const gtm_cacheline *addr, ls_modifier ltype)
+{
+ switch (ltype)
+ {
+ case NONTXNAL:
+ return addr;
+ case R:
+ return do_read_lock (addr, false);
+ case RaR:
+ return do_read_lock (addr, true);
+ case RaW:
+ return do_after_write_lock (const_cast<gtm_cacheline *>(addr));
+ case RfW:
+ return do_write_lock (const_cast<gtm_cacheline *>(addr));
+ default:
+ abort ();
+ }
+}
+
+abi_dispatch::mask_pair
+wbetl_dispatch::write_lock (gtm_cacheline *addr, ls_modifier ltype)
+{
+ gtm_cacheline *line;
+
+ switch (ltype)
+ {
+ case NONTXNAL:
+ return mask_pair (addr, &mask_sink);
+ case W:
+ case WaR:
+ line = do_write_lock (addr);
+ break;
+ case WaW:
+ line = do_after_write_lock (addr);
+ break;
+ default:
+ abort ();
+ }
+
+ return mask_pair (line, gtm_cacheline_page::mask_for_page_line (line));
+}
+
+/* Commit the transaction. */
+
+bool
+wbetl_dispatch::trycommit ()
+{
+ const size_t n = m_wset_nb_entries;
+ if (n != 0)
+ {
+ /* Get commit timestamp. */
+ gtm_version t = gtm_inc_clock ();
+
+ /* Validate only if a concurrent transaction has started since. */
+ if (m_start != t - 1 && !validate ())
+ return false;
+
+ /* Install new versions. */
+ for (size_t i = 0; i < n; ++i)
+ {
+ w_entry *w = &m_wset_entries[i];
+ gtm_cacheline_mask mask
+ = *gtm_cacheline_page::mask_for_page_line (w->value);
+
+ /* Filter out any updates that overlap the libitm stack. */
+ mask = gtm_mask_stack (w->addr, mask);
+
+ gtm_cacheline::copy_mask (w->addr, w->value, mask);
+ }
+
+ /* Only emit barrier after all cachelines are copied. */
+ gtm_cacheline::copy_mask_wb ();
+
+ /* Drop locks. */
+ for (size_t i = 0; i < n; ++i)
+ {
+ w_entry *w = &m_wset_entries[i];
+
+ /* Every link along the chain has the same lock, but only
+ bother dropping the lock once per bucket (at the end). */
+ if (w->next == NULL)
+ *w->lock = gtm_stmlock_set_version (t);
+ }
+ }
+
+ __sync_synchronize ();
+ return true;
+}
+
+void
+wbetl_dispatch::rollback ()
+{
+ /* Drop locks. */
+ const size_t n = m_wset_nb_entries;
+ for (size_t i = 0; i < n; ++i)
+ {
+ w_entry *w = &m_wset_entries[i];
+
+ /* Every link along the chain has the same lock, but only
+ bother dropping the lock once per bucket (at the end). */
+ if (w->next == NULL)
+ *w->lock = gtm_stmlock_set_version (w->version);
+ }
+
+ __sync_synchronize ();
+}
+
+void
+wbetl_dispatch::reinit ()
+{
+ gtm_cacheline_page *page;
+
+ m_rset_nb_entries = 0;
+ m_wset_nb_entries = 0;
+
+ if (m_wset_reallocate)
+ {
+ m_wset_reallocate = 0;
+ m_wset_entries = (w_entry *)
+ xrealloc (m_wset_entries, m_wset_size * sizeof(w_entry));
+ }
+
+ page = m_cache_page;
+ if (page)
+ {
+ /* Release all but one of the pages of cachelines. */
+ gtm_cacheline_page *prev = page->prev;
+ if (prev)
+ {
+ page->prev = 0;
+ delete prev;
+ }
+
+ /* Start the next cacheline allocation from the beginning. */
+ m_n_cache_page = 0;
+ }
+
+ m_start = m_end = gtm_get_clock ();
+}
+
+void
+wbetl_dispatch::fini ()
+{
+ delete m_cache_page;
+ free (m_rset_entries);
+ free (m_wset_entries);
+ delete this;
+}
+
+/* Attempt to drop any internal references to PTR. Return TRUE if successful.
+
+ This is an adaptation of the transactional memcpy function.
+
+ What we do here is flush out the current transactional content of
+ PTR to real memory, and remove the write mask bits associated with
+ it so future commits will ignore this piece of memory. */
+
+bool
+wbetl_dispatch::trydropreference (void *ptr, size_t size)
+{
+ if (size == 0)
+ return true;
+
+ if (!validate ())
+ return false;
+
+ uintptr_t isrc = (uintptr_t)ptr;
+ // The position in the source cacheline where *PTR starts.
+ uintptr_t sofs = isrc & (CACHELINE_SIZE - 1);
+ gtm_cacheline *src
+ = reinterpret_cast<gtm_cacheline *>(isrc & -CACHELINE_SIZE);
+ unsigned char *dst = (unsigned char *)ptr;
+ abi_dispatch::mask_pair pair;
+
+ // If we're trying to drop a reference, we should already have a
+ // write lock on it. If we don't have one, there's no work to do.
+ if (!gtm_stmlock_owned_p (*gtm_get_stmlock (src)))
+ return true;
+
+ // We copy the data in three stages:
+
+ // (a) Copy stray bytes at the beginning that are smaller than a
+ // cacheline.
+ if (sofs != 0)
+ {
+ size_t sleft = CACHELINE_SIZE - sofs;
+ size_t min = (size <= sleft ? size : sleft);
+
+ // WaW will give us the current locked entry.
+ pair = this->write_lock (src, WaW);
+
+ // *jedi mind wave*...these aren't the droids you're looking for.
+ *pair.mask &= ~((((gtm_cacheline_mask)1 << min) - 1) << sofs);
+
+ memcpy (dst, &pair.line->b[sofs], min);
+ dst += min;
+ src++;
+ size -= min;
+ }
+
+ // (b) Copy subsequent cacheline sized chunks.
+ while (size >= CACHELINE_SIZE)
+ {
+ pair = this->write_lock(src, WaW);
+ *pair.mask = 0;
+ memcpy (dst, pair.line, CACHELINE_SIZE);
+ dst += CACHELINE_SIZE;
+ src++;
+ size -= CACHELINE_SIZE;
+ }
+
+ // (c) Copy anything left over.
+ if (size != 0)
+ {
+ pair = this->write_lock(src, WaW);
+ *pair.mask &= ~(((gtm_cacheline_mask)1 << size) - 1);
+ memcpy (dst, pair.line, size);
+ }
+
+ // No need to drop locks, since we're going to abort the transaction
+ // anyhow.
+
+ return true;
+}
+
+
+wbetl_dispatch::wbetl_dispatch ()
+ : abi_dispatch (false, false)
+{
+ m_rset_entries = (r_entry *) xmalloc (RW_SET_SIZE * sizeof(r_entry));
+ m_rset_nb_entries = 0;
+ m_rset_size = RW_SET_SIZE;
+
+ m_wset_entries = (w_entry *) xmalloc (RW_SET_SIZE * sizeof(w_entry));
+ m_wset_nb_entries = 0;
+ m_wset_size = RW_SET_SIZE;
+ m_wset_reallocate = false;
+
+ m_start = m_end = gtm_get_clock ();
+
+ m_cache_page = 0;
+ m_n_cache_page = 0;
+}
+
+} // anon namespace
+
+abi_dispatch *
+GTM::dispatch_wbetl ()
+{
+ return new wbetl_dispatch ();
+}