Add setjmp and setjmp-based error handling.

This is modelled on the OpenStep approach, which built Objective-C
exceptions atop setjmp.  This maintains a linked list of on-stack jump
buffers.  You can unwind out of an error handler and so these can be
used for cleanup in case of a trap.
diff --git a/scripts/run_clang_tidy_format.sh b/scripts/run_clang_tidy_format.sh
index 6b17baf..aecda51 100755
--- a/scripts/run_clang_tidy_format.sh
+++ b/scripts/run_clang_tidy_format.sh
@@ -35,7 +35,7 @@
 # FreeRTOS-Compat headers follow FreeRTOS naming conventions and should be
 # excluded for now.  Eventually they should be included for everything except
 # the identifier naming checks.
-HEADERS=$(find ${DIRECTORIES} -name '*.h' -or -name '*.hh' | grep -v libc++ | grep -v third_party | grep -v 'std.*.h' | grep -v errno.h | grep -v strings.h | grep -v string.h | grep -v -assembly.h | grep -v cdefs.h | grep -v /riscv.h | grep -v inttypes.h | grep -v /cheri-builtins.h | grep -v c++-config | grep -v ctype.h | grep -v switcher.h | grep -v assert.h | grep -v std*.h | grep -v /build/ | grep -v microvium | grep -v FreeRTOS-Compat)
+HEADERS=$(find ${DIRECTORIES} -name '*.h' -or -name '*.hh' | grep -v libc++ | grep -v third_party | grep -v 'std.*.h' | grep -v errno.h | grep -v strings.h | grep -v string.h | grep -v -assembly.h | grep -v cdefs.h | grep -v /riscv.h | grep -v inttypes.h | grep -v /cheri-builtins.h | grep -v c++-config | grep -v ctype.h | grep -v switcher.h | grep -v assert.h | grep -v std*.h | grep -v setjmp.h | grep -v unwind.h | grep -v /build/ | grep -v microvium | grep -v FreeRTOS-Compat)
 SOURCES=$(find ${DIRECTORIES} -name '*.cc' | grep -v /build/ | grep -v third_party | grep -v arith64.c)
 
 echo Headers: ${HEADERS}
diff --git a/sdk/include/setjmp.h b/sdk/include/setjmp.h
new file mode 100644
index 0000000..d8d3cdf
--- /dev/null
+++ b/sdk/include/setjmp.h
@@ -0,0 +1,64 @@
+// Copyright Microsoft and CHERIoT Contributors.
+// SPDX-License-Identifier: MIT
+#pragma once
+
+/**
+ * This is a minimal implementation of setjmp/longjmp.
+ *
+ * CHERIoT cannot store a `jmp_buf` anywhere other than the stack without
+ * clearing tags (which will then cause `longjmp` to fail).
+ */
+
+#include <stddef.h>
+#include <stdint.h>
+
+/**
+ * Jump buffer for setjmp/longjmp.
+ */
+struct __jmp_buf
+{
+	uintptr_t __cs0;
+	uintptr_t __cs1;
+	uintptr_t __csp;
+	uintptr_t __cra;
+};
+
+/**
+ * C requires that `setjmp` and `longjmp` take a `jmp_buf` by reference and so
+ * this ends up being defined as an array of one element, which allows it to
+ * both be allocated and passed by reference.
+ */
+typedef struct __jmp_buf jmp_buf[1];
+
+/**
+ * C `setjmp` function.  Returns (up to) twice.  First returns 0, returns a
+ * value passed to `longjmp` on the second return.
+ */
+__attribute__((returns_twice)) extern "C" int setjmp(jmp_buf env);
+__asm__(".section .text.setjmp,\"awG\",@progbits,setjmp,comdat\n"
+        ".globl setjmp\n"
+        ".p2align 2\n"
+        ".type setjmp,@function\n"
+        "setjmp:\n"
+        "	csc	cs0, 0(ca0)\n"
+        "	csc	cs1, 8(ca0)\n"
+        "	csc	csp, 16(ca0)\n"
+        "	csc	cra, 24(ca0)\n"
+        "	li	a0, 0\n"
+        "	cret\n");
+
+/**
+ * C `longjmp` function.  Does not return, jumps back to the `setjmp` call.
+ */
+extern "C" void longjmp(jmp_buf env, int val);
+__asm__(".section .text.longjmp,\"awG\",@progbits,longjmp,comdat\n"
+        ".globl longjmp\n"
+        ".p2align 2\n"
+        ".type longjmp,@function\n"
+        "longjmp:\n"
+        "	clc	cs0, 0(ca0)\n"
+        "	clc	cs1, 8(ca0)\n"
+        "	clc	csp, 16(ca0)\n"
+        "	clc	cra, 24(ca0)\n"
+        "	mv	a0, a1\n"
+        "	cjr	cra\n");
diff --git a/sdk/include/unwind.h b/sdk/include/unwind.h
new file mode 100644
index 0000000..9049828
--- /dev/null
+++ b/sdk/include/unwind.h
@@ -0,0 +1,98 @@
+#pragma once
+#include <cdefs.h>
+#include <setjmp.h>
+
+/**
+ * On-stack linked list of cleanup handlers.
+ */
+struct CleanupList
+{
+	/// Next pointer.
+	CleanupList *next;
+	/// Jump buffer to return to.
+	__jmp_buf env;
+};
+
+/**
+ * Head of the cleanup list.
+ *
+ * This is stored in the space that the switcher reserves at the top of the
+ * stack.  The stack is zeroed on entry to a compartment and so this will be
+ * null until explicitly written to.
+ */
+__always_inline static inline struct CleanupList **cleanup_list_head()
+{
+	void     *csp = __builtin_cheri_stack_get();
+	ptraddr_t top =
+	  __builtin_cheri_base_get(csp) + __builtin_cheri_length_get(csp);
+	csp = __builtin_cheri_address_set(csp, top - 8);
+	return (struct CleanupList **)csp;
+}
+
+/**
+ * Unwind the stack to the most recent `CHERIOT_HANDLER` block.
+ */
+__always_inline static inline void cleanup_unwind(void)
+{
+	CleanupList **__head = cleanup_list_head();
+	CleanupList  *__top  = *__head;
+	*__head              = __top->next;
+	longjmp(&__top->env, 1);
+}
+
+/**
+ * Simple error handling macros.  These are modelled on the OpenStep exception
+ * macros and are similarly built on top of `setjmp`.  Code between
+ * `CHERIOT_DURING` and `CHERIOT_HANDLER` corresponds to a `try` block.  Code
+ * between `CHERIOT_HANDLER` and `CHERIOT_END_HANDLER` corresponds to a `catch`
+ * block, though no exception value is actually thrown.
+ *
+ * Any automatic-storage values accessed in both blocks must be declared
+ * `volatile`.
+ */
+#define CHERIOT_DURING                                                         \
+	{                                                                          \
+		CleanupList cleanupListEntry;                                          \
+		auto      **__head    = cleanup_list_head();                           \
+		cleanupListEntry.next = *__head;                                       \
+		*__head               = &cleanupListEntry;                             \
+		if (setjmp(&cleanupListEntry.env) == 0)                                \
+		{
+/// See CHERIOT_DURING.
+#define CHERIOT_HANDLER                                                        \
+	*__head = cleanupListEntry.next;                                           \
+	}                                                                          \
+	else                                                                       \
+	{                                                                          \
+		*__head = cleanupListEntry.next;
+
+/// See CHERIOT_DURING.
+#define CHERIOT_END_HANDLER                                                    \
+	}                                                                          \
+	}
+
+#ifdef __cplusplus
+
+/**
+ * On-error helper.  Invokes `fn` and, if `cleanup_unwind` is called, invokes
+ * `err`.  Destructors in between `fn` and the frame that calls
+ * `cleanup_unwind` are not called, but this function returns normally and so
+ * destructors of objects above this on the stack will be called normally.
+ */
+void on_error(auto fn, auto err)
+{
+	CHERIOT_DURING
+	fn();
+	CHERIOT_HANDLER
+	err();
+	CHERIOT_END_HANDLER
+}
+
+/**
+ * On-error helper with no error handler (returns normally from forced unwind).
+ */
+void on_error(auto fn)
+{
+	on_error(fn, []() {});
+}
+#endif
diff --git a/tests/test-runner.cc b/tests/test-runner.cc
index dfc1d10..824de18 100644
--- a/tests/test-runner.cc
+++ b/tests/test-runner.cc
@@ -115,6 +115,7 @@
 		run_timed("Debug helpers (C++)", test_debug_cxx);
 		run_timed("Debug helpers (C)", test_debug_c);
 		run_timed("MMIO", test_mmio);
+		run_timed("Unwind cleanup", test_unwind_cleanup);
 		run_timed("stdio", test_stdio);
 		run_timed("Static sealing", test_static_sealing);
 		run_timed("Crash recovery", test_crash_recovery);
diff --git a/tests/tests.hh b/tests/tests.hh
index 4193ed3..c805bde 100644
--- a/tests/tests.hh
+++ b/tests/tests.hh
@@ -23,6 +23,7 @@
 __cheri_compartment("stdio_test") void test_stdio();
 __cheri_compartment("debug_test") void test_debug_cxx();
 __cheri_compartment("debug_test") void test_debug_c();
+__cheri_compartment("unwind_cleanup_test") void test_unwind_cleanup();
 
 // Simple tests don't need a separate compartment.
 void test_global_constructors();
diff --git a/tests/unwind_cleanup-test.cc b/tests/unwind_cleanup-test.cc
new file mode 100644
index 0000000..42158d2
--- /dev/null
+++ b/tests/unwind_cleanup-test.cc
@@ -0,0 +1,104 @@
+// Copyright CHERIoT Contributors.
+// SPDX-License-Identifier: MIT
+
+#define TEST_NAME "Test unwind cleanup"
+#include "locks.hh"
+#include "tests.hh"
+#include "unwind.h"
+
+extern "C" ErrorRecoveryBehaviour
+compartment_error_handler(ErrorState *, size_t, size_t)
+{
+	cleanup_unwind();
+	return ErrorRecoveryBehaviour::ForceUnwind;
+}
+
+namespace
+{
+	void test_setjmp()
+	{
+		jmp_buf      env;
+		volatile int x = 0;
+		if (int r = setjmp(env); r == 0)
+		{
+			TEST_EQUAL(x, 0, "setjmp should return 0 the first time");
+			x = 42;
+			longjmp(env, 1);
+		}
+		else
+		{
+			TEST_EQUAL(r, 1, "setjmp should return 1 the second time");
+			TEST_EQUAL(
+			  x, 42, "On the second return, x should have been modified");
+			x = 53;
+		}
+		TEST_EQUAL(x, 53, "After longjmp, x should have been modified");
+	}
+
+	FlagLock flagLock;
+
+	void test_on_error()
+	{
+		LockGuard g(flagLock);
+		on_error([&]() { cleanup_unwind(); }, [&]() { g.unlock(); });
+		TEST(!g, "on_error should lock the lock");
+	}
+
+	void test_on_error_raii_inner()
+	{
+		LockGuard g(flagLock);
+		// No handler.  g's destructor runs after on_error returns.
+		on_error([&]() { cleanup_unwind(); });
+	}
+
+	void test_on_error_raii()
+	{
+		test_on_error_raii_inner();
+		TEST(flagLock.try_lock(), "raii should have been dropped the lock");
+		flagLock.unlock();
+	}
+
+	void test_c_macros()
+	{
+		volatile int x = 0;
+		CHERIOT_DURING
+		{
+			x = 42;
+			cleanup_unwind();
+		}
+		CHERIOT_HANDLER
+		{
+			TEST_EQUAL(x, 42, "In the handler, x should have been modified");
+			x = 53;
+		}
+		CHERIOT_END_HANDLER
+		TEST_EQUAL(x, 53, "After longjmp, object should have been modified");
+	}
+
+	void test_from_trap()
+	{
+		volatile int x = 0;
+		CHERIOT_DURING
+		{
+			x = 42;
+			__builtin_trap();
+		}
+		CHERIOT_HANDLER
+		{
+			TEST_EQUAL(x, 42, "In the handler, x should have been modified");
+			x = 53;
+		}
+		CHERIOT_END_HANDLER
+		TEST_EQUAL(x, 53, "After longjmp, object should have been modified");
+	}
+
+} // namespace
+
+void test_unwind_cleanup()
+{
+	test_setjmp();
+	test_on_error();
+	test_c_macros();
+	test_from_trap();
+	debug_log("Test unwind_cleanup passed");
+}
diff --git a/tests/xmake.lua b/tests/xmake.lua
index bbc1d78..e40c5ab 100644
--- a/tests/xmake.lua
+++ b/tests/xmake.lua
@@ -88,6 +88,7 @@
     on_load(function(target)
         target:values_set("shared_objects", { exampleK = 1024, test_word = 4 }, {expand = false})
     end)
+test("unwind_cleanup")
 
 includes(path.join(sdkdir, "lib"))
 
@@ -122,6 +123,7 @@
     add_deps("misc_test")
     add_deps("stdio_test")
     add_deps("debug_test")
+    add_deps("unwind_cleanup_test")
     -- Set the thread entry point to the test runner.
     on_load(function(target)
         target:values_set("board", "$(board)")