diff --git a/Cargo.lock b/Cargo.lock index 8630e5ebe2d4..3f70d7811d8b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -991,6 +991,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "dlmalloc" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "203540e710bfadb90e5e29930baf5d10270cec1f43ab34f46f78b147b2de715a" +dependencies = [ + "libc", +] + [[package]] name = "downcast-rs" version = "1.2.0" @@ -1017,6 +1026,15 @@ version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" +[[package]] +name = "embedding" +version = "16.0.0" +dependencies = [ + "anyhow", + "dlmalloc", + "wasmtime", +] + [[package]] name = "encode_unicode" version = "0.3.6" @@ -1755,6 +1773,16 @@ dependencies = [ "autocfg", ] +[[package]] +name = "min-platform-host" +version = "16.0.0" +dependencies = [ + "anyhow", + "libloading", + "object", + "wasmtime", +] + [[package]] name = "miniz_oxide" version = "0.7.1" diff --git a/Cargo.toml b/Cargo.toml index b18a39960c2e..d263944f6335 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -116,6 +116,8 @@ members = [ "examples/fib-debug/wasm", "examples/wasi/wasm", "examples/tokio/wasm", + "examples/min-platform", + "examples/min-platform/embedding", "fuzz", "winch", "winch/codegen", diff --git a/cranelift/Cargo.toml b/cranelift/Cargo.toml index 06dcf4ef8ce6..31dd54578b1b 100644 --- a/cranelift/Cargo.toml +++ b/cranelift/Cargo.toml @@ -23,7 +23,7 @@ harness = false [dependencies] cfg-if = { workspace = true } -cranelift-codegen = { workspace = true, features = ["disas", "trace-log"] } +cranelift-codegen = { workspace = true, features = ["disas", "trace-log", "timing"] } cranelift-entity = { workspace = true } cranelift-interpreter = { workspace = true } cranelift-reader = { workspace = true } diff --git a/cranelift/codegen/Cargo.toml b/cranelift/codegen/Cargo.toml index bde448f5a5a1..25ae26af144f 100644 --- a/cranelift/codegen/Cargo.toml +++ b/cranelift/codegen/Cargo.toml @@ -48,7 +48,7 @@ cranelift-codegen-meta = { path = "meta", version = "0.103.0" } cranelift-isle = { path = "../isle/isle", version = "=0.103.0" } [features] -default = ["std", "unwind", "host-arch"] +default = ["std", "unwind", "host-arch", "timing"] # The "std" feature enables use of libstd. The "core" feature enables use # of some minimal std-like replacement libraries. At least one of these two @@ -114,6 +114,11 @@ isle-errors = ["cranelift-isle/fancy-errors"] # inspection, rather than inside of target/. isle-in-source-tree = [] +# Enable tracking how long passes take in Cranelift. +# +# Enabled by default. +timing = [] + [[bench]] name = "x64-evex-encoding" harness = false diff --git a/cranelift/codegen/src/timing.rs b/cranelift/codegen/src/timing.rs index 573b82c0cd96..47f0eaec1cec 100644 --- a/cranelift/codegen/src/timing.rs +++ b/cranelift/codegen/src/timing.rs @@ -5,9 +5,9 @@ use core::fmt; use std::any::Any; use std::boxed::Box; -use std::cell::{Cell, RefCell}; +use std::cell::RefCell; use std::mem; -use std::time::{Duration, Instant}; +use std::time::Duration; // Each pass that can be timed is predefined with the `define_passes!` macro. Each pass has a // snake_case name and a plain text description used when printing out the timing report. @@ -130,22 +130,6 @@ fn start_pass(pass: Pass) -> Box { PROFILER.with(|profiler| profiler.borrow().start_pass(pass)) } -/// A timing token is responsible for timing the currently running pass. Timing starts when it -/// is created and ends when it is dropped. -/// -/// Multiple passes can be active at the same time, but they must be started and stopped in a -/// LIFO fashion. -struct DefaultTimingToken { - /// Start time for this pass. - start: Instant, - - // Pass being timed by this token. - pass: Pass, - - // The previously active pass which will be restored when this token is dropped. - prev: Pass, -} - /// Accumulated timing information for a single pass. #[derive(Default, Copy, Clone)] struct PassTime { @@ -215,42 +199,12 @@ impl fmt::Display for PassTimes { // Information about passes in a single thread. thread_local! { - static CURRENT_PASS: Cell = const { Cell::new(Pass::None) }; static PASS_TIME: RefCell = RefCell::new(Default::default()); } /// The default profiler. You can get the results using [`take_current`]. pub struct DefaultProfiler; -impl Profiler for DefaultProfiler { - fn start_pass(&self, pass: Pass) -> Box { - let prev = CURRENT_PASS.with(|p| p.replace(pass)); - log::debug!("timing: Starting {}, (during {})", pass, prev); - Box::new(DefaultTimingToken { - start: Instant::now(), - pass, - prev, - }) - } -} - -/// Dropping a timing token indicated the end of the pass. -impl Drop for DefaultTimingToken { - fn drop(&mut self) { - let duration = self.start.elapsed(); - log::debug!("timing: Ending {}: {}ms", self.pass, duration.as_millis()); - let old_cur = CURRENT_PASS.with(|p| p.replace(self.prev)); - debug_assert_eq!(self.pass, old_cur, "Timing tokens dropped out of order"); - PASS_TIME.with(|rc| { - let mut table = rc.borrow_mut(); - table.pass[self.pass.idx()].total += duration; - if let Some(parent) = table.pass.get_mut(self.prev.idx()) { - parent.child += duration; - } - }) - } -} - /// Take the current accumulated pass timings and reset the timings for the current thread. /// /// Only applies when [`DefaultProfiler`] is used. @@ -258,6 +212,78 @@ pub fn take_current() -> PassTimes { PASS_TIME.with(|rc| mem::take(&mut *rc.borrow_mut())) } +#[cfg(feature = "timing")] +mod enabled { + use super::{DefaultProfiler, Pass, Profiler, PASS_TIME}; + use std::any::Any; + use std::boxed::Box; + use std::cell::Cell; + use std::time::Instant; + + // Information about passes in a single thread. + thread_local! { + static CURRENT_PASS: Cell = const { Cell::new(Pass::None) }; + } + + impl Profiler for DefaultProfiler { + fn start_pass(&self, pass: Pass) -> Box { + let prev = CURRENT_PASS.with(|p| p.replace(pass)); + log::debug!("timing: Starting {}, (during {})", pass, prev); + Box::new(DefaultTimingToken { + start: Instant::now(), + pass, + prev, + }) + } + } + + /// A timing token is responsible for timing the currently running pass. Timing starts when it + /// is created and ends when it is dropped. + /// + /// Multiple passes can be active at the same time, but they must be started and stopped in a + /// LIFO fashion. + struct DefaultTimingToken { + /// Start time for this pass. + start: Instant, + + // Pass being timed by this token. + pass: Pass, + + // The previously active pass which will be restored when this token is dropped. + prev: Pass, + } + + /// Dropping a timing token indicated the end of the pass. + impl Drop for DefaultTimingToken { + fn drop(&mut self) { + let duration = self.start.elapsed(); + log::debug!("timing: Ending {}: {}ms", self.pass, duration.as_millis()); + let old_cur = CURRENT_PASS.with(|p| p.replace(self.prev)); + debug_assert_eq!(self.pass, old_cur, "Timing tokens dropped out of order"); + PASS_TIME.with(|rc| { + let mut table = rc.borrow_mut(); + table.pass[self.pass.idx()].total += duration; + if let Some(parent) = table.pass.get_mut(self.prev.idx()) { + parent.child += duration; + } + }) + } + } +} + +#[cfg(not(feature = "timing"))] +mod disabled { + use super::{DefaultProfiler, Pass, Profiler}; + use std::any::Any; + use std::boxed::Box; + + impl Profiler for DefaultProfiler { + fn start_pass(&self, _pass: Pass) -> Box { + Box::new(()) + } + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/cranelift/Cargo.toml b/crates/cranelift/Cargo.toml index 733c9a377810..354e29e163bd 100644 --- a/crates/cranelift/Cargo.toml +++ b/crates/cranelift/Cargo.toml @@ -18,7 +18,7 @@ anyhow = { workspace = true } log = { workspace = true } wasmtime-environ = { workspace = true } cranelift-wasm = { workspace = true } -cranelift-codegen = { workspace = true, features = ["default"] } +cranelift-codegen = { workspace = true, features = ["host-arch"] } cranelift-frontend = { workspace = true } cranelift-entity = { workspace = true } cranelift-native = { workspace = true } diff --git a/crates/jit/src/instantiate.rs b/crates/jit/src/instantiate.rs index d68b51cb3454..7f73d77451df 100644 --- a/crates/jit/src/instantiate.rs +++ b/crates/jit/src/instantiate.rs @@ -5,7 +5,7 @@ use crate::code_memory::CodeMemory; use crate::profiling::ProfilingAgent; -use anyhow::{bail, Context, Error, Result}; +use anyhow::{bail, Error, Result}; use object::write::{Object, SectionId, StandardSegment, WritableBuffer}; use object::SectionKind; use serde_derive::{Deserialize, Serialize}; @@ -472,6 +472,8 @@ impl CompiledModule { fn register_debug_and_profiling(&mut self, profiler: &dyn ProfilingAgent) -> Result<()> { #[cfg(feature = "debug-builtins")] if self.meta.native_debug_info_present { + use anyhow::Context; + let text = self.text(); let bytes = crate::debug::create_gdbjit_image( self.mmap().to_vec(), @@ -672,6 +674,7 @@ impl CompiledModule { /// what filename and line number a wasm pc comes from. #[cfg(feature = "addr2line")] pub fn symbolize_context(&self) -> Result>> { + use anyhow::Context; use gimli::EndianSlice; if !self.meta.has_wasm_debuginfo { return Ok(None); diff --git a/crates/runtime/src/sys/custom/mod.rs b/crates/runtime/src/sys/custom/mod.rs index c25b60ddab99..4f12e740272e 100644 --- a/crates/runtime/src/sys/custom/mod.rs +++ b/crates/runtime/src/sys/custom/mod.rs @@ -1,4 +1,7 @@ -/// TODO: dox +//! Custom platform support in Wasmtime. +//! +//! TODO: dox + pub mod capi; pub mod mmap; pub mod traphandlers; diff --git a/crates/runtime/src/sys/unix/vm.rs b/crates/runtime/src/sys/unix/vm.rs index b08cee8d47a0..c0f6b927bce0 100644 --- a/crates/runtime/src/sys/unix/vm.rs +++ b/crates/runtime/src/sys/unix/vm.rs @@ -22,6 +22,7 @@ pub unsafe fn erase_existing_mapping(ptr: *mut u8, len: usize) -> io::Result<()> Ok(()) } +#[cfg(any(feature = "pooling-allocator", feature = "async"))] unsafe fn decommit(addr: *mut u8, len: usize) -> io::Result<()> { if len == 0 { return Ok(()); diff --git a/examples/min-platform/.gitignore b/examples/min-platform/.gitignore new file mode 100644 index 000000000000..29fa3497b8ff --- /dev/null +++ b/examples/min-platform/.gitignore @@ -0,0 +1 @@ +libwasmtime-platform.so diff --git a/examples/min-platform/Cargo.toml b/examples/min-platform/Cargo.toml new file mode 100644 index 000000000000..e28389c50a4e --- /dev/null +++ b/examples/min-platform/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "min-platform-host" +version.workspace = true +authors.workspace = true +edition.workspace = true +rust-version.workspace = true + +[lints] +workspace = true + +[dependencies] +anyhow = { workspace = true } +wasmtime = { workspace = true, features = ['cranelift'] } +libloading = "0.7" +object = { workspace = true } diff --git a/examples/min-platform/build.sh b/examples/min-platform/build.sh new file mode 100755 index 000000000000..94955fdd5193 --- /dev/null +++ b/examples/min-platform/build.sh @@ -0,0 +1,19 @@ +#!/bin/sh + +set -ex + +cbindgen ../../crates/runtime/src/sys/custom/capi.rs \ + --lang C \ + --cpp-compat > embedding/wasmtime-platform.h + +clang -shared -o libwasmtime-platform.so ./embedding/wasmtime-platform.c + +RUSTC_BOOTSTRAP_SYNTHETIC_TARGET=1 \ +CARGO_PROFILE_DEV_PANIC=abort \ +CARGO_PROFILE_RELEASE_PANIC=abort \ +RUSTFLAGS="--cfg=wasmtime_custom_platform -Cforce-frame-pointers" \ + cargo +nightly build -Zbuild-std=std,panic_abort \ + --manifest-path embedding/Cargo.toml \ + --target ./embedding/aarch64-unknown-unknown.json + +cargo run diff --git a/examples/min-platform/embedding/Cargo.toml b/examples/min-platform/embedding/Cargo.toml new file mode 100644 index 000000000000..ccbc547f0fea --- /dev/null +++ b/examples/min-platform/embedding/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "embedding" +version.workspace = true +authors.workspace = true +edition.workspace = true +rust-version.workspace = true + +[lints] +workspace = true + +[dependencies] +wasmtime = { workspace = true, features = ['cranelift', 'wat'] } +dlmalloc = "0.2.4" +anyhow = { workspace = true } + +[lib] +crate-type = ['cdylib'] diff --git a/examples/min-platform/embedding/aarch64-unknown-unknown.json b/examples/min-platform/embedding/aarch64-unknown-unknown.json new file mode 100644 index 000000000000..36c78955dc30 --- /dev/null +++ b/examples/min-platform/embedding/aarch64-unknown-unknown.json @@ -0,0 +1,16 @@ +{ + "arch": "aarch64", + "data-layout": "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128", + "dynamic-linking": true, + "has-thread-local": true, + "is-builtin": false, + "llvm-target": "aarch64-unknown-linux-gnu", + "max-atomic-width": 64, + "os": "minwasmtime", + "position-independent-executables": true, + "relro-level": "full", + "stack-probes": { + "kind": "inline" + }, + "target-pointer-width": "64" +} diff --git a/examples/min-platform/embedding/src/allocator.rs b/examples/min-platform/embedding/src/allocator.rs new file mode 100644 index 000000000000..b2ad5fd66e7c --- /dev/null +++ b/examples/min-platform/embedding/src/allocator.rs @@ -0,0 +1,92 @@ +use dlmalloc::Dlmalloc; +use std::alloc::{GlobalAlloc, Layout}; +use std::sync::Mutex; + +#[global_allocator] +static MALLOC: MyGlobalDmalloc = MyGlobalDmalloc { + dlmalloc: Mutex::new(Dlmalloc::new_with_allocator(MyAllocator)), +}; + +struct MyGlobalDmalloc { + dlmalloc: Mutex>, +} + +unsafe impl Send for MyGlobalDmalloc {} +unsafe impl Sync for MyGlobalDmalloc {} + +struct MyAllocator; + +unsafe impl GlobalAlloc for MyGlobalDmalloc { + unsafe fn alloc(&self, layout: Layout) -> *mut u8 { + self.dlmalloc + .lock() + .unwrap() + .malloc(layout.size(), layout.align()) + } + + unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 { + self.dlmalloc + .lock() + .unwrap() + .calloc(layout.size(), layout.align()) + } + + unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 { + self.dlmalloc + .lock() + .unwrap() + .realloc(ptr, layout.size(), layout.align(), new_size) + } + + unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { + self.dlmalloc + .lock() + .unwrap() + .free(ptr, layout.size(), layout.align()) + } +} + +const PROT_READ: u32 = 1 << 0; +const PROT_WRITE: u32 = 1 << 1; + +extern "C" { + fn wasmtime_mmap_new(size: usize, prot_flags: u32) -> *mut u8; + fn wasmtime_page_size() -> usize; + fn wasmtime_munmap(ptr: *mut u8, size: usize); +} + +unsafe impl dlmalloc::Allocator for MyAllocator { + fn alloc(&self, size: usize) -> (*mut u8, usize, u32) { + unsafe { + let ptr = wasmtime_mmap_new(size, PROT_READ | PROT_WRITE); + (ptr, size, 0) + } + } + + fn remap(&self, _ptr: *mut u8, _old: usize, _new: usize, _can_move: bool) -> *mut u8 { + std::ptr::null_mut() + } + + fn free_part(&self, _ptr: *mut u8, _old: usize, _new: usize) -> bool { + false + } + + fn free(&self, ptr: *mut u8, size: usize) -> bool { + unsafe { + wasmtime_munmap(ptr, size); + true + } + } + + fn can_release_part(&self, _flags: u32) -> bool { + false + } + + fn allocates_zeros(&self) -> bool { + true + } + + fn page_size(&self) -> usize { + unsafe { wasmtime_page_size() } + } +} diff --git a/examples/min-platform/embedding/src/lib.rs b/examples/min-platform/embedding/src/lib.rs new file mode 100644 index 000000000000..aabc8484ed17 --- /dev/null +++ b/examples/min-platform/embedding/src/lib.rs @@ -0,0 +1,71 @@ +use anyhow::Result; +use wasmtime::{Engine, Instance, Linker, Module, Store}; + +mod allocator; + +#[no_mangle] +pub unsafe extern "C" fn run(buf: *mut u8, size: usize) -> usize { + let buf = std::slice::from_raw_parts_mut(buf, size); + match run_result() { + Ok(()) => 0, + Err(e) => { + let msg = format!("{e:?}"); + let len = buf.len().min(msg.len()); + buf[..len].copy_from_slice(&msg.as_bytes()[..len]); + len + } + } +} + +fn run_result() -> Result<()> { + smoke()?; + simple_add()?; + simple_host_fn()?; + Ok(()) +} + +fn smoke() -> Result<()> { + let engine = Engine::default(); + let module = Module::new(&engine, "(module)")?; + Instance::new(&mut Store::new(&engine, ()), &module, &[])?; + Ok(()) +} + +fn simple_add() -> Result<()> { + let engine = Engine::default(); + let module = Module::new( + &engine, + r#" + (module + (func (export "add") (param i32 i32) (result i32) + (i32.add (local.get 0) (local.get 1))) + ) + "#, + )?; + let mut store = Store::new(&engine, ()); + let instance = Instance::new(&mut store, &module, &[])?; + let func = instance.get_typed_func::<(u32, u32), u32>(&mut store, "add")?; + assert_eq!(func.call(&mut store, (2, 3))?, 5); + Ok(()) +} + +fn simple_host_fn() -> Result<()> { + let engine = Engine::default(); + let module = Module::new( + &engine, + r#" + (module + (import "host" "multiply" (func $multiply (param i32 i32) (result i32))) + (func (export "add_and_mul") (param i32 i32 i32) (result i32) + (i32.add (call $multiply (local.get 0) (local.get 1)) (local.get 2))) + ) + "#, + )?; + let mut linker = Linker::<()>::new(&engine); + linker.func_wrap("host", "multiply", |a: u32, b: u32| a.saturating_mul(b))?; + let mut store = Store::new(&engine, ()); + let instance = linker.instantiate(&mut store, &module)?; + let func = instance.get_typed_func::<(u32, u32, u32), u32>(&mut store, "add_and_mul")?; + assert_eq!(func.call(&mut store, (2, 3, 4))?, 10); + Ok(()) +} diff --git a/examples/min-platform/embedding/wasmtime-platform.c b/examples/min-platform/embedding/wasmtime-platform.c new file mode 100644 index 000000000000..ac9fa06a3724 --- /dev/null +++ b/examples/min-platform/embedding/wasmtime-platform.c @@ -0,0 +1,109 @@ +#define NDEBUG 1 + +#include +#include +#include +#include +#include +#include + +#include "wasmtime-platform.h" + +static int wasmtime_to_mmap_prot_flags(uint32_t prot_flags) { + int flags = 0; + if (prot_flags & WASMTIME_PROT_READ) + flags |= PROT_READ; + if (prot_flags & WASMTIME_PROT_WRITE) + flags |= PROT_WRITE; + if (prot_flags & WASMTIME_PROT_EXEC) + flags |= PROT_EXEC; + return flags; +} + +uint8_t *wasmtime_mmap_new(uintptr_t size, uint32_t prot_flags) { + void *rc = mmap(NULL, size, + wasmtime_to_mmap_prot_flags(prot_flags), + MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + assert(rc != MAP_FAILED); + return rc; +} + +void wasmtime_mmap_remap(uint8_t *addr, uintptr_t size, uint32_t prot_flags) { + void *rc = mmap(addr, size, + wasmtime_to_mmap_prot_flags(prot_flags), + MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + assert(rc == addr); + (void) rc; +} + +void wasmtime_munmap(uint8_t *ptr, uintptr_t size) { + int rc = munmap(ptr, size); + assert(rc == 0); + (void) rc; +} + +void wasmtime_mprotect(uint8_t *ptr, uintptr_t size, uint32_t prot_flags) { + int rc = mprotect(ptr, size, wasmtime_to_mmap_prot_flags(prot_flags)); + assert(rc == 0); + (void) rc; +} + +uintptr_t wasmtime_page_size(void) { + return sysconf(_SC_PAGESIZE); +} + +int32_t wasmtime_setjmp(const uint8_t **jmp_buf_out, + void (*callback)(uint8_t*, uint8_t*), + uint8_t *payload, + uint8_t *callee) { + jmp_buf buf; + if (setjmp(buf) != 0) + return 0; + *jmp_buf_out = (uint8_t*) &buf; + callback(payload, callee); + return 1; +} + +void wasmtime_longjmp(const uint8_t *jmp_buf_ptr) { + longjmp(*(jmp_buf*) jmp_buf_ptr, 1); +} + +static wasmtime_trap_handler_t g_handler = NULL; + +static void handle_signal(int signal, siginfo_t *info, void *context) { + assert(g_handler != NULL); + uintptr_t ip, fp; +#ifdef __aarch64__ + ucontext_t *cx = context; + ip = cx->uc_mcontext.pc; + fp = cx->uc_mcontext.regs[29]; +#else +#error "Unsupported platform" +#endif + + bool has_faulting_addr = signal == SIGSEGV; + uintptr_t faulting_addr = 0; + if (has_faulting_addr) + faulting_addr = (uintptr_t) info->si_addr; + g_handler(ip, fp, has_faulting_addr, faulting_addr); +} + +extern void wasmtime_init_traps(wasmtime_trap_handler_t handler) { + int rc; + g_handler = handler; + + struct sigaction action; + memset(&action, 0, sizeof(action)); + + action.sa_sigaction = handle_signal; + action.sa_flags = SA_SIGINFO | SA_NODEFER; + sigemptyset(&action.sa_mask); + + rc = sigaction(SIGILL, &action, NULL); + assert(rc == 0); + rc = sigaction(SIGSEGV, &action, NULL); + assert(rc == 0); + rc = sigaction(SIGFPE, &action, NULL); + assert(rc == 0); + (void) rc; +} diff --git a/examples/min-platform/embedding/wasmtime-platform.h b/examples/min-platform/embedding/wasmtime-platform.h new file mode 100644 index 000000000000..7ca369de97f5 --- /dev/null +++ b/examples/min-platform/embedding/wasmtime-platform.h @@ -0,0 +1,156 @@ +#include +#include +#include +#include + +/** + * Indicates that the memory region should be readable. + */ +#define WASMTIME_PROT_READ (1 << 0) + +/** + * Indicates that the memory region should be writable. + */ +#define WASMTIME_PROT_WRITE (1 << 1) + +/** + * Indicates that the memory region should be executable. + */ +#define WASMTIME_PROT_EXEC (1 << 2) + +/** + * Handler function for traps in Wasmtime passed to `wasmtime_init_traps`. + * + * This function is invoked whenever a trap is caught by the system. For + * example this would be invoked during a signal handler on Linux. This + * function is passed a number of parameters indicating information about the + * trap: + * + * * `ip` - the instruction pointer at the time of the trap. + * * `fp` - the frame pointer register's value at the time of the trap. + * * `has_faulting_addr` - whether this trap is associated with an access + * violation (e.g. a segfault) meaning memory was accessed when it shouldn't + * be. If this is `true` then the next parameter is filled in. + * * `faulting_addr` - if `has_faulting_addr` is true then this is the address + * that was attempted to be accessed. Otherwise this value is not used. + * + * If this function returns then the trap was not handled. This probably means + * that a fatal exception happened and the process should be aborted. + * + * This function may not return as it may invoke `wasmtime_longjmp` if a wasm + * trap is detected. + */ +typedef void (*wasmtime_trap_handler_t)(uintptr_t ip, + uintptr_t fp, + bool has_faulting_addr, + uintptr_t faulting_addr); + +#ifdef __cplusplus +extern "C" { +#endif // __cplusplus + +/** + * Creates a new virtual memory mapping of the `size` specified with + * protection bits specified in `prot_flags`. + * + * Memory can be lazily committed. + * + * Returns the base pointer of the new mapping. Aborts the process on + * failure. + * + * Similar to `mmap(0, size, prot_flags, MAP_PRIVATE, 0, -1)` on Linux. + */ +extern uint8_t *wasmtime_mmap_new(uintptr_t size, uint32_t prot_flags); + +/** + * Remaps the virtual memory starting at `addr` going for `size` bytes to + * the protections specified with a new blank mapping. + * + * This will unmap any prior mappings and decommit them. New mappings for + * anonymous memory are used to replace these mappings and the new area + * should have the protection specified by `prot_flags`. + * + * Aborts the process on failure. + * + * Similar to `mmap(addr, size, prot_flags, MAP_PRIVATE | MAP_FIXED, 0, -1)` on Linux. + */ +extern void wasmtime_mmap_remap(uint8_t *addr, uintptr_t size, uint32_t prot_flags); + +/** + * Unmaps memory at the specified `ptr` for `size` bytes. + * + * The memory should be discarded and decommitted and should generate a + * segfault if accessed after this function call. + * + * Aborts the process on failure. + * + * Similar to `munmap` on Linux. + */ +extern void wasmtime_munmap(uint8_t *ptr, uintptr_t size); + +/** + * Configures the protections associated with a region of virtual memory + * starting at `ptr` and going to `size`. + * + * Aborts the process on failure. + * + * Similar to `mprotect` on Linux. + */ +extern void wasmtime_mprotect(uint8_t *ptr, uintptr_t size, uint32_t prot_flags); + +/** + * Returns the page size, in bytes, of the current system. + */ +extern uintptr_t wasmtime_page_size(void); + +/** + * Used to setup a frame on the stack to longjmp back to in the future. + * + * This function is used for handling traps in WebAssembly and is paried + * with `wasmtime_longjmp`. + * + * * `jmp_buf` - this argument is filled in with a pointer which if used + * will be passed to `wasmtime_longjmp` later on by the runtime. + * * `callback` - this callback should be invoked after `jmp_buf` is + * configured. + * * `payload` and `callee` - the two arguments to pass to `callback`. + * + * Returns 0 if `wasmtime_longjmp` was used to return to this function. + * Returns 1 if `wasmtime_longjmp` was not called an `callback` returned. + */ +extern int32_t wasmtime_setjmp(const uint8_t **jmp_buf, + void (*callback)(uint8_t*, uint8_t*), + uint8_t *payload, + uint8_t *callee); + +/** + * Paired with `wasmtime_setjmp` this is used to jump back to the `setjmp` + * point. + * + * The argument here was originally passed to `wasmtime_setjmp` through its + * out-param. + * + * This function cannot return. + * + * This function may be invoked from the `wasmtime_trap_handler_t` + * configured by `wasmtime_init_traps`. + */ +extern void wasmtime_longjmp(const uint8_t *jmp_buf); + +/** + * Initializes trap-handling logic for this platform. + * + * Wasmtime's implementation of WebAssembly relies on the ability to catch + * signals/traps/etc. For example divide-by-zero may raise a machine + * exception. Out-of-bounds memory accesses may also raise a machine + * exception. This function is used to initialize trap handling. + * + * The `handler` provided is a function pointer to invoke whenever a trap + * is encountered. The `handler` is invoked whenever a trap is caught by + * the system. + */ +extern void wasmtime_init_traps(wasmtime_trap_handler_t handler); + +#ifdef __cplusplus +} // extern "C" +#endif // __cplusplus diff --git a/examples/min-platform/src/main.rs b/examples/min-platform/src/main.rs new file mode 100644 index 000000000000..439a1398e2ac --- /dev/null +++ b/examples/min-platform/src/main.rs @@ -0,0 +1,39 @@ +use anyhow::Result; +use libloading::os::unix::{Library, Symbol, RTLD_GLOBAL, RTLD_NOW}; +use object::{Object, ObjectSymbol}; +use std::io::Write; + +fn main() -> Result<()> { + let lib = "../../target/aarch64-unknown-unknown/debug/libembedding.so"; + let binary = std::fs::read(lib)?; + let object = object::File::parse(&binary[..])?; + + for sym in object.symbols() { + if !sym.is_undefined() || sym.is_weak() { + continue; + } + + match sym.name()? { + "" | "memmove" | "memset" | "memcmp" | "memcpy" => {} + s if s.starts_with("wasmtime_") => {} + other => { + panic!("unexpected dependency on symbol `{other}`") + } + } + } + + unsafe { + let _platform_symbols = + Library::open(Some("./libwasmtime-platform.so"), RTLD_NOW | RTLD_GLOBAL)?; + + let lib = Library::new(lib)?; + let run: Symbol usize> = lib.get(b"run")?; + + let mut buf = Vec::with_capacity(1024); + let len = run(buf.as_mut_ptr(), buf.capacity()); + buf.set_len(len); + + std::io::stderr().write_all(&buf).unwrap(); + } + Ok(()) +}