vmm: Moves gpci into guest_thread
[akaros.git] / user / parlib / mutex.c
index 00838ba..c391c03 100644 (file)
@@ -8,8 +8,51 @@
 #include <parlib/uthread.h>
 #include <sys/queue.h>
 #include <parlib/spinlock.h>
+#include <parlib/alarm.h>
+#include <parlib/assert.h>
 #include <malloc.h>
 
+struct timeout_blob {
+       bool                                            timed_out;
+       struct uthread                          *uth;
+       uth_sync_t                                      *sync_ptr;
+       struct spin_pdr_lock            *lock_ptr;
+};
+
+/* When sync primitives want to time out, they can use this alarm handler.  It
+ * needs a timeout_blob, which is independent of any particular sync method. */
+static void timeout_handler(struct alarm_waiter *waiter)
+{
+       struct timeout_blob *blob = (struct timeout_blob*)waiter->data;
+
+       spin_pdr_lock(blob->lock_ptr);
+       if (__uth_sync_get_uth(blob->sync_ptr, blob->uth))
+               blob->timed_out = TRUE;
+       spin_pdr_unlock(blob->lock_ptr);
+       if (blob->timed_out)
+               uthread_runnable(blob->uth);
+}
+
+/* Minor helper, sets a blob's fields */
+static void set_timeout_blob(struct timeout_blob *blob, uth_sync_t *sync_ptr,
+                             struct spin_pdr_lock *lock_ptr)
+{
+       blob->timed_out = FALSE;
+       blob->uth = current_uthread;
+       blob->sync_ptr = sync_ptr;
+       blob->lock_ptr = lock_ptr;
+}
+
+/* Minor helper, sets an alarm for blob and a timespec */
+static void set_timeout_alarm(struct alarm_waiter *waiter,
+                              struct timeout_blob *blob,
+                              const struct timespec *abs_timeout)
+{
+       init_awaiter(waiter, timeout_handler);
+       waiter->data = blob;
+       set_awaiter_abs_unix(waiter, timespec_to_alarm_time(abs_timeout));
+       set_alarm(waiter);
+}
 
 /************** Semaphores and Mutexes **************/
 
@@ -18,7 +61,7 @@ static void __uth_semaphore_init(void *arg)
        struct uth_semaphore *sem = (struct uth_semaphore*)arg;
 
        spin_pdr_init(&sem->lock);
-       sem->sync_obj = __uth_sync_alloc();
+       __uth_sync_init(&sem->sync_obj);
        /* If we used a static initializer for a semaphore, count is already set.
         * o/w it will be set by _alloc() or _init() (via uth_semaphore_init()). */
 }
@@ -36,7 +79,7 @@ void uth_semaphore_init(uth_semaphore_t *sem, unsigned int count)
 /* Undoes whatever was done in init. */
 void uth_semaphore_destroy(uth_semaphore_t *sem)
 {
-       __uth_sync_free(sem->sync_obj);
+       __uth_sync_destroy(&sem->sync_obj);
 }
 
 uth_semaphore_t *uth_semaphore_alloc(unsigned int count)
@@ -65,12 +108,18 @@ static void __semaphore_cb(struct uthread *uth, void *arg)
         *
         * Also note the lock-ordering rule.  The sem lock is grabbed before any
         * locks the 2LS might grab. */
-       uthread_has_blocked(uth, sem->sync_obj, UTH_EXT_BLK_MUTEX);
+       uthread_has_blocked(uth, UTH_EXT_BLK_MUTEX);
+       __uth_sync_enqueue(uth, &sem->sync_obj);
        spin_pdr_unlock(&sem->lock);
 }
 
-void uth_semaphore_down(uth_semaphore_t *sem)
+bool uth_semaphore_timed_down(uth_semaphore_t *sem,
+                              const struct timespec *abs_timeout)
 {
+       struct alarm_waiter waiter[1];
+       struct timeout_blob blob[1];
+
+       assert_can_block();
        parlib_run_once(&sem->once_ctl, __uth_semaphore_init, sem);
        spin_pdr_lock(&sem->lock);
        if (sem->count > 0) {
@@ -79,18 +128,35 @@ void uth_semaphore_down(uth_semaphore_t *sem)
                 * our timeout function works for sems and CVs. */
                sem->count--;
                spin_pdr_unlock(&sem->lock);
-               return;
+               return TRUE;
+       }
+       if (abs_timeout) {
+               set_timeout_blob(blob, &sem->sync_obj, &sem->lock);
+               set_timeout_alarm(waiter, blob, abs_timeout);
        }
        /* the unlock and sync enqueuing is done in the yield callback.  as always,
         * we need to do this part in vcore context, since as soon as we unlock the
         * uthread could restart.  (atomically yield and unlock). */
        uthread_yield(TRUE, __semaphore_cb, sem);
+       if (abs_timeout) {
+               /* We're guaranteed the alarm will either be cancelled or the handler
+                * complete when unset_alarm() returns. */
+               unset_alarm(waiter);
+               return blob->timed_out ? FALSE : TRUE;
+       }
+       return TRUE;
+}
+
+void uth_semaphore_down(uth_semaphore_t *sem)
+{
+       uth_semaphore_timed_down(sem, NULL);
 }
 
 bool uth_semaphore_trydown(uth_semaphore_t *sem)
 {
        bool ret = FALSE;
 
+       assert_can_block();
        parlib_run_once(&sem->once_ctl, __uth_semaphore_init, sem);
        spin_pdr_lock(&sem->lock);
        if (sem->count > 0) {
@@ -108,7 +174,7 @@ void uth_semaphore_up(uth_semaphore_t *sem)
        /* once-ing the 'up', unlike mtxs 'unlock', since sems can be special. */
        parlib_run_once(&sem->once_ctl, __uth_semaphore_init, sem);
        spin_pdr_lock(&sem->lock);
-       uth = __uth_sync_get_next(sem->sync_obj);
+       uth = __uth_sync_get_next(&sem->sync_obj);
        /* If there was a waiter, we pass our resource/count to them. */
        if (!uth)
                sem->count++;
@@ -155,6 +221,12 @@ void uth_mutex_free(uth_mutex_t *mtx)
        uth_semaphore_free(mtx);
 }
 
+bool uth_mutex_timed_lock(uth_mutex_t *mtx, const struct timespec *abs_timeout)
+{
+       parlib_run_once(&mtx->once_ctl, __uth_mutex_init, mtx);
+       return uth_semaphore_timed_down(mtx, abs_timeout);
+}
+
 void uth_mutex_lock(uth_mutex_t *mtx)
 {
        parlib_run_once(&mtx->once_ctl, __uth_mutex_init, mtx);
@@ -213,11 +285,11 @@ void uth_recurse_mutex_free(uth_recurse_mutex_t *r_mtx)
        free(r_mtx);
 }
 
-void uth_recurse_mutex_lock(uth_recurse_mutex_t *r_mtx)
+bool uth_recurse_mutex_timed_lock(uth_recurse_mutex_t *r_mtx,
+                                  const struct timespec *abs_timeout)
 {
+       assert_can_block();
        parlib_run_once(&r_mtx->once_ctl, __uth_recurse_mutex_init, r_mtx);
-       assert(!in_vcore_context());
-       assert(current_uthread);
        /* We don't have to worry about races on current_uthread or count.  They are
         * only written by the initial lockholder, and this check will only be true
         * for the initial lockholder, which cannot concurrently call this function
@@ -228,20 +300,26 @@ void uth_recurse_mutex_lock(uth_recurse_mutex_t *r_mtx)
         * we'll have to disable notifs temporarily. */
        if (r_mtx->lockholder == current_uthread) {
                r_mtx->count++;
-               return;
+               return TRUE;
        }
-       uth_mutex_lock(&r_mtx->mtx);
+       if (!uth_mutex_timed_lock(&r_mtx->mtx, abs_timeout))
+               return FALSE;
        r_mtx->lockholder = current_uthread;
        r_mtx->count = 1;
+       return TRUE;
+}
+
+void uth_recurse_mutex_lock(uth_recurse_mutex_t *r_mtx)
+{
+       uth_recurse_mutex_timed_lock(r_mtx, NULL);
 }
 
 bool uth_recurse_mutex_trylock(uth_recurse_mutex_t *r_mtx)
 {
        bool ret;
 
+       assert_can_block();
        parlib_run_once(&r_mtx->once_ctl, __uth_recurse_mutex_init, r_mtx);
-       assert(!in_vcore_context());
-       assert(current_uthread);
        if (r_mtx->lockholder == current_uthread) {
                r_mtx->count++;
                return TRUE;
@@ -272,7 +350,7 @@ static void __uth_cond_var_init(void *arg)
        struct uth_cond_var *cv = (struct uth_cond_var*)arg;
 
        spin_pdr_init(&cv->lock);
-       cv->sync_obj = __uth_sync_alloc();
+       __uth_sync_init(&cv->sync_obj);
 }
 
 void uth_cond_var_init(uth_cond_var_t *cv)
@@ -283,7 +361,7 @@ void uth_cond_var_init(uth_cond_var_t *cv)
 
 void uth_cond_var_destroy(uth_cond_var_t *cv)
 {
-       __uth_sync_free(cv->sync_obj);
+       __uth_sync_destroy(&cv->sync_obj);
 }
 
 uth_cond_var_t *uth_cond_var_alloc(void)
@@ -319,7 +397,8 @@ static void __cv_wait_cb(struct uthread *uth, void *arg)
         *
         * Also note the lock-ordering rule.  The cv lock is grabbed before any
         * locks the 2LS might grab. */
-       uthread_has_blocked(uth, cv->sync_obj, UTH_EXT_BLK_MUTEX);
+       uthread_has_blocked(uth, UTH_EXT_BLK_MUTEX);
+       __uth_sync_enqueue(uth, &cv->sync_obj);
        spin_pdr_unlock(&cv->lock);
        /* This looks dangerous, since both the CV and MTX could use the
         * uth->sync_next TAILQ_ENTRY (or whatever the 2LS uses), but the uthread
@@ -387,16 +466,68 @@ static void __cv_wait_cb(struct uthread *uth, void *arg)
  *
  * Also note that we use the external API for the mutex operations.  A 2LS could
  * have their own mutex ops but still use the generic cv ops. */
-void uth_cond_var_wait(uth_cond_var_t *cv, uth_mutex_t *mtx)
+bool uth_cond_var_timed_wait(uth_cond_var_t *cv, uth_mutex_t *mtx,
+                             const struct timespec *abs_timeout)
 {
        struct uth_cv_link link;
+       struct alarm_waiter waiter[1];
+       struct timeout_blob blob[1];
+       bool ret = TRUE;
 
+       assert_can_block();
        parlib_run_once(&cv->once_ctl, __uth_cond_var_init, cv);
        link.cv = cv;
        link.mtx = mtx;
        spin_pdr_lock(&cv->lock);
+       if (abs_timeout) {
+               set_timeout_blob(blob, &cv->sync_obj, &cv->lock);
+               set_timeout_alarm(waiter, blob, abs_timeout);
+       }
        uthread_yield(TRUE, __cv_wait_cb, &link);
+       if (abs_timeout) {
+               unset_alarm(waiter);
+               ret = blob->timed_out ? FALSE : TRUE;
+       }
        uth_mutex_lock(mtx);
+       return ret;
+}
+
+void uth_cond_var_wait(uth_cond_var_t *cv, uth_mutex_t *mtx)
+{
+       uth_cond_var_timed_wait(cv, mtx, NULL);
+}
+
+/* GCC doesn't list this as one of the C++0x functions, but it's easy to do and
+ * implement uth_cond_var_wait_recurse() with it, just like for all the other
+ * 'timed' functions.
+ *
+ * Note the timeout applies to getting the signal on the CV, not on reacquiring
+ * the mutex. */
+bool uth_cond_var_timed_wait_recurse(uth_cond_var_t *cv,
+                                     uth_recurse_mutex_t *r_mtx,
+                                     const struct timespec *abs_timeout)
+{
+       unsigned int old_count = r_mtx->count;
+       bool ret;
+
+       /* In cond_wait, we're going to unlock the internal mutex.  We'll do the
+        * prep-work for that now.  (invariant is that an unlocked r_mtx has no
+        * lockholder and count == 0. */
+       r_mtx->lockholder = NULL;
+       r_mtx->count = 0;
+       ret = uth_cond_var_timed_wait(cv, &r_mtx->mtx, abs_timeout);
+       /* Now we hold the internal mutex again.  Need to restore the tracking. */
+       r_mtx->lockholder = current_uthread;
+       r_mtx->count = old_count;
+       return ret;
+}
+
+/* GCC wants this function, though its semantics are a little unclear.  I
+ * imagine you'd want to completely unlock it (say you locked it 3 times), and
+ * when you get it back, that you have your three locks back. */
+void uth_cond_var_wait_recurse(uth_cond_var_t *cv, uth_recurse_mutex_t *r_mtx)
+{
+       uth_cond_var_timed_wait_recurse(cv, r_mtx, NULL);
 }
 
 void uth_cond_var_signal(uth_cond_var_t *cv)
@@ -405,7 +536,7 @@ void uth_cond_var_signal(uth_cond_var_t *cv)
 
        parlib_run_once(&cv->once_ctl, __uth_cond_var_init, cv);
        spin_pdr_lock(&cv->lock);
-       uth = __uth_sync_get_next(cv->sync_obj);
+       uth = __uth_sync_get_next(&cv->sync_obj);
        spin_pdr_unlock(&cv->lock);
        if (uth)
                uthread_runnable(uth);
@@ -413,19 +544,189 @@ void uth_cond_var_signal(uth_cond_var_t *cv)
 
 void uth_cond_var_broadcast(uth_cond_var_t *cv)
 {
-       struct uth_tailq restartees = TAILQ_HEAD_INITIALIZER(restartees);
-       struct uthread *i, *safe;
+       uth_sync_t restartees;
 
        parlib_run_once(&cv->once_ctl, __uth_cond_var_init, cv);
        spin_pdr_lock(&cv->lock);
-       /* If this turns out to be slow or painful for 2LSs, we can implement a
-        * get_all or something (default used to use TAILQ_SWAP). */
-       while ((i = __uth_sync_get_next(cv->sync_obj))) {
-               /* Once the uth is out of the sync obj, we can reuse sync_next. */
-               TAILQ_INSERT_TAIL(&restartees, i, sync_next);
+       if (__uth_sync_is_empty(&cv->sync_obj)) {
+               spin_pdr_unlock(&cv->lock);
+               return;
        }
+       __uth_sync_init(&restartees);
+       __uth_sync_swap(&restartees, &cv->sync_obj);
        spin_pdr_unlock(&cv->lock);
-       /* Need the SAFE, since we can't touch the linkage once the uth could run */
+       __uth_sync_wake_all(&restartees);
+}
+
+
+/************** Reader-writer Sleeping Locks **************/
+
+
+static void __uth_rwlock_init(void *arg)
+{
+       struct uth_rwlock *rwl = (struct uth_rwlock*)arg;
+
+       spin_pdr_init(&rwl->lock);
+       rwl->nr_readers = 0;
+       rwl->has_writer = FALSE;
+       __uth_sync_init(&rwl->readers);
+       __uth_sync_init(&rwl->writers);
+}
+
+void uth_rwlock_init(uth_rwlock_t *rwl)
+{
+       __uth_rwlock_init(rwl);
+       parlib_set_ran_once(&rwl->once_ctl);
+}
+
+void uth_rwlock_destroy(uth_rwlock_t *rwl)
+{
+       __uth_sync_destroy(&rwl->readers);
+       __uth_sync_destroy(&rwl->writers);
+}
+
+uth_rwlock_t *uth_rwlock_alloc(void)
+{
+       struct uth_rwlock *rwl;
+
+       rwl = malloc(sizeof(struct uth_rwlock));
+       assert(rwl);
+       uth_rwlock_init(rwl);
+       return rwl;
+}
+
+void uth_rwlock_free(uth_rwlock_t *rwl)
+{
+       uth_rwlock_destroy(rwl);
+       free(rwl);
+}
+
+/* Readers and writers block until they have the lock.  The delicacies are dealt
+ * with by the unlocker. */
+static void __rwlock_rd_cb(struct uthread *uth, void *arg)
+{
+       struct uth_rwlock *rwl = (struct uth_rwlock*)arg;
+
+       uthread_has_blocked(uth, UTH_EXT_BLK_MUTEX);
+       __uth_sync_enqueue(uth, &rwl->readers);
+       spin_pdr_unlock(&rwl->lock);
+}
+
+void uth_rwlock_rdlock(uth_rwlock_t *rwl)
+{
+       assert_can_block();
+       parlib_run_once(&rwl->once_ctl, __uth_rwlock_init, rwl);
+       spin_pdr_lock(&rwl->lock);
+       /* Readers always make progress when there is no writer */
+       if (!rwl->has_writer) {
+               rwl->nr_readers++;
+               spin_pdr_unlock(&rwl->lock);
+               return;
+       }
+       uthread_yield(TRUE, __rwlock_rd_cb, rwl);
+}
+
+bool uth_rwlock_try_rdlock(uth_rwlock_t *rwl)
+{
+       bool ret = FALSE;
+
+       assert_can_block();
+       parlib_run_once(&rwl->once_ctl, __uth_rwlock_init, rwl);
+       spin_pdr_lock(&rwl->lock);
+       if (!rwl->has_writer) {
+               rwl->nr_readers++;
+               ret = TRUE;
+       }
+       spin_pdr_unlock(&rwl->lock);
+       return ret;
+}
+
+static void __rwlock_wr_cb(struct uthread *uth, void *arg)
+{
+       struct uth_rwlock *rwl = (struct uth_rwlock*)arg;
+
+       uthread_has_blocked(uth, UTH_EXT_BLK_MUTEX);
+       __uth_sync_enqueue(uth, &rwl->writers);
+       spin_pdr_unlock(&rwl->lock);
+}
+
+void uth_rwlock_wrlock(uth_rwlock_t *rwl)
+{
+       assert_can_block();
+       parlib_run_once(&rwl->once_ctl, __uth_rwlock_init, rwl);
+       spin_pdr_lock(&rwl->lock);
+       /* Writers require total mutual exclusion - no writers or readers */
+       if (!rwl->has_writer && !rwl->nr_readers) {
+               rwl->has_writer = TRUE;
+               spin_pdr_unlock(&rwl->lock);
+               return;
+       }
+       uthread_yield(TRUE, __rwlock_wr_cb, rwl);
+}
+
+bool uth_rwlock_try_wrlock(uth_rwlock_t *rwl)
+{
+       bool ret = FALSE;
+
+       assert_can_block();
+       parlib_run_once(&rwl->once_ctl, __uth_rwlock_init, rwl);
+       spin_pdr_lock(&rwl->lock);
+       if (!rwl->has_writer && !rwl->nr_readers) {
+               rwl->has_writer = TRUE;
+               ret = TRUE;
+       }
+       spin_pdr_unlock(&rwl->lock);
+       return ret;
+}
+
+/* Let's try to wake writers (yes, this is a policy decision), and if none, wake
+ * all the readers.  The invariant there is that if there is no writer, then
+ * there are no waiting readers. */
+static void __rw_unlock_writer(struct uth_rwlock *rwl,
+                               struct uth_tailq *restartees)
+{
+       struct uthread *uth;
+
+       uth = __uth_sync_get_next(&rwl->writers);
+       if (uth) {
+               TAILQ_INSERT_TAIL(restartees, uth, sync_next);
+       } else {
+               rwl->has_writer = FALSE;
+               while ((uth = __uth_sync_get_next(&rwl->readers))) {
+                       TAILQ_INSERT_TAIL(restartees, uth, sync_next);
+                       rwl->nr_readers++;
+               }
+       }
+}
+
+static void __rw_unlock_reader(struct uth_rwlock *rwl,
+                               struct uth_tailq *restartees)
+{
+       struct uthread *uth;
+
+       rwl->nr_readers--;
+       if (!rwl->nr_readers) {
+               uth = __uth_sync_get_next(&rwl->writers);
+               if (uth) {
+                       TAILQ_INSERT_TAIL(restartees, uth, sync_next);
+                       rwl->has_writer = TRUE;
+               }
+       }
+}
+
+/* Unlock works for either readers or writer locks.  You can tell which you were
+ * based on whether has_writer is set or not. */
+void uth_rwlock_unlock(uth_rwlock_t *rwl)
+{
+       struct uth_tailq restartees = TAILQ_HEAD_INITIALIZER(restartees);
+       struct uthread *i, *safe;
+
+       spin_pdr_lock(&rwl->lock);
+       if (rwl->has_writer)
+               __rw_unlock_writer(rwl, &restartees);
+       else
+               __rw_unlock_reader(rwl, &restartees);
+       spin_pdr_unlock(&rwl->lock);
        TAILQ_FOREACH_SAFE(i, &restartees, sync_next, safe)
                uthread_runnable(i);
 }
@@ -433,25 +734,29 @@ void uth_cond_var_broadcast(uth_cond_var_t *cv)
 
 /************** Default Sync Obj Implementation **************/
 
-static uth_sync_t uth_default_sync_alloc(void)
+static void uth_default_sync_init(uth_sync_t *sync)
 {
-       struct uth_tailq *tq;
+       struct uth_tailq *tq = (struct uth_tailq*)sync;
 
-       tq = malloc(sizeof(struct uth_tailq));
-       assert(tq);
+       parlib_static_assert(sizeof(struct uth_tailq) <= sizeof(uth_sync_t));
        TAILQ_INIT(tq);
-       return (uth_sync_t)tq;
 }
 
-static void uth_default_sync_free(uth_sync_t sync)
+static void uth_default_sync_destroy(uth_sync_t *sync)
 {
        struct uth_tailq *tq = (struct uth_tailq*)sync;
 
        assert(TAILQ_EMPTY(tq));
-       free(tq);
 }
 
-static struct uthread *uth_default_sync_get_next(uth_sync_t sync)
+static void uth_default_sync_enqueue(struct uthread *uth, uth_sync_t *sync)
+{
+       struct uth_tailq *tq = (struct uth_tailq*)sync;
+
+       TAILQ_INSERT_TAIL(tq, uth, sync_next);
+}
+
+static struct uthread *uth_default_sync_get_next(uth_sync_t *sync)
 {
        struct uth_tailq *tq = (struct uth_tailq*)sync;
        struct uthread *first;
@@ -462,7 +767,7 @@ static struct uthread *uth_default_sync_get_next(uth_sync_t sync)
        return first;
 }
 
-static bool uth_default_sync_get_uth(uth_sync_t sync, struct uthread *uth)
+static bool uth_default_sync_get_uth(uth_sync_t *sync, struct uthread *uth)
 {
        struct uth_tailq *tq = (struct uth_tailq*)sync;
        struct uthread *i;
@@ -476,36 +781,55 @@ static bool uth_default_sync_get_uth(uth_sync_t sync, struct uthread *uth)
        return FALSE;
 }
 
-/************** External uthread sync interface **************/
+static void uth_default_sync_swap(uth_sync_t *a, uth_sync_t *b)
+{
+       struct uth_tailq *tq_a = (struct uth_tailq*)a;
+       struct uth_tailq *tq_b = (struct uth_tailq*)b;
 
-/* Called by the 2LS->has_blocked op, if they are using the default sync.*/
-void __uth_default_sync_enqueue(struct uthread *uth, uth_sync_t sync)
+       TAILQ_SWAP(tq_a, tq_b, uthread, sync_next);
+}
+
+static bool uth_default_sync_is_empty(uth_sync_t *sync)
 {
        struct uth_tailq *tq = (struct uth_tailq*)sync;
 
-       TAILQ_INSERT_TAIL(tq, uth, sync_next);
+       return TAILQ_EMPTY(tq);
 }
 
-/* Called by 2LS-independent sync code when a sync object is created. */
-uth_sync_t __uth_sync_alloc(void)
+/************** External uthread sync interface **************/
+
+/* Called by 2LS-independent sync code when a sync object needs initialized. */
+void __uth_sync_init(uth_sync_t *sync)
 {
-       if (sched_ops->sync_alloc)
-               return sched_ops->sync_alloc();
-       return uth_default_sync_alloc();
+       if (sched_ops->sync_init) {
+               sched_ops->sync_init(sync);
+               return;
+       }
+       uth_default_sync_init(sync);
 }
 
 /* Called by 2LS-independent sync code when a sync object is destroyed. */
-void __uth_sync_free(uth_sync_t sync)
+void __uth_sync_destroy(uth_sync_t *sync)
+{
+       if (sched_ops->sync_destroy) {
+               sched_ops->sync_destroy(sync);
+               return;
+       }
+       uth_default_sync_destroy(sync);
+}
+
+/* Called by 2LS-independent sync code when a thread blocks on sync */
+void __uth_sync_enqueue(struct uthread *uth, uth_sync_t *sync)
 {
-       if (sched_ops->sync_free) {
-               sched_ops->sync_free(sync);
+       if (sched_ops->sync_enqueue) {
+               sched_ops->sync_enqueue(uth, sync);
                return;
        }
-       uth_default_sync_free(sync);
+       uth_default_sync_enqueue(uth, sync);
 }
 
 /* Called by 2LS-independent sync code when a thread needs to be woken. */
-struct uthread *__uth_sync_get_next(uth_sync_t sync)
+struct uthread *__uth_sync_get_next(uth_sync_t *sync)
 {
        if (sched_ops->sync_get_next)
                return sched_ops->sync_get_next(sync);
@@ -514,9 +838,42 @@ struct uthread *__uth_sync_get_next(uth_sync_t sync)
 
 /* Called by 2LS-independent sync code when a specific thread needs to be woken.
  * Returns TRUE if the uthread was blocked on the object, FALSE o/w. */
-bool __uth_sync_get_uth(uth_sync_t sync, struct uthread *uth)
+bool __uth_sync_get_uth(uth_sync_t *sync, struct uthread *uth)
 {
        if (sched_ops->sync_get_uth)
                return sched_ops->sync_get_uth(sync, uth);
        return uth_default_sync_get_uth(sync, uth);
 }
+
+/* Called by 2LS-independent sync code to swap members of sync objects. */
+void __uth_sync_swap(uth_sync_t *a, uth_sync_t *b)
+{
+       if (sched_ops->sync_swap) {
+               sched_ops->sync_swap(a, b);
+               return;
+       }
+       uth_default_sync_swap(a, b);
+}
+
+/* Called by 2LS-independent sync code */
+bool __uth_sync_is_empty(uth_sync_t *sync)
+{
+       if (sched_ops->sync_is_empty)
+               return sched_ops->sync_is_empty(sync);
+       return uth_default_sync_is_empty(sync);
+}
+
+/* Called by 2LS-independent sync code to wake up all uths on sync.  You should
+ * probably not hold locks while you do this - swap the items to a local sync
+ * object first. */
+void __uth_sync_wake_all(uth_sync_t *wakees)
+{
+       struct uthread *uth_i;
+
+       if (sched_ops->thread_bulk_runnable) {
+               sched_ops->thread_bulk_runnable(wakees);
+       } else {
+               while ((uth_i = __uth_sync_get_next(wakees)))
+                       uthread_runnable(uth_i);
+       }
+}