parlib: Add uthread_create()
[akaros.git] / user / parlib / uthread.c
index ba9a74b..70ef9b4 100644 (file)
@@ -46,6 +46,8 @@ static void uthread_init_thread0(struct uthread *uthread)
        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);
@@ -91,7 +93,7 @@ static void uthread_track_thread0(struct uthread *uthread)
 void uthread_mcp_init()
 {
        /* Prevent this from happening more than once. */
-       init_once_racy(return);
+       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
@@ -134,9 +136,28 @@ void uthread_mcp_init()
        vcore_change_to_m();
 }
 
-/* The real 2LS calls this, passing in a uthread representing thread0. */
-void uthread_2ls_init(struct uthread *uthread, struct schedule_ops *ops)
+/* 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
@@ -158,9 +179,13 @@ void uthread_2ls_init(struct uthread *uthread, struct schedule_ops *ops)
         * 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
@@ -207,19 +232,36 @@ void __attribute__((constructor)) uthread_lib_init(void)
        /* 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. */
-       init_once_racy(return);
+       parlib_init_once_racy(return);
        vcore_lib_init();
 
        ret = posix_memalign((void**)&thread0_uth, __alignof__(struct uthread),
                             sizeof(struct uthread));
        assert(!ret);
        memset(thread0_uth, 0, sizeof(struct uthread)); /* aggressively 0 for bugs*/
-       /* Init the 2LS, which sets up current_uthread, before thread0 lib */
-       uthread_2ls_init(thread0_uth, &thread0_2ls_ops);
+       /* 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.
@@ -228,11 +270,10 @@ void __attribute__((constructor)) uthread_lib_init(void)
        ros_syscall_blockon = __ros_uth_syscall_blockon;
        cmb();
        init_posix_signals();
-       /* 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);
+       /* 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 */
@@ -291,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)
@@ -309,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
@@ -325,17 +372,22 @@ 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, flags);
+       sched_ops->thread_has_blocked(uthread, sync, flags);
 }
 
 /* Function indicating an external event has temporarily paused a uthread, but
@@ -475,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. */
@@ -533,6 +596,24 @@ static void __ros_uth_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
@@ -576,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();
@@ -602,12 +703,7 @@ 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->vcore_stack);
@@ -634,7 +730,7 @@ 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);
@@ -811,11 +907,12 @@ bool __check_preempt_pending(uint32_t vcoreid)
 void uth_disable_notifs(void)
 {
        if (!in_vcore_context()) {
-               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 (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:
@@ -826,11 +923,12 @@ out:
 void uth_enable_notifs(void)
 {
        if (!in_vcore_context()) {
-               assert(current_uthread);
-               if (--current_uthread->notif_disabled_depth)
-                       return;
-               current_uthread->flags &= ~UTHREAD_DONT_MIGRATE;
-               cmb();  /* don't enable before ~DONT_MIGRATE */
+               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());
        }
 }
@@ -1108,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);
+}