loader: populate threads' initial cra

If a thread returns from its topmost compartment, it should now directly
enter the thread exit path without first going through a trap on an
untagged cra.
diff --git a/sdk/core/loader/boot.cc b/sdk/core/loader/boot.cc
index 12567fa..5bcb70c 100644
--- a/sdk/core/loader/boot.cc
+++ b/sdk/core/loader/boot.cc
@@ -823,6 +823,21 @@
 		// Space per thread for hazard pointers.
 		static constexpr size_t HazardPointerSpace =
 		  HazardPointersPerThread * sizeof(void *);
+
+		/*
+		 * Construct a return sentry with which to populate initial thread
+		 * register files, as if they had been entered by the switcher rather
+		 * than by fiat of initial construction.  The switcher will detect the
+		 * trusted stack underflow and will signal the scheduler that the thread
+		 * has exited and should not be brought back on core.
+		 */
+		auto threadInitialReturn =
+		  build<void, Root::Type::Execute, SwitcherPccPermissions>(
+		    image.switcher.code);
+		threadInitialReturn.address() += image.switcher.crossCallReturnEntry;
+		threadInitialReturn =
+		  seal_return<InterruptStatus::Disabled>(threadInitialReturn);
+
 		for (size_t i = 0; const auto &config : image.threads())
 		{
 			Debug::log("Creating thread {}", i);
@@ -858,6 +873,7 @@
 			        false>(config.trustedStack);
 			threadTStack->mepcc = pcc;
 			threadTStack->cgp   = cgp;
+			threadTStack->cra   = threadInitialReturn;
 			// Stacks have store-local but not global permission.
 			auto stack =
 			  build<void,