| #pragma once | 
 |  | 
 | #include "secret.h" | 
 | #include <debug.hh> | 
 | #include <functional> | 
 | #include <magic_enum/magic_enum.hpp> | 
 | #include <microvium/microvium.h> | 
 | #include <tuple> | 
 |  | 
 | /** | 
 |  * Code related to the JavaScript interpreter. | 
 |  */ | 
 | namespace | 
 | { | 
 | 	using CHERI::Capability; | 
 |  | 
 | 	/** | 
 | 	 * Constants for functions exposed to JavaScript->C++ FFI | 
 | 	 * | 
 | 	 * The values here must match the ones used in cheri.js. | 
 | 	 */ | 
 | 	enum Exports : mvm_HostFunctionID | 
 | 	{ | 
 | 		Print = 1, | 
 | 		Move, | 
 | 		LoadCapability, | 
 | 		LoadInt, | 
 | 		Store, | 
 | 		GetAddress, | 
 | 		SetAddress, | 
 | 		GetBase, | 
 | 		GetLength, | 
 | 		GetPermissions, | 
 | 		CheckSecret, | 
 | 	}; | 
 |  | 
 | 	/// Constant for the say_hello function exposed to C++->JavaScript FFI | 
 | 	static constexpr mvm_VMExportID ExportRun = 1234; | 
 |  | 
 | 	/** | 
 | 	 * Type used for the set of capabilities that JavaScript has complete | 
 | 	 * control over. | 
 | 	 */ | 
 | 	using AttackerRegisterState = std::array<void *, 8>; | 
 |  | 
 | 	/** | 
 | 	 * Address of the `AttackerRegisterState` on the stack.  This structure is | 
 | 	 * on the stack so that it can hold local capabilities. | 
 | 	 */ | 
 | 	ptraddr_t attackerRegisterStateAddress; | 
 |  | 
 | 	/** | 
 | 	 * Re-derive the pointer to the on-stack register state. | 
 | 	 */ | 
 | 	AttackerRegisterState &state() | 
 | 	{ | 
 | 		register AttackerRegisterState *cspRegister asm("csp"); | 
 | 		asm("" : "=C"(cspRegister)); | 
 | 		Capability<AttackerRegisterState> rs{cspRegister}; | 
 | 		rs.address() = attackerRegisterStateAddress; | 
 | 		rs.bounds()  = sizeof(AttackerRegisterState); | 
 | 		return *rs; | 
 | 	} | 
 |  | 
 | 	/** | 
 | 	 * Write a capability into one of the VM's register set. | 
 | 	 */ | 
 | 	void register_write(int regno, void *value) | 
 | 	{ | 
 | 		if ((regno >= 0) && (regno < 8)) | 
 | 		{ | 
 | 			state()[regno] = value; | 
 | 		} | 
 | 	} | 
 |  | 
 | 	/** | 
 | 	 * Read a register from the VM's register set.  This reads one of the 8 | 
 | 	 * that can be written or CSP (8), CGP (9), or PCC (10). | 
 | 	 */ | 
 | 	void *register_read(int regno) | 
 | 	{ | 
 | 		switch (regno) | 
 | 		{ | 
 | 			default: | 
 | 				return nullptr; | 
 | 			case 0 ... 7: | 
 | 				return state()[regno]; | 
 | 			case 8: | 
 | 			{ | 
 | 				register void *cspRegister asm("csp"); | 
 | 				asm("" : "=C"(cspRegister)); | 
 | 				return cspRegister; | 
 | 			} | 
 | 			case 9: | 
 | 			{ | 
 | 				register void *cgpRegister asm("cgp"); | 
 | 				asm("" : "=C"(cgpRegister)); | 
 | 				return cgpRegister; | 
 | 			} | 
 | 			case 10: | 
 | 			{ | 
 | 				void *pcc; | 
 | 				asm("auipcc %0, 0\n" : "=C"(pcc)); | 
 | 				return pcc; | 
 | 			} | 
 | 		} | 
 | 	} | 
 |  | 
 | 	/** | 
 | 	 * Template that returns JavaScript argument specified in `arg` as a C++ | 
 | 	 * type T. | 
 | 	 */ | 
 | 	template<typename T> | 
 | 	T extract_argument(mvm_VM *vm, mvm_Value arg); | 
 |  | 
 | 	/** | 
 | 	 * Specialisation to return integers. | 
 | 	 */ | 
 | 	template<> | 
 | 	__always_inline int32_t extract_argument<int32_t>(mvm_VM *vm, mvm_Value arg) | 
 | 	{ | 
 | 		return mvm_toInt32(vm, arg); | 
 | 	} | 
 |  | 
 | 	/** | 
 | 	 * Specialisation to return booleans. | 
 | 	 */ | 
 | 	template<> | 
 | 	__always_inline bool extract_argument<bool>(mvm_VM *vm, mvm_Value arg) | 
 | 	{ | 
 | 		return mvm_toBool(vm, arg); | 
 | 	} | 
 |  | 
 | 	/** | 
 | 	 * Populate a tuple with arguments from an array of JavaScript values. | 
 | 	 * This uses `extract_argument` to coerce each JavaScript value to the | 
 | 	 * expected type. | 
 | 	 */ | 
 | 	template<typename Tuple, int Idx = 0> | 
 | 	__always_inline void | 
 | 	args_to_tuple(Tuple &tuple, mvm_VM *vm, mvm_Value *args) | 
 | 	{ | 
 | 		if constexpr (Idx < std::tuple_size_v<Tuple>) | 
 | 		{ | 
 | 			std::get<Idx>(tuple) = extract_argument< | 
 | 			  std::remove_reference_t<decltype(std::get<Idx>(tuple))>>( | 
 | 			  vm, args[Idx]); | 
 | 			args_to_tuple<Tuple, Idx + 1>(tuple, vm, args); | 
 | 		} | 
 | 	} | 
 |  | 
 | 	/** | 
 | 	 * Helper template to extract the arguments from a function type. | 
 | 	 */ | 
 | 	template<typename T> | 
 | 	struct FunctionSignature; | 
 |  | 
 | 	/** | 
 | 	 * The concrete specialisation that decomposes the function type. | 
 | 	 */ | 
 | 	template<typename R, typename... Args> | 
 | 	struct FunctionSignature<R(Args...)> | 
 | 	{ | 
 | 		/** | 
 | 		 * A tuple type containing all of the argument types of the function | 
 | 		 * whose type is being extracted. | 
 | 		 */ | 
 | 		using ArgumentType = std::tuple<Args...>; | 
 | 	}; | 
 |  | 
 | 	/** | 
 | 	 * The concrete specialisation that decomposes the function type for a cross | 
 | 	 * compartment call. | 
 | 	 */ | 
 | 	template<typename R, typename... Args> | 
 | 	struct FunctionSignature<R __attribute__((cheri_ccall)) (Args...)> | 
 | 	{ | 
 | 		/** | 
 | 		 * A tuple type containing all of the argument types of the function | 
 | 		 * whose type is being extracted. | 
 | 		 */ | 
 | 		using ArgumentType = std::tuple<Args...>; | 
 | 	}; | 
 |  | 
 | 	/** | 
 | 	 * Call `Fn` with arguments from the Microvium arguments array. | 
 | 	 * | 
 | 	 * This is a wrapper that allows automatic forwarding from a function | 
 | 	 * exported to JavaScript | 
 | 	 */ | 
 | 	template<auto Fn> | 
 | 	__always_inline mvm_TeError call_export(mvm_VM    *vm, | 
 | 	                                        mvm_Value *result, | 
 | 	                                        mvm_Value *args, | 
 | 	                                        uint8_t    argsCount) | 
 | 	{ | 
 | 		using TupleType = typename FunctionSignature< | 
 | 		  std::remove_pointer_t<decltype(Fn)>>::ArgumentType; | 
 | 		// Return an error if we have the wrong number of arguments. | 
 | 		if (argsCount < std::tuple_size_v<TupleType>) | 
 | 		{ | 
 | 			return MVM_E_UNEXPECTED; | 
 | 		} | 
 | 		// Get the arguments in a tuple. | 
 | 		TupleType arguments; | 
 | 		args_to_tuple(arguments, vm, args); | 
 | 		// If this returns void, we don't need to do anything with the return. | 
 | 		if constexpr (std::is_same_v<void, decltype(std::apply(Fn, arguments))>) | 
 | 		{ | 
 | 			std::apply(Fn, arguments); | 
 | 		} | 
 | 		else | 
 | 		{ | 
 | 			// Coerce the return type to a JavaScript object of the correct | 
 | 			// type and return it. | 
 | 			auto primitiveResult = std::apply(Fn, arguments); | 
 | 			if constexpr (std::is_same_v<decltype(primitiveResult), bool>) | 
 | 			{ | 
 | 				*result = mvm_newBoolean(primitiveResult); | 
 | 			} | 
 | 			if constexpr (std::is_same_v<decltype(primitiveResult), int32_t>) | 
 | 			{ | 
 | 				*result = mvm_newInt32(vm, primitiveResult); | 
 | 			} | 
 | 			if constexpr (std::is_same_v<decltype(primitiveResult), | 
 | 			                             std::string>) | 
 | 			{ | 
 | 				*result = mvm_newString( | 
 | 				  vm, primitiveResult.data(), primitiveResult.size()); | 
 | 			} | 
 | 		} | 
 | 		return MVM_E_SUCCESS; | 
 | 	} | 
 |  | 
 | 	/** | 
 | 	 * Helper that maps from Exports | 
 | 	 */ | 
 | 	template<Exports> | 
 | 	constexpr static std::nullptr_t ExportedFn = nullptr; | 
 |  | 
 | 	/** | 
 | 	 * Move a value from the source register to the destination. | 
 | 	 */ | 
 | 	void export_move(int32_t destination, int32_t source) | 
 | 	{ | 
 | 		register_write(destination, register_read(source)); | 
 | 	} | 
 |  | 
 | 	template<> | 
 | 	constexpr static auto ExportedFn<Move> = export_move; | 
 |  | 
 | 	/** | 
 | 	 * Load a capability into the destination register. | 
 | 	 */ | 
 | 	void export_load(int32_t destination, int32_t source, int32_t offset) | 
 | 	{ | 
 | 		Capability<void *> s{static_cast<void **>(register_read(source))}; | 
 | 		s.address() += offset; | 
 | 		register_write(destination, *s); | 
 | 	} | 
 |  | 
 | 	template<> | 
 | 	constexpr static auto ExportedFn<LoadCapability> = export_load; | 
 |  | 
 | 	/** | 
 | 	 * Load and return an integer. | 
 | 	 */ | 
 | 	int32_t export_load_int(int32_t source, int32_t offset) | 
 | 	{ | 
 | 		Capability<int32_t> s{static_cast<int32_t *>(register_read(source))}; | 
 | 		s.address() += offset; | 
 | 		return *s; | 
 | 	} | 
 |  | 
 | 	template<> | 
 | 	constexpr static auto ExportedFn<LoadInt> = export_load_int; | 
 |  | 
 | 	/** | 
 | 	 * Store a capability from a register at a specified location. | 
 | 	 */ | 
 | 	void export_store(int32_t value, int32_t source, int32_t offset) | 
 | 	{ | 
 | 		Capability<void *> s{static_cast<void **>(register_read(source))}; | 
 | 		s.address() += offset; | 
 | 		*s = register_read(value); | 
 | 	} | 
 |  | 
 | 	template<> | 
 | 	constexpr static auto ExportedFn<Store> = export_store; | 
 |  | 
 | 	/** | 
 | 	 * Returns the address of the capability in the specified register. | 
 | 	 */ | 
 | 	int32_t export_get_address(int32_t regno) | 
 | 	{ | 
 | 		Capability<void> value{register_read(regno)}; | 
 | 		return value.address(); | 
 | 	} | 
 |  | 
 | 	template<> | 
 | 	constexpr static auto ExportedFn<GetAddress> = export_get_address; | 
 |  | 
 | 	/** | 
 | 	 * Set the address of the capability in the specified register. | 
 | 	 */ | 
 | 	void export_set_address(int32_t regno, int32_t address) | 
 | 	{ | 
 | 		Capability<void> value{register_read(regno)}; | 
 | 		value.address() = address; | 
 | 		register_write(regno, value); | 
 | 	} | 
 |  | 
 | 	template<> | 
 | 	constexpr static auto ExportedFn<SetAddress> = export_set_address; | 
 |  | 
 | 	/** | 
 | 	 * Return the base address of the capability in the specified register. | 
 | 	 */ | 
 | 	int32_t export_get_base(int32_t regno) | 
 | 	{ | 
 | 		Capability<void> value{register_read(regno)}; | 
 | 		return value.base(); | 
 | 	} | 
 |  | 
 | 	template<> | 
 | 	constexpr static auto ExportedFn<GetBase> = export_get_base; | 
 |  | 
 | 	/** | 
 | 	 * Return the length of the capability in the specified register. | 
 | 	 */ | 
 | 	int32_t export_get_length(int32_t regno) | 
 | 	{ | 
 | 		Capability<void> value{register_read(regno)}; | 
 | 		return value.length(); | 
 | 	} | 
 |  | 
 | 	template<> | 
 | 	constexpr static auto ExportedFn<GetLength> = export_get_length; | 
 |  | 
 | 	/** | 
 | 	 * Return the permissions of the capability in the specified register. | 
 | 	 */ | 
 | 	int32_t export_get_permissions(int32_t regno) | 
 | 	{ | 
 | 		Capability<void> value{register_read(regno)}; | 
 | 		return value.permissions().as_raw(); | 
 | 	} | 
 |  | 
 | 	template<> | 
 | 	constexpr static auto ExportedFn<GetPermissions> = export_get_permissions; | 
 |  | 
 | 	template<> | 
 | 	constexpr static auto ExportedFn<CheckSecret> = check_secret; | 
 |  | 
 | 	/** | 
 | 	 * Base template for exported functions.  Forwards to the function defined | 
 | 	 * with `ExportedFn<E>`. | 
 | 	 */ | 
 | 	template<Exports E> | 
 | 	mvm_TeError exported_function(mvm_VM *vm, | 
 | 	                              mvm_HostFunctionID, | 
 | 	                              mvm_Value *result, | 
 | 	                              mvm_Value *args, | 
 | 	                              uint8_t    argCount) | 
 | 	{ | 
 | 		return call_export<ExportedFn<E>>(vm, result, args, argCount); | 
 | 	} | 
 |  | 
 | 	/** | 
 | 	 * Print a string passed from JavaScript. | 
 | 	 */ | 
 | 	template<> | 
 | 	mvm_TeError exported_function<Print>(mvm_VM            *vm, | 
 | 	                                     mvm_HostFunctionID funcID, | 
 | 	                                     mvm_Value         *result, | 
 | 	                                     mvm_Value         *args, | 
 | 	                                     uint8_t            argCount) | 
 | 	{ | 
 | 		auto *uart = MMIO_CAPABILITY(Uart, uart); | 
 | 		// Iterate over the arguments. | 
 | 		for (unsigned i = 0; i < argCount; i++) | 
 | 		{ | 
 | 			// Coerce the argument to a string and get it as a C string | 
 | 			const char *str = mvm_toStringUtf8(vm, args[i], nullptr); | 
 | 			// Write each character to the UART | 
 | 			for (; *str != '\0'; str++) | 
 | 			{ | 
 | 				uart->blocking_write(*str); | 
 | 			} | 
 | 		} | 
 | 		// Write a trailing newline | 
 | 		uart->blocking_write('\n'); | 
 | 		// Unconditionally return success | 
 | 		return MVM_E_SUCCESS; | 
 | 	} | 
 |  | 
 | 	/** | 
 | 	 * Callback from microvium that resolves imports. | 
 | 	 * | 
 | 	 * This resolves each function to the template instantiation of | 
 | 	 * `exported_function` with `funcID` as the template parameter. | 
 | 	 */ | 
 | 	mvm_TeError | 
 | 	resolve_import(mvm_HostFunctionID funcID, void *, mvm_TfHostFunction *out) | 
 | 	{ | 
 | 		return magic_enum::enum_switch( | 
 | 		  [&](auto val) { | 
 | 			  constexpr Exports Export = val; | 
 | 			  *out                     = exported_function<Export>; | 
 | 			  return MVM_E_SUCCESS; | 
 | 		  }, | 
 | 		  Exports(funcID), | 
 | 		  MVM_E_UNRESOLVED_IMPORT); | 
 | 	} | 
 |  | 
 | 	/** | 
 | 	 * Helper that deletes a Microvium VM when used with a C++ unique pointer. | 
 | 	 */ | 
 | 	struct MVMDeleter | 
 | 	{ | 
 | 		void operator()(mvm_VM *mvm) const | 
 | 		{ | 
 | 			mvm_free(mvm); | 
 | 		} | 
 | 	}; | 
 | } // namespace |