summaryrefslogtreecommitdiffstats
path: root/third_party/rust/lucet-runtime-wasmsbx/src
diff options
context:
space:
mode:
Diffstat (limited to 'third_party/rust/lucet-runtime-wasmsbx/src')
-rw-r--r--third_party/rust/lucet-runtime-wasmsbx/src/c_api.rs530
-rw-r--r--third_party/rust/lucet-runtime-wasmsbx/src/lib.rs384
2 files changed, 914 insertions, 0 deletions
diff --git a/third_party/rust/lucet-runtime-wasmsbx/src/c_api.rs b/third_party/rust/lucet-runtime-wasmsbx/src/c_api.rs
new file mode 100644
index 0000000000..d8ab481870
--- /dev/null
+++ b/third_party/rust/lucet-runtime-wasmsbx/src/c_api.rs
@@ -0,0 +1,530 @@
+use crate::{DlModule, Instance, Limits, MmapRegion, Module, Region};
+use libc::{c_char, c_int, c_void};
+use lucet_module::TrapCode;
+use lucet_runtime_internals::c_api::*;
+use lucet_runtime_internals::instance::{
+ instance_handle_from_raw, instance_handle_to_raw, InstanceInternal,
+};
+use lucet_runtime_internals::vmctx::VmctxInternal;
+use lucet_runtime_internals::WASM_PAGE_SIZE;
+use lucet_runtime_internals::{
+ assert_nonnull, lucet_hostcall_terminate, lucet_hostcalls, with_ffi_arcs,
+};
+use num_traits::FromPrimitive;
+use std::ffi::CStr;
+use std::ptr;
+use std::sync::{Arc, Once};
+
+macro_rules! with_instance_ptr {
+ ( $name:ident, $body:block ) => {{
+ assert_nonnull!($name);
+ let $name: &mut Instance = &mut *($name as *mut Instance);
+ $body
+ }};
+}
+
+macro_rules! with_instance_ptr_unchecked {
+ ( $name:ident, $body:block ) => {{
+ let $name: &mut Instance = &mut *($name as *mut Instance);
+ $body
+ }};
+}
+
+#[no_mangle]
+pub extern "C" fn lucet_error_name(e: c_int) -> *const c_char {
+ if let Some(e) = lucet_error::from_i32(e) {
+ use self::lucet_error::*;
+ match e {
+ Ok => "lucet_error_ok\0".as_ptr() as _,
+ InvalidArgument => "lucet_error_invalid_argument\0".as_ptr() as _,
+ RegionFull => "lucet_error_region_full\0".as_ptr() as _,
+ Module => "lucet_error_module\0".as_ptr() as _,
+ LimitsExceeded => "lucet_error_limits_exceeded\0".as_ptr() as _,
+ NoLinearMemory => "lucet_error_no_linear_memory\0".as_ptr() as _,
+ SymbolNotFound => "lucet_error_symbol_not_found\0".as_ptr() as _,
+ FuncNotFound => "lucet_error_func_not_found\0".as_ptr() as _,
+ RuntimeFault => "lucet_error_runtime_fault\0".as_ptr() as _,
+ RuntimeTerminated => "lucet_error_runtime_terminated\0".as_ptr() as _,
+ Dl => "lucet_error_dl\0".as_ptr() as _,
+ InstanceNotReturned => "lucet_error_instance_not_returned\0".as_ptr() as _,
+ InstanceNotYielded => "lucet_error_instance_not_yielded\0".as_ptr() as _,
+ StartYielded => "lucet_error_start_yielded\0".as_ptr() as _,
+ Internal => "lucet_error_internal\0".as_ptr() as _,
+ Unsupported => "lucet_error_unsupported\0".as_ptr() as _,
+ }
+ } else {
+ "!!! error: unknown lucet_error variant\0".as_ptr() as _
+ }
+}
+
+#[no_mangle]
+pub extern "C" fn lucet_result_tag_name(tag: libc::c_int) -> *const c_char {
+ if let Some(tag) = lucet_result_tag::from_i32(tag) {
+ use self::lucet_result_tag::*;
+ match tag {
+ Returned => "lucet_result_tag_returned\0".as_ptr() as _,
+ Yielded => "lucet_result_tag_yielded\0".as_ptr() as _,
+ Faulted => "lucet_result_tag_faulted\0".as_ptr() as _,
+ Terminated => "lucet_result_tag_terminated\0".as_ptr() as _,
+ Errored => "lucet_result_tag_errored\0".as_ptr() as _,
+ }
+ } else {
+ "!!! unknown lucet_result_tag variant!\0".as_ptr() as _
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn lucet_mmap_region_create(
+ instance_capacity: u64,
+ limits: *const lucet_alloc_limits,
+ region_out: *mut *mut lucet_region,
+) -> lucet_error {
+ assert_nonnull!(region_out);
+ let limits = limits
+ .as_ref()
+ .map(|l| l.into())
+ .unwrap_or(Limits::default());
+ match MmapRegion::create(instance_capacity as usize, &limits) {
+ Ok(region) => {
+ let region_thin = Arc::into_raw(Arc::new(region as Arc<dyn Region>));
+ region_out.write(region_thin as _);
+ return lucet_error::Ok;
+ }
+ Err(e) => return e.into(),
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn lucet_region_release(region: *const lucet_region) {
+ Arc::from_raw(region as *const Arc<dyn Region>);
+}
+
+// omg this naming convention might not scale
+#[no_mangle]
+pub unsafe extern "C" fn lucet_region_new_instance_with_ctx(
+ region: *const lucet_region,
+ module: *const lucet_dl_module,
+ embed_ctx: *mut c_void,
+ inst_out: *mut *mut lucet_instance,
+) -> lucet_error {
+ assert_nonnull!(inst_out);
+ with_ffi_arcs!([region: dyn Region, module: DlModule], {
+ region
+ .new_instance_builder(module.clone() as Arc<dyn Module>)
+ .with_embed_ctx(embed_ctx)
+ .build()
+ .map(|i| {
+ inst_out.write(instance_handle_to_raw(i) as _);
+ lucet_error::Ok
+ })
+ .unwrap_or_else(|e| e.into())
+ })
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn lucet_region_new_instance(
+ region: *const lucet_region,
+ module: *const lucet_dl_module,
+ inst_out: *mut *mut lucet_instance,
+) -> lucet_error {
+ lucet_region_new_instance_with_ctx(region, module, ptr::null_mut(), inst_out)
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn lucet_dl_module_load(
+ path: *const c_char,
+ mod_out: *mut *mut lucet_dl_module,
+) -> lucet_error {
+ assert_nonnull!(mod_out);
+ let path = CStr::from_ptr(path);
+ DlModule::load(path.to_string_lossy().into_owned())
+ .map(|m| {
+ mod_out.write(Arc::into_raw(m) as _);
+ lucet_error::Ok
+ })
+ .unwrap_or_else(|e| e.into())
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn lucet_dl_module_release(module: *const lucet_dl_module) {
+ Arc::from_raw(module as *const DlModule);
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn lucet_instance_run(
+ inst: *mut lucet_instance,
+ entrypoint: *const c_char,
+ argc: usize,
+ argv: *const lucet_val::lucet_val,
+ result_out: *mut lucet_result::lucet_result,
+) -> lucet_error {
+ assert_nonnull!(entrypoint);
+ if argc != 0 && argv.is_null() {
+ return lucet_error::InvalidArgument;
+ }
+ let args = if argc == 0 {
+ vec![]
+ } else {
+ std::slice::from_raw_parts(argv, argc)
+ .into_iter()
+ .map(|v| v.into())
+ .collect()
+ };
+ let entrypoint = match CStr::from_ptr(entrypoint).to_str() {
+ Ok(entrypoint_str) => entrypoint_str,
+ Err(_) => {
+ return lucet_error::SymbolNotFound;
+ }
+ };
+
+ with_instance_ptr!(inst, {
+ let res = inst.run(entrypoint, args.as_slice());
+ let ret = res
+ .as_ref()
+ .map(|_| lucet_error::Ok)
+ .unwrap_or_else(|e| e.into());
+ if !result_out.is_null() {
+ std::ptr::write(result_out, res.into());
+ }
+ ret
+ })
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn lucet_instance_run_func_idx(
+ inst: *mut lucet_instance,
+ table_idx: u32,
+ func_idx: u32,
+ argc: usize,
+ argv: *const lucet_val::lucet_val,
+ result_out: *mut lucet_result::lucet_result,
+) -> lucet_error {
+ if argc != 0 && argv.is_null() {
+ return lucet_error::InvalidArgument;
+ }
+ let args = if argc == 0 {
+ vec![]
+ } else {
+ std::slice::from_raw_parts(argv, argc)
+ .into_iter()
+ .map(|v| v.into())
+ .collect()
+ };
+ with_instance_ptr!(inst, {
+ let res = inst.run_func_idx(table_idx, func_idx, args.as_slice());
+ let ret = res
+ .as_ref()
+ .map(|_| lucet_error::Ok)
+ .unwrap_or_else(|e| e.into());
+ if !result_out.is_null() {
+ std::ptr::write(result_out, res.into());
+ }
+ ret
+ })
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn lucet_instance_resume(
+ inst: *const lucet_instance,
+ val: *mut c_void,
+ result_out: *mut lucet_result::lucet_result,
+) -> lucet_error {
+ with_instance_ptr!(inst, {
+ let res = inst.resume_with_val(CYieldedVal { val });
+ let ret = res
+ .as_ref()
+ .map(|_| lucet_error::Ok)
+ .unwrap_or_else(|e| e.into());
+ if !result_out.is_null() {
+ std::ptr::write(result_out, res.into());
+ }
+ ret
+ })
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn lucet_instance_reset(inst: *mut lucet_instance) -> lucet_error {
+ with_instance_ptr!(inst, {
+ inst.reset()
+ .map(|_| lucet_error::Ok)
+ .unwrap_or_else(|e| e.into())
+ })
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn lucet_instance_release(inst: *mut lucet_instance) {
+ instance_handle_from_raw(inst as *mut Instance, true);
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn lucet_instance_heap(inst: *mut lucet_instance) -> *mut u8 {
+ with_instance_ptr_unchecked!(inst, { inst.heap_mut().as_mut_ptr() })
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn lucet_instance_heap_len(inst: *const lucet_instance) -> u32 {
+ with_instance_ptr_unchecked!(inst, { inst.heap().len() as u32 })
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn lucet_instance_check_heap(
+ inst: *const lucet_instance,
+ ptr: *const c_void,
+ len: usize,
+) -> bool {
+ with_instance_ptr_unchecked!(inst, { inst.check_heap(ptr, len) })
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn lucet_instance_grow_heap(
+ inst: *mut lucet_instance,
+ additional_pages: u32,
+ previous_pages_out: *mut u32,
+) -> lucet_error {
+ with_instance_ptr!(inst, {
+ match inst.grow_memory(additional_pages) {
+ Ok(previous_pages) => {
+ if !previous_pages_out.is_null() {
+ previous_pages_out.write(previous_pages);
+ }
+ lucet_error::Ok
+ }
+ Err(e) => e.into(),
+ }
+ })
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn lucet_instance_embed_ctx(inst: *mut lucet_instance) -> *mut c_void {
+ with_instance_ptr_unchecked!(inst, {
+ inst.get_embed_ctx::<*mut c_void>()
+ .map(|r| r.map(|p| *p).unwrap_or(ptr::null_mut()))
+ .unwrap_or(ptr::null_mut())
+ })
+}
+
+/// Release or run* must not be called in the body of this function!
+#[no_mangle]
+pub unsafe extern "C" fn lucet_instance_set_signal_handler(
+ inst: *mut lucet_instance,
+ signal_handler: lucet_signal_handler,
+) -> lucet_error {
+ let handler = move |inst: &Instance, trap: &Option<TrapCode>, signum, siginfo, context| {
+ let inst = inst as *const Instance as *mut lucet_instance;
+ signal_handler(inst, trap.into(), signum, siginfo, context).into()
+ };
+ with_instance_ptr!(inst, {
+ inst.set_signal_handler(handler);
+ });
+ lucet_error::Ok
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn lucet_instance_set_fatal_handler(
+ inst: *mut lucet_instance,
+ fatal_handler: lucet_fatal_handler,
+) -> lucet_error {
+ // transmuting is fine here because *mut lucet_instance = *mut Instance
+ let fatal_handler: unsafe extern "C" fn(inst: *mut Instance) =
+ std::mem::transmute(fatal_handler);
+ with_instance_ptr!(inst, {
+ inst.set_c_fatal_handler(fatal_handler);
+ });
+ lucet_error::Ok
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn lucet_retval_gp(retval: *const lucet_untyped_retval) -> lucet_retval_gp {
+ lucet_retval_gp {
+ as_untyped: (*retval).gp,
+ }
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn lucet_retval_f32(retval: *const lucet_untyped_retval) -> f32 {
+ let mut v = 0.0f32;
+ core::arch::x86_64::_mm_storeu_ps(
+ &mut v as *mut f32,
+ core::arch::x86_64::_mm_loadu_ps((*retval).fp.as_ptr() as *const f32),
+ );
+ v
+}
+
+#[no_mangle]
+pub unsafe extern "C" fn lucet_retval_f64(retval: *const lucet_untyped_retval) -> f64 {
+ let mut v = 0.0f64;
+ core::arch::x86_64::_mm_storeu_pd(
+ &mut v as *mut f64,
+ core::arch::x86_64::_mm_loadu_pd((*retval).fp.as_ptr() as *const f64),
+ );
+ v
+}
+
+static C_API_INIT: Once = Once::new();
+
+/// Should never actually be called, but should be reachable via a trait method to prevent DCE.
+pub fn ensure_linked() {
+ use std::ptr::read_volatile;
+ C_API_INIT.call_once(|| unsafe {
+ read_volatile(lucet_vmctx_get_heap as *const extern "C" fn());
+ read_volatile(lucet_vmctx_current_memory as *const extern "C" fn());
+ read_volatile(lucet_vmctx_grow_memory as *const extern "C" fn());
+ });
+}
+
+lucet_hostcalls! {
+ #[no_mangle]
+ pub unsafe extern "C" fn lucet_vmctx_get_heap(
+ &mut vmctx,
+ ) -> *mut u8 {
+ vmctx.instance().alloc().slot().heap as *mut u8
+ }
+
+ #[no_mangle]
+ pub unsafe extern "C" fn lucet_vmctx_get_globals(
+ &mut vmctx,
+ ) -> *mut i64 {
+ vmctx.instance().alloc().slot().globals as *mut i64
+ }
+
+ #[no_mangle]
+ /// Get the number of WebAssembly pages currently in the heap.
+ pub unsafe extern "C" fn lucet_vmctx_current_memory(
+ &mut vmctx,
+ ) -> u32 {
+ vmctx.instance().alloc().heap_len() as u32 / WASM_PAGE_SIZE
+ }
+
+ #[no_mangle]
+ /// Grows the guest heap by the given number of WebAssembly pages.
+ ///
+ /// On success, returns the number of pages that existed before the call. On failure, returns `-1`.
+ pub unsafe extern "C" fn lucet_vmctx_grow_memory(
+ &mut vmctx,
+ additional_pages: u32,
+ ) -> i32 {
+ if let Ok(old_pages) = vmctx.instance_mut().grow_memory(additional_pages) {
+ old_pages as i32
+ } else {
+ -1
+ }
+ }
+
+ #[no_mangle]
+ /// Check if a memory region is inside the instance heap.
+ pub unsafe extern "C" fn lucet_vmctx_check_heap(
+ &mut vmctx,
+ ptr: *mut c_void,
+ len: libc::size_t,
+ ) -> bool {
+ vmctx.instance().check_heap(ptr, len)
+ }
+
+ #[no_mangle]
+ pub unsafe extern "C" fn lucet_vmctx_get_func_from_idx(
+ &mut vmctx,
+ table_idx: u32,
+ func_idx: u32,
+ ) -> *const c_void {
+ vmctx.instance()
+ .module()
+ .get_func_from_idx(table_idx, func_idx)
+ .map(|fptr| fptr.ptr.as_usize() as *const c_void)
+ .unwrap_or(std::ptr::null())
+ }
+
+ #[no_mangle]
+ pub unsafe extern "C" fn lucet_vmctx_terminate(
+ &mut _vmctx,
+ details: *mut c_void,
+ ) -> () {
+ lucet_hostcall_terminate!(CTerminationDetails { details });
+ }
+
+ #[no_mangle]
+ /// Get the delegate object for the current instance.
+ ///
+ /// TODO: rename
+ pub unsafe extern "C" fn lucet_vmctx_get_delegate(
+ &mut vmctx,
+ ) -> *mut c_void {
+ vmctx.instance()
+ .get_embed_ctx::<*mut c_void>()
+ .map(|r| r.map(|p| *p).unwrap_or(ptr::null_mut()))
+ .unwrap_or(std::ptr::null_mut())
+ }
+
+ #[no_mangle]
+ pub unsafe extern "C" fn lucet_vmctx_yield(
+ &mut vmctx,
+ val: *mut c_void,
+ ) -> *mut c_void {
+ vmctx
+ .yield_val_try_val(CYieldedVal { val })
+ .map(|CYieldedVal { val }| val)
+ .unwrap_or(std::ptr::null_mut())
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::lucet_dl_module;
+ use crate::DlModule;
+ use lucet_module::bindings::Bindings;
+ use lucet_wasi_sdk::{CompileOpts, LinkOpt, LinkOpts, Lucetc};
+ use lucetc::LucetcOpts;
+ use std::sync::Arc;
+ use tempfile::TempDir;
+
+ extern "C" {
+ fn lucet_runtime_test_expand_heap(module: *mut lucet_dl_module) -> bool;
+ fn lucet_runtime_test_yield_resume(module: *mut lucet_dl_module) -> bool;
+ }
+
+ #[test]
+ fn expand_heap() {
+ let workdir = TempDir::new().expect("create working directory");
+
+ let native_build = Lucetc::new(&["tests/guests/null.c"])
+ .with_cflag("-nostartfiles")
+ .with_link_opt(LinkOpt::NoDefaultEntryPoint)
+ .with_link_opt(LinkOpt::AllowUndefinedAll)
+ .with_link_opt(LinkOpt::ExportAll);
+
+ let so_file = workdir.path().join("null.so");
+
+ native_build.build(so_file.clone()).unwrap();
+
+ let dlmodule = DlModule::load(so_file).unwrap();
+
+ unsafe {
+ assert!(lucet_runtime_test_expand_heap(
+ Arc::into_raw(dlmodule) as *mut lucet_dl_module
+ ));
+ }
+ }
+
+ #[test]
+ fn yield_resume() {
+ let workdir = TempDir::new().expect("create working directory");
+
+ let native_build = Lucetc::new(&["tests/guests/yield_resume.c"])
+ .with_cflag("-nostartfiles")
+ .with_link_opt(LinkOpt::NoDefaultEntryPoint)
+ .with_link_opt(LinkOpt::AllowUndefinedAll)
+ .with_link_opt(LinkOpt::ExportAll)
+ .with_bindings(Bindings::from_file("tests/guests/yield_resume_bindings.json").unwrap());
+
+ let so_file = workdir.path().join("yield_resume.so");
+
+ native_build.build(so_file.clone()).unwrap();
+
+ let dlmodule = DlModule::load(so_file).unwrap();
+
+ unsafe {
+ assert!(lucet_runtime_test_yield_resume(
+ Arc::into_raw(dlmodule) as *mut lucet_dl_module
+ ));
+ }
+ }
+}
diff --git a/third_party/rust/lucet-runtime-wasmsbx/src/lib.rs b/third_party/rust/lucet-runtime-wasmsbx/src/lib.rs
new file mode 100644
index 0000000000..60126a9e41
--- /dev/null
+++ b/third_party/rust/lucet-runtime-wasmsbx/src/lib.rs
@@ -0,0 +1,384 @@
+//! # Lucet Runtime for Sandboxed WebAssembly Applications
+//!
+//! This crate runs programs that were compiled with the `lucetc` WebAssembly to native code
+//! compiler. It provides an interface for modules to be loaded from shared object files (see
+//! `DlModule`), and for hosts to provide specialized functionality to guests (see
+//! `Instance::embed_ctx()`).
+//!
+//! The runtime is a critical part of the safety and security story for Lucet. While the semantics
+//! of WebAssembly and the `lucetc` compiler provide many guarantees, the runtime must be correct in
+//! order for the assumptions of those guarantees to hold. For example, the runtime uses guard pages
+//! to ensure that any attempts by guest programs to access memory past the end of the guest heap are
+//! safely caught.
+//!
+//! The runtime is also extensible, and some of the key types are defined as traits for
+//! flexibility. See the `lucet-runtime-internals` crate for details.
+//!
+//! ## Running a Lucet Program
+//!
+//! There are a few essential types for using the runtime:
+//!
+//! - [`Instance`](struct.Instance.html): a Lucet program, together with its dedicated memory and
+//! signal handlers. Users of this API never own an `Instance` directly, but can own the
+//! [`InstanceHandle`](struct.InstanceHandle.html) smart pointer.
+//!
+//! - [`Region`](trait.Region.html): the memory from which instances are created. This crate
+//! includes [`MmapRegion`](struct.MmapRegion.html), an implementation backed by `mmap`.
+//!
+//! - [`Limits`](struct.Limits.html): upper bounds for the resources a Lucet instance may
+//! consume. These may be larger or smaller than the limits described in the WebAssembly module
+//! itself; the smaller limit is always enforced.
+//!
+//! - [`Module`](trait.Module.html): the read-only parts of a Lucet program, including its code and
+//! initial heap configuration. This crate includes [`DlModule`](struct.DlModule.html), an
+//! implementation backed by dynamic loading of shared objects.
+//!
+//! - [`Val`](enum.Val.html): an enum describing values in WebAssembly, used to provide
+//! arguments. These can be created using `From` implementations of primitive types, for example
+//! `5u64.into()` in the example below.
+//!
+//! - [`RunResult`](enum.RunResult.html): the result of running or resuming an instance. These
+//! contain either `UntypedRetVal`s for WebAssembly functions that have returned, or `YieldedVal`s
+//! for WebAssembly programs that have yielded.
+//!
+//! - [`UntypedRetVal`](struct.UntypedRetVal.html): values returned from WebAssembly
+//! functions. These must be interpreted at the correct type by the user via `From` implementations
+//! or `retval.as_T()` methods, for example `u64::from(retval)` in the example below.
+//!
+//! - [`YieldedVal`](struct.YieldedVal.html): dynamically-values yielded by WebAssembly
+//! programs. Not all yield points are given values, so this may be empty. To use the values, if
+//! present, you must first downcast them with the provided methods.
+//!
+//! To run a Lucet program, you start by creating a region, capable of backing a number of
+//! instances. You then load a module and then create a new instance using the region and the
+//! module. You can then run any of the functions that the Lucet program exports, retrieve return
+//! values from those functions, and access the linear memory of the guest.
+//!
+//! ```no_run
+//! use lucet_runtime::{DlModule, Limits, MmapRegion, Region};
+//!
+//! let module = DlModule::load("/my/lucet/module.so").unwrap();
+//! let region = MmapRegion::create(1, &Limits::default()).unwrap();
+//! let mut inst = region.new_instance(module).unwrap();
+//!
+//! let retval = inst.run("factorial", &[5u64.into()]).unwrap().unwrap_returned();
+//! assert_eq!(u64::from(retval), 120u64);
+//! ```
+//!
+//! ## Embedding With Hostcalls
+//!
+//! A "hostcall" is a function called by WebAssembly that is not defined in WebAssembly. Since
+//! WebAssembly is such a minimal language, hostcalls are required for Lucet programs to do anything
+//! interesting with the outside world. For example, in Fastly's [Terrarium
+//! demo](https://wasm.fastly-labs.com/), hostcalls are provided for manipulating HTTP requests,
+//! accessing a key/value store, etc.
+//!
+//! Some simple hostcalls can be implemented by wrapping an externed C function with the
+//! [`lucet_hostcalls!`](macro.lucet_hostcalls.html] macro. The function must take a special `&mut
+//! vmctx` argument for the guest context, similar to `&mut self` on methods. Hostcalls that require
+//! access to some underlying state, such as the key/value store in Terrarium, can access a custom
+//! embedder context through `vmctx`. For example, to make a `u32` available to hostcalls:
+//!
+//! ```no_run
+//! use lucet_runtime::{DlModule, Limits, MmapRegion, Region, lucet_hostcalls};
+//! use lucet_runtime::vmctx::{Vmctx, lucet_vmctx};
+//!
+//! struct MyContext { x: u32 }
+//!
+//! lucet_hostcalls! {
+//! #[no_mangle]
+//! pub unsafe extern "C" fn foo(
+//! &mut vmctx,
+//! ) -> () {
+//! let mut hostcall_context = vmctx.get_embed_ctx_mut::<MyContext>();
+//! hostcall_context.x = 42;
+//! }
+//! }
+//!
+//! let module = DlModule::load("/my/lucet/module.so").unwrap();
+//! let region = MmapRegion::create(1, &Limits::default()).unwrap();
+//! let mut inst = region
+//! .new_instance_builder(module)
+//! .with_embed_ctx(MyContext { x: 0 })
+//! .build()
+//! .unwrap();
+//!
+//! inst.run("call_foo", &[]).unwrap();
+//!
+//! let context_after = inst.get_embed_ctx::<MyContext>().unwrap().unwrap();
+//! assert_eq!(context_after.x, 42);
+//! ```
+//!
+//! The embedder context is backed by a structure that can hold a single value of any type. Rust
+//! embedders should add their own custom state type (like `MyContext` above) for any context they
+//! require, rather than using a common type (such as the `u32`) from the standard library. This
+//! avoids collisions between libraries, and allows for easy composition of embeddings.
+//!
+//! For C-based embedders, the type `*mut libc::c_void` is privileged as the only type that the C
+//! API provides. The following example shows how a Rust embedder can initialize a C-compatible
+//! context:
+//!
+//! ```no_run
+//! use lucet_runtime::{DlModule, Limits, MmapRegion, Region};
+//!
+//! let module = DlModule::load("/my/lucet/module.so").unwrap();
+//! let region = MmapRegion::create(1, &Limits::default()).unwrap();
+//! #[repr(C)]
+//! struct MyForeignContext { x: u32 };
+//! let mut foreign_ctx = Box::into_raw(Box::new(MyForeignContext{ x: 0 }));
+//! let mut inst = region
+//! .new_instance_builder(module)
+//! .with_embed_ctx(foreign_ctx as *mut libc::c_void)
+//! .build()
+//! .unwrap();
+//!
+//! inst.run("main", &[]).unwrap();
+//!
+//! // clean up embedder context
+//! drop(inst);
+//! // foreign_ctx must outlive inst, but then must be turned back into a box
+//! // in order to drop.
+//! unsafe { Box::from_raw(foreign_ctx) };
+//! ```
+//!
+//! ## Yielding and Resuming
+//!
+//! Lucet hostcalls can use the `vmctx` argument to yield, suspending themselves and optionally
+//! returning a value back to the host context. A yielded instance can then be resumed by the host,
+//! and execution will continue from the point of the yield.
+//!
+//! Four yield methods are available for hostcall implementors:
+//!
+//! | | Yields value? | Expects value? |
+//! |-------------------------------------------------------------------------------------|---------------|----------------|
+//! | [`yield_`](vmctx/struct.Vmctx.html#method.yield_) | ❌ | ❌ |
+//! | [`yield_val`](vmctx/struct.Vmctx.html#method.yield_val) | ✅ | ❌ |
+//! | [`yield_expecting_val`](vmctx/struct.Vmctx.html#method.yield_expecting_val) | ❌ | ✅ |
+//! | [`yield_val_expecting_val`](vmctx/struct.Vmctx.html#method.yield_val_expecting_val) | ✅ | ✅ |
+//!
+//! The host is free to ignore values yielded by guests, but a yielded instance may only be resumed
+//! with a value of the correct type using
+//! [`Instance::resume_with_val()`](struct.Instance.html#method.resume_with_val), if one is
+//! expected.
+//!
+//! ### Factorial example
+//!
+//! In this example, we use yielding and resuming to offload multiplication to the host context, and
+//! to incrementally return results to the host. While certainly overkill for computing a factorial
+//! function, this structure mirrors that of many asynchronous workflows.
+//!
+//! Since the focus of this example is on the behavior of hostcalls that yield, our Lucet guest
+//! program just invokes a hostcall:
+//!
+//! ```no_run
+//! // factorials_guest.rs
+//! extern "C" {
+//! fn hostcall_factorials(n: u64) -> u64;
+//! }
+//!
+//! #[no_mangle]
+//! pub extern "C" fn run() -> u64 {
+//! unsafe {
+//! hostcall_factorials(5)
+//! }
+//! }
+//! ```
+//!
+//! In our hostcall, there are two changes from a standard recursive implementation of factorial.
+//!
+//! - Instead of performing the `n * fact(n - 1)` multiplication ourselves, we yield the operands
+//! and expect the product when resumed.
+//!
+//! - Whenever we have computed a factorial, including both intermediate values and the final
+//! answer, we yield it.
+//!
+//! The final answer is returned normally as the result of the guest function.
+//!
+//! To implement this, we introduce a new `enum` type to represent what we want the host to do next,
+//! and yield it when appropriate.
+//!
+//! ```no_run
+//! use lucet_runtime::lucet_hostcalls;
+//! use lucet_runtime::vmctx::Vmctx;
+//!
+//! pub enum FactorialsK {
+//! Mult(u64, u64),
+//! Result(u64),
+//! }
+//!
+//! lucet_hostcalls! {
+//! #[no_mangle]
+//! pub unsafe extern "C" fn hostcall_factorials(
+//! &mut vmctx,
+//! n: u64,
+//! ) -> u64 {
+//! fn fact(vmctx: &mut Vmctx, n: u64) -> u64 {
+//! let result = if n <= 1 {
+//! 1
+//! } else {
+//! let n_rec = fact(vmctx, n - 1);
+//! // yield a request for the host to perform multiplication
+//! vmctx.yield_val_expecting_val(FactorialsK::Mult(n, n_rec))
+//! // once resumed, that yield evaluates to the multiplication result
+//! };
+//! // yield a result
+//! vmctx.yield_val(FactorialsK::Result(result));
+//! result
+//! }
+//! fact(vmctx, n)
+//! }
+//! }
+//! ```
+//!
+//! The host side of the code, then, is an interpreter that repeatedly checks the yielded value and
+//! performs the appropriate operation. The hostcall returns normally with the final answer when it
+//! is finished, so we exit the loop when the run/resume result is `Ok`.
+//!
+//! ```no_run
+//! # pub enum FactorialsK {
+//! # Mult(u64, u64),
+//! # Result(u64),
+//! # }
+//! use lucet_runtime::{DlModule, Error, Limits, MmapRegion, Region};
+//!
+//! let module = DlModule::load("factorials_guest.so").unwrap();
+//! let region = MmapRegion::create(1, &Limits::default()).unwrap();
+//! let mut inst = region.new_instance(module).unwrap();
+//!
+//! let mut factorials = vec![];
+//!
+//! let mut res = inst.run("run", &[]).unwrap();
+//!
+//! while let Ok(val) = res.yielded_ref() {
+//! if let Some(k) = val.downcast_ref::<FactorialsK>() {
+//! match k {
+//! FactorialsK::Mult(n, n_rec) => {
+//! // guest wants us to multiply for it
+//! res = inst.resume_with_val(n * n_rec).unwrap();
+//! }
+//! FactorialsK::Result(n) => {
+//! // guest is returning an answer
+//! factorials.push(*n);
+//! res = inst.resume().unwrap();
+//! }
+//! }
+//! } else {
+//! panic!("didn't yield with expected type");
+//! }
+//! }
+//!
+//! // intermediate values are correct
+//! assert_eq!(factorials.as_slice(), &[1, 2, 6, 24, 120]);
+//! // final value is correct
+//! assert_eq!(u64::from(res.unwrap_returned()), 120u64);
+//! ```
+//!
+//! ## Custom Signal Handlers
+//!
+//! Since Lucet programs are run as native machine code, signals such as `SIGSEGV` and `SIGFPE` can
+//! arise during execution. Rather than letting these signals bring down the entire process, the
+//! Lucet runtime installs alternate signal handlers that limit the effects to just the instance
+//! that raised the signal.
+//!
+//! By default, the signal handler sets the instance state to `State::Fault` and returns early from
+//! the call to `Instance::run()`. You can, however, implement custom error recovery and logging
+//! behavior by defining new signal handlers on a per-instance basis. For example, the following
+//! signal handler increments a counter of signals it has seen before setting the fault state:
+//!
+//! ```no_run
+//! use lucet_runtime::{
+//! DlModule, Error, Instance, Limits, MmapRegion, Region, SignalBehavior, TrapCode,
+//! };
+//! use std::sync::atomic::{AtomicUsize, Ordering, ATOMIC_USIZE_INIT};
+//!
+//! static SIGNAL_COUNT: AtomicUsize = ATOMIC_USIZE_INIT;
+//!
+//! fn signal_handler_count(
+//! _inst: &Instance,
+//! _trapcode: &Option<TrapCode>,
+//! _signum: libc::c_int,
+//! _siginfo_ptr: *const libc::siginfo_t,
+//! _ucontext_ptr: *const libc::c_void,
+//! ) -> SignalBehavior {
+//! SIGNAL_COUNT.fetch_add(1, Ordering::SeqCst);
+//! SignalBehavior::Default
+//! }
+//!
+//! let module = DlModule::load("/my/lucet/module.so").unwrap();
+//! let region = MmapRegion::create(1, &Limits::default()).unwrap();
+//! let mut inst = region.new_instance(module).unwrap();
+//!
+//! // install the handler
+//! inst.set_signal_handler(signal_handler_count);
+//!
+//! match inst.run("raise_a_signal", &[]) {
+//! Err(Error::RuntimeFault(_)) => {
+//! println!("I've now handled {} signals!", SIGNAL_COUNT.load(Ordering::SeqCst));
+//! }
+//! res => panic!("unexpected result: {:?}", res),
+//! }
+//! ```
+//!
+//! When implementing custom signal handlers for the Lucet runtime, the usual caveats about signal
+//! safety apply: see
+//! [`signal-safety(7)`](http://man7.org/linux/man-pages/man7/signal-safety.7.html).
+//!
+//! ## Interaction With Host Signal Handlers
+//!
+//! Great care must be taken if host application installs or otherwise modifies signal handlers
+//! anywhere in the process. Lucet installs handlers for `SIGBUS`, `SIGFPE`, `SIGILL`, and `SIGSEGV`
+//! when the first Lucet instance begins running, and restores the preëxisting handlers when the
+//! last Lucet instance terminates. During this time, other threads in the host process *must not*
+//! modify those signal handlers, since signal handlers can only be installed on a process-wide
+//! basis.
+//!
+//! Despite this limitation, Lucet is designed to compose with other signal handlers in the host
+//! program. If one of the above signals is caught by the Lucet signal handler, but that thread is
+//! not currently running a Lucet instance, the saved host signal handler is called. This means
+//! that, for example, a `SIGSEGV` on a non-Lucet thread of a host program will still likely abort
+//! the entire process.
+
+#![deny(bare_trait_objects)]
+
+pub mod c_api;
+
+#[cfg(feature = "signature_checking")]
+pub use lucet_module::PublicKey;
+pub use lucet_module::TrapCode;
+pub use lucet_runtime_internals::alloc::Limits;
+pub use lucet_runtime_internals::error::Error;
+pub use lucet_runtime_internals::instance::{
+ FaultDetails, Instance, InstanceHandle, RunResult, SignalBehavior, TerminationDetails,
+ YieldedVal,
+};
+pub use lucet_runtime_internals::module::{DlModule, Module};
+pub use lucet_runtime_internals::region::mmap::MmapRegion;
+pub use lucet_runtime_internals::region::{InstanceBuilder, Region, RegionCreate};
+pub use lucet_runtime_internals::val::{UntypedRetVal, Val};
+pub use lucet_runtime_internals::{lucet_hostcall_terminate, lucet_hostcalls, WASM_PAGE_SIZE};
+
+pub mod vmctx {
+ //! Functions for manipulating instances from hostcalls.
+ //!
+ //! The Lucet compiler inserts an extra `*mut lucet_vmctx` argument to all functions defined and
+ //! called by WebAssembly code. Through this pointer, code running in the guest context can
+ //! access and manipulate the instance and its structures. These functions are intended for use
+ //! in hostcall implementations, and must only be used from within a running guest.
+ //!
+ //! # Panics
+ //!
+ //! All of the `Vmctx` methods will panic if the `Vmctx` was not created from a valid pointer
+ //! associated with a running instance. This should never occur if run in guest code on the
+ //! pointer argument inserted by the compiler.
+ pub use lucet_runtime_internals::vmctx::{lucet_vmctx, Vmctx};
+}
+
+/// Call this if you're having trouble with `lucet_*` symbols not being exported.
+///
+/// This is pretty hackish; we will hopefully be able to avoid this altogether once [this
+/// issue](https://github.com/rust-lang/rust/issues/58037) is addressed.
+#[no_mangle]
+#[doc(hidden)]
+pub extern "C" fn lucet_internal_ensure_linked() {
+ self::c_api::ensure_linked();
+}