Index: uspace/lib/c/generic/context.c
===================================================================
--- uspace/lib/c/generic/context.c	(revision 53ad43cce05b0004bca31a7c5d6822fc33686cf1)
+++ uspace/lib/c/generic/context.c	(revision 53ad43cce05b0004bca31a7c5d6822fc33686cf1)
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2018 CZ.NIC, z.s.p.o.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ * - Redistributions in binary form must reproduce the above copyright
+ *   notice, this list of conditions and the following disclaimer in the
+ *   documentation and/or other materials provided with the distribution.
+ * - The name of the author may not be used to endorse or promote products
+ *   derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <context.h>
+#include <libarch/tls.h>
+#include <libarch/fibril.h>
+#include <libarch/faddr.h>
+
+/**
+ * Saves current context to the variable pointed to by `self`,
+ * and restores the context denoted by `other`.
+ *
+ * When the `self` context is later restored by another call to
+ * `context_swap()`, the control flow behaves as if the earlier call to
+ * `context_swap()` just returned.
+ */
+void context_swap(context_t *self, context_t *other)
+{
+	if (context_save(self))
+		context_restore(other);
+}
+
+void context_create(context_t *context, const context_create_t *arg)
+{
+	context_save(context);
+	context_set(context, FADDR(arg->fn), arg->stack_base,
+	    arg->stack_size, arg->tls);
+}
+
+uintptr_t context_get_pc(context_t *ctx)
+{
+	// This is a simple wrapper for now, and exists to allow
+	// potential future implementation of context_swap to omit
+	// program counter from the context structure (e.g. if it's
+	// stored on the stack).
+	return ctx->pc;
+}
+
+uintptr_t context_get_fp(context_t *ctx)
+{
+	return _context_get_fp(ctx);
+}
Index: uspace/lib/c/generic/fibril.c
===================================================================
--- uspace/lib/c/generic/fibril.c	(revision a35b458e9db1ca95e679799dc7c1b12c83359ca3)
+++ uspace/lib/c/generic/fibril.c	(revision 53ad43cce05b0004bca31a7c5d6822fc33686cf1)
@@ -44,5 +44,5 @@
 #include <stdio.h>
 #include <libarch/barrier.h>
-#include <libarch/faddr.h>
+#include <context.h>
 #include <futex.h>
 #include <assert.h>
@@ -155,5 +155,27 @@
 	futex_lock(&fibril_futex);
 
+	fibril_t *srcf = __tcb_get()->fibril_data;
+	fibril_t *dstf = NULL;
+
+	/* Choose a new fibril to run */
 	switch (stype) {
+	case FIBRIL_TO_MANAGER:
+	case FIBRIL_FROM_DEAD:
+		/* Make sure the async_futex is held. */
+		assert((atomic_signed_t) async_futex.val.count <= 0);
+
+		/* If we are going to manager and none exists, create it */
+		while (list_empty(&manager_list)) {
+			futex_unlock(&fibril_futex);
+			async_create_manager();
+			futex_lock(&fibril_futex);
+		}
+
+		dstf = list_get_instance(list_first(&manager_list),
+		    fibril_t, link);
+
+		if (stype == FIBRIL_FROM_DEAD)
+			dstf->clean_after_me = srcf;
+		break;
 	case FIBRIL_PREEMPT:
 	case FIBRIL_FROM_MANAGER:
@@ -162,88 +184,30 @@
 			return 0;
 		}
-		break;
-	case FIBRIL_TO_MANAGER:
-	case FIBRIL_FROM_DEAD:
-		/* Make sure the async_futex is held. */
-		assert((atomic_signed_t) async_futex.val.count <= 0);
-
-		/* If we are going to manager and none exists, create it */
-		while (list_empty(&manager_list)) {
-			futex_unlock(&fibril_futex);
-			async_create_manager();
-			futex_lock(&fibril_futex);
-		}
-		break;
-	}
-
-	fibril_t *srcf = __tcb_get()->fibril_data;
-	if (stype != FIBRIL_FROM_DEAD) {
-
-		/* Save current state */
-		if (!context_save(&srcf->ctx)) {
-			if (srcf->clean_after_me) {
-				/*
-				 * Cleanup after the dead fibril from which we
-				 * restored context here.
-				 */
-				void *stack = srcf->clean_after_me->stack;
-				if (stack) {
-					/*
-					 * This check is necessary because a
-					 * thread could have exited like a
-					 * normal fibril using the
-					 * FIBRIL_FROM_DEAD switch type. In that
-					 * case, its fibril will not have the
-					 * stack member filled.
-					 */
-					as_area_destroy(stack);
-				}
-				fibril_teardown(srcf->clean_after_me, true);
-				srcf->clean_after_me = NULL;
-			}
-
-			return 1;	/* futex_unlock already done here */
-		}
-
-		/* Put the current fibril into the correct run list */
-		switch (stype) {
-		case FIBRIL_PREEMPT:
-			list_append(&srcf->link, &ready_list);
-			break;
-		case FIBRIL_FROM_MANAGER:
-			list_append(&srcf->link, &manager_list);
-			break;
-		default:
-			assert(stype == FIBRIL_TO_MANAGER);
-
-			srcf->switches++;
-
-			/*
-			 * Don't put the current fibril into any list, it should
-			 * already be somewhere, or it will be lost.
-			 */
-			break;
-		}
-	}
-
-	fibril_t *dstf;
-
-	/* Choose a new fibril to run */
-	switch (stype) {
-	case FIBRIL_TO_MANAGER:
-	case FIBRIL_FROM_DEAD:
-		dstf = list_get_instance(list_first(&manager_list), fibril_t,
-		    link);
-
-		if (stype == FIBRIL_FROM_DEAD)
-			dstf->clean_after_me = srcf;
-		break;
-	default:
+
 		dstf = list_get_instance(list_first(&ready_list), fibril_t,
 		    link);
 		break;
 	}
-
 	list_remove(&dstf->link);
+
+	/* Put the current fibril into the correct run list */
+	switch (stype) {
+	case FIBRIL_PREEMPT:
+		list_append(&srcf->link, &ready_list);
+		break;
+	case FIBRIL_FROM_MANAGER:
+		list_append(&srcf->link, &manager_list);
+		break;
+	case FIBRIL_FROM_DEAD:
+		// Nothing.
+		break;
+	case FIBRIL_TO_MANAGER:
+		srcf->switches++;
+		/*
+		 * Don't put the current fibril into any list, it should
+		 * already be somewhere, or it will be lost.
+		 */
+		break;
+	}
 
 	futex_unlock(&fibril_futex);
@@ -255,6 +219,31 @@
 #endif
 
-	context_restore(&dstf->ctx);
-	/* not reached */
+	/* Swap to the next fibril. */
+	context_swap(&srcf->ctx, &dstf->ctx);
+
+	/* Restored by another fibril! */
+
+	if (srcf->clean_after_me) {
+		/*
+		 * Cleanup after the dead fibril from which we
+		 * restored context here.
+		 */
+		void *stack = srcf->clean_after_me->stack;
+		if (stack) {
+			/*
+			 * This check is necessary because a
+			 * thread could have exited like a
+			 * normal fibril using the
+			 * FIBRIL_FROM_DEAD switch type. In that
+			 * case, its fibril will not have the
+			 * stack member filled.
+			 */
+			as_area_destroy(stack);
+		}
+		fibril_teardown(srcf->clean_after_me, true);
+		srcf->clean_after_me = NULL;
+	}
+
+	return 1;
 }
 
@@ -289,8 +278,12 @@
 	fibril->arg = arg;
 
-	context_save(&fibril->ctx);
-	context_set(&fibril->ctx, FADDR(fibril_main), fibril->stack,
-	    stack_size, fibril->tcb);
-
+	context_create_t sctx = {
+		.fn = fibril_main,
+		.stack_base = fibril->stack,
+		.stack_size = stack_size,
+		.tls = fibril->tcb,
+	};
+
+	context_create(&fibril->ctx, &sctx);
 	return (fid_t) fibril;
 }
Index: uspace/lib/c/generic/fibril_synch.c
===================================================================
--- uspace/lib/c/generic/fibril_synch.c	(revision a35b458e9db1ca95e679799dc7c1b12c83359ca3)
+++ uspace/lib/c/generic/fibril_synch.c	(revision 53ad43cce05b0004bca31a7c5d6822fc33686cf1)
@@ -73,6 +73,7 @@
 		if (oi->owned_by == f)
 			break;
-		stacktrace_print_fp_pc(context_get_fp(&oi->owned_by->ctx),
-		    oi->owned_by->ctx.pc);
+		stacktrace_print_fp_pc(
+		    context_get_fp(&oi->owned_by->ctx),
+		    context_get_pc(&oi->owned_by->ctx));
 		printf("Fibril %p waits for primitive %p.\n",
 		     oi->owned_by, oi->owned_by->waits_for);
Index: uspace/lib/c/generic/setjmp.c
===================================================================
--- uspace/lib/c/generic/setjmp.c	(revision a35b458e9db1ca95e679799dc7c1b12c83359ca3)
+++ uspace/lib/c/generic/setjmp.c	(revision 53ad43cce05b0004bca31a7c5d6822fc33686cf1)
@@ -37,6 +37,10 @@
 
 #include <setjmp.h>
-#include <libarch/fibril.h>
-#include <fibril.h>
+#include <context.h>
+
+// TODO: setjmp/longjmp are basically a stronger version of
+// context_save/context_restore. It would be preferable to turn
+// those two into setjmp/longjmp (all it would need is preserving the
+// return value).
 
 /**
@@ -52,5 +56,4 @@
 	env[0].return_value = (val == 0) ? 1 : val;
 	context_restore(&env[0].context);
-	__builtin_unreachable();
 }
 
