aboutsummaryrefslogtreecommitdiff
path: root/libgupc/portals4/gupcr_lock.upc
diff options
context:
space:
mode:
Diffstat (limited to 'libgupc/portals4/gupcr_lock.upc')
-rw-r--r--libgupc/portals4/gupcr_lock.upc539
1 files changed, 539 insertions, 0 deletions
diff --git a/libgupc/portals4/gupcr_lock.upc b/libgupc/portals4/gupcr_lock.upc
new file mode 100644
index 00000000000..fc20fb5ef8c
--- /dev/null
+++ b/libgupc/portals4/gupcr_lock.upc
@@ -0,0 +1,539 @@
+/* Copyright (C) 2012-2016 Free Software Foundation, Inc.
+ This file is part of the UPC runtime Library.
+ Written by Gary Funck <gary@intrepid.com>
+ and Nenad Vukicevic <nenad@intrepid.com>
+
+This file is part of GCC.
+
+GCC 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, or (at your option)
+any later version.
+
+GCC 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 <upc.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <stdio.h>
+#include "gupcr_config.h"
+#include "gupcr_defs.h"
+#include "gupcr_utils.h"
+#include "gupcr_lock_sup.h"
+#include "gupcr_lock.h"
+#include "gupcr_barrier.h"
+
+/**
+ * @file gupcr_lock.upc
+ * GUPC Portals4 UPC lock implementation.
+ *
+ * The GUPC lock functions use MCS locks as described in the
+ * Mellor-Crummey and Scott paper: Algorithms for Scalable Synchronization
+ * on Shared-Memory Multiprocessors, ACM Transaction on Computer Systems,
+ * February 1991.
+ *
+ * A lock is a simple data structure that lives in the shared memory space.
+ * A pointer is used to point to the last thread on the waiting list.
+ * A lock is available if this pointer is NULL. The following Portals
+ * atomic operations are used:
+ * - SWAP - determine if the lock is available
+ * - CSWAP - determine if the lock can be released
+ *
+ * The GUPC Portals implementation of UPC locks has the following
+ * characteristics:
+ * - The lock object has affinity to the thread that creates the lock.
+ * If the collective function, upc_all_lock_alloc, is called, then
+ * the lock object will have affinity to thread 0.
+ * - A thread's lock waiting queue link object has affinity to the
+ * waiting thread.
+ * - Portals atomic functions (SWAP and CSWAP) are used to
+ * guarantee fair access and FIFO ordering for all waiting threads.
+ * - A special Portals Table Entry (GUPCR_PTL_PTE_LOCK) is used to provide
+ * for signaling threads taken off the waiting list.
+ *
+ * @addtogroup LOCK GUPCR Lock Functions
+ * @{
+ */
+
+typedef struct gupcr_lock_link_struct gupcr_lock_link_t;
+typedef shared [] gupcr_lock_link_t *gupcr_lock_link_ref;
+
+/** upc_lock_t is an opaque shared type. The 'upc_lock_struct'
+ structure describes the internal representation of the
+ UPC lock type. */
+struct upc_lock_struct
+{
+ gupcr_lock_link_ref last; /* Must be first. */
+ gupcr_lock_link_ref owner_link;
+ upc_lock_t *free_link;
+};
+
+struct gupcr_lock_link_struct
+{
+ gupcr_lock_link_ref next; /* Must be first. */
+ int signal; /* Signal the lock ownership. */
+ int free; /* Lock has been freed. */
+};
+
+struct gupcr_lock_link_cache_struct
+{
+ gupcr_lock_link_t lock_links[GUPCR_MAX_LOCKS];
+};
+typedef struct gupcr_lock_link_cache_struct gupcr_lock_link_cache_t;
+typedef shared gupcr_lock_link_cache_t *gupcr_lock_link_cache_ref;
+
+/** Array of lock links managed as a per-thread free list. */
+static shared gupcr_lock_link_cache_t gupcr_lock_link_cache[THREADS];
+/** Per thread lock link free list. */
+static gupcr_lock_link_ref gupcr_lock_links;
+
+/** UPC lock free list. */
+static upc_lock_t *lock_free;
+
+/* Heap allocator locks. */
+/** Heap region allocation lock. */
+shared upc_lock_t gupcr_heap_region_lock_data;
+upc_lock_t *gupcr_heap_region_lock;
+/** Global heap lock. */
+shared upc_lock_t gupcr_global_heap_lock_data;
+upc_lock_t *gupcr_global_heap_lock;
+/** Local heap locks. */
+shared upc_lock_t gupcr_local_heap_lock_data[THREADS];
+upc_lock_t *gupcr_local_heap_lock;
+
+/**
+ * Initialize the heap allocator locks.
+ *
+ * All shared references must be local due to the fact
+ * this is called before Portals has been initialized.
+ */
+void
+gupcr_lock_heap_sup_init (void)
+{
+ if (!MYTHREAD)
+ {
+ upc_memset (&gupcr_heap_region_lock_data, '\0', sizeof (upc_lock_t));
+ upc_memset (&gupcr_global_heap_lock_data, '\0', sizeof (upc_lock_t));
+ }
+ gupcr_heap_region_lock = &gupcr_heap_region_lock_data;
+ gupcr_global_heap_lock = &gupcr_global_heap_lock_data;
+
+ upc_memset (&gupcr_local_heap_lock_data[MYTHREAD],
+ '\0', sizeof (upc_lock_t));
+ gupcr_local_heap_lock = &gupcr_local_heap_lock_data[MYTHREAD];
+}
+
+/**
+ * Initialize the local lock free list.
+ */
+void
+gupcr_lock_free_init (void)
+{
+ lock_free = NULL;
+}
+
+/**
+ * Initialize the local lock link free list.
+ *
+ * gupcr_lock_link_init() is called before the UPC runtime
+ * is fully initialized. Care is taken to make no UPC shared
+ * memory accesses.
+ */
+void
+gupcr_lock_link_init (void)
+{
+ gupcr_lock_link_ref link;
+ gupcr_lock_link_t *local_link;
+ gupcr_lock_links = gupcr_lock_link_cache[MYTHREAD].lock_links;
+ link = gupcr_lock_links;
+ local_link = (gupcr_lock_link_t *) link;
+ memset (local_link, '\0', sizeof (gupcr_lock_link_cache_t));
+ for (int i = 0; i < (GUPCR_MAX_LOCKS - 1); ++i)
+ (local_link++)->next = ++link;
+}
+
+/**
+ * Release the lock link record.
+ */
+static inline void
+gupcr_lock_link_free (gupcr_lock_link_ref link)
+{
+ link->next = gupcr_lock_links;
+ gupcr_lock_links = link;
+}
+
+/**
+ * Allocate a lock link record.
+ *
+ * A lock link is a data structure used to link together
+ * all threads waiting on a particular lock.
+ * Lock links are located in the shared space of the thread
+ * acquiring the lock.
+ *
+ * Lock links are kept in a locally managed list
+ * (used as a cache) rooted at 'gupcr_lock_links'.
+ * This locally managed free list avoids the need
+ * to call upc_alloc().
+ */
+static inline gupcr_lock_link_ref
+gupcr_lock_link_alloc (void)
+{
+ gupcr_lock_link_ref link;
+ link = gupcr_lock_links;
+ if (!link)
+ {
+ /* Try to find a link block that has been freed by
+ other threads and thus not returned to the free list. */
+ gupcr_lock_link_ref mlink = (gupcr_lock_link_ref)
+ &gupcr_lock_link_cache[MYTHREAD].lock_links;
+ for (int i = 0; i < (GUPCR_MAX_LOCKS - 1); ++i)
+ {
+ if (mlink->free)
+ {
+ mlink->free = 0;
+ gupcr_lock_link_free (mlink);
+ }
+ mlink++;
+ }
+ link = gupcr_lock_links;
+ if (!link)
+ gupcr_fatal_error ("cannot allocate a UPC lock link. "
+ "The number of allocated per thread lock links "
+ "exceeds the configuration defined "
+ "maximum of %d entries.",
+ GUPCR_MAX_LOCKS);
+ }
+ gupcr_lock_links = link->next;
+ return link;
+}
+
+/**
+ * Allocate a lock and return a pointer to it.
+ *
+ * The 'upc_global_lock_alloc' function dynamically allocates a lock and
+ * returns a pointer to it. The lock is created in an unlocked state.
+ * The 'upc_global_lock_alloc' function is not a collective function.
+ * If called by multiple threads, each thread will receive a pointer
+ * to a unique lock.
+ *
+ * @retval Pointer to a newly allocated lock
+ */
+upc_lock_t *
+upc_global_lock_alloc (void)
+{
+ upc_lock_t *lock;
+ gupcr_trace (FC_LOCK, "LOCK GLOBAL_ALLOC ENTER");
+ /* Check if there is a lock cached on the free list. */
+ if (lock_free)
+ {
+ lock = lock_free;
+ lock_free = lock->free_link;
+ }
+ else
+ {
+ /* Allocate space for the lock from shared memory with
+ affinity to the calling thread. */
+ lock = upc_alloc (sizeof (upc_lock_t));
+ if (lock == NULL)
+ gupcr_fatal_error ("cannot allocate memory for the lock");
+ }
+ lock->last = NULL;
+ lock->owner_link = NULL;
+ gupcr_trace (FC_LOCK, "LOCK GLOBAL_ALLOC EXIT %lu:0x%lx",
+ (long unsigned) upc_threadof (lock),
+ (long unsigned) upc_addrfield (lock));
+ return lock;
+}
+
+void
+upc_all_lock_free (upc_lock_t *ptr)
+{
+ upc_all_free (ptr);
+}
+
+/**
+ * Free all lock resources.
+ *
+ * The 'upc_lock_free' function frees all resources associated with the
+ * dynamically allocated 'upc_lock_t' pointed to by 'lock'. If 'lock' is a
+ * null pointer, no action occurs. Otherwise, if the argument does not
+ * match a pointer earlier returned by the 'upc_global_lock_alloc' or
+ * 'upc_all_lock_alloc' function, or if the lock has been de-allocated by
+ * a previous call to 'upc_lock_free' the behavior is undefined.
+ *
+ * @param [in] lock Pointer to a lock
+ */
+void
+upc_lock_free (upc_lock_t *lock)
+{
+ gupcr_lock_link_ref link;
+
+ gupcr_trace (FC_LOCK, "LOCK FREE ENTER %lu:0x%lx",
+ (long unsigned) upc_threadof (lock),
+ (long unsigned) upc_addrfield (lock));
+ if (lock == NULL)
+ return;
+
+ link = lock->owner_link;
+ /* Release the link block if this thread owns the lock. */
+ if (link)
+ {
+ if (MYTHREAD == (int) upc_threadof (link))
+ {
+ gupcr_lock_link_free (link);
+ }
+ else
+ link->free = 1;
+ }
+ if (MYTHREAD == (int) upc_threadof (lock))
+ {
+ /* Release it on the local free list. */
+ lock->free_link = lock_free;
+ lock_free = lock;
+ }
+ else
+ upc_free (lock);
+
+ gupcr_trace (FC_LOCK, "LOCK FREE EXIT");
+}
+
+/**
+ * Allocate a lock and return a pointer to it on all threads.
+ *
+ * The 'upc_all_lock_alloc' function dynamically allocates a lock
+ * and returns a pointer to it. The lock is created in an unlocked state.
+ * 'upc_all_lock_alloc' is a collective function.
+ * The return value on every thread points to the same lock object.
+ *
+ * @retval Pointer to a newly allocated lock
+ */
+upc_lock_t *
+upc_all_lock_alloc (void)
+{
+ upc_lock_t *lock;
+ gupcr_trace (FC_LOCK, "LOCK ALL_ALLOC ENTER");
+ /* Allocate space for the lock from the shared memory of
+ thread 0 and broadcast its address. */
+ if (MYTHREAD == 0)
+ {
+ if (lock_free)
+ {
+ lock = lock_free;
+ lock_free = lock->free_link;
+ }
+ else
+ {
+ lock = upc_alloc (sizeof (upc_lock_t));
+ if (lock == NULL)
+ gupcr_fatal_error ("cannot allocate memory for the lock");
+ }
+ lock->last = NULL;
+ lock->owner_link = NULL;
+ gupcr_bcast_send (&lock, sizeof (lock));
+ }
+ else
+ gupcr_bcast_recv (&lock, sizeof (lock));
+ gupcr_trace (FC_LOCK, "LOCK ALL_ALLOC EXIT %lu:0x%lx",
+ (long unsigned) upc_threadof (lock),
+ (long unsigned) upc_addrfield (lock));
+ return lock;
+}
+
+/**
+ * Set the state of the lock to locked.
+ *
+ * If the lock is already in a 'locked' state due to the calling thread
+ * setting it to the 'locked' state, the result is undefined.
+ * If the lock is already in a 'locked' state, then the calling thread
+ * waits for some other thread to set the state to 'unlocked'.
+ * Once the lock is in the state 'unlocked', a single calling thread
+ * sets the state to 'locked' and the function returns.
+ * A null strict access is implied after a call to 'upc_lock'.
+ *
+ * @param [in] lock Pointer to a lock
+ */
+void
+upc_lock (upc_lock_t *lock)
+{
+ gupcr_lock_link_ref link, old_link;
+ shared [] gupcr_lock_link_ref *lock_last_addr;
+ size_t lock_last_thread, lock_last_offset;
+ gupcr_trace (FC_LOCK, "LOCK LOCK ENTER %lu:0x%lx",
+ (long unsigned) upc_threadof (lock),
+ (long unsigned) upc_addrfield (lock));
+ if (lock == NULL)
+ gupcr_fatal_error ("NULL lock pointer");
+ /* Allocate space for the lock waiting queue link.
+ It will have affinity to the calling thread. */
+ link = gupcr_lock_link_alloc ();
+ if (link == NULL)
+ gupcr_fatal_error ("cannot allocate memory for the lock link");
+ link->next = NULL;
+ link->signal = 0;
+ /* Atomically set the lock value to point to the
+ calling thread's link queue object and
+ return the previous value of the lock link. */
+ lock_last_addr = &lock->last;
+ lock_last_thread = upc_threadof (lock_last_addr);
+ lock_last_offset = upc_addrfield (lock_last_addr);
+ gupcr_lock_swap (lock_last_thread, lock_last_offset,
+ &link, &old_link, sizeof (link));
+ if (old_link != NULL)
+ {
+ shared [] gupcr_lock_link_ref *old_link_next_addr;
+ size_t old_link_next_thread, old_link_next_offset;
+ /* We have to wait. Clear the ownership signal field
+ and insert our pointer into the predecessor's link. */
+ link->signal = 0;
+ upc_fence;
+ old_link_next_addr = &old_link->next;
+ old_link_next_thread = upc_threadof (old_link_next_addr);
+ old_link_next_offset = upc_addrfield (old_link_next_addr);
+ gupcr_lock_put (old_link_next_thread, old_link_next_offset,
+ &link, sizeof (link));
+ /* At this point the thread has to wait until the lock is
+ is released. Process counting events one by one until
+ the value of the signal word changes. */
+ do
+ {
+ gupcr_lock_wait ();
+ upc_fence;
+ }
+ while (!link->signal);
+ }
+ lock->owner_link = link;
+ gupcr_trace (FC_LOCK, "LOCK LOCK EXIT");
+ upc_fence;
+}
+
+/**
+ * Attempt to set the state of the lock to locked.
+ *
+ * The 'upc_lock_attempt' function attempts to set the state of the lock
+ * pointed to by 'lock' to 'locked'. If the lock is already in the 'locked'
+ * state due to the calling thread setting it to the 'locked' state, the
+ * result is undefined. If the lock is already in the 'locked' state, the
+ * function returns 0. If the lock is in the state 'unlocked',
+ * a single calling thread sets the state to 'locked' and the function
+ * returns 1. A null strict access is implied after a call to
+ * 'upc_lock_attempt' that returns 1.
+ *
+ * @param [in] lock Pointer to a lock
+ * @retval Lock attempt result
+ * - 1, lock was acquired successfully
+ * - 0, lock was not acquired
+ */
+int
+upc_lock_attempt (upc_lock_t *lock)
+{
+ gupcr_lock_link_ref link;
+ gupcr_lock_link_ref null_link = NULL;
+ shared [] gupcr_lock_link_ref *lock_last_addr;
+ size_t lock_last_thread, lock_last_offset;
+ int compare_ok;
+ gupcr_trace (FC_LOCK, "LOCK ATTEMPT ENTER %lu:0x%lx",
+ (long unsigned) upc_threadof (lock),
+ (long unsigned) upc_addrfield (lock));
+ if (lock == NULL)
+ gupcr_fatal_error ("NULL lock pointer");
+ /* Allocate space for the lock waiting queue with affinity
+ to the calling thread. */
+ link = gupcr_lock_link_alloc ();
+ if (link == NULL)
+ gupcr_fatal_error ("cannot allocate memory for the lock link");
+ link->next = NULL;
+ link->signal = 0;
+ /* Atomically set the lock value to the link entry and
+ return the previous value of the lock ONLY if the value
+ of the lock is already NULL. */
+ lock_last_addr = &lock->last;
+ lock_last_thread = upc_threadof (lock_last_addr);
+ lock_last_offset = upc_addrfield (lock_last_addr);
+ compare_ok = gupcr_lock_cswap (lock_last_thread, lock_last_offset,
+ &null_link, &link, sizeof (link));
+ if (compare_ok)
+ {
+ lock->owner_link = link;
+ upc_fence;
+ gupcr_trace (FC_LOCK, "LOCK ATTEMPT EXIT 1");
+ }
+ else
+ {
+ gupcr_lock_link_free (link);
+ gupcr_trace (FC_LOCK, "LOCK ATTEMPT EXIT 0");
+ }
+ return compare_ok;
+}
+
+/**
+ * Set the state of the lock to unlocked.
+ *
+ * The 'upc_unlock' function sets the state of the lock pointed
+ * to by 'lock' to 'unlocked'. Unless the lock is in 'locked' state
+ * and the calling thread is the locking thread, the result is undefined.
+ * A null strict access is implied before a call to 'upc_unlock'.
+ *
+ * @param [in] lock Pointer to a lock
+ */
+void
+upc_unlock (upc_lock_t *lock)
+{
+ gupcr_lock_link_ref link = lock->owner_link;
+ gupcr_lock_link_ref null_link = NULL;
+ shared [] gupcr_lock_link_ref *lock_last_addr = &lock->last;
+ size_t lock_last_thread = upc_threadof (lock_last_addr);
+ size_t lock_last_offset = upc_addrfield (lock_last_addr);
+ int compare_ok;
+ gupcr_trace (FC_LOCK, "LOCK UNLOCK ENTER %lu:0x%lx",
+ (long unsigned) upc_threadof (lock),
+ (long unsigned) upc_addrfield (lock));
+ if (lock == NULL)
+ gupcr_fatal_error ("NULL lock pointer");
+ upc_fence;
+ /* Try to release the lock: write NULL into lock->last
+ if it contains a pointer to our own link block. If it fails then
+ some other thread is on the waiting list. */
+ lock->owner_link = NULL;
+ compare_ok = gupcr_lock_cswap (lock_last_thread, lock_last_offset,
+ &link, &null_link, sizeof (link));
+ if (!compare_ok)
+ {
+ shared void *link_next_signal_addr;
+ size_t signal_addr_thread, signal_addr_offset;
+ int signal = 1;
+ /* Pass ownership to the next waiting thread. Wait until
+ the link->next pointer is being set. Use Portals call to
+ avoid possibility of data tearing on pointer to shared. */
+ for (;;)
+ {
+ size_t addr_offset;
+ gupcr_lock_link_ref val;
+ addr_offset = upc_addrfield (&link->next);
+ gupcr_lock_get (MYTHREAD, addr_offset, &val, sizeof (val));
+ if (val) break;
+ gupcr_lock_wait ();
+ }
+ /* Signal the waiting thread that it now owns the lock. */
+ link_next_signal_addr = &link->next->signal;
+ signal_addr_thread = upc_threadof (link_next_signal_addr);
+ signal_addr_offset = upc_addrfield (link_next_signal_addr);
+ gupcr_lock_put (signal_addr_thread, signal_addr_offset,
+ &signal, sizeof (signal));
+ }
+ gupcr_lock_link_free (link);
+ gupcr_trace (FC_LOCK, "LOCK UNLOCK EXIT");
+}
+
+/** @} */