parlib: Add uthread_create()
[akaros.git] / user / parlib / uthread.c
index 135e506..70ef9b4 100644 (file)
@@ -1,20 +1,26 @@
+/* Copyright (c) 2011-2014 The Regents of the University of California
+ * Barret Rhoden <brho@cs.berkeley.edu>
+ * See LICENSE for details. */
+
 #include <ros/arch/membar.h>
-#include <arch/atomic.h>
-#include <parlib.h>
-#include <vcore.h>
-#include <uthread.h>
-#include <event.h>
+#include <parlib/arch/atomic.h>
+#include <parlib/parlib.h>
+#include <parlib/vcore.h>
+#include <parlib/uthread.h>
+#include <parlib/event.h>
 #include <stdlib.h>
+#include <parlib/assert.h>
+#include <parlib/arch/trap.h>
 
-/* Which operations we'll call for the 2LS.  Will change a bit with Lithe.  For
- * now, there are no defaults.  2LSs can override sched_ops. */
-struct schedule_ops default_2ls_ops = {0};
-struct schedule_ops *sched_ops __attribute__((weak)) = &default_2ls_ops;
+/* SCPs have a default 2LS that only manages thread 0.  Any other 2LS, such as
+ * pthreads, should override sched_ops in its init code. */
+extern struct schedule_ops thread0_2ls_ops;
+struct schedule_ops *sched_ops = &thread0_2ls_ops;
 
 __thread struct uthread *current_uthread = 0;
 /* ev_q for all preempt messages (handled here to keep 2LSs from worrying
  * extensively about the details.  Will call out when necessary. */
-struct event_queue *preempt_ev_q;
+static struct event_queue *preempt_ev_q;
 
 /* Helpers: */
 #define UTH_TLSDESC_NOTLS (void*)(-1)
@@ -28,75 +34,96 @@ static void handle_vc_preempt(struct event_msg *ev_msg, unsigned int ev_type,
                               void *data);
 static void handle_vc_indir(struct event_msg *ev_msg, unsigned int ev_type,
                             void *data);
+static void __ros_uth_syscall_blockon(struct syscall *sysc);
 
-/* Block the calling uthread on sysc until it makes progress or is done */
-static void __ros_mcp_syscall_blockon(struct syscall *sysc);
-
-/* Helper, make the uthread code manage thread0.  This sets up uthread such
- * that the calling code and its TLS are tracked by the uthread struct, and
- * vcore0 thinks the uthread is running there.  Called only by slim_init (early
- * _S code) and lib_init.
- *
- * Whether or not uthreads have TLS, thread0 has TLS, given to it by glibc.
- * This TLS will get set whenever we use thread0, regardless of whether or not
- * we use TLS for uthreads in general.  glibc cares about this TLS and will use
- * it at exit.  We can't simply use that TLS for VC0 either, since we don't know
- * where thread0 will be running when the program ends. */
-static void uthread_manage_thread0(struct uthread *uthread)
+/* Helper, initializes a fresh uthread to be thread0. */
+static void uthread_init_thread0(struct uthread *uthread)
 {
        assert(uthread);
        /* Save a pointer to thread0's tls region (the glibc one) into its tcb */
-       uthread->tls_desc = get_tls_desc(0);
+       uthread->tls_desc = get_tls_desc();
        /* Save a pointer to the uthread in its own TLS */
        current_uthread = uthread;
        /* Thread is currently running (it is 'us') */
        uthread->state = UT_RUNNING;
+       /* Thread is detached */
+       atomic_set(&uthread->join_ctl.state, UTH_JOIN_DETACHED);
+       /* Reset the signal state */
+       uthread->sigstate.mask = 0;
+       __sigemptyset(&uthread->sigstate.pending);
+       uthread->sigstate.data = NULL;
        /* utf/as doesn't represent the state of the uthread (we are running) */
        uthread->flags &= ~(UTHREAD_SAVED | UTHREAD_FPSAVED);
        /* need to track thread0 for TLS deallocation */
        uthread->flags |= UTHREAD_IS_THREAD0;
        uthread->notif_disabled_depth = 0;
-       /* Change temporarily to vcore0s tls region so we can save the newly created
-        * tcb into its current_uthread variable and then restore it.  One minor
-        * issue is that vcore0's transition-TLS isn't TLS_INITed yet.  Until it is
-        * (right before vcore_entry(), don't try and take the address of any of
-        * its TLS vars. */
-       set_tls_desc(get_vcpd_tls_desc(0), 0);
+       /* setting the uthread's TLS var.  this is idempotent for SCPs (us) */
+       __vcoreid = 0;
+}
+
+/* Helper, makes VC ctx tracks uthread as its current_uthread in its TLS.
+ *
+ * Whether or not uthreads have TLS, thread0 has TLS, given to it by glibc.
+ * This TLS will get set whenever we use thread0, regardless of whether or not
+ * we use TLS for uthreads in general.  glibc cares about this TLS and will use
+ * it at exit.  We can't simply use that TLS for VC0 either, since we don't know
+ * where thread0 will be running when the program ends. */
+static void uthread_track_thread0(struct uthread *uthread)
+{
+       set_tls_desc(get_vcpd_tls_desc(0));
        begin_safe_access_tls_vars();
-       /* We might have a basic uthread already installed (from slim_init), so
+       /* We might have a basic uthread already installed (from a prior call), so
         * free it before installing the new one. */
        if (current_uthread)
                free(current_uthread);
        current_uthread = uthread;
+       /* We may not be an MCP at this point (and thus not really working with
+        * vcores), but there is still the notion of something vcore_context-like
+        * even when running as an SCP (i.e. its more of a scheduler_context than a
+        * vcore_context).  Threfore we need to set __vcore_context to TRUE here to
+        * represent this (otherwise we will hit some asserts of not being in
+        * vcore_context when running in scheduler_context for the SCP. */
+       __vcore_context = TRUE;
        end_safe_access_tls_vars();
-       set_tls_desc(uthread->tls_desc, 0);
-       begin_safe_access_tls_vars();
-       __vcoreid = 0;  /* setting the uthread's TLS var */
-       assert(!in_vcore_context());
-       end_safe_access_tls_vars();
+       set_tls_desc(uthread->tls_desc);
 }
 
-/* The real 2LS calls this, passing in a uthread representing thread0.  When it
+/* The real 2LS calls this to transition us into mcp mode.  When it
  * returns, you're in _M mode, still running thread0, on vcore0 */
-void uthread_lib_init(struct uthread *uthread)
+void uthread_mcp_init()
 {
-       init_once_racy(return);
-       vcore_init();
-       uthread_manage_thread0(uthread);
-       register_ev_handler(EV_EVENT, handle_ev_ev, 0);
+       /* Prevent this from happening more than once. */
+       parlib_init_once_racy(return);
+
+       /* Doing this after the init_once check, since we don't want to let the
+        * process/2LS change their mind about being an MCP or not once they have
+        * multiple threads.
+        *
+        * The reason is that once you set "MCP please" on, you could get
+        * interrupted into VC ctx, say for a syscall completion, and then make
+        * decisions based on the fact that you're an MCP (e.g., unblocking a
+        * uthread, asking for vcores, etc), even though you are not an MCP.
+        * Arguably, these things could happen for signals too, but all of this is
+        * less likely than if we have multiple threads.
+        *
+        * Also, we could just abort here, since they shouldn't be calling
+        * mcp_init() if they don't want to be an MCP. */
+       if (!parlib_wants_to_be_mcp)
+               return;
+
        /* Receive preemption events.  Note that this merely tells the kernel how to
         * send the messages, and does not necessarily provide storage space for the
         * messages.  What we're doing is saying that all PREEMPT and CHECK_MSGS
         * events should be spammed to vcores that are running, preferring whatever
         * the kernel thinks is appropriate.  And IPI them.
         *
-        * It is critical that these are either SPAM_PUB or INDIR|FALLBACK, so that
-        * yielding vcores do not miss the preemption messages. */
+        * It is critical that these are either SPAM_PUB or INDIR|SPAM_INDIR, so
+        * that yielding vcores do not miss the preemption messages. */
        register_ev_handler(EV_VCORE_PREEMPT, handle_vc_preempt, 0);
        register_ev_handler(EV_CHECK_MSGS, handle_vc_indir, 0);
-       preempt_ev_q = get_event_q();   /* small ev_q, mostly a vehicle for flags */
+       preempt_ev_q = get_eventq_slim();       /* small ev_q, mostly a vehicle for flags */
        preempt_ev_q->ev_flags = EVENT_IPI | EVENT_SPAM_PUBLIC | EVENT_VCORE_APPRO |
-                                                        EVENT_VCORE_MUST_RUN;
+                                                        EVENT_VCORE_MUST_RUN | EVENT_WAKEUP;
        /* Tell the kernel to use the ev_q (it's settings) for the two types.  Note
         * that we still have two separate handlers.  We just want the events
         * delivered in the same way.  If we ever want to have a big_event_q with
@@ -109,6 +136,58 @@ void uthread_lib_init(struct uthread *uthread)
        vcore_change_to_m();
 }
 
+/* The real 2LS calls this, passing in a uthread representing thread0, its 2LS
+ * ops, and its syscall handling routine.  (NULL is fine).
+ *
+ * When we're called, thread0 has it's handler installed.  We need to remove it
+ * and install our own (if they want one).  All of the actual changes  must be
+ * done atomically with respect to syscalls (i.e., no syscalls in between).  If
+ * the user did something like have an outstanding async syscall while we're in
+ * here, then they'll crash hard. */
+void uthread_2ls_init(struct uthread *uthread, struct schedule_ops *ops,
+                      void (*handle_sysc)(struct event_msg *, unsigned int,
+                                          void *),
+                      void *data)
+{
+       struct ev_handler *old_h, *new_h = NULL;
+
+       if (handle_sysc) {
+               new_h = malloc(sizeof(struct ev_handler));
+               assert(new_h);
+               new_h->func = handle_sysc;
+               new_h->data = data;
+               new_h->next = NULL;
+       }
+       uthread_init_thread0(uthread);
+       /* We need to *atomically* change the current_uthread and the schedule_ops
+        * to the new 2LSs thread0 and ops, such that there is no moment when only
+        * one is changed and that we call a sched_ops.  There are sources of
+        * implicit calls to sched_ops.  Two big ones are sched_entry, called
+        * whenever we receive a notif (so we need to disable notifs), and
+        * syscall_blockon, called whenver we had a syscall that blocked (so we say
+        * tell the *uthread* that *it* is in vc ctx (TLS var).
+        *
+        * When disabling notifs, don't use a helper.  We're changing
+        * current_uthread under the hood, which messes with the helpers.  When
+        * setting __vcore_context, we're in thread0's TLS.  Even when we change
+        * current_uthread, we're still in the *same* TLS. */
+       __disable_notifs(0);
+       __vcore_context = TRUE;
+       cmb();
+       /* Under the hood, this function will free any previously allocated uthread
+        * structs representing thread0 (e.g. the one set up by uthread_lib_init()
+        * previously). */
+       uthread_track_thread0(uthread);
+       sched_ops = ops;
+       /* Racy, but we shouldn't be concurrent */
+       old_h = ev_handlers[EV_SYSCALL];
+       ev_handlers[EV_SYSCALL] = new_h;
+       cmb();
+       __vcore_context = FALSE;
+       enable_notifs(0);       /* will trigger a self_notif if we missed a notif */
+       free(old_h);
+}
+
 /* Helper: tells the kernel our SCP is capable of going into vcore context on
  * vcore 0.  Pairs with k/s/process.c scp_is_vcctx_ready(). */
 static void scp_vcctx_ready(void)
@@ -145,30 +224,56 @@ static char *__ros_errstr_loc(void)
                return current_uthread->err_str;
 }
 
-/* Slim-init - sets up basic uthreading for when we are in _S mode and before
- * we set up the 2LS.  Some apps may not have a 2LS and thus never do the full
+/* Sets up basic uthreading for when we are in _S mode and before we set up the
+ * 2LS.  Some apps may not have a 2LS and thus never do the full
  * vcore/2LS/uthread init. */
-void uthread_slim_init(void)
+void __attribute__((constructor)) uthread_lib_init(void)
 {
-       struct uthread *uthread;
-       int ret = posix_memalign((void**)&uthread, __alignof__(struct uthread),
-                                sizeof(struct uthread));
+       /* Use the thread0 sched's uth */
+       extern struct uthread *thread0_uth;
+       extern void thread0_lib_init(void);
+       extern void thread0_handle_syscall(struct event_msg *ev_msg,
+                                          unsigned int ev_type, void *data);
+       int ret;
+
+       /* Surprise!  Parlib's ctors also run in shared objects.  We can't have
+        * multiple versions of parlib (with multiple data structures). */
+       if (__in_fake_parlib())
+               return;
+       /* Only run once, but make sure that vcore_lib_init() has run already. */
+       parlib_init_once_racy(return);
+       vcore_lib_init();
+
+       ret = posix_memalign((void**)&thread0_uth, __alignof__(struct uthread),
+                            sizeof(struct uthread));
        assert(!ret);
-       memset(uthread, 0, sizeof(struct uthread));     /* aggressively 0 for bugs */
-       /* TODO: consider a vcore_init_vc0 call. */
-       vcore_init();
-       uthread_manage_thread0(uthread);
-       scp_vcctx_ready();
-       init_posix_signals();
-       /* change our blockon from glibc's internal one to the mcp one (which can
-        * handle SCPs too).  we must do this before switching to _M, or at least
-        * before blocking while an _M.  it's harmless (and probably saner) to do it
-        * earlier, so we do it as early as possible. */
-       ros_syscall_blockon = __ros_mcp_syscall_blockon;
+       memset(thread0_uth, 0, sizeof(struct uthread)); /* aggressively 0 for bugs*/
+       /* Need to do thread0_lib_init() first, since it sets up evq's referred to
+        * in thread0_handle_syscall(). */
+       thread0_lib_init();
+       uthread_2ls_init(thread0_uth, &thread0_2ls_ops, thread0_handle_syscall,
+                        NULL);
        /* Switch our errno/errstr functions to be uthread-aware.  See glibc's
         * errno.c for more info. */
        ros_errno_loc = __ros_errno_loc;
        ros_errstr_loc = __ros_errstr_loc;
+       register_ev_handler(EV_EVENT, handle_ev_ev, 0);
+       /* Now that we're ready (I hope) to operate as an MCP, we tell the kernel.
+        * We must set vcctx and blockon atomically with respect to syscalls,
+        * meaning no syscalls in between. */
+       cmb();
+       scp_vcctx_ready();
+       /* Change our blockon from glibc's internal one to the regular one, which
+        * uses vcore context and works for SCPs (with or without 2LS) and MCPs.
+        * Once we tell the kernel we are ready to utilize vcore context, we need
+        * our blocking syscalls to utilize it as well. */
+       ros_syscall_blockon = __ros_uth_syscall_blockon;
+       cmb();
+       init_posix_signals();
+       /* Accept diagnostic events.  Other parts of the program/libraries can
+        * register handlers to run.  You can kick these with "notify PID 9". */
+       enable_kevent(EV_FREE_APPLE_PIE, 0, EVENT_IPI | EVENT_WAKEUP |
+                                           EVENT_SPAM_PUBLIC);
 }
 
 /* 2LSs shouldn't call uthread_vcore_entry directly */
@@ -205,17 +310,8 @@ void __attribute__((noreturn)) uthread_vcore_entry(void)
        handle_events(vcoreid);
        __check_preempt_pending(vcoreid);
        assert(in_vcore_context());     /* double check, in case an event changed it */
-       /* Consider using the default_2ls_op for this, though it's a bit weird. */
-       if (sched_ops->sched_entry) {
-               sched_ops->sched_entry();
-       } else if (current_uthread) {
-               run_current_uthread();
-       }
-       /* 2LS sched_entry should never return */
-       /* Either the 2LS sched_entry returned, run_cur_uth() returned, or we
-        * didn't have a current_uthread.  If we didn't have a 2LS op, we should be
-        * in _S mode and always have a current_uthread. */
-       assert(0);
+       sched_ops->sched_entry();
+       assert(0); /* 2LS sched_entry should never return */
 }
 
 /* Does the uthread initialization of a uthread that the caller created.  Call
@@ -225,6 +321,10 @@ void uthread_init(struct uthread *new_thread, struct uth_thread_attr *attr)
        int ret;
        assert(new_thread);
        new_thread->state = UT_NOT_RUNNING;
+       /* Set the signal state. */
+       new_thread->sigstate.mask = current_uthread->sigstate.mask;
+       __sigemptyset(&new_thread->sigstate.pending);
+       new_thread->sigstate.data = NULL;
        /* They should have zero'd the uthread.  Let's check critical things: */
        assert(!new_thread->flags && !new_thread->sysc);
        /* the utf holds the GP context of the uthread (set by the 2LS earlier).
@@ -232,6 +332,8 @@ void uthread_init(struct uthread *new_thread, struct uth_thread_attr *attr)
         * were interrupted off a core. */
        new_thread->flags |= UTHREAD_SAVED;
        new_thread->notif_disabled_depth = 0;
+       /* TODO: on a reinit, if they changed whether or not they want TLS, we'll
+        * have issues (checking tls_desc, assert in allocate_tls, maybe more). */
        if (attr && attr->want_tls) {
                /* Get a TLS.  If we already have one, reallocate/refresh it */
                if (new_thread->tls_desc)
@@ -250,6 +352,10 @@ void uthread_init(struct uthread *new_thread, struct uth_thread_attr *attr)
        } else {
                new_thread->tls_desc = UTH_TLSDESC_NOTLS;
        }
+       if (attr && attr->detached)
+               atomic_set(&new_thread->join_ctl.state, UTH_JOIN_DETACHED);
+       else
+               atomic_set(&new_thread->join_ctl.state, UTH_JOIN_JOINABLE);
 }
 
 /* This is a wrapper for the sched_ops thread_runnable, for use by functions
@@ -266,17 +372,33 @@ void uthread_runnable(struct uthread *uthread)
  * the 2LS.  This is for informational purposes, and some semantic meaning
  * should be passed by flags (from uthread.h's UTH_EXT_BLK_xxx options).
  * Eventually, whoever calls this will call uthread_runnable(), giving the
- * thread back to the 2LS.
+ * thread back to the 2LS.  If the 2LS provide sync ops, it will have a say in
+ * which thread wakes up at a given time.
  *
  * If code outside the 2LS has blocked a thread (via uthread_yield) and ran its
  * own callback/yield_func instead of some 2LS code, that callback needs to
  * call this.
  *
+ * If sync is set, then the 2LS must save the uthread in the sync object.  Using
+ * the default implementatin is fine.  If sync is NULL, then there's nothing the
+ * 2LS should do regarding sync; it'll be told when the thread is runnable.
+ *
  * AKA: obviously_a_uthread_has_blocked_in_lincoln_park() */
-void uthread_has_blocked(struct uthread *uthread, int flags)
+void uthread_has_blocked(struct uthread *uthread, uth_sync_t sync, int flags)
+{
+       assert(sched_ops->thread_has_blocked);
+       sched_ops->thread_has_blocked(uthread, sync, flags);
+}
+
+/* Function indicating an external event has temporarily paused a uthread, but
+ * it is ok to resume it if possible. */
+void uthread_paused(struct uthread *uthread)
 {
-       if (sched_ops->thread_has_blocked)
-               sched_ops->thread_has_blocked(uthread, flags);
+       /* Call out to the 2LS to let it know the uthread was paused for some
+        * reason, but it is ok to resume it now. */
+    assert(uthread->state == UT_NOT_RUNNING);
+    assert(sched_ops->thread_paused);
+    sched_ops->thread_paused(uthread);
 }
 
 /* Need to have this as a separate, non-inlined function since we clobber the
@@ -361,9 +483,10 @@ void uthread_yield(bool save_state, void (*yield_func)(struct uthread*, void*),
        }
        /* Change to the transition context (both TLS (if applicable) and stack). */
        if (__uthread_has_tls(uthread)) {
-               set_tls_desc(get_vcpd_tls_desc(vcoreid), vcoreid);
+               set_tls_desc(get_vcpd_tls_desc(vcoreid));
                begin_safe_access_tls_vars();
                assert(current_uthread == uthread);
+               /* If this assert fails, see the note in uthread_track_thread0 */
                assert(in_vcore_context());
                end_safe_access_tls_vars();
        } else {
@@ -379,7 +502,7 @@ void uthread_yield(bool save_state, void (*yield_func)(struct uthread*, void*),
         * as you need to any local vars that might be pushed before calling the
         * next function, or for whatever other reason the compiler/hardware might
         * walk up the stack a bit when calling a noreturn function. */
-       set_stack_pointer((void*)vcpd->transition_stack);
+       set_stack_pointer((void*)vcpd->vcore_stack);
        /* Finish exiting in another function. */
        __uthread_yield();
        /* Should never get here */
@@ -404,6 +527,17 @@ void uthread_usleep(unsigned int usecs)
        sys_block(usecs);       /* usec sleep */
 }
 
+static void __sleep_forever_cb(struct uthread *uth, void *arg)
+{
+       uthread_has_blocked(uth, NULL, UTH_EXT_BLK_JUSTICE);
+}
+
+void __attribute__((noreturn)) uthread_sleep_forever(void)
+{
+       uthread_yield(FALSE, __sleep_forever_cb, NULL);
+       assert(0);
+}
+
 /* Cleans up the uthread (the stuff we did in uthread_init()).  If you want to
  * destroy a currently running uthread, you'll want something like
  * pthread_exit(), which yields, and calls this from its sched_ops yield. */
@@ -422,34 +556,34 @@ static void __ros_syscall_spinon(struct syscall *sysc)
                cpu_relax();
 }
 
-/* Attempts to block on sysc, returning when it is done or progress has been
- * made. */
-void __ros_mcp_syscall_blockon(struct syscall *sysc)
+static void __ros_vcore_ctx_syscall_blockon(struct syscall *sysc)
 {
-       /* even if we are in 'vcore context', an _S can block */
-       if (!in_multi_mode()) {
-               /* the SCP could have an alarm set to abort this sysc.  When we have a
-                * uth blocked on a sysc, we want this pointer set up (like we do below
-                * for MCP)s */
-               current_uthread->sysc = sysc;
-               __ros_scp_syscall_blockon(sysc);
-               current_uthread->sysc = 0;
-               return;
+       if (in_multi_mode()) {
+               /* MCP vcore's don't know what to do yet, so we have to spin */
+               __ros_syscall_spinon(sysc);
+       } else {
+               /* SCPs can use the early blockon, which acts like VC ctx. */
+               __ros_early_syscall_blockon(sysc);
        }
-       /* MCP vcore's don't know what to do yet, so we have to spin */
+}
+
+/* Attempts to block on sysc, returning when it is done or progress has been
+ * made.  Made for initialized processes using uthreads. */
+static void __ros_uth_syscall_blockon(struct syscall *sysc)
+{
        if (in_vcore_context()) {
-               __ros_syscall_spinon(sysc);
+               __ros_vcore_ctx_syscall_blockon(sysc);
                return;
        }
-       /* At this point, we know we're a uthread in an MCP.  If we're a
-        * DONT_MIGRATE uthread, then it's disabled notifs and is basically in
-        * vcore context, enough so that it can't call into the 2LS. */
+       /* At this point, we know we're a uthread.  If we're a DONT_MIGRATE uthread,
+        * then it's disabled notifs and is basically in vcore context, enough so
+        * that it can't call into the 2LS. */
        assert(current_uthread);
        if (current_uthread->flags & UTHREAD_DONT_MIGRATE) {
                assert(!notif_is_enabled(vcore_id()));  /* catch bugs */
                /* if we had a notif_disabled_depth, then we should also have
                 * DONT_MIGRATE set */
-               __ros_syscall_spinon(sysc);
+               __ros_vcore_ctx_syscall_blockon(sysc);
                return;
        }
        assert(!current_uthread->notif_disabled_depth);
@@ -462,6 +596,24 @@ void __ros_mcp_syscall_blockon(struct syscall *sysc)
        uthread_yield(TRUE, sched_ops->thread_blockon_sysc, sysc);
 }
 
+/* 2LS helper.  Run this from vcore context.  It will block a uthread on it's
+ * internal syscall struct, which should be an async call.  You'd use this in
+ * e.g. thread_refl_fault when the 2LS initiates a syscall on behalf of the
+ * uthread. */
+void __block_uthread_on_async_sysc(struct uthread *uth)
+{
+       assert(in_vcore_context());
+       uth->sysc = &uth->local_sysc;
+       /* If a DONT_MIGRATE issued a syscall that blocks, we gotta spin, same as
+        * with the usual blockon. */
+       if (uth->flags & UTHREAD_DONT_MIGRATE) {
+               __ros_vcore_ctx_syscall_blockon(uth->sysc);
+               uth->sysc = 0;
+               return;
+       }
+       sched_ops->thread_blockon_sysc(uth, uth->sysc);
+}
+
 /* Simply sets current uthread to be whatever the value of uthread is.  This
  * can be called from outside of sched_entry() to highjack the current context,
  * and make sure that the new uthread struct is used to store this context upon
@@ -480,7 +632,7 @@ void highjack_current_uthread(struct uthread *uthread)
        /* and make sure we are using the correct TLS for the new uthread */
        if (__uthread_has_tls(uthread)) {
                assert(uthread->tls_desc);
-               set_tls_desc(uthread->tls_desc, vcoreid);
+               set_tls_desc(uthread->tls_desc);
                begin_safe_access_tls_vars();
                __vcoreid = vcoreid;    /* setting the uthread's TLS var */
                end_safe_access_tls_vars();
@@ -493,7 +645,7 @@ void highjack_current_uthread(struct uthread *uthread)
 static void set_uthread_tls(struct uthread *uthread, uint32_t vcoreid)
 {
        if (__uthread_has_tls(uthread)) {
-               set_tls_desc(uthread->tls_desc, vcoreid);
+               set_tls_desc(uthread->tls_desc);
                begin_safe_access_tls_vars();
                __vcoreid = vcoreid;    /* setting the uthread's TLS var */
                end_safe_access_tls_vars();
@@ -505,16 +657,36 @@ static void set_uthread_tls(struct uthread *uthread, uint32_t vcoreid)
 /* Attempts to handle a fault for uth, etc */
 static void handle_refl_fault(struct uthread *uth, struct user_context *ctx)
 {
-       sched_ops->thread_refl_fault(uth, __arch_refl_get_nr(ctx),
-                                    __arch_refl_get_err(ctx),
-                                    __arch_refl_get_aux(ctx));
+       sched_ops->thread_refl_fault(uth, ctx);
+}
+
+/* 2LS helper: stops the current uthread, saves its state, and returns a pointer
+ * to it.  Unlike __uthread_pause, which is called by non-specific 2LS code,
+ * this function is called by a specific 2LS to stop it's current uthread. */
+struct uthread *stop_current_uthread(void)
+{
+       struct uthread *uth;
+       struct preempt_data *vcpd = vcpd_of(vcore_id());
+
+       uth = current_uthread;
+       current_uthread = 0;
+       if (!(uth->flags & UTHREAD_SAVED)) {
+               uth->u_ctx = vcpd->uthread_ctx;
+               uth->flags |= UTHREAD_SAVED;
+       }
+       if ((uth->u_ctx.type != ROS_SW_CTX) && !(uth->flags & UTHREAD_FPSAVED)) {
+               save_fp_state(&uth->as);
+               uth->flags |= UTHREAD_FPSAVED;
+       }
+       uth->state = UT_NOT_RUNNING;
+       return uth;
 }
 
 /* Run the thread that was current_uthread, from a previous run.  Should be
  * called only when the uthread already was running, and we were interrupted by
  * the kernel (event, etc).  Do not call this to run a fresh uthread, even if
  * you've set it to be current. */
-void run_current_uthread(void)
+void __attribute__((noreturn)) run_current_uthread(void)
 {
        struct uthread *uth;
        uint32_t vcoreid = vcore_id();
@@ -531,15 +703,10 @@ void run_current_uthread(void)
                /* we preemptively copy out and make non-running, so that there is a
                 * consistent state for the handler.  it can then block the uth or
                 * whatever. */
-               uth = current_uthread;
-               current_uthread = 0;
-               uth->u_ctx = vcpd->uthread_ctx;
-               save_fp_state(&uth->as);
-               uth->state = UT_NOT_RUNNING;
-               uth->flags |= UTHREAD_SAVED | UTHREAD_FPSAVED;
+               uth = stop_current_uthread();
                handle_refl_fault(uth, &vcpd->uthread_ctx);
                /* we abort no matter what.  up to the 2LS to reschedule the thread */
-               set_stack_pointer((void*)vcpd->transition_stack);
+               set_stack_pointer((void*)vcpd->vcore_stack);
                vcore_entry();
        }
        /* Go ahead and start the uthread */
@@ -563,24 +730,29 @@ void run_current_uthread(void)
  * necessary (we only must do it once for an entire time in VC ctx, and in
  * loops), and might have been optimizing a rare event at a cost in both
  * instructions and complexity. */
-void run_uthread(struct uthread *uthread)
+void __attribute__((noreturn)) run_uthread(struct uthread *uthread)
 {
        uint32_t vcoreid = vcore_id();
        struct preempt_data *vcpd = vcpd_of(vcoreid);
        assert(!current_uthread);
        assert(uthread->state == UT_NOT_RUNNING);
        assert(uthread->flags & UTHREAD_SAVED);
-       /* For HW CTX, FPSAVED must match UTH SAVE (and both be on here).  For SW,
-        * FP should never be saved. */
-       if (uthread->u_ctx.type == ROS_HW_CTX)
+       /* For HW/VM CTX, FPSAVED must match UTH SAVE (and both be on here).  For
+        * SW, FP should never be saved. */
+       switch (uthread->u_ctx.type) {
+       case ROS_HW_CTX:
+       case ROS_VM_CTX:
                assert(uthread->flags & UTHREAD_FPSAVED);
-       else
+               break;
+       case ROS_SW_CTX:
                assert(!(uthread->flags & UTHREAD_FPSAVED));
+               break;
+       }
        if (has_refl_fault(&uthread->u_ctx)) {
                clear_refl_fault(&uthread->u_ctx);
                handle_refl_fault(uthread, &uthread->u_ctx);
                /* we abort no matter what.  up to the 2LS to reschedule the thread */
-               set_stack_pointer((void*)vcpd->transition_stack);
+               set_stack_pointer((void*)vcpd->vcore_stack);
                vcore_entry();
        }
        uthread->state = UT_RUNNING;
@@ -642,9 +814,12 @@ static void copyout_uthread(struct preempt_data *vcpd, struct uthread *uthread,
 {
        assert(uthread);
        if (uthread->flags & UTHREAD_SAVED) {
-               /* I don't know of scenarios where HW ctxs FP state differs from GP */
-               if (uthread->u_ctx.type == ROS_HW_CTX)
+               /* I don't know of scenarios where HW/VM ctxs FP state differs from GP*/
+               switch (uthread->u_ctx.type) {
+               case ROS_HW_CTX:
+               case ROS_VM_CTX:
                        assert(uthread->flags & UTHREAD_FPSAVED);
+               }
                assert(vcore_local);
                return;
        }
@@ -731,28 +906,29 @@ bool __check_preempt_pending(uint32_t vcoreid)
  * uth_enable_notifs() unless you know what you're doing. */
 void uth_disable_notifs(void)
 {
-       if (!in_vcore_context() && in_multi_mode()) {
-               assert(current_uthread);
-               if (current_uthread->notif_disabled_depth++)
-                       goto out;
-               current_uthread->flags |= UTHREAD_DONT_MIGRATE;
-               cmb();  /* don't issue the flag write before the vcore_id() read */
+       if (!in_vcore_context()) {
+               if (current_uthread) {
+                       if (current_uthread->notif_disabled_depth++)
+                               goto out;
+                       current_uthread->flags |= UTHREAD_DONT_MIGRATE;
+                       cmb();  /* don't issue the flag write before the vcore_id() read */
+               }
                disable_notifs(vcore_id());
        }
 out:
-       if (in_multi_mode())
-               assert(!notif_is_enabled(vcore_id()));
+       assert(!notif_is_enabled(vcore_id()));
 }
 
 /* Helper: Pair this with uth_disable_notifs(). */
 void uth_enable_notifs(void)
 {
-       if (!in_vcore_context() && in_multi_mode()) {
-               assert(current_uthread);
-               if (--current_uthread->notif_disabled_depth)
-                       return;
-               current_uthread->flags &= ~UTHREAD_DONT_MIGRATE;
-               cmb();  /* don't enable before ~DONT_MIGRATE */
+       if (!in_vcore_context()) {
+               if (current_uthread) {
+                       if (--current_uthread->notif_disabled_depth)
+                               return;
+                       current_uthread->flags &= ~UTHREAD_DONT_MIGRATE;
+                       cmb();  /* don't enable before ~DONT_MIGRATE */
+               }
                enable_notifs(vcore_id());
        }
 }
@@ -795,8 +971,8 @@ static void handle_indirs(uint32_t rem_vcoreid)
        struct preempt_data *rem_vcpd = vcpd_of(rem_vcoreid);
        /* Turn off their message reception if they are still preempted.  If they
         * are no longer preempted, we do nothing - they will handle their own
-        * messages.  Turning on CAN_RCV will route this vcore's messages to
-        * fallback vcores (if applicable). */
+        * messages.  Turning off CAN_RCV will route this vcore's messages to
+        * fallback vcores (if those messages were 'spammed'). */
        do {
                old_flags = atomic_read(&rem_vcpd->flags);
                while (old_flags & VC_K_LOCK)
@@ -955,7 +1131,7 @@ static void handle_vc_preempt(struct event_msg *ev_msg, unsigned int ev_type,
        /* At this point, we're clear to try and steal the uthread.  We used to
         * switch to their TLS to steal the uthread, but we can access their
         * current_uthread directly. */
-       rem_cur_uth = get_cur_uth_addr(rem_vcoreid);
+       rem_cur_uth = get_tlsvar_linaddr(rem_vcoreid, current_uthread);
        uthread_to_steal = *rem_cur_uth;
        if (uthread_to_steal) {
                /* Extremely rare: they have a uthread, but it can't migrate.  So we'll
@@ -998,62 +1174,6 @@ static void handle_vc_indir(struct event_msg *ev_msg, unsigned int ev_type,
        handle_indirs(rem_vcoreid);
 }
 
-/* Attempts to register ev_q with sysc, so long as sysc is not done/progress.
- * Returns true if it succeeded, and false otherwise.  False means that the
- * syscall is done, and does not need an event set (and should be handled
- * accordingly).
- * 
- * A copy of this is in glibc/sysdeps/ros/syscall.c.  Keep them in sync. */
-bool register_evq(struct syscall *sysc, struct event_queue *ev_q)
-{
-       int old_flags;
-       sysc->ev_q = ev_q;
-       wrmb(); /* don't let that write pass any future reads (flags) */
-       /* Try and set the SC_UEVENT flag (so the kernel knows to look at ev_q) */
-       do {
-               /* no cmb() needed, the atomic_read will reread flags */
-               old_flags = atomic_read(&sysc->flags);
-               /* Spin if the kernel is mucking with syscall flags */
-               while (old_flags & SC_K_LOCK)
-                       old_flags = atomic_read(&sysc->flags);
-               /* If the kernel finishes while we are trying to sign up for an event,
-                * we need to bail out */
-               if (old_flags & (SC_DONE | SC_PROGRESS)) {
-                       sysc->ev_q = 0;         /* not necessary, but might help with bugs */
-                       return FALSE;
-               }
-       } while (!atomic_cas(&sysc->flags, old_flags, old_flags | SC_UEVENT));
-       return TRUE;
-}
-
-/* De-registers a syscall, so that the kernel will not send an event when it is
- * done.  The call could already be SC_DONE, or could even finish while we try
- * to unset SC_UEVENT.
- *
- * There is a chance the kernel sent an event if you didn't do this in time, but
- * once this returns, the kernel won't send a message.
- *
- * If the kernel is trying to send a message right now, this will spin (on
- * SC_K_LOCK).  We need to make sure we deregistered, and that if a message
- * is coming, that it already was sent (and possibly overflowed), before
- * returning. */
-void deregister_evq(struct syscall *sysc)
-{
-       int old_flags;
-       sysc->ev_q = 0;
-       wrmb(); /* don't let that write pass any future reads (flags) */
-       /* Try and unset the SC_UEVENT flag */
-       do {
-               /* no cmb() needed, the atomic_read will reread flags */
-               old_flags = atomic_read(&sysc->flags);
-               /* Spin if the kernel is mucking with syscall flags */
-               while (old_flags & SC_K_LOCK)
-                       old_flags = atomic_read(&sysc->flags);
-               /* Note we don't care if the SC_DONE flag is getting set.  We just need
-                * to avoid clobbering flags */
-       } while (!atomic_cas(&sysc->flags, old_flags, old_flags & ~SC_UEVENT));
-}
-
 static inline bool __uthread_has_tls(struct uthread *uthread)
 {
        return uthread->tls_desc != UTH_TLSDESC_NOTLS;
@@ -1086,3 +1206,176 @@ static void __uthread_free_tls(struct uthread *uthread)
        free_tls(uthread->tls_desc);
        uthread->tls_desc = NULL;
 }
+
+bool uth_2ls_is_multithreaded(void)
+{
+       /* thread 0 is single threaded.  For the foreseeable future, every other 2LS
+        * will be multithreaded. */
+       return sched_ops != &thread0_2ls_ops;
+}
+
+struct uthread *uthread_create(void *(*func)(void *), void *arg)
+{
+       return sched_ops->thread_create(func, arg);
+}
+
+/* Who does the thread_exited callback (2LS-specific cleanup)?  It depends.  If
+ * the thread exits first, then join/detach does it.  o/w, the exit path does.
+ *
+ * What are the valid state changes?
+ *
+ *             JOINABLE   -> DETACHED (only by detach())
+ *             JOINABLE   -> HAS_JOINER (only by join())
+ *             JOINABLE   -> EXITED (only by uth_2ls_thread_exit())
+ *
+ * That's it.  The initial state is either JOINABLE or DETACHED. */
+void uthread_detach(struct uthread *uth)
+{
+       struct uth_join_ctl *jc = &uth->join_ctl;
+       long old_state;
+
+       do {
+               old_state = atomic_read(&jc->state);
+               switch (old_state) {
+               case UTH_JOIN_EXITED:
+                       sched_ops->thread_exited(uth);
+                       return;
+               case UTH_JOIN_DETACHED:
+                       panic("Uth %p has already been detached!", uth);
+               case UTH_JOIN_HAS_JOINER:
+                       panic("Uth %p has a pending joiner, can't detach!", uth);
+               };
+               assert(old_state == UTH_JOIN_JOINABLE);
+       } while (!atomic_cas(&jc->state, old_state, UTH_JOIN_DETACHED));
+}
+
+/* Helper.  We have a joiner.  So we'll write the retval to the final location
+ * (the one passed to join() and decref to wake the joiner.  This may seem a
+ * little odd for a normal join, but it works identically a parallel join - and
+ * there's only one wakeup (hence the kref). */
+static void uth_post_and_kick_joiner(struct uthread *uth, void *retval)
+{
+       struct uth_join_ctl *jc = &uth->join_ctl;
+
+       if (jc->retval_loc)
+               *jc->retval_loc = retval;
+       /* Note the JC has a pointer to the kicker.  There's one kicker for the
+        * joiner, but there could be many joinees. */
+       kref_put(&jc->kicker->kref);
+}
+
+/* Callback after the exiting uthread has yielded and is in vcore context.  Note
+ * that the thread_exited callback can be called concurrently (e.g., a racing
+ * call to detach()), so it's important to not be in the uthread's context. */
+static void __uth_2ls_thread_exit_cb(struct uthread *uth, void *retval)
+{
+       struct uth_join_ctl *jc = &uth->join_ctl;
+       long old_state;
+
+       do {
+               old_state = atomic_read(&jc->state);
+               switch (old_state) {
+               case UTH_JOIN_DETACHED:
+                       sched_ops->thread_exited(uth);
+                       return;
+               case UTH_JOIN_HAS_JOINER:
+                       uth_post_and_kick_joiner(uth, retval);
+                       sched_ops->thread_exited(uth);
+                       return;
+               case UTH_JOIN_JOINABLE:
+                       /* This write is harmless and idempotent; we can lose the race and
+                        * still be safe.  Assuming we don't, the joiner will look here for
+                        * the retval.  It's temporary storage since we don't know the final
+                        * retval location (since join hasn't happened yet). */
+                       jc->retval = retval;
+                       break;
+               };
+               assert(old_state == UTH_JOIN_JOINABLE);
+       } while (!atomic_cas(&jc->state, old_state, UTH_JOIN_EXITED));
+       /* We were joinable, now we have exited.  A detacher or joiner will trigger
+        * thread_exited. */
+}
+
+/* 2LSs call this when their threads are exiting.  The 2LS will regain control
+ * of the thread in sched_ops->thread_exited.  This will be after the
+ * join/detach/exit has completed, and might be in vcore context. */
+void __attribute__((noreturn)) uth_2ls_thread_exit(void *retval)
+{
+       uthread_yield(FALSE, __uth_2ls_thread_exit_cb, retval);
+       assert(0);
+}
+
+/* Helper: Attaches the caller (specifically the jk) to the target uthread.
+ * When the thread has been joined (either due to the UTH_EXITED case or due to
+ * __uth_2ls_thread_exit_cb), the join kicker will be decreffed. */
+static void join_one(struct uthread *uth, struct uth_join_kicker *jk,
+                     void **retval_loc)
+{
+       struct uth_join_ctl *jc = &uth->join_ctl;
+       long old_state;
+
+       /* We can safely write to the join_ctl, even if we don't end up setting
+        * HAS_JOINER.  There's only supposed to be one joiner, and if not, we'll
+        * catch the bad state. */
+       jc->retval_loc = retval_loc;
+       jc->kicker = jk;
+       do {
+               old_state = atomic_read(&jc->state);
+               switch (old_state) {
+               case UTH_JOIN_EXITED:
+                       if (retval_loc)
+                               *retval_loc = jc->retval;
+                       sched_ops->thread_exited(uth);
+                       kref_put(&jk->kref);
+                       return;
+               case UTH_JOIN_DETACHED:
+                       panic("Uth %p has been detached, can't join!", uth);
+               case UTH_JOIN_HAS_JOINER:
+                       panic("Uth %p has another pending joiner!", uth);
+               };
+               assert(old_state == UTH_JOIN_JOINABLE);
+       } while (!atomic_cas(&jc->state, old_state, UTH_JOIN_HAS_JOINER));
+}
+
+/* Bottom half of the join, in vcore context */
+static void __uth_join_cb(struct uthread *uth, void *arg)
+{
+       struct uth_join_kicker *jk = (struct uth_join_kicker*)arg;
+
+       uthread_has_blocked(uth, NULL, UTH_EXT_BLK_MUTEX);
+       /* After this, and after all threads join, we could be woken up. */
+       kref_put(&jk->kref);
+}
+
+static void kicker_release(struct kref *k)
+{
+       struct uth_join_kicker *jk = container_of(k, struct uth_join_kicker, kref);
+
+       uthread_runnable(jk->joiner);
+}
+
+void uthread_join_arr(struct uth_join_request reqs[], size_t nr_req)
+{
+       struct uth_join_kicker jk[1];
+
+       jk->joiner = current_uthread;
+       /* One ref for each target, another for *us*, which we drop in the yield
+        * callback.  As as soon as it is fully decreffed, our thread will be
+        * restarted.  We must block before that (in the yield callback). */
+       kref_init(&jk->kref, kicker_release, nr_req + 1);
+       for (int i = 0; i < nr_req; i++)
+               join_one(reqs[i].uth, jk, reqs[i].retval_loc);
+       uthread_yield(TRUE, __uth_join_cb, jk);
+}
+
+/* Unlike POSIX, we don't bother with returning error codes.  Anything that can
+ * go wrong is so horrendous that you should crash (the specs say the behavior
+ * is undefined). */
+void uthread_join(struct uthread *uth, void **retval_loc)
+{
+       struct uth_join_request req[1];
+
+       req->uth = uth;
+       req->retval_loc = retval_loc;
+       uthread_join_arr(req, 1);
+}