Merge merge/wasmtime49: wasmtime 49.0.2 (vendored, wasm_fault fix), rustc 1.96

Gated on gooner at 86734783: a64 449, x86 438, nt 60/60 both modes,
x86-programs, glibc, lockstep 3/3, browser NT 49/49, PPC 3/3.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01PeRutwFgZ66XpxjasKrPn1
This commit is contained in:
velzieandClaude Opus 5.5 committed 2026-10-05 01:17:24 -04:00
commit fd8be1d161
266 files changed
+124167 -366

No files matched your search

Generated
+422 -300
View File
File diff suppressed because it is too large. Load diff
+8
View File
@@ -72,3 +72,11 @@ gex-eject = { path = "crates/gex-eject" }
[profile.release]
opt-level = 3
debug = 1
# wasmtime 49 with one change (third_party/wasmtime-49.0.2/src/runtime/store.rs,
# `wasm_fault`): several instances claiming one shared memory is not an
# assertion failure. Every module import of the guest's SharedMemory creates
# such an instance, so without it a guest fault in a guard page panicked the
# host instead of reaching the guest as SIGSEGV / an access violation.
[patch.crates-io]
wasmtime = { path = "third_party/wasmtime-49.0.2" }
+1 -1
View File
@@ -6,4 +6,4 @@ edition.workspace = true
[dependencies]
anyhow = "1"
gex-key.workspace = true
wasmtime = { version = "27", default-features = false, features = ["cranelift", "runtime", "std", "threads"] }
wasmtime = { version = "49", default-features = false, features = ["cranelift", "runtime", "std", "threads", "gc", "gc-null", "anyhow"] }
+4 -4
View File
@@ -235,8 +235,8 @@ pub fn load_or_compile(
}
let (wasm, metadata) = build()?;
let module = Module::new(engine, &wasm).context("validating translated wasm")?;
if let Ok(serialized) = module.serialize() {
let module = Module::new(engine, &wasm).map_err(anyhow::Error::from).context("validating translated wasm")?;
if let Ok(serialized) = module.serialize().map_err(anyhow::Error::from) {
// Metadata first: a reader requires both, so the module's arrival is
// what publishes the entry.
store(dir, &meta_path, &metadata);
@@ -268,8 +268,8 @@ pub fn load_or_compile_at(engine: &Engine, path: &Path, wasm: &[u8]) -> Result<M
}
}
}
let m = Module::new(engine, wasm).context("validating the module")?;
if let (Ok(bytes), Some(dir)) = (m.serialize(), path.parent()) {
let m = Module::new(engine, wasm).map_err(anyhow::Error::from).context("validating the module")?;
if let (Ok(bytes), Some(dir)) = (m.serialize().map_err(anyhow::Error::from), path.parent()) {
store(dir, path, &bytes);
}
Ok(m)
+1 -1
View File
@@ -4,4 +4,4 @@ version.workspace = true
edition.workspace = true
[dependencies]
wasmtime = { version = "27", default-features = false, features = ["cranelift", "runtime", "std", "threads"] }
wasmtime = { version = "49", default-features = false, features = ["cranelift", "runtime", "std", "threads", "gc", "gc-null", "anyhow"] }
+6
View File
@@ -0,0 +1,6 @@
# wasmtime 49 needs rustc 1.96 (the native engine, with wasm exception
# handling for native Wine's setjmp: docs/wine-native.md §4.2).
[toolchain]
channel = "1.96.0"
# the browser shim (shims/browser) builds for wasm32-unknown-unknown
targets = ["wasm32-unknown-unknown"]
+1 -1
View File
@@ -27,7 +27,7 @@ anyhow = "1"
# module with one function per guest basic block -- fifty thousand of them for
# bash, half a million for Mesa -- which is close to the ideal shape for
# compiling in parallel, and it was using one core out of twenty-four.
wasmtime = { version = "27", default-features = false, features = ["cranelift", "runtime", "std", "threads", "parallel-compilation"] }
wasmtime = { version = "49", default-features = false, features = ["cranelift", "runtime", "std", "threads", "gc", "gc-null", "anyhow", "parallel-compilation"] }
[features]
# Host an x86-64 guest. See `gex_abi::X86`.
+8 -2
View File
@@ -28,6 +28,7 @@
//! [`REPL_THREADS`] stacks of [`REPL_STACK_BYTES`], one per guest thread by
//! context slot. The region is recorded as a mapping so `fork` copies it.
use crate::WasmtimeContext;
use super::{read_u64, write_u64, HostState};
use anyhow::{anyhow, bail, Context, Result};
use gex_abi::ctx;
@@ -499,6 +500,11 @@ fn engine_config() -> wasmtime::Config {
let mut config = wasmtime::Config::new();
config.wasm_tail_call(true);
config.wasm_threads(true);
// wasmtime 49 creates no SharedMemory unless asked: the guest's memory is one.
config.shared_memory(true);
// Wasm exception handling: setjmp/longjmp in native Wine and wasm32
// programs (clang's SjLj lowering), docs/wine-native.md §4.2.
config.wasm_exceptions(true);
config.wasm_bulk_memory(true);
config.wasm_multi_memory(true);
config
@@ -609,9 +615,9 @@ pub(crate) fn call(caller: &mut Caller<'_, HostState>, ctx_addr: i32, pc: u64, i
Abi::Ctx | Abi::Entry => {
if let Err(e) = func.call(&mut *caller, &[Val::I32(ctx_addr)], &mut []) {
if e.is::<super::StopExecution>() {
return Err(e);
return Err(anyhow::Error::from(e));
}
return Err(e.context(format!("replacement `{}` for {}", r.export, r.symbol)));
return Err(anyhow::Error::from(e).context(format!("replacement `{}` for {}", r.export, r.symbol)));
}
}
Abi::C => {
+2 -2
View File
@@ -104,14 +104,14 @@ pub(crate) fn override_imports(linker: &mut Linker<HostState>) -> Result<()> {
linker.func_wrap(
IMPORT_MODULE,
imports::DISPATCH_MISS,
|mut caller: Caller<'_, HostState>, _ctx: i32, pc: i64| -> Result<i32> { Err(missing(&mut caller, pc as u64, "a dispatch")) },
|mut caller: Caller<'_, HostState>, _ctx: i32, pc: i64| -> wasmtime::Result<i32> { crate::host((|| -> anyhow::Result<i32> { Err(missing(&mut caller, pc as u64, "a dispatch")) })()) },
)?;
// Leave: the root looks the target up, and stops the run if nothing
// built ahead of time holds it.
linker.func_wrap(
IMPORT_MODULE,
imports::CALL_OUT,
|_caller: Caller<'_, HostState>, _ctx: i32, _target: i64, _ret: i64, _sp: i64| -> Result<i32> { Ok(0) },
|_caller: Caller<'_, HostState>, _ctx: i32, _target: i64, _ret: i64, _sp: i64| -> wasmtime::Result<i32> { crate::host((|| -> anyhow::Result<i32> { Ok(0) })()) },
)?;
Ok(())
}
+3 -3
View File
@@ -204,14 +204,14 @@ pub(crate) fn ensure(mut cx: impl AsContextMut<Data = HostState>) -> Result<()>
let fallback = if crate::aot_only::enabled() {
wasmtime::Func::wrap(
&mut c,
|mut caller: Caller<'_, HostState>, ctx: i32, _depth: i32, _need: i32, _ret: i64| -> Result<i32> {
|mut caller: Caller<'_, HostState>, ctx: i32, _depth: i32, _need: i32, _ret: i64| -> wasmtime::Result<i32> { crate::host((|| -> anyhow::Result<i32> {
crate::aot_only::fallback(&mut caller, ctx)
},
})()) },
)
} else {
wasmtime::Func::wrap(
&mut c,
|mut caller: Caller<'_, HostState>, ctx: i32, _depth: i32, _need: i32, ret: i64| -> Result<i32> { fallback(&mut caller, ctx, ret as u64) },
|mut caller: Caller<'_, HostState>, ctx: i32, _depth: i32, _need: i32, ret: i64| -> wasmtime::Result<i32> { crate::host((|| -> anyhow::Result<i32> { fallback(&mut caller, ctx, ret as u64) })()) },
)
};
let want = sh.lock().unwrap_or_else(|e| e.into_inner()).ftab.map.size() + HEADROOM;
+65 -31
View File
@@ -28,6 +28,30 @@ pub mod wayland;
use wasmtime::{Caller, Config, Engine, Extern, Linker, Module, Ref, Store, Table, Val};
/// A host function's result for wasmtime 49 (its own error type): the
/// anyhow error inside, which `downcast_ref` still finds (`StopExecution`).
pub(crate) fn host<T>(r: anyhow::Result<T>) -> wasmtime::Result<T> {
r.map_err(wasmtime::Error::from_anyhow)
}
/// `anyhow::Context` for wasmtime's own error type (wasmtime 49's
/// `wasmtime::Error` is not a `std::error::Error`, so anyhow's trait does
/// not apply to it): the same `context`/`with_context`, giving an
/// `anyhow::Error`.
pub(crate) trait WasmtimeContext<T> {
fn context<C: std::fmt::Display + Send + Sync + 'static>(self, c: C) -> anyhow::Result<T>;
fn with_context<C: std::fmt::Display + Send + Sync + 'static, F: FnOnce() -> C>(self, f: F) -> anyhow::Result<T>;
}
impl<T> WasmtimeContext<T> for std::result::Result<T, wasmtime::Error> {
fn context<C: std::fmt::Display + Send + Sync + 'static>(self, c: C) -> anyhow::Result<T> {
self.map_err(|e| anyhow::Error::from(e).context(c))
}
fn with_context<C: std::fmt::Display + Send + Sync + 'static, F: FnOnce() -> C>(self, f: F) -> anyhow::Result<T> {
self.map_err(|e| anyhow::Error::from(e).context(f()))
}
}
/// How a guest run ended.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Exit {
@@ -527,11 +551,16 @@ pub fn validate_module(wasm: &[u8]) -> Result<()> {
let mut config = Config::new();
config.wasm_tail_call(true);
config.wasm_threads(true);
// wasmtime 49 creates no SharedMemory unless asked: the guest's memory is one.
config.shared_memory(true);
// Wasm exception handling: setjmp/longjmp in native Wine and wasm32
// programs (clang's SjLj lowering), docs/wine-native.md §4.2.
config.wasm_exceptions(true);
config.wasm_bulk_memory(true);
// The profile counters are a second memory (`profile`).
config.wasm_multi_memory(true);
let engine = Engine::new(&config).context("creating the wasm engine")?;
Module::validate(&engine, wasm)
Module::validate(&engine, wasm).map_err(anyhow::Error::from)
}
pub fn run_image(image: &Image, opts: &RunOptions, xlate: &Options) -> Result<(Exit, Stats)> {
@@ -569,6 +598,8 @@ pub fn engine() -> Result<Engine> {
// Deep guest call chains are wasm recursion (units call units): room for
// them, on threads with stacks to match (`guest_thread_stack`).
config.max_wasm_stack(wasm_stack_bytes());
// wasmtime 49 checks it against the async stack, used or not.
config.async_stack_size(wasm_stack_bytes() + (1 << 20));
// Traps are control flow here (a call-out's return, a resume, a callback's
// end, all StopExecution), hundreds a second in Noita, and each walked
// and symbolised the whole wasm stack for a backtrace nothing reads
@@ -579,6 +610,11 @@ pub fn engine() -> Result<Engine> {
// PLAN §2.4 depends on them.
config.wasm_tail_call(true);
config.wasm_threads(true);
// wasmtime 49 creates no SharedMemory unless asked: the guest's memory is one.
config.shared_memory(true);
// Wasm exception handling: setjmp/longjmp in native Wine and wasm32
// programs (clang's SjLj lowering), docs/wine-native.md §4.2.
config.wasm_exceptions(true);
config.wasm_bulk_memory(true);
// The profile counters are a second memory (`profile`).
config.wasm_multi_memory(true);
@@ -1468,11 +1504,9 @@ fn run_thread_loop(
store.data_mut().root_enter = Some(enter.clone());
let mut pc = entry;
loop {
let result = enter.call(
&mut *store,
&[Val::I32(ctx_addr as i32), Val::I64(pc as i64)],
&mut [],
);
let result = enter
.call(&mut *store, &[Val::I32(ctx_addr as i32), Val::I64(pc as i64)], &mut [])
.map_err(anyhow::Error::from);
if let Some(req) = store.data_mut().pending_fork.take() {
fork_process(&*store, req)?;
}
@@ -2626,7 +2660,7 @@ fn register_imports(linker: &mut Linker<HostState>) -> Result<()> {
linker.func_wrap(
m,
imports::SYSCALL,
|mut caller: Caller<'_, HostState>, _ctx: i32, pc: i64| -> Result<()> {
|mut caller: Caller<'_, HostState>, _ctx: i32, pc: i64| -> wasmtime::Result<()> { crate::host((|| -> anyhow::Result<()> {
if caller.data().nt.is_some() {
let r = nt::syscall_import(&mut caller, pc as u64);
check_i386_high(&caller, "an NT syscall");
@@ -2866,27 +2900,27 @@ fn register_imports(linker: &mut Linker<HostState>) -> Result<()> {
Err(StopExecution.into())
}
}
},
})()) },
)?;
linker.func_wrap(
m,
gex_abi::ftab::LOOKUP,
|mut caller: Caller<'_, HostState>, _ctx: i32, addr: i64| -> Result<i32> { ftab::lookup(&mut caller, addr as u64) },
|mut caller: Caller<'_, HostState>, _ctx: i32, addr: i64| -> wasmtime::Result<i32> { crate::host((|| -> anyhow::Result<i32> { ftab::lookup(&mut caller, addr as u64) })()) },
)?;
linker.func_wrap(
m,
imports::CALL_OUT,
|mut caller: Caller<'_, HostState>, ctx: i32, target: i64, ret: i64, sp_want: i64| -> Result<i32> {
|mut caller: Caller<'_, HostState>, ctx: i32, target: i64, ret: i64, sp_want: i64| -> wasmtime::Result<i32> { crate::host((|| -> anyhow::Result<i32> {
call_out(&mut caller, ctx, target as u64, ret as u64, sp_want as u64)
},
})()) },
)?;
linker.func_wrap(
m,
imports::DISPATCH_MISS,
|mut caller: Caller<'_, HostState>, _ctx: i32, pc: i64| -> Result<i32> {
|mut caller: Caller<'_, HostState>, _ctx: i32, pc: i64| -> wasmtime::Result<i32> { crate::host((|| -> anyhow::Result<i32> {
let pc = pc as u64;
// Returning from a signal handler with no `sa_restorer` lands
// here: the trampoline address deliberately has no translation.
@@ -2987,13 +3021,13 @@ fn register_imports(linker: &mut Linker<HostState>) -> Result<()> {
Err(StopExecution.into())
}
}
},
})()) },
)?;
linker.func_wrap(
m,
imports::ILLEGAL,
|mut caller: Caller<'_, HostState>, _ctx: i32, pc: i64, raw: i32| -> Result<()> {
|mut caller: Caller<'_, HostState>, _ctx: i32, pc: i64, raw: i32| -> wasmtime::Result<()> { crate::host((|| -> anyhow::Result<()> {
let resumable = pc as u64 & gex_cgen::RESUMABLE_PC != 0;
let pc = (pc as u64 & !gex_cgen::RESUMABLE_PC) as i64;
if caller.data().nt.is_some() {
@@ -3042,13 +3076,13 @@ fn register_imports(linker: &mut Linker<HostState>) -> Result<()> {
}
}
Err(StopExecution.into())
},
})()) },
)?;
linker.func_wrap(
m,
imports::BREAKPOINT,
|mut caller: Caller<'_, HostState>, ctx: i32, pc: i64, imm: i32| -> Result<()> {
|mut caller: Caller<'_, HostState>, ctx: i32, pc: i64, imm: i32| -> wasmtime::Result<()> { crate::host((|| -> anyhow::Result<()> {
let resumable = pc as u64 & gex_cgen::RESUMABLE_PC != 0;
let pc = (pc as u64 & !gex_cgen::RESUMABLE_PC) as i64;
// The diagnostic trace points dump the registers and carry on.
@@ -3155,7 +3189,7 @@ fn register_imports(linker: &mut Linker<HostState>) -> Result<()> {
internal: false,
});
Err(StopExecution.into())
},
})()) },
)?;
linker.func_wrap(
@@ -3184,7 +3218,7 @@ fn register_imports(linker: &mut Linker<HostState>) -> Result<()> {
linker.func_wrap(
m,
imports::SYSREG_WRITE,
|mut caller: Caller<'_, HostState>, ctx: i32, pc: i64, reg: i32, v: i64| -> Result<()> {
|mut caller: Caller<'_, HostState>, ctx: i32, pc: i64, reg: i32, v: i64| -> wasmtime::Result<()> { crate::host((|| -> anyhow::Result<()> {
// A function an ejected project replaced (`Intrinsic::Replace`).
if reg == gex_xlate::intrinsic::REPLACE_SYSREG {
return aot::call(&mut caller, ctx, pc as u64, v as u64);
@@ -3244,13 +3278,13 @@ fn register_imports(linker: &mut Linker<HostState>) -> Result<()> {
gex_kernel::x86::serve_x87(&mut gm, ctx as u32 as u64, v as u64);
}
Ok(())
},
})()) },
)?;
linker.func_wrap(
m,
imports::CACHE_OP,
|mut caller: Caller<'_, HostState>, _ctx: i32, pc: i64, op: i32, addr: i64| -> Result<()> {
|mut caller: Caller<'_, HostState>, _ctx: i32, pc: i64, op: i32, addr: i64| -> wasmtime::Result<()> { crate::host((|| -> anyhow::Result<()> {
if op == gex_abi::cache_op::DC_ZVA {
let memory = caller
.data()
@@ -3365,7 +3399,7 @@ fn register_imports(linker: &mut Linker<HostState>) -> Result<()> {
)?;
}
Ok(())
},
})()) },
)?;
// Fused multiply-add. The host has one; wasm does not, and a multiply
@@ -3397,7 +3431,7 @@ fn register_imports(linker: &mut Linker<HostState>) -> Result<()> {
b_sig: i64,
b_se: i32,
cw: i32|
-> Result<i64> {
-> wasmtime::Result<i64> { crate::host((|| -> anyhow::Result<i64> {
let (r, se, sw) = x87_op(op, a_sig as u64, a_se as u16, b_sig as u64, b_se as u16, cw as u16);
let memory = caller
.data()
@@ -3409,13 +3443,13 @@ fn register_imports(linker: &mut Linker<HostState>) -> Result<()> {
data[at..at + 4].copy_from_slice(&se.to_le_bytes());
data[at + 4..at + 8].copy_from_slice(&sw.to_le_bytes());
Ok(r as i64)
},
})()) },
)?;
linker.func_wrap(
m,
imports::SAFEPOINT,
|mut caller: Caller<'_, HostState>, _ctx: i32, pc: i64| -> Result<()> {
|mut caller: Caller<'_, HostState>, _ctx: i32, pc: i64| -> wasmtime::Result<()> { crate::host((|| -> anyhow::Result<()> {
if let Some(s) = caller.data().sampler.clone() {
s.sample(pc as u64);
}
@@ -3485,7 +3519,7 @@ fn register_imports(linker: &mut Linker<HostState>) -> Result<()> {
Err(StopExecution.into())
}
}
},
})()) },
)?;
Ok(())
@@ -4027,7 +4061,7 @@ fn watch_stub(cx: &mut impl wasmtime::AsContextMut<Data = HostState>) -> wasmtim
if let Some(f) = cx.as_context().data().watch_stub.clone() {
return f;
}
let f = wasmtime::Func::wrap(cx.as_context_mut(), |mut caller: Caller<'_, HostState>, ctx: i32| -> Result<()> {
let f = wasmtime::Func::wrap(cx.as_context_mut(), |mut caller: Caller<'_, HostState>, ctx: i32| -> wasmtime::Result<()> { crate::host((|| -> anyhow::Result<()> {
let memory = caller.data().memory.clone().ok_or_else(|| anyhow!("guest memory unavailable"))?;
let gm = GuestMem::new(memory.bytes());
let c = ctx as u32 as u64;
@@ -4054,7 +4088,7 @@ fn watch_stub(cx: &mut impl wasmtime::AsContextMut<Data = HostState>) -> wasmtim
let _ = GuestMem::new(memory.bytes()).write_u64(c + ctx::PC as u64, target);
state.callout_signal = Some(signal);
Err(StopExecution.into())
});
})()) });
cx.as_context_mut().data_mut().watch_stub = Some(f.clone());
f
}
@@ -4185,7 +4219,7 @@ fn call_out(caller: &mut Caller<'_, HostState>, ctx_i: i32, target: u64, ret: u6
}
let e = match r {
Ok(()) => break Err(anyhow!("guest code returned out of a call-out")),
Err(e) => e,
Err(e) => anyhow::Error::from(e),
};
if let Some(sig) = caller.data_mut().callout_signal.take() {
match sig {
@@ -4200,12 +4234,12 @@ fn call_out(caller: &mut Caller<'_, HostState>, ctx_i: i32, target: u64, ret: u6
// loop, every wasm frame between with it.
sig @ CalloutSignal::Escape(_) => {
caller.data_mut().callout_signal = Some(sig);
break Err(e);
break Err(anyhow::Error::from(e));
}
}
}
if caller.data().exit.is_some() {
break Err(e);
break Err(anyhow::Error::from(e));
}
if let Some(at) = caller.data_mut().resume_at.take() {
crate::ftab::stats::RESUMES.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
@@ -4220,7 +4254,7 @@ fn call_out(caller: &mut Caller<'_, HostState>, ctx_i: i32, target: u64, ret: u6
pc = GuestMem::new(memory.bytes()).read_u64(ctx_addr + ctx::PC as u64).unwrap_or(pc);
continue;
}
break Err(e);
break Err(anyhow::Error::from(e));
};
// Unwatch: put the slot back unless an install has written over it.
if let Some(co) = caller.data_mut().callouts.pop() {
+12 -12
View File
@@ -239,18 +239,18 @@ impl std::error::Error for Throw {}
/// The host functions stateful providers import.
pub(crate) fn register(linker: &mut Linker<HostState>) -> Result<()> {
linker.func_wrap(IMPORT_MODULE, "provider_try", |mut caller: Caller<'_, HostState>, f: i32, arg: i32| -> Result<i32> {
linker.func_wrap(IMPORT_MODULE, "provider_try", |mut caller: Caller<'_, HostState>, f: i32, arg: i32| -> wasmtime::Result<i32> { crate::host((|| -> anyhow::Result<i32> {
let func = ftab::table_func(&mut caller, f as u32)?;
let typed = func.typed::<i32, ()>(&caller).context("gex.provider_try: the function is not void(int)")?;
match typed.call(&mut caller, arg) {
Ok(()) => Ok(0),
Err(e) => match e.downcast_ref::<Throw>() {
Some(t) => Ok(t.0),
None => Err(e),
None => Err(anyhow::Error::from(e)),
},
}
})?;
linker.func_wrap(IMPORT_MODULE, "provider_throw", |_caller: Caller<'_, HostState>, code: i32| -> Result<()> { Err(Throw(code).into()) })?;
})()) })?;
linker.func_wrap(IMPORT_MODULE, "provider_throw", |_caller: Caller<'_, HostState>, code: i32| -> wasmtime::Result<()> { crate::host((|| -> anyhow::Result<()> { Err(Throw(code).into()) })()) })?;
// The NT process clock as NtQueryPerformanceCounter reads it (100 ns
// units): the `nt_qpc` provider's.
linker.func_wrap(IMPORT_MODULE, "nt_qpc", |caller: Caller<'_, HostState>| -> i64 {
@@ -266,7 +266,7 @@ pub(crate) fn register(linker: &mut Linker<HostState>) -> Result<()> {
}
}
})?;
linker.func_wrap(IMPORT_MODULE, "provider_missing", |mut caller: Caller<'_, HostState>, addr: i32| -> Result<()> {
linker.func_wrap(IMPORT_MODULE, "provider_missing", |mut caller: Caller<'_, HostState>, addr: i32| -> wasmtime::Result<()> { crate::host((|| -> anyhow::Result<()> {
let addr = addr as u32 as u64;
let name = MISSING.lock().unwrap_or_else(|e| e.into_inner()).as_ref().and_then(|m| m.get(&addr).cloned()).unwrap_or_else(|| "?".into());
let what = match caller.data().nt.as_ref() {
@@ -274,16 +274,16 @@ pub(crate) fn register(linker: &mut Linker<HostState>) -> Result<()> {
None => format!("{addr:#x}"),
};
Err(crate::aot_only::stop(&mut caller, addr, &format!("gex-eject: the program called {name} ({what}), an export of a replaced library that its provider does not implement")))
})?;
linker.func_wrap(IMPORT_MODULE, "provider_alloc", |mut caller: Caller<'_, HostState>, size: i32| -> Result<i32> {
})()) })?;
linker.func_wrap(IMPORT_MODULE, "provider_alloc", |mut caller: Caller<'_, HostState>, size: i32| -> wasmtime::Result<i32> { crate::host((|| -> anyhow::Result<i32> {
Ok(alloc(caller.data_mut(), size as u32 as u64, "[provider heap]")? as i32)
})?;
})()) })?;
// The same from the top down: a built-in provider's heap (`alloc_top`).
linker.func_wrap(IMPORT_MODULE, "provider_alloc_top", |mut caller: Caller<'_, HostState>, size: i32| -> Result<i32> {
linker.func_wrap(IMPORT_MODULE, "provider_alloc_top", |mut caller: Caller<'_, HostState>, size: i32| -> wasmtime::Result<i32> { crate::host((|| -> anyhow::Result<i32> {
Ok(alloc_top(caller.data_mut(), size as u32 as u64, "[provider heap]")? as i32)
})?;
})()) })?;
// Diagnostics: text a provider writes to its stderr.
linker.func_wrap(IMPORT_MODULE, "provider_log", |caller: Caller<'_, HostState>, ptr: i32, len: i32| -> Result<()> {
linker.func_wrap(IMPORT_MODULE, "provider_log", |caller: Caller<'_, HostState>, ptr: i32, len: i32| -> wasmtime::Result<()> { crate::host((|| -> anyhow::Result<()> {
if let Some(m) = caller.data().memory.as_ref() {
if let Some(b) = m.bytes().get(ptr as u32 as usize..(ptr as u32 as usize).saturating_add(len as u32 as usize)) {
use std::io::Write;
@@ -291,6 +291,6 @@ pub(crate) fn register(linker: &mut Linker<HostState>) -> Result<()> {
}
}
Ok(())
})?;
})()) })?;
Ok(())
}
+7 -7
View File
@@ -706,7 +706,7 @@ fn raise_nested(caller: &mut Caller<'_, HostState>, p: &Arc<nt::Process>, target
}
let e = match r {
Ok(()) => break Err(anyhow!("guest code returned out of exception dispatch")),
Err(e) => e,
Err(e) => anyhow::Error::from(e),
};
if let Some(sig) = caller.data_mut().callout_signal.take() {
match sig {
@@ -719,12 +719,12 @@ fn raise_nested(caller: &mut Caller<'_, HostState>, p: &Arc<nt::Process>, target
// out waits for: theirs, with these frames.
other => {
caller.data_mut().callout_signal = Some(other);
break Err(e);
break Err(anyhow::Error::from(e));
}
}
}
if caller.data().exit.is_some() {
break Err(e);
break Err(anyhow::Error::from(e));
}
if let Some(at) = caller.data_mut().resume_at.take() {
crate::ftab::stats::RESUMES.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
@@ -739,7 +739,7 @@ fn raise_nested(caller: &mut Caller<'_, HostState>, p: &Arc<nt::Process>, target
}
// Anything else (an NtCallbackReturn ending a callback further out,
// a failure) is for the loops below.
break Err(e);
break Err(anyhow::Error::from(e));
};
caller.data_mut().exc_levels.truncate(me);
result?;
@@ -917,7 +917,7 @@ fn nested_loop(caller: &mut Caller<'_, HostState>, p: &Arc<nt::Process>) -> Resu
}
let e = match result {
Ok(()) => bail!("guest code returned out of a user-mode callback"),
Err(e) => e,
Err(e) => anyhow::Error::from(e),
};
// A function-tier call-out further out watches a dispatch slot this
// callback's code reached: something else that uses the slot runs
@@ -931,7 +931,7 @@ fn nested_loop(caller: &mut Caller<'_, HostState>, p: &Arc<nt::Process>) -> Resu
}
other => {
caller.data_mut().callout_signal = Some(other);
return Err(e);
return Err(anyhow::Error::from(e));
}
}
}
@@ -952,6 +952,6 @@ fn nested_loop(caller: &mut Caller<'_, HostState>, p: &Arc<nt::Process>) -> Resu
pc = read_u64(memory.bytes(), (ctx_addr + ctx::PC as u64) as usize);
continue;
}
return Err(e);
return Err(anyhow::Error::from(e));
}
}
+1
View File
@@ -15,6 +15,7 @@
//! its counts in at its next safepoint or syscall; a thread blocked forever in
//! the kernel contributes when it wakes or ends.
use crate::WasmtimeContext;
use crate::HostState;
use anyhow::{anyhow, Context, Result};
use gex_xlate::profile::{self as gp, FileEntry, ObjectEntry, Placements, Profile, Session};
+24
View File
@@ -0,0 +1,24 @@
/* setjmp/longjmp in a wasm32 program: clang lowers them onto wasm
* exception handling (needs an engine with it: wasmtime 49 natively). */
#include <setjmp.h>
#include <stdio.h>
static jmp_buf jb;
static int depth;
__attribute__((noinline)) static void dive(int n)
{
depth = n;
if (n == 5) longjmp(jb, 42);
dive(n + 1);
}
int main(void)
{
volatile int count = 0;
int r = setjmp(jb);
count++;
if (!r) dive(0);
printf("longjmp returned %d after depth %d, setjmp returned %d times\n", r, depth, count);
return 0;
}
+1
View File
@@ -0,0 +1 @@
{"v":1}
+2362
View File
File diff suppressed because it is too large. Load diff
+563
View File
@@ -0,0 +1,563 @@
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
#
# When uploading crates to the registry Cargo will automatically
# "normalize" Cargo.toml files for maximal compatibility
# with all versions of Cargo and also rewrite `path` dependencies
# to registry (e.g., crates.io) dependencies.
#
# If you are reading this file be aware that the original Cargo.toml
# will likely look very different (and much more reasonable).
# See Cargo.toml.orig for the original contents.
[package]
edition = "2024"
rust-version = "1.96.0"
name = "wasmtime"
version = "49.0.2"
authors = ["The Wasmtime Project Developers"]
build = "build.rs"
autolib = false
autobins = false
autoexamples = false
autotests = false
autobenches = false
description = "High-level API to expose the Wasmtime runtime"
documentation = "https://docs.rs/wasmtime"
readme = "README.md"
license = "Apache-2.0 WITH LLVM-exception"
repository = "https://github.com/bytecodealliance/wasmtime"
resolver = "2"
[package.metadata.docs.rs]
all-features = true
[features]
addr2line = [
"dep:addr2line",
"dep:gimli",
"std",
]
all-arch = [
"wasmtime-cranelift?/all-arch",
"wasmtime-winch?/all-arch",
"pulley",
]
anyhow = ["wasmtime-environ/anyhow"]
async = [
"dep:wasmtime-fiber",
"wasmtime-component-macro?/async",
"runtime",
]
backtrace = [
"std",
"wasmtime-environ/backtrace",
]
cache = [
"dep:wasmtime-cache",
"std",
]
call-hook = []
compile-time-builtins = [
"anyhow",
"dep:wasm-compose",
"dep:tempfile",
]
component-model = [
"wasmtime-environ/component-model",
"dep:wasmtime-component-macro",
"dep:wasmtime-component-util",
"dep:encoding_rs",
"dep:semver",
]
component-model-async = [
"async",
"component-model",
"wasmtime-component-macro?/component-model-async",
"dep:futures",
]
component-model-bytes = ["dep:bytes"]
coredump = [
"dep:wasm-encoder",
"runtime",
"std",
]
cranelift = [
"dep:wasmtime-cranelift",
"std",
"wasmtime-unwinder/cranelift",
]
custom-fiber = ["wasmtime-fiber?/custom"]
custom-native-signals = []
custom-sync-primitives = []
custom-virtual-memory = []
debug = [
"runtime",
"async",
]
debug-builtins = ["wasmtime-jit-debug/gdb_jit_int"]
default = [
"anyhow",
"async",
"backtrace",
"cache",
"gc",
"gc-copying",
"gc-drc",
"gc-null",
"wat",
"profiling",
"parallel-compilation",
"cranelift",
"pooling-allocator",
"demangle",
"addr2line",
"coredump",
"debug-builtins",
"runtime",
"component-model",
"component-model-async",
"threads",
"stack-switching",
"std",
"debug",
"compile-time-builtins",
"wit-parser",
]
demangle = ["wasmtime-environ/demangle"]
gc = ["wasmtime-environ/gc"]
gc-copying = [
"gc",
"wasmtime-environ/gc-copying",
]
gc-drc = [
"gc",
"wasmtime-environ/gc-drc",
]
gc-null = [
"gc",
"wasmtime-environ/gc-null",
]
incremental-cache = ["std"]
memory-protection-keys = [
"pooling-allocator",
"call-hook",
]
parallel-compilation = [
"dep:rayon",
"std",
]
pooling-allocator = [
"runtime",
"std",
]
profile-pulley = [
"anyhow",
"profiling",
"pulley",
"pulley-interpreter/interp",
"pulley-interpreter/profile",
]
profiling = [
"dep:fxprof-processed-profile",
"dep:ittapi",
"dep:rustix",
"rustix/thread",
"dep:serde_json",
"std",
"wasmtime-jit-debug/perf_jitdump",
]
pulley = []
reexport-wasmparser = []
rr = [
"component-model",
"wasmtime-environ/rr",
]
runtime = [
"dep:cc",
"dep:smallvec",
"dep:mach2",
"dep:memfd",
"dep:wasmtime-versioned-export-macros",
"dep:windows-sys",
"pulley-interpreter/interp",
"dep:wasmtime-unwinder",
"dep:async-trait",
]
stack-switching = [
"runtime",
"std",
"wasmtime-environ/stack-switching",
]
std = [
"postcard/use-std",
"wasmtime-environ/std",
"wasmtime-core/std",
"object/std",
"once_cell",
"wasmtime-fiber?/std",
"pulley-interpreter/std",
"addr2line?/std",
"dep:rustix",
"wasmtime-jit-icache-coherence",
"wasmtime-jit-debug?/std",
"futures?/std",
]
threads = ["std"]
trace-log = ["wasmtime-cranelift?/trace-log"]
wave = [
"dep:wasm-wave",
"component-model",
"std",
]
winch = [
"dep:wasmtime-winch",
"std",
]
wit-parser = [
"dep:wit-parser",
"component-model",
"std",
]
wmemcheck = [
"dep:wasmtime-wmemcheck",
"wasmtime-environ/wmemcheck",
"std",
]
[lib]
name = "wasmtime"
path = "src/lib.rs"
[[test]]
name = "custom_signal_handler"
path = "tests/custom_signal_handler.rs"
[[test]]
name = "engine_across_forks"
path = "tests/engine_across_forks.rs"
harness = false
[[test]]
name = "host_segfault"
path = "tests/host_segfault.rs"
harness = false
[[test]]
name = "pooling_alloc_near_oom"
path = "tests/pooling_alloc_near_oom.rs"
harness = false
[[test]]
name = "unload-engine"
path = "tests/unload-engine.rs"
[dependencies.addr2line]
version = "0.26.0"
optional = true
default-features = false
[dependencies.async-trait]
version = "0.1.89"
optional = true
[dependencies.bitflags]
version = "2.9.4"
[dependencies.bumpalo]
version = "3.20.2"
[dependencies.bytes]
version = "1.11.1"
optional = true
default-features = false
[dependencies.encoding_rs]
version = "0.8.31"
optional = true
[dependencies.futures]
version = "0.3.31"
features = ["alloc"]
optional = true
default-features = false
[dependencies.fxprof-processed-profile]
version = "0.8.1"
optional = true
[dependencies.gimli]
version = "0.33.0"
features = ["read"]
optional = true
default-features = false
[dependencies.libc]
version = "0.2.185"
default-features = true
[dependencies.log]
version = "0.4.28"
default-features = false
[dependencies.object]
version = "0.40.0"
features = [
"read_core",
"elf",
"unaligned",
]
default-features = false
[dependencies.once_cell]
version = "1.12.0"
optional = true
[dependencies.postcard]
version = "1.1.3"
features = ["alloc"]
default-features = false
[dependencies.pulley-interpreter]
version = "=49.0.2"
[dependencies.rayon]
version = "1.5.3"
optional = true
[dependencies.semver]
version = "1.0.27"
optional = true
default-features = false
[dependencies.serde]
version = "1.0.228"
features = ["alloc"]
default-features = false
[dependencies.serde_derive]
version = "1.0.228"
[dependencies.serde_json]
version = "1.0.80"
optional = true
[dependencies.smallvec]
version = "1.15.1"
features = ["union"]
optional = true
[dependencies.target-lexicon]
version = "0.13.5"
[dependencies.tempfile]
version = "3.27.0"
optional = true
[dependencies.wasm-compose]
version = "0.258.3"
optional = true
default-features = false
[dependencies.wasm-encoder]
version = "0.258.3"
optional = true
[dependencies.wasm-wave]
version = "0.258.3"
optional = true
[dependencies.wasmparser]
version = "0.258.3"
features = ["simd"]
default-features = false
[dependencies.wasmtime-cache]
version = "=49.0.2"
optional = true
package = "wasmtime-internal-cache"
[dependencies.wasmtime-component-macro]
version = "=49.0.2"
optional = true
package = "wasmtime-internal-component-macro"
[dependencies.wasmtime-component-util]
version = "=49.0.2"
optional = true
package = "wasmtime-internal-component-util"
[dependencies.wasmtime-core]
version = "=49.0.2"
features = ["serde"]
package = "wasmtime-internal-core"
[dependencies.wasmtime-cranelift]
version = "=49.0.2"
features = ["pulley"]
optional = true
package = "wasmtime-internal-cranelift"
[dependencies.wasmtime-environ]
version = "=49.0.2"
[dependencies.wasmtime-fiber]
version = "=49.0.2"
optional = true
package = "wasmtime-internal-fiber"
[dependencies.wasmtime-jit-debug]
version = "=49.0.2"
optional = true
package = "wasmtime-internal-jit-debug"
[dependencies.wasmtime-jit-icache-coherence]
version = "=49.0.2"
optional = true
package = "wasmtime-internal-jit-icache-coherence"
[dependencies.wasmtime-unwinder]
version = "=49.0.2"
optional = true
package = "wasmtime-internal-unwinder"
[dependencies.wasmtime-versioned-export-macros]
version = "=49.0.2"
package = "wasmtime-internal-versioned-export-macros"
[dependencies.wasmtime-winch]
version = "=49.0.2"
optional = true
package = "wasmtime-internal-winch"
[dependencies.wasmtime-wmemcheck]
version = "=49.0.2"
optional = true
package = "wasmtime-internal-wmemcheck"
[dependencies.wat]
version = "1.258.3"
optional = true
[dependencies.wit-parser]
version = "0.258.3"
optional = true
[dev-dependencies.cranelift-native]
version = "0.136.2"
[dev-dependencies.env_logger]
version = "0.11.5"
[dev-dependencies.libtest-mimic]
version = "0.8.1"
[dev-dependencies.proptest]
version = "1.11.0"
[dev-dependencies.rand]
version = "0.10.1"
features = ["thread_rng"]
default-features = false
[dev-dependencies.tempfile]
version = "3.27.0"
[dev-dependencies.tokio]
version = "1.51.1"
features = [
"rt",
"time",
"macros",
"sync",
"rt-multi-thread",
]
[build-dependencies.cc]
version = "1.2.0"
optional = true
[build-dependencies.wasmtime-versioned-export-macros]
version = "=49.0.2"
optional = true
package = "wasmtime-internal-versioned-export-macros"
[target.'cfg(all(target_arch = "x86_64", target_os = "linux"))'.dependencies.ittapi]
version = "0.4.0"
optional = true
[target.'cfg(all(target_arch = "x86_64", target_os = "macos"))'.dependencies.ittapi]
version = "0.4.0"
optional = true
[target.'cfg(all(target_arch = "x86_64", target_os = "windows"))'.dependencies.ittapi]
version = "0.4.0"
optional = true
[target.'cfg(target_os = "linux")'.dependencies.memfd]
version = "0.6.5"
optional = true
[target.'cfg(target_os = "windows")'.dependencies.windows-sys]
version = "0.61.2"
features = [
"Win32_Foundation",
"Win32_System_Kernel",
"Win32_System_Memory",
"Win32_System_Diagnostics_Debug",
"Win32_System_SystemInformation",
"Win32_Storage_FileSystem",
"Win32_Security",
]
optional = true
[target.'cfg(target_vendor = "apple")'.dependencies.mach2]
version = "0.6"
optional = true
[target."cfg(unix)".dependencies.rustix]
version = "1.1.4"
features = [
"mm",
"param",
]
optional = true
[lints.clippy]
allow_attributes_without_reason = "warn"
clone_on_copy = "warn"
extra_unused_type_parameters = "warn"
from_over_into = "warn"
manual_strip = "warn"
map_clone = "warn"
multiple_bound_locations = "warn"
redundant_field_names = "warn"
uninlined_format_args = "warn"
unnecessary_cast = "warn"
unnecessary_fallible_conversions = "warn"
unnecessary_mut_passed = "warn"
unnecessary_to_owned = "warn"
useless_conversion = "warn"
[lints.clippy.all]
level = "allow"
priority = -1
[lints.rust]
trivial_numeric_casts = "warn"
unstable_features = "warn"
unused_extern_crates = "warn"
unused_import_braces = "warn"
unused_lifetimes = "warn"
unused_macro_rules = "warn"
[lints.rust.unexpected_cfgs]
level = "warn"
priority = 0
check-cfg = [
"cfg(pulley_tail_calls)",
"cfg(pulley_assume_llvm_makes_tail_calls)",
"cfg(pulley_disable_interp_simd)",
"cfg(arc_try_new)",
"cfg(fuzzing)",
"cfg(gc_zeal)",
]
+434
View File
@@ -0,0 +1,434 @@
[package]
name = "wasmtime"
version.workspace = true
authors.workspace = true
description = "High-level API to expose the Wasmtime runtime"
documentation = "https://docs.rs/wasmtime"
license = "Apache-2.0 WITH LLVM-exception"
repository = "https://github.com/bytecodealliance/wasmtime"
readme = "README.md"
edition.workspace = true
rust-version.workspace = true
[lints]
workspace = true
[package.metadata.docs.rs]
all-features = true
[dependencies]
wasmtime-environ = { workspace = true }
wasmtime-jit-debug = { workspace = true, optional = true }
wasmtime-jit-icache-coherence = { workspace = true, optional = true }
wasmtime-cache = { workspace = true, optional = true }
wasmtime-fiber = { workspace = true, optional = true }
wasmtime-cranelift = { workspace = true, optional = true, features = ['pulley'] }
wasmtime-unwinder = { workspace = true, optional = true }
wasmtime-winch = { workspace = true, optional = true }
wasmtime-component-macro = { workspace = true, optional = true }
wasmtime-component-util = { workspace = true, optional = true }
wasmtime-versioned-export-macros = { workspace = true }
wasmtime-wmemcheck = { workspace = true, optional = true }
wasmtime-core = { workspace = true, features = ['serde'] }
pulley-interpreter = { workspace = true }
target-lexicon = { workspace = true }
wasmparser = { workspace = true }
wasm-encoder = { workspace = true, optional = true }
wasm-wave = { workspace = true, optional = true }
wasm-compose = { workspace = true, optional = true }
wit-parser = { workspace = true, optional = true }
tempfile = { workspace = true, optional = true }
libc = { workspace = true }
log = { workspace = true }
wat = { workspace = true, optional = true }
serde = { workspace = true }
serde_derive = { workspace = true }
serde_json = { workspace = true, optional = true }
postcard = { workspace = true }
once_cell = { version = "1.12.0", optional = true }
rayon = { workspace = true, optional = true }
object = { workspace = true, features = ['unaligned'] }
async-trait = { workspace = true, optional = true }
encoding_rs = { version = "0.8.31", optional = true }
bumpalo = { workspace = true }
fxprof-processed-profile = { version = "0.8.1", optional = true }
gimli = { workspace = true, optional = true }
addr2line = { workspace = true, optional = true }
semver = { workspace = true, optional = true }
smallvec = { workspace = true, optional = true }
bitflags = { workspace = true }
futures = { workspace = true, features = ["alloc"], optional = true }
bytes = { workspace = true, optional = true }
[target.'cfg(target_os = "windows")'.dependencies.windows-sys]
workspace = true
optional = true
features = [
"Win32_Foundation",
"Win32_System_Kernel",
"Win32_System_Memory",
"Win32_System_Diagnostics_Debug",
"Win32_System_SystemInformation",
"Win32_Storage_FileSystem",
"Win32_Security",
]
# Allow-list the platforms that `ittapi` builds on since it's got C code that
# doesn't compile by default on all platforms just yet. Also note that this is
# only enabled for `x86_64` targets as well.
[target.'cfg(all(target_arch = "x86_64", target_os = "linux"))'.dependencies]
ittapi = { workspace = true, optional = true }
[target.'cfg(all(target_arch = "x86_64", target_os = "macos"))'.dependencies]
ittapi = { workspace = true, optional = true }
[target.'cfg(all(target_arch = "x86_64", target_os = "windows"))'.dependencies]
ittapi = { workspace = true, optional = true }
[target.'cfg(target_os = "linux")'.dependencies]
memfd = { workspace = true, optional = true }
[target.'cfg(target_vendor = "apple")'.dependencies]
mach2 = { workspace = true, optional = true }
[target.'cfg(unix)'.dependencies]
rustix = { workspace = true, optional = true, features = ["mm", "param"] }
[dev-dependencies]
env_logger = { workspace = true }
proptest = { workspace = true }
rand = { workspace = true, features = ['thread_rng'] }
tempfile = { workspace = true }
libtest-mimic = { workspace = true }
cranelift-native = { workspace = true }
wasmtime-test-util = { workspace = true }
tokio = { workspace = true, features = ["macros", "sync", "rt-multi-thread"] }
[build-dependencies]
cc = { workspace = true, optional = true }
wasmtime-versioned-export-macros = { workspace = true, optional = true }
[[test]]
name = "host_segfault"
harness = false
[[test]]
name = "engine_across_forks"
harness = false
[[test]]
name = "pooling_alloc_near_oom"
harness = false
# =============================================================================
#
# Features for the Wasmtime crate.
#
# This section contains the compile-time features of the `wasmtime` crate. These
# features can be used to add more functionality and APIs exposed from the
# crate's API. Most of them are enabled by default. Disabling features can be
# done to cut down on binary size by disabling features that are not required
# for a particular embedding.
[features]
default = [
'anyhow',
'async',
'backtrace',
'cache',
'gc',
'gc-copying',
'gc-drc',
'gc-null',
'wat',
'profiling',
'parallel-compilation',
'cranelift',
'pooling-allocator',
'demangle',
'addr2line',
'coredump',
'debug-builtins',
'runtime',
'component-model',
'component-model-async',
'threads',
'stack-switching',
'std',
'debug',
'compile-time-builtins',
'wit-parser',
]
# Enables conversion helpers between `anyhow::Error` and `wasmtime::Error`.
anyhow = ["wasmtime-environ/anyhow"]
# Enables backtrace capturing in `wasmtime::Error`.
backtrace = ["std", "wasmtime-environ/backtrace"]
# An on-by-default feature enabling runtime compilation of WebAssembly modules
# with the Cranelift compiler. Cranelift is the default compilation backend of
# Wasmtime. If disabled then WebAssembly modules can only be created from
# precompiled WebAssembly modules.
cranelift = ["dep:wasmtime-cranelift", "std", "wasmtime-unwinder/cranelift"]
# Enables support for Winch, the WebAssembly baseline compiler. The Winch compiler
# strategy in `Config` will be available. It is currently in active development
# and shouldn't be used in production applications.
winch = ["dep:wasmtime-winch", "std"]
# Enables support for Pulley, the WebAssembly interpreter. When paired with the
# `cranelift` feature, the compiler backends for the `pulley32` and `pulley64`
# targets will be available. When paired with the `runtime` feature, the Pulley
# interpreter will be built into the runtime and you can interpret WebAssembly
# modules that have been compiled to Pulley bytecode.
pulley = [
# Note that this is intentionally empty. This feature is dynamically activated
# in `build.rs` as well when the host platform does not have Cranelift support
# for example. That means that dependencies for pulley need to be already
# activated anyway.
]
# Enables support for incremental compilation cache to be enabled in `Config`.
incremental-cache = ["std"]
# Enables support for profiling guest modules.
profiling = [
"dep:fxprof-processed-profile",
"dep:ittapi",
"dep:rustix",
"rustix/thread",
"dep:serde_json",
"std",
"wasmtime-jit-debug/perf_jitdump",
]
# Enables parallel compilation of WebAssembly code.
parallel-compilation = ["dep:rayon", "std"]
# Enables support for automatic cache configuration to be enabled in `Config`.
cache = ["dep:wasmtime-cache", "std"]
# Enables support for "async stores" as well as defining host functions as
# `async fn` and calling functions asynchronously.
async = [
"dep:wasmtime-fiber",
"wasmtime-component-macro?/async",
"runtime",
]
# Enables support for the pooling instance allocation strategy
pooling-allocator = [
"runtime",
"std", # not ported to no_std yet
]
# Enables support for all architectures in Cranelift, allowing
# cross-compilation using the `wasmtime` crate's API, notably the
# `Engine::precompile_module` function.
all-arch = ["wasmtime-cranelift?/all-arch", "wasmtime-winch?/all-arch", "pulley"]
# Enables in-progress support for the component model. Note that this feature is
# in-progress, buggy, and incomplete. This is primarily here for internal
# testing purposes.
component-model = [
"wasmtime-environ/component-model",
"dep:wasmtime-component-macro",
"dep:wasmtime-component-util",
"dep:encoding_rs",
"dep:semver",
]
wmemcheck = [
"dep:wasmtime-wmemcheck",
"wasmtime-environ/wmemcheck",
"std",
]
# Enables detailed internal compiler logging via WASMTIME_LOG
trace-log = ["wasmtime-cranelift?/trace-log"]
# Enables support for demangling WebAssembly function names at runtime in
# errors such as backtraces.
demangle = ["wasmtime-environ/demangle"]
# Enable support for generating core dumps on traps.
coredump = ["dep:wasm-encoder", "runtime", "std"]
# Export some symbols from the final binary to assist in debugging
# Cranelift-generated code with native debuggers like GDB and LLDB.
debug-builtins = [
"wasmtime-jit-debug/gdb_jit_int",
]
# Enable support for executing compiled Wasm modules.
runtime = [
"dep:cc",
"dep:smallvec",
"dep:mach2",
"dep:memfd",
"dep:wasmtime-versioned-export-macros",
"dep:windows-sys",
"pulley-interpreter/interp",
"dep:wasmtime-unwinder",
"dep:async-trait",
]
# Enable support for garbage collection-related things.
#
# This Cargo feature is required to compile or run Wasm that uses any of the
# following Wasm proposals:
#
# * Reference types: https://github.com/WebAssembly/reference-types/
# * Typed function references: https://github.com/WebAssembly/function-references/
# * Garbage collection: https://github.com/WebAssembly/gc
#
# When a compiler Cargo feature (`cranelift` or `winch`) is enabled, this
# feature gates the ability to compile Wasm that uses those proposals.
#
# When the `runtime` Cargo feature is enabled, this feature gates the ability to
# load and run Wasm that uses those proposals.
#
# You can additionally configure which GC implementations are enabled via the
# `gc-drc`, `gc-null`, and `gc-copying` features.
gc = ["wasmtime-environ/gc"]
# Enable the deferred reference counting garbage collector.
gc-drc = ["gc", "wasmtime-environ/gc-drc"]
# Enable the null garbage collector.
gc-null = ["gc", "wasmtime-environ/gc-null"]
# Enable the copying garbage collector.
gc-copying = ["gc", "wasmtime-environ/gc-copying"]
# Enable runtime support for the WebAssembly threads proposal.
threads = ["std"]
stack-switching = [
"runtime",
"std",
"wasmtime-environ/stack-switching",
]
# Controls whether backtraces will attempt to parse DWARF information in
# WebAssembly modules and components to provide filenames and line numbers in
# stack traces.
addr2line = ["dep:addr2line", "dep:gimli", "std"]
# Enables support for the Rust standard library, enabling APIs that require
# types and traits from Rust's `std` such as `Path` and `Error`.
#
# Many features of the Wasmtime crate implicitly require this `std` feature.
# This will be automatically enabled if necessary.
std = [
'postcard/use-std',
'wasmtime-environ/std',
'wasmtime-core/std',
'object/std',
'once_cell',
'wasmtime-fiber?/std',
'pulley-interpreter/std',
'addr2line?/std',
"dep:rustix",
"wasmtime-jit-icache-coherence",
"wasmtime-jit-debug?/std",
"futures?/std",
]
# Enables support for the `Store::call_hook` API which enables injecting custom
# logic around all entries/exits from WebAssembly. This has a slight performance
# cost for all host functions.
call-hook = []
# Enables support for "memory protection keys" which can be used in conjunction
# with the pooling allocator on x64 to compact linear memory allocations.
memory-protection-keys = [
"pooling-allocator",
# MPK currently relies on call-hooks to manage the PKRU register for the host,
# so this is implicitly enabled when MPK is enabled.
"call-hook",
]
# Enables a re-export of wasmparser, so that an embedder of Wasmtime can use
# exactly the version of the parser library that Wasmtime does. Sometimes this
# is necessary, e.g. to guarantee that there will not be any mismatches in
# which modules are accepted due to Wasm feature configuration or support
# levels.
#
# Note that when this feature is enabled, the version of wasmparser that is
# re-exported is *not subject to semver*: we reserve the right to make patch
# releases of Wasmtime that bump the version of wasmparser used, and hence the
# version re-exported, in semver-incompatible ways. This is the tradeoff that
# the embedder needs to opt into: in order to stay exactly in sync with an
# internal detail of Wasmtime, the cost is visibility into potential internal
# version changes. This is why the re-export is guarded by a feature flag which
# is off by default.
reexport-wasmparser = []
# Enables instances of the traits defined in the wasm-wave crate, which
# provides a human-readable text format for component values.
wave = ["dep:wasm-wave", "component-model", "std"]
# Enables instance of ExportLookup for wit_parser::ItemName
wit-parser = ["dep:wit-parser", "component-model", "std"]
# For platforms that Wasmtime does not have support for Wasmtime will disable
# the use of virtual memory by default, for example allocating linear memories
# with `malloc` instead. This feature can be used, for these platforms, to
# instead use a C API defined in `wasmtime-platform.h` instead.
#
# For some more information see
# https://docs.wasmtime.dev/stability-platform-support.html#support-for-no_std
#
# This feature is not necessary for supported platforms like Unix and Windows as
# virtual memory is always enabled there.
custom-virtual-memory = []
# Same as `custom-virtual-memory` above, but for custom signal-handling APIs.
custom-native-signals = []
# Same as `custom-virtual-memory` above, but for custom sync primitive APIs.
custom-sync-primitives = []
# Same as `custom-virtual-memory` above, but for custom fiber APIs.
custom-fiber = ["wasmtime-fiber?/custom"]
# Off-by-default support to profile the Pulley interpreter. This has a
# performance hit, even when not profiling, so it's disabled by default at
# compile time.
profile-pulley = [
"anyhow",
"profiling",
"pulley",
"pulley-interpreter/interp",
"pulley-interpreter/profile",
]
# Enables support for the Component Model Async ABI, along with `future`,
# `stream`, and `error-context` types.
component-model-async = [
"async",
"component-model",
"wasmtime-component-macro?/component-model-async",
"dep:futures",
]
# Enables support and interop with the `Bytes` and `BytesMut` types contained
# within the `bytes` crate. This enables lift/lower impls, for example, as well
# as stream-related impls for `component-model-async` if that's enabled.
component-model-bytes = [
"dep:bytes",
]
# Enables support for guest debugging.
debug = [
'runtime',
'async'
]
# Enables support for defining compile-time builtins.
compile-time-builtins = ['anyhow', 'dep:wasm-compose', 'dep:tempfile']
# Enable support for the common base infrastructure of record/replay
rr = [
"component-model",
"wasmtime-environ/rr"
]
+220
View File
@@ -0,0 +1,220 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
--- LLVM Exceptions to the Apache 2.0 License ----
As an exception, if, as a result of your compiling your source code, portions
of this Software are embedded into an Object form of such source code, you
may redistribute such embedded portions in such Object form without complying
with the conditions of Sections 4(a), 4(b) and 4(d) of the License.
In addition, if you combine or link compiled forms of this Software with
software that is licensed under the GPLv2 ("Combined Software") and if a
court of competent jurisdiction determines that the patent provision (Section
3), the indemnity provision (Section 9) or other Section of the License
conflicts with the conditions of the GPLv2, you may retroactively and
prospectively choose to deem waived or otherwise exclude such Section(s) of
the License, but only in their entirety and only with respect to the Combined
Software.
+122
View File
@@ -0,0 +1,122 @@
<div align="center">
<h1><code>wasmtime</code></h1>
<p>
<strong>A standalone runtime for
<a href="https://webassembly.org/">WebAssembly</a></strong>
</p>
<strong>A <a href="https://bytecodealliance.org/">Bytecode Alliance</a> project</strong>
</div>
## About
This crate is the Rust embedding API for the [Wasmtime] project: a
cross-platform engine for running WebAssembly programs. Notable features of
Wasmtime are:
* **Fast**. Wasmtime is built on the optimizing [Cranelift] code generator to
quickly generate high-quality machine code either at runtime or
ahead-of-time. Wasmtime's runtime is also optimized for cases such as
efficient instantiation, low-overhead transitions between the embedder and
wasm, and scalability of concurrent instances.
* **[Secure]**. Wasmtime's development is strongly focused on the correctness of
its implementation with 24/7 fuzzing donated by [Google's OSS Fuzz],
leveraging Rust's API and runtime safety guarantees, careful design of
features and APIs through an [RFC process], a [security policy] in place
for when things go wrong, and a [release policy] for patching older versions
as well. We follow best practices for defense-in-depth and known
protections and mitigations for issues like Spectre. Finally, we're working
to push the state-of-the-art by collaborating with academic
researchers to formally verify critical parts of Wasmtime and Cranelift.
* **[Configurable]**. Wastime supports a rich set of APIs and build time
configuration to provide many options such as further means of restricting
WebAssembly beyond its basic guarantees such as its CPU and Memory
consumption. Wasmtime also runs in tiny environments all the way up to massive
servers with many concurrent instances.
* **[WASI]**. Wasmtime supports a rich set of APIs for interacting with the host
environment through the [WASI standard](https://wasi.dev).
* **[Standards Compliant]**. Wasmtime passes the [official WebAssembly test
suite](https://github.com/WebAssembly/testsuite), implements the [official C
API of wasm](https://github.com/WebAssembly/wasm-c-api), and implements
[future proposals to WebAssembly](https://github.com/WebAssembly/proposals) as
well. Wasmtime developers are intimately engaged with the WebAssembly
standards process all along the way too.
[Wasmtime]: https://github.com/bytecodealliance/wasmtime
[Cranelift]: https://github.com/bytecodealliance/wasmtime/blob/main/cranelift/README.md
[Google's OSS Fuzz]: https://google.github.io/oss-fuzz/
[security policy]: https://bytecodealliance.org/security
[RFC process]: https://github.com/bytecodealliance/rfcs
[release policy]: https://docs.wasmtime.dev/stability-release.html
[Secure]: https://docs.wasmtime.dev/security.html
[Configurable]: https://docs.rs/wasmtime/latest/wasmtime/struct.Config.html
[WASI]: https://docs.rs/wasmtime-wasi/latest/wasmtime_wasi/
[Standards Compliant]: https://docs.wasmtime.dev/stability-wasm-proposals.html
## Example
An example of using the Wasmtime embedding API for running a small WebAssembly
module might look like:
```rust
use anyhow::Result;
use wasmtime::*;
fn main() -> Result<()> {
// Modules can be compiled through either the text or binary format
let engine = Engine::default();
let wat = r#"
(module
(import "host" "host_func" (func $host_hello (param i32)))
(func (export "hello")
i32.const 3
call $host_hello)
)
"#;
let module = Module::new(&engine, wat)?;
// Create a `Linker` which will be later used to instantiate this module.
// Host functionality is defined by name within the `Linker`.
let mut linker = Linker::new(&engine);
linker.func_wrap("host", "host_func", |caller: Caller<'_, u32>, param: i32| {
println!("Got {} from WebAssembly", param);
println!("my host state is: {}", caller.data());
})?;
// All wasm objects operate within the context of a "store". Each
// `Store` has a type parameter to store host-specific data, which in
// this case we're using `4` for.
let mut store = Store::new(&engine, 4);
let instance = linker.instantiate(&mut store, &module)?;
let hello = instance.get_typed_func::<(), ()>(&mut store, "hello")?;
// And finally we can call the wasm!
hello.call(&mut store, ())?;
Ok(())
}
```
More examples and information can be found in the `wasmtime` crate's [online
documentation](https://docs.rs/wasmtime) as well.
## Documentation
[📚 Read the Wasmtime guide here! 📚][guide]
The [wasmtime guide][guide] is the best starting point to learn about what
Wasmtime can do for you or help answer your questions about Wasmtime. If you're
curious in contributing to Wasmtime, [it can also help you do
that][contributing]!
For information on supported versions of Wasmtime see [the corresponding online book
documentation](https://docs.wasmtime.dev/stability-release.html#current-versions).
[contributing]: https://bytecodealliance.github.io/wasmtime/contributing.html
[guide]: https://bytecodealliance.github.io/wasmtime
+137
View File
@@ -0,0 +1,137 @@
use std::str;
fn main() {
println!("cargo:rerun-if-changed=build.rs");
// NB: duplicating a workaround in the wasmtime-fiber build script.
custom_cfg("asan", cfg_is("sanitize", "address"));
let unix = cfg("unix");
let windows = cfg("windows");
let miri = cfg("miri");
// A boolean indicating whether there's a `sys` module for this platform.
// This is true for `unix` or `windows`, but both of those require the `std`
// feature to also be active so check that too.
let supported_os = (unix || windows) && cfg!(feature = "std");
let target_arch = std::env::var("CARGO_CFG_TARGET_ARCH").unwrap();
// Determine if the current host architecture is supported by Cranelift
// meaning that we might be executing native code.
let has_host_compiler_backend = match target_arch.as_str() {
"x86_64" | "riscv64" | "s390x" | "aarch64" => true,
_ => false,
};
// Determine if builtin stack-switching routines are provided for the
// current host architecture by `wasmtime-internal-fiber`.
let has_builtin_stackswitch = match target_arch.as_str() {
"aarch64" | "x86_64" | "x86" | "arm" | "s390x" | "riscv64" => true,
"riscv32" => std::env::var("CARGO_CFG_TARGET_FEATURE")
.unwrap()
.split(',')
.find(|&feat| matches!(feat, "f" | "v"))
.is_none(),
_ => false,
};
let has_native_signals = !miri
&& (supported_os || cfg!(feature = "custom-native-signals"))
&& has_host_compiler_backend
&& !is_buggy_s390x_qemu_emulation();
let has_virtual_memory = supported_os || cfg!(feature = "custom-virtual-memory");
let has_custom_sync = !cfg!(feature = "std")
&& cfg!(feature = "custom-sync-primitives")
&& cfg!(feature = "runtime");
let has_custom_fiber = !has_builtin_stackswitch && cfg!(feature = "custom-fiber");
custom_cfg("has_native_signals", has_native_signals);
custom_cfg("has_virtual_memory", has_virtual_memory);
custom_cfg("has_custom_fiber", has_custom_fiber);
custom_cfg("has_custom_sync", has_custom_sync);
custom_cfg("has_host_compiler_backend", has_host_compiler_backend);
custom_cfg("gc_zeal", cfg("fuzzing"));
// If this OS isn't supported and no debug-builtins or if Cranelift doesn't support
// the host or there's no need to build these helpers.
#[cfg(feature = "runtime")]
if has_host_compiler_backend && (supported_os || cfg!(feature = "debug-builtins")) {
build_c_helpers();
}
// Figure out what to do about Pulley.
//
// If the target platform does not have any Cranelift support then Pulley
// will be used by default. That means that the pulley feature is "enabled"
// here and the default target is pulley. Note that by enabling the feature
// here it doesn't actually enable the Cargo feature, it just passes a cfg
// to rustc. That means that conditional dependencies enabled in
// `Cargo.toml` (or other features) by `pulley` aren't activated, which is
// why the `pulley` feature of this crate depends on nothing else.
let default_target_pulley = !has_host_compiler_backend || miri;
custom_cfg("default_target_pulley", default_target_pulley);
if default_target_pulley {
println!("cargo:rustc-cfg=feature=\"pulley\"");
}
}
// Attempts to detect if we're running in Wasmtime's CI, testing s390x, and
// testing s390x under QEMU. In this situation we're experiencing flaky
// failures, more info in #10000, and current LLM-based analysis seems to point
// to the signal handler state being corrupted in emulation. We're seeing
// many spurious failures per week so this is a bit of a last-ditch attempt to
// work around the issue. In this situation we pretend that native signals are
// not available for s390x which forces disabling signals-based-traps which in
// theory means we don't rely on signal handlers...
fn is_buggy_s390x_qemu_emulation() -> bool {
std::env::var("CI").is_ok()
&& std::env::var("WASMTIME_TEST_NO_HOG_MEMORY").is_ok()
&& std::env::var("QEMU_BUILD_VERSION").is_ok()
&& std::env::var("CARGO_CFG_TARGET_ARCH").unwrap() == "s390x"
}
fn cfg(key: &str) -> bool {
std::env::var(&format!("CARGO_CFG_{}", key.to_uppercase())).is_ok()
}
fn cfg_is(key: &str, val: &str) -> bool {
std::env::var(&format!("CARGO_CFG_{}", key.to_uppercase()))
.ok()
.as_deref()
== Some(val)
}
fn custom_cfg(key: &str, enabled: bool) {
println!("cargo:rustc-check-cfg=cfg({key})");
if enabled {
println!("cargo:rustc-cfg={key}");
}
}
#[cfg(feature = "runtime")]
fn build_c_helpers() {
use wasmtime_versioned_export_macros::versioned_suffix;
let mut build = cc::Build::new();
build.warnings(true);
let arch = std::env::var("CARGO_CFG_TARGET_ARCH").unwrap();
let os = std::env::var("CARGO_CFG_TARGET_OS").unwrap();
build.define(&format!("CFG_TARGET_OS_{os}"), None);
build.define(&format!("CFG_TARGET_ARCH_{arch}"), None);
build.define("VERSIONED_SUFFIX", Some(versioned_suffix!()));
if std::env::var("CARGO_FEATURE_DEBUG_BUILTINS").is_ok() {
build.define("FEATURE_DEBUG_BUILTINS", None);
} else if cfg("windows") {
// If debug builtins are disabled and this target is for Windows then
// there's no need to build the C helpers file.
//
// TODO: should skip this on Unix targets as well but needs a solution
// for `wasmtime_using_libunwind`.
return;
}
println!("cargo:rerun-if-changed=src/runtime/vm/helpers.c");
build.file("src/runtime/vm/helpers.c");
build.compile("wasmtime-helpers");
}
@@ -0,0 +1,8 @@
# Seeds for failure cases proptest has generated in the past. It is
# automatically read and these particular cases re-run before any
# novel cases are generated.
#
# It is recommended to check this file in to source control so that
# everyone who runs the test benefits from these saved cases.
cc b26e69fbaf46deb79652859039538e422818fd40b9afff63faa7aacbddecfd3d # shrinks to (capacity, ops) = (219544665809630458, [(10, Alloc(Layout { size: 193045289231815352, align: 8 (1 << 3) })), (10, Dealloc(Layout { size: 193045289231815352, align: 8 (1 << 3) }))])
cc 174fe731edb88dd41ae77aeb8ddc0a94e09f8d8ad0849709c440c8995e639bbd # shrinks to (initial_capacity, ops) = (558656369836710805, [Alloc(292159696945657979, Layout { size: 339386803970957424, align: 2 (1 << 1) }), Dealloc(292159696945657979, Layout { size: 339386803970957424, align: 2 (1 << 1) }), AddCapacity(5898306987443335331)])
@@ -0,0 +1,9 @@
# Seeds for failure cases proptest has generated in the past. It is
# automatically read and these particular cases re-run before any
# novel cases are generated.
#
# It is recommended to check this file in to source control so that
# everyone who runs the test benefits from these saved cases.
cc 696808084287d5d58b85c60c4720227ab4dd83ada7be6841a67162023aaf4914 # shrinks to c = SlabConstraints { max_memory_bytes: 0, num_memory_slots: 1, num_pkeys_available: 0, guard_bytes: 9223372036854775808 }
cc cf9f6c36659f7f56ed8ea646e8c699cbf46708cef6911cdd376418ad69ea1388 # shrinks to c = SlabConstraints { max_memory_bytes: 14161452635954640438, num_memory_slots: 0, num_pkeys_available: 0, guard_bytes: 4285291437754911178 }
cc 58f42405c4fbfb4b464950f372995d4d08a77d6884335e38c2e68d590cacf7d8 # shrinks to c = SlabConstraints { expected_slot_bytes: 0, max_memory_bytes: 8483846582232735745, num_slots: 0, num_pkeys_available: 0, guard_bytes: 0, guard_before_slots: false }
File diff suppressed because it is too large. Load diff
+866
View File
@@ -0,0 +1,866 @@
use crate::Engine;
use crate::prelude::*;
use std::borrow::Cow;
use std::path::Path;
#[cfg(feature = "compile-time-builtins")]
use crate::hash_map::HashMap;
#[cfg(feature = "compile-time-builtins")]
mod compile_time_builtins;
/// Builder-style structure used to create a [`Module`](crate::module::Module) or
/// pre-compile a module to a serialized list of bytes.
///
/// This structure can be used for more advanced configuration when compiling a
/// WebAssembly module. Most configuration can use simpler constructors such as:
///
/// * [`Module::new`](crate::Module::new)
/// * [`Module::from_file`](crate::Module::from_file)
/// * [`Module::from_binary`](crate::Module::from_binary)
///
/// Note that a [`CodeBuilder`] always involves compiling WebAssembly bytes
/// to machine code. To deserialize a list of bytes use
/// [`Module::deserialize`](crate::Module::deserialize) instead.
///
/// A [`CodeBuilder`] requires a source of WebAssembly bytes to be configured
/// before calling [`compile_module_serialized`] or [`compile_module`]. This can
/// be provided with either the [`wasm_binary`] or [`wasm_binary_file`] method.
/// Note that only a single source of bytes can be provided.
///
/// # WebAssembly Text Format
///
/// This builder supports the WebAssembly Text Format (`*.wat` files) through
/// the [`CodeBuilder::wasm_binary_or_text`] and
/// [`CodeBuilder::wasm_binary_or_text_file`] methods. These methods
/// automatically convert WebAssembly text files to binary. Note though that
/// this behavior is disabled if the `wat` crate feature is not enabled.
///
/// [`compile_module_serialized`]: CodeBuilder::compile_module_serialized
/// [`compile_module`]: CodeBuilder::compile_module
/// [`wasm_binary`]: CodeBuilder::wasm_binary
/// [`wasm_binary_file`]: CodeBuilder::wasm_binary_file
pub struct CodeBuilder<'a> {
pub(super) engine: &'a Engine,
wasm: Option<Cow<'a, [u8]>>,
wasm_path: Option<Cow<'a, Path>>,
dwarf_package: Option<Cow<'a, [u8]>>,
dwarf_package_path: Option<Cow<'a, Path>>,
unsafe_intrinsics_import: Option<String>,
/// A map from import name to the Wasm bytes of the associated compile-time
/// builtin and its file path, if any.
//
// XXX: we can't use `'a` here without forcing us to change a bunch of
// callers because `HashMap` has a `Drop` implementation, so `dropck` thinks
// that all `'a` borrows could be used by `CodeBuilder`'s `Drop`, which
// means that a bunch of the existing calls to `.wasm_bytes` and such would
// need to be reordered with the `CodeBuilder`'s construction.
#[cfg(feature = "compile-time-builtins")]
compile_time_builtins: HashMap<Cow<'a, str>, Cow<'a, [u8]>>,
}
/// Return value of [`CodeBuilder::hint`]
pub enum CodeHint {
/// Hint that the code being compiled is a module.
Module,
/// Hint that the code being compiled is a component.
Component,
}
impl<'a> CodeBuilder<'a> {
/// Creates a new builder which will insert modules into the specified
/// [`Engine`].
pub fn new(engine: &'a Engine) -> Self {
CodeBuilder {
engine,
wasm: None,
wasm_path: None,
dwarf_package: None,
dwarf_package_path: None,
unsafe_intrinsics_import: None,
#[cfg(feature = "compile-time-builtins")]
compile_time_builtins: HashMap::default(),
}
}
/// Configures the WebAssembly binary that is being compiled.
///
/// The `wasm_bytes` parameter must be a binary WebAssembly file.
/// This will be stored within the [`CodeBuilder`] for processing later when
/// compilation is finalized.
///
/// The optional `wasm_path` parameter is the path to the `wasm_bytes` on
/// disk, if any. This may be used for diagnostics and other
/// debugging-related purposes, but this method will not read the path
/// specified.
///
/// # Errors
///
/// This method will return an error if WebAssembly bytes have already been
/// configured.
pub fn wasm_binary(
&mut self,
wasm_bytes: impl Into<Cow<'a, [u8]>>,
wasm_path: Option<&'a Path>,
) -> Result<&mut Self> {
if self.wasm.is_some() {
bail!("cannot configure wasm bytes twice");
}
self.wasm = Some(wasm_bytes.into());
self.wasm_path = wasm_path.map(|p| p.into());
if self.wasm_path.is_some() {
self.dwarf_package_from_wasm_path()?;
}
Ok(self)
}
/// Equivalent of [`CodeBuilder::wasm_binary`] that also accepts the
/// WebAssembly text format.
///
/// This method will configure the WebAssembly binary to be compiled. The
/// input `wasm_bytes` may either be the wasm text format or the binary
/// format. If the `wat` crate feature is enabled, which is enabled by
/// default, then the text format will automatically be converted to the
/// binary format.
///
/// # Errors
///
/// This method will return an error if WebAssembly bytes have already been
/// configured. This method will also return an error if `wasm_bytes` is the
/// wasm text format and the text syntax is not valid.
pub fn wasm_binary_or_text(
&mut self,
wasm_bytes: &'a [u8],
wasm_path: Option<&'a Path>,
) -> Result<&mut Self> {
#[cfg(feature = "wat")]
let wasm_bytes = wat::parse_bytes(wasm_bytes).map_err(|mut e| {
if let Some(path) = wasm_path {
e.set_path(path);
}
e
})?;
self.wasm_binary(wasm_bytes, wasm_path)
}
/// Reads the `file` specified for the WebAssembly bytes that are going to
/// be compiled.
///
/// This method will read `file` from the filesystem and interpret it
/// as a WebAssembly binary.
///
/// A DWARF package file will be probed using the root of `file` and with a
/// `.dwp` extension. If found, it will be loaded and DWARF fusion
/// performed.
///
/// # Errors
///
/// This method will return an error if WebAssembly bytes have already been
/// configured.
///
/// If `file` can't be read or an error happens reading it then that will
/// also be returned.
///
/// If DWARF fusion is performed and the DWARF packaged file cannot be read
/// then an error will be returned.
pub fn wasm_binary_file(&mut self, file: &'a Path) -> Result<&mut Self> {
let wasm = std::fs::read(file)
.with_context(|| format!("failed to read input file: {}", file.display()))?;
self.wasm_binary(wasm, Some(file))
}
/// Equivalent of [`CodeBuilder::wasm_binary_file`] that also accepts the
/// WebAssembly text format.
///
/// This method is will read the file at `path` and interpret the contents
/// to determine if it's the wasm text format or binary format. The file
/// extension of `file` is not consulted. The text format is automatically
/// converted to the binary format if the crate feature `wat` is active.
///
/// # Errors
///
/// In addition to the errors returned by [`CodeBuilder::wasm_binary_file`]
/// this may also fail if the text format is read and the syntax is invalid.
pub fn wasm_binary_or_text_file(&mut self, file: &'a Path) -> Result<&mut Self> {
#[cfg(feature = "wat")]
{
let wasm = wat::parse_file(file)?;
self.wasm_binary(wasm, Some(file))
}
#[cfg(not(feature = "wat"))]
{
self.wasm_binary_file(file)
}
}
/// Get the Wasm to be compiled.
///
/// When using compile-time builtins, compose the builtins and the guest
/// Wasm together, before returning the Wasm.
pub(super) fn get_wasm(&self) -> Result<Cow<'_, [u8]>> {
let wasm = self
.wasm
.as_deref()
.ok_or_else(|| format_err!("no wasm bytes have been configured"))?;
#[cfg(not(feature = "compile-time-builtins"))]
{
Ok(wasm.into())
}
#[cfg(feature = "compile-time-builtins")]
{
self.compose_compile_time_builtins(wasm)
.context("failed to compose compile-time builtins with the main Wasm")
}
}
/// Expose Wasmtime's unsafe intrinsics under the given import name.
///
/// These intrinsics provide native memory loads and stores to Wasm; they
/// are *extremely* unsafe! If you are not absolutely sure that you need
/// these unsafe intrinsics, *do not use them!* See the safety section below
/// for details.
///
/// This functionality is intended to be used when implementing
/// [compile-time builtins][CodeBuilder::compile_time_builtins_binary]; that
/// is, satisfying a Wasm import via special-cased, embedder-specific code
/// at compile time. You should never use these intrinsics to intentionally
/// subvert the Wasm sandbox. You should strive to implement safe functions
/// that encapsulate your uses of these intrinsics such that, regardless of
/// any value given as arguments, your functions *cannot* result in loading
/// from or storing to invalid pointers, or any other kind of unsafety. See
/// below for an example of the intended use cases.
///
/// Wasmtime's unsafe intrinsics can only be exposed to Wasm components, not
/// core modules, currently.
///
/// Note that when compile-time builtins are defined on a `CodeBuilder`,
/// only the compile-time builtins can import the unsafe intrinsics, and the
/// main guest program cannot import them.
///
/// # Safety
///
/// Extreme care must be taken when using these intrinsics.
///
/// All loads of or stores to pointers derived from `store-data-address` are
/// inherently tied to a particular `T` type in a `Store<T>`. It is wildly
/// unsafe to run a Wasm program that uses unsafe intrinsics to access the
/// store's `T` inside a `Store<U>`. You must only run Wasm that uses unsafe
/// intrinsics in a `Store<T>` where the `T` is the type expected by the
/// Wasm's unsafe-intrinsics usage.
///
/// Furthermore, usage of these intrinsics is not only tied to a particular
/// `T` type, but also to `T`'s layout on the host platform. The size and
/// alignment of `T`, the offsets of its fields, and those fields' size and
/// alignment can all vary across not only architecture but also operating
/// system. With care, you can define your `T` type such that its layout is
/// identical across the platforms that you run Wasm on, allowing you to
/// reuse the same Wasm binary and its unsafe-intrinsics usage on all your
/// platforms. Failing that, you must only run a Wasm program that uses
/// unsafe intrinsics on the host platform that its unsafe-intrinsic usage
/// is specialized to. See the portability section and example below for
/// more details.
///
/// You are *strongly* encouraged to add assertions for the layout
/// properties that your unsafe-intrinsic usage's safety relies upon:
///
/// ```rust
/// /// This type is used as `wasmtime::Store<MyData>` and accessed by Wasm via
/// /// unsafe intrinsics.
/// #[repr(C, align(8))]
/// struct MyData {
/// id: u64,
/// counter: u32,
/// buf: [u8; 4],
/// }
///
/// // Assert that the layout is what our Wasm's unsafe-intrinsics usage expects.
/// static _MY_DATA_LAYOUT_ASSERTIONS: () = {
/// assert!(core::mem::size_of::<MyData>() == 16);
/// assert!(core::mem::align_of::<MyData>() == 8);
/// assert!(core::mem::offset_of!(MyData, id) == 0);
/// assert!(core::mem::offset_of!(MyData, counter) == 8);
/// assert!(core::mem::offset_of!(MyData, buf) == 12);
/// };
/// ```
///
/// Finally, every pointer loaded from or stored to must:
///
/// * Be non-null
///
/// * Be aligned to the access type's natural alignment (e.g. 8-byte alignment
/// for `u64`, 4-byte alignment for `u32`, etc...)
///
/// * Point to a memory block that is valid to read from (for loads) or
/// valid to write to (for stores) under Rust's pointer provenance rules
///
/// * Point to a memory block that is at least as large as the access type's
/// natural size (e.g. 1 byte for `u8`, 2 bytes for `u16`, etc...)
///
/// * Point to a memory block that is not accessed concurrently by any other
/// threads
///
/// Failure to uphold any of these invariants will lead to unsafety,
/// undefined behavior, and/or data races.
///
/// # Intrinsics
///
/// | Name | Parameters | Results |
/// |----------------------------|-----------------------------|---------|
/// | `u8-native-load` | `u64` | `u8` |
/// | `u16-native-load` | `u64` | `u16` |
/// | `u32-native-load` | `u64` | `u32` |
/// | `u64-native-load` | `u64` | `u64` |
/// | `u8-native-store` | `u64`, `u8` | - |
/// | `u16-native-store` | `u64`, `u16` | - |
/// | `u32-native-store` | `u64`, `u32` | - |
/// | `u64-native-store` | `u64`, `u64` | - |
/// | `u8-checked-native-load` | `u64`, `u64`, `u64` | `u8` |
/// | `u16-checked-native-load` | `u64`, `u64`, `u64` | `u16` |
/// | `u32-checked-native-load` | `u64`, `u64`, `u64` | `u32` |
/// | `u64-checked-native-load` | `u64`, `u64`, `u64` | `u64` |
/// | `u8-checked-native-store` | `u64`, `u64`, `u64`, `u8` | - |
/// | `u16-checked-native-store` | `u64`, `u64`, `u64`, `u16` | - |
/// | `u32-checked-native-store` | `u64`, `u64`, `u64`, `u32` | - |
/// | `u64-checked-native-store` | `u64`, `u64`, `u64`, `u64` | - |
/// | `store-data-address` | - | `u64` |
///
/// ## `*-native-load`
///
/// These intrinsics perform an unsandboxed, unsynchronized load from native
/// memory, using the native endianness.
///
/// ## `*-native-store`
///
/// These intrinsics perform an unsandboxed, unsynchronized store to native
/// memory, using the native endianness.
///
/// ## `*-checked-native-load`
///
/// These intrinsics perform a bounds-checked version of the corresponding
/// `*-native-load` intrinsic. Their parameters are a `base_address`, an
/// `offset`, and a `length` (in bytes) describing a region of native
/// memory, and they load from `base_address + offset`.
///
/// Before performing the load, they check that the access is in bounds. If
/// `offset + SIZE > length` -- where `SIZE` is the byte width of the access
/// (e.g. `4` for `u32-checked-native-load`) -- or if that addition
/// overflows, then the intrinsic raises a trap and the load is *not*
/// performed. Otherwise the load is performed exactly as the corresponding
/// unchecked `*-native-load` intrinsic would, against the address
/// `base_address + offset`.
///
/// Note that the bounds check is performed against the given `length`
/// argument; it is the caller's responsibility to pass a `length` that
/// actually describes a valid region of memory beginning at `base_address`.
/// When the access is in bounds, the resulting `base_address + offset`
/// access must still uphold all of the safety invariants described above
/// for the unchecked intrinsics.
///
/// ## `*-checked-native-store`
///
/// These intrinsics perform a bounds-checked version of the corresponding
/// `*-native-store` intrinsic. Their parameters are a `base_address`, an
/// `offset`, a `length` (in bytes), and a `value`. They perform the same
/// bounds check as the `*-checked-native-load` intrinsics -- trapping,
/// without storing anything, if `offset + SIZE > length` or if that
/// addition overflows -- and otherwise store `value` to `base_address +
/// offset`.
///
/// ## `store-data-address`
///
/// This intrinsic function returns the pointer to the embedder's `T` data
/// inside a `Store<T>`.
///
/// In general, all native load and store intinsics should operate on memory
/// addresses that are derived from a call to this intrinsic. If you want to
/// expose data for raw memory access by Wasm, put it inside the `T` in your
/// `Store<T>` and Wasm's access to that data should derive from this
/// intrinsic.
///
/// # Portability
///
/// Loads and stores are always performed using the architecture's native
/// endianness.
///
/// Addresses passed to and returned from these intrinsics are always
/// 64-bits large. The upper half of the value is simply ignored on 32-bit
/// architectures.
///
/// With care, you can design your store's `T` type such that accessing it
/// via these intrinsics is portable, and you can reuse a single Wasm binary
/// (and its set of intrinsic calls) across all of the platforms, with the
/// following rules of thumb:
///
/// * Only access `u8`, `u16`, `u32`, and `u64` data via these intrinsics.
///
/// * If you need to access other types of data, encode it into those types
/// and then access the encoded data from the intrinsics.
///
/// * Use `union`s to encode pointers and pointer-sized data as a `u64` and
/// then access it via the `u64-native-{load,store}` intrinsics. See
/// `ExposedPointer` in the example below.
///
/// # Example
///
/// The following example shows how you can use unsafe intrinsics and
/// compile-time builtins to give Wasm direct zero-copy access to a host
/// buffer.
///
/// ```rust
/// use std::mem;
/// use wasmtime::*;
///
/// // A `*mut u8` pointer that is exposed directly to Wasm via unsafe intrinsics.
/// #[repr(align(8))]
/// union ExposedPointer {
/// pointer: *mut u8,
/// padding: u64,
/// }
///
/// static _EXPOSED_POINTER_LAYOUT_ASSERTIONS: () = {
/// assert!(mem::size_of::<ExposedPointer>() == 8);
/// assert!(mem::align_of::<ExposedPointer>() == 8);
/// };
///
/// impl ExposedPointer {
/// /// Wrap the given pointer into an `ExposedPointer`.
/// fn new(pointer: *mut u8) -> Self {
/// // NB: Zero-initialize to avoid potential footguns with accessing
/// // undefined bytes.
/// let mut p = Self { padding: 0 };
/// p.pointer = pointer;
/// p
/// }
///
/// /// Get the wrapped pointer.
/// fn get(&self) -> *mut u8 {
/// unsafe { self.pointer }
/// }
/// }
///
/// /// This is the `T` type we will put inside our
/// /// `wasmtime::Store<T>`s. It contains a pointer to a heap-allocated buffer
/// /// in host memory, which we will give Wasm zero-copy access to via unsafe
/// /// intrinsics.
/// #[repr(C)]
/// struct StoreData {
/// buf_ptr: ExposedPointer,
/// buf_len: u64,
/// }
///
/// static _STORE_DATA_LAYOUT_ASSERTIONS: () = {
/// assert!(mem::size_of::<StoreData>() == 16);
/// assert!(mem::align_of::<StoreData>() == 8);
/// assert!(mem::offset_of!(StoreData, buf_ptr) == 0);
/// assert!(mem::offset_of!(StoreData, buf_len) == 8);
/// };
///
/// impl Drop for StoreData {
/// fn drop(&mut self) {
/// let len = usize::try_from(self.buf_len).unwrap();
/// let ptr = std::ptr::slice_from_raw_parts_mut(self.buf_ptr.get(), len);
/// unsafe {
/// let _ = Box::from_raw(ptr);
/// }
/// }
/// }
///
/// impl StoreData {
/// /// Create a new `StoreData`, allocating an inner buffer containing
/// /// `bytes`.
/// fn new(bytes: impl IntoIterator<Item = u8>) -> Self {
/// let buf: Box<[u8]> = bytes.into_iter().collect();
/// let ptr = Box::into_raw(buf);
/// Self {
/// buf_ptr: ExposedPointer::new(ptr.cast::<u8>()),
/// buf_len: u64::try_from(ptr.len()).unwrap(),
/// }
/// }
///
/// /// Get the inner buffer as a shared slice.
/// fn buf(&self) -> &[u8] {
/// let ptr = self.buf_ptr.get().cast_const();
/// let len = usize::try_from(self.buf_len).unwrap();
/// unsafe {
/// std::slice::from_raw_parts(ptr, len)
/// }
/// }
/// }
///
/// # fn main() -> Result<()> {
/// // Enable function inlining during compilation. If you are using unsafe intrinsics, you
/// // almost assuredly want them inlined to avoid function call overheads.
/// let mut config = Config::new();
/// config.compiler_inlining(wasmtime::Inlining::Yes);
///
/// let engine = Engine::new(&config)?;
/// let linker = wasmtime::component::Linker::new(&engine);
///
/// // Create a new builder for configuring a Wasm compilation.
/// let mut builder = CodeBuilder::new(&engine);
///
/// // Allow the code we are building to use Wasmtime's unsafe intrinsics.
/// //
/// // SAFETY: we wrap all usage of the intrinsics in safe APIs and only instantiate the code
/// // within a `Store<T>` where `T = StoreData`, as the code expects.
/// unsafe {
/// builder.expose_unsafe_intrinsics("unsafe-intrinsics");
/// }
///
/// // Define the compile-time builtin that encapsulates the
/// // intrinsics' unsafety and builds a safe API on top of them.
/// unsafe {
/// builder.compile_time_builtins_binary_or_text(
/// "safe-api",
/// r#"
/// (component
/// (import "unsafe-intrinsics"
/// (instance $intrinsics
/// (export "store-data-address" (func (result u64)))
/// (export "u64-native-load" (func (param "pointer" u64) (result u64)))
/// (export "u8-native-load" (func (param "pointer" u64) (result u8)))
/// (export "u8-native-store" (func (param "pointer" u64) (param "value" u8)))
/// )
/// )
///
/// ;; The core Wasm module that implements the safe API.
/// (core module $safe-api-impl
/// (import "" "store-data-address" (func $store-data-address (result i64)))
/// (import "" "u64-native-load" (func $u64-native-load (param i64) (result i64)))
/// (import "" "u8-native-load" (func $u8-native-load (param i64) (result i32)))
/// (import "" "u8-native-store" (func $u8-native-store (param i64 i32)))
///
/// ;; Load the `StoreData::buf_ptr` field
/// (func $get-buf-ptr (result i64)
/// (call $u64-native-load (i64.add (call $store-data-address) (i64.const 0)))
/// )
///
/// ;; Load the `StoreData::buf_len` field
/// (func $get-buf-len (result i64)
/// (call $u64-native-load (i64.add (call $store-data-address) (i64.const 8)))
/// )
///
/// ;; Check that `$i` is within `StoreData` buffer's bounds, raising a trap
/// ;; otherwise.
/// (func $bounds-check (param $i i64)
/// (if (i64.lt_u (local.get $i) (call $get-buf-len))
/// (then (return))
/// (else (unreachable))
/// )
/// )
///
/// ;; A safe function to get the `i`th byte from `StoreData`'s buffer,
/// ;; raising a trap on out-of-bounds accesses.
/// (func (export "get") (param $i i64) (result i32)
/// (call $bounds-check (local.get $i))
/// (call $u8-native-load (i64.add (call $get-buf-ptr) (local.get $i)))
/// )
///
/// ;; A safe function to set the `i`th byte in `StoreData`'s buffer,
/// ;; raising a trap on out-of-bounds accesses.
/// (func (export "set") (param $i i64) (param $value i32)
/// (call $bounds-check (local.get $i))
/// (call $u8-native-store (i64.add (call $get-buf-ptr) (local.get $i))
/// (local.get $value))
/// )
///
/// ;; A safe function to get the length of the `StoreData` buffer.
/// (func (export "len") (result i64)
/// (call $get-buf-len)
/// )
/// )
///
/// ;; Lower the imported intrinsics from component functions to core functions.
/// (core func $store-data-address' (canon lower (func $intrinsics "store-data-address")))
/// (core func $u64-native-load' (canon lower (func $intrinsics "u64-native-load")))
/// (core func $u8-native-load' (canon lower (func $intrinsics "u8-native-load")))
/// (core func $u8-native-store' (canon lower (func $intrinsics "u8-native-store")))
///
/// ;; Instantiate our safe API implementation, passing in the lowered unsafe
/// ;; intrinsics as its imports.
/// (core instance $instance
/// (instantiate $safe-api-impl
/// (with "" (instance
/// (export "store-data-address" (func $store-data-address'))
/// (export "u64-native-load" (func $u64-native-load'))
/// (export "u8-native-load" (func $u8-native-load'))
/// (export "u8-native-store" (func $u8-native-store'))
/// ))
/// )
/// )
///
/// ;; Lift the safe API's exports from core functions to component functions
/// ;; and export them.
/// (func (export "get") (param "i" u64) (result u8)
/// (canon lift (core func $instance "get"))
/// )
/// (func (export "set") (param "i" u64) (param "value" u8)
/// (canon lift (core func $instance "set"))
/// )
/// (func (export "len") (result u64)
/// (canon lift (core func $instance "len"))
/// )
/// )
/// "#.as_bytes(),
/// None,
/// )?;
/// }
///
/// // Provide the guest Wasm that we are compiling, which uses the safe API we
/// // implemented as a compile-time builtin.
/// builder.wasm_binary_or_text(
/// r#"
/// (component
/// ;; Import the safe API.
/// (import "safe-api"
/// (instance $safe-api
/// (export "get" (func (param "i" u64) (result u8)))
/// (export "set" (func (param "i" u64) (param "value" u8)))
/// (export "len" (func (result u64)))
/// )
/// )
///
/// ;; Define this component's core module implementation.
/// (core module $main-impl
/// (import "" "get" (func $get (param i64) (result i32)))
/// (import "" "set" (func $set (param i64 i32)))
/// (import "" "len" (func $len (result i64)))
///
/// (func (export "main")
/// (local $i i64)
/// (local $n i64)
///
/// (local.set $i (i64.const 0))
/// (local.set $n (call $len))
///
/// (loop $loop
/// ;; When we have iterated over every byte in the
/// ;; buffer, exit.
/// (if (i64.ge_u (local.get $i) (local.get $n))
/// (then (return)))
///
/// ;; Increment the `i`th byte in the buffer.
/// (call $set (local.get $i)
/// (i32.add (call $get (local.get $i))
/// (i32.const 1)))
///
/// ;; Increment `i` and continue to the next iteration
/// ;; of the loop.
/// (local.set $i (i64.add (local.get $i) (i64.const 1)))
/// (br $loop)
/// )
/// )
/// )
///
/// ;; Lower the imported safe APIs from component functions to core functions.
/// (core func $get' (canon lower (func $safe-api "get")))
/// (core func $set' (canon lower (func $safe-api "set")))
/// (core func $len' (canon lower (func $safe-api "len")))
///
/// ;; Instantiate our module, providing the lowered safe APIs as imports.
/// (core instance $instance
/// (instantiate $main-impl
/// (with "" (instance
/// (export "get" (func $get'))
/// (export "set" (func $set'))
/// (export "len" (func $len'))
/// ))
/// )
/// )
///
/// ;; Lift the implementation's `main` from a core function to a component function
/// ;; and export it!
/// (func (export "main")
/// (canon lift (core func $instance "main"))
/// )
/// )
/// "#.as_bytes(),
/// None,
/// )?;
///
/// // Finish the builder and compile the component.
/// let component = builder.compile_component()?;
///
/// // Create a new `Store<StoreData>`, wrapping a buffer of the given elements.
/// let mut store = Store::new(&engine, StoreData::new([0, 10, 20, 30, 40, 50]));
///
/// // Instantiate our component into the store.
/// let instance = linker.instantiate(&mut store, &component)?;
///
/// // Get the instance's exported `main` function and call it.
/// instance
/// .get_typed_func::<(), ()>(&mut store, "main")?
/// .call(&mut store, ())?;
///
/// // Our `StoreData`'s buffer had each element incremented directly from Wasm!
/// assert_eq!(store.data().buf(), &[1, 11, 21, 31, 41, 51]);
/// # Ok(())
/// # }
/// ```
pub unsafe fn expose_unsafe_intrinsics(&mut self, import_name: impl Into<String>) -> &mut Self {
self.unsafe_intrinsics_import = Some(import_name.into());
self
}
/// Explicitly specify DWARF `.dwp` path.
///
/// # Errors
///
/// This method will return an error if the `.dwp` file has already been set
/// through [`CodeBuilder::dwarf_package`] or auto-detection in
/// [`CodeBuilder::wasm_binary_file`].
///
/// This method will also return an error if `file` cannot be read.
pub fn dwarf_package_file(&mut self, file: &Path) -> Result<&mut Self> {
if self.dwarf_package.is_some() {
bail!("cannot call `dwarf_package` or `dwarf_package_file` twice");
}
let dwarf_package = std::fs::read(file)
.with_context(|| format!("failed to read dwarf input file: {}", file.display()))?;
self.dwarf_package_path = Some(Cow::Owned(file.to_owned()));
self.dwarf_package = Some(dwarf_package.into());
Ok(self)
}
fn dwarf_package_from_wasm_path(&mut self) -> Result<&mut Self> {
let dwarf_package_path_buf = self.wasm_path.as_ref().unwrap().with_extension("dwp");
if dwarf_package_path_buf.exists() {
return self.dwarf_package_file(dwarf_package_path_buf.as_path());
}
Ok(self)
}
/// Gets the DWARF package.
pub(super) fn get_dwarf_package(&self) -> Option<&[u8]> {
self.dwarf_package.as_deref()
}
/// Set the DWARF package binary.
///
/// Initializes `dwarf_package` from `dwp_bytes` in preparation for
/// DWARF fusion. Allows the DWARF package to be supplied as a byte array
/// when the file probing performed in `wasm_file` is not appropriate.
///
/// # Errors
///
/// Returns an error if the `*.dwp` file is already set via auto-probing in
/// [`CodeBuilder::wasm_binary_file`] or explicitly via
/// [`CodeBuilder::dwarf_package_file`].
pub fn dwarf_package(&mut self, dwp_bytes: &'a [u8]) -> Result<&mut Self> {
if self.dwarf_package.is_some() {
bail!("cannot call `dwarf_package` or `dwarf_package_file` twice");
}
self.dwarf_package = Some(dwp_bytes.into());
Ok(self)
}
/// Returns a hint, if possible, of what the provided bytes are.
///
/// This method can be use to detect what the previously supplied bytes to
/// methods such as [`CodeBuilder::wasm_binary_or_text`] are. This will
/// return whether a module or a component was found in the provided bytes.
///
/// This method will return `None` if wasm bytes have not been configured
/// or if the provided bytes don't look like either a component or a
/// module.
pub fn hint(&self) -> Option<CodeHint> {
let wasm = self.wasm.as_ref()?;
if wasmparser::Parser::is_component(wasm) {
Some(CodeHint::Component)
} else if wasmparser::Parser::is_core_wasm(wasm) {
Some(CodeHint::Module)
} else {
None
}
}
/// Finishes this compilation and produces a serialized list of bytes.
///
/// This method requires that either [`CodeBuilder::wasm_binary`] or
/// related methods were invoked prior to indicate what is being compiled.
///
/// This method will block the current thread until compilation has
/// finished, and when done the serialized artifact will be returned.
///
/// Note that this method will never cache compilations, even if the
/// `cache` feature is enabled.
///
/// # Errors
///
/// This can fail if the input wasm module was not valid or if another
/// compilation-related error is encountered.
pub fn compile_module_serialized(&self) -> Result<Vec<u8>> {
ensure!(
self.unsafe_intrinsics_import.is_none(),
"`CodeBuilder::expose_unsafe_intrinsics` can only be used with components"
);
#[cfg(feature = "compile-time-builtins")]
ensure!(
self.get_compile_time_builtins().is_empty(),
"compile-time builtins can only be used with components"
);
let wasm = self.get_wasm()?;
let dwarf_package = self.get_dwarf_package();
let (v, _) =
super::build_module_artifacts(self.engine, &wasm, dwarf_package.as_deref(), &())?;
Ok(v)
}
/// Same as [`CodeBuilder::compile_module_serialized`] except that it
/// compiles a serialized [`Component`](crate::component::Component)
/// instead of a module.
#[cfg(feature = "component-model")]
pub fn compile_component_serialized(&self) -> Result<Vec<u8>> {
let wasm = self.get_wasm()?;
let (v, _) = super::build_component_artifacts(
self.engine,
&wasm,
None,
self.get_unsafe_intrinsics_import(),
&(),
)?;
Ok(v)
}
pub(super) fn get_unsafe_intrinsics_import(&self) -> Option<&str> {
self.unsafe_intrinsics_import.as_deref()
}
}
/// This is a helper struct used when caching to hash the state of an `Engine`
/// used for module compilation.
///
/// The hash computed for this structure is used to key the global wasmtime
/// cache and dictates whether artifacts are reused. Consequently the contents
/// of this hash dictate when artifacts are or aren't re-used.
pub struct HashedEngineCompileEnv<'a>(pub &'a Engine);
impl std::hash::Hash for HashedEngineCompileEnv<'_> {
fn hash<H: std::hash::Hasher>(&self, hasher: &mut H) {
// Hash the compiler's state based on its target and configuration.
if let Some(compiler) = self.0.compiler() {
compiler.triple().hash(hasher);
compiler.flags().hash(hasher);
compiler.isa_flags().hash(hasher);
}
// Hash configuration state read for compilation
let config = self.0.config();
self.0.tunables().hash(hasher);
self.0.features().hash(hasher);
config.wmemcheck.hash(hasher);
// Catch accidental bugs of reusing across crate versions.
config.module_version.hash(hasher);
}
}
@@ -0,0 +1,313 @@
use super::*;
use crate::ToWasmtimeResult as _;
impl<'a> CodeBuilder<'a> {
pub(crate) fn get_compile_time_builtins(&self) -> &HashMap<Cow<'a, str>, Cow<'a, [u8]>> {
&self.compile_time_builtins
}
pub(super) fn compose_compile_time_builtins<'b>(
&self,
main_wasm: &'b [u8],
) -> Result<Cow<'b, [u8]>> {
if self.get_compile_time_builtins().is_empty() {
return Ok(main_wasm.into());
}
let imports = self.check_imports_for_compile_time_builtins(&main_wasm)?;
if imports.is_empty() {
drop(imports);
return Ok(main_wasm.into());
}
let tempdir = tempfile::TempDir::new().context("failed to create a temporary directory")?;
let deps = tempdir.path().join("_deps");
std::fs::create_dir(&deps)
.with_context(|| format!("failed to create directory: {}", deps.display()))?;
let main_wasm_path = tempdir.path().join("_main.wasm");
std::fs::write(&main_wasm_path, &main_wasm)
.with_context(|| format!("failed to write to file: {}", main_wasm_path.display()))?;
let mut config = wasm_compose::config::Config::default();
for (name, bytes) in self.get_compile_time_builtins() {
let name: &str = &*name;
if !imports.contains(&name) {
continue;
}
let mut path = deps.join(Path::new(name));
path.set_extension("wasm");
std::fs::write(&path, &bytes)
.with_context(|| format!("failed to write to file: {}", path.display()))?;
config
.dependencies
.insert(name.to_string(), wasm_compose::config::Dependency { path });
}
let composer = wasm_compose::composer::ComponentComposer::new(&main_wasm_path, &config);
let composed = composer.compose().to_wasmtime_result()?;
Ok(composed.into())
}
/// Check that the main Wasm doesn't import unsafe intrinsics, keeping the
/// TCB to just the compile-time builtins' implementation.
///
/// Returns the Wasm's top-level instance imports for `wasm-compose`
/// configuration.
fn check_imports_for_compile_time_builtins<'b>(
&self,
main_wasm: &'b [u8],
) -> Result<crate::hash_set::HashSet<&'b str>, Error> {
let intrinsics_import = self.unsafe_intrinsics_import.as_deref().ok_or_else(|| {
format_err!(
"must configure the unsafe-intrinsics import when using compile-time builtins"
)
})?;
let mut instance_imports = crate::hash_set::HashSet::new();
let parser = wasmparser::Parser::new(0);
let mut level = 0;
for payload in parser.parse_all(main_wasm) {
match payload? {
wasmparser::Payload::Version { .. } => {
level += 1;
}
wasmparser::Payload::End(_) => {
level -= 1;
}
wasmparser::Payload::ComponentImportSection(imports) if level == 1 => {
for imp in imports.into_iter() {
let imp = imp?;
// Ideally we would simply choose a new import name that
// doesn't conflict with the main Wasm's imports and
// plumb that through to the compile-time builtins
// regardless of the import name that they use, but
// unfortunately the `wasm-compose` API is not powerful
// enough for us to do all that.
ensure!(
imp.name.name != intrinsics_import,
"main Wasm cannot import the unsafe intrinsics (`{intrinsics_import}`) \
when using compile-time builtins"
);
if let wasmparser::ComponentTypeRef::Instance(_) = imp.ty {
instance_imports.insert(imp.name.name);
}
}
}
_ => {}
}
}
Ok(instance_imports)
}
/// Define a compile-time builtin component, via its Wasm bytes.
///
/// Compile-time builtins enable you to build safe, zero-copy, and (with
/// [inlining][crate::Config::compiler_inlining])
/// zero-function-call-overhead Wasm APIs for accessing host data, buffers,
/// and objects.
///
/// A compile-time builtin is a component that is
///
/// * authored by the host (Wasmtime embedder),
///
/// * whose implementation (though not necessarily its interface!) is
/// host-specific,
///
/// * has access to unsafe intrinsics (and is therefore part of the host's
/// [trusted compute base]), and
///
/// * is linked into guest Wasm programs at compile-time.
///
/// Any imports satisfied by a compile-time builtin during compilation will
/// not show up in the resulting component's
/// [imports][crate::component::types::Component::imports], and they can no
/// longer be customized by a [`Linker`][crate::component::Linker]
/// definition at instantiation time.[^0]
///
/// [^0]: If linking compile-time builtins into a component at compile-time
/// reminds you of [component composition], that is not a coincidence:
/// component composition is used under the covers as part of compile-time
/// builtins' implementation.
///
/// Comparing compile-time builtins with
/// [`Linker`][crate::component::Linker]s is informative:
///
/// * Both mechanisms define APIs to satisfy a Wasm program's imports.
///
/// * A `Linker` satisfies those imports at instantiation-time, while
/// compile-time builtins do it during compilation.
///
/// * APIs defined by a `Linker` are implemented in Rust, and hosts can
/// build safe, sandboxed Wasm APIs on top of raw, un-sandboxed primitives
/// via Rust's `unsafe`. APIs defined by compile-time builtins are
/// implemented as Wasm components, and hosts can build safe, sandboxed
/// Wasm APIs on top of raw, un-sandboxed primitives via [unsafe
/// intrinsics][CodeBuilder::expose_unsafe_intrinsics].
///
/// * Imports satisfied via `Linker`-defined APIs are implemented with
/// [PLT/GOT]-style function table lookups and indirect calls in the
/// Wasm's compiled native code. On the other hand, Wasmtime implements
/// calls to imports satisfied via compile-time builtins with direct calls
/// in the Wasm's compiled native code. Wasmtime's compiler can also
/// [inline][crate::Config::compiler_inlining] these direct calls,
/// removing function call overheads and enabling further, cascading
/// compiler optimizations.
///
/// If you are familiar with Wasm on the Web, you can think of compile-time
/// builtins as the rough equivalent of [the `js-string-builtins` proposal]
/// but for arbitrary host-defined APIs in a Wasmtime embedding environment
/// rather than JS string APIs in a Web browser environment.
///
/// [trusted compute base]: https://en.wikipedia.org/wiki/Trusted_computing_base
/// [the `js-string-builtins` proposal]: https://github.com/WebAssembly/js-string-builtins/blob/main/proposals/js-string-builtins/Overview.md
/// [component composition]: https://component-model.bytecodealliance.org/composing-and-distributing/composing.html
/// [PLT/GOT]: https://reverseengineering.stackexchange.com/a/1993
///
/// # Safety
///
/// Compile-time builtins are part of your [trusted compute base] and should
/// be authored by trusted, first-party developers with extreme care. You
/// should never use compile-time builtins authored by untrusted,
/// third-party developers.
///
/// Compile-time builtins are given access to Wasmtime's [unsafe
/// intrinsics][CodeBuilder::expose_unsafe_intrinsics], and the same safety
/// invariants and portability concerns apply. However, when compile-time
/// builtins are defined on a `CodeBuilder`, unsafe intrinsics are *only*
/// exposed to the compile-time builtins, and they are *not* exposed to the
/// main guest Wasm program. This means that — assuming your compile-time
/// builtins only exposing safe APIs, encapsulating the intrinsics'
/// unsafety, and modulo bugs in your implementation of those safe APIs —
/// that the main guest Wasm program is not part of your trusted compute
/// base.
///
/// # Example
///
/// See the example in [CodeBuilder::expose_unsafe_intrinsics].
pub unsafe fn compile_time_builtins_binary(
&mut self,
name: impl Into<Cow<'a, str>>,
wasm_bytes: impl Into<Cow<'a, [u8]>>,
) -> &mut Self {
self.compile_time_builtins
.insert(name.into(), wasm_bytes.into());
self
}
/// Equivalent of [`CodeBuilder::compile_time_builtins_binary`] that also
/// accepts the WebAssembly text format.
///
/// This method will configure the WebAssembly binary to be compiled and
/// used to satisfy the `name` instance import. The input `wasm_bytes` may
/// either be the wasm text format or the binary format. If the `wat` crate
/// feature is enabled, which is enabled by default, then the text format
/// will automatically be converted to the binary format.
///
/// # Errors
///
/// This method will also return an error if `wasm_bytes` is the wasm text
/// format and the text syntax is not valid.
///
/// # Safety
///
/// See [`CodeBuilder::compile_time_builtins_binary`].
///
/// # Example
///
/// See the example in [CodeBuilder::expose_unsafe_intrinsics], which uses
/// compile-time builtins.
#[allow(unused_variables, reason = "`wasm_path` only used with `wat` feature")]
pub unsafe fn compile_time_builtins_binary_or_text(
&mut self,
name: impl Into<Cow<'a, str>>,
wasm_bytes: impl Into<Cow<'a, [u8]>>,
wasm_path: Option<&Path>,
) -> Result<&mut Self> {
let wasm_bytes = wasm_bytes.into();
#[cfg(feature = "wat")]
if let Cow::Owned(wasm_bytes) = wat::parse_bytes(&wasm_bytes).map_err(|mut e| {
if let Some(path) = wasm_path {
e.set_path(path);
}
e
})? {
// SAFETY: Same as our unsafe contract.
return Ok(unsafe { self.compile_time_builtins_binary(name, wasm_bytes) });
}
// SAFETY: Same as our unsafe contract.
Ok(unsafe { self.compile_time_builtins_binary(name, wasm_bytes) })
}
/// Like [`CodeBuilder::compile_time_builtins_binary`], but reads the `file`
/// specified for the bytes that will define the compile-time builtin.
///
/// # Safety
///
/// See [`CodeBuilder::compile_time_builtins_binary`].
///
/// # Example
///
/// See the example in [CodeBuilder::expose_unsafe_intrinsics], which uses
/// compile-time builtins.
pub unsafe fn compile_time_builtins_binary_file(
&mut self,
name: impl Into<Cow<'a, str>>,
file: &Path,
) -> Result<&mut Self> {
let wasm_bytes = std::fs::read(file)
.with_context(|| format!("failed to read file: {}", file.display()))?;
// SAFETY: Same as our unsafe contract.
Ok(unsafe { self.compile_time_builtins_binary(name, wasm_bytes) })
}
/// Equivalent of [`CodeBuilder::compile_time_builtins_binary_file`] that
/// also accepts the WebAssembly text format.
///
/// This method is will read the file at the given path and interpret the
/// contents to determine if it's the Wasm text format or binary format. The
/// file extension is not consulted. The text format is automatically
/// converted to the binary format if the crate feature `wat` is active.
///
/// # Errors
///
/// In addition to the errors returned by
/// [`CodeBuilder::compile_time_builtins_binary_file`] this may also fail if
/// the text format is read and the syntax is invalid.
///
/// # Safety
///
/// See [`CodeBuilder::compile_time_builtins_binary`].
///
/// # Example
///
/// See the example in [CodeBuilder::expose_unsafe_intrinsics], which uses
/// compile-time builtins.
pub unsafe fn compile_time_builtins_binary_or_text_file(
&mut self,
name: impl Into<Cow<'a, str>>,
file: &Path,
) -> Result<&mut Self> {
#[cfg(feature = "wat")]
{
let wasm = wat::parse_file(file)
.with_context(|| format!("error parsing file: {}", file.display()))?;
// SAFETY: Same as our unsafe contract.
Ok(unsafe { self.compile_time_builtins_binary(name, wasm) })
}
#[cfg(not(feature = "wat"))]
{
// SAFETY: Same as our unsafe contract.
unsafe { self.compile_time_builtins_binary_file(name, file) }
}
}
}
+242
View File
@@ -0,0 +1,242 @@
#[cfg(feature = "component-model")]
use crate::component::Component;
use crate::prelude::*;
use crate::runtime::vm::MmapVec;
use crate::{CodeBuilder, CodeMemory, Engine, Module};
use object::write::WritableBuffer;
use std::sync::Arc;
use wasmtime_environ::{FinishedObject, ObjectBuilder};
impl<'a> CodeBuilder<'a> {
fn compile_cached<T, S>(
&self,
build_artifacts: fn(
&Engine,
&[u8],
Option<&[u8]>,
Option<&str>,
&S,
) -> Result<(MmapVecWrapper, Option<T>)>,
state: &S,
) -> Result<(Arc<CodeMemory>, Option<T>)> {
let wasm = self.get_wasm()?;
let dwarf_package = self.get_dwarf_package();
let unsafe_intrinsics_import = self.get_unsafe_intrinsics_import();
self.engine
.check_compatible_with_native_host()
.context("compilation settings are not compatible with the native host")?;
#[cfg(feature = "cache")]
{
let state = (
crate::compile::HashedEngineCompileEnv(self.engine),
&wasm,
&dwarf_package,
&unsafe_intrinsics_import,
// Don't hash this as it's just its own "pure" function pointer.
NotHashed(build_artifacts),
// Don't hash the FinishedObject state: this contains
// things like required runtime alignment, and does
// not impact the compilation result itself.
NotHashed(state),
);
let (code, info_and_types) =
wasmtime_cache::ModuleCacheEntry::new("wasmtime", self.engine.cache())
.get_data_raw(
&state,
// Cache miss, compute the actual artifacts
|(
engine,
wasm,
dwarf_package,
unsafe_intrinsics_import,
build_artifacts,
state,
)|
-> Result<_> {
let (mmap, info) = (build_artifacts.0)(
engine.0,
wasm,
dwarf_package.as_deref(),
**unsafe_intrinsics_import,
state.0,
)?;
let code = publish_mmap(engine.0, mmap.0)?;
Ok((code, info))
},
// Implementation of how to serialize artifacts
|(_engine, _wasm, _, _, _, _), (code, _info_and_types)| {
Some(code.mmap().to_vec())
},
// Cache hit, deserialize the provided artifacts
|(engine, wasm, _, _, _, _), serialized_bytes| {
let kind = if wasmparser::Parser::is_component(&wasm) {
wasmtime_environ::ObjectKind::Component
} else {
wasmtime_environ::ObjectKind::Module
};
let code = engine.0.load_code_bytes(&serialized_bytes, kind).ok()?;
Some((code, None))
},
)?;
return Ok((code, info_and_types));
struct NotHashed<T>(T);
impl<T> std::hash::Hash for NotHashed<T> {
fn hash<H: std::hash::Hasher>(&self, _hasher: &mut H) {}
}
}
#[cfg(not(feature = "cache"))]
{
let (mmap, info_and_types) = build_artifacts(
self.engine,
&wasm,
dwarf_package.as_deref(),
unsafe_intrinsics_import,
state,
)?;
let code = publish_mmap(self.engine, mmap.0)?;
return Ok((code, info_and_types));
}
}
/// Same as [`CodeBuilder::compile_module_serialized`] except that a
/// [`Module`](crate::Module) is produced instead.
///
/// Note that this method will cache compilations if the `cache` feature is
/// enabled and turned on in [`Config`](crate::Config).
pub fn compile_module(&self) -> Result<Module> {
ensure!(
self.get_unsafe_intrinsics_import().is_none(),
"`CodeBuilder::expose_unsafe_intrinsics` can only be used with components"
);
#[cfg(feature = "compile-time-builtins")]
ensure!(
self.get_compile_time_builtins().is_empty(),
"compile-time builtins can only be used with components"
);
let custom_alignment = self.custom_alignment();
let (code, info_and_types) = self.compile_cached(
|engine, wasm, dwarf, unsafe_intrinsics_import, state| {
assert!(unsafe_intrinsics_import.is_none());
super::build_module_artifacts(engine, wasm, dwarf, state)
},
&custom_alignment,
)?;
Module::from_parts(self.engine, code, info_and_types)
}
/// Same as [`CodeBuilder::compile_module`] except that it compiles a
/// [`Component`] instead of a module.
#[cfg(feature = "component-model")]
pub fn compile_component(&self) -> Result<Component> {
let custom_alignment = self.custom_alignment();
let (code, artifacts) = self.compile_cached(
|engine, wasm, dwarf, unsafe_intrinsics_import, state| {
super::build_component_artifacts(
engine,
wasm,
dwarf,
unsafe_intrinsics_import,
state,
)
},
&custom_alignment,
)?;
Component::from_parts(self.engine, code, artifacts)
}
fn custom_alignment(&self) -> CustomAlignment {
CustomAlignment {
alignment: self
.engine
.custom_code_memory()
.map(|c| c.required_alignment())
.unwrap_or(1),
}
}
}
fn publish_mmap(engine: &Engine, mmap: MmapVec) -> Result<Arc<CodeMemory>> {
let mut code = CodeMemory::new(engine, mmap)?;
code.publish()?;
Ok(Arc::new(code))
}
pub(crate) struct MmapVecWrapper(pub MmapVec);
/// Custom alignment requirements from the Engine for
/// produced-at-runtime-in-memory code artifacts.
pub(crate) struct CustomAlignment {
alignment: usize,
}
impl FinishedObject for MmapVecWrapper {
type State = CustomAlignment;
fn finish_object(obj: ObjectBuilder<'_>, align: &CustomAlignment) -> Result<Self> {
let mut result = ObjectMmap::default();
result.alignment = align.alignment;
return match obj.finish(&mut result) {
Ok(()) => {
assert!(result.mmap.is_some(), "no reserve");
let mmap = result.mmap.expect("reserve not called");
assert_eq!(mmap.len(), result.len);
Ok(MmapVecWrapper(mmap))
}
Err(e) => match result.err.take() {
Some(original) => Err(original.context(e)),
None => Err(e),
},
};
/// Helper struct to implement the `WritableBuffer` trait from the `object`
/// crate.
///
/// This enables writing an object directly into an mmap'd memory so it's
/// immediately usable for execution after compilation. This implementation
/// relies on a call to `reserve` happening once up front with all the needed
/// data, and the mmap internally does not attempt to grow afterwards.
#[derive(Default)]
struct ObjectMmap {
mmap: Option<MmapVec>,
len: usize,
alignment: usize,
err: Option<Error>,
}
impl WritableBuffer for ObjectMmap {
fn reserve(&mut self, additional: u64) -> Result<(), ()> {
assert!(self.mmap.is_none(), "cannot reserve twice");
self.mmap =
match MmapVec::with_capacity_and_alignment(additional as usize, self.alignment)
{
Ok(mmap) => Some(mmap),
Err(e) => {
self.err = Some(e);
return Err(());
}
};
Ok(())
}
fn write_zeros(&mut self, additional: u64) {
self.len += additional as usize;
}
fn write_bytes(&mut self, val: &[u8]) {
let mmap = self.mmap.as_mut().expect("write before reserve");
// SAFETY: the `mmap` has not be made readonly yet so it should
// be safe to mutate it.
unsafe {
mmap.as_mut_slice()[self.len..][..val.len()].copy_from_slice(val);
}
self.len += val.len();
}
}
}
}
+415
View File
@@ -0,0 +1,415 @@
//! Stratification of call graphs for parallel bottom-up inlining.
//!
//! This module takes a call graph and constructs a strata, which is essentially
//! a parallel execution plan. A strata consists of an ordered sequence of
//! layers, and a layer of an unordered set of functions. The `i`th layer must
//! be processed before the `i + 1`th layer, but functions within the same layer
//! may be processed in any order (and in parallel).
//!
//! For example, when given the following tree-like call graph:
//!
//! ```text
//! +---+ +---+ +---+
//! | a |-->| b |-->| c |
//! +---+ +---+ +---+
//! | |
//! | | +---+
//! | '---->| d |
//! | +---+
//! |
//! | +---+ +---+
//! '---->| e |-->| f |
//! +---+ +---+
//! |
//! | +---+
//! '---->| g |
//! +---+
//! ```
//!
//! then stratification will produce these layers:
//!
//! ```text
//! [
//! {c, d, f, g},
//! {b, e},
//! {a},
//! ]
//! ```
//!
//! Our goal in constructing the layers is to maximize potential parallelism at
//! each layer. Logically, we do this by finding the strongly-connected
//! components of the input call graph and peeling off all of the leaves of
//! SCCs' condensation (i.e. the DAG that the SCCs form; see the documentation
//! for the `StronglyConnectedComponents::evaporation` method for
//! details). These leaves become the strata's first layer. The layer's
//! components are removed from the condensation graph, and we repeat the
//! process, so that the condensation's new leaves become the strata's second
//! layer, and etc... until the condensation graph is empty and all components
//! have been processed. In practice we don't actually mutate the condensation
//! graph or remove its nodes but instead count how many unprocessed
//! dependencies each component has, and a component is ready for inclusion in a
//! layer once its unprocessed-dependencies count reaches zero.
use super::*;
use std::{fmt::Debug, ops::Range};
use wasmtime_environ::{
EntityRef, SecondaryMap,
graphs::{Graph, Scc, StronglyConnectedComponents},
};
/// A stratified call graph; essentially a parallel-execution plan for bottom-up
/// inlining.
///
/// See the module doc comment for more details.
pub struct Strata<Node> {
layers: Vec<Range<u32>>,
layer_elems: Vec<Node>,
}
impl<Node: Debug> Debug for Strata<Node> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("Strata")
.field(
"layers",
&core::fmt::from_fn(|f| {
let mut f = f.debug_list();
for layer in self.layers() {
f.entry(&layer);
}
f.finish()
}),
)
.finish()
}
}
impl<Node> Strata<Node> {
/// Stratify the given call graph, yielding a `Strata` parallel-execution
/// plan.
pub fn new<G>(call_graph: &G) -> Self
where
Node: EntityRef + Debug,
G: Debug + Graph<Node>,
{
log::trace!("Stratifying {call_graph:#?}");
let components = StronglyConnectedComponents::new(call_graph);
let evaporation = components.evaporation(call_graph);
// A map from each component to the count of how many call-graph
// dependencies to other components it has that have not been fulfilled
// yet. These counts are decremented as we assign a component's dependencies
// to layers.
let mut unfulfilled_deps_count = SecondaryMap::<Scc, u32>::with_capacity(components.len());
for to_component in components.keys() {
for from_component in evaporation.reverse_edges(to_component) {
unfulfilled_deps_count[*from_component] += 1;
}
}
// Build the strata.
//
// The first layer is formed by searching through all components for those
// that have a zero unfulfilled-deps count. When we finish a layer, we
// iterate over each of component in that layer and decrement the
// unfulfilled-deps count of every other component that depends on the
// newly-assigned-to-a-layer component. Any component that then reaches a
// zero unfulfilled-dep count is added to the next layer. This proceeds to a
// fixed point, similarly to GC tracing and ref-count decrementing.
let mut layers: Vec<Range<u32>> = vec![];
let (min, max) = call_graph.nodes().size_hint();
let cap = max.unwrap_or(min);
let mut layer_elems: Vec<Node> = Vec::with_capacity(cap);
let mut current_layer: Vec<Scc> = components
.keys()
.filter(|scc| unfulfilled_deps_count[*scc] == 0)
.collect();
debug_assert!(
!current_layer.is_empty() || call_graph.nodes().next().is_none(),
"the first layer can only be empty when the call graph itself is empty"
);
let mut next_layer = vec![];
while !current_layer.is_empty() {
debug_assert!(next_layer.is_empty());
for dependee in &current_layer {
for depender in evaporation.reverse_edges(*dependee) {
debug_assert!(unfulfilled_deps_count[*depender] > 0);
unfulfilled_deps_count[*depender] -= 1;
if unfulfilled_deps_count[*depender] == 0 {
next_layer.push(*depender);
}
}
}
layers.push(extend_with_range(
&mut layer_elems,
current_layer
.drain(..)
.flat_map(|scc| components.nodes(scc).iter().copied()),
));
std::mem::swap(&mut next_layer, &mut current_layer);
}
debug_assert!(
unfulfilled_deps_count.values().all(|c| *c == 0),
"after every component is assigned to a layer, all dependencies should be fulfilled"
);
let result = Strata {
layers,
layer_elems,
};
log::trace!(" -> {result:#?}");
result
}
/// Iterate over the layers of this `Strata`.
///
/// The `i`th layer must be processed before the `i + 1`th layer, but the
/// functions within a layer may be processed in any order and in parallel.
pub fn layers(&self) -> impl ExactSizeIterator<Item = &[Node]> {
self.layers.iter().map(|range| {
let start = usize::try_from(range.start).unwrap();
let end = usize::try_from(range.end).unwrap();
&self.layer_elems[start..end]
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use wasmtime_environ::graphs::Graph;
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
struct Function(u32);
wasmtime_environ::entity_impl!(Function);
#[derive(Debug)]
struct Functions {
calls: SecondaryMap<Function, Vec<Function>>,
}
impl Default for Functions {
fn default() -> Self {
let _ = env_logger::try_init();
Self {
calls: Default::default(),
}
}
}
impl Graph<Function> for Functions {
type NodesIter<'a>
= wasmtime_environ::Keys<Function>
where
Self: 'a;
fn nodes(&self) -> Self::NodesIter<'_> {
self.calls.keys()
}
type SuccessorsIter<'a>
= core::iter::Copied<core::slice::Iter<'a, Function>>
where
Self: 'a;
fn successors(&self, f: Function) -> Self::SuccessorsIter<'_> {
self.calls[f].iter().copied()
}
}
impl Functions {
fn define_func(&mut self, f: u32) -> &mut Self {
let f = Function::from_u32(f);
if self.calls.get(f).is_none() {
self.calls[f] = vec![];
}
self
}
fn define_call(&mut self, caller: u32, callee: u32) -> &mut Self {
self.define_func(caller);
self.define_func(callee);
let caller = Function::from_u32(caller);
let callee = Function::from_u32(callee);
self.calls[caller].push(callee);
self
}
fn define_calls(
&mut self,
caller: u32,
callees: impl IntoIterator<Item = u32>,
) -> &mut Self {
for callee in callees {
self.define_call(caller, callee);
}
self
}
fn stratify(&self) -> Strata<Function> {
Strata::new(self)
}
fn assert_stratification(&self, mut expected: Vec<Vec<u32>>) {
for layer in &mut expected {
layer.sort();
}
log::trace!("expected stratification = {expected:?}");
let actual = self
.stratify()
.layers()
.map(|layer| {
let mut layer = layer.iter().map(|f| f.as_u32()).collect::<Vec<_>>();
layer.sort();
layer
})
.collect::<Vec<_>>();
log::trace!("actual stratification = {actual:?}");
assert_eq!(expected.len(), actual.iter().len());
for (expected, actual) in expected.into_iter().zip(actual) {
log::trace!("expected layer = {expected:?}");
log::trace!(" actual layer = {expected:?}");
assert_eq!(expected.len(), actual.len());
for (expected, actual) in expected.into_iter().zip(actual) {
assert_eq!(expected, actual);
}
}
}
}
#[test]
fn test_disconnected_functions() {
// +---+ +---+ +---+
// | 0 | | 1 | | 2 |
// +---+ +---+ +---+
Functions::default()
.define_func(0)
.define_func(1)
.define_func(2)
.assert_stratification(vec![vec![0, 1, 2]]);
}
#[test]
fn test_chained_functions() {
// +---+ +---+ +---+
// | 0 |-->| 1 |-->| 2 |
// +---+ +---+ +---+
Functions::default()
.define_call(0, 1)
.define_call(1, 2)
.assert_stratification(vec![vec![2], vec![1], vec![0]]);
}
#[test]
fn test_cycle() {
// ,---------------.
// V |
// +---+ +---+ +---+
// | 0 |-->| 1 |-->| 2 |
// +---+ +---+ +---+
Functions::default()
.define_call(0, 1)
.define_call(1, 2)
.define_call(2, 0)
.assert_stratification(vec![vec![0, 1, 2]]);
}
#[test]
fn test_tree() {
// +---+ +---+ +---+
// | 0 |-->| 1 |-->| 2 |
// +---+ +---+ +---+
// | |
// | | +---+
// | '---->| 3 |
// | +---+
// |
// | +---+ +---+
// '---->| 4 |-->| 5 |
// +---+ +---+
// |
// | +---+
// '---->| 6 |
// +---+
Functions::default()
.define_calls(0, [1, 4])
.define_calls(1, [2, 3])
.define_calls(4, [5, 6])
.assert_stratification(vec![vec![2, 3, 5, 6], vec![1, 4], vec![0]]);
}
#[test]
fn test_chain_of_cycles() {
// ,-----.
// | |
// V |
// +---+ |
// | 0 |---'
// +---+
// |
// V
// +---+ +---+
// | 1 |<-->| 2 |
// +---+ +---+
// |
// | ,----------------.
// | | |
// V | V
// +---+ +---+ +---+
// | 3 |<---| 4 |<---| 5 |
// +---+ +---+ +---+
Functions::default()
.define_calls(0, [0, 1])
.define_calls(1, [2, 3])
.define_calls(2, [1])
.define_calls(3, [5])
.define_calls(4, [3])
.define_calls(5, [4])
.assert_stratification(vec![vec![3, 4, 5], vec![1, 2], vec![0]]);
}
#[test]
fn test_multiple_edges_to_same_component() {
// +---+ +---+
// | 0 | | 1 |
// +---+ +---+
// ^ ^
// | |
// V V
// +---+ +---+
// | 2 | | 3 |
// +---+ +---+
// | |
// `------. ,------'
// | |
// V V
// +---+
// | 4 |
// +---+
// ^
// |
// V
// +---+
// | 5 |
// +---+
Functions::default()
.define_calls(0, [2])
.define_calls(1, [3])
.define_calls(2, [0, 4])
.define_calls(3, [1, 4])
.define_calls(4, [5])
.define_calls(5, [4])
.assert_stratification(vec![vec![4, 5], vec![0, 1, 2, 3]]);
}
}
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+884
View File
@@ -0,0 +1,884 @@
//! This module implements serialization and deserialization of `Engine`
//! configuration data which is embedded into compiled artifacts of Wasmtime.
//!
//! The data serialized here is used to double-check that when a module is
//! loaded from one host onto another that it's compatible with the target host.
//! Additionally though this data is the first data read from a precompiled
//! artifact so it's "extra hardened" to provide reasonable-ish error messages
//! for mismatching wasmtime versions. Once something successfully deserializes
//! here it's assumed it's meant for this wasmtime so error messages are in
//! general much worse afterwards.
//!
//! Wasmtime AOT artifacts are ELF files so the data for the engine here is
//! stored into a section of the output file. The structure of this section is:
//!
//! 1. A version byte, currently `VERSION`.
//! 2. A byte indicating how long the next field is.
//! 3. A version string of the length of the previous byte value.
//! 4. A `postcard`-encoded `Metadata` structure.
//!
//! This is hoped to help distinguish easily Wasmtime-based ELF files from
//! other random ELF files, as well as provide better error messages for
//! using wasmtime artifacts across versions.
use crate::prelude::*;
use crate::{Engine, ModuleVersionStrategy, Precompiled};
use core::fmt;
use core::str::FromStr;
use object::endian::Endianness;
#[cfg(any(feature = "cranelift", feature = "winch"))]
use object::write::{Object, StandardSegment};
use object::{
FileFlags, Object as _,
elf::FileHeader64,
read::elf::{ElfFile64, FileHeader, SectionHeader},
};
use serde_derive::{Deserialize, Serialize};
use wasmtime_environ::{FlagValue, ObjectKind, OperatorCostStrategy, Tunables, obj};
const VERSION: u8 = 0;
/// Verifies that the serialized engine in `mmap` is compatible with the
/// `engine` provided.
///
/// This function will verify that the `mmap` provided can be deserialized
/// successfully and that the contents are all compatible with the `engine`
/// provided here, notably compatible wasm features are enabled, compatible
/// compiler options, etc. If a mismatch is found and the compilation metadata
/// specified is incompatible then an error is returned.
pub fn check_compatible(engine: &Engine, mmap: &[u8], expected: ObjectKind) -> Result<()> {
// Parse the input `mmap` as an ELF file and see if the header matches the
// Wasmtime-generated header. This includes a Wasmtime-specific `os_abi` and
// the `e_flags` field should indicate whether `expected` matches or not.
//
// Note that errors generated here could mean that a precompiled module was
// loaded as a component, or vice versa, both of which aren't supposed to
// work.
//
// Ideally we'd only `File::parse` once and avoid the linear
// `section_by_name` search here but the general serialization code isn't
// structured well enough to make this easy and additionally it's not really
// a perf issue right now so doing that is left for another day's
// refactoring.
let header = FileHeader64::<Endianness>::parse(mmap)
.map_err(obj::ObjectCrateErrorWrapper)
.context("failed to parse precompiled artifact as an ELF")?;
let endian = header
.endian()
.context("failed to parse header endianness")?;
let expected_e_flags = match expected {
ObjectKind::Module => obj::EF_WASMTIME_MODULE,
ObjectKind::Component => obj::EF_WASMTIME_COMPONENT,
};
ensure!(
header.e_flags(endian).contains(expected_e_flags),
"incompatible object file format"
);
let section_headers = header
.section_headers(endian, mmap)
.context("failed to parse section headers")?;
let strings = header
.section_strings(endian, mmap, section_headers)
.context("failed to parse strings table")?;
let sections = header
.sections(endian, mmap)
.context("failed to parse sections table")?;
let mut section_header = None;
for s in sections.iter() {
let name = s.name(endian, strings)?;
if name == obj::ELF_WASM_ENGINE.as_bytes() {
section_header = Some(s);
}
}
let Some(section_header) = section_header else {
bail!("failed to find section `{}`", obj::ELF_WASM_ENGINE)
};
let data = section_header
.data(endian, mmap)
.map_err(obj::ObjectCrateErrorWrapper)?;
let (first, data) = data
.split_first()
.ok_or_else(|| format_err!("invalid engine section"))?;
if *first != VERSION {
bail!("mismatched version in engine section");
}
let (len, data) = data
.split_first()
.ok_or_else(|| format_err!("invalid engine section"))?;
let len = usize::from(*len);
let (version, data) = if data.len() < len + 1 {
bail!("engine section too small")
} else {
data.split_at(len)
};
match &engine.config().module_version {
ModuleVersionStrategy::None => { /* ignore the version info, accept all */ }
_ => {
let version = core::str::from_utf8(&version)?;
if version != engine.config().module_version.as_str() {
bail!("Module was compiled with incompatible version '{version}'");
}
}
}
postcard::from_bytes::<Metadata<'_>>(data)?.check_compatible(engine)
}
#[cfg(any(feature = "cranelift", feature = "winch"))]
pub fn append_compiler_info(engine: &Engine, obj: &mut Object<'_>, metadata: &Metadata<'_>) {
let section = obj.add_section(
obj.segment_name(StandardSegment::Data).to_vec(),
obj::ELF_WASM_ENGINE.as_bytes().to_vec(),
object::SectionKind::ReadOnlyData,
);
let mut data = Vec::new();
data.push(VERSION);
let version = engine.config().module_version.as_str();
// This precondition is checked in Config::module_version:
assert!(
version.len() < 256,
"package version must be less than 256 bytes"
);
data.push(version.len() as u8);
data.extend_from_slice(version.as_bytes());
data.extend(postcard::to_allocvec(metadata).unwrap());
obj.set_section_data(section, data, 1);
}
fn detect_precompiled<'data, R: object::ReadRef<'data>>(
obj: ElfFile64<'data, Endianness, R>,
) -> Option<Precompiled> {
match obj.flags() {
FileFlags::Elf {
os_abi: obj::ELFOSABI_WASMTIME,
abi_version: 0,
e_flags,
} if e_flags.contains(obj::EF_WASMTIME_MODULE) => Some(Precompiled::Module),
FileFlags::Elf {
os_abi: obj::ELFOSABI_WASMTIME,
abi_version: 0,
e_flags,
} if e_flags.contains(obj::EF_WASMTIME_COMPONENT) => Some(Precompiled::Component),
_ => None,
}
}
pub fn detect_precompiled_bytes(bytes: &[u8]) -> Option<Precompiled> {
detect_precompiled(ElfFile64::parse(bytes).ok()?)
}
#[cfg(feature = "std")]
pub fn detect_precompiled_file(path: impl AsRef<std::path::Path>) -> Result<Option<Precompiled>> {
let read_cache = object::ReadCache::new(std::fs::File::open(path)?);
let obj = ElfFile64::parse(&read_cache)?;
Ok(detect_precompiled(obj))
}
#[derive(Serialize, Deserialize)]
pub struct Metadata<'a> {
target: TryString,
#[serde(borrow)]
shared_flags: TryVec<(&'a str, FlagValue<'a>)>,
#[serde(borrow)]
isa_flags: TryVec<(&'a str, FlagValue<'a>)>,
tunables: Tunables,
features: u64,
}
impl Metadata<'_> {
#[cfg(any(feature = "cranelift", feature = "winch"))]
pub fn new(engine: &Engine) -> Result<Metadata<'static>> {
let compiler = engine.try_compiler()?;
Ok(Metadata {
target: compiler.triple().to_string().into(),
shared_flags: compiler.flags().into(),
isa_flags: compiler.isa_flags().into(),
tunables: engine.tunables().clone(),
features: engine.features().bits(),
})
}
fn check_compatible(mut self, engine: &Engine) -> Result<()> {
self.check_triple(engine)?;
self.check_shared_flags(engine)?;
self.check_isa_flags(engine)?;
self.check_tunables(&engine.tunables())?;
self.check_features(&engine.features())?;
Ok(())
}
fn check_triple(&self, engine: &Engine) -> Result<()> {
let engine_target = engine.target();
let module_target =
target_lexicon::Triple::from_str(&self.target).map_err(|e| format_err!(e))?;
if module_target.architecture != engine_target.architecture {
bail!(
"Module was compiled for architecture '{}'",
module_target.architecture
);
}
if module_target.operating_system != engine_target.operating_system {
bail!(
"Module was compiled for operating system '{}'",
module_target.operating_system
);
}
Ok(())
}
fn check_shared_flags(&mut self, engine: &Engine) -> Result<()> {
for (name, val) in self.shared_flags.iter() {
engine
.check_compatible_with_shared_flag(name, val)
.map_err(|s| crate::Error::msg(s))
.context("compilation settings of module incompatible with native host")?;
}
Ok(())
}
fn check_isa_flags(&mut self, engine: &Engine) -> Result<()> {
for (name, val) in self.isa_flags.iter() {
engine
.check_compatible_with_isa_flag(name, val)
.map_err(|s| crate::Error::msg(s))
.context("compilation settings of module incompatible with native host")?;
}
Ok(())
}
fn check_int<T: Eq + fmt::Display>(found: T, expected: T, feature: &str) -> Result<()> {
if found == expected {
return Ok(());
}
bail!(
"Module was compiled with a {feature} of '{found}' but '{expected}' is expected for the host"
);
}
fn check_bool(found: bool, expected: bool, feature: impl fmt::Display) -> Result<()> {
if found == expected {
return Ok(());
}
bail!(
"Module was compiled {} {} but it {} enabled for the host",
if found { "with" } else { "without" },
feature,
if expected { "is" } else { "is not" }
);
}
fn check_cost(
consume_fuel: bool,
found: &OperatorCostStrategy,
expected: &OperatorCostStrategy,
) -> Result<()> {
if !consume_fuel {
return Ok(());
}
if found != expected {
bail!("Module costs are incompatible");
}
Ok(())
}
fn check_tunables(&mut self, other: &Tunables) -> Result<()> {
let Tunables {
collector,
memory_reservation,
memory_guard_size,
debug_native,
debug_guest,
debug_symbols,
parse_wasm_debuginfo,
consume_fuel,
ref operator_cost,
epoch_interruption,
memory_may_move,
guard_before_linear_memory,
table_lazy_init,
relaxed_simd_deterministic,
winch_callable,
signals_based_traps,
memory_init_cow,
inlining,
inlining_small_callee_size,
inlining_sum_size_threshold,
concurrency_support,
recording,
// This doesn't affect compilation, it's just a runtime setting.
memory_reservation_for_growth: _,
// This does technically affect compilation but modules with/without
// trap information can be loaded into engines with the opposite
// setting just fine (it's just a section in the compiled file and
// whether it's present or not)
generate_address_map: _,
// Just a debugging aid, doesn't affect functionality at all.
debug_adapter_modules: _,
// This is a runtime GC debugging setting, doesn't affect compilation.
gc_zeal_alloc_counter: _,
gc_heap_reservation,
gc_heap_guard_size,
gc_heap_may_move,
gc_heap_initial_size,
// This doesn't affect compilation, it's just a runtime setting.
gc_heap_reservation_for_growth: _,
// No need to match whether or not this metadata is emitted, if it
// is or isn't then that's fine, the runtime handles it the same
// way.
metadata_for_internal_asserts: _,
metadata_for_gc_heap_corruption: _,
// Only affects cold-block layout; a compiled artifact loads into an
// engine configured either way.
branch_hinting: _,
} = self.tunables;
Self::check_collector(collector, other.collector)?;
Self::check_int(
memory_reservation,
other.memory_reservation,
"memory reservation",
)?;
Self::check_int(
memory_guard_size,
other.memory_guard_size,
"memory guard size",
)?;
Self::check_bool(
debug_native,
other.debug_native,
"native debug information support",
)?;
Self::check_bool(debug_guest, other.debug_guest, "guest debug")?;
Self::check_bool(debug_symbols, other.debug_symbols, "debug symbols")?;
Self::check_bool(
parse_wasm_debuginfo,
other.parse_wasm_debuginfo,
"WebAssembly backtrace support",
)?;
Self::check_bool(consume_fuel, other.consume_fuel, "fuel support")?;
Self::check_cost(consume_fuel, operator_cost, &other.operator_cost)?;
Self::check_bool(
epoch_interruption,
other.epoch_interruption,
"epoch interruption",
)?;
Self::check_bool(memory_may_move, other.memory_may_move, "memory may move")?;
Self::check_bool(
guard_before_linear_memory,
other.guard_before_linear_memory,
"guard before linear memory",
)?;
Self::check_bool(table_lazy_init, other.table_lazy_init, "table lazy init")?;
Self::check_bool(
relaxed_simd_deterministic,
other.relaxed_simd_deterministic,
"relaxed simd deterministic semantics",
)?;
Self::check_bool(
winch_callable,
other.winch_callable,
"Winch calling convention",
)?;
Self::check_bool(
signals_based_traps,
other.signals_based_traps,
"Signals-based traps",
)?;
Self::check_bool(
memory_init_cow,
other.memory_init_cow,
"memory initialization with CoW",
)?;
Self::check_int(
inlining_small_callee_size,
other.inlining_small_callee_size,
"function inlining small-callee size",
)?;
Self::check_int(
inlining_sum_size_threshold,
other.inlining_sum_size_threshold,
"function inlining sum-size threshold",
)?;
Self::check_bool(
concurrency_support,
other.concurrency_support,
"concurrency support",
)?;
Self::check_bool(recording, other.recording, "RR recording support")?;
Self::check_inlining(inlining, other.inlining)?;
Self::check_int(
gc_heap_reservation,
other.gc_heap_reservation,
"GC heap reservation",
)?;
Self::check_int(
gc_heap_guard_size,
other.gc_heap_guard_size,
"GC heap guard size",
)?;
Self::check_int(
gc_heap_initial_size,
other.gc_heap_initial_size,
"GC heap initial size",
)?;
Self::check_bool(gc_heap_may_move, other.gc_heap_may_move, "GC heap may move")?;
Ok(())
}
fn check_features(&mut self, other: &wasmparser::WasmFeatures) -> Result<()> {
let module_features = wasmparser::WasmFeatures::from_bits_truncate(self.features);
let missing_features = (*other & module_features) ^ module_features;
for (name, _) in missing_features.iter_names() {
let name = name.to_ascii_lowercase();
bail!(
"Module was compiled with support for WebAssembly feature \
`{name}` but it is not enabled for the host",
);
}
Ok(())
}
fn check_collector(
module: Option<wasmtime_environ::Collector>,
host: Option<wasmtime_environ::Collector>,
) -> Result<()> {
match (module, host) {
// If the module doesn't require GC support it doesn't matter
// whether the host has GC support enabled or not.
(None, _) => Ok(()),
(Some(module), Some(host)) if module == host => Ok(()),
(Some(_), None) => {
bail!("module was compiled with GC however GC is disabled in the host")
}
(Some(module), Some(host)) => {
bail!(
"module was compiled for the {module} collector but \
the host is configured to use the {host} collector",
)
}
}
}
fn check_inlining(
module: wasmtime_environ::Inlining,
host: wasmtime_environ::Inlining,
) -> Result<()> {
if module == host {
return Ok(());
}
let desc = |cfg| match cfg {
wasmtime_environ::Inlining::No => "without intra-module inlining",
wasmtime_environ::Inlining::Yes => "with intra-module inlining",
wasmtime_environ::Inlining::InterModuleAndIntraGc => {
"with intra-module inlining only when using GC"
}
wasmtime_environ::Inlining::Intrinsics => "with intrinsic inlining",
wasmtime_environ::Inlining::InterModule => "with inter-module inlining",
};
let module = desc(module);
let host = desc(host);
bail!("module was compiled {module} however the host is configured {host}")
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::{Cache, Config, Module, OptLevel};
use std::{
collections::hash_map::DefaultHasher,
hash::{Hash, Hasher},
};
use tempfile::TempDir;
#[test]
fn test_architecture_mismatch() -> Result<()> {
let engine = Engine::default();
let mut metadata = Metadata::new(&engine)?;
metadata.target = "unknown-generic-linux".to_string().into();
match metadata.check_compatible(&engine) {
Ok(_) => unreachable!(),
Err(e) => assert_eq!(
e.to_string(),
"Module was compiled for architecture 'unknown'",
),
}
Ok(())
}
// Note that this test runs on a platform that is known to use Cranelift
#[test]
#[cfg(all(target_arch = "x86_64", not(miri)))]
fn test_os_mismatch() -> Result<()> {
let engine = Engine::default();
let mut metadata = Metadata::new(&engine)?;
metadata.target = format!(
"{}-generic-unknown",
target_lexicon::Triple::host().architecture
)
.into();
match metadata.check_compatible(&engine) {
Ok(_) => unreachable!(),
Err(e) => assert_eq!(
e.to_string(),
"Module was compiled for operating system 'unknown'",
),
}
Ok(())
}
fn assert_contains(error: &Error, msg: &str) {
let msg = msg.trim();
if error.chain().any(|e| e.to_string().contains(msg)) {
return;
}
panic!("failed to find:\n\n'''{msg}\n'''\n\nwithin error message:\n\n'''{error:?}'''")
}
#[test]
fn test_cranelift_flags_mismatch() -> Result<()> {
let engine = Engine::default();
let mut metadata = Metadata::new(&engine)?;
metadata
.shared_flags
.push(("preserve_frame_pointers", FlagValue::Bool(false)))?;
match metadata.check_compatible(&engine) {
Ok(_) => unreachable!(),
Err(e) => {
assert_contains(
&e,
"compilation settings of module incompatible with native host",
);
assert_contains(
&e,
"setting \"preserve_frame_pointers\" is configured to Bool(false) which is not supported",
);
}
}
Ok(())
}
#[test]
fn test_isa_flags_mismatch() -> Result<()> {
let engine = Engine::default();
let mut metadata = Metadata::new(&engine)?;
metadata
.isa_flags
.push(("not_a_flag", FlagValue::Bool(true)))?;
match metadata.check_compatible(&engine) {
Ok(_) => unreachable!(),
Err(e) => {
assert_contains(
&e,
"compilation settings of module incompatible with native host",
);
assert_contains(
&e,
"don't know how to test for target-specific flag \"not_a_flag\" at runtime",
);
}
}
Ok(())
}
#[test]
#[cfg_attr(any(miri, not(has_native_signals)), ignore)]
#[cfg(target_pointer_width = "64")] // different defaults on 32-bit platforms
fn test_tunables_int_mismatch() -> Result<()> {
let engine = Engine::default();
let mut metadata = Metadata::new(&engine)?;
metadata.tunables.memory_guard_size = 0;
match metadata.check_compatible(&engine) {
Ok(_) => unreachable!(),
Err(e) => assert_eq!(
e.to_string(),
"Module was compiled with a memory guard size of '0' but '33554432' is expected for the host"
),
}
Ok(())
}
#[test]
fn test_tunables_bool_mismatch() -> Result<()> {
let mut config = Config::new();
config.epoch_interruption(true);
let engine = Engine::new(&config)?;
let mut metadata = Metadata::new(&engine)?;
metadata.tunables.epoch_interruption = false;
match metadata.check_compatible(&engine) {
Ok(_) => unreachable!(),
Err(e) => assert_eq!(
e.to_string(),
"Module was compiled without epoch interruption but it is enabled for the host"
),
}
let mut config = Config::new();
config.epoch_interruption(false);
let engine = Engine::new(&config)?;
let mut metadata = Metadata::new(&engine)?;
metadata.tunables.epoch_interruption = true;
match metadata.check_compatible(&engine) {
Ok(_) => unreachable!(),
Err(e) => assert_eq!(
e.to_string(),
"Module was compiled with epoch interruption but it is not enabled for the host"
),
}
Ok(())
}
/// This test is only run a platform that is known to implement threads
#[test]
#[cfg(all(target_arch = "x86_64", not(miri)))]
fn test_feature_mismatch() -> Result<()> {
let mut config = Config::new();
config.wasm_threads(true);
let engine = Engine::new(&config)?;
let mut metadata = Metadata::new(&engine)?;
metadata.features &= !wasmparser::WasmFeatures::THREADS.bits();
// If a feature is disabled in the module and enabled in the host,
// that's always ok.
metadata.check_compatible(&engine)?;
let mut config = Config::new();
config.wasm_threads(false);
let engine = Engine::new(&config)?;
let mut metadata = Metadata::new(&engine)?;
metadata.features |= wasmparser::WasmFeatures::THREADS.bits();
match metadata.check_compatible(&engine) {
Ok(_) => unreachable!(),
Err(e) => assert_eq!(
e.to_string(),
"Module was compiled with support for WebAssembly feature \
`threads` but it is not enabled for the host"
),
}
Ok(())
}
#[test]
fn engine_weak_upgrades() {
let engine = Engine::default();
let weak = engine.weak();
weak.upgrade()
.expect("engine is still alive, so weak reference can upgrade");
drop(engine);
assert!(
weak.upgrade().is_none(),
"engine was dropped, so weak reference cannot upgrade"
);
}
#[test]
#[cfg_attr(miri, ignore)]
fn cache_accounts_for_opt_level() -> Result<()> {
let _ = env_logger::try_init();
let td = TempDir::new()?;
let config_path = td.path().join("config.toml");
std::fs::write(
&config_path,
&format!(
"
[cache]
directory = '{}'
",
td.path().join("cache").display()
),
)?;
let mut cfg = Config::new();
cfg.cranelift_opt_level(OptLevel::None)
.cache(Some(Cache::from_file(Some(&config_path))?));
let engine = Engine::new(&cfg)?;
Module::new(&engine, "(module (func))")?;
let cache_config = engine
.config()
.cache
.as_ref()
.expect("Missing cache config");
assert_eq!(cache_config.cache_hits(), 0);
assert_eq!(cache_config.cache_misses(), 1);
Module::new(&engine, "(module (func))")?;
assert_eq!(cache_config.cache_hits(), 1);
assert_eq!(cache_config.cache_misses(), 1);
let mut cfg = Config::new();
cfg.cranelift_opt_level(OptLevel::Speed)
.cache(Some(Cache::from_file(Some(&config_path))?));
let engine = Engine::new(&cfg)?;
let cache_config = engine
.config()
.cache
.as_ref()
.expect("Missing cache config");
Module::new(&engine, "(module (func))")?;
assert_eq!(cache_config.cache_hits(), 0);
assert_eq!(cache_config.cache_misses(), 1);
Module::new(&engine, "(module (func))")?;
assert_eq!(cache_config.cache_hits(), 1);
assert_eq!(cache_config.cache_misses(), 1);
let mut cfg = Config::new();
cfg.cranelift_opt_level(OptLevel::SpeedAndSize)
.cache(Some(Cache::from_file(Some(&config_path))?));
let engine = Engine::new(&cfg)?;
let cache_config = engine
.config()
.cache
.as_ref()
.expect("Missing cache config");
Module::new(&engine, "(module (func))")?;
assert_eq!(cache_config.cache_hits(), 0);
assert_eq!(cache_config.cache_misses(), 1);
Module::new(&engine, "(module (func))")?;
assert_eq!(cache_config.cache_hits(), 1);
assert_eq!(cache_config.cache_misses(), 1);
let mut cfg = Config::new();
cfg.debug_info(true)
.cache(Some(Cache::from_file(Some(&config_path))?));
let engine = Engine::new(&cfg)?;
let cache_config = engine
.config()
.cache
.as_ref()
.expect("Missing cache config");
Module::new(&engine, "(module (func))")?;
assert_eq!(cache_config.cache_hits(), 0);
assert_eq!(cache_config.cache_misses(), 1);
Module::new(&engine, "(module (func))")?;
assert_eq!(cache_config.cache_hits(), 1);
assert_eq!(cache_config.cache_misses(), 1);
Ok(())
}
#[test]
fn precompile_compatibility_key_accounts_for_opt_level() {
fn hash_for_config(cfg: &Config) -> u64 {
let engine = Engine::new(cfg).expect("Config should be valid");
let mut hasher = DefaultHasher::new();
engine.precompile_compatibility_hash().hash(&mut hasher);
hasher.finish()
}
let mut cfg = Config::new();
cfg.cranelift_opt_level(OptLevel::None);
let opt_none_hash = hash_for_config(&cfg);
cfg.cranelift_opt_level(OptLevel::Speed);
let opt_speed_hash = hash_for_config(&cfg);
assert_ne!(opt_none_hash, opt_speed_hash)
}
#[test]
fn precompile_compatibility_key_accounts_for_module_version_strategy() -> Result<()> {
fn hash_for_config(cfg: &Config) -> u64 {
let engine = Engine::new(cfg).expect("Config should be valid");
let mut hasher = DefaultHasher::new();
engine.precompile_compatibility_hash().hash(&mut hasher);
hasher.finish()
}
let mut cfg_custom_version = Config::new();
cfg_custom_version.module_version(ModuleVersionStrategy::Custom("1.0.1111".to_string()))?;
let custom_version_hash = hash_for_config(&cfg_custom_version);
let mut cfg_default_version = Config::new();
cfg_default_version.module_version(ModuleVersionStrategy::WasmtimeVersion)?;
let default_version_hash = hash_for_config(&cfg_default_version);
let mut cfg_none_version = Config::new();
cfg_none_version.module_version(ModuleVersionStrategy::None)?;
let none_version_hash = hash_for_config(&cfg_none_version);
assert_ne!(custom_version_hash, default_version_hash);
assert_ne!(custom_version_hash, none_version_hash);
assert_ne!(default_version_hash, none_version_hash);
Ok(())
}
#[test]
#[cfg_attr(miri, ignore)]
#[cfg(feature = "component-model")]
fn components_are_cached() -> Result<()> {
use crate::component::Component;
let td = TempDir::new()?;
let config_path = td.path().join("config.toml");
std::fs::write(
&config_path,
&format!(
"
[cache]
directory = '{}'
",
td.path().join("cache").display()
),
)?;
let mut cfg = Config::new();
cfg.cache(Some(Cache::from_file(Some(&config_path))?));
let engine = Engine::new(&cfg)?;
let cache_config = engine
.config()
.cache
.as_ref()
.expect("Missing cache config");
Component::new(&engine, "(component (core module (func)))")?;
assert_eq!(cache_config.cache_hits(), 0);
assert_eq!(cache_config.cache_misses(), 1);
Component::new(&engine, "(component (core module (func)))")?;
assert_eq!(cache_config.cache_hits(), 1);
assert_eq!(cache_config.cache_misses(), 1);
Ok(())
}
}
+555
View File
@@ -0,0 +1,555 @@
//! # Wasmtime's embedding API
//!
//! Wasmtime is a WebAssembly engine for JIT-compiled or ahead-of-time compiled
//! WebAssembly modules and components. More information about the Wasmtime
//! project as a whole can be found [in the documentation
//! book](https://docs.wasmtime.dev) whereas this documentation mostly focuses
//! on the API reference of the `wasmtime` crate itself.
//!
//! This crate contains an API used to interact with [WebAssembly modules] or
//! [WebAssembly components]. For example you can compile WebAssembly, create
//! instances, call functions, etc. As an embedder of WebAssembly you can also
//! provide guests functionality from the host by creating host-defined
//! functions, memories, globals, etc, which can do things that WebAssembly
//! cannot (such as print to the screen).
//!
//! [WebAssembly modules]: https://webassembly.github.io/spec
//! [WebAssembly components]: https://component-model.bytecodealliance.org
//!
//! The `wasmtime` crate is designed to be safe, efficient, and ergonomic.
//! This enables executing WebAssembly without the embedder needing to use
//! `unsafe` code, meaning that you're guaranteed there is no undefined behavior
//! or segfaults in either the WebAssembly guest or the host itself.
//!
//! The `wasmtime` crate can roughly be thought of as being split into two
//! halves:
//!
//! * One half of the crate is similar to the [JS WebAssembly
//! API](https://developer.mozilla.org/en-US/docs/WebAssembly) as well as the
//! [proposed C API](https://github.com/webassembly/wasm-c-api) and is
//! intended for working with [WebAssembly modules]. This API resides in the
//! root of the `wasmtime` crate's namespace, for example
//! [`wasmtime::Module`](`Module`).
//!
//! * The second half of the crate is for use with the [WebAssembly Component
//! Model]. The implementation of the component model is present in
//! [`wasmtime::component`](`component`) and roughly mirrors the structure for
//! core WebAssembly, for example [`component::Func`] mirrors [`Func`].
//!
//! [WebAssembly Component Model]: https://component-model.bytecodealliance.org
//!
//! An example of using Wasmtime to run a core WebAssembly module looks like:
//!
//! ```
//! use wasmtime::*;
//!
//! fn main() -> wasmtime::Result<()> {
//! let engine = Engine::default();
//!
//! // Modules can be compiled through either the text or binary format
//! let wat = r#"
//! (module
//! (import "host" "host_func" (func $host_hello (param i32)))
//!
//! (func (export "hello")
//! i32.const 3
//! call $host_hello)
//! )
//! "#;
//! let module = Module::new(&engine, wat)?;
//!
//! // Host functionality can be arbitrary Rust functions and is provided
//! // to guests through a `Linker`.
//! let mut linker = Linker::new(&engine);
//! linker.func_wrap("host", "host_func", |caller: Caller<'_, u32>, param: i32| {
//! println!("Got {} from WebAssembly", param);
//! println!("my host state is: {}", caller.data());
//! })?;
//!
//! // All wasm objects operate within the context of a "store". Each
//! // `Store` has a type parameter to store host-specific data, which in
//! // this case we're using `4` for.
//! let mut store: Store<u32> = Store::new(&engine, 4);
//!
//! // Instantiation of a module requires specifying its imports and then
//! // afterwards we can fetch exports by name, as well as asserting the
//! // type signature of the function with `get_typed_func`.
//! let instance = linker.instantiate(&mut store, &module)?;
//! let hello = instance.get_typed_func::<(), ()>(&mut store, "hello")?;
//!
//! // And finally we can call the wasm!
//! hello.call(&mut store, ())?;
//!
//! Ok(())
//! }
//! ```
//!
//! ## Core Concepts
//!
//! There are a number of core types and concepts that are important to be aware
//! of when using the `wasmtime` crate:
//!
//! * [`Engine`] - a global compilation and runtime environment for WebAssembly.
//! An [`Engine`] is an object that can be shared concurrently across threads
//! and is created with a [`Config`] with many knobs for configuring
//! behavior. Compiling or executing any WebAssembly requires first
//! configuring and creating an [`Engine`]. All [`Module`]s and
//! [`Component`](component::Component)s belong to an [`Engine`], and
//! typically there's one [`Engine`] per process.
//!
//! * [`Store`] - container for all information related to WebAssembly objects
//! such as functions, instances, memories, etc. A [`Store<T>`][`Store`]
//! allows customization of the `T` to store arbitrary host data within a
//! [`Store`]. This host data can be accessed through host functions via the
//! [`Caller`] function parameter in host-defined functions. A [`Store`] is
//! required for all WebAssembly operations, such as calling a wasm function.
//! The [`Store`] is passed in as a "context" to methods like [`Func::call`].
//! Dropping a [`Store`] will deallocate all memory associated with
//! WebAssembly objects within the [`Store`]. A [`Store`] is cheap to create
//! and destroy and does not GC objects such as unused instances internally,
//! so it's intended to be short-lived (or no longer than the instances it
//! contains).
//!
//! * [`Linker`] (or [`component::Linker`]) - host functions are defined within
//! a linker to provide them a string-based name which can be looked up when
//! instantiating a WebAssembly module or component. Linkers are traditionally
//! populated at startup and then reused for all future instantiations of all
//! instances, assuming the set of host functions does not change over time.
//! Host functions are `Fn(..) + Send + Sync` and typically do not close over
//! mutable state. Instead it's recommended to store mutable state in the `T`
//! of [`Store<T>`] which is accessed through [`Caller<'_,
//! T>`](crate::Caller) provided to host functions.
//!
//! * [`Module`] (or [`Component`](component::Component)) - a compiled
//! WebAssembly module or component. These structures contain compiled
//! executable code from a WebAssembly binary which is ready to execute after
//! being instantiated. These are expensive to create as they require
//! compilation of the input WebAssembly. Modules and components are safe to
//! share across threads, however. Modules and components can additionally be
//! [serialized into a list of bytes](crate::Module::serialize) to later be
//! [deserialized](crate::Module::deserialize) quickly. This enables JIT-style
//! compilation through constructors such as [`Module::new`] and AOT-style
//! compilation by having the compilation process use [`Module::serialize`]
//! and the execution process use [`Module::deserialize`].
//!
//! * [`Instance`] (or [`component::Instance`]) - an instantiated WebAssembly
//! module or component. An instance is where you can actually acquire a
//! [`Func`] (or [`component::Func`]) from, for example, to call.
//!
//! * [`Func`] (or [`component::Func`]) - a WebAssembly function. This can be
//! acquired as the export of an [`Instance`] to call WebAssembly functions,
//! or it can be created via functions like [`Func::wrap`] to wrap
//! host-defined functionality and give it to WebAssembly. Functions also have
//! typed views as [`TypedFunc`] or [`component::TypedFunc`] for a more
//! efficient calling convention.
//!
//! * [`Table`], [`Global`], [`Memory`], [`component::Resource`] - other
//! WebAssembly objects which can either be defined on the host or in wasm
//! itself (via instances). These all have various ways of being interacted
//! with like [`Func`].
//!
//! All "store-connected" types such as [`Func`], [`Memory`], etc, require the
//! store to be passed in as a context to each method. Methods in wasmtime
//! frequently have their first parameter as either [`impl
//! AsContext`][`AsContext`] or [`impl AsContextMut`][`AsContextMut`]. These
//! traits are implemented for a variety of types, allowing you to, for example,
//! pass the following types into methods:
//!
//! * `&Store<T>`
//! * `&mut Store<T>`
//! * `&Caller<'_, T>`
//! * `&mut Caller<'_, T>`
//! * `StoreContext<'_, T>`
//! * `StoreContextMut<'_, T>`
//!
//! A [`Store`] is the sole owner of all WebAssembly internals. Types like
//! [`Func`] point within the [`Store`] and require the [`Store`] to be provided
//! to actually access the internals of the WebAssembly function, for instance.
//!
//! ## WASI
//!
//! The `wasmtime` crate does not natively provide support for WASI, but you can
//! use the [`wasmtime-wasi`] crate for that purpose. With [`wasmtime-wasi`] all
//! WASI functions can be added to a [`Linker`] and then used to instantiate
//! WASI-using modules. For more information see the [WASI example in the
//! documentation](https://docs.wasmtime.dev/examples-rust-wasi.html).
//!
//! [`wasmtime-wasi`]: https://crates.io/crates/wasmtime-wasi
//!
//! ## Async
//!
//! Wasmtime supports executing WebAssembly guests through Rust-level `async`
//! functions. This enables Wasmtime to block the guest without blocking the
//! host, interrupt infinite loops or long-running CPU-bound guests, and
//! integrate with Rust host functions that are themselves `async`.
//!
//! Many functions in the embedding API have a sync variant and an async
//! variant, for example [`Func::call`] and [`Func::call_async`]. Embedders
//! may decide which is most appropriate for their use case, but if certain
//! features of Wasmtime are configured then `*_async` variants of functions are
//! required. If any of these features are used, for example, then `*_async`
//! must be used:
//!
//! * Async core wasm host functions, for example via [`Linker::func_wrap_async`]
//! * Async component host functions, for example via [`component::LinkerInstance::func_wrap_async`]
//! * Async resource limiters, via [`Store::limiter_async`]
//! * Async yields with fuel via [`Store::fuel_async_yield_interval`]
//! * Async yields via epochs via [`Store::epoch_deadline_async_yield_and_update`]
//!
//! This is not an exhaustive list, but if any of these configurations/APIs are
//! used then all `*_async` APIs must be used in Wasmtime. If synchronous APIs
//! are used instead they will return an error.
//!
//! #### Asynchronous Wasm
//!
//! Core WebAssembly and synchronous WIT functions (e.g. WASIp2-and-prior)
//! require that all imported functions appear synchronous from the perspective
//! of the guest. Host functions which perform I/O and block, however, are often
//! defined with `async` in Rust. Wasmtime's async support bridges this gap with
//! asynchronous wasm execution.
//!
//! When using `*_async` APIs to execute WebAssembly Wasmtime will always
//! represent its computation as a [`Future`]. The `poll` method of the futures
//! returned by Wasmtime will perform the actual work of calling the
//! WebAssembly. Wasmtime won't manage its own thread pools or similar, that's
//! left up to the embedder.
//!
//! To implement futures in a way that WebAssembly sees asynchronous host
//! functions as synchronous, all async Wasmtime futures will execute on a
//! separately allocated native stack from the thread otherwise executing
//! Wasmtime. This separate native stack can then be switched to and from.
//! Using this whenever an `async` host function returns a future that
//! resolves to `Pending` we switch away from the temporary stack back to
//! the main stack and propagate the `Pending` status.
//!
//! #### Execution in `poll`
//!
//! The [`Future::poll`] method is the main driving force behind Rust's futures.
//! That method's own documentation states "an implementation of `poll` should
//! strive to return quickly, and should not block". This, however, can be at
//! odds with executing WebAssembly code as part of the `poll` method itself. If
//! your WebAssembly is untrusted then this could allow the `poll` method to
//! take arbitrarily long in the worst case, likely blocking all other
//! asynchronous tasks.
//!
//! To remedy this situation you have a few possible ways to solve this:
//!
//! * The most efficient solution is to enable
//! [`Config::epoch_interruption`] in conjunction with
//! [`crate::Store::epoch_deadline_async_yield_and_update`]. Coupled with
//! periodic calls to [`crate::Engine::increment_epoch`] this will cause
//! executing WebAssembly to periodically yield back according to the
//! epoch configuration settings. This enables [`Future::poll`] to take at
//! most a certain amount of time according to epoch configuration
//! settings and when increments happen. The benefit of this approach is
//! that the instrumentation in compiled code is quite lightweight, but a
//! downside can be that the scheduling is somewhat nondeterministic since
//! increments are usually timer-based which are not always deterministic.
//!
//! Note that to prevent infinite execution of wasm it's recommended to
//! place a timeout on the entire future representing executing wasm code
//! and the periodic yields with epochs should ensure that when the
//! timeout is reached it's appropriately recognized.
//!
//! * Alternatively you can enable the
//! [`Config::consume_fuel`](crate::Config::consume_fuel) method as well
//! as [`crate::Store::fuel_async_yield_interval`] When doing so this will
//! configure Wasmtime futures to yield periodically while they're
//! executing WebAssembly code. After consuming the specified amount of
//! fuel wasm futures will return `Poll::Pending` from their `poll`
//! method, and will get automatically re-polled later. This enables the
//! `Future::poll` method to take roughly a fixed amount of time since
//! fuel is guaranteed to get consumed while wasm is executing. Unlike
//! epoch-based preemption this is deterministic since wasm always
//! consumes a fixed amount of fuel per-operation. The downside of this
//! approach, however, is that the compiled code instrumentation is
//! significantly more expensive than epoch checks.
//!
//! Note that to prevent infinite execution of wasm it's recommended to
//! place a timeout on the entire future representing executing wasm code
//! and the periodic yields with epochs should ensure that when the
//! timeout is reached it's appropriately recognized.
//!
//! In all cases special care needs to be taken when integrating
//! asynchronous wasm into your application. You should carefully plan where
//! WebAssembly will execute and what compute resources will be allotted to
//! it. If Wasmtime doesn't support exactly what you'd like just yet, please
//! feel free to open an issue!
//!
//! ## Crate Features
//!
//! The `wasmtime` crate comes with a number of compile-time features that can
//! be used to customize what features it supports. Some of these features are
//! just internal details, but some affect the public API of the `wasmtime`
//! crate. Wasmtime APIs gated behind a Cargo feature should be indicated as
//! such in the documentation.
//!
//! * `runtime` - Enabled by default, this feature enables executing
//! WebAssembly modules and components. If a compiler is not available (such
//! as `cranelift`) then [`Module::deserialize`] must be used, for example, to
//! provide an ahead-of-time compiled artifact to execute WebAssembly.
//!
//! * `cranelift` - Enabled by default, this features enables using Cranelift at
//! runtime to compile a WebAssembly module to native code. This feature is
//! required to process and compile new WebAssembly modules and components.
//!
//! * `cache` - Enabled by default, this feature adds support for wasmtime to
//! perform internal caching of modules in a global location. This must still
//! be enabled explicitly through [`Config::cache`].
//!
//! * `wat` - Enabled by default, this feature adds support for accepting the
//! text format of WebAssembly in [`Module::new`] and
//! [`Component::new`](component::Component::new). The text format will be
//! automatically recognized and translated to binary when compiling a
//! module.
//!
//! * `parallel-compilation` - Enabled by default, this feature enables support
//! for compiling functions in parallel with `rayon`.
//!
//! * `async` - Enabled by default, this feature enables APIs and runtime
//! support for defining asynchronous host functions and calling WebAssembly
//! asynchronously. For more information see [async documentation](#async)
//!
//! * `profiling` - Enabled by default, this feature compiles in support for
//! profiling guest code via a number of possible strategies. See
//! [`Config::profiler`] for more information.
//!
//! * `all-arch` - Not enabled by default. This feature compiles in support for
//! all architectures for both the JIT compiler and the `wasmtime compile` CLI
//! command. This can be combined with [`Config::target`] to precompile
//! modules for a different platform than the host.
//!
//! * `pooling-allocator` - Enabled by default, this feature adds support for
//! [`PoolingAllocationConfig`] to pass to [`Config::allocation_strategy`].
//! The pooling allocator can enable efficient reuse of resources for
//! high-concurrency and high-instantiation-count scenarios.
//!
//! * `demangle` - Enabled by default, this will affect how backtraces are
//! printed and whether symbol names from WebAssembly are attempted to be
//! demangled. Rust and C++ demanglings are currently supported.
//!
//! * `coredump` - Enabled by default, this will provide support for generating
//! a core dump when a trap happens. This can be configured via
//! [`Config::coredump_on_trap`].
//!
//! * `addr2line` - Enabled by default, this feature configures whether traps
//! will attempt to parse DWARF debug information and convert WebAssembly
//! addresses to source filenames and line numbers.
//!
//! * `debug-builtins` - Enabled by default, this feature includes some built-in
//! debugging utilities and symbols for native debuggers such as GDB and LLDB
//! to attach to the process Wasmtime is used within. The intrinsics provided
//! will enable debugging guest code compiled to WebAssembly. This must also
//! be enabled via [`Config::debug_info`] as well for guests.
//!
//! * `component-model` - Enabled by default, this enables support for the
//! [`wasmtime::component`](component) API for working with components.
//!
//! * `gc` - Enabled by default, this enables support for a number of
//! WebAssembly proposals such as `reference-types`, `function-references`,
//! and `gc`. Note that the implementation of the `gc` proposal itself is not
//! yet complete at this time.
//!
//! * `threads` - Enabled by default, this enables compile-time support for the
//! WebAssembly `threads` proposal, notably shared memories.
//!
//! * `call-hook` - Disabled by default, this enables support for the
//! [`Store::call_hook`] API. This incurs a small overhead on all
//! entries/exits from WebAssembly and may want to be disabled by some
//! embedders.
//!
//! * `memory-protection-keys` - Disabled by default, this enables support for
//! the [`PoolingAllocationConfig::memory_protection_keys`] API. This feature
//! currently only works on x64 Linux and can enable compacting the virtual
//! memory allocation for linear memories in the pooling allocator. This comes
//! with the same overhead as the `call-hook` feature where entries/exits into
//! WebAssembly will have more overhead than before.
//!
//! More crate features can be found in the [manifest] of Wasmtime itself for
//! seeing what can be enabled and disabled.
//!
//! [manifest]: https://github.com/bytecodealliance/wasmtime/blob/main/crates/wasmtime/Cargo.toml
#![deny(missing_docs)]
#![doc(test(attr(deny(warnings))))]
#![doc(test(attr(allow(dead_code, unused_variables, unused_mut))))]
#![cfg_attr(docsrs, feature(doc_cfg))]
// NB: this list is currently being burned down to remove all features listed
// here to get warnings in all configurations of Wasmtime.
#![cfg_attr(
any(not(feature = "runtime"), not(feature = "std")),
expect(dead_code, unused_imports, reason = "list not burned down yet")
)]
// Allow broken links when the default features is disabled because most of our
// documentation is written for the "one build" of the `main` branch which has
// most features enabled. This will present warnings in stripped-down doc builds
// and will prevent the doc build from failing.
#![cfg_attr(feature = "default", warn(rustdoc::broken_intra_doc_links))]
#![no_std]
// Wasmtime liberally uses #[cfg]'d definitions of structures to uninhabited
// types to reduce the total amount of #[cfg], but rustc warns that much usage
// of these structures, rightfully, leads to unreachable code. This unreachable
// code is only conditional, however, so it's generally just annoying to deal
// with. Disable the `unreachable_code` lint in situations like this when some
// major features are disabled. If all the features are enabled, though, we
// still want to get warned about this.
#![cfg_attr(
any(not(feature = "threads"), not(feature = "gc",)),
allow(unreachable_code, reason = "see comment")
)]
#[cfg(feature = "std")]
#[macro_use]
extern crate std;
extern crate alloc;
// Internal `use` statement which isn't used in this module but enable
// `use crate::prelude::*;` everywhere else within this crate, for example.
use wasmtime_environ::prelude;
// FIXME(#12069) should transition to OOM-handling versions of these collections
// for all internal usage instead of using abort-on-OOM versions. Once that's
// done this can be removed and the collections should be directly imported from
// `wasmtime_environ::collections::*`.
#[allow(
unused_imports,
reason = "not all build configs use these; easier to allow than to precisely `cfg`"
)]
use wasmtime_environ::collections::oom_abort::{hash_map, hash_set};
/// A helper macro to safely map `MaybeUninit<T>` to `MaybeUninit<U>` where `U`
/// is a field projection within `T`.
///
/// This is intended to be invoked as:
///
/// ```ignore
/// struct MyType {
/// field: u32,
/// }
///
/// let initial: &mut MaybeUninit<MyType> = ...;
/// let field: &mut MaybeUninit<u32> = map_maybe_uninit!(initial.field);
/// ```
///
/// Note that array accesses are also supported:
///
/// ```ignore
///
/// let initial: &mut MaybeUninit<[u32; 2]> = ...;
/// let element: &mut MaybeUninit<u32> = map_maybe_uninit!(initial[1]);
/// ```
#[doc(hidden)]
#[macro_export]
macro_rules! map_maybe_uninit {
($maybe_uninit:ident $($field:tt)*) => ({
#[allow(unused_unsafe, reason = "macro-generated code")]
{
unsafe {
use $crate::MaybeUninitExt;
let m: &mut core::mem::MaybeUninit<_> = $maybe_uninit;
// Note the usage of `&raw` here which is an attempt to "stay
// safe" here where we never accidentally create `&mut T` where `T` is
// actually uninitialized, hopefully appeasing the Rust unsafe
// guidelines gods.
m.map(|p| &raw mut (*p)$($field)*)
}
}
})
}
#[doc(hidden)]
pub trait MaybeUninitExt<T> {
/// Maps `MaybeUninit<T>` to `MaybeUninit<U>` using the closure provided.
///
/// # Safety
///
/// Requires that `*mut U` is a field projection from `*mut T`. Use
/// `map_maybe_uninit!` above instead.
unsafe fn map<U>(&mut self, f: impl FnOnce(*mut T) -> *mut U)
-> &mut core::mem::MaybeUninit<U>;
}
impl<T> MaybeUninitExt<T> for core::mem::MaybeUninit<T> {
unsafe fn map<U>(
&mut self,
f: impl FnOnce(*mut T) -> *mut U,
) -> &mut core::mem::MaybeUninit<U> {
let new_ptr = f(self.as_mut_ptr());
// SAFETY: the memory layout of these two types are the same, and
// asserting that it's a safe reference with the same lifetime as `self`
// is a requirement of this function itself.
unsafe { core::mem::transmute::<*mut U, &mut core::mem::MaybeUninit<U>>(new_ptr) }
}
}
#[cfg(feature = "runtime")]
mod runtime;
#[cfg(feature = "runtime")]
pub use runtime::*;
#[cfg(any(feature = "cranelift", feature = "winch"))]
mod compile;
#[cfg(any(feature = "cranelift", feature = "winch"))]
pub use compile::{CodeBuilder, CodeHint};
mod config;
mod engine;
mod profiling_agent;
pub use crate::config::*;
pub use crate::engine::*;
#[cfg(feature = "std")]
mod sync_std;
#[cfg(feature = "std")]
use sync_std as sync;
mod sync_nostd;
#[cfg(not(feature = "std"))]
use sync_nostd as sync;
pub use wasmtime_environ::ToWasmtimeResult;
#[doc(inline)]
pub use wasmtime_environ::error;
pub use wasmtime_environ::{FuncIndex, StaticModuleIndex};
pub use wasmtime_environ::{OperatorCost, VariableOperatorCost};
// Only for use in `bindgen!`-generated code.
#[doc(hidden)]
#[cfg(feature = "anyhow")]
pub use wasmtime_environ::anyhow;
pub use self::error::{Error, OutOfMemory, Result, bail, ensure, format_err};
/// A re-exported instance of Wasmtime's `wasmparser` dependency.
///
/// This may be useful for embedders that also use `wasmparser`
/// directly: it allows embedders to ensure that they are using the same
/// version as Wasmtime, both to eliminate redundant dependencies on
/// multiple versions of the library, and to ensure compatibility in
/// validation and feature support.
///
/// Note that this re-export is *not subject to semver*: we reserve the
/// right to make patch releases of Wasmtime that bump the version of
/// wasmparser used, and hence the version re-exported, in
/// semver-incompatible ways. This is the tradeoff that the embedder
/// needs to opt into: in order to stay exactly in sync with an internal
/// detail of Wasmtime, the cost is visibility into potential internal
/// version changes.
#[cfg(feature = "reexport-wasmparser")]
pub use wasmparser;
fn _assert_send_and_sync<T: Send + Sync>() {}
fn _assertions_lib() {
_assert_send_and_sync::<Engine>();
_assert_send_and_sync::<Config>();
}
#[cfg(feature = "runtime")]
#[doc(hidden)]
pub mod _internal {
// Exported just for the CLI.
pub use crate::runtime::vm::MmapVec;
}
+135
View File
@@ -0,0 +1,135 @@
use crate::prelude::*;
cfg_select! {
all(feature = "profiling", target_os = "linux") => {
mod jitdump;
pub use jitdump::new as new_jitdump;
}
_ => {
pub fn new_jitdump() -> Result<Box<dyn ProfilingAgent>> {
if cfg!(feature = "profiling") {
bail!("jitdump is not supported on this platform");
} else {
bail!("jitdump support disabled at compile time");
}
}
}
}
cfg_select! {
all(unix, feature = "std") => {
mod perfmap;
pub use perfmap::new as new_perfmap;
}
_ => {
pub fn new_perfmap() -> Result<Box<dyn ProfilingAgent>> {
bail!("perfmap support not supported on this platform");
}
}
}
cfg_select! {
// Note that the `#[cfg]` here should be kept in sync with the
// corresponding dependency directive on `ittapi` in `Cargo.toml`.
all(
feature = "profiling",
target_arch = "x86_64",
any(
target_os = "windows",
target_os = "macos",
target_os = "linux",
),
) => {
mod vtune;
pub use vtune::new as new_vtune;
}
_ => {
pub fn new_vtune() -> Result<Box<dyn ProfilingAgent>> {
if cfg!(feature = "profiling") {
bail!("VTune is not supported on this platform.");
} else {
bail!("VTune support disabled at compile time.");
}
}
}
}
cfg_select! {
feature = "profile-pulley" => {
mod pulley;
pub use pulley::new as new_pulley;
}
_ => {
pub fn new_pulley() -> Result<Box<dyn ProfilingAgent>> {
bail!("pulley profiling support disabled at compile time.");
}
}
}
/// Common interface for profiling tools.
pub trait ProfilingAgent: Send + Sync + 'static {
fn register_function(&self, name: &str, code: &[u8]);
#[cfg(all(feature = "runtime", feature = "pulley"))]
fn register_interpreter(&self, interp: &crate::vm::Interpreter) {
let _ = interp;
}
fn register_module(&self, code: &[u8], custom_name: &dyn Fn(usize) -> Option<String>) {
use object::{File, Object as _, ObjectSection, ObjectSymbol, SectionKind, SymbolKind};
let image = match File::parse(code) {
Ok(image) => image,
Err(_) => return,
};
let text = match image
.sections()
.find(|s| s.kind() == SectionKind::Text || s.name() == Ok(".text"))
{
Some(section) => match section.data() {
Ok(data) => data,
Err(_) => return,
},
None => return,
};
for sym in image.symbols() {
if !sym.is_definition() {
continue;
}
if sym.kind() != SymbolKind::Text {
continue;
}
let address = sym.address();
let size = sym.size();
if size == 0 {
continue;
}
if let Ok(name) = sym.name() {
let owned;
let name = match custom_name(address as usize) {
Some(name) => {
owned = name;
&owned
}
None => name,
};
let code = &text[address as usize..][..size as usize];
self.register_function(name, code)
}
}
}
}
pub fn new_null() -> Box<dyn ProfilingAgent> {
Box::new(NullProfilerAgent)
}
#[derive(Debug, Default, Clone, Copy)]
struct NullProfilerAgent;
impl ProfilingAgent for NullProfilerAgent {
fn register_function(&self, _name: &str, _code: &[u8]) {}
fn register_module(&self, _code: &[u8], _custom_name: &dyn Fn(usize) -> Option<String>) {}
}
@@ -0,0 +1,66 @@
//! Support for jitdump files which can be used by perf for profiling jitted code.
//! Spec definitions for the output format is as described here:
//! <https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/tools/perf/Documentation/jitdump-specification.txt>
//!
//! Usage Example:
//! Record
//! sudo perf record -k 1 -e instructions:u target/debug/wasmtime -g --profile=jitdump test.wasm
//! Combine
//! sudo perf inject -v -j -i perf.data -o perf.jit.data
//! Report
//! sudo perf report -i perf.jit.data -F+period,srcline
//! Note: For descriptive results, the WASM file being executed should contain dwarf debug data
use crate::prelude::*;
use crate::profiling_agent::ProfilingAgent;
use object::elf;
use std::process;
use std::sync::Mutex;
use target_lexicon::Architecture;
use wasmtime_jit_debug::perf_jitdump::*;
/// Interface for driving the creation of jitdump files
struct JitDumpAgent {
pid: u32,
}
/// Process-wide JIT dump file. Perf only accepts a unique file per process, in the injection step.
static JITDUMP_FILE: Mutex<Option<JitDumpFile>> = Mutex::new(None);
/// Initialize a JitDumpAgent and write out the header.
pub fn new() -> Result<Box<dyn ProfilingAgent>> {
let mut jitdump_file = JITDUMP_FILE.lock().unwrap();
if jitdump_file.is_none() {
let filename = format!("./jit-{}.dump", process::id());
let e_machine = match target_lexicon::HOST.architecture {
Architecture::X86_64 => elf::EM_X86_64,
Architecture::X86_32(_) => elf::EM_386,
Architecture::Arm(_) => elf::EM_ARM,
Architecture::Aarch64(_) => elf::EM_AARCH64,
Architecture::S390x => elf::EM_S390,
_ => unimplemented!("unrecognized architecture"),
};
*jitdump_file = Some(JitDumpFile::new(filename, e_machine.0 as u32)?);
}
Ok(Box::new(JitDumpAgent {
pid: std::process::id(),
}))
}
impl ProfilingAgent for JitDumpAgent {
fn register_function(&self, name: &str, code: &[u8]) {
let mut jitdump_file = JITDUMP_FILE.lock().unwrap();
let jitdump_file = jitdump_file.as_mut().unwrap();
let timestamp = jitdump_file.get_time_stamp();
let tid = rustix::thread::gettid()
.as_raw_nonzero()
.get()
.cast_unsigned();
if let Err(err) = jitdump_file.dump_code_load_record(&name, code, timestamp, self.pid, tid)
{
println!("Jitdump: write_code_load_failed_record failed: {err:?}\n");
}
}
}
@@ -0,0 +1,63 @@
use crate::prelude::*;
use crate::profiling_agent::ProfilingAgent;
use std::fs::OpenOptions;
use std::io::{self, Write};
use std::process;
use std::{fs::File, sync::Mutex};
/// Process-wide perf map file. Perf only reads a unique file per process.
static PERFMAP_FILE: Mutex<Option<File>> = Mutex::new(None);
/// Interface for driving the creation of jitdump files
struct PerfMapAgent;
/// Initialize a JitDumpAgent and write out the header.
pub fn new() -> Result<Box<dyn ProfilingAgent>> {
let mut file = PERFMAP_FILE.lock().unwrap();
if file.is_none() {
let filename = format!("/tmp/perf-{}.map", process::id());
// Open the file specifically in append mode to handle the case where
// multiple engines in the same process are all writing to this file.
*file = Some(
OpenOptions::new()
.append(true)
.write(true)
.create(true)
.open(&filename)?,
);
}
Ok(Box::new(PerfMapAgent))
}
impl PerfMapAgent {
fn make_line(writer: &mut File, name: &str, code: &[u8]) -> io::Result<()> {
// Format is documented here: https://github.com/torvalds/linux/blob/master/tools/perf/Documentation/jit-interface.txt
// Try our best to sanitize the name, since wasm allows for any utf8 string in there.
let sanitized_name = name.replace('\n', "_").replace('\r', "_");
let line = format!("{:p} {:x} {sanitized_name}\n", code.as_ptr(), code.len());
// To handle multiple concurrent engines in the same process writing to
// this file an attempt is made to issue a single `write` syscall with
// all of the contents. This would mean, though, that partial writes
// would be an error. In lieu of returning an error the `write_all`
// helper is used instead which may result in a corrupt file if there
// are other engines writing to the file at the same time.
//
// For more discussion of the tradeoffs here see the `perf_jitdump.rs`
// file which has to deal with the same problem.
writer.write_all(line.as_bytes())?;
Ok(())
}
}
impl ProfilingAgent for PerfMapAgent {
fn register_function(&self, name: &str, code: &[u8]) {
let mut file = PERFMAP_FILE.lock().unwrap();
let file = file.as_mut().unwrap();
if let Err(err) = Self::make_line(file, name, code) {
eprintln!("Error when writing import trampoline info to the perf map file: {err}");
}
}
}
@@ -0,0 +1,266 @@
//! Basic support for emitting a `*.data` file which contains samples of pulley
//! bytecode.
//!
//! Pulley is Wasmtime's interpreter and native profilers are not good at
//! profiling bytecode interpreters because they show hot bytecode instructions
//! but we're instead often interested in the shape of the bytecode itself
//! around the hot instruction, for example to identify new macro-instructions
//! to add to Pulley. This module serves as a means of collecting data from
//! Pulley being executed in-process and serializing it to a file.
//!
//! The file collected here is populated by a sampling thread in-process. This
//! sampling thread only collects the current program counter of any interpreters
//! in the process. This does not collect stack traces at all. That means that
//! this profiler is only suitable for looking at "self time" and is not
//! suitable for getting a broader picture of what's going on (e.g. why
//! something was called in the first place).
//!
//! The general design of this profiler is:
//!
//! * Support for this all requires a `pulley-profile` feature at compile-time
//! as it's generally a perf hit to the interpreter loop.
//! * Each Pulley interpreter updates an `AtomicUsize` before all instructions
//! with the current PC that it's executing.
//! * This module spawns a "sampling thread" which will, at some frequency,
//! collect all the PCs of all interpreters in the process.
//! * Once enough samples have been collected they're flushed out to a data file
//! on a second thread, the "recording thread".
//!
//! The hope is that the sampling thread stays as steady as possible in its
//! sampling rate while not hitting OOM conditions in the process or anything
//! like that. The `*.data` file that's emitted is intended to be processed by
//! example code in the `pulley-interpreter` crate or `pulley/examples/*.rs` in
//! the Wasmtime repository.
use crate::ToWasmtimeResult as _;
use crate::prelude::*;
use crate::profiling_agent::ProfilingAgent;
#[cfg(feature = "runtime")]
use crate::vm::Interpreter;
use pulley_interpreter::profile::{ExecutingPc, Recorder, Samples};
use std::mem;
use std::sync::mpsc;
use std::sync::{Arc, Condvar, Mutex};
use std::thread::{self, JoinHandle};
use std::time::{Duration, Instant};
/// Implementation of `ProfilingAgent` from the Wasmtime crate.
struct PulleyAgent {
state: Arc<State>,
/// Handle to the thread performing periodic sampling. This is joined on
/// `Drop` of this structure so it's not a daemon thread permanently.
sampling_thread: Option<JoinHandle<()>>,
/// Same as the sampling thread above, but for recording data to the
/// filesystem.
recording_thread: Option<JoinHandle<()>>,
}
struct State {
/// Protected state about the recorder, or the file being created. This is
/// accessed both from the "recording thread" as well as `Engine` threads to
/// register new pulley bytecode.
recorder: Mutex<Recorder>,
/// Protected state about sampling interpreters. This is accessed both from
/// the "sampling thread" primarily but is additionally accessed from
/// `Engine` threads to register new interpreters coming online.
sampling: Mutex<SamplingState>,
/// Condition variable which is signaled when sampling should cease and
/// exit. This is coupled with `Drop for PulleyAgent`.
sampling_done: Condvar,
/// The frequency at which samples are collected. Defaults to 1000 but can
/// be configured with the `PULLEY_SAMPLING_FREQ` environment variable.
sampling_freq: u32,
/// Number of samples to buffer before flushing them to a file. Defaults to
/// 20000 but can be configured with the `PULLEY_SAMPLING_FLUSH_AMT`
/// environment variable.
sampling_flush_amt: u32,
}
/// State protected by a mutex in `State` above related to sampling.
#[derive(Default)]
struct SamplingState {
/// All interpreters known to be executing. This is a list of
/// pointers-to-the-current-PC which is updated whenever the interpreter
/// executes an instruction.
interpreters: Vec<ExecutingPc>,
/// Current list of samples that have been collected.
samples: Samples,
}
pub fn new() -> Result<Box<dyn ProfilingAgent>> {
let pid = std::process::id();
let filename = format!("./pulley-{pid}.data");
let mut agent = PulleyAgent {
state: Arc::new(State {
recorder: Mutex::new(Recorder::new(&filename).to_wasmtime_result()?),
sampling: Default::default(),
sampling_done: Condvar::new(),
sampling_freq: std::env::var("PULLEY_SAMPLING_FREQ")
.ok()
.and_then(|s| s.parse::<u32>().ok())
.unwrap_or(1_000),
sampling_flush_amt: std::env::var("PULLEY_SAMPLING_FLUSH_AMT")
.ok()
.and_then(|s| s.parse::<u32>().ok())
.unwrap_or(20_000),
}),
sampling_thread: None,
recording_thread: None,
};
let (tx, rx) = mpsc::channel();
let state = agent.state.clone();
agent.sampling_thread = Some(thread::spawn(move || sampling_thread(&state, tx)));
let state = agent.state.clone();
agent.recording_thread = Some(thread::spawn(move || recording_thread(&state, rx)));
Ok(Box::new(agent))
}
impl ProfilingAgent for PulleyAgent {
/// New functions are registered with `Recorder` to record the exact
/// bytecode so disassembly is available during profile analysis.
///
/// Note that this also provides the native address that code is loaded at
/// so samples know what code it's within.
fn register_function(&self, name: &str, code: &[u8]) {
self.state
.recorder
.lock()
.unwrap()
.add_function(name, code)
.expect("failed to register pulley function");
}
/// Registers a new interpreter coming online. Interpreters, with
/// `pulley-profile` enabled, store a shadow program counter updated on each
/// instruction which we can read from a different thread.
#[cfg(feature = "runtime")]
fn register_interpreter(&self, interpreter: &Interpreter) {
let pc = interpreter.pulley().executing_pc();
self.state
.sampling
.lock()
.unwrap()
.interpreters
.push(pc.clone());
}
}
/// Execution of the thread responsible for sampling interpreters.
///
/// This thread has a few tasks:
///
/// * Needs to sample, at `state.sampling_freq`, the state of all known
/// interpreters. Ideally this sampling is as steady as possible.
/// * Needs to clean up interpreters which have been destroyed as there's
/// otherwise no hook for doing so.
/// * Needs to send batches of samples to the recording thread to get written to
/// the filesystem.
fn sampling_thread(state: &State, to_record: mpsc::Sender<Samples>) {
// Calculate the `Duration` between each sample which will be in
// nanoseconds. This duration is then used to create an `Instant` in time
// where we'll be collecting the next sample.
let between_ticks = Duration::new(0, 1_000_000_000 / state.sampling_freq);
let start = Instant::now();
let mut next_sample = start + between_ticks;
// Helper closure to send off a batch of samples to the recording thread.
// Note that recording is done off-thread to ensure that the filesystem I/O
// interferes as little as possible with the sampling rate here.
let record = |sampling: &mut SamplingState| {
if sampling.samples.num_samples() == 0 {
return;
}
let samples = mem::take(&mut sampling.samples);
to_record.send(samples).unwrap();
};
let mut sampling = state.sampling.lock().unwrap();
loop {
// Calculate the duration, from this current moment in time, to when the
// next sample is supposed to be taken. If the next sampling time is in
// the past then this won't sleep but will still check the condvar.
let dur = next_sample
.checked_duration_since(Instant::now())
.unwrap_or(Duration::new(0, 0));
// Wait on `state.sampling_done`, but with the timeout we've calculated.
// If this times out that means that the next sample can proceed.
// Otherwise if this did not time out then it means that sampling should
// cease as the profiler is being destroyed.
let (guard, result) = state.sampling_done.wait_timeout(sampling, dur).unwrap();
sampling = guard;
if !result.timed_out() {
break;
}
// Now that we've decided to take a sample increment the next sample
// time by our interval. Once we're done sampling below we'll then sleep
// again up to this time.
next_sample += between_ticks;
// Sample the state of all interpreters known. This first starts by
// discarding any interpreters that are offline. Samples without a PC
// are additionally discarded as it means the interpreter is inactive.
//
// Once enough samples have been collected they're flushed to the
// recording thread.
let SamplingState {
interpreters,
samples,
} = &mut *sampling;
interpreters.retain(|a| !a.is_done());
for interpreter in interpreters.iter() {
if let Some(pc) = interpreter.get() {
samples.append(pc);
}
}
if samples.num_samples() > state.sampling_flush_amt {
record(&mut sampling);
}
}
// Send any final samples to the recording thread after the loop has exited.
record(&mut sampling);
}
/// Helper thread responsible for writing samples to the filesystem.
///
/// This receives samples over `to_record` and then performs the filesystem I/O
/// necessary to write them out. This thread completes once `to_record` is
/// closed, or when the sampling thread completes. At that time all data in the
/// recorder is flushed out as well.
fn recording_thread(state: &State, to_record: mpsc::Receiver<Samples>) {
for mut samples in to_record {
state
.recorder
.lock()
.unwrap()
.add_samples(&mut samples)
.expect("failed to write samples");
}
state.recorder.lock().unwrap().flush().unwrap();
}
impl Drop for PulleyAgent {
fn drop(&mut self) {
// First notify the sampling thread that it's time to shut down and
// wait for it to exit.
self.state.sampling_done.notify_one();
self.sampling_thread.take().unwrap().join().unwrap();
// Wait on the recording thread as well which should terminate once
// `sampling_thread` has terminated as well.
self.recording_thread.take().unwrap().join().unwrap();
}
}
@@ -0,0 +1,77 @@
//! Adds support for profiling JIT-ed code using VTune. By default, VTune
//! support is built in to Wasmtime (configure with the `vtune` feature flag).
//! To enable it at runtime, use the `--profile=vtune` CLI flag.
//!
//! ### Profile
//!
//! ```ignore
//! vtune -run-pass-thru=--no-altstack -v -collect hotspots target/debug/wasmtime --profile=vtune test.wasm
//! ```
//!
//! Note: `vtune` is a command-line tool for VTune which must [be
//! installed](https://www.intel.com/content/www/us/en/developer/tools/oneapi/vtune-profiler.html#standalone)
//! for this to work.
use crate::prelude::*;
use crate::profiling_agent::ProfilingAgent;
use ittapi::jit::MethodLoadBuilder;
use std::sync::Mutex;
/// Interface for driving the ittapi for VTune support
struct VTuneAgent {
// Note that we use a mutex internally to serialize state updates since multiple threads may be
// sharing this agent.
state: Mutex<State>,
}
/// Interface for driving vtune
#[derive(Default)]
struct State {
vtune: ittapi::jit::Jit,
}
/// Initialize a VTuneAgent.
pub fn new() -> Result<Box<dyn ProfilingAgent>> {
Ok(Box::new(VTuneAgent {
state: Mutex::new(State {
vtune: Default::default(),
}),
}))
}
impl Drop for VTuneAgent {
fn drop(&mut self) {
self.state.lock().unwrap().event_shutdown();
}
}
impl State {
/// Notify vtune about a newly tracked code region.
fn notify_code(&mut self, module_name: &str, method_name: &str, code: &[u8]) {
self.vtune
.load_method(
MethodLoadBuilder::new(method_name.to_owned(), code.as_ptr(), code.len())
.class_file_name(module_name.to_owned())
.source_file_name("<unknown wasm filename>".to_owned()),
)
.unwrap();
}
/// Shutdown module
fn event_shutdown(&mut self) {
// Ignore if something went wrong.
let _ = self.vtune.shutdown();
}
}
impl ProfilingAgent for VTuneAgent {
fn register_function(&self, name: &str, code: &[u8]) {
self.state.lock().unwrap().register_function(name, code);
}
}
impl State {
fn register_function(&mut self, name: &str, code: &[u8]) {
self.notify_code("wasmtime", name, code);
}
}
+204
View File
@@ -0,0 +1,204 @@
// Wasmtime's runtime has lots of fiddly bits where we're doing operations like
// casting between wasm i32/i64 and host `usize` values. There's also in general
// just lots of pieces of low-level manipulation of memory and internals of VM
// runtime state. To help keep all the integer casts correct be a bit more
// strict than the default settings to help weed out bugs ahead of time.
//
// This inevitably leads to wordier code than might otherwise be used because,
// for example, `u64 as usize` is warned against and will be an error on CI.
// This happens pretty frequently and needs to be replaced with `val.try_into()`
// or `usize::try_from(val)` where the error is handled. In some cases the
// correct thing to do is to `.unwrap()` the error to indicate a fatal mistake,
// but in some cases the correct thing is to propagate the error.
//
// Some niche cases that explicitly want truncation are recommended to have a
// function along the lines of
//
// #[allow(clippy::cast_possible_truncation)]
// fn truncate_i32_to_i8(a: i32) -> i8 { a as i8 }
//
// as this explicitly indicates the intent of truncation is desired. Other
// locations should use fallible conversions.
//
// If performance is absolutely critical then it's recommended to use `#[allow]`
// with a comment indicating why performance is critical as well as a short
// explanation of why truncation shouldn't be happening at runtime. This
// situation should be pretty rare though.
#![warn(clippy::cast_possible_truncation)]
use crate::prelude::*;
use core::marker;
use core::pin::Pin;
use core::task::{Context, Poll};
mod bug;
#[macro_use]
pub(crate) mod func;
pub(crate) mod code;
pub(crate) mod code_memory;
#[cfg(feature = "debug")]
pub(crate) mod debug;
pub(crate) mod exception;
pub(crate) mod externals;
#[cfg(feature = "async")]
pub(crate) mod fiber;
pub(crate) mod gc;
pub(crate) mod instance;
pub(crate) mod instantiate;
pub(crate) mod limits;
pub(crate) mod linker;
pub(crate) mod memory;
pub(crate) mod module;
#[cfg(feature = "debug-builtins")]
pub(crate) mod native_debug;
pub(crate) mod resources;
pub(crate) mod store;
pub(crate) mod trampoline;
pub(crate) mod trap;
#[cfg(feature = "component-model-async")]
pub(crate) mod try_mutex;
pub(crate) mod type_registry;
pub(crate) mod types;
pub(crate) mod v128;
pub(crate) mod values;
pub(crate) mod vm;
#[cfg(feature = "component-model")]
pub mod component;
cfg_select! {
miri => {
// no extensions on miri
}
not(feature = "std") => {
// no extensions on no-std
}
unix => {
pub mod unix;
}
windows => {
pub mod windows;
}
_ => {
// ... unknown os!
}
}
pub use bug::WasmtimeBug;
pub(crate) use bug::{bail_bug, bug};
pub use code_memory::CodeMemory;
#[cfg(feature = "debug")]
pub use debug::*;
pub use exception::*;
pub use externals::*;
pub use func::*;
pub use gc::*;
pub use instance::{Instance, InstancePre};
pub use instantiate::CompiledModule;
pub use limits::*;
pub use linker::*;
pub use memory::*;
pub use module::{Module, ModuleExport, ModuleFunction};
pub use resources::*;
#[cfg(all(feature = "async", feature = "call-hook"))]
pub use store::CallHookHandler;
pub use store::{
AsContext, AsContextMut, CallHook, Store, StoreContext, StoreContextMut, UpdateDeadline,
};
pub use trap::*;
pub use types::*;
pub use v128::V128;
pub use values::*;
#[cfg(feature = "pooling-allocator")]
pub use vm::{PoolConcurrencyLimitError, PoolingAllocatorMetrics};
#[cfg(feature = "profiling")]
mod profiling;
#[cfg(feature = "profiling")]
pub use profiling::GuestProfiler;
#[cfg(feature = "async")]
pub(crate) mod stack;
#[cfg(feature = "async")]
pub use stack::*;
#[cfg(feature = "coredump")]
mod coredump;
#[cfg(feature = "coredump")]
pub use coredump::*;
#[cfg(feature = "wave")]
mod wave;
/// Helper method to create a future trait object from the future `F` provided.
///
/// This requires that the output of `F` is a result where the error can be
/// created from `OutOfMemory`. This will handle OOM when allocation of `F` on
/// the heap fails.
fn box_future<'a, F, T, E>(future: F) -> Pin<Box<dyn Future<Output = Result<T, E>> + Send + 'a>>
where
F: Future<Output = Result<T, E>> + Send + 'a,
T: 'a,
E: From<OutOfMemory> + 'a,
{
if let Ok(future) = try_new::<Box<F>>(future) {
return Pin::from(future);
}
// Use a custom guaranteed-zero-size struct to implement a future that
// returns an OOM error which satisfies the type signature of this function.
struct OomFuture<F, T, E>(marker::PhantomData<fn() -> (T, F, E)>);
impl<F, T, E> Future for OomFuture<F, T, E>
where
E: From<OutOfMemory>,
{
type Output = Result<T, E>;
fn poll(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<Self::Output> {
Poll::Ready(Err(OutOfMemory::new(size_of::<F>()).into()))
}
}
// Zero-size allocations don't actually allocate memory with a `Box`, so
// it's ok to use the standard `Box::pin` here and not worry about OOM.
let future = OomFuture::<F, T, E>(marker::PhantomData);
assert_eq!(size_of_val(&future), 0);
Box::pin(future)
}
fn _assertions_runtime() {
use crate::_assert_send_and_sync;
#[cfg(feature = "async")]
fn _assert_send<T: Send>(_t: T) {}
_assert_send_and_sync::<Caller<'_, ()>>();
_assert_send_and_sync::<ExternRef>();
_assert_send_and_sync::<(Func, TypedFunc<(), ()>, Global, Table, Memory)>();
_assert_send_and_sync::<Instance>();
_assert_send_and_sync::<InstancePre<()>>();
_assert_send_and_sync::<InstancePre<*mut u8>>();
_assert_send_and_sync::<Linker<()>>();
_assert_send_and_sync::<Linker<*mut u8>>();
_assert_send_and_sync::<Module>();
_assert_send_and_sync::<Store<()>>();
_assert_send_and_sync::<StoreContext<'_, ()>>();
_assert_send_and_sync::<StoreContextMut<'_, ()>>();
#[cfg(feature = "async")]
fn _call_async(s: &mut Store<()>, f: Func) {
_assert_send(f.call_async(&mut *s, &[], &mut []))
}
#[cfg(feature = "async")]
fn _typed_call_async(s: &mut Store<()>, f: TypedFunc<(), ()>) {
_assert_send(f.call_async(&mut *s, ()))
}
#[cfg(feature = "async")]
fn _instantiate_async(s: &mut Store<()>, m: &Module) {
_assert_send(Instance::new_async(s, m, &[]))
}
}
+99
View File
@@ -0,0 +1,99 @@
use crate::prelude::*;
use core::fmt;
/// Helper macro to `bail!` with a `WasmtimeBug` instance.
///
/// This is used in locations in lieu of panicking. The general idea when using
/// this is:
///
/// * The invocation of this cannot be refactored to be statically ruled out.
/// * The invocation cannot be reasoned about locally to determine that this is
/// dynamically not reachable.
///
/// This macro serves as an alternative to `panic!` which returns a
/// `WasmtimeBug` instead of panicking. This means that a trap is raised in the
/// guest and a store is poisoned for example (w.r.t. components). This
/// primarily serves as a DoS mitigation mechanism where if the panic were
/// actually hit at runtime it would be a CVE. The worst-case scenario of
/// raising a trap is that a guest is erroneously terminated, which is a much
/// more controlled failure mode.
///
/// The general guideline for using this is "don't" if you can avoid it because
/// it's best to either statically rule out these cases or make it verifiable
/// locally that it can't be hit. When this isn't possible, however, this is a
/// good alternative to panicking in the case that this is actually executed at
/// runtime.
macro_rules! bail_bug {
($($arg:tt)*) => {{
$crate::bail!($crate::bug!($($arg)*))
}}
}
pub(crate) use bail_bug;
/// Similar to `bail_bug`, but just returns a `WasmtimeBug`.
macro_rules! bug {
($($arg:tt)*) => {{
// Minimize argument passing to the `new` function by placing the
// file/line in a static which is passed by reference to just pass a
// single extra pointer argument.
static POS: (&'static str, u32) = (file!(), line!());
crate::WasmtimeBug::new(format_args!($($arg)*), &POS)
}}
}
pub(crate) use bug;
/// Error which indicates a bug in Wasmtime.
///
/// This structure is used internally with Wasmtime for situations which are a
/// bug in Wasmtime but not serious enough to raise a panic and unwind the
/// current thread of execution. In these situations this is still considered a
/// bug and a trap is raised to terminate a guest, and it's considered something
/// that needs to be fixed in Wasmtime.
#[derive(Debug)]
pub struct WasmtimeBug {
message: String,
file: &'static str,
line: u32,
}
impl WasmtimeBug {
#[cold]
pub(crate) fn new(message: fmt::Arguments<'_>, pos: &'static (&'static str, u32)) -> Self {
if cfg!(debug_assertions) {
panic!("BUG: {message}");
}
Self {
message: message.to_string(),
file: pos.0,
line: pos.1,
}
}
}
impl fmt::Display for WasmtimeBug {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"\
BUG: {}
location: {}:{}
version: {}
This is a bug in Wasmtime that was not thought to be reachable. A panic is
not happening to avoid taking down the thread, but this trap is being injected
into WebAssembly guests to prevent their execution. The Wasmtime project would
appreciate a bug report with a copy of this message to help investigate what
happened. If you're able to provide a reproduction, that would be appreciated,
but it is not necessary to do so and instead indicating that this is reachable
is a sufficiently actionable bug for maintainers to investigate.
",
self.message,
self.file,
self.line,
env!("CARGO_PKG_VERSION"),
)
}
}
impl core::error::Error for WasmtimeBug {}
+438
View File
@@ -0,0 +1,438 @@
use crate::Engine;
use crate::Module;
use crate::Result;
use crate::module::ModuleRegistry;
use crate::vm::ModuleMemoryImageSource;
use crate::{code_memory::CodeMemory, type_registry::TypeCollection};
#[cfg(feature = "debug")]
use alloc::boxed::Box;
use alloc::sync::Arc;
use core::ops::{Add, Range, Sub};
use wasmtime_core::error::OutOfMemory;
#[cfg(feature = "component-model")]
use wasmtime_environ::component::ComponentTypes;
use wasmtime_environ::{FuncKey, ModuleTypes, StaticModuleIndex};
macro_rules! define_pc_kind {
($ty:ident) => {
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct $ty(usize);
impl Add<usize> for $ty {
type Output = $ty;
fn add(self, other: usize) -> $ty {
$ty(self.0.wrapping_add(other))
}
}
impl Sub<usize> for $ty {
type Output = $ty;
fn sub(self, other: usize) -> $ty {
$ty(self.0.wrapping_sub(other))
}
}
impl $ty {
/// Is the given PC within this range? Give the relative
/// offset if so.
pub fn offset_of(range: Range<$ty>, pc: usize) -> Option<usize> {
if pc >= range.start.0 && pc < range.end.0 {
Some(pc.wrapping_sub(range.start.0))
} else {
None
}
}
/// Get the raw PC value.
pub fn raw(&self) -> usize {
self.0
}
}
};
}
// An address in "engine code": the original (canonical) copy of code
// for a compiled module.
//
// See [`EngineCode`] for more details.
define_pc_kind!(EngineCodePC);
// An address in "store code": the copy of code used when executing
// instances of a module in a given store.
//
// May or may not be the same as the engine code address -- a store
// is allowed to make a private copy of engine code as its store
// code.
//
// See [`StoreCode`] for more details.
define_pc_kind!(StoreCodePC);
impl StoreCodePC {
/// Construct a StoreCodePC for search purposes from a raw PC
/// observed during execution.
pub fn from_raw(pc: usize) -> StoreCodePC {
StoreCodePC(pc)
}
}
/// Metadata about, and original machine code for, a loaded compiled
/// artifact in memory which is ready for Wasmtime to execute.
///
/// This structure is used in both `Module` and `Component`. For components it's
/// notably shared amongst the core wasm modules within a component and the
/// component itself. For core wasm modules this is uniquely owned within a
/// `Module`.
///
/// The `EngineCode` is "static", like a module: there is one copy per
/// unit of code. Instantiation of a module containing an `EngineCode`
/// into a `Store` produces a `StoreCode`. The latter is the owner of
/// the actual machine code that runs. This machine code *may* be a
/// read-only-shared copy of the original, or may be a private copy
/// that we have patched. The latter is useful for some kinds of
/// debugging/instrumentation, which is always scoped per-Store.
///
/// The `EngineCode` does *not* expose its underlying `CodeMemory`, to
/// guard against the original-copy being used in an inappropriate
/// (store-specific) context. Instead, it has accessors that provide
/// the instance-agnostic metadata from the `CodeMemory`, and
/// instance-specific data and code pointers can be obtained from the
/// `StoreCode`.
pub struct EngineCode {
/// Actual underlying code which is executable and contains other
/// compiled information.
///
/// Note the `Arc` here is used to share this with `CompiledModule` and the
/// global module registry of traps. While probably not strictly necessary
/// and could be avoided with some refactorings is a hopefully a relatively
/// minor `Arc` for now.
///
/// As noted above, this is the *original* copy of the code, and
/// may be used directly, but may also be deep-cloned to a private
/// copy in a `StoreCode`.
original_code: Arc<CodeMemory>,
/// Registered shared signature for the loaded object.
///
/// Note that this type has a significant destructor which unregisters
/// signatures within the `Engine` it was originally tied to, and this ends
/// up corresponding to the lifetime of a `Component` or `Module`.
signatures: TypeCollection,
/// Type information for the loaded object.
///
/// This is either a `ModuleTypes` or a `ComponentTypes` depending on the
/// top-level creator of this code.
types: Types,
}
impl EngineCode {
pub fn new(
mmap: Arc<CodeMemory>,
signatures: TypeCollection,
types: Types,
) -> Result<EngineCode, OutOfMemory> {
// The corresponding unregister for this is below in `Drop for
// EngineCode`.
crate::module::register_code(&mmap, mmap.raw_addr_range())?;
Ok(EngineCode {
original_code: mmap,
signatures,
types,
})
}
#[cfg(feature = "component-model")]
pub fn types(&self) -> &Types {
&self.types
}
pub fn module_types(&self) -> &ModuleTypes {
self.types.module_types()
}
pub fn signatures(&self) -> &TypeCollection {
&self.signatures
}
pub fn text_size(&self) -> usize {
self.original_code.text().len()
}
/// Give the range of engine-code PCs in this code.
pub fn text_range(&self) -> Range<EngineCodePC> {
let raw = self.original_code.raw_addr_range();
EngineCodePC(raw.start)..EngineCodePC(raw.end)
}
// For all accessors to code object sections below: we can use the
// original (uncloned) code image for these: they are just slices
// of bytes that we interpret independent of location.
//
// We hide the raw `CodeMemory` in `original_code` (*not* a public
// field) because we do not want the EngineCode copy used
// inadvertently for execution of Wasm functions.
//
// We thus *cannot* add an accessor below for anything that
// actually interprets the location of the bytes as a code
// pointer to a Wasm function.
//
// Note that Wasm-to-array trampolines, in contrast, are fair game
// to execute directly from the EngineCode: these are not specific
// to Wasm functions, but instead call builtins, and we never
// patch trampolines in StoreCode, so we can freely mix in
// EngineCode variants.
/// Get the Wasm-to-array trampoline for the given raw range in
/// the text segment.
pub(crate) fn raw_wasm_to_array_trampoline_data(&self, range: Range<usize>) -> &[u8] {
&self.original_code.text()[range]
}
/// Provide the address-map data for this EngineCode.
pub fn address_map_data(&self) -> &[u8] {
// We can use the original (uncloned) code image for this: it
// is just a slice of bytes that we interpret independent of
// location.
self.original_code.address_map_data()
}
/// Provide the stack-map data for this EngineCode.
pub fn stack_map_data(&self) -> &[u8] {
// We can use the original (uncloned) code image for this: it
// is just a slice of bytes that we interpret independent of
// location.
self.original_code.stack_map_data()
}
/// Returns the encoded exception-tables section to pass to
/// `wasmtime_unwinder::ExceptionTable::parse`.
pub fn exception_tables(&self) -> &[u8] {
// We can use the original (uncloned) code image for this: it
// is just a slice of bytes that we interpret independent of
// location.
self.original_code.exception_tables()
}
/// Returns the data in the `ELF_NAME_DATA` section.
#[inline]
pub fn func_name_data(&self) -> &[u8] {
self.original_code.func_name_data()
}
/// Returns the contents of the `ELF_WASMTIME_DWARF` section.
#[inline]
pub fn wasm_dwarf(&self) -> &[u8] {
self.original_code.wasm_dwarf()
}
/// Returns the original Wasm bytecode section if preserved in the
/// compiled artifact.
#[inline]
pub fn wasm_bytecode_for_module(&self, module: StaticModuleIndex) -> Option<&[u8]> {
self.original_code.wasm_bytecode_for_module(module)
}
/// Returns the raw image as bytes (in our internal image format).
pub fn image(&self) -> &[u8] {
&self.original_code.mmap()[..]
}
pub fn text(&self) -> &[u8] {
&self.original_code.text()
}
/// Returns the concatenated list of all data associated with this wasm
/// module.
///
/// This is used for initialization of memories and all data ranges stored
/// in a `Module` are relative to the slice returned here.
#[inline]
pub fn wasm_data(&self) -> &[u8] {
self.original_code.wasm_data()
}
pub(crate) fn module_memory_image_source(&self) -> &Arc<impl ModuleMemoryImageSource> {
&self.original_code
}
/// See [`CodeMemory::frame_table`].
#[cfg(feature = "debug")]
pub(crate) fn frame_table<'a>(&'a self) -> Option<wasmtime_environ::FrameTable<'a>> {
self.original_code.frame_table()
}
}
impl Drop for EngineCode {
fn drop(&mut self) {
crate::module::unregister_code(self.original_code.raw_addr_range());
}
}
pub enum Types {
Module(ModuleTypes),
#[cfg(feature = "component-model")]
Component(Arc<ComponentTypes>),
}
impl Types {
fn module_types(&self) -> &ModuleTypes {
match self {
Types::Module(m) => m,
#[cfg(feature = "component-model")]
Types::Component(c) => c.module_types(),
}
}
}
impl From<ModuleTypes> for Types {
fn from(types: ModuleTypes) -> Types {
Types::Module(types)
}
}
#[cfg(feature = "component-model")]
impl From<Arc<ComponentTypes>> for Types {
fn from(types: Arc<ComponentTypes>) -> Types {
Types::Component(types)
}
}
/// A `Store`-local instance of code.
///
/// This type encapsulates executable code within the context of
/// instantiation in a single store. It may be an unmodified pointer
/// to the read-only original code, or it may be locally patched for
/// this store due to debugging or instrumentation settings.
///
/// Most things that the runtime will want to do with a module's image
/// will require the `EngineCode` -- all metadata lives there. The
/// `StoreCode` is solely responsible for the executable machine code.
///
/// This type is designed to be uniquely owned, unlike `EngineCode`
/// above. The runtime data structures will have scattered `Arc`s to
/// the `EngineCode` but only one
pub struct StoreCode(StoreCodeStorage);
enum StoreCodeStorage {
Shared(Arc<CodeMemory>),
/// Private copy of the given code memory.
///
/// This is the only reference to this CodeMemory. The StoreCode
/// is owned directly by the Store's ModuleRegistry.
#[cfg(feature = "debug")]
Private(Box<CodeMemory>),
}
impl StoreCode {
/// Create a new StoreCode for a given Store from a given
/// EngineCode, given the engine's settings (tunables).
pub fn new(engine: &Engine, engine_code: &Arc<EngineCode>) -> Result<Self> {
// Enabled guest-debugging causes us to allocate private
// copies of code in every store, to allow individual enabling
// of breakpoints (by code patching) independently in each
// one.
#[cfg(feature = "debug")]
let code = if engine.tunables().debug_guest {
// TODO(#12104): we should be able to clone only `.text`;
// this clones the whole image.
let mut private_copy = engine_code.original_code.deep_clone(engine)?;
private_copy.publish()?;
crate::module::register_code(
&engine_code.original_code,
private_copy.raw_addr_range(),
)?;
StoreCodeStorage::Private(Box::new(private_copy))
} else {
StoreCodeStorage::Shared(engine_code.original_code.clone())
};
#[cfg(not(feature = "debug"))]
let code = StoreCodeStorage::Shared(engine_code.original_code.clone());
// Avoid unused-variable warning in build without debugging
// support.
let _ = engine;
Ok(StoreCode(code))
}
/// Provide the underlying CodeMemory.
pub fn code_memory(&self) -> &CodeMemory {
match &self.0 {
StoreCodeStorage::Shared(m) => m,
#[cfg(feature = "debug")]
StoreCodeStorage::Private(m) => m,
}
}
/// Provide a mutable reference to a CodeMemory that is privately
/// owned only by this StoreCode.
#[cfg(feature = "debug")]
pub fn code_memory_mut(&mut self) -> Option<&mut CodeMemory> {
match &mut self.0 {
StoreCodeStorage::Shared(_) => None,
StoreCodeStorage::Private(m) => Some(m),
}
}
/// Provide the address range for this StoreCode.
pub fn text_range(&self) -> Range<StoreCodePC> {
let raw = self.code_memory().raw_addr_range();
StoreCodePC(raw.start)..StoreCodePC(raw.end)
}
/// Provide the actual text segment for this StoreCode.
pub fn text(&self) -> &[u8] {
self.code_memory().text()
}
}
impl Drop for StoreCode {
fn drop(&mut self) {
match &self.0 {
StoreCodeStorage::Shared(_) => {
// Drop impl for EngineCode will de-register (see
// above).
}
#[cfg(feature = "debug")]
StoreCodeStorage::Private(mem) => {
crate::module::unregister_code(mem.raw_addr_range());
}
}
}
}
/// A wrapper for a Module together with a StoreCode. Allows fetching
/// code pointers, ready to call.
pub struct ModuleWithCode<'a> {
module: &'a Module,
store_code: &'a StoreCode,
}
impl<'a> ModuleWithCode<'a> {
/// Find the StoreCode in a given store for a module and wrap it
/// up with that module, ready to compute code pointers.
pub fn in_store(
registry: &'a ModuleRegistry,
module: &'a Module,
) -> Option<ModuleWithCode<'a>> {
let store_code = registry.store_code(module.engine_code())?;
Some(ModuleWithCode { module, store_code })
}
pub(crate) fn from_raw(module: &'a Module, store_code: &'a StoreCode) -> ModuleWithCode<'a> {
ModuleWithCode { module, store_code }
}
/// Provide the Module wrapped in this tuple.
pub fn module(&self) -> &'a Module {
self.module
}
/// Returns the slice in the text section of the function that
/// `key` points to.
#[inline]
pub fn function(&self, key: FuncKey) -> &[u8] {
let range = self.module.compiled_module().function_range(key);
&self.store_code.text()[range]
}
}
+715
View File
@@ -0,0 +1,715 @@
//! Memory management for executable code.
use crate::Engine;
use crate::prelude::*;
use crate::runtime::vm::MmapVec;
use alloc::sync::Arc;
use core::ops::Range;
use object::read::elf::SectionTable;
use object::{LittleEndian, SectionIndex, U32};
use object::{
elf::{FileHeader64, SectionHeader64},
endian::Endianness,
read::elf::{FileHeader as _, SectionHeader as _},
};
use wasmtime_environ::StaticModuleIndex;
use wasmtime_environ::{CompiledTrap, lookup_trap_code, obj};
use wasmtime_unwinder::ExceptionTable;
/// Management of executable memory within a `MmapVec`
///
/// This type consumes ownership of a region of memory and will manage the
/// executable permissions of the contained JIT code as necessary.
pub struct CodeMemory {
mmap: MmapVec,
#[cfg(has_host_compiler_backend)]
unwind_registration: Option<crate::runtime::vm::UnwindRegistration>,
#[cfg(feature = "debug-builtins")]
debug_registration: Option<crate::runtime::vm::GdbJitImageRegistration>,
published: bool,
registered: bool,
enable_branch_protection: bool,
needs_executable: bool,
#[cfg(feature = "debug-builtins")]
has_native_debug_info: bool,
custom_code_memory: Option<Arc<dyn CustomCodeMemory>>,
// Ranges within `self.mmap` of where the particular sections lie.
text: Range<usize>,
unwind: Range<usize>,
trap_data: Range<usize>,
wasm_data: Range<usize>,
address_map_data: Range<usize>,
stack_map_data: Range<usize>,
exception_data: Range<usize>,
frame_tables_data: Range<usize>,
func_name_data: Range<usize>,
info_data: Range<usize>,
wasm_dwarf: Range<usize>,
wasm_bytecode: Range<usize>,
wasm_bytecode_ends: Range<usize>,
}
impl Drop for CodeMemory {
fn drop(&mut self) {
// If there is a custom code memory handler, restore the
// original (non-executable) state of the memory.
//
// We do this rather than invoking `unpublish()` because we
// want to skip the mprotect() if we natively own the mmap and
// are going to munmap soon anyway.
if let Some(mem) = self.custom_code_memory.as_ref() {
if self.published && self.needs_executable {
let text = self.text();
mem.unpublish_executable(text.as_ptr(), text.len())
.expect("Executable memory unpublish failed");
}
}
// Drop the registrations before `self.mmap` since they (implicitly) refer to it.
#[cfg(has_host_compiler_backend)]
let _ = self.unwind_registration.take();
#[cfg(feature = "debug-builtins")]
let _ = self.debug_registration.take();
}
}
fn _assert() {
fn _assert_send_sync<T: Send + Sync>() {}
_assert_send_sync::<CodeMemory>();
}
/// Interface implemented by an embedder to provide custom
/// implementations of code-memory protection and execute permissions.
pub trait CustomCodeMemory: Send + Sync {
/// The minimal alignment granularity for an address region that
/// can be made executable.
///
/// Wasmtime does not assume the system page size for this because
/// custom code-memory protection can be used when all other uses
/// of virtual memory are disabled.
fn required_alignment(&self) -> usize;
/// Publish a region of memory as executable.
///
/// This should update permissions from the default RW
/// (readable/writable but not executable) to RX
/// (readable/executable but not writable), enforcing W^X
/// discipline.
///
/// If the platform requires any data/instruction coherence
/// action, that should be performed as part of this hook as well.
///
/// `ptr` and `ptr.offset(len)` are guaranteed to be aligned as
/// per `required_alignment()`.
fn publish_executable(&self, ptr: *const u8, len: usize) -> crate::Result<()>;
/// Unpublish a region of memory.
///
/// This should perform the opposite effect of `make_executable`,
/// switching a range of memory back from RX (readable/executable)
/// to RW (readable/writable). It is guaranteed that no code is
/// running anymore from this region.
///
/// `ptr` and `ptr.offset(len)` are guaranteed to be aligned as
/// per `required_alignment()`.
fn unpublish_executable(&self, ptr: *const u8, len: usize) -> crate::Result<()>;
}
impl CodeMemory {
/// Creates a new `CodeMemory` by taking ownership of the provided
/// `MmapVec`.
///
/// The returned `CodeMemory` manages the internal `MmapVec` and the
/// `publish` method is used to actually make the memory executable.
pub fn new(engine: &Engine, mmap: MmapVec) -> Result<Self> {
let mmap_data = &*mmap;
let header = FileHeader64::<Endianness>::parse(mmap_data)
.map_err(obj::ObjectCrateErrorWrapper)
.context("failed to parse precompiled artifact as an ELF")?;
let endian = header
.endian()
.context("failed to parse header endianness")?;
let section_headers = header
.section_headers(endian, mmap_data)
.context("failed to parse section headers")?;
let strings = header
.section_strings(endian, mmap_data, section_headers)
.context("failed to parse strings table")?;
let sections = header
.sections(endian, mmap_data)
.context("failed to parse sections table")?;
let mut text = 0..0;
let mut unwind = 0..0;
let mut enable_branch_protection = None;
let mut needs_executable = true;
#[cfg(feature = "debug-builtins")]
let mut has_native_debug_info = false;
let mut trap_data = 0..0;
let mut exception_data = 0..0;
let mut frame_tables_data = 0..0;
let mut wasm_data = 0..0;
let mut address_map_data = 0..0;
let mut stack_map_data = 0..0;
let mut func_name_data = 0..0;
let mut info_data = 0..0;
let mut wasm_dwarf = 0..0;
let mut wasm_bytecode = 0..0;
let mut wasm_bytecode_ends = 0..0;
for section_header in sections.iter() {
let data = section_header
.data(endian, mmap_data)
.map_err(obj::ObjectCrateErrorWrapper)?;
let name = section_name(endian, strings, section_header)?;
let range = subslice_range(data, &mmap);
// Double-check that sections are all aligned properly.
let section_align = usize::try_from(section_header.sh_addralign(endian))?;
if section_align != 0 && data.len() != 0 {
let section_offset = data.as_ptr().addr() - mmap.as_ptr().addr();
ensure!(
section_offset % section_align == 0,
"section {name:?} isn't aligned to {section_align:#x}",
);
}
// Check that we don't have any relocations, which would make
// loading precompiled Wasm modules slower and also force them to
// get paged into memory from disk.
//
// We avoid using things like Cranelift's `floor`, `ceil`,
// etc... operators in the Wasm-to-CLIF translator specifically to
// avoid having to do any relocations here. This also ensures that
// all builtins use the same trampoline mechanism.
//
// We do, however, allow relocations in `.debug_*` DWARF sections.
if let Some(target_section) = reloc_section_target(&sections, section_header, endian)? {
let target_name = section_name(endian, strings, target_section)?;
ensure!(
target_name.starts_with(".debug_"),
"section {target_name:?} has unexpected relocations \
(defined in section {name:?})",
);
}
match name {
obj::ELF_WASM_BTI => match data.len() {
1 => enable_branch_protection = Some(data[0] != 0),
_ => bail!("invalid {name:?} section"),
},
".text" => {
text = range;
if section_header
.sh_flags(endian)
.contains(obj::SH_WASMTIME_NOT_EXECUTED)
{
needs_executable = false;
}
}
#[cfg(has_host_compiler_backend)]
crate::runtime::vm::UnwindRegistration::SECTION_NAME => unwind = range,
obj::ELF_WASM_DATA => wasm_data = range,
obj::ELF_WASMTIME_ADDRMAP => address_map_data = range,
obj::ELF_WASMTIME_STACK_MAP => stack_map_data = range,
obj::ELF_WASMTIME_TRAPS => trap_data = range,
obj::ELF_WASMTIME_EXCEPTIONS => exception_data = range,
obj::ELF_WASMTIME_FRAMES => frame_tables_data = range,
obj::ELF_NAME_DATA => func_name_data = range,
obj::ELF_WASMTIME_INFO => info_data = range,
obj::ELF_WASMTIME_DWARF => wasm_dwarf = range,
obj::ELF_WASMTIME_WASM_BYTECODE => wasm_bytecode = range,
obj::ELF_WASMTIME_WASM_BYTECODE_ENDS => wasm_bytecode_ends = range,
#[cfg(feature = "debug-builtins")]
".debug_info" => has_native_debug_info = true,
// These sections are expected, but we do not need to retain any
// info about them.
"" | ".symtab" | ".strtab" | ".shstrtab" | ".xdata" | obj::ELF_WASM_ENGINE => {
log::debug!("ignoring section {name:?}")
}
_ if name.starts_with(".debug_") || name.starts_with(".rela.debug_") => {
log::debug!("ignoring debug section {name:?}")
}
_ => bail!("unexpected section {name:?} in Wasm compilation artifact"),
}
}
// Silence unused `mut` warning.
#[cfg(not(has_host_compiler_backend))]
let _ = &mut unwind;
// Ensure that the exception table is well-formed. This parser
// construction is cheap: it reads the header and validates
// ranges but nothing else. We do this only in debug-assertion
// builds because we otherwise require for safety that the
// compiled artifact is as-produced-by this version of
// Wasmtime, and we should always produce a correct exception
// table (i.e., we are not expecting untrusted data here).
if cfg!(debug_assertions) {
let _ = ExceptionTable::parse(&mmap[exception_data.clone()])?;
}
Ok(Self {
mmap,
#[cfg(has_host_compiler_backend)]
unwind_registration: None,
#[cfg(feature = "debug-builtins")]
debug_registration: None,
published: false,
registered: false,
enable_branch_protection: enable_branch_protection
.ok_or_else(|| format_err!("missing `{}` section", obj::ELF_WASM_BTI))?,
needs_executable,
#[cfg(feature = "debug-builtins")]
has_native_debug_info,
custom_code_memory: engine.custom_code_memory().cloned(),
text,
unwind,
trap_data,
address_map_data,
stack_map_data,
exception_data,
frame_tables_data,
func_name_data,
wasm_dwarf,
info_data,
wasm_data,
wasm_bytecode,
wasm_bytecode_ends,
})
}
/// Returns a reference to the underlying `MmapVec` this memory owns.
#[inline]
pub fn mmap(&self) -> &MmapVec {
&self.mmap
}
/// Returns the contents of the text section of the ELF executable this
/// represents.
#[inline]
pub fn text(&self) -> &[u8] {
&self.mmap[self.text.clone()]
}
/// Returns the contents of the `ELF_WASMTIME_DWARF` section.
#[inline]
pub fn wasm_dwarf(&self) -> &[u8] {
&self.mmap[self.wasm_dwarf.clone()]
}
/// Returns the data in the `ELF_NAME_DATA` section.
#[inline]
pub fn func_name_data(&self) -> &[u8] {
&self.mmap[self.func_name_data.clone()]
}
/// Returns the concatenated list of all data associated with this wasm
/// module.
///
/// This is used for initialization of memories and all data ranges stored
/// in a `Module` are relative to the slice returned here.
#[inline]
pub fn wasm_data(&self) -> &[u8] {
&self.mmap[self.wasm_data.clone()]
}
/// Returns the encoded address map section used to pass to
/// `wasmtime_environ::lookup_file_pos`.
#[inline]
pub fn address_map_data(&self) -> &[u8] {
&self.mmap[self.address_map_data.clone()]
}
/// Returns the encoded stack map section used to pass to
/// `wasmtime_environ::StackMap::lookup`.
pub fn stack_map_data(&self) -> &[u8] {
&self.mmap[self.stack_map_data.clone()]
}
/// Returns the encoded exception-tables section to pass to
/// `wasmtime_unwinder::ExceptionTable::parse`.
pub fn exception_tables(&self) -> &[u8] {
&self.mmap[self.exception_data.clone()]
}
/// Returns the encoded frame-tables section to pass to
/// `wasmtime_environ::FrameTable::parse`.
pub fn frame_tables(&self) -> &[u8] {
&self.mmap[self.frame_tables_data.clone()]
}
/// Returns the concatenated Wasm bytecode section, or an empty slice if
/// the artifact was not compiled with `guest-debug` enabled.
pub fn wasm_bytecode(&self) -> &[u8] {
&self.mmap[self.wasm_bytecode.clone()]
}
/// Returns the Wasm bytecode section end-offset array.
pub fn wasm_bytecode_ends(&self) -> &[u8] {
&self.mmap[self.wasm_bytecode_ends.clone()]
}
/// Returns the contents of the `ELF_WASMTIME_INFO` section, or an empty
/// slice if it wasn't found.
#[inline]
pub fn wasmtime_info(&self) -> &[u8] {
&self.mmap[self.info_data.clone()]
}
/// Returns the contents of the `ELF_WASMTIME_TRAPS` section, or an empty
/// slice if it wasn't found.
#[inline]
pub fn trap_data(&self) -> &[u8] {
&self.mmap[self.trap_data.clone()]
}
/// Returns the Wasm bytecode section end-offset for a given core
/// module, or `None` if no bytecode is present.
///
/// # Panics
///
/// Panics if index is out-of-range.
fn wasm_bytecode_end_for_module(&self, index: StaticModuleIndex) -> Option<usize> {
if self.wasm_bytecode_ends().is_empty() {
return None;
}
let ends = self.wasm_bytecode_ends();
let count = ends.len() / core::mem::size_of::<u32>();
let (ends, _) = object::slice_from_bytes::<U32<LittleEndian>>(ends, count)
.expect("Invalid alignment of `ends` section");
let index = usize::try_from(index.as_u32()).unwrap();
Some(usize::try_from(ends[index].get(LittleEndian)).unwrap())
}
/// Returns the Wasm bytecode for the a core module in this
/// artifact, or `None` if bytecode was not preserved.
pub(crate) fn wasm_bytecode_for_module(&self, index: StaticModuleIndex) -> Option<&[u8]> {
let start = if index.as_u32() == 0 {
0
} else {
self.wasm_bytecode_end_for_module(StaticModuleIndex::from_u32(index.as_u32() - 1))?
};
let end = self.wasm_bytecode_end_for_module(index)?;
Some(&self.wasm_bytecode()[start..end])
}
/// Publishes the internal ELF image to be ready for execution.
///
/// This method can only be when the image is not published (its
/// default state) and will panic if called when already
/// published. This will parse the ELF image from the original
/// `MmapVec` and do everything necessary to get it ready for
/// execution, including:
///
/// * Change page protections from read/write to read/execute.
/// * Register unwinding information with the OS
/// * Register this image with the debugger if native DWARF is present
///
/// After this function executes all JIT code should be ready to execute.
///
/// The action may be reversed by calling [`Self::unpublish`], as long
/// as that method's safety requirements are upheld.
pub fn publish(&mut self) -> Result<()> {
assert!(!self.published);
self.published = true;
if self.text().is_empty() {
return Ok(());
}
// The unsafety here comes from a few things:
//
// * We're actually updating some page protections to executable memory.
//
// * We're registering unwinding information which relies on the
// correctness of the information in the first place. This applies to
// both the actual unwinding tables as well as the validity of the
// pointers we pass in itself.
unsafe {
// Next freeze the contents of this image by making all of the
// memory readonly. Nothing after this point should ever be modified
// so commit everything. For a compiled-in-memory image this will
// mean IPIs to evict writable mappings from other cores. For
// loaded-from-disk images this shouldn't result in IPIs so long as
// there weren't any relocations because nothing should have
// otherwise written to the image at any point either.
//
// Note that if virtual memory is disabled this is skipped because
// we aren't able to make it readonly, but this is just a
// defense-in-depth measure and isn't required for correctness.
#[cfg(has_virtual_memory)]
if self.mmap.supports_virtual_memory() {
self.mmap.make_readonly(0..self.mmap.len())?;
}
// Switch the executable portion from readonly to read/execute.
if self.needs_executable {
if !self.custom_publish()? {
if !self.mmap.supports_virtual_memory() {
bail!("this target requires virtual memory to be enabled");
}
#[cfg(has_virtual_memory)]
self.mmap
.make_executable(self.text.clone(), self.enable_branch_protection)
.context("unable to make memory executable")?;
}
}
if !self.registered {
// With all our memory set up use the platform-specific
// `UnwindRegistration` implementation to inform the general
// runtime that there's unwinding information available for all
// our just-published JIT functions.
self.register_unwind_info()?;
#[cfg(feature = "debug-builtins")]
self.register_debug_image()?;
self.registered = true;
}
}
Ok(())
}
fn custom_publish(&mut self) -> Result<bool> {
if let Some(mem) = self.custom_code_memory.as_ref() {
let text = self.text();
// The text section should be aligned to
// `custom_code_memory.required_alignment()` due to a
// combination of two invariants:
//
// - MmapVec aligns its start address, even in owned-Vec mode; and
// - The text segment inside the ELF image will be aligned according
// to the platform's requirements.
let text_addr = text.as_ptr() as usize;
assert_eq!(text_addr & (mem.required_alignment() - 1), 0);
// The custom code memory handler will ensure the
// memory is executable and also handle icache
// coherence.
mem.publish_executable(text.as_ptr(), text.len())?;
Ok(true)
} else {
Ok(false)
}
}
/// "Unpublish" code memory (transition it from executable to read/writable).
///
/// This may be used to edit the code image, as long as the
/// overall size of the memory remains the same. Note the hazards
/// inherent in editing code that may have been executed: any
/// stack frames with PC still active in this code must be
/// suspended (e.g., called into a hostcall that is then invoking
/// this method, or async-yielded) and any active PC values must
/// point to valid instructions. Thus this is mostly useful for
/// patching in-place at particular sites, such as by the use of
/// Cranelift's `patchable_call` instruction.
///
/// If this fails, then the memory remains executable.
pub fn unpublish(&mut self) -> Result<()> {
assert!(self.published);
self.published = false;
if self.text().is_empty() {
return Ok(());
}
if self.custom_unpublish()? {
return Ok(());
}
if !self.mmap.supports_virtual_memory() {
bail!("this target requires virtual memory to be enabled");
}
// SAFETY: we are guaranteed by our own safety conditions that
// we have exclusive access to this code and can change its
// permissions (removing the execute bit) without causing
// problems.
#[cfg(has_virtual_memory)]
unsafe {
self.mmap.make_readwrite(0..self.mmap.len())?;
}
// Note that we do *not* unregister: we expect unpublish
// to be used for temporary edits, so we want the
// registration to "stick" after the initial publish and
// not toggle in subsequent unpublish/publish cycles.
Ok(())
}
fn custom_unpublish(&mut self) -> Result<bool> {
if let Some(mem) = self.custom_code_memory.as_ref() {
let text = self.text();
mem.unpublish_executable(text.as_ptr(), text.len())?;
Ok(true)
} else {
Ok(false)
}
}
/// Return a mutable borrow to the code, suitable for editing.
///
/// Must not be published.
///
/// # Panics
///
/// This method panics if the code has been published (and not
/// subsequently unpublished).
pub fn text_mut(&mut self) -> &mut [u8] {
assert!(!self.published);
// SAFETY: we assert !published, which means we either have
// not yet applied readonly + execute permissions, or we have
// undone that and flipped back to read-write via unpublish.
unsafe { &mut self.mmap.as_mut_slice()[self.text.clone()] }
}
unsafe fn register_unwind_info(&mut self) -> Result<()> {
if self.unwind.len() == 0 {
return Ok(());
}
#[cfg(has_host_compiler_backend)]
{
let text = self.text();
let unwind_info = &self.mmap[self.unwind.clone()];
let registration = unsafe {
crate::runtime::vm::UnwindRegistration::new(
text.as_ptr(),
unwind_info.as_ptr(),
unwind_info.len(),
)
.context("failed to create unwind info registration")?
};
self.unwind_registration = Some(registration);
return Ok(());
}
#[cfg(not(has_host_compiler_backend))]
{
bail!("should not have unwind info for non-native backend")
}
}
#[cfg(feature = "debug-builtins")]
fn register_debug_image(&mut self) -> Result<()> {
if !self.has_native_debug_info {
return Ok(());
}
// TODO-DebugInfo: we're copying the whole image here, which is pretty wasteful.
// Use the existing memory by teaching code here about relocations in DWARF sections
// and anything else necessary that is done in "create_gdbjit_image" right now.
let image = self.mmap().to_vec();
let text: &[u8] = self.text();
let bytes = crate::native_debug::create_gdbjit_image(image, (text.as_ptr(), text.len()))?;
let reg = crate::runtime::vm::GdbJitImageRegistration::register(bytes);
self.debug_registration = Some(reg);
Ok(())
}
/// Looks up the given offset within this module's text section and returns
/// the trap code associated with that instruction, if there is one.
pub fn lookup_trap_code(&self, text_offset: usize) -> Option<CompiledTrap> {
lookup_trap_code(self.trap_data(), text_offset)
}
/// Get the raw address range of this CodeMemory.
pub(crate) fn raw_addr_range(&self) -> Range<usize> {
let start = self.text().as_ptr().addr();
let end = start + self.text().len();
start..end
}
/// Create a "deep clone": a separate CodeMemory for the same code
/// that can be patched or mutated independently. Also returns a
/// "metadata and location" handle that can be registered with the
/// global module registry and used for trap metadata lookups.
#[cfg(feature = "debug")]
pub(crate) fn deep_clone(self: &Arc<Self>, engine: &Engine) -> Result<CodeMemory> {
let mmap = self.mmap.deep_clone()?;
Self::new(engine, mmap)
}
/// Obtain a frame-table parser on this module's frame state slot
/// (debug instrumentation) metadata.
#[cfg(feature = "debug")]
pub(crate) fn frame_table(&self) -> Option<wasmtime_environ::FrameTable<'_>> {
let data = self.frame_tables();
if data.is_empty() {
None
} else {
let orig_text = self.text();
Some(
wasmtime_environ::FrameTable::parse(data, orig_text)
.expect("Frame tables were validated on module load"),
)
}
}
}
fn section_name<'a>(
endian: Endianness,
strings: object::StringTable<'a>,
section_header: &SectionHeader64<Endianness>,
) -> Result<&'a str> {
let name = section_header
.name(endian, strings)
.map_err(obj::ObjectCrateErrorWrapper)?;
Ok(str::from_utf8(name).context("invalid section name in Wasm compilation artifact")?)
}
fn is_reloc_section(section_header: &SectionHeader64<Endianness>, endian: Endianness) -> bool {
let sh_type = section_header.sh_type(endian);
matches!(
sh_type,
object::elf::SHT_REL | object::elf::SHT_RELA | object::elf::SHT_CREL
)
}
fn reloc_section_target<'a>(
sections: &'a SectionTable<'a, FileHeader64<Endianness>, &'a [u8]>,
section: &'a SectionHeader64<Endianness>,
endian: Endianness,
) -> Result<Option<&'a SectionHeader64<Endianness>>> {
if !is_reloc_section(&section, endian) {
return Ok(None);
}
let sh_info = section.info_link(endian);
// Dynamic relocation.
if sh_info == SectionIndex(0) {
return Ok(None);
}
ensure!(
sh_info.0 < sections.len(),
"invalid ELF `sh_info` for relocation section",
);
Ok(Some(sections.section(sh_info)?))
}
/// Returns the range of `inner` within `outer`, such that `outer[range]` is the
/// same as `inner`.
///
/// This method requires that `inner` is a sub-slice of `outer`, and if that
/// isn't true then this method will panic.
fn subslice_range(inner: &[u8], outer: &[u8]) -> Range<usize> {
if inner.len() == 0 {
return 0..0;
}
assert!(outer.as_ptr() <= inner.as_ptr());
assert!((&inner[inner.len() - 1] as *const _) <= (&outer[outer.len() - 1] as *const _));
let start = inner.as_ptr() as usize - outer.as_ptr() as usize;
start..start + inner.len()
}
@@ -0,0 +1,9 @@
bindgen!({
inline: r#"
package my:project;
world hello-world {
import name: func() -> string;
export greet: func();
}
"#,
});
@@ -0,0 +1,17 @@
bindgen!({
inline: r#"
package example:imports;
world my-world {
/// Fetch a greeting to present.
import greet: func() -> string;
/// Log a message to the host.
import log: func(msg: string);
import my-custom-host: interface {
tick: func();
}
}
"#,
});
@@ -0,0 +1,18 @@
bindgen!({
inline: r#"
package my:project;
interface host {
gen-random-integer: func() -> u32;
sha256: func(bytes: list<u8>) -> string;
}
world hello-world {
import host;
export demo: interface {
run: func();
}
}
"#,
});
@@ -0,0 +1,20 @@
bindgen!({
inline: r#"
package example:interface-imports;
interface logging {
enum level {
debug,
info,
warn,
error,
}
log: func(level: level, msg: string);
}
world with-imports {
import logging;
}
"#,
});
@@ -0,0 +1,45 @@
bindgen!({
inline: r#"
package example:imported-resources;
interface logging {
enum level {
debug,
info,
warn,
error,
}
resource logger {
constructor(max-level: level);
get-max-level: func() -> level;
set-max-level: func(level: level);
log: func(level: level, msg: string);
}
}
world import-some-resources {
import logging;
}
"#,
with: {
// Specify that our host resource is going to point to the `MyLogger`
// which is defined just below this macro.
"example:imported-resources/logging.logger": MyLogger,
},
// Interactions with `ResourceTable` can possibly trap so enable the ability
// to return traps from generated functions.
imports: { default: trappable },
});
/// A sample host-defined type which contains arbitrary host-defined data.
///
/// In this case this is relatively simple but there's no restrictions on what
/// this type can hold other than that it must be `'static + Send`.
pub struct MyLogger {
pub max_level: example::imported_resources::logging::Level,
}
@@ -0,0 +1,29 @@
bindgen!({
inline: r#"
package example:world-exports;
world with-exports {
import log: func(msg: string);
export run: func();
/// An example of exporting an interface inline and naming it
/// directly.
export environment: interface {
get: func(var: string) -> string;
set: func(var: string, val: string);
}
/// An example of exporting an interface.
export units;
}
interface units {
/// Renders the number of bytes as a human readable string.
bytes-to-string: func(bytes: u64) -> string;
/// Renders the provided duration as a human readable string.
duration-to-string: func(seconds: u64, ns: u32) -> string;
}
"#,
});
@@ -0,0 +1,26 @@
bindgen!({
inline: r#"
package example:exported-resources;
world export-some-resources {
export logging;
}
interface logging {
enum level {
debug,
info,
warn,
error,
}
resource logger {
constructor(max-level: level);
get-max-level: func() -> level;
set-max-level: func(level: level);
log: func(level: level, msg: string);
}
}
"#,
});
@@ -0,0 +1,47 @@
bindgen!({
inline: r#"
package example:imported-resources;
interface logging {
enum level {
debug,
info,
warn,
error,
}
resource logger {
constructor(max-level: level);
get-max-level: func() -> level;
set-max-level: func(level: level);
log: func(level: level, msg: string);
}
}
world import-some-resources {
import logging;
}
"#,
// NEW: Make all imports/exports `async` by default, and additionally
// interactions with `ResourceTable` can possibly trap so enable the ability
// to return traps from generated functions with `trappable`.
imports: { default: async | trappable },
exports: { default: async },
with: {
// Specify that our host resource is going to point to the `MyLogger`
// which is defined just below this macro.
"example:imported-resources/logging.logger": MyLogger,
},
});
/// A sample host-defined type which contains arbitrary host-defined data.
///
/// In this case this is relatively simple but there's no restrictions on what
/// this type can hold other than that it must be `'static + Send`.
pub struct MyLogger {
pub max_level: example::imported_resources::logging::Level,
}
@@ -0,0 +1,23 @@
bindgen!({
inline: r#"
package example:store-in-imports;
world my-world {
import sync-with-store: func();
import async-with-store: async func();
import sync-without-store: func();
import async-without-store: func();
export run: async func();
}
"#,
imports: {
"sync-with-store": store,
// note that this isn't required because WIT-level `async` functions
// always have access to the store.
// "async-with-store": store,
"async-without-store": async,
},
});
@@ -0,0 +1,644 @@
//! Examples of output of the [`bindgen!`] macro.
//!
//! This module is only included in docs.rs documentation and is not present in
//! the actual crate when compiling from crates.io. The purpose of this module
//! is to showcase what the output of the [`bindgen!`] macro looks like and some
//! examples of how to use it.
//!
//! If you're confused or lost in [`bindgen!`] feel free to [open an issue]
//! with a description of your issue and it can hopefully lead to a new example
//! being added here for others to use as reference.
//!
//! ## Including `*.wit` files in your project
//!
//! Note that most of the examples in this module will use the `inline` key of
//! the [`bindgen!`] macro. This is done as it's easy to show the example and
//! WIT all in one self-contained snippet of Rust code. Typically though a
//! project will have a `wit` directory next to `Cargo.toml` which contains WIT
//! files.
//!
//! The general layout of a `wit` directory is that:
//!
//! * All `*.wit` files at `wit/*.wit` are parsed and included in the same
//! package.
//! * If the `wit/deps` folder is present then it can either contain:
//! * Subdirectories with a package-per-directory. For example
//! `wit/deps/wasi-http` and `wit/deps/wasi-cli`.
//! * WIT files that are a single-file rendering of a package, for example
//! `wit/deps/wasi-http.wit`
//! * WIT packages encoded as WebAssembly binaries for a package, for example
//! `wit/deps/wasi-http.wasm`
//!
//! This means that at this time you'll need to copy around `*.wit` files or
//! WIT packages encoded as `*.wasm` and check them in to your project's `wit`
//! directory. The hope is that in the future it will be easier to manage these
//! files with registry tooling and they won't have to be copied manually.
//! For reference documentation on the layout of the `wit` directory see
//! [`wit_parser::Resolve::push_dir`].
//!
//! [`bindgen!`]: crate::component::bindgen
//! [`wit_parser::Resolve::push_dir`]: https://docs.rs/wit-parser/latest/wit_parser/struct.Resolve.html#method.push_dir
//! [open an issue]: https://github.com/bytecodealliance/wasmtime/issues/new
#![expect(
missing_docs,
reason = "bindgen-generated types known to not have docs"
)]
// This "hack" will shadow the `bindgen` macro in general and be inherited to
// following modules by default. This enables documenting sources as-is while
// additionally customizing them to working within the wasmtime crate itself by
// injecting a configuration option to change how the `wasmtime` crate is
// referenced in the generated output.
//
// Note that this has an additional "hack" such that when docs.rs is documenting
// this crate (or CI) then `include_generated_code_from_file` is unconditionally
// turned on. This makes `[source]` links on documentation show the actual
// generated code rather than just the `bindgen!` macro invocation, which can be
// helpful when exploring code.
#[cfg(docsrs)]
macro_rules! bindgen {
({$($t:tt)*}) => (crate::component::bindgen!({
$($t)*
wasmtime_crate: crate,
include_generated_code_from_file: true,
}););
}
#[cfg(not(docsrs))]
macro_rules! bindgen {
({$($t:tt)*}) => (crate::component::bindgen!({
$($t)*
wasmtime_crate: crate,
}););
}
/// A "hello world" style example.
///
/// This example loads a component which has access to a single host function.
/// The exported function is called on an instantiation of the component.
///
/// ```rust
/// use wasmtime::component::*;
/// use wasmtime::{Engine, Store};
///
#[doc = include_str!("./_0_hello_world.rs")]
///
/// struct MyState {
/// name: String,
/// }
///
/// // Imports into the world, like the `name` import for this world, are
/// // satisfied through traits.
/// impl HelloWorldImports for MyState {
/// fn name(&mut self) -> String {
/// self.name.clone()
/// }
/// }
///
/// fn main() -> wasmtime::Result<()> {
/// # if true { return Ok(()) }
/// // Compile the `Component` that is being run for the application.
/// let engine = Engine::default();
/// let component = Component::from_file(&engine, "./your-component.wasm")?;
///
/// // Instantiation of bindings always happens through a `Linker`.
/// // Configuration of the linker is done through a generated `add_to_linker`
/// // method on the bindings structure.
/// //
/// // Note that the function provided here is a projection from `T` in
/// // `Store<T>` to `&mut U` where `U` implements the `HelloWorldImports`
/// // trait. In this case the `T`, `MyState`, is stored directly in the
/// // structure so no projection is necessary here.
/// //
/// // Note that the second type parameter of `add_to_linker` is chosen here
/// // as the built-in `HasSelf` type in Wasmtime. This effectively says
/// // that our function isn't actually projecting, it's returning the
/// // input, so `HasSelf<_>` is a convenience to avoid writing a custom
/// // `HasData` implementation.
/// let mut linker = Linker::new(&engine);
/// HelloWorld::add_to_linker::<_, HasSelf<_>>(&mut linker, |state| state)?;
///
/// // As with the core wasm API of Wasmtime instantiation occurs within a
/// // `Store`. The bindings structure contains an `instantiate` method which
/// // takes the store, component, and linker. This returns the `bindings`
/// // structure which is an instance of `HelloWorld` and supports typed access
/// // to the exports of the component.
/// let mut store = Store::new(
/// &engine,
/// MyState {
/// name: "me".to_string(),
/// },
/// );
/// let bindings = HelloWorld::instantiate(&mut store, &component, &linker)?;
///
/// // Here our `greet` function doesn't take any parameters for the component,
/// // but in the Wasmtime embedding API the first argument is always a `Store`.
/// bindings.call_greet(&mut store)?;
/// Ok(())
/// }
/// ```
pub mod _0_hello_world;
/// An example of generated bindings for top-level imported functions and
/// interfaces into a world.
///
/// The code used to generate this module is:
///
/// ```rust
/// use wasmtime::component::*;
/// use wasmtime::{Engine, Store};
///
#[doc = include_str!("./_1_world_imports.rs")]
///
/// struct MyState {
/// // ...
/// }
///
/// impl my_custom_host::Host for MyState {
/// fn tick(&mut self) {
/// todo!()
/// }
/// }
///
/// impl MyWorldImports for MyState {
/// fn greet(&mut self) -> String {
/// todo!()
/// }
///
/// fn log(&mut self, msg: String) {
/// println!("{msg}");
/// }
/// }
///
/// fn main() -> wasmtime::Result<()> {
/// # if true { return Ok(()) }
/// let engine = Engine::default();
/// let component = Component::from_file(&engine, "./your-component.wasm")?;
///
/// let mut linker = Linker::new(&engine);
/// MyWorld::add_to_linker::<_, HasSelf<_>>(&mut linker, |state| state)?;
///
/// let mut store = Store::new(
/// &engine,
/// MyState { /* ... */ },
/// );
/// let bindings = MyWorld::instantiate(&mut store, &component, &linker)?;
///
/// // ... NB: this world has no exports just yet so not much can be done
/// // with `bindings`.
///
/// Ok(())
/// }
/// ```
pub mod _1_world_imports;
/// An example of generated bindings for top-level exported functions for a
/// world.
///
/// Some notable generated items here are:
///
/// * [`my::project::host::Host`](_2_world_exports::my::project::host::Host) -
/// the generated trait for the `interface host` import.
/// * [`exports::demo::Guest`](_2_world_exports::exports::demo::Guest) -
/// the generated structured used to invoke exports on the returned instance.
/// * [`HelloWorld`](_2_world_exports::HelloWorld) -
/// the overall generated structure representing our `world`.
///
/// ```rust
/// use wasmtime::component::*;
/// use wasmtime::{Engine, Store};
///
#[doc = include_str!("./_2_world_exports.rs")]
///
/// struct MyState {
/// // ...
/// }
///
/// # mod rand { pub fn thread_rng() -> G { G } pub struct G; impl G { pub fn r#gen(&self) -> u32 { 0 } } }
/// // Note that the trait here is per-interface and within a submodule now.
/// impl my::project::host::Host for MyState {
/// fn gen_random_integer(&mut self) -> u32 {
/// rand::thread_rng().r#gen()
/// }
///
/// fn sha256(&mut self, bytes: Vec<u8>) -> String {
/// // ...
/// # panic!()
/// }
/// }
///
/// fn main() -> wasmtime::Result<()> {
/// # if true { return Ok(()) }
/// let engine = Engine::default();
/// let component = Component::from_file(&engine, "./your-component.wasm")?;
///
/// let mut linker = Linker::new(&engine);
/// HelloWorld::add_to_linker::<_, HasSelf<_>>(&mut linker, |state| state)?;
///
/// let mut store = Store::new(
/// &engine,
/// MyState { /* ... */ },
/// );
/// let bindings = HelloWorld::instantiate(&mut store, &component, &linker)?;
///
/// // Note that the `demo` method returns a `&exports::Demo::Guest`
/// // through which we can run the methods on that interface.
/// bindings.demo().call_run(&mut store)?;
/// Ok(())
/// }
/// ```
pub mod _2_world_exports;
/// Example of generating bindings for imported interfaces in a world.
///
/// Notable parts of this example are:
///
/// * Imported interfaces use the Rust module system to encapsulate themselves.
/// The interface imported here is `example:interface-imports/logging` so the
/// generated trait and types are located in
/// [`example::interface_imports::logging`][module].
/// * Types in the `logging` interface are generated in the `logging` module,
/// for example [`Level`].
/// * Generated types have implementations of [`ComponentType`], [`Lift`], and
/// [`Lower`] derived.
/// * The generated trait that host's must implement is always called [`Host`]
/// and is located in the generated module.
///
/// [module]: _3_interface_imports::example::interface_imports::logging
/// [`Level`]: _3_interface_imports::example::interface_imports::logging::Level
/// [`Host`]: _3_interface_imports::example::interface_imports::logging::Host
/// [`ComponentType`]: crate::component::ComponentType
/// [`Lift`]: crate::component::Lift
/// [`Lower`]: crate::component::Lower
///
/// ```rust
/// use wasmtime::component::bindgen;
/// use example::interface_imports::logging::Level;
///
#[doc = include_str!("./_3_interface_imports.rs")]
///
/// struct MyState {
/// // ...
/// }
///
/// impl example::interface_imports::logging::Host for MyState {
/// fn log(&mut self, level: Level, msg: String) {
/// // ...
/// }
/// }
/// ```
pub mod _3_interface_imports;
/// Example of generating bindings for imported resources in a world.
///
/// Notable parts of this example are:
///
/// * Imported resources from the host are represented as traits, in this case
/// [`HostLogger`].
/// * The per-interface [`Host`] trait still exists but has a supertrait of
/// [`HostLogger`].
/// * Resources are represented as [`Resource<T>`] and it's recommended to
/// specify a `with` key to indicate what host type you'd like to use for
/// each resource.
/// * A [`ResourceTable`] can be used to manage resources when working with
/// guests.
///
/// [`Host`]: _4_imported_resources::example::imported_resources::logging::Host
/// [`HostLogger`]: _4_imported_resources::example::imported_resources::logging::HostLogger
/// [`Resource<T>`]: crate::component::Resource
/// [`ResourceTable`]: crate::component::ResourceTable
///
/// ```rust
/// use wasmtime::Result;
/// use wasmtime::component::{bindgen, ResourceTable, Resource};
/// use example::imported_resources::logging::{Level, Host, HostLogger};
///
#[doc = include_str!("./_4_imported_resources.rs")]
///
/// #[derive(Default)]
/// struct MyState {
/// // Manages the mapping of `MyLogger` structures to `Resource<MyLogger>`.
/// table: ResourceTable,
/// }
///
/// // There are no free-functions on `interface logging`, so this is an empty
/// // impl.
/// impl Host for MyState {}
///
/// // This separate `HostLogger` trait serves to act as a namespace for just
/// // the `logger`-related resource methods.
/// impl HostLogger for MyState {
/// // A `constructor` in WIT maps to a `new` function in Rust.
/// fn new(&mut self, max_level: Level) -> Result<Resource<MyLogger>> {
/// let id = self.table.push(MyLogger { max_level })?;
/// Ok(id)
/// }
///
/// fn get_max_level(&mut self, logger: Resource<MyLogger>) -> Result<Level> {
/// debug_assert!(!logger.owned());
/// let logger = self.table.get(&logger)?;
/// Ok(logger.max_level)
/// }
///
/// fn set_max_level(&mut self, logger: Resource<MyLogger>, level: Level) -> Result<()> {
/// debug_assert!(!logger.owned());
/// let logger = self.table.get_mut(&logger)?;
/// logger.max_level = level;
/// Ok(())
/// }
///
/// fn log(&mut self, logger: Resource<MyLogger>, level: Level, msg: String) -> Result<()> {
/// debug_assert!(!logger.owned());
/// let logger = self.table.get_mut(&logger)?;
/// if (level as u32) <= (logger.max_level as u32) {
/// println!("{msg}");
/// }
/// Ok(())
/// }
///
/// fn drop(&mut self, logger: Resource<MyLogger>) -> Result<()> {
/// debug_assert!(logger.owned());
/// let _logger: MyLogger = self.table.delete(logger)?;
/// // ... custom destruction logic here if necessary, otherwise
/// // a `Drop for MyLogger` would also work.
/// Ok(())
/// }
/// }
///
/// # fn main() {}
/// ```
pub mod _4_imported_resources;
/// Example of all kinds of structures of exports from a world.
///
/// * Top-level functions in a `world` are exported directly on the generated
/// structure such as [`call_run`].
/// * All other exports are otherwise scoped with generated traits/types
/// in a top level [`exports`] module.
/// * Exported named interfaces are located at the root of the [`exports`]
/// module, such as [`exports::environment`].
/// * Interfaces are all bound with a structure called `Guest` which has typed
/// functions for each export that can be called. For example
/// [`exports::environment::Guest`][guest1] and
/// [`exports::example::world_exports::units::Guest`][guest2].
/// * Interfaces exported by their id are modeled with multiple namespacing
/// modules, such as [`exports::example::world_exports::units`][units].
///
/// [`call_run`]: _5_all_world_export_kinds::WithExports::call_run
/// [`exports`]: _5_all_world_export_kinds::exports
/// [`exports::environment`]: _5_all_world_export_kinds::exports::environment
/// [guest1]: _5_all_world_export_kinds::exports::environment::Guest
/// [guest2]: _5_all_world_export_kinds::exports::example::world_exports::units::Guest
/// [units]: _5_all_world_export_kinds::exports::example::world_exports::units
///
/// ```rust
/// use wasmtime::{Result, Engine, Store};
/// use wasmtime::component::{bindgen, Component, Linker, HasSelf};
///
#[doc = include_str!("./_5_all_world_export_kinds.rs")]
///
/// struct MyState;
///
/// impl WithExportsImports for MyState {
/// fn log(&mut self, msg: String) {
/// println!("{msg}");
/// }
/// }
///
/// fn main() -> Result<()> {
/// # if true { return Ok(()) }
/// let engine = Engine::default();
/// let component = Component::from_file(&engine, "./your-component.wasm")?;
///
/// let mut linker = Linker::new(&engine);
/// WithExports::add_to_linker::<_, HasSelf<_>>(&mut linker, |state| state)?;
///
/// let mut store = Store::new(&engine, MyState);
/// let bindings = WithExports::instantiate(&mut store, &component, &linker)?;
///
/// // top-level functions are exported directly on `WithExports` and are
/// // all prefixed with `call_*`.
/// bindings.call_run(&mut store)?;
///
/// // exported named interfaces are named directly after their export name
/// // and the `&Guest` return value has `call_*` functions on it.
/// bindings.environment().call_set(&mut store, "key", "value")?;
/// let value = bindings.environment().call_get(&mut store, "key")?;
/// assert_eq!(value, "value");
///
/// // exported interfaces by id are similar to export-by-name except that
/// // the exported name is modeled after the full id, not just the name.
/// let units = bindings.example_world_exports_units();
/// let bytes = 1 << 30 + 1 << 20;
/// let s = units.call_bytes_to_string(&mut store, bytes)?;
/// println!("{bytes} = {s}");
///
/// let (seconds, ns) = (1 << 20, 12345);
/// let s = units.call_duration_to_string(&mut store, seconds, ns)?;
/// println!("{seconds}s + {ns}ns = {s}");
/// Ok(())
/// }
/// ```
pub mod _5_all_world_export_kinds;
/// Example of a world which exports a resource.
///
/// * Guest resources are modeled as [`ResourceAny`]. Note that this type is not
/// specialized per-resource at this time so care must be taken to not mix
/// them up.
/// * Resource-related methods are a projection from a [`Guest`] structure, for
/// example to [`GuestLogger`] here.
/// * Resource-related methods all take a [`ResourceAny`] as an argument or
/// a return value.
/// * The [`ResourceAny`] must be explicitly dropped.
///
/// [`ResourceAny`]: crate::component::ResourceAny
/// [`Guest`]: _6_exported_resources::exports::example::exported_resources::logging::Guest
/// [`GuestLogger`]: _6_exported_resources::exports::example::exported_resources::logging::GuestLogger
///
/// ```rust
/// use wasmtime::{Result, Engine, Store};
/// use wasmtime::component::{bindgen, Component, Linker};
/// use self::exports::example::exported_resources::logging::Level;
///
#[doc = include_str!("./_6_exported_resources.rs")]
///
/// struct MyState;
///
/// fn main() -> Result<()> {
/// # if true { return Ok(()) }
/// let engine = Engine::default();
/// let component = Component::from_file(&engine, "./your-component.wasm")?;
///
/// let linker = Linker::new(&engine);
/// // ... this small example has no imports so nothing is added here, but
/// // if you had imports this is where they'd go.
///
/// let mut store = Store::new(&engine, MyState);
/// let bindings = ExportSomeResources::instantiate(&mut store, &component, &linker)?;
/// let guest = bindings.example_exported_resources_logging();
/// let logger = guest.logger();
///
/// // Resource methods are all attached to `logger` and take the
/// // `ResourceAny` parameter explicitly.
/// let my_logger = logger.call_constructor(&mut store, Level::Warn)?;
/// assert_eq!(logger.call_get_max_level(&mut store, my_logger)?, Level::Warn);
/// logger.call_set_max_level(&mut store, my_logger, Level::Info)?;
///
/// logger.call_log(&mut store, my_logger, Level::Debug, "hello!")?;
///
/// // The `ResourceAny` type has no destructor but when the host is done
/// // with it it needs to invoke the guest-level destructor.
/// my_logger.resource_drop(&mut store)?;
///
/// Ok(())
/// }
/// ```
pub mod _6_exported_resources;
/// Example of generating **async** bindings for imported resources in a world.
///
/// Notable differences from [`_4_imported_resources`] are:
/// * async functions are used
/// * enabled async in bindgen! macro
///
/// See [wasi_async_example](https://github.com/bytecodealliance/wasmtime/blob/main/examples/wasip1-async/main.rs) for async function calls on a host.
///
/// ```rust
/// use wasmtime::Result;
/// use wasmtime::component::{bindgen, ResourceTable, Resource};
/// use example::imported_resources::logging::{Level, Host, HostLogger};
///
#[doc = include_str!("./_7_async.rs")]
///
/// #[derive(Default)]
/// struct MyState {
/// // Manages the mapping of `MyLogger` structures to `Resource<MyLogger>`.
/// table: ResourceTable,
/// }
///
/// // There are no free-functions on `interface logging`, so this is an empty
/// // impl.
/// impl Host for MyState {}
///
/// // This separate `HostLogger` trait serves to act as a namespace for just
/// // the `logger`-related resource methods.
/// impl HostLogger for MyState {
/// // A `constructor` in WIT maps to a `new` function in Rust.
/// async fn new(&mut self, max_level: Level) -> Result<Resource<MyLogger>> {
/// let id = self.table.push(MyLogger { max_level })?;
/// Ok(id)
/// }
///
/// async fn get_max_level(&mut self, logger: Resource<MyLogger>) -> Result<Level> {
/// debug_assert!(!logger.owned());
/// let logger = self.table.get(&logger)?;
/// Ok(logger.max_level)
/// }
///
/// async fn set_max_level(&mut self, logger: Resource<MyLogger>, level: Level) -> Result<()> {
/// debug_assert!(!logger.owned());
/// let logger = self.table.get_mut(&logger)?;
/// logger.max_level = level;
/// Ok(())
/// }
///
/// async fn log(&mut self, logger: Resource<MyLogger>, level: Level, msg: String) -> Result<()> {
/// debug_assert!(!logger.owned());
/// let logger = self.table.get_mut(&logger)?;
/// if (level as u32) <= (logger.max_level as u32) {
/// println!("{msg}");
/// }
/// Ok(())
/// }
///
/// async fn drop(&mut self, logger: Resource<MyLogger>) -> Result<()> {
/// debug_assert!(logger.owned());
/// let _logger: MyLogger = self.table.delete(logger)?;
/// // ... custom destruction logic here if necessary, otherwise
/// // a `Drop for MyLogger` would also work.
/// Ok(())
/// }
/// }
///
/// # fn main() {}
/// ```
pub mod _7_async;
/// Example of using [`StoreContextMut`] in imported functions.
///
/// This is an example of using the `store` option to the `imports`
/// configuration of the [`bindgen!`](crate::component::bindgen) macro. Like
/// seen in previous examples a
/// [`MyWorldImports`](_8_store_in_imports::MyWorldImports) trait is generated
/// for `my-world`, but imports that have access to the store get added to a
/// separate trait with a `*WithStore` suffix, in this case
/// [`MyWorldImportsWithStore`](_8_store_in_imports::MyWorldImportsWithStore).
/// This second trait provides access to a [`StoreContextMut`].
///
/// [`StoreContextMut`]: crate::StoreContextMut
///
/// ```rust
/// use wasmtime::component::{bindgen, HasData, Access, Accessor};
/// use wasmtime::{StoreContextMut, AsContextMut};
///
#[doc = include_str!("./_8_store_in_imports.rs")]
///
/// struct MyState {
/// // ...
/// }
///
/// impl HasData for MyState {
/// type Data<'a> = &'a mut MyState;
/// }
///
/// impl<T> MyWorldImportsWithStore<T> for MyState {
/// /// Synchronous functions that have access to the store are defined in
/// /// Rust as a normal `fn` with an `Access` as the first parameter.
/// fn sync_with_store(mut host: Access<'_, T, Self>) {
/// // The `Access` type implements `AsContextMut` to manipulate and
/// // operate on the store.
/// let mut store: StoreContextMut<'_, T> = host.as_context_mut();
///
/// // The `Access` type can be used to get the `Data` projection of
/// // the `HasData` trait implementation above, in this case
/// // ourselves.
/// let my_state: &mut MyState = host.get();
/// }
///
/// /// Asynchronous functions that have access to the store are defined in
/// /// Rust as an `async fn` with an `Accessor` as the first parameter.
/// async fn async_with_store(accessor: &Accessor<T, Self>) {
/// // The `Accessor` type does not implement `AsContextMut` directly
/// // and instead represents the ability to, synchronously, work with
/// // the store. Notably borrows into the store or `Self` cannot be
/// // persisted across await points
/// accessor.with(|mut access| {
/// // same as `sync_with_store` above
/// let mut store: StoreContextMut<'_, T> = access.as_context_mut();
/// let my_state: &mut MyState = access.get();
///
/// // ...
/// });
///
/// some_function().await;
/// # async fn some_function() {}
///
/// accessor.with(|mut access| {
/// // ...
/// });
/// }
/// }
///
/// // Functions that don't have access to the store are defined with
/// // `&mut self` indicating that they can only access data in `self`, not the
/// // entire store.
/// impl MyWorldImports for MyState {
/// fn sync_without_store(&mut self) { /* ... */ }
/// async fn async_without_store(&mut self) { /* ... */ }
/// }
///
/// # fn main() {}
/// ```
#[cfg(feature = "component-model-async")]
pub mod _8_store_in_imports;
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,294 @@
use crate::try_mutex::TryMutex;
use alloc::sync::Arc;
use core::mem::{self, ManuallyDrop};
use core::pin::Pin;
use core::task::{Context, Poll, Waker};
/// Handle to a task which may be used to join on the result of executing it.
///
/// This represents a handle to a running task which can be cancelled with
/// [`JoinHandle::abort`]. The final result and drop of the task can be
/// determined by `await`-ing this handle.
///
/// Note that dropping this handle does not affect the running task it's
/// connected to. A manual invocation of [`JoinHandle::abort`] is required to
/// affect the task.
pub struct JoinHandle {
// Note that at this time the usage of this type within Wasmtime's async
// implementation is not expected to ever expose the ability to expose
// a situation where this lock can be contended. Everything's bound to the
// store and this is largely just used to satisfy compiler bounds. Hence,
// lock operations in this module all unwrap.
state: Arc<TryMutex<JoinState>>,
}
enum JoinState {
/// The task this is connected to is still running and has not completed or
/// been dropped.
Running {
/// The waker that the running task has registered which is signaled
/// upon abort.
waiting_for_abort_signal: Option<Waker>,
/// The waker that the `JoinHandle` has registered to await
/// destruction of the running task itself.
waiting_for_abort_to_complete: Option<Waker>,
},
/// An abort as been requested through an `JoinHandle`. The task specified
/// here is used for `Future for JoinHandle`.
AbortRequested {
waiting_for_abort_to_complete: Option<Waker>,
},
/// The running task has completed, so no need to abort it and nothing else
/// needs to wait.
Complete,
}
impl JoinHandle {
/// Abort the task.
///
/// This flags the connected task should abort in the near future, but note
/// that if this is called while the future is being polled then that call
/// will still complete.
///
/// Note that this `JoinHandle` is itself a `Future` and can be used to
/// await the result and destruction of the task that this is associated
/// with.
pub fn abort(&self) {
let mut state = self.state.try_lock().expect("should not be contended");
match &mut *state {
// If this task is still running, then fall through to below to
// transition it into the `AbortRequested` state. If present the
// waker for the running task is notified to indicate that an abort
// signal has been received.
JoinState::Running {
waiting_for_abort_signal,
waiting_for_abort_to_complete,
} => {
if let Some(task) = waiting_for_abort_signal.take() {
task.wake();
}
*state = JoinState::AbortRequested {
waiting_for_abort_to_complete: waiting_for_abort_to_complete.take(),
};
}
// If this task has already been aborted or has completed, nothing
// is left to do.
JoinState::AbortRequested { .. } | JoinState::Complete => {}
}
}
/// Wraps the `future` provided in a new future which is "abortable" where
/// if the returned `JoinHandle` is flagged then the future will resolve
/// ASAP with `None` and drop the provided `future`.
pub(crate) fn run<F>(future: F) -> (JoinHandle, impl Future<Output = Option<F::Output>>)
where
F: Future,
{
let handle = JoinHandle {
state: Arc::new(TryMutex::new(JoinState::Running {
waiting_for_abort_signal: None,
waiting_for_abort_to_complete: None,
})),
};
let future = JoinHandleFuture {
future: ManuallyDrop::new(future),
state: handle.state.clone(),
};
(handle, future)
}
}
impl Future for JoinHandle {
type Output = ();
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let mut state = self
.state
.try_lock()
.expect("this lock should not be contended");
match &mut *state {
// If this task is running or still only has requested an abort,
// wait further for the task to get dropped.
JoinState::Running {
waiting_for_abort_to_complete,
..
}
| JoinState::AbortRequested {
waiting_for_abort_to_complete,
} => {
*waiting_for_abort_to_complete = Some(cx.waker().clone());
Poll::Pending
}
// The task is dropped, done!
JoinState::Complete => Poll::Ready(()),
}
}
}
struct JoinHandleFuture<F> {
future: ManuallyDrop<F>,
state: Arc<TryMutex<JoinState>>,
}
impl<F> Future for JoinHandleFuture<F>
where
F: Future,
{
type Output = Option<F::Output>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
// SAFETY: this is a pin-projection from `Self` to the state and `Pin`
// of the internal future. This is the exclusive access of these fields
// apart from the destructor and should be safe.
let (state, future) = unsafe {
let me = self.get_unchecked_mut();
(&me.state, Pin::new_unchecked(&mut *me.future))
};
// First, before polling the future, check to see if we've been
// aborted. If not register our task as awaiting such an abort.
{
let mut state = state.try_lock().expect("this lock should not be contended");
match &mut *state {
JoinState::Running {
waiting_for_abort_signal,
..
} => {
*waiting_for_abort_signal = Some(cx.waker().clone());
}
JoinState::AbortRequested { .. } | JoinState::Complete => {
return Poll::Ready(None);
}
}
}
future.poll(cx).map(Some)
}
}
impl<F> Drop for JoinHandleFuture<F> {
fn drop(&mut self) {
// SAFETY: this is the exclusive owner of this future and it's safe to
// drop here during the owning destructor.
//
// Note that this explicitly happens before notifying the abort handle
// that the task completed so that when the notification goes through
// it's guaranteed that the future has been destroyed.
unsafe {
ManuallyDrop::drop(&mut self.future);
}
// After the future dropped see if there was a task awaiting its
// destruction. Simultaneously flag this state as complete.
let prev = mem::replace(
&mut *self.state.try_lock().expect("should not be contended"),
JoinState::Complete,
);
let task = match prev {
JoinState::Running {
waiting_for_abort_to_complete,
..
}
| JoinState::AbortRequested {
waiting_for_abort_to_complete,
} => waiting_for_abort_to_complete,
JoinState::Complete => None,
};
if let Some(task) = task {
task.wake();
}
}
}
#[cfg(test)]
mod tests {
use super::JoinHandle;
use std::pin::{Pin, pin};
use std::task::{Context, Poll, Waker};
use tokio::sync::oneshot;
fn is_ready<F>(future: Pin<&mut F>) -> bool
where
F: Future,
{
match future.poll(&mut Context::from_waker(Waker::noop())) {
Poll::Ready(_) => true,
Poll::Pending => false,
}
}
#[tokio::test]
async fn abort_in_progress() {
let (tx, rx) = oneshot::channel::<()>();
let (mut handle, future) = JoinHandle::run(rx);
let mut handle = Pin::new(&mut handle);
{
let mut future = pin!(future);
assert!(!is_ready(future.as_mut()));
assert!(!is_ready(handle.as_mut()));
handle.abort();
assert!(is_ready(future.as_mut()));
assert!(!is_ready(handle.as_mut()));
assert!(!tx.is_closed());
}
assert!(is_ready(handle.as_mut()));
assert!(tx.is_closed());
}
#[tokio::test]
async fn abort_complete() {
let (tx, rx) = oneshot::channel::<()>();
let (mut handle, future) = JoinHandle::run(rx);
let mut handle = Pin::new(&mut handle);
tx.send(()).unwrap();
assert!(!is_ready(handle.as_mut()));
{
let mut future = pin!(future);
assert!(is_ready(future.as_mut()));
assert!(!is_ready(handle.as_mut()));
}
assert!(is_ready(handle.as_mut()));
handle.abort();
assert!(is_ready(handle.as_mut()));
}
#[tokio::test]
async fn abort_dropped() {
let (tx, rx) = oneshot::channel::<()>();
let (mut handle, future) = JoinHandle::run(rx);
let mut handle = Pin::new(&mut handle);
drop(future);
assert!(is_ready(handle.as_mut()));
handle.abort();
assert!(is_ready(handle.as_mut()));
assert!(tx.is_closed());
}
#[tokio::test]
async fn await_completion() {
let (tx, rx) = oneshot::channel::<()>();
tx.send(()).unwrap();
let (handle, future) = JoinHandle::run(rx);
let task = tokio::task::spawn(future);
handle.await;
task.await.unwrap();
}
#[tokio::test]
async fn await_abort() {
let (tx, rx) = oneshot::channel::<()>();
tx.send(()).unwrap();
let (handle, future) = JoinHandle::run(rx);
handle.abort();
let task = tokio::task::spawn(future);
handle.await;
task.await.unwrap();
}
}
@@ -0,0 +1,8 @@
/// Error context reference count across a [`ComponentInstance`]
///
/// Contrasted to `LocalErrorContextRefCount`, this count is maintained
/// across all sub-components in a given component.
///
/// When this count is zero it is *definitely* safe to remove an error context.
#[repr(transparent)]
pub struct GlobalErrorContextRefCount(pub(crate) usize);
@@ -0,0 +1,497 @@
use crate::component::concurrent::TaskId;
use crate::component::concurrent::{self, GuestTaskId, PreparedCall};
use crate::component::func::LowerContext;
use crate::component::{AsAccessor, ComponentNamedList, Func, Lift, Lower, TypedFunc, Val};
use crate::prelude::*;
use crate::runtime::vm::SendSyncPtr;
use crate::{AsContextMut, StoreContextMut, ValRaw};
use core::marker;
use core::mem::MaybeUninit;
use core::ptr::NonNull;
use wasmtime_environ::component::{InterfaceType, MAX_FLAT_PARAMS, MAX_FLAT_RESULTS};
/// Returned from [`Func::start_call_concurrent`] to represent a
/// pending-but-not-yet-resolved call into wasm.
pub struct FuncCallConcurrent<'a, T> {
call: concurrent::StagedCall<Vec<Val>>,
results: &'a mut [Val],
_marker: marker::PhantomData<fn(T)>,
}
impl Func {
/// Start a concurrent call to this function.
///
/// Concurrency is achieved by relying on the [`Accessor`] argument, which
/// can be obtained by calling [`StoreContextMut::run_concurrent`].
///
/// Unlike [`Self::call`] and [`Self::call_async`] (both of which require
/// exclusive access to the store until the completion of the call), calls
/// made using this method may run concurrently with other calls to the same
/// instance. In addition, the runtime will call the `post-return` function
/// (if any) automatically when the guest task completes.
///
/// # Progress
///
/// For the wasm task being created in `call_concurrent` to make progress it
/// must be run within the scope of [`run_concurrent`]. If there are no
/// active calls to [`run_concurrent`] then the wasm task will appear as
/// stalled. This is typically not a concern as an [`Accessor`] is bound
/// by default to a scope of [`run_concurrent`].
///
/// One situation in which this can arise, for example, is that if a
/// [`run_concurrent`] computation finishes its async closure before all
/// wasm tasks have completed, then there will be no scope of
/// [`run_concurrent`] anywhere. In this situation the wasm tasks that have
/// not yet completed will not make progress until [`run_concurrent`] is
/// called again.
///
/// Embedders will need to ensure that this future is `await`'d within the
/// scope of [`run_concurrent`] to ensure that the value can be produced
/// during the `await` call.
///
/// # Cancellation
///
/// Cancelling an async task created via `call_concurrent`, at this time, is
/// only possible by dropping the store that the computation runs within.
/// With [#11833] implemented then it will be possible to request
/// cancellation of a task, but that is not yet implemented. Hard-cancelling
/// a task will only ever be possible by dropping the entire store and it is
/// not possible to remove just one task from a store.
///
/// This async function behaves more like a "spawn" than a normal Rust async
/// function. When this function is invoked then metadata for the function
/// call is recorded in the store connected to the `accessor` argument and
/// the wasm invocation is from then on connected to the store. If the
/// future created by this function is dropped it does not cancel the
/// in-progress execution of the wasm task. Dropping the future
/// relinquishes the host's ability to learn about the result of the task
/// but the task will still progress and invoke callbacks and such until
/// completion.
///
/// This function will return an error if [`Config::concurrency_support`] is
/// disabled.
///
/// [`Config::concurrency_support`]: crate::Config::concurrency_support
/// [`run_concurrent`]: crate::Store::run_concurrent
/// [#11833]: https://github.com/bytecodealliance/wasmtime/issues/11833
/// [`Accessor`]: crate::component::Accessor
///
/// # Panics
///
/// Panics if the store that the [`Accessor`] is derived from does not own
/// this function.
///
/// # Example
///
/// Using [`StoreContextMut::run_concurrent`] to get an [`Accessor`]:
///
/// ```
/// # use {
/// # wasmtime::{
/// # error::{Result},
/// # component::{Component, Linker, ResourceTable},
/// # Config, Engine, Store
/// # },
/// # };
/// #
/// # struct Ctx { table: ResourceTable }
/// #
/// # async fn foo() -> Result<()> {
/// # let mut config = Config::new();
/// # let engine = Engine::new(&config)?;
/// # let mut store = Store::new(&engine, Ctx { table: ResourceTable::new() });
/// # let mut linker = Linker::new(&engine);
/// # let component = Component::new(&engine, "")?;
/// # let instance = linker.instantiate_async(&mut store, &component).await?;
/// let my_func = instance.get_func(&mut store, "my_func").unwrap();
/// store.run_concurrent(async |accessor| -> wasmtime::Result<_> {
/// my_func.call_concurrent(accessor, &[], &mut Vec::new()).await?;
/// Ok(())
/// }).await??;
/// # Ok(())
/// # }
/// ```
pub async fn call_concurrent(
self,
accessor: impl AsAccessor<Data: Send>,
params: &[Val],
results: &mut [Val],
) -> Result<()> {
let accessor = accessor.as_accessor();
let call = accessor.with(|store| self.start_call_concurrent(store, params, results))?;
self.finish_call_concurrent(accessor, call).await
}
/// Performs preparatory work for invoking this function with `params`,
/// returning a [`FuncCallConcurrent`]
/// which can be passed to [`Func::finish_call_concurrent`] to resolve
/// the call.
///
/// For more information see [`Func::call_concurrent`].
pub fn start_call_concurrent<'a, T: Send + 'static>(
self,
mut store: impl AsContextMut<Data = T>,
params: &'a [Val],
results: &'a mut [Val],
) -> Result<FuncCallConcurrent<'a, T>> {
self.check_params_results(store.as_context_mut(), params, results)?;
let prepared = self.prepare_call_dynamic(store.as_context_mut(), params.to_vec())?;
let call = concurrent::StagedCall::new(store.as_context_mut(), prepared)?;
Ok(FuncCallConcurrent {
call,
results,
_marker: marker::PhantomData,
})
}
/// Completes a call that was initiated via
/// [`Func::start_call_concurrent`].
pub async fn finish_call_concurrent<T: Send>(
self,
accessor: impl AsAccessor<Data = T>,
call: FuncCallConcurrent<'_, T>,
) -> Result<()> {
// Intentionally not used today, but left here for future API
// compatibility with using this.
let _ = accessor;
let FuncCallConcurrent { call, results, .. } = call;
let run_results = call.await?;
assert_eq!(run_results.len(), results.len());
for (result, slot) in run_results.into_iter().zip(results) {
*slot = result;
}
Ok(())
}
/// Calls `concurrent::prepare_call` with monomorphized functions for
/// lowering the parameters and lifting the result.
fn prepare_call_dynamic<'a, T: Send + 'static>(
self,
mut store: StoreContextMut<'a, T>,
params: Vec<Val>,
) -> Result<PreparedCall<Vec<Val>>> {
let store = store.as_context_mut();
let (options, flags, ty, raw_options) = self.abi_info(store.0);
let async_ = raw_options.async_;
let instance = self.instance();
concurrent::prepare_call(
store,
self,
MAX_FLAT_PARAMS,
false,
move |store, params_out| {
Func::with_lower_context(instance, store, options, flags, ty, |cx, ty| {
Self::lower_args(cx, &params, ty, params_out)
})
},
move |store, results| {
let max_flat = if async_ {
MAX_FLAT_PARAMS
} else {
MAX_FLAT_RESULTS
};
let results = Func::with_lift_context(instance, store, options, ty, |cx, ty| {
Self::lift_results(cx, ty, results, max_flat)?.collect::<Result<Vec<_>>>()
})?;
Ok(Box::new(results))
},
)
}
}
impl<T> FuncCallConcurrent<'_, T> {
/// Returns the task that this invocation corresponds to.
///
/// This can be later correlated with [`StoreContextMut::async_call_stack`]
/// for example.
pub fn task(&self) -> GuestTaskId {
self.call.task()
}
}
/// Returned from [`TypedFunc::start_call_concurrent`] to represent a
/// pending-but-not-yet-resolved call into wasm.
pub struct TypedFuncCallConcurrent<T, P, R> {
call: concurrent::StagedCall<R>,
_marker: marker::PhantomData<fn(T, P)>,
}
impl<Params, Return> TypedFunc<Params, Return>
where
Params: ComponentNamedList + Lower,
Return: ComponentNamedList + Lift,
{
pub(crate) async fn call_async_concurrent(
&self,
mut store: impl AsContextMut<Data: Send>,
params: Params,
) -> Result<Return>
where
Return: 'static,
{
let mut store = store.as_context_mut();
let ptr = SendSyncPtr::from(NonNull::from(&params).cast::<u8>());
let prepared = self.prepare_call(store.as_context_mut(), true, move |cx, ty, dst| {
// SAFETY: The goal here is to get `Params`, a non-`'static`
// value, to live long enough to the lowering of the
// parameters. We're guaranteed that `Params` lives in the
// future of the outer function (we're in an `async fn`) so it'll
// stay alive as long as the future itself. That is distinct,
// for example, from the signature of `call_concurrent` below.
//
// Here a pointer to `Params` is smuggled to this location
// through a `SendSyncPtr<u8>` to thwart the `'static` check
// of rustc and the signature of `prepare_call`.
//
// Note the use of `SignalOnDrop` in the code that follows
// this closure, which ensures that the task will be removed
// from the concurrent state to which it belongs when the
// containing `Future` is dropped, so long as the parameters
// have not yet been lowered. Since this closure is removed from
// the task after the parameters are lowered, it will never be called
// after the containing `Future` is dropped.
let params = unsafe { ptr.cast::<Params>().as_ref() };
Self::lower_args(cx, ty, dst, params)
})?;
struct SignalOnDrop<'a, T: 'static> {
store: StoreContextMut<'a, T>,
task: TaskId,
}
impl<'a, T> Drop for SignalOnDrop<'a, T> {
fn drop(&mut self) {
self.task.host_future_dropped(self.store.0).unwrap();
}
}
let mut wrapper = SignalOnDrop {
store,
task: prepared.task_id(),
};
let result = concurrent::StagedCall::new(wrapper.store.as_context_mut(), prepared)?;
wrapper
.store
.as_context_mut()
.run_concurrent_trap_on_idle(async |_| Ok(result.await?))
.await?
}
/// Start a concurrent call to this function.
///
/// Concurrency is achieved by relying on the [`Accessor`] argument, which
/// can be obtained by calling [`StoreContextMut::run_concurrent`].
///
/// Unlike [`Self::call`] and [`Self::call_async`] (both of which require
/// exclusive access to the store until the completion of the call), calls
/// made using this method may run concurrently with other calls to the same
/// instance. In addition, the runtime will call the `post-return` function
/// (if any) automatically when the guest task completes.
///
/// This function will return an error if [`Config::concurrency_support`] is
/// disabled.
///
/// [`Config::concurrency_support`]: crate::Config::concurrency_support
///
/// # Progress and Cancellation
///
/// For more information about how to make progress on the wasm task or how
/// to cancel the wasm task see the documentation for
/// [`Func::call_concurrent`].
///
/// [`Func::call_concurrent`]: crate::component::Func::call_concurrent
///
/// # Panics
///
/// Panics if the store that the [`Accessor`] is derived from does not own
/// this function.
///
/// [`Accessor`]: crate::component::Accessor
///
/// # Example
///
/// Using [`StoreContextMut::run_concurrent`] to get an [`Accessor`]:
///
/// ```
/// # use {
/// # wasmtime::{
/// # error::{Result},
/// # component::{Component, Linker, ResourceTable},
/// # Config, Engine, Store
/// # },
/// # };
/// #
/// # struct Ctx { table: ResourceTable }
/// #
/// # async fn foo() -> Result<()> {
/// # let mut config = Config::new();
/// # let engine = Engine::new(&config)?;
/// # let mut store = Store::new(&engine, Ctx { table: ResourceTable::new() });
/// # let mut linker = Linker::new(&engine);
/// # let component = Component::new(&engine, "")?;
/// # let instance = linker.instantiate_async(&mut store, &component).await?;
/// let my_typed_func = instance.get_typed_func::<(), ()>(&mut store, "my_typed_func")?;
/// store.run_concurrent(async |accessor| -> wasmtime::Result<_> {
/// my_typed_func.call_concurrent(accessor, ()).await?;
/// Ok(())
/// }).await??;
/// # Ok(())
/// # }
/// ```
pub async fn call_concurrent(
self,
accessor: impl AsAccessor<Data: Send>,
params: Params,
) -> Result<Return>
where
Params: 'static,
Return: 'static,
{
let call = accessor
.as_accessor()
.with(|store| self.start_call_concurrent(store, params))?;
self.finish_call_concurrent(accessor, call).await
}
/// Performs preparatory work for invoking this function with `params`,
/// returning a [`TypedFuncCallConcurrent`]
/// which can be passed to [`TypedFunc::finish_call_concurrent`] to resolve
/// the call.
///
/// For more information see [`TypedFunc::call_concurrent`].
pub fn start_call_concurrent<T>(
self,
mut store: impl AsContextMut<Data = T>,
params: Params,
) -> Result<TypedFuncCallConcurrent<T, Params, Return>>
where
T: Send + 'static,
Params: 'static,
Return: 'static,
{
let mut store = store.as_context_mut();
let mut store = store.as_context_mut();
ensure!(
store.0.concurrency_support(),
"cannot use `call_concurrent` Config::concurrency_support disabled",
);
let prepared = self.prepare_call(store.as_context_mut(), false, move |cx, ty, dst| {
Self::lower_args(cx, ty, dst, &params)
})?;
let call = concurrent::StagedCall::new(store, prepared)?;
Ok(TypedFuncCallConcurrent {
call,
_marker: marker::PhantomData,
})
}
/// Completes a call that was initiated via
/// [`TypedFunc::start_call_concurrent`].
pub async fn finish_call_concurrent<T>(
self,
accessor: impl AsAccessor<Data = T>,
call: TypedFuncCallConcurrent<T, Params, Return>,
) -> Result<Return>
where
T: Send + 'static,
Params: 'static,
Return: 'static,
{
// This is intentionally part of the public API but not used yet.
// This'll likely want to be used in future refactorings.
let _ = accessor;
call.call.await
}
/// Calls `concurrent::prepare_call` with monomorphized functions for
/// lowering the parameters and lifting the result according to the number
/// of core Wasm parameters and results in the signature of the function to
/// be called.
fn prepare_call<T>(
self,
store: StoreContextMut<'_, T>,
host_future_present: bool,
lower: impl FnOnce(
&mut LowerContext<T>,
InterfaceType,
&mut [MaybeUninit<ValRaw>],
) -> Result<()>
+ Send
+ Sync
+ 'static,
) -> Result<PreparedCall<Return>>
where
Return: 'static,
{
use crate::component::storage::slice_to_storage;
debug_assert!(store.0.concurrency_support());
let param_count = if Params::flatten_count() <= MAX_FLAT_PARAMS {
Params::flatten_count()
} else {
1
};
let (options, flags, ty, raw_options) = self.func().abi_info(store.0);
let instance = self.func().instance();
let max_results = if raw_options.async_ {
MAX_FLAT_PARAMS
} else {
MAX_FLAT_RESULTS
};
concurrent::prepare_call(
store,
*self.func(),
param_count,
host_future_present,
move |store, params_out| {
Func::with_lower_context(instance, store, options, flags, ty, |cx, ty| {
lower(cx, ty, params_out)
})
},
move |store, results| {
let result = if Return::flatten_count() <= max_results {
Func::with_lift_context(instance, store, options, ty, |cx, ty| {
// SAFETY: Per the safety requirements documented for the
// `ComponentType` trait, `Return::Lower` must be
// compatible at the binary level with a `[ValRaw; N]`,
// where `N` is `mem::size_of::<Return::Lower>() /
// mem::size_of::<ValRaw>()`. And since this function
// is only used when `Return::flatten_count() <=
// MAX_FLAT_RESULTS` and `MAX_FLAT_RESULTS == 1`, `N`
// can only either be 0 or 1.
//
// See `ComponentInstance::exit_call` for where we use
// the result count passed from
// `wasmtime_environ::fact::trampoline`-generated code
// to ensure the slice has the correct length, and also
// `concurrent::start_call` for where we conservatively
// use a slice length of 1 unconditionally. Also note
// that, as of this writing `slice_to_storage`
// double-checks the slice length is sufficient.
let results: &Return::Lower = unsafe { slice_to_storage(results) };
Self::lift_stack_result(cx, ty, results)
})?
} else {
Func::with_lift_context(instance, store, options, ty, |cx, ty| {
Self::lift_heap_result(cx, ty, &results[0])
})?
};
Ok(Box::new(result))
},
)
}
}
impl<T, P, R> TypedFuncCallConcurrent<T, P, R> {
/// Returns the task that this invocation corresponds to.
///
/// This can be later correlated with [`StoreContextMut::async_call_stack`]
/// for example.
pub fn task(&self) -> GuestTaskId {
self.call.task()
}
}
@@ -0,0 +1,458 @@
//! Implementation of [`FutureAny`] and [`StreamAny`].
use crate::component::concurrent::futures_and_streams::{self, TransmitOrigin};
use crate::component::concurrent::{TableId, TransmitHandle};
use crate::component::func::{LiftContext, LowerContext, bad_type_info, desc};
use crate::component::matching::InstanceType;
use crate::component::types::{self, FutureType, StreamType};
use crate::component::{
ComponentInstanceId, ComponentType, FutureReader, Lift, Lower, StreamReader,
};
use crate::store::StoreOpaque;
use crate::{AsContextMut, Result, bail, error::Context};
use core::any::TypeId;
use core::mem::MaybeUninit;
use wasmtime_environ::component::{
CanonicalAbiInfo, InterfaceType, TypeFutureTableIndex, TypeStreamTableIndex,
};
/// Represents a type-erased component model `future`.
///
/// This type is similar to [`ResourceAny`](crate::component::ResourceAny)
/// where it's a static guarantee that it represents a component model
/// `future`, but it does not contain any information about the underlying type
/// that is associated with this future. This is intended to be used in
/// "dynamically typed" situations where embedders may not know ahead of time
/// the type of a `future` being used by component that is loaded.
///
/// # Closing futures
///
/// A [`FutureAny`] represents a resource that is owned by a [`Store`]. Proper
/// disposal of a future requires invoking the [`FutureAny::close`] method to
/// ensure that this handle does not leak. If [`FutureAny::close`] is not
/// called then memory will not be leaked once the owning [`Store`] is dropped,
/// but the resource handle will be leaked until the [`Store`] is dropped.
///
/// [`Store`]: crate::Store
#[derive(Debug, Clone, PartialEq)]
pub struct FutureAny {
id: TableId<TransmitHandle>,
ty: PayloadType<FutureType>,
}
impl FutureAny {
fn lower_to_index<T>(&self, cx: &mut LowerContext<'_, T>, ty: InterfaceType) -> Result<u32> {
// Note that unlike `FutureReader<T>` we need to perform an extra
// typecheck to ensure that the dynamic type of this future matches
// what the guest we're lowering into expects. This couldn't happen
// before this point (see the `ComponentType::typecheck` implementation
// for this type), so do it now.
let future_ty = match ty {
InterfaceType::Future(payload) => payload,
_ => bad_type_info(),
};
let payload = cx.types[cx.types[future_ty].ty].payload.as_ref();
self.ty.typecheck_guest(
&cx.instance_type(),
payload,
FutureType::equivalent_payload_guest,
)?;
// Like `FutureReader<T>`, however, lowering "just" gets a u32.
futures_and_streams::lower_future_to_index(self.id, cx, ty)
}
/// Attempts to convert this [`FutureAny`] to a [`FutureReader<T>`]
/// with a statically known type.
///
/// # Errors
///
/// This function will return an error if `T` does not match the type of
/// value on this future.
pub fn try_into_future_reader<T>(self) -> Result<FutureReader<T>>
where
T: ComponentType + 'static,
{
self.ty
.typecheck_host::<T>(FutureType::equivalent_payload_host::<T>)?;
Ok(FutureReader::new_(self.id))
}
/// Attempts to convert `reader` to a [`FutureAny`], erasing its statically
/// known type.
///
/// # Errors
///
/// This function will return an error if `reader` does not belong to
/// `store`.
pub fn try_from_future_reader<T>(
mut store: impl AsContextMut,
reader: FutureReader<T>,
) -> Result<Self>
where
T: ComponentType + 'static,
{
let store = store.as_context_mut();
let ty = match store.0.transmit_origin(reader.id())? {
TransmitOrigin::Host => PayloadType::new_host::<T>(),
TransmitOrigin::GuestFuture(id, ty) => PayloadType::new_guest_future(store.0, id, ty),
TransmitOrigin::GuestStream(..) => bail!("not a future"),
};
Ok(FutureAny {
id: reader.id(),
ty,
})
}
fn lift_from_index(cx: &mut LiftContext<'_>, ty: InterfaceType, index: u32) -> Result<Self> {
let id = futures_and_streams::lift_index_to_future(cx, ty, index)?;
let InterfaceType::Future(ty) = ty else {
unreachable!()
};
let ty = cx.types[ty].ty;
Ok(FutureAny {
id,
// Note that this future might actually be a host-originating
// future which means that this ascription of "the type is the
// guest" may be slightly in accurate. The guest, however, has the
// most accurate view of what type this future has so that should
// be reasonable to ascribe as the type here regardless.
ty: PayloadType::Guest(FutureType::from(ty, &cx.instance_type())),
})
}
/// Close this `FutureAny`.
///
/// This will close this future and cause any write that happens later to
/// returned `DROPPED`.
///
/// # Errors
///
/// Returns an error if this future has already been closed.
///
/// # Panics
///
/// Panics if the `store` does not own this future.
pub fn close(&mut self, mut store: impl AsContextMut) -> Result<()> {
futures_and_streams::future_close(store.as_context_mut().0, &mut self.id)
}
}
unsafe impl ComponentType for FutureAny {
const ABI: CanonicalAbiInfo = CanonicalAbiInfo::SCALAR4;
type Lower = <u32 as ComponentType>::Lower;
fn typecheck(ty: &InterfaceType, _types: &InstanceType<'_>) -> Result<()> {
match ty {
InterfaceType::Future(_) => Ok(()),
other => bail!("expected `future`, found `{}`", desc(other)),
}
}
}
unsafe impl Lower for FutureAny {
fn linear_lower_to_flat<T>(
&self,
cx: &mut LowerContext<'_, T>,
ty: InterfaceType,
dst: &mut MaybeUninit<Self::Lower>,
) -> Result<()> {
self.lower_to_index(cx, ty)?
.linear_lower_to_flat(cx, InterfaceType::U32, dst)
}
fn linear_lower_to_memory<T>(
&self,
cx: &mut LowerContext<'_, T>,
ty: InterfaceType,
offset: usize,
) -> Result<()> {
self.lower_to_index(cx, ty)?
.linear_lower_to_memory(cx, InterfaceType::U32, offset)
}
}
unsafe impl Lift for FutureAny {
fn linear_lift_from_flat(
cx: &mut LiftContext<'_>,
ty: InterfaceType,
src: &Self::Lower,
) -> Result<Self> {
let index = u32::linear_lift_from_flat(cx, InterfaceType::U32, src)?;
Self::lift_from_index(cx, ty, index)
}
fn linear_lift_from_memory(
cx: &mut LiftContext<'_>,
ty: InterfaceType,
bytes: &[u8],
) -> Result<Self> {
let index = u32::linear_lift_from_memory(cx, InterfaceType::U32, bytes)?;
Self::lift_from_index(cx, ty, index)
}
}
/// Represents a type-erased component model `stream`.
///
/// This type is similar to [`ResourceAny`](crate::component::ResourceAny)
/// where it's a static guarantee that it represents a component model
/// `stream`, but it does not contain any information about the underlying type
/// that is associated with this stream. This is intended to be used in
/// "dynamically typed" situations where embedders may not know ahead of time
/// the type of a `stream` being used by component that is loaded.
///
/// # Closing streams
///
/// A [`StreamAny`] represents a resource that is owned by a [`Store`]. Proper
/// disposal of a stream requires invoking the [`StreamAny::close`] method to
/// ensure that this handle does not leak. If [`StreamAny::close`] is not
/// called then memory will not be leaked once the owning [`Store`] is dropped,
/// but the resource handle will be leaked until the [`Store`] is dropped.
///
/// [`Store`]: crate::Store
#[derive(Debug, Clone, PartialEq)]
pub struct StreamAny {
id: TableId<TransmitHandle>,
ty: PayloadType<StreamType>,
}
impl StreamAny {
fn lower_to_index<T>(&self, cx: &mut LowerContext<'_, T>, ty: InterfaceType) -> Result<u32> {
// See comments in `FutureAny::lower_to_index` for why this is
// different from `StreamReader`'s implementation.
let stream_ty = match ty {
InterfaceType::Stream(payload) => payload,
_ => bad_type_info(),
};
let payload = cx.types[cx.types[stream_ty].ty].payload.as_ref();
self.ty.typecheck_guest(
&cx.instance_type(),
payload,
StreamType::equivalent_payload_guest,
)?;
futures_and_streams::lower_stream_to_index(self.id, cx, ty)
}
/// Attempts to convert this [`StreamAny`] to a [`StreamReader<T>`]
/// with a statically known type.
///
/// # Errors
///
/// This function will return an error if `T` does not match the type of
/// value on this stream.
pub fn try_into_stream_reader<T>(self) -> Result<StreamReader<T>>
where
T: ComponentType + 'static,
{
self.ty
.typecheck_host::<T>(StreamType::equivalent_payload_host::<T>)?;
Ok(StreamReader::new_(self.id))
}
/// Attempts to convert `reader` to a [`StreamAny`], erasing its statically
/// known type.
///
/// # Errors
///
/// This function will return an error if `reader` does not belong to
/// `store`.
pub fn try_from_stream_reader<T>(
mut store: impl AsContextMut,
reader: StreamReader<T>,
) -> Result<Self>
where
T: ComponentType + 'static,
{
let store = store.as_context_mut();
let ty = match store.0.transmit_origin(reader.id())? {
TransmitOrigin::Host => PayloadType::new_host::<T>(),
TransmitOrigin::GuestStream(id, ty) => PayloadType::new_guest_stream(store.0, id, ty),
TransmitOrigin::GuestFuture(..) => bail!("not a stream"),
};
Ok(StreamAny {
id: reader.id(),
ty,
})
}
fn lift_from_index(cx: &mut LiftContext<'_>, ty: InterfaceType, index: u32) -> Result<Self> {
let id = futures_and_streams::lift_index_to_stream(cx, ty, index)?;
let InterfaceType::Stream(ty) = ty else {
unreachable!()
};
let ty = cx.types[ty].ty;
Ok(StreamAny {
id,
// Note that this stream might actually be a host-originating, but
// see the documentation in `FutureAny::lift_from_index` for why
// this should be ok.
ty: PayloadType::Guest(StreamType::from(ty, &cx.instance_type())),
})
}
/// Close this `StreamAny`.
///
/// This will close this stream and cause any write that happens later to
/// returned `DROPPED`.
///
/// # Errors
///
/// Returns an error if this stream has already been closed.
///
/// # Panics
///
/// Panics if the `store` does not own this stream.
pub fn close(&mut self, mut store: impl AsContextMut) -> Result<()> {
futures_and_streams::stream_close(store.as_context_mut().0, &mut self.id)
}
}
unsafe impl ComponentType for StreamAny {
const ABI: CanonicalAbiInfo = CanonicalAbiInfo::SCALAR4;
type Lower = <u32 as ComponentType>::Lower;
fn typecheck(ty: &InterfaceType, _types: &InstanceType<'_>) -> Result<()> {
match ty {
InterfaceType::Stream(_) => Ok(()),
other => bail!("expected `stream`, found `{}`", desc(other)),
}
}
}
unsafe impl Lower for StreamAny {
fn linear_lower_to_flat<T>(
&self,
cx: &mut LowerContext<'_, T>,
ty: InterfaceType,
dst: &mut MaybeUninit<Self::Lower>,
) -> Result<()> {
self.lower_to_index(cx, ty)?
.linear_lower_to_flat(cx, InterfaceType::U32, dst)
}
fn linear_lower_to_memory<T>(
&self,
cx: &mut LowerContext<'_, T>,
ty: InterfaceType,
offset: usize,
) -> Result<()> {
self.lower_to_index(cx, ty)?
.linear_lower_to_memory(cx, InterfaceType::U32, offset)
}
}
unsafe impl Lift for StreamAny {
fn linear_lift_from_flat(
cx: &mut LiftContext<'_>,
ty: InterfaceType,
src: &Self::Lower,
) -> Result<Self> {
let index = u32::linear_lift_from_flat(cx, InterfaceType::U32, src)?;
Self::lift_from_index(cx, ty, index)
}
fn linear_lift_from_memory(
cx: &mut LiftContext<'_>,
ty: InterfaceType,
bytes: &[u8],
) -> Result<Self> {
let index = u32::linear_lift_from_memory(cx, InterfaceType::U32, bytes)?;
Self::lift_from_index(cx, ty, index)
}
}
#[derive(Debug, Clone)]
enum PayloadType<T> {
Guest(T),
Host {
id: TypeId,
typecheck: fn(Option<&InterfaceType>, &InstanceType<'_>) -> Result<()>,
},
}
impl<T: PartialEq> PartialEq for PayloadType<T> {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(PayloadType::Guest(a), PayloadType::Guest(b)) => a == b,
(PayloadType::Guest(_), _) => false,
(PayloadType::Host { id: a_id, .. }, PayloadType::Host { id: b_id, .. }) => {
a_id == b_id
}
(PayloadType::Host { .. }, _) => false,
}
}
}
impl PayloadType<FutureType> {
fn new_guest_future(
store: &StoreOpaque,
id: ComponentInstanceId,
ty: TypeFutureTableIndex,
) -> Self {
let types = InstanceType::new(&store.component_instance(id));
let ty = types.types[ty].ty;
PayloadType::Guest(FutureType::from(ty, &types))
}
}
impl PayloadType<StreamType> {
fn new_guest_stream(
store: &StoreOpaque,
id: ComponentInstanceId,
ty: TypeStreamTableIndex,
) -> Self {
let types = InstanceType::new(&store.component_instance(id));
let ty = types.types[ty].ty;
PayloadType::Guest(StreamType::from(ty, &types))
}
}
impl<T> PayloadType<T> {
fn new_host<P>() -> Self
where
P: ComponentType + 'static,
{
PayloadType::Host {
typecheck: types::typecheck_payload::<P>,
id: TypeId::of::<P>(),
}
}
fn typecheck_guest(
&self,
types: &InstanceType<'_>,
payload: Option<&InterfaceType>,
equivalent: fn(&T, &InstanceType<'_>, Option<&InterfaceType>) -> bool,
) -> Result<()> {
match self {
Self::Guest(ty) => {
if equivalent(ty, types, payload) {
Ok(())
} else {
bail!("future payload types differ")
}
}
Self::Host { typecheck, .. } => {
typecheck(payload, types).context("future payload types differ")
}
}
}
fn typecheck_host<P>(&self, equivalent: fn(&T) -> Result<()>) -> Result<()>
where
P: ComponentType + 'static,
{
match self {
Self::Guest(ty) => equivalent(ty),
Self::Host { id, .. } => {
if *id == TypeId::of::<P>() {
Ok(())
} else {
bail!("future payload types differ")
}
}
}
}
}
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,500 @@
use crate::prelude::*;
#[cfg(feature = "component-model-bytes")]
use bytes::{Bytes, BytesMut};
use core::mem::{self, MaybeUninit};
use core::slice;
// Inner module here to restrict possible readers of the fields of
// `UntypedWriteBuffer`.
pub use untyped::*;
mod untyped {
use super::WriteBuffer;
use crate::vm::SendSyncPtr;
use core::any::TypeId;
use core::marker;
use core::mem;
use core::ptr::NonNull;
/// Helper structure to type-erase the `T` in `WriteBuffer<T>`.
///
/// This is constructed with a `&mut dyn WriteBuffer<T>` and then can only
/// be viewed as `&mut dyn WriteBuffer<T>` as well. The `T`, however, is
/// carried through methods rather than the struct itself.
///
/// Note that this structure has a lifetime `'a` which forces an active
/// borrow on the original buffer passed in.
pub struct UntypedWriteBuffer<'a> {
element_type_id: TypeId,
buf: SendSyncPtr<dyn WriteBuffer<()>>,
_marker: marker::PhantomData<&'a mut dyn WriteBuffer<()>>,
}
/// Helper structure to transmute between `WriteBuffer<T>` and
/// `WriteBuffer<()>`.
union ReinterpretWriteBuffer<T> {
typed: *mut dyn WriteBuffer<T>,
untyped: *mut dyn WriteBuffer<()>,
}
impl<'a> UntypedWriteBuffer<'a> {
/// Creates a new `UntypedWriteBuffer` from the `buf` provided.
///
/// The returned value can be used with the `get_mut` method to get the
/// original write buffer back.
pub fn new<T: 'static>(buf: &'a mut dyn WriteBuffer<T>) -> UntypedWriteBuffer<'a> {
UntypedWriteBuffer {
element_type_id: TypeId::of::<T>(),
// SAFETY: this is `unsafe` due to reading union fields. That
// is safe here because `typed` and `untyped` have the same size
// and we're otherwise reinterpreting a raw pointer with a type
// parameter to one without one.
buf: SendSyncPtr::new(
NonNull::new(unsafe {
let r = ReinterpretWriteBuffer { typed: buf };
assert_eq!(mem::size_of_val(&r.typed), mem::size_of_val(&r.untyped));
r.untyped
})
.unwrap(),
),
_marker: marker::PhantomData,
}
}
/// Acquires the underlying `WriteBuffer<T>` this was created with.
///
/// # Panics
///
/// Panics if `T` does not match the type that this was created with.
pub fn get_mut<T: 'static>(&mut self) -> &mut dyn WriteBuffer<T> {
assert_eq!(self.element_type_id, TypeId::of::<T>());
// SAFETY: the `T` has been checked with `TypeId` and this
// structure also is proof of valid existence of the original
// `&mut WriteBuffer<T>`, so taking the raw pointer back to a safe
// reference is valid.
unsafe {
&mut *ReinterpretWriteBuffer {
untyped: self.buf.as_ptr(),
}
.typed
}
}
}
}
/// Trait representing a buffer which may be written to a `StreamWriter`.
///
/// See also [`crate::component::StreamProducer`].
///
/// # Unsafety
///
/// This trait is unsafe due to the contract of the `take` function. This trait
/// is only safe to implement if the `take` function is implemented correctly,
/// namely that all the items passed to the closure are fully initialized for
/// `T`.
pub unsafe trait WriteBuffer<T>: Send + Sync + 'static {
/// Slice of items remaining to be read.
fn remaining(&self) -> &[T];
/// Skip and drop the specified number of items.
fn skip(&mut self, count: usize);
/// Take ownership of the specified number of items.
///
/// This function will take `count` items from `self` and pass them as a
/// contiguous slice to the closure `fun` provided. The `fun` closure may
/// assume that the items are all fully initialized and available to read.
/// It is expected that `fun` will read all the items provided. Any items
/// that aren't read by `fun` will be leaked.
///
/// # Panics
///
/// Panics if `count` is larger than `self.remaining()`. If `fun` panics
/// then items may be leaked.
fn take(&mut self, count: usize, fun: &mut dyn FnMut(&[MaybeUninit<T>]));
}
/// Trait representing a buffer which may be used to read from a `StreamReader`.
///
/// See also [`crate::component::Source`].
pub trait ReadBuffer<T>: Send + Sync + 'static {
/// Move the specified items into this buffer.
fn extend<I: IntoIterator<Item = T>>(&mut self, iter: I);
/// Number of items which may be read before this buffer is full.
fn remaining_capacity(&self) -> usize;
/// Move (i.e. take ownership of) the specified items into this buffer.
///
/// This method will drain `count` items from the `input` provided and move
/// ownership into this buffer.
///
/// # Panics
///
/// This method will panic if `count` is larger than
/// `self.remaining_capacity()` or if it's larger than `input.remaining()`.
fn move_from(&mut self, input: &mut dyn WriteBuffer<T>, count: usize);
}
pub(super) struct Extender<'a, B>(pub(super) &'a mut B);
impl<T, B: ReadBuffer<T>> Extend<T> for Extender<'_, B> {
fn extend<I: IntoIterator<Item = T>>(&mut self, iter: I) {
self.0.extend(iter)
}
}
// SAFETY: the `take` implementation below guarantees that the `fun` closure is
// provided with fully initialized items.
unsafe impl<T: Send + Sync + 'static> WriteBuffer<T> for Option<T> {
fn remaining(&self) -> &[T] {
if let Some(me) = self {
slice::from_ref(me)
} else {
&[]
}
}
fn skip(&mut self, count: usize) {
match count {
0 => {}
1 => {
assert!(self.is_some());
*self = None;
}
_ => panic!("cannot skip more than {} item(s)", self.remaining().len()),
}
}
fn take(&mut self, count: usize, fun: &mut dyn FnMut(&[MaybeUninit<T>])) {
match count {
0 => fun(&mut []),
1 => {
let mut item = MaybeUninit::new(self.take().unwrap());
fun(slice::from_mut(&mut item));
}
_ => panic!("cannot forget more than {} item(s)", self.remaining().len()),
}
}
}
impl<T: Send + Sync + 'static> ReadBuffer<T> for Option<T> {
fn extend<I: IntoIterator<Item = T>>(&mut self, iter: I) {
let mut iter = iter.into_iter();
if self.is_none() {
*self = iter.next();
}
assert!(iter.next().is_none());
}
fn remaining_capacity(&self) -> usize {
if self.is_some() { 0 } else { 1 }
}
fn move_from(&mut self, input: &mut dyn WriteBuffer<T>, count: usize) {
match count {
0 => {}
1 => {
assert!(self.is_none());
input.take(1, &mut |slice| {
// SAFETY: Per the `WriteBuffer` trait contract this block
// has ownership of the items in `slice` and they're all
// valid to take.
unsafe {
*self = Some(slice[0].assume_init_read());
}
});
}
_ => panic!(
"cannot take more than {} item(s)",
self.remaining_capacity()
),
}
}
}
/// A `WriteBuffer` implementation, backed by a `Vec<u8>`, a position, and a limit.
pub struct SliceBuffer {
buffer: Vec<u8>,
offset: usize,
limit: usize,
}
impl SliceBuffer {
pub fn new(buffer: Vec<u8>, offset: usize, limit: usize) -> Self {
assert!(offset <= limit);
assert!(limit <= buffer.len());
Self {
buffer,
offset,
limit,
}
}
pub fn into_parts(self) -> (Vec<u8>, usize, usize) {
(self.buffer, self.offset, self.limit)
}
}
// SAFETY: the `take` implementation below guarantees that the `fun` closure is
// provided with fully initialized items due to all elements in the slice being
// initialized.
unsafe impl WriteBuffer<u8> for SliceBuffer {
fn remaining(&self) -> &[u8] {
&self.buffer[self.offset..self.limit]
}
fn skip(&mut self, count: usize) {
assert!(self.offset + count <= self.limit);
self.offset += count;
}
fn take(&mut self, count: usize, fun: &mut dyn FnMut(&[MaybeUninit<u8>])) {
assert!(count <= self.remaining().len());
self.offset += count;
// SAFETY: Transmuting from `&[u8]` to `&[MaybeUninit<u8>]` should
// always be sound.
fun(unsafe {
mem::transmute::<&[u8], &[MaybeUninit<u8>]>(
&self.buffer[self.offset - count..self.offset],
)
});
}
}
/// A `WriteBuffer` implementation, backed by a `Vec`.
pub struct VecBuffer<T> {
buffer: Vec<MaybeUninit<T>>,
offset: usize,
}
impl<T> Default for VecBuffer<T> {
fn default() -> Self {
Self::with_capacity(0)
}
}
impl<T> VecBuffer<T> {
/// Create a new instance with the specified capacity.
pub fn with_capacity(capacity: usize) -> Self {
Self {
buffer: Vec::with_capacity(capacity),
offset: 0,
}
}
/// Reset the state of this buffer, removing all items and preserving its
/// capacity.
pub fn reset(&mut self) {
self.skip_(self.remaining_().len());
self.buffer.clear();
self.offset = 0;
}
fn remaining_(&self) -> &[T] {
// SAFETY: This relies on the invariant (upheld in the other methods of
// this type) that all the elements from `self.offset` onward are
// initialized and valid for `self.buffer`.
unsafe { mem::transmute::<&[MaybeUninit<T>], &[T]>(&self.buffer[self.offset..]) }
}
fn skip_(&mut self, count: usize) {
assert!(count <= self.remaining_().len());
for item in &mut self.buffer[self.offset..][..count] {
// Note that the offset is incremented first here to ensure that if
// any destructors panic we don't attempt to re-drop the item.
self.offset += 1;
// SAFETY: See comment in `Self::remaining`
unsafe {
item.assume_init_drop();
}
}
}
}
// SAFETY: the `take` implementation below guarantees that the `fun` closure is
// provided with fully initialized items due to `self.offset`-and-onwards being
// always initialized.
unsafe impl<T: Send + Sync + 'static> WriteBuffer<T> for VecBuffer<T> {
fn remaining(&self) -> &[T] {
self.remaining_()
}
fn skip(&mut self, count: usize) {
self.skip_(count)
}
fn take(&mut self, count: usize, fun: &mut dyn FnMut(&[MaybeUninit<T>])) {
assert!(count <= self.remaining().len());
// Note that the offset here is incremented before `fun` is called to
// ensure that if `fun` panics that the items are still considered
// transferred.
self.offset += count;
fun(&self.buffer[self.offset - count..self.offset]);
}
}
impl<T> From<Vec<T>> for VecBuffer<T> {
fn from(buffer: Vec<T>) -> Self {
Self {
// SAFETY: Transmuting from `Vec<T>` to `Vec<MaybeUninit<T>>` should
// be sound for any `T`.
buffer: unsafe { mem::transmute::<Vec<T>, Vec<MaybeUninit<T>>>(buffer) },
offset: 0,
}
}
}
impl<T> Drop for VecBuffer<T> {
fn drop(&mut self) {
self.reset();
}
}
impl<T: Send + Sync + 'static> ReadBuffer<T> for Vec<T> {
fn extend<I: IntoIterator<Item = T>>(&mut self, iter: I) {
Extend::extend(self, iter)
}
fn remaining_capacity(&self) -> usize {
self.capacity().checked_sub(self.len()).unwrap()
}
fn move_from(&mut self, input: &mut dyn WriteBuffer<T>, count: usize) {
assert!(count <= self.remaining_capacity());
input.take(count, &mut |slice| {
for item in slice {
// SAFETY: Per the `WriteBuffer` implementation contract this
// function has exclusive ownership of all items in `slice` so
// this is safe to take and transfer them here.
self.push(unsafe { item.assume_init_read() });
}
});
}
}
// SAFETY: the `take` implementation below guarantees that the `fun` closure is
// provided with fully initialized items.
#[cfg(feature = "component-model-bytes")]
unsafe impl WriteBuffer<u8> for Bytes {
fn remaining(&self) -> &[u8] {
self
}
fn skip(&mut self, count: usize) {
let _prefix = self.split_to(count);
}
fn take(&mut self, count: usize, fun: &mut dyn FnMut(&[MaybeUninit<u8>])) {
let prefix = self.split_to(count);
fun(unsafe_byte_slice(&prefix));
}
}
// SAFETY: the `take` implementation below guarantees that the `fun` closure is
// provided with fully initialized items.
#[cfg(feature = "component-model-bytes")]
unsafe impl WriteBuffer<u8> for BytesMut {
fn remaining(&self) -> &[u8] {
self
}
fn skip(&mut self, count: usize) {
let _prefix = self.split_to(count);
}
fn take(&mut self, count: usize, fun: &mut dyn FnMut(&[MaybeUninit<u8>])) {
let prefix = self.split_to(count);
fun(unsafe_byte_slice(&prefix));
}
}
#[cfg(feature = "component-model-bytes")]
impl ReadBuffer<u8> for BytesMut {
fn extend<I: IntoIterator<Item = u8>>(&mut self, iter: I) {
Extend::extend(self, iter)
}
fn remaining_capacity(&self) -> usize {
self.capacity().checked_sub(self.len()).unwrap()
}
fn move_from(&mut self, input: &mut dyn WriteBuffer<u8>, count: usize) {
assert!(count <= self.remaining_capacity());
input.take(count, &mut |slice| {
// SAFETY: per the contract of `WriteBuffer` all the elements of
// the input `slice` are fully initialized so this is safe
// to reinterpret the slice.
let slice = unsafe { mem::transmute::<&[MaybeUninit<u8>], &[u8]>(slice) };
self.extend_from_slice(slice);
});
}
}
#[cfg(feature = "component-model-bytes")]
fn unsafe_byte_slice(slice: &[u8]) -> &[MaybeUninit<u8>] {
// SAFETY: it's always safe to interpret a slice of items as a
// possibly-initialized slice of items.
unsafe { mem::transmute::<&[u8], &[MaybeUninit<u8>]>(slice) }
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_vec_buffer_take() {
let mut buf = VecBuffer::from(vec!["a".to_string(), "b".to_string(), "c".to_string()]);
let mut dst = Vec::new();
dst.reserve(1);
dst.move_from(&mut buf, 1);
assert_eq!(buf.remaining().len(), 2);
assert_eq!(dst.len(), 1);
None.move_from(&mut buf, 1);
assert_eq!(buf.remaining().len(), 1);
assert_eq!(dst.len(), 1);
}
#[test]
fn test_slice_buffer_take() {
let mut buf = SliceBuffer::new(vec![1, 2, 3], 0, 3);
let mut dst = Vec::new();
dst.reserve(1);
dst.move_from(&mut buf, 1);
assert_eq!(buf.remaining().len(), 2);
assert_eq!(dst.len(), 1);
}
#[test]
#[cfg(feature = "component-model-bytes")]
fn test_cursor_bytes_take() {
let mut buf = Bytes::from(&b"123"[..]);
let mut dst = Vec::new();
dst.reserve(1);
dst.move_from(&mut buf, 1);
assert_eq!(buf.remaining().len(), 2);
assert_eq!(dst.len(), 1);
let mut dst = BytesMut::new();
dst.reserve(1);
dst.move_from(&mut buf, 1);
assert_eq!(buf.remaining().len(), 1);
assert_eq!(dst.len(), 1);
}
#[test]
#[cfg(feature = "component-model-bytes")]
fn test_cursor_bytes_mut_take() {
let mut buf = BytesMut::from(&b"123"[..]);
let mut dst = Vec::new();
dst.reserve(1);
dst.move_from(&mut buf, 1);
assert_eq!(buf.remaining().len(), 2);
assert_eq!(dst.len(), 1);
let mut dst = BytesMut::new();
dst.reserve(1);
dst.move_from(&mut buf, 1);
assert_eq!(buf.remaining().len(), 1);
assert_eq!(dst.len(), 1);
}
}
@@ -0,0 +1,89 @@
use crate::component::Resource;
use core::cmp::Ordering;
use core::fmt;
use core::hash::{Hash, Hasher};
use core::marker::PhantomData;
/// Represents a `ResourceTable` entry for a `waitable` or `waitable-set`.
///
/// This is equivalent to a `Resource<T>`, except without any tracking of borrow
/// status (since neither `waitable`s nor `waitable-set`s can be borrowed) or
/// other resource-specific bookkeeping.
pub struct TableId<T> {
rep: u32,
_marker: PhantomData<fn() -> T>,
}
pub trait TableDebug {
fn type_name() -> &'static str;
}
impl<T: TableDebug> fmt::Debug for TableId<T> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}({})", T::type_name(), self.rep)
}
}
impl<T> Hash for TableId<T> {
fn hash<H: Hasher>(&self, state: &mut H) {
self.rep.hash(state)
}
}
impl<T> PartialEq for TableId<T> {
fn eq(&self, other: &Self) -> bool {
self.rep == other.rep
}
}
impl<T> Eq for TableId<T> {}
impl<T> PartialOrd for TableId<T> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
self.rep.partial_cmp(&other.rep)
}
}
impl<T> Ord for TableId<T> {
fn cmp(&self, other: &Self) -> Ordering {
self.rep.cmp(&other.rep)
}
}
impl<T> TableId<T> {
pub fn new(rep: u32) -> Self {
Self {
rep,
_marker: PhantomData,
}
}
}
impl<T> Clone for TableId<T> {
fn clone(&self) -> Self {
Self::new(self.rep)
}
}
impl<T> Copy for TableId<T> {}
impl<T> TableId<T> {
pub fn rep(&self) -> u32 {
self.rep
}
}
impl<T: 'static> From<Resource<T>> for TableId<T> {
fn from(value: Resource<T>) -> Self {
Self {
rep: value.rep(),
_marker: PhantomData,
}
}
}
impl<T: 'static> From<TableId<T>> for Resource<T> {
fn from(value: TableId<T>) -> Self {
Resource::new_own(value.rep)
}
}
@@ -0,0 +1,240 @@
//! A small self-contained module to manage passing a `&mut dyn VMStore` across
//! function boundaries without it actually being a function parameter.
//!
//! Much of concurrent.rs and futures_and_streams.rs work with `Future` which
//! does not allow customizing state being passed to each poll of a future. In
//! Wasmtime, however, the mutable store is available during a calls to
//! `Future::poll`, but not across calls of `Future::poll`. That means that
//! effectively what we would ideally want is to thread `&mut dyn VMStore` as a
//! parameter to futures, but that's not possible with Rust's future trait.
//!
//! This module is the workaround to otherwise enable this which is to use
//! thread-local-storage instead to pass around this pointer. The goal of this
//! module is to enable the `set` API to pretend like it's passing a pointer as
//! a parameter to a closure and then `get` can be called to acquire this
//! parameter. This module is intentionally small and isolated to keep the
//! internal implementation details private and reduce the surface area that
//! must be audited for the `unsafe` blocks contained within.
use crate::runtime::vm::VMStore;
use crate::vm::{component_async_tls_get, component_async_tls_set};
use core::mem;
use core::ptr::NonNull;
fn tls_get() -> Option<NonNull<SetStorage>> {
NonNull::new(component_async_tls_get().cast())
}
fn tls_set(val: Option<NonNull<SetStorage>>) {
component_async_tls_set(match val {
Some(v) => v.as_ptr().cast(),
None => core::ptr::null_mut(),
})
}
enum SetStorage {
Present(NonNull<dyn VMStore>),
Taken,
}
/// Configures `store` to be available for the duration of `f` through calls to
/// the [`get`] function below.
///
/// This function will replace any prior state that was configured and overwrite
/// it. Upon `f` returning the previous state will be restored. This function
/// intentionally borrows `store` for the entire duration of `f` meaning that
/// `f` is not allowed to access `store` via Rust's borrow checker.
pub fn set<R>(store: &mut dyn VMStore, f: impl FnOnce() -> R) -> R {
let mut storage = SetStorage::Present(NonNull::from(store));
let _reset = ResetTls(component_async_tls_get());
tls_set(Some(NonNull::from(&mut storage)));
return f();
struct ResetTls(*mut u8);
impl Drop for ResetTls {
fn drop(&mut self) {
component_async_tls_set(self.0);
}
}
}
/// Acquires a reference to the previous store configured via [`set`] above,
/// yielding this reference to the closure `f provided here.
///
/// This function will "take" the store from thread-local-storage for the
/// duration of the `get` function here. This "take" operation means that
/// recursive calls to `get` here will fail as the second one won't be able to
/// re-acquire the same pointer the first one has (due to it having `&mut`
/// exclusive access.
///
/// # Panics
///
/// This function will panic if [`set`] has not been previously called or if the
/// current pointer is taken by a previous call to [`get`] on the stack.
pub fn get<R>(f: impl FnOnce(&mut dyn VMStore) -> R) -> R {
try_get(|val| match val {
TryGet::Some(store) => f(store),
TryGet::None => get_failed(false),
TryGet::Taken => get_failed(true),
})
}
#[cold]
fn get_failed(taken: bool) -> ! {
if taken {
panic!(
"attempted to recursively call `Accessor::with` when the pointer \
was already taken by a previous call to `Accessor::with`; try \
using `RUST_BACKTRACE=1` to find two stack frames to \
`Accessor::with` on the stack"
);
} else {
panic!(
"`Accessor::with` was called when the TLS pointer was not \
previously set; this is likely a bug in Wasmtime and we would \
appreciate an issue being filed to help fix this."
);
}
}
/// Values yielded to the [`try_get`] closure as an argument.
pub enum TryGet<'a> {
/// The [`set`] API was not previously called, so there is no store
/// available at all.
None,
/// The [`set`] API was previously called but it was then subsequently taken
/// via a call to [`get`] meaning it's not available.
Taken,
/// The [`set`] API was previously called and this is the store that it was
/// called with.
Some(&'a mut dyn VMStore),
}
/// Same as [`get`] except that this does not panic if `set` has not been
/// called.
pub fn try_get<R>(f: impl FnOnce(TryGet<'_>) -> R) -> R {
// SAFETY: This is The Unsafe Block of this module on which everything
// hinges. The overall idea is that the pointer previously provided to
// `set` is passed to the closure here but only at most once because it's
// passed mutably. Thus there's a number of things that this takes care of:
//
// * The lifetime in `TryGet` that's handed out is anonymous via the
// type signature of `f`, meaning that it cannot be safely persisted
// outside that closure. That means that once `f` is returned this
// function has exclusive access to the store again.
//
// * If TLS is not set then that means `set` has not been configured,
// thus `TryGet::None` is yielded.
//
// * If TLS is set then we're guaranteed it's set for the entire
// lifetime of this function call, and we're also guaranteed that the
// pointer stored in there is the same pointer we'll be modifying for
// this whole function call.
//
// * The TLS pointer is read/written only in a scoped manner here and
// borrows of this value are not persisted for very long.
//
// With all of that put together it should make it such that this is a safe
// reborrow of the store provided to `set` to pass to the closure `f` here.
unsafe {
let storage = tls_get();
let _reset;
let val = match storage {
Some(mut storage) => match mem::replace(storage.as_mut(), SetStorage::Taken) {
SetStorage::Taken => TryGet::Taken,
SetStorage::Present(mut ptr) => {
_reset = ResetStorage(storage, ptr);
TryGet::Some(ptr.as_mut())
}
},
None => TryGet::None,
};
return f(val);
}
struct ResetStorage(NonNull<SetStorage>, NonNull<dyn VMStore>);
impl Drop for ResetStorage {
fn drop(&mut self) {
unsafe {
*self.0.as_mut() = SetStorage::Present(self.1);
}
}
}
}
#[cfg(test)]
mod tests {
use super::{TryGet, get, set, try_get};
use crate::{AsContextMut, Engine, Store};
#[test]
fn test_simple() {
let engine = Engine::default();
let mut store = Store::new(&engine, ());
set(store.as_context_mut().0, || {
get(|_| {});
try_get(|t| {
assert!(matches!(t, TryGet::Some(_)));
});
});
}
#[test]
fn test_try_get() {
let engine = Engine::default();
let mut store = Store::new(&engine, ());
try_get(|t| {
assert!(matches!(t, TryGet::None));
try_get(|t| {
assert!(matches!(t, TryGet::None));
});
});
set(store.as_context_mut().0, || {
get(|_| {
try_get(|t| {
assert!(matches!(t, TryGet::Taken));
try_get(|t| {
assert!(matches!(t, TryGet::Taken));
});
});
});
try_get(|t| {
assert!(matches!(t, TryGet::Some(_)));
try_get(|t| {
assert!(matches!(t, TryGet::Taken));
try_get(|t| {
assert!(matches!(t, TryGet::Taken));
});
});
});
try_get(|t| {
assert!(matches!(t, TryGet::Some(_)));
try_get(|t| {
assert!(matches!(t, TryGet::Taken));
});
});
});
try_get(|t| {
assert!(matches!(t, TryGet::None));
});
}
#[test]
#[should_panic(expected = "attempted to recursively call")]
fn test_get_panic() {
let engine = Engine::default();
let mut store = Store::new(&engine, ());
set(store.as_context_mut().0, || {
get(|_| {
get(|_| {
panic!("should not get here");
});
});
});
}
}
@@ -0,0 +1,176 @@
use crate::component::func::{LiftContext, LowerContext};
use crate::component::matching::InstanceType;
use crate::component::{ComponentType, Lift, Lower, RuntimeInstance, Val};
use crate::store::StoreOpaque;
use crate::{Result, bail, error::format_err};
use core::convert::Infallible;
use core::mem::MaybeUninit;
use wasmtime_environ::component::{CanonicalAbiInfo, InterfaceType};
fn should_have_failed_validation<T>(what: &str) -> Result<T> {
// This should be unreachable; if we trap here, it indicates a
// bug in Wasmtime rather than in the guest.
Err(format_err!(
"{what} should have failed validation \
when `component-model-async` feature disabled"
))
}
pub(crate) fn lower_error_context_to_index<U>(
_rep: u32,
_cx: &mut LowerContext<'_, U>,
_ty: InterfaceType,
) -> Result<u32> {
should_have_failed_validation("use of `error-context`")
}
pub struct ErrorContext(Infallible);
impl ErrorContext {
pub(crate) fn into_val(self) -> Val {
match self.0 {}
}
pub(crate) fn linear_lift_from_flat(
_cx: &mut LiftContext<'_>,
_ty: InterfaceType,
_src: &<u32 as ComponentType>::Lower,
) -> Result<Self> {
should_have_failed_validation("use of `error-context`")
}
pub(crate) fn linear_lift_from_memory(
_cx: &mut LiftContext<'_>,
_ty: InterfaceType,
_bytes: &[u8],
) -> Result<Self> {
should_have_failed_validation("use of `error-context`")
}
}
#[derive(PartialEq, Clone, Debug)]
pub struct FutureAny(Infallible);
unsafe impl ComponentType for FutureAny {
type Lower = <u32 as ComponentType>::Lower;
const ABI: CanonicalAbiInfo = CanonicalAbiInfo::SCALAR4;
fn typecheck(_ty: &InterfaceType, _types: &InstanceType<'_>) -> Result<()> {
bail!("support for component-model-async disabled at compile time")
}
}
unsafe impl Lift for FutureAny {
fn linear_lift_from_flat(
_cx: &mut LiftContext<'_>,
_ty: InterfaceType,
_src: &Self::Lower,
) -> Result<Self> {
bail!("support for component-model-async disabled at compile time")
}
fn linear_lift_from_memory(
_cx: &mut LiftContext<'_>,
_ty: InterfaceType,
_bytes: &[u8],
) -> Result<Self> {
bail!("support for component-model-async disabled at compile time")
}
}
unsafe impl Lower for FutureAny {
fn linear_lower_to_flat<T>(
&self,
_cx: &mut LowerContext<'_, T>,
_ty: InterfaceType,
_dst: &mut MaybeUninit<Self::Lower>,
) -> Result<()> {
match self.0 {}
}
fn linear_lower_to_memory<T>(
&self,
_cx: &mut LowerContext<'_, T>,
_ty: InterfaceType,
_offset: usize,
) -> Result<()> {
match self.0 {}
}
}
#[derive(PartialEq, Clone, Debug)]
pub struct StreamAny(Infallible);
unsafe impl ComponentType for StreamAny {
type Lower = <u32 as ComponentType>::Lower;
const ABI: CanonicalAbiInfo = CanonicalAbiInfo::SCALAR4;
fn typecheck(_ty: &InterfaceType, _types: &InstanceType<'_>) -> Result<()> {
bail!("support for component-model-async disabled at compile time")
}
}
unsafe impl Lift for StreamAny {
fn linear_lift_from_flat(
_cx: &mut LiftContext<'_>,
_ty: InterfaceType,
_src: &Self::Lower,
) -> Result<Self> {
bail!("support for component-model-async disabled at compile time")
}
fn linear_lift_from_memory(
_cx: &mut LiftContext<'_>,
_ty: InterfaceType,
_bytes: &[u8],
) -> Result<Self> {
bail!("support for component-model-async disabled at compile time")
}
}
unsafe impl Lower for StreamAny {
fn linear_lower_to_flat<T>(
&self,
_cx: &mut LowerContext<'_, T>,
_ty: InterfaceType,
_dst: &mut MaybeUninit<Self::Lower>,
) -> Result<()> {
match self.0 {}
}
fn linear_lower_to_memory<T>(
&self,
_cx: &mut LowerContext<'_, T>,
_ty: InterfaceType,
_offset: usize,
) -> Result<()> {
match self.0 {}
}
}
impl StoreOpaque {
pub(crate) fn enter_guest_sync_call(
&mut self,
_guest_caller: Option<RuntimeInstance>,
_callee_async: bool,
_callee: RuntimeInstance,
) -> Result<()> {
self.enter_call_not_concurrent()
}
pub(crate) fn exit_guest_sync_call(&mut self) -> Result<()> {
Ok(self.exit_call_not_concurrent())
}
pub(crate) fn host_task_create(&mut self) -> Result<()> {
self.enter_call_not_concurrent()
}
pub(crate) fn host_task_delete(&mut self, (): ()) -> Result<()> {
Ok(self.exit_call_not_concurrent())
}
pub(crate) fn current_scope_id(&mut self) -> Result<Option<u32>> {
self.current_scope_id_not_concurrent()
}
}
@@ -0,0 +1,746 @@
use crate::component::instance::Instance;
use crate::component::matching::InstanceType;
use crate::component::storage::storage_as_slice;
use crate::component::types::ComponentFunc;
use crate::component::values::Val;
use crate::prelude::*;
use crate::runtime::vm::component::{ComponentInstance, InstanceFlags};
use crate::runtime::vm::{Export, SendSyncPtr, VMFuncRef};
use crate::store::StoreOpaque;
use crate::{AsContext, AsContextMut, StoreContextMut, ValRaw};
use core::mem::{self, MaybeUninit};
use core::ptr::NonNull;
use wasmtime_environ::component::{
CanonicalOptions, ExportIndex, InterfaceType, MAX_FLAT_PARAMS, MAX_FLAT_RESULTS, OptionsIndex,
TypeFuncIndex, TypeTuple,
};
mod host;
mod options;
mod typed;
pub use self::host::*;
pub use self::options::*;
pub use self::typed::*;
/// A WebAssembly component function which can be called.
///
/// This type is the dual of [`wasmtime::Func`](crate::Func) for component
/// functions. An instance of [`Func`] represents a component function from a
/// component [`Instance`](crate::component::Instance). Like with
/// [`wasmtime::Func`](crate::Func) it's possible to call functions either
/// synchronously or asynchronously and either typed or untyped.
#[derive(Copy, Clone, Debug)]
#[repr(C)] // here for the C API.
pub struct Func {
instance: Instance,
/// The export index of this lifted function within its component.
index: ExportIndex,
/// The resolved core `VMFuncRef` for this lifted function, whose lifetime
/// is bound to the `Store` this `Func` belongs to.
///
/// Note that this field has an `unsafe_*` prefix to discourage use of it.
/// This is only safe to read/use if the store that owns `instance`
/// (identified by `instance.id().store_id()`) is in scope. Use the
/// `self.lifted_core_func()` method instead of this field to perform this
/// check.
unsafe_func_ref: SendSyncPtr<VMFuncRef>,
}
// Double-check that the C representation in `component/func.h` matches our
// in-Rust representation here in terms of size/alignment/etc.
const _: () = {
#[repr(C)]
struct T(u64, u32);
#[repr(C)]
struct C(T, u32, *mut u8);
assert!(core::mem::size_of::<C>() == core::mem::size_of::<Func>());
assert!(core::mem::align_of::<C>() == core::mem::align_of::<Func>());
assert!(core::mem::offset_of!(Func, instance) == 0);
};
impl Func {
pub(crate) fn from_lifted_func(
store: &mut StoreOpaque,
instance: Instance,
index: ExportIndex,
) -> Func {
let def = {
let vminstance = instance.id().get(store);
let (_ty, def, _options) = vminstance.component().export_lifted_function(index);
def.clone()
};
let unsafe_func_ref = match instance.lookup_vmdef(store, &def) {
Export::Function(f) => f.vm_func_ref(store),
_ => unreachable!(),
}
.into();
Func {
instance,
index,
unsafe_func_ref,
}
}
/// Attempt to cast this [`Func`] to a statically typed [`TypedFunc`] with
/// the provided `Params` and `Return`.
///
/// This function will perform a type-check at runtime that the [`Func`]
/// takes `Params` as parameters and returns `Return`. If the type-check
/// passes then a [`TypedFunc`] will be returned which can be used to
/// invoke the function in an efficient, statically-typed, and ergonomic
/// manner.
///
/// The `Params` type parameter here is a tuple of the parameters to the
/// function. A function which takes no arguments should use `()`, a
/// function with one argument should use `(T,)`, etc. Note that all
/// `Params` must also implement the [`Lower`] trait since they're going
/// into wasm.
///
/// The `Return` type parameter is the return value of this function. A
/// return value of `()` means that there's no return (similar to a Rust
/// unit return) and otherwise a type `T` can be specified. Note that the
/// `Return` must also implement the [`Lift`] trait since it's coming from
/// wasm.
///
/// Types specified here must implement the [`ComponentType`] trait. This
/// trait is implemented for built-in types to Rust such as integer
/// primitives, floats, `Option<T>`, `Result<T, E>`, strings, `Vec<T>`, and
/// more. As parameters you'll be passing native Rust types.
///
/// See the documentation for [`ComponentType`] for more information about
/// supported types.
///
/// # Errors
///
/// If the function does not actually take `Params` as its parameters or
/// return `Return` then an error will be returned.
///
/// This function will return an [`OutOfMemory`][crate::OutOfMemory] error when
/// memory allocation fails. See the `OutOfMemory` type's documentation for
/// details on Wasmtime's out-of-memory handling.
///
/// # Panics
///
/// This function will panic if `self` is not owned by the `store`
/// specified.
///
/// # Examples
///
/// Calling a function which takes no parameters and has no return value:
///
/// ```
/// # use wasmtime::component::Func;
/// # use wasmtime::Store;
/// # fn foo(func: &Func, store: &mut Store<()>) -> wasmtime::Result<()> {
/// let typed = func.typed::<(), ()>(&store)?;
/// typed.call(store, ())?;
/// # Ok(())
/// # }
/// ```
///
/// Calling a function which takes one string parameter and returns a
/// string:
///
/// ```
/// # use wasmtime::component::Func;
/// # use wasmtime::Store;
/// # fn foo(func: &Func, mut store: Store<()>) -> wasmtime::Result<()> {
/// let typed = func.typed::<(&str,), (String,)>(&store)?;
/// let ret = typed.call(&mut store, ("Hello, ",))?.0;
/// println!("returned string was: {}", ret);
/// # Ok(())
/// # }
/// ```
///
/// Calling a function which takes multiple parameters and returns a boolean:
///
/// ```
/// # use wasmtime::component::Func;
/// # use wasmtime::Store;
/// # fn foo(func: &Func, mut store: Store<()>) -> wasmtime::Result<()> {
/// let typed = func.typed::<(u32, Option<&str>, &[u8]), (bool,)>(&store)?;
/// let ok: bool = typed.call(&mut store, (1, Some("hello"), b"bytes!"))?.0;
/// println!("return value was: {ok}");
/// # Ok(())
/// # }
/// ```
pub fn typed<Params, Return>(&self, store: impl AsContext) -> Result<TypedFunc<Params, Return>>
where
Params: ComponentNamedList + Lower,
Return: ComponentNamedList + Lift,
{
self._typed(store.as_context().0, None)
}
pub(crate) fn _typed<Params, Return>(
&self,
store: &StoreOpaque,
instance: Option<&ComponentInstance>,
) -> Result<TypedFunc<Params, Return>>
where
Params: ComponentNamedList + Lower,
Return: ComponentNamedList + Lift,
{
self.typecheck::<Params, Return>(store, instance)?;
unsafe { Ok(TypedFunc::new_unchecked(*self)) }
}
fn typecheck<Params, Return>(
&self,
store: &StoreOpaque,
instance: Option<&ComponentInstance>,
) -> Result<()>
where
Params: ComponentNamedList + Lower,
Return: ComponentNamedList + Lift,
{
let cx = InstanceType::new(instance.unwrap_or_else(|| self.instance.id().get(store)));
let ty = &cx.types[self.ty_index(store)];
Params::typecheck(&InterfaceType::Tuple(ty.params), &cx)
.context("type mismatch with parameters")?;
Return::typecheck(&InterfaceType::Tuple(ty.results), &cx)
.context("type mismatch with results")?;
Ok(())
}
/// Get the type of this function.
pub fn ty(&self, store: impl AsContext) -> ComponentFunc {
let store = store.as_context().0;
let cx = InstanceType::new(self.instance.id().get(store));
let ty = self.ty_index(store);
ComponentFunc::from(ty, &cx)
}
fn ty_index(&self, store: &StoreOpaque) -> TypeFuncIndex {
let instance = self.instance.id().get(store);
let (ty, _, _) = instance.component().export_lifted_function(self.index);
ty
}
/// Invokes this function with the `params` given and returns the result.
///
/// The `params` provided must match the parameters that this function takes
/// in terms of their types and the number of parameters. Results will be
/// written to the `results` slice provided if the call completes
/// successfully. The initial types of the values in `results` are ignored
/// and values are overwritten to write the result. It's required that the
/// size of `results` exactly matches the number of results that this
/// function produces.
///
/// This will also call the corresponding `post-return` function, if any.
///
/// For more detailed information see the documentation of
/// [`TypedFunc::call`].
///
/// # Errors
///
/// Returns an error in situations including but not limited to:
///
/// * `params` is not the right size or if the values have the wrong type
/// * `results` is not the right size
/// * A trap occurs while executing the function
/// * The function calls a host function which returns an error
/// * The `store` used requires the use of [`Func::call_async`] instead. See
/// [store documentation](crate#async) for more information.
///
/// See [`TypedFunc::call`] for more information in addition to
/// [`wasmtime::Func::call`](crate::Func::call).
///
/// This function will return an [`OutOfMemory`][crate::OutOfMemory] error when
/// memory allocation fails. See the `OutOfMemory` type's documentation for
/// details on Wasmtime's out-of-memory handling.
///
/// # Panics
///
/// Panics if `store` does not own this function.
pub fn call(
&self,
mut store: impl AsContextMut,
params: &[Val],
results: &mut [Val],
) -> Result<()> {
let mut store = store.as_context_mut();
store.0.validate_sync_call()?;
self.call_impl(store.as_context_mut(), params, results)?;
Ok(())
}
/// Exactly like [`Self::call`] except for use on async stores.
///
/// # Errors
///
/// This function will return an [`OutOfMemory`][crate::OutOfMemory] error when
/// memory allocation fails. See the `OutOfMemory` type's documentation for
/// details on Wasmtime's out-of-memory handling.
///
/// # Panics
///
/// Panics if `store` does not own this function.
#[cfg(feature = "async")]
pub async fn call_async(
&self,
mut store: impl AsContextMut<Data: Send>,
params: &[Val],
results: &mut [Val],
) -> Result<()> {
let mut store = store.as_context_mut();
#[cfg(feature = "component-model-async")]
if store.0.concurrency_support() {
let call = self.start_call_concurrent(&mut store, params, results)?;
return store
.run_concurrent_trap_on_idle(async |store| {
self.finish_call_concurrent(store, call).await
})
.await?;
}
store
.on_fiber(|store| self.call_impl(store, params, results))
.await?
}
pub(crate) fn check_params_results<T>(
&self,
store: StoreContextMut<T>,
params: &[Val],
results: &mut [Val],
) -> Result<()> {
let ty = self.ty(&store);
if ty.params().len() != params.len() {
bail!(
"expected {} argument(s), got {}",
ty.params().len(),
params.len(),
);
}
if ty.results().len() != results.len() {
bail!(
"expected {} result(s), got {}",
ty.results().len(),
results.len(),
);
}
Ok(())
}
fn call_impl(
&self,
mut store: impl AsContextMut,
params: &[Val],
results: &mut [Val],
) -> Result<()> {
let mut store = store.as_context_mut();
self.check_params_results(store.as_context_mut(), params, results)?;
if self.abi_async(store.0) {
unreachable!(
"async-lifted exports should have failed validation \
when `component-model-async` feature disabled"
);
}
// SAFETY: the chosen representations of type parameters to `call_raw`
// here should be generally safe to work with:
//
// * parameters use `MaybeUninit<[MaybeUninit<ValRaw>; MAX_FLAT_PARAMS]>`
// which represents the maximal possible number of parameters that can
// be passed to lifted component functions. This is modeled with
// `MaybeUninit` to represent how it all starts as uninitialized and
// thus can't be safely read during lowering.
//
// * results are modeled as `[ValRaw; MAX_FLAT_RESULTS]` which
// represents the maximal size of values that can be returned. Note
// that if the function doesn't actually have a return value then the
// `ValRaw` inside the array will have undefined contents. That is
// safe in Rust, however, due to `ValRaw` being a `union`. The
// contents should dynamically not be read due to the type of the
// function used here matching the actual lift.
let result = unsafe {
self.call_raw(
store.as_context_mut(),
|cx, ty, dst: &mut MaybeUninit<[MaybeUninit<ValRaw>; MAX_FLAT_PARAMS]>| {
// SAFETY: it's safe to assume that
// `MaybeUninit<array-of-maybe-uninit>` is initialized because
// each individual element is still considered uninitialized.
let dst: &mut [MaybeUninit<ValRaw>] = dst.assume_init_mut();
Self::lower_args(cx, params, ty, dst)
},
|cx, results_ty, src: &[ValRaw; MAX_FLAT_RESULTS]| {
let max_flat = MAX_FLAT_RESULTS;
for (result, slot) in
Self::lift_results(cx, results_ty, src, max_flat)?.zip(results)
{
*slot = result?;
}
Ok(())
},
)
};
if result.is_err() {
store.0.set_trapped();
}
result
}
#[inline]
pub(crate) fn lifted_core_func(&self, store: &StoreOpaque) -> NonNull<VMFuncRef> {
self.instance.id().assert_belongs_to(store.id());
self.unsafe_func_ref.as_non_null()
}
pub(crate) fn abi_async(&self, store: &StoreOpaque) -> bool {
let instance = self.instance.id().get(store);
let component = instance.component();
let (_ty, _def, options) = component.export_lifted_function(self.index);
component.env_component().options[options].async_
}
pub(crate) fn abi_info<'a>(
&self,
store: &'a StoreOpaque,
) -> (
OptionsIndex,
InstanceFlags,
TypeFuncIndex,
&'a CanonicalOptions,
) {
let vminstance = self.instance.id().get(store);
let component = vminstance.component();
let (ty, _def, options_index) = component.export_lifted_function(self.index);
let raw_options = &component.env_component().options[options_index];
(
options_index,
vminstance.instance_flags(raw_options.instance),
ty,
raw_options,
)
}
/// Invokes the underlying wasm function, lowering arguments and lifting the
/// result.
///
/// The `lower` function and `lift` function provided here are what actually
/// do the lowering and lifting. The `LowerParams` and `LowerReturn` types
/// are what will be allocated on the stack for this function call. They
/// should be appropriately sized for the lowering/lifting operation
/// happening.
///
/// # Safety
///
/// The safety of this function relies on the correct definitions of the
/// `LowerParams` and `LowerReturn` type. They must match the type of `self`
/// for the params/results that are going to be produced. Additionally
/// these types must be representable with a sequence of `ValRaw` values.
unsafe fn call_raw<T, Return, LowerParams, LowerReturn>(
&self,
mut store: StoreContextMut<'_, T>,
lower: impl FnOnce(
&mut LowerContext<'_, T>,
InterfaceType,
&mut MaybeUninit<LowerParams>,
) -> Result<()>,
lift: impl FnOnce(&mut LiftContext<'_>, InterfaceType, &LowerReturn) -> Result<Return>,
) -> Result<Return>
where
LowerParams: Copy,
LowerReturn: Copy,
{
let export = self.lifted_core_func(store.0);
let (options_idx, flags, ty, raw_options) = self.abi_info(store.0);
let post_return = raw_options
.post_return
.map(|i| self.instance.id().get(store.0).runtime_post_return(i));
let instance = self.instance.runtime_instance(raw_options.instance);
let async_ = raw_options.async_;
if !store.0.may_enter() {
bail!(crate::Trap::CannotEnterComponent);
}
store.0.enter_guest_sync_call(None, async_, instance)?;
#[repr(C)]
union Union<Params: Copy, Return: Copy> {
params: Params,
ret: Return,
}
let space = &mut MaybeUninit::<Union<LowerParams, LowerReturn>>::uninit();
// Double-check the size/alignment of `space`, just in case.
//
// Note that this alone is not enough to guarantee the validity of the
// `unsafe` block below, but it's definitely required. In any case LLVM
// should be able to trivially see through these assertions and remove
// them in release mode.
let val_size = mem::size_of::<ValRaw>();
let val_align = mem::align_of::<ValRaw>();
assert!(mem::size_of_val(space) % val_size == 0);
assert!(mem::size_of_val(map_maybe_uninit!(space.params)) % val_size == 0);
assert!(mem::size_of_val(map_maybe_uninit!(space.ret)) % val_size == 0);
assert!(mem::align_of_val(space) == val_align);
assert!(mem::align_of_val(map_maybe_uninit!(space.params)) == val_align);
assert!(mem::align_of_val(map_maybe_uninit!(space.ret)) == val_align);
Func::with_lower_context(
self.instance,
store.as_context_mut(),
options_idx,
flags,
ty,
|cx, ty| lower(cx, ty, map_maybe_uninit!(space.params)),
)?;
// SAFETY: We are providing the guarantee that all the inputs are valid.
// The various pointers passed in for the function are all valid since
// they're coming from our store, and the `params_and_results` should
// have the correct layout for the core wasm function we're calling.
// Note that this latter point relies on the correctness of this module
// and `ComponentType` implementations, hence `ComponentType` being an
// `unsafe` trait.
unsafe {
crate::Func::call_unchecked_raw(
&mut store,
export,
NonNull::new(core::ptr::slice_from_raw_parts_mut(
space.as_mut_ptr().cast(),
mem::size_of_val(space) / mem::size_of::<ValRaw>(),
))
.unwrap(),
)?;
}
// Validate that the task, after returning, has no more active borrows
// as they're required to have been dropped by this point.
store
.0
.component_resource_tables(Some(self.instance))?
.validate_scope_exit()?;
// SAFETY: We're relying on the correctness of the structure of
// `LowerReturn` and the type-checking performed to acquire the
// `TypedFunc` to make this safe. It should be the case that
// `LowerReturn` is the exact representation of the return value when
// interpreted as `[ValRaw]`, and additionally they should have the
// correct types for the function we just called (which filled in the
// return values).
let ret: &LowerReturn = unsafe { map_maybe_uninit!(space.ret).assume_init_ref() };
let val = Func::with_lift_context(self.instance, store.0, options_idx, ty, |cx, ty| {
lift(cx, ty, ret)
})?;
// SAFETY: it's a contract of this function that `LowerReturn` is an
// appropriate representation of the result of this function.
let ret_slice = unsafe { storage_as_slice(ret) };
let post_return_arg = match ret_slice.len() {
0 => ValRaw::i32(0),
1 => ret_slice[0],
_ => unreachable!(),
};
// SAFETY: `post_return` and `flags` were resolved from this function's
// own canonical options above, and `store` is the store this call is
// running in.
unsafe {
call_post_return(&mut store, post_return, post_return_arg, flags)?;
}
store.0.exit_guest_sync_call()?;
Ok(val)
}
#[doc(hidden)]
#[deprecated(note = "no longer needs to be called; this function has no effect")]
pub fn post_return(&self, _store: impl AsContextMut) -> Result<()> {
Ok(())
}
#[doc(hidden)]
#[deprecated(note = "no longer needs to be called; this function has no effect")]
#[cfg(feature = "async")]
pub async fn post_return_async(&self, _store: impl AsContextMut<Data: Send>) -> Result<()> {
Ok(())
}
pub(crate) fn lower_args<T>(
cx: &mut LowerContext<'_, T>,
params: &[Val],
params_ty: InterfaceType,
dst: &mut [MaybeUninit<ValRaw>],
) -> Result<()> {
let params_ty = match params_ty {
InterfaceType::Tuple(i) => &cx.types[i],
_ => unreachable!(),
};
if params_ty.abi.flat_count(MAX_FLAT_PARAMS).is_some() {
let dst = &mut dst.iter_mut();
params
.iter()
.zip(params_ty.types.iter())
.try_for_each(|(param, ty)| param.lower(cx, *ty, dst))
} else {
Self::store_args(cx, &params_ty, params, dst)
}
}
fn store_args<T>(
cx: &mut LowerContext<'_, T>,
params_ty: &TypeTuple,
args: &[Val],
dst: &mut [MaybeUninit<ValRaw>],
) -> Result<()> {
let size = usize::try_from(params_ty.abi.size32).unwrap();
let ptr = cx.realloc(0, 0, params_ty.abi.align32, size)?;
let mut offset = ptr;
for (ty, arg) in params_ty.types.iter().zip(args) {
let abi = cx.types.canonical_abi(ty);
arg.store(cx, *ty, abi.next_field32_size(&mut offset))?;
}
dst[0].write(ValRaw::i64(ptr as i64));
Ok(())
}
pub(crate) fn lift_results<'a, 'b>(
cx: &'a mut LiftContext<'b>,
results_ty: InterfaceType,
src: &'a [ValRaw],
max_flat: usize,
) -> Result<Box<dyn Iterator<Item = Result<Val>> + 'a>> {
let results_ty = match results_ty {
InterfaceType::Tuple(i) => &cx.types[i],
_ => unreachable!(),
};
if results_ty.abi.flat_count(max_flat).is_some() {
let mut flat = src.iter();
Ok(try_new::<Box<_>>(
results_ty
.types
.iter()
.map(move |ty| Val::lift(cx, *ty, &mut flat)),
)?)
} else {
let iter = Self::load_results(cx, results_ty, &mut src.iter())?;
Ok(try_new::<Box<_>>(iter)?)
}
}
fn load_results<'a, 'b>(
cx: &'a mut LiftContext<'b>,
results_ty: &'a TypeTuple,
src: &mut core::slice::Iter<'_, ValRaw>,
) -> Result<impl Iterator<Item = Result<Val>> + use<'a, 'b>> {
// FIXME(#4311): needs to read an i64 for memory64
let ptr = usize::try_from(src.next().unwrap().get_u32())?;
if ptr % usize::try_from(results_ty.abi.align32)? != 0 {
bail!("return pointer not aligned");
}
let bytes = cx
.memory()
.get(ptr..)
.and_then(|b| b.get(..usize::try_from(results_ty.abi.size32).unwrap()))
.ok_or_else(|| crate::format_err!("pointer out of bounds of memory"))?;
let mut offset = 0;
Ok(results_ty.types.iter().map(move |ty| {
let abi = cx.types.canonical_abi(ty);
let offset = abi.next_field32_size(&mut offset);
Val::load(cx, *ty, &bytes[offset..][..abi.size32 as usize])
}))
}
#[cfg(feature = "component-model-async")]
pub(crate) fn instance(self) -> Instance {
self.instance
}
/// Creates a `LowerContext` using the provided configuration values and runs
/// the given `lower` closure within it.
///
/// The `lower` closure provided should perform the actual lowering and
/// return the result of the lowering operation which is then returned from
/// this function as well.
pub(crate) fn with_lower_context<T>(
instance: Instance,
mut store: StoreContextMut<T>,
options: OptionsIndex,
mut flags: InstanceFlags,
ty: TypeFuncIndex,
lower: impl FnOnce(&mut LowerContext<T>, InterfaceType) -> Result<()>,
) -> Result<()> {
// Perform the actual lowering, where while this is running the
// component is forbidden from calling imports.
unsafe {
debug_assert!(flags.may_leave());
flags.set_may_leave(false);
}
let mut cx = LowerContext::new(store.as_context_mut(), options, instance);
let param_ty = InterfaceType::Tuple(cx.types[ty].params);
let result = lower(&mut cx, param_ty);
unsafe { flags.set_may_leave(true) };
result
}
/// Creates a `LiftContext` using the provided configuration values and runs
/// the given `lift` closure within it.
///
/// The closure `lift` provided should actually perform the lift itself and
/// the result of that closure is returned from this function call as well.
pub(crate) fn with_lift_context<R>(
instance: Instance,
store: &mut StoreOpaque,
options: OptionsIndex,
ty: TypeFuncIndex,
lift: impl FnOnce(&mut LiftContext, InterfaceType) -> Result<R>,
) -> Result<R> {
let mut cx = LiftContext::new(store, options, instance)?;
let ty = InterfaceType::Tuple(cx.types[ty].results);
lift(&mut cx, ty)
}
}
pub(crate) unsafe fn call_post_return(
mut store: impl AsContextMut,
func: Option<NonNull<VMFuncRef>>,
arg: ValRaw,
mut flags: InstanceFlags,
) -> Result<()> {
unsafe {
// Post return functions are forbidden from calling imports or
// intrinsics.
flags.set_may_leave(false);
// If the function actually had a `post-return` configured in its
// canonical options that's executed here.
if let Some(func) = func {
crate::Func::call_unchecked_raw(
&mut store.as_context_mut(),
func,
core::slice::from_ref(&arg).into(),
)?;
}
// And finally if everything completed successfully then the "may
// leave" flags is set to `true` again here which enables further
// use of the component.
flags.set_may_leave(true);
}
Ok(())
}
@@ -0,0 +1,794 @@
//! Implementation of calling Rust-defined functions from components.
#[cfg(feature = "component-model-async")]
use crate::component::concurrent::{self, Accessor, Status};
use crate::component::func::{LiftContext, LowerContext};
use crate::component::matching::InstanceType;
use crate::component::storage::{slice_to_storage, slice_to_storage_mut};
use crate::component::types::ComponentFunc;
use crate::component::{ComponentNamedList, Instance, Lift, Lower, Val};
use crate::prelude::*;
use crate::runtime::vm::component::{
ComponentInstance, VMComponentContext, VMLowering, VMLoweringCallee,
};
use crate::runtime::vm::{VMOpaqueContext, VMStore};
use crate::store::Asyncness;
use crate::{AsContextMut, StoreContextMut, ValRaw};
use alloc::sync::Arc;
use core::any::Any;
use core::mem::{self, MaybeUninit};
#[cfg(feature = "async")]
use core::pin::Pin;
use core::ptr::NonNull;
use wasmtime_environ::component::{
CanonicalAbiInfo, InterfaceType, MAX_FLAT_PARAMS, MAX_FLAT_RESULTS, OptionsIndex, TypeFuncIndex,
};
/// A host function suitable for passing into a component.
///
/// This structure represents a monomorphic host function that can only be used
/// in the specific context of a particular store. This is generally not too
/// too safe to use and is only meant for internal use.
pub struct HostFunc {
/// The raw function pointer which Cranelift will invoke.
entrypoint: VMLoweringCallee,
/// The implementation of type-checking to ensure that this function
/// ascribes to the provided function type.
///
/// This is used, for example, when a component imports a host function and
/// this will determine if the host function can be imported with the given
/// type.
typecheck: fn(TypeFuncIndex, &InstanceType<'_>) -> Result<()>,
/// The actual host function.
///
/// This is frequently an empty allocation in the sense that the underlying
/// type is a zero-sized-type. Host functions are allowed, though, to close
/// over the environment as well.
func: Box<dyn Any + Send + Sync>,
/// Whether or not this host function was defined in such a way that async
/// stack switching is required when calling it.
asyncness: Asyncness,
}
impl core::fmt::Debug for HostFunc {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("HostFunc").finish_non_exhaustive()
}
}
enum HostResult<T> {
Done(Result<T>),
#[cfg(feature = "component-model-async")]
Future(Pin<Box<dyn Future<Output = Result<T>> + Send>>),
}
impl HostFunc {
/// Creates a new host function based on the implementation of `func`.
///
/// The `asyncness` parameter indicates whether the `func` requires
/// wasm to be on a fiber. This is used to propagate to the `Store` during
/// instantiation to ensure that this guarantee is met.
///
/// Note that if `asyncness` is mistaken then that'll result in panics
/// in Wasmtime, but not memory unsafety.
fn new<T, F, P, R>(asyncness: Asyncness, func: F) -> Result<Arc<HostFunc>>
where
T: 'static,
R: Send + Sync + 'static,
F: HostFn<T, P, R> + Send + Sync + 'static,
{
Ok(try_new::<Arc<_>>(HostFunc {
entrypoint: F::cabi_entrypoint,
typecheck: F::typecheck,
func: try_new::<Box<_>>(func)?,
asyncness,
})?)
}
/// Equivalent for `Linker::func_wrap`
pub(crate) fn func_wrap<T, F, P, R>(func: F) -> Result<Arc<HostFunc>>
where
T: 'static,
F: Fn(StoreContextMut<T>, P) -> Result<R> + Send + Sync + 'static,
P: ComponentNamedList + Lift + 'static,
R: ComponentNamedList + Lower + 'static,
{
Self::new(
Asyncness::No,
StaticHostFn::<_, false>::new(move |store, params| {
HostResult::Done(func(store, params))
}),
)
}
/// Equivalent for `Linker::func_wrap_async`
#[cfg(feature = "async")]
pub(crate) fn func_wrap_async<T, F, P, R>(func: F) -> Result<Arc<HostFunc>>
where
T: 'static,
F: Fn(StoreContextMut<'_, T>, P) -> Box<dyn Future<Output = Result<R>> + Send + '_>
+ Send
+ Sync
+ 'static,
P: ComponentNamedList + Lift + 'static,
R: ComponentNamedList + Lower + 'static,
{
Self::new(
Asyncness::Yes,
StaticHostFn::<_, false>::new(move |store, params| {
HostResult::Done(
store
.block_on(|store| Pin::from(func(store, params)))
.and_then(|r| r),
)
}),
)
}
/// Equivalent for `Linker::func_wrap_concurrent`
#[cfg(feature = "component-model-async")]
pub(crate) fn func_wrap_concurrent<T, F, P, R>(func: F) -> Result<Arc<HostFunc>>
where
T: 'static,
F: Fn(&Accessor<T>, P) -> Pin<Box<dyn Future<Output = Result<R>> + Send + '_>>
+ Send
+ Sync
+ 'static,
P: ComponentNamedList + Lift + 'static,
R: ComponentNamedList + Lower + 'static,
{
let func = Arc::new(func);
Self::new(
Asyncness::Yes,
StaticHostFn::<_, true>::new(move |store, params| {
let func = func.clone();
HostResult::Future(Box::pin(
store.wrap_call(move |accessor| func(accessor, params)),
))
}),
)
}
/// Equivalent of `Linker::func_new`
pub(crate) fn func_new<T, F>(func: F) -> Result<Arc<HostFunc>>
where
T: 'static,
F: Fn(StoreContextMut<'_, T>, ComponentFunc, &[Val], &mut [Val]) -> Result<()>
+ Send
+ Sync
+ 'static,
{
Self::new(
Asyncness::No,
DynamicHostFn::<_, false>::new(
move |store, ty, mut params_and_results, result_start| {
let (params, results) = params_and_results.split_at_mut(result_start);
let result = func(store, ty, params, results).map(move |()| params_and_results);
HostResult::Done(result)
},
),
)
}
/// Equivalent of `Linker::func_new_async`
#[cfg(feature = "async")]
pub(crate) fn func_new_async<T, F>(func: F) -> Result<Arc<HostFunc>>
where
T: 'static,
F: for<'a> Fn(
StoreContextMut<'a, T>,
ComponentFunc,
&'a [Val],
&'a mut [Val],
) -> Box<dyn Future<Output = Result<()>> + Send + 'a>
+ Send
+ Sync
+ 'static,
{
Self::new(
Asyncness::Yes,
DynamicHostFn::<_, false>::new(
move |store, ty, mut params_and_results, result_start| {
let (params, results) = params_and_results.split_at_mut(result_start);
let result = store
.with_blocking(|store, cx| {
cx.block_on(Pin::from(func(store, ty, params, results)))
})
.and_then(|r| r);
let result = result.map(move |()| params_and_results);
HostResult::Done(result)
},
),
)
}
/// Equivalent of `Linker::func_new_concurrent`
#[cfg(feature = "component-model-async")]
pub(crate) fn func_new_concurrent<T, F>(func: F) -> Result<Arc<HostFunc>>
where
T: 'static,
F: for<'a> Fn(
&'a Accessor<T>,
ComponentFunc,
&'a [Val],
&'a mut [Val],
) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'a>>
+ Send
+ Sync
+ 'static,
{
let func = Arc::new(func);
Self::new(
Asyncness::Yes,
DynamicHostFn::<_, true>::new(
move |store, ty, mut params_and_results, result_start| {
let func = func.clone();
HostResult::Future(Box::pin(store.wrap_call(move |accessor| {
Box::pin(async move {
let (params, results) = params_and_results.split_at_mut(result_start);
func(accessor, ty, params, results).await?;
Ok(params_and_results)
})
})))
},
),
)
}
pub fn typecheck(&self, ty: TypeFuncIndex, types: &InstanceType<'_>) -> Result<()> {
(self.typecheck)(ty, types)
}
pub fn lowering(&self) -> VMLowering {
let data = NonNull::from(&*self.func).cast();
VMLowering {
callee: NonNull::new(self.entrypoint as *mut _).unwrap().into(),
data: data.into(),
}
}
pub fn asyncness(&self) -> Asyncness {
self.asyncness
}
}
/// Argument to [`HostFn::lift_params`]
enum Source<'a> {
/// The parameters come from flat wasm arguments which are provided here.
Flat(&'a [ValRaw]),
/// The parameters come from linear memory at the provided offset, which is
/// already validated to be in-bounds.
Memory(usize),
}
/// Argument to [`HostFn::lower_result`]
enum Destination<'a> {
/// The result is stored in flat parameters whose storage is provided here.
Flat(&'a mut [MaybeUninit<ValRaw>]),
/// The result is stored in linear memory at the provided offset, which is
/// already validated to be in-bounds.
Memory(usize),
}
/// Consolidation of functionality of invoking a host function.
///
/// This trait primarily serves as a deduplication of the "static" and
/// "dynamic" host function paths where all default functions here are shared
/// (source-wise at least) across the two styles of host functions.
trait HostFn<T, P, R>
where
T: 'static,
R: Send + Sync + 'static,
{
/// Performs a type-check to ensure that this host function can be imported
/// with the provided signature that a component is using.
fn typecheck(ty: TypeFuncIndex, types: &InstanceType<'_>) -> Result<()>;
/// Execute this host function.
fn run(&self, store: StoreContextMut<'_, T>, params: P) -> HostResult<R>;
/// Performs the lifting operation to convert arguments from the canonical
/// ABI in wasm memory/arguments into their Rust representation.
fn lift_params(cx: &mut LiftContext<'_>, ty: TypeFuncIndex, source: Source<'_>) -> Result<P>;
/// Performs the lowering operation to convert the result from its Rust
/// representation to the canonical ABI representation.
fn lower_result(
cx: &mut LowerContext<'_, T>,
ty: TypeFuncIndex,
result: R,
dst: Destination<'_>,
) -> Result<()>;
/// Raw entrypoint invoked by Cranelift.
///
/// # Safety
///
/// This function is only safe when called from a trusted source which
/// upholds at least these invariants:
///
/// * `cx` is a valid pointer which comes from calling wasm.
/// * `data` is a valid pointer to `Self`
/// * `ty` and `options` are valid within the context of `cx`
/// * `storage` and `storage_len` are valid pointers and correspond to
/// correctly initialized wasm arguments/results according to the
/// canonical ABI specified by `ty` and `options`.
///
/// The code elsewhere in this trait is all downstream of this `unsafe`,
/// and upholding this `unsafe` invariant requires Cranelift, function
/// translation, the canonical ABI, and Wasmtime to all stay in sync.
/// Basically we can't statically rule out this `unsafe`, we just gotta
/// not have bugs.
unsafe extern "C" fn cabi_entrypoint(
cx: NonNull<VMOpaqueContext>,
data: NonNull<u8>,
ty: u32,
options: u32,
storage: NonNull<MaybeUninit<ValRaw>>,
storage_len: usize,
) -> bool
where
Self: Sized,
{
let cx = unsafe { VMComponentContext::from_opaque(cx) };
unsafe {
ComponentInstance::enter_host_from_wasm(cx, |store, instance| {
let mut store = store.unchecked_context_mut();
let ty = TypeFuncIndex::from_u32(ty);
let options = OptionsIndex::from_u32(options);
let storage = NonNull::slice_from_raw_parts(storage, storage_len).as_mut();
let data = data.cast::<Self>().as_ref();
data.entrypoint(store.as_context_mut(), instance, ty, options, storage)
})
}
}
/// "Rust" entrypoint after panic-handling infrastructure is set up and raw
/// arguments are translated to Rust types.
fn entrypoint(
&self,
mut store: StoreContextMut<'_, T>,
instance: Instance,
ty: TypeFuncIndex,
options: OptionsIndex,
storage: &mut [MaybeUninit<ValRaw>],
) -> Result<()> {
let vminstance = instance.id().get(store.0);
let async_ = vminstance.component().env_component().options[options].async_;
if async_ {
#[cfg(feature = "component-model-async")]
{
self.call_async_lower(store.as_context_mut(), instance, ty, options, storage)
}
#[cfg(not(feature = "component-model-async"))]
unreachable!(
"async-lowered imports should have failed validation \
when `component-model-async` feature disabled"
);
} else {
self.call_sync_lower(store.as_context_mut(), instance, ty, options, storage)
}
}
/// Implementation of the "sync" ABI.
///
/// This is the implementation of invoking a host function through the
/// synchronous ABI of the component model, or when a function doesn't have
/// the `async` option when lowered. Note that the host function itself
/// can still be async, in which case this will block here waiting for it
/// to finish.
fn call_sync_lower(
&self,
mut store: StoreContextMut<'_, T>,
instance: Instance,
ty: TypeFuncIndex,
options: OptionsIndex,
storage: &mut [MaybeUninit<ValRaw>],
) -> Result<()> {
let entered_host_task = store.0.host_task_create()?;
let mut lift = LiftContext::new(store.0.store_opaque_mut(), options, instance)?;
let (params, rest) = self.load_params(&mut lift, ty, MAX_FLAT_PARAMS, storage)?;
let ret = match self.run(store.as_context_mut(), params) {
HostResult::Done(result) => result?,
#[cfg(feature = "component-model-async")]
HostResult::Future(future) => {
concurrent::poll_and_block(store.0, entered_host_task, future)?
}
};
let mut lower = LowerContext::new(store, options, instance);
let fty = &lower.types[ty];
let result_tys = &lower.types[fty.results];
let dst = if let Some(cnt) = result_tys.abi.flat_count(MAX_FLAT_RESULTS) {
Destination::Flat(&mut storage[..cnt])
} else {
// SAFETY: due to the contract of `entrypoint` we know that the
// return pointer, located after the parameters, is initialized
// by wasm and safe to read.
let ptr = unsafe { rest[0].assume_init_ref() };
Destination::Memory(validate_inbounds_dynamic(
&result_tys.abi,
lower.as_slice_mut(),
ptr,
)?)
};
lower.validate_scope_exit()?;
lower.store.0.host_task_delete(entered_host_task)?;
Self::lower_raw(&mut lower, ty, ret, dst)
}
/// Implementation of the "async" ABI of the component model.
///
/// This is invoked when a component has the `async` options specified on
/// its `canon lower` for a host function. Note that the host function may
/// be either sync or async, and that's handled here too.
#[cfg(feature = "component-model-async")]
fn call_async_lower(
&self,
store: StoreContextMut<'_, T>,
instance: Instance,
ty: TypeFuncIndex,
options: OptionsIndex,
storage: &mut [MaybeUninit<ValRaw>],
) -> Result<()> {
use wasmtime_environ::component::MAX_FLAT_ASYNC_PARAMS;
let (component, store) = instance.component_and_store_mut(store.0);
let mut store = StoreContextMut(store);
let types = component.types();
let fty = &types[ty];
let entered_host_task = store.0.host_task_create()?;
// Lift the parameters, either from flat storage or from linear
// memory.
let mut lift = LiftContext::new(store.0.store_opaque_mut(), options, instance)?;
let (params, rest) = self.load_params(&mut lift, ty, MAX_FLAT_ASYNC_PARAMS, storage)?;
// Load/validate the return pointer, if present.
let retptr = if !lift.types[fty.results].types.is_empty() {
let mut lower = LowerContext::new(store.as_context_mut(), options, instance);
// SAFETY: see `load_params` below about how the return pointer
// should be safe to use.
let ptr = unsafe { rest[0].assume_init_ref() };
let result_tys = &lower.types[fty.results];
validate_inbounds_dynamic(&result_tys.abi, lower.as_slice_mut(), ptr)?
} else {
// If there's no return pointer then `R` should have an
// empty flat representation. In this situation pretend the return
// pointer was 0 so we have something to shepherd along into the
// closure below.
0
};
let host_result = self.run(store.as_context_mut(), params);
let rc = match host_result {
HostResult::Done(result) => {
let result = result?;
let mut lower = LowerContext::new(store, options, instance);
lower.validate_scope_exit()?;
lower.store.0.host_task_delete(entered_host_task)?;
Self::lower_raw(&mut lower, ty, result, Destination::Memory(retptr))?;
Status::Returned.pack(None)
}
HostResult::Future(future) => instance.first_poll(
store.as_context_mut(),
entered_host_task,
future,
move |store, ret, immediate| {
let mut lower = LowerContext::new(store, options, instance);
lower.validate_scope_exit()?;
if immediate {
lower.store.0.host_task_delete(entered_host_task)?;
}
// FIXME(WebAssembly/component-model#678) the currently
// running thread for this exit lower is wrong. This happens
// to pick whatever's in the store at the time of a
// non-immediate exit which is not correct. There's no real
// right answer here, hence the upstream issue.
if let Some(result) = ret {
Self::lower_raw(&mut lower, ty, result, Destination::Memory(retptr))?;
}
Ok(())
},
)?,
};
storage[0].write(ValRaw::u32(rc));
Ok(())
}
/// Loads parameters the wasm arguments `storage`.
///
/// This will internally decide the ABI source of the parameters and use
/// `storage` appropriately.
fn load_params<'a>(
&self,
lift: &mut LiftContext<'_>,
ty: TypeFuncIndex,
max_flat_params: usize,
storage: &'a [MaybeUninit<ValRaw>],
) -> Result<(P, &'a [MaybeUninit<ValRaw>])> {
let fty = &lift.types[ty];
let param_tys = &lift.types[fty.params];
let param_flat_count = param_tys.abi.flat_count(max_flat_params);
let src = match param_flat_count {
Some(cnt) => {
let params = &storage[..cnt];
// SAFETY: due to the contract of `entrypoint` we are
// guaranteed that all flat parameters are initialized by
// compiled wasm.
Source::Flat(unsafe { mem::transmute::<&[MaybeUninit<ValRaw>], &[ValRaw]>(params) })
}
None => {
// SAFETY: due to the contract of `entrypoint` we are
// guaranteed that the return pointer is initialized by
// compiled wasm.
let ptr = unsafe { storage[0].assume_init_ref() };
Source::Memory(validate_inbounds_dynamic(
&param_tys.abi,
lift.memory(),
ptr,
)?)
}
};
let params = Self::lift_params(lift, ty, src)?;
Ok((params, &storage[param_flat_count.unwrap_or(1)..]))
}
fn lower_raw(
lower: &mut LowerContext<'_, T>,
ty: TypeFuncIndex,
ret: R,
dst: Destination<'_>,
) -> Result<()> {
let caller_instance = lower.options().instance;
let mut flags = lower.instance_mut().instance_flags(caller_instance);
unsafe {
flags.set_may_leave(false);
}
Self::lower_result(lower, ty, ret, dst)?;
unsafe {
flags.set_may_leave(true);
}
Ok(())
}
}
/// Checks that a host function's `ASYNC`-ness (as encoded by which of
/// `func_new`/`func_new_async`/`func_new_concurrent` — or their `_wrap`
/// counterparts — constructed it) matches whether the component's WIT type
/// for this import is declared `async func`.
///
/// This isn't a case of one or the other being "correct": `func_new_async`/
/// `func_wrap_async` intentionally implement a *sync*-WIT-typed function via
/// blocking/async host code (see their docs), so they can never satisfy an
/// `async func` import — only `func_new_concurrent`/`func_wrap_concurrent`
/// can. The error message names which specific mismatch occurred and points
/// at the API that would work, since "type mismatch with async" alone gives
/// no indication of *why* or what to use instead.
fn typecheck_async(host_async: bool, wit_async: bool) -> Result<()> {
if host_async == wit_async {
return Ok(());
}
if wit_async {
bail!(
"type mismatch with async: this import is declared `async func` in WIT, but was \
satisfied with a sync-style host function (`func_new`/`func_wrap`, or \
`func_new_async`/`func_wrap_async` — despite the name, these implement a \
*sync*-WIT-typed function via blocking host code, not an `async func` import); \
use `func_new_concurrent`/`func_wrap_concurrent` instead"
);
} else {
bail!(
"type mismatch with async: this import's WIT type is a plain (non-`async`) \
function, but was satisfied with `func_new_concurrent`/`func_wrap_concurrent`, \
which is only for `async func`-typed imports; use `func_new`/`func_wrap` (or \
`func_new_async`/`func_wrap_async` for blocking host code) instead"
);
}
}
/// Implementation of a "static" host function where the parameters and results
/// of a function are known at compile time.
#[repr(transparent)]
struct StaticHostFn<F, const ASYNC: bool>(F);
impl<F, const ASYNC: bool> StaticHostFn<F, ASYNC> {
fn new<T, P, R>(func: F) -> Self
where
T: 'static,
P: ComponentNamedList + Lift + 'static,
R: ComponentNamedList + Lower + 'static,
F: Fn(StoreContextMut<'_, T>, P) -> HostResult<R>,
{
Self(func)
}
}
impl<T, F, P, R, const ASYNC: bool> HostFn<T, P, R> for StaticHostFn<F, ASYNC>
where
T: 'static,
F: Fn(StoreContextMut<'_, T>, P) -> HostResult<R>,
P: ComponentNamedList + Lift + 'static,
R: ComponentNamedList + Lower + 'static,
{
fn typecheck(ty: TypeFuncIndex, types: &InstanceType<'_>) -> Result<()> {
let ty = &types.types[ty];
typecheck_async(ASYNC, ty.async_)?;
P::typecheck(&InterfaceType::Tuple(ty.params), types)
.context("type mismatch with parameters")?;
R::typecheck(&InterfaceType::Tuple(ty.results), types)
.context("type mismatch with results")?;
Ok(())
}
fn run(&self, store: StoreContextMut<'_, T>, params: P) -> HostResult<R> {
(self.0)(store, params)
}
fn lift_params(cx: &mut LiftContext<'_>, ty: TypeFuncIndex, src: Source<'_>) -> Result<P> {
let ty = InterfaceType::Tuple(cx.types[ty].params);
match src {
Source::Flat(storage) => {
// SAFETY: the contract of `ComponentType` for `P` means that
// it's safe to interpret the parameters `storage` as
// `P::Lower`. The contract of `entrypoint` is that everything
// is initialized correctly internally.
let storage: &P::Lower = unsafe { slice_to_storage(storage) };
P::linear_lift_from_flat(cx, ty, storage)
}
Source::Memory(offset) => {
P::linear_lift_from_memory(cx, ty, &cx.memory()[offset..][..P::SIZE32])
}
}
}
fn lower_result(
cx: &mut LowerContext<'_, T>,
ty: TypeFuncIndex,
ret: R,
dst: Destination<'_>,
) -> Result<()> {
let fty = &cx.types[ty];
let ty = InterfaceType::Tuple(fty.results);
match dst {
Destination::Flat(storage) => {
// SAFETY: the contract of `ComponentType` for `R` means that
// it's safe to reinterpret `ValRaw` storage to initialize as
// `R::Lower`.
let storage: &mut MaybeUninit<R::Lower> = unsafe { slice_to_storage_mut(storage) };
ret.linear_lower_to_flat(cx, ty, storage)
}
Destination::Memory(ptr) => ret.linear_lower_to_memory(cx, ty, ptr),
}
}
}
/// Implementation of a "dynamic" host function where the number of parameters,
/// types of parameters, and result type/presence, are all not known at compile
/// time.
///
/// This is intended for more-dynamic use cases than `StaticHostFn` above such
/// as demos, gluing things together quickly, and `wast` testing.
struct DynamicHostFn<F, const ASYNC: bool>(F);
impl<F, const ASYNC: bool> DynamicHostFn<F, ASYNC> {
fn new<T>(func: F) -> Self
where
T: 'static,
F: Fn(StoreContextMut<'_, T>, ComponentFunc, Vec<Val>, usize) -> HostResult<Vec<Val>>,
{
Self(func)
}
}
impl<T, F, const ASYNC: bool> HostFn<T, (ComponentFunc, Vec<Val>), Vec<Val>>
for DynamicHostFn<F, ASYNC>
where
T: 'static,
F: Fn(StoreContextMut<'_, T>, ComponentFunc, Vec<Val>, usize) -> HostResult<Vec<Val>>,
{
/// This function performs dynamic type checks on its parameters and
/// results and subsequently does not need to perform up-front type
/// checks. However, we _do_ verify async-ness here.
fn typecheck(ty: TypeFuncIndex, types: &InstanceType<'_>) -> Result<()> {
let ty = &types.types[ty];
typecheck_async(ASYNC, ty.async_)
}
fn run(
&self,
store: StoreContextMut<'_, T>,
(ty, mut params): (ComponentFunc, Vec<Val>),
) -> HostResult<Vec<Val>> {
let offset = params.len();
for _ in 0..ty.results().len() {
params.push(Val::Bool(false));
}
(self.0)(store, ty, params, offset)
}
fn lift_params(
cx: &mut LiftContext<'_>,
ty: TypeFuncIndex,
src: Source<'_>,
) -> Result<(ComponentFunc, Vec<Val>)> {
let param_tys = &cx.types[cx.types[ty].params];
let mut params = Vec::new();
match src {
Source::Flat(storage) => {
let mut iter = storage.iter();
for ty in param_tys.types.iter() {
params.push(Val::lift(cx, *ty, &mut iter)?);
}
assert!(iter.next().is_none());
}
Source::Memory(mut offset) => {
for ty in param_tys.types.iter() {
let abi = cx.types.canonical_abi(ty);
let size = usize::try_from(abi.size32).unwrap();
let memory = &cx.memory()[abi.next_field32_size(&mut offset)..][..size];
params.push(Val::load(cx, *ty, memory)?);
}
}
}
Ok((ComponentFunc::from(ty, &cx.instance_type()), params))
}
fn lower_result(
cx: &mut LowerContext<'_, T>,
ty: TypeFuncIndex,
result_vals: Vec<Val>,
dst: Destination<'_>,
) -> Result<()> {
let fty = &cx.types[ty];
let param_tys = &cx.types[fty.params];
let result_tys = &cx.types[fty.results];
let result_vals = &result_vals[param_tys.types.len()..];
match dst {
Destination::Flat(storage) => {
let mut dst = storage.iter_mut();
for (val, ty) in result_vals.iter().zip(result_tys.types.iter()) {
val.lower(cx, *ty, &mut dst)?;
}
assert!(dst.next().is_none());
}
Destination::Memory(mut ptr) => {
for (val, ty) in result_vals.iter().zip(result_tys.types.iter()) {
let offset = cx.types.canonical_abi(ty).next_field32_size(&mut ptr);
val.store(cx, *ty, offset)?;
}
}
}
Ok(())
}
}
pub(crate) fn validate_inbounds_dynamic(
abi: &CanonicalAbiInfo,
memory: &[u8],
ptr: &ValRaw,
) -> Result<usize> {
// FIXME(#4311): needs memory64 support
let ptr = usize::try_from(ptr.get_u32())?;
if ptr % usize::try_from(abi.align32)? != 0 {
bail!("pointer not aligned");
}
let end = match ptr.checked_add(usize::try_from(abi.size32).unwrap()) {
Some(n) => n,
None => bail!("pointer size overflow"),
};
if end > memory.len() {
bail!("pointer out of bounds")
}
Ok(ptr)
}
@@ -0,0 +1,545 @@
use crate::StoreContextMut;
#[cfg(feature = "component-model-async")]
use crate::component::concurrent::ConcurrentState;
use crate::component::matching::InstanceType;
use crate::component::resources::{HostResourceData, HostResourceIndex, HostResourceTables};
use crate::component::store::ComponentTaskState;
use crate::component::{Instance, ResourceType, RuntimeInstance};
use crate::prelude::*;
use crate::runtime::vm::VMFuncRef;
use crate::runtime::vm::component::{ComponentInstance, HandleTable, ResourceTables};
use crate::store::{StoreId, StoreOpaque};
use alloc::sync::Arc;
use core::fmt;
use core::pin::Pin;
use core::ptr::NonNull;
use wasmtime_environ::component::{
CanonicalOptions, CanonicalOptionsDataModel, ComponentTypes, OptionsIndex,
TypeResourceTableIndex,
};
/// A helper structure which is a "package" of the context used during lowering
/// values into a component (or storing them into memory).
///
/// This type is used by the `Lower` trait extensively and contains any
/// contextual information necessary related to the context in which the
/// lowering is happening.
#[doc(hidden)]
pub struct LowerContext<'a, T: 'static> {
/// Lowering may involve invoking memory allocation functions so part of the
/// context here is carrying access to the entire store that wasm is
/// executing within. This store serves as proof-of-ability to actually
/// execute wasm safely.
pub store: StoreContextMut<'a, T>,
/// Lowering always happens into a function that's been `canon lift`'d or
/// `canon lower`'d, both of which specify a set of options for the
/// canonical ABI. For example details like string encoding are contained
/// here along with which memory pointers are relative to or what the memory
/// allocation function is.
options: OptionsIndex,
/// Lowering happens within the context of a component instance and this
/// field stores the type information of that component instance. This is
/// used for type lookups and general type queries during the
/// lifting/lowering process.
pub types: &'a ComponentTypes,
/// Index of the component instance that's being lowered into.
instance: Instance,
/// Whether to allow `options.realloc` to be used when lowering.
allow_realloc: bool,
}
#[doc(hidden)]
impl<'a, T: 'static> LowerContext<'a, T> {
/// Creates a new lowering context from the specified parameters.
pub fn new(
store: StoreContextMut<'a, T>,
options: OptionsIndex,
instance: Instance,
) -> LowerContext<'a, T> {
// Debug-assert that if we can't block that blocking is indeed allowed.
// This'll catch when this is accidentally created outside of a fiber
// when we need to be on a fiber.
if cfg!(debug_assertions) && !store.0.can_block() {
store.0.validate_sync_call().unwrap();
}
let (component, store) = instance.component_and_store_mut(store.0);
LowerContext {
store: StoreContextMut(store),
options,
types: component.types(),
instance,
allow_realloc: true,
}
}
/// Like `new`, except disallows use of `options.realloc`.
///
/// The returned object will panic if its `realloc` method is called.
///
/// This is meant for use when lowering "flat" values (i.e. values which
/// require no allocations) into already-allocated memory or into stack
/// slots, in which case the lowering may safely be done outside of a fiber
/// since there is no need to make any guest calls.
#[cfg(feature = "component-model-async")]
pub(crate) fn new_without_realloc(
store: StoreContextMut<'a, T>,
options: OptionsIndex,
instance: Instance,
) -> LowerContext<'a, T> {
let (component, store) = instance.component_and_store_mut(store.0);
LowerContext {
store: StoreContextMut(store),
options,
types: component.types(),
instance,
allow_realloc: false,
}
}
/// Returns the `Instance` that's being lowered into.
pub fn instance_handle(&self) -> Instance {
self.instance
}
/// Returns the `&ComponentInstance` that's being lowered into.
pub fn instance(&self) -> &ComponentInstance {
self.instance.id().get(self.store.0)
}
/// Returns the `&mut ComponentInstance` that's being lowered into.
pub fn instance_mut(&mut self) -> Pin<&mut ComponentInstance> {
self.instance.id().get_mut(self.store.0)
}
/// Returns the canonical options that are being used during lifting.
pub fn options(&self) -> &CanonicalOptions {
&self.instance().component().env_component().options[self.options]
}
/// Returns a view into memory as a mutable slice of bytes.
///
/// # Panics
///
/// This will panic if memory has not been configured for this lowering
/// (e.g. it wasn't present during the specification of canonical options).
pub fn as_slice_mut(&mut self) -> &mut [u8] {
self.instance.options_memory_mut(self.store.0, self.options)
}
/// Invokes the memory allocation function (which is style after `realloc`)
/// with the specified parameters.
///
/// # Panics
///
/// This will panic if realloc hasn't been configured for this lowering via
/// its canonical options.
pub fn realloc(
&mut self,
old: usize,
old_size: usize,
old_align: u32,
new_size: usize,
) -> Result<usize> {
assert!(self.allow_realloc);
// All calls to `realloc` options in the canonical ABI zero out the
// `context.{get,set}` slots for the duration of the call. This sort of
// fakes a "fresh thread" for each call, but this is the only observable
// state so nothing else needs adjusting. Note though that the original
// values are preserved still to get restored after this call.
#[cfg(feature = "component-model-async")]
let orig_context = core::mem::replace(
self.store.0.vm_store_context_mut().component_context_mut(),
Default::default(),
);
let (component, store) = self.instance.component_and_store_mut(self.store.0);
let instance = self.instance.id().get(store);
let options = &component.env_component().options[self.options];
let realloc_ty = component.realloc_func_ty();
let realloc = match options.data_model {
CanonicalOptionsDataModel::Gc {} => unreachable!(),
CanonicalOptionsDataModel::LinearMemory(m) => m.realloc.unwrap(),
};
let realloc = instance.runtime_realloc(realloc);
let params = (
u32::try_from(old)?,
u32::try_from(old_size)?,
old_align,
u32::try_from(new_size)?,
);
type ReallocFunc = crate::TypedFunc<(u32, u32, u32, u32), u32>;
// Invoke the wasm malloc function using its raw and statically known
// signature.
let result = unsafe {
ReallocFunc::call_raw(&mut StoreContextMut(store), &realloc_ty, realloc, params)?
};
if result % old_align != 0 {
bail!("realloc return: result not aligned");
}
let result = usize::try_from(result)?;
if self
.as_slice_mut()
.get_mut(result..)
.and_then(|s| s.get_mut(..new_size))
.is_none()
{
bail!("realloc return: beyond end of memory")
}
// Note that this restoration isn't part of a `Drop` guard which works
// because once a component traps it's locked-down and inaccessible, so
// it's ok if this isn't restored.
#[cfg(feature = "component-model-async")]
{
*self.store.0.vm_store_context_mut().component_context_mut() = orig_context;
}
Ok(result)
}
/// Returns a fixed mutable slice of memory `N` bytes large starting at
/// offset `N`, panicking on out-of-bounds.
///
/// It should be previously verified that `offset` is in-bounds via
/// bounds-checks.
///
/// # Panics
///
/// This will panic if memory has not been configured for this lowering
/// (e.g. it wasn't present during the specification of canonical options).
pub fn get<const N: usize>(&mut self, offset: usize) -> &mut [u8; N] {
// FIXME: this bounds check shouldn't actually be necessary, all
// callers of `ComponentType::store` have already performed a bounds
// check so we're guaranteed that `offset..offset+N` is in-bounds. That
// being said we at least should do bounds checks in debug mode and
// it's not clear to me how to easily structure this so that it's
// "statically obvious" the bounds check isn't necessary.
//
// For now I figure we can leave in this bounds check and if it becomes
// an issue we can optimize further later, probably with judicious use
// of `unsafe`.
self.as_slice_mut()[offset..].first_chunk_mut().unwrap()
}
/// Lowers an `own` resource into the guest, converting the `rep` specified
/// into a guest-local index.
///
/// The `ty` provided is which table to put this into.
pub fn guest_resource_lower_own(
&mut self,
ty: TypeResourceTableIndex,
rep: u32,
) -> Result<u32> {
self.resource_tables()?.guest_resource_lower_own(rep, ty)
}
/// Lowers a `borrow` resource into the guest, converting the `rep` to a
/// guest-local index in the `ty` table specified.
pub fn guest_resource_lower_borrow(
&mut self,
ty: TypeResourceTableIndex,
rep: u32,
) -> Result<u32> {
// Implement `lower_borrow`'s special case here where if a borrow is
// inserted into a table owned by the instance which implemented the
// original resource then no borrow tracking is employed and instead the
// `rep` is returned "raw".
//
// This check is performed by comparing the owning instance of `ty`
// against the owning instance of the resource that `ty` is working
// with.
if self.instance().resource_owned_by_own_instance(ty) {
return Ok(rep);
}
self.resource_tables()?.guest_resource_lower_borrow(rep, ty)
}
/// Lifts a host-owned `own` resource at the `idx` specified into the
/// representation of that resource.
pub fn host_resource_lift_own(&mut self, idx: HostResourceIndex) -> Result<u32> {
self.resource_tables()?.host_resource_lift_own(idx)
}
/// Lifts a host-owned `borrow` resource at the `idx` specified into the
/// representation of that resource.
pub fn host_resource_lift_borrow(&mut self, idx: HostResourceIndex) -> Result<u32> {
self.resource_tables()?.host_resource_lift_borrow(idx)
}
/// Lowers a resource into the host-owned table, returning the index it was
/// inserted at.
///
/// Note that this is a special case for `Resource<T>`. Most of the time a
/// host value shouldn't be lowered with a lowering context.
pub fn host_resource_lower_own(
&mut self,
rep: u32,
dtor: Option<NonNull<VMFuncRef>>,
instance: Option<RuntimeInstance>,
) -> Result<HostResourceIndex> {
self.resource_tables()?
.host_resource_lower_own(rep, dtor, instance)
}
/// Returns the underlying resource type for the `ty` table specified.
pub fn resource_type(&self, ty: TypeResourceTableIndex) -> ResourceType {
self.instance_type().resource_type(ty)
}
/// Returns the instance type information corresponding to the instance that
/// this context is lowering into.
pub fn instance_type(&self) -> InstanceType<'_> {
InstanceType::new(self.instance())
}
fn resource_tables(&mut self) -> Result<HostResourceTables<'_>> {
let (tables, data) = self
.store
.0
.component_resource_tables_and_host_resource_data(Some(self.instance))?;
Ok(HostResourceTables::from_parts(tables, data))
}
/// See [`HostResourceTables::validate_scope_exit`].
#[inline]
pub fn validate_scope_exit(&mut self) -> Result<()> {
self.resource_tables()?.validate_scope_exit()
}
}
/// Contextual information used when lifting a type from a component into the
/// host.
///
/// This structure is the analogue of `LowerContext` except used during lifting
/// operations (or loading from memory).
#[doc(hidden)]
pub struct LiftContext<'a> {
store_id: StoreId,
current_scope_id: Option<u32>,
/// Like lowering, lifting always has options configured.
options: OptionsIndex,
/// Instance type information, like with lowering.
pub types: &'a Arc<ComponentTypes>,
memory: &'a [u8],
instance: Pin<&'a mut ComponentInstance>,
instance_handle: Instance,
host_table: &'a mut HandleTable,
host_resource_data: &'a mut HostResourceData,
task_state: &'a mut ComponentTaskState,
/// Remaining fuel for this hostcall/lift operation.
///
/// This is decremented for strings/lists, for example, to cap the size of
/// data the host allocates on behalf of the guest.
hostcall_fuel: usize,
}
#[doc(hidden)]
impl<'a> LiftContext<'a> {
/// Creates a new lifting context given the provided context.
#[inline]
pub fn new(
store: &'a mut StoreOpaque,
options: OptionsIndex,
instance_handle: Instance,
) -> Result<LiftContext<'a>> {
let store_id = store.id();
let hostcall_fuel = store.hostcall_fuel();
let current_scope_id = store.current_scope_id()?;
// From `&mut StoreOpaque` provided the goal here is to project out
// three different disjoint fields owned by the store: memory,
// `CallContexts`, and `HandleTable`. There's no native API for that
// so it's hacked around a bit. This unsafe pointer cast could be fixed
// with more methods in more places, but it doesn't seem worth doing it
// at this time.
let memory =
instance_handle.options_memory(unsafe { &*(store as *const StoreOpaque) }, options);
let (task_state, host_table, host_resource_data, instance) =
store.lift_context_parts(instance_handle);
let (component, instance) = instance.component_and_self();
Ok(LiftContext {
store_id,
current_scope_id,
memory,
options,
types: component.types(),
instance,
instance_handle,
task_state,
host_table,
host_resource_data,
hostcall_fuel,
})
}
/// Returns the canonical options that are being used during lifting.
pub fn options(&self) -> &CanonicalOptions {
&self.instance.component().env_component().options[self.options]
}
/// Returns the `OptionsIndex` being used during lifting.
pub fn options_index(&self) -> OptionsIndex {
self.options
}
/// Returns the entire contents of linear memory for this set of lifting
/// options.
///
/// # Panics
///
/// This will panic if memory has not been configured for this lifting
/// operation.
pub fn memory(&self) -> &'a [u8] {
self.memory
}
/// Returns an identifier for the store from which this `LiftContext` was
/// created.
pub fn store_id(&self) -> StoreId {
self.store_id
}
/// Returns the component instance that is being lifted from.
pub fn instance_mut(&mut self) -> Pin<&mut ComponentInstance> {
self.instance.as_mut()
}
/// Returns the component instance that is being lifted from.
pub fn instance_handle(&self) -> Instance {
self.instance_handle
}
#[cfg(feature = "component-model-async")]
pub(crate) fn concurrent_state_and_instance_mut(
&mut self,
) -> (&mut ConcurrentState, Pin<&mut ComponentInstance>) {
(
self.task_state.concurrent_state_mut(),
self.instance.as_mut(),
)
}
/// Lifts an `own` resource from the guest at the `idx` specified into its
/// representation.
///
/// Additionally returns a destructor/instance flags to go along with the
/// representation so the host knows how to destroy this resource.
pub fn guest_resource_lift_own(
&mut self,
ty: TypeResourceTableIndex,
idx: u32,
) -> Result<(u32, Option<NonNull<VMFuncRef>>, Option<RuntimeInstance>)> {
let idx = self.resource_tables().guest_resource_lift_own(idx, ty)?;
let (dtor, instance) = self.instance.dtor_and_instance(ty);
Ok((idx, dtor, instance))
}
/// Lifts a `borrow` resource from the guest at the `idx` specified.
pub fn guest_resource_lift_borrow(
&mut self,
ty: TypeResourceTableIndex,
idx: u32,
) -> Result<u32> {
self.resource_tables().guest_resource_lift_borrow(idx, ty)
}
/// Lowers a resource into the host-owned table, returning the index it was
/// inserted at.
pub fn host_resource_lower_own(
&mut self,
rep: u32,
dtor: Option<NonNull<VMFuncRef>>,
instance: Option<RuntimeInstance>,
) -> Result<HostResourceIndex> {
self.resource_tables()
.host_resource_lower_own(rep, dtor, instance)
}
/// Lowers a resource into the host-owned table, returning the index it was
/// inserted at.
pub fn host_resource_lower_borrow(&mut self, rep: u32) -> Result<HostResourceIndex> {
self.resource_tables().host_resource_lower_borrow(rep)
}
/// Returns the underlying type of the resource table specified by `ty`.
pub fn resource_type(&self, ty: TypeResourceTableIndex) -> ResourceType {
self.instance_type().resource_type(ty)
}
/// Returns instance type information for the component instance that is
/// being lifted from.
pub fn instance_type(&self) -> InstanceType<'_> {
InstanceType::new(&self.instance)
}
fn resource_tables(&mut self) -> HostResourceTables<'_> {
HostResourceTables::from_parts(
ResourceTables {
host_table: self.host_table,
task_state: self.task_state,
guest: Some(self.instance.as_mut().instance_states()),
current_scope_id: self.current_scope_id,
},
self.host_resource_data,
)
}
/// See [`HostResourceTables::validate_scope_exit`].
#[inline]
pub fn validate_scope_exit(&mut self) -> Result<()> {
self.resource_tables().validate_scope_exit()
}
/// Consumes `amt` units of fuel, typically a number of bytes, from this
/// context.
///
/// Returns an error if the fuel is exhausted which will cause a trap in the
/// guest. Note that this is distinct from Wasm's fuel, this is just for
/// keeping track of data flowing from the guest to the host.
pub fn consume_fuel(&mut self, amt: usize) -> Result<()> {
match self.hostcall_fuel.checked_sub(amt) {
Some(new) => self.hostcall_fuel = new,
None => bail!(HostcallFuelExhausted),
}
Ok(())
}
/// Same as [`Self::consume_fuel`], but safely multiplies `len` and `size`
/// together before calling that.
pub fn consume_fuel_array(&mut self, len: usize, size: usize) -> Result<()> {
match len.checked_mul(size) {
Some(bytes) => self.consume_fuel(bytes),
None => bail!(HostcallFuelExhausted),
}
}
}
#[derive(Debug)]
struct HostcallFuelExhausted;
impl fmt::Display for HostcallFuelExhausted {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"too much data is being copied between the host and the guest: \
fuel allocated for hostcalls has been exhausted"
)
}
}
impl core::error::Error for HostcallFuelExhausted {}
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,305 @@
/// A trait used as part of [`bindgen!`] to indicate a `Data<'_>` payload that
/// implements some host bindings traits.
///
/// The purpose of the [`bindgen!`] macro is to define Rust traits that the host
/// can implement to fulfill WIT functions imported from the host into a
/// component. The [`bindgen!`] macro then additionally generates a function
/// which takes a [`Linker`] and an implementation of the traits and fills out
/// the [`Linker`]. This trait, [`HasData`], is used in this process of filling
/// out the [`Linker`] for some WIT interfaces.
///
/// Wasmtime's [`Store<T>`] type is the home for all per-instance state.
/// Notably the `T` here is generic (the Wasmtime library allows any type to be
/// placed here) and it's also instance-specific as a [`Store<T>`] is typically
/// allocated one-per-instance. Implementations of host APIs, however, often
/// want to live in a library and not be tied to any particular `T`. For example
/// Wasmtime provides the `wasmtime-wasi` crates as an implementation of
/// standard WASI APIs as a library, but they don't want to fix a particular `T`
/// in [`Store<T>`] as embedders should be able to fill out their own `T` for
/// their needs. The purpose of this trait is to enable this situation.
///
/// This trait is used in `add_to_linker` functions generated by [`bindgen!`] in
/// conjunction with a function pointer. It looks something along the lines of:
///
/// ```
/// use wasmtime::component::{Linker, HasData};
///
/// // generated by bindgen!
/// trait Host {
/// // ..
/// }
///
/// fn add_to_linker<T, D>(linker: &mut Linker<T>, getter: fn(&mut T) -> D::Data<'_>)
/// where D: HasData,
/// for<'a> D::Data<'a>: Host,
/// {
/// // ...
/// # let _ = (linker, getter);
/// }
/// ```
///
/// Here the `D` type parameter, bounded by [`HasData`], is used to specify
/// the return type of the `getter` function provided here. The `getter`
/// "projects" from `&mut T` to `D::Data<'_>`, notably enabling it to capture
/// the lifetime of the `&mut T` passed in as well.
///
/// The `Data` associated type here is further bounded in `add_to_linker` above
/// as it must implement the traits generated by [`bindgen!`]. This means that
/// `linker` is filled out with functions that, when called, first `getter` is
/// invoked and then the actual function delegates to the `Host` trait
/// implementation in this case.
///
/// The `D` type parameter here isn't actually a runtime value, nor is it stored
/// anywhere. It's purely used as a means of projecting a "generic associated
/// type", here where `<'a>` is the generic argument, a lifetime. This means
/// that the choice of `D` is disconnected from both `T` and `D::Data<'_>` and
/// can be whatever you like. Sometimes you might need to define an empty struct
/// in your project to use this, but Wasmtime also provides a convenience
/// [`HasSelf`] type to avoid the need for this in some common use cases.
///
/// # Example: `Host for T` using `Store<T>`
///
/// Let's say you wanted to invoke the above `add_to_linker` function where the
/// `T` in [`Store<T>`] directly implements the `Host` trait itself:
///
/// ```
/// use wasmtime::component::{Linker, HasSelf};
/// # use wasmtime::component::HasData;
/// #
/// # trait Host { }
/// # impl<T: Host + ?Sized> Host for &mut T {}
/// #
/// # fn add_to_linker<T, D>(linker: &mut Linker<T>, getter: fn(&mut T) -> D::Data<'_>)
/// # where D: HasData,
/// # for<'a> D::Data<'a>: Host,
/// # {
/// # let _ = (linker, getter);
/// # }
///
/// struct MyStoreState { /* ... */ }
///
/// impl Host for MyStoreState {
/// // ..
/// }
///
/// fn fill_out_my_linker(linker: &mut Linker<MyStoreState>) {
/// add_to_linker::<_, HasSelf<_>>(linker, |x| x)
/// }
/// ```
///
/// Here the `add_to_linker` invocation is annotated with `<_, HasSelf<_>>`. The
/// first argument gets inferred to `MyStoreState`, and the second argument gets
/// inferred to `HasSelf<MyStoreState>` This means that the projection function
/// in this case, here the identity `|x| x`, is typed as
/// `fn(&mut MyStoreState) -> &mut MyStoreState`. This is because the `HasData`
/// implementation for `HasSelf<T>` means that we have
/// `type Data<'a> = &'a mut T`.
///
/// # Example: `Host for MyLibraryState` using `Store<T>`
///
/// Let's say though that you instead are writing a library like WASI where you
/// don't know the `T` of [`Store<T>`] ahead of time. In such a case you might
/// hand-write your own `add_to_linker` wrapper that looks like this:
///
/// ```
/// use wasmtime::component::{Linker, HasData};
/// #
/// # trait Host { }
/// # impl<T: Host + ?Sized> Host for &mut T {}
/// #
/// # fn add_to_linker<T, D>(linker: &mut Linker<T>, getter: fn(&mut T) -> D::Data<'_>)
/// # where D: HasData,
/// # for<'a> D::Data<'a>: Host,
/// # {
/// # let _ = (linker, getter);
/// # }
///
/// // publicly exposed per-instance state for your library, users will
/// // construct this and store it within the `T` in `Store<T>`
/// pub struct MyLibraryState { /* ... */ }
///
/// impl Host for MyLibraryState {
/// // ..
/// }
///
/// // hand-written publicly exposed convenience function to add this crate's
/// // component functionality to the provided linker.
/// pub fn add_my_library_to_linker<T>(
/// linker: &mut Linker<T>,
/// f: fn(&mut T) -> &mut MyLibraryState,
/// ) {
/// // invoke the bindgen!-generated `add_to_linker`
/// add_to_linker::<_, MyLibrary>(linker, f);
/// }
///
/// // Note this need not be publicly exposed, it's just a private internal
/// // detail.
/// struct MyLibrary;
///
/// impl HasData for MyLibrary {
/// type Data<'a> = &'a mut MyLibraryState;
/// }
/// ```
///
/// Here the `MyLibrary` type, private to this crate, has a `HasData`
/// implementation which indicates that implementations of the
/// `bindgen!`-generated APIs are done in terms of `MyLibraryState`
/// specifically. The `add_my_library_to_linker` takes an externally-provided
/// `f` closure which projects from `&mut T`, whatever data the store is using,
/// to `MyLibraryState` which must be stored within the store itself.
///
/// # Example: `Host for MyLibraryState<'_>` using `Store<T>`
///
/// Let's say you're like the above scenario where you're writing a library but
/// instead of being able to wrap up all your state for each trait
/// implementation in a structure it's spread across a few structures. These
/// structures may be stored in different locations inside of `Store<T>` which
/// makes it difficult to wrap up in a single type and return that. Here you can
/// make use of "view" types to collect a number of disjoint borrows into one
/// structure:
///
/// ```
/// use wasmtime::component::{Linker, HasData};
/// #
/// # trait Host { }
/// # impl<T: Host + ?Sized> Host for &mut T {}
/// #
/// # fn add_to_linker<T, D>(linker: &mut Linker<T>, getter: fn(&mut T) -> D::Data<'_>)
/// # where D: HasData,
/// # for<'a> D::Data<'a>: Host,
/// # {
/// # let _ = (linker, getter);
/// # }
/// # struct StateA;
/// # struct StateB;
///
/// // Like before, this is publicly exposed, and this is a "bag of pointers" to
/// // all the pieces of state necessary to implement `Host` below.
/// pub struct MyLibraryState<'a> {
/// pub state_a: &'a mut StateA,
/// pub state_b: &'a mut StateB,
/// }
///
/// impl Host for MyLibraryState<'_> {
/// // ..
/// }
///
/// pub fn add_my_library_to_linker<T>(
/// linker: &mut Linker<T>,
/// f: fn(&mut T) -> MyLibraryState<'_>,
/// ) {
/// // invoke the bindgen!-generated `add_to_linker`
/// add_to_linker::<_, MyLibrary>(linker, f);
/// }
///
/// struct MyLibrary;
///
/// impl HasData for MyLibrary {
/// type Data<'a> = MyLibraryState<'a>;
/// }
/// ```
///
/// This is similar to the above example but shows using a lifetime parameter on
/// a structure instead of using a pointer-with-a-lifetime only. Otherwise
/// though this'll end up working out the same way.
///
/// # Example: `Host for U: MyLibrary` using `Store<T>`
///
/// Let's say you're in a situation where you're a library which wants to create
/// a "simpler" trait than the `Host`-generated traits from [`bindgen!`] and
/// then you want to implement `Host` in terms of this trait. This requires a
/// bit more boilerplate as well as a new custom structure, but doing so looks
/// like this:
///
/// ```
/// use wasmtime::component::{Linker, HasData};
/// #
/// # trait Host { }
/// # impl<T: Host + ?Sized> Host for &mut T {}
/// #
/// # fn add_to_linker<T, D>(linker: &mut Linker<T>, getter: fn(&mut T) -> D::Data<'_>)
/// # where D: HasData,
/// # for<'a> D::Data<'a>: Host,
/// # {
/// # let _ = (linker, getter);
/// # }
///
/// // This is your public trait which external users need to implement. This
/// // can encapsulate any "core" functionality needed to implement
/// // bindgen!-generated traits such as `Host` below.
/// pub trait MyLibraryTrait {
/// // ...
/// }
///
/// // You'll need to provide a "forwarding" implementation of this trait from
/// // `&mut T` to `T` to get the below implementation to compile.
/// impl<T: MyLibraryTrait + ?Sized> MyLibraryTrait for &mut T {
/// // ...
/// }
///
/// // This is a bit of a "hack" and an unfortunate workaround, but is currently
/// // used to work within the confines of orphan rules and such. This is
/// // publicly exposed as the function provided below must provide access to
/// // this.
/// pub struct MyLibraryImpl<T>(pub T);
///
/// // Here you'd implement all of `Host` in terms of `MyLibraryTrait`.
/// // Functions with `&mut self` would use `self.0` to access the functionality
/// // in `MyLibraryTrait`.
/// impl<T: MyLibraryTrait> Host for MyLibraryImpl<T> {
/// // ..
/// }
///
/// // optional: this avoids the need for `self.0` accessors in `Host` above,
/// // but this otherwise isn't required.
/// impl<T: MyLibraryTrait> MyLibraryTrait for MyLibraryImpl<T> {
/// // ..
/// }
///
/// // Note the second type parameter on this method, `U`, which is the user's
/// // implementation of `MyLibraryTrait`. Note that this additionally must
/// // be bounded with `'static` here.
/// pub fn add_my_library_to_linker<T, U>(
/// linker: &mut Linker<T>,
/// f: fn(&mut T) -> MyLibraryImpl<&mut U>,
/// )
/// where U: MyLibraryTrait + 'static,
/// {
/// add_to_linker::<_, MyLibrary<U>>(linker, f);
/// }
///
/// // An adjusted definition of `MyLibrary` relative to the previous example,
/// // still private, which hooks up all the types used here.
/// struct MyLibrary<U>(U);
///
/// impl<U: 'static> HasData for MyLibrary<U> {
/// type Data<'a> = MyLibraryImpl<&'a mut U>;
/// }
/// ```
///
/// This iteration of implementing component-interfaces-as-a-library is
/// unfortunately relatively verbose and unintuitive at this time. We're always
/// keen on improving this though, so if you've got ideas of how to improve this
/// please let us know!
///
/// [`bindgen!`]: super::bindgen
/// [`Linker`]: super::Linker
/// [`Store<T>`]: crate::Store
pub trait HasData: 'static {
/// The data associated with this trait implementation, chiefly used as a
/// generic associated type to allow plumbing the `'a` lifetime into the
/// definition here.
///
/// See the trait documentation for more examples.
type Data<'a>;
}
/// A convenience implementation of the [`HasData`] trait when the data
/// associated with an implementation is `&mut T`.
///
/// For more examples on using this see the [`HasData`] trait.
pub struct HasSelf<T: ?Sized>(core::marker::PhantomData<T>);
impl<T: ?Sized + 'static> HasData for HasSelf<T> {
type Data<'a> = &'a mut T;
}
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,952 @@
#[cfg(feature = "component-model-async")]
use crate::component::concurrent::Accessor;
use crate::component::func::HostFunc;
use crate::component::instance::RuntimeImport;
use crate::component::matching::{InstanceType, TypeChecker};
use crate::component::types;
use crate::component::{
Component, ComponentNamedList, Instance, InstancePre, Lift, Lower, ResourceType, Val,
};
use crate::prelude::*;
use crate::{AsContextMut, Engine, Module, StoreContextMut};
use alloc::sync::Arc;
use core::marker;
#[cfg(feature = "component-model-async")]
use core::pin::Pin;
use wasmtime_environ::component::{NameMap, NameMapIntern};
use wasmtime_environ::{Atom, PrimaryMap, StringPool};
/// A type used to instantiate [`Component`]s.
///
/// This type is used to supply host functionality to components. Values are
/// defined in a [`Linker`] by their import name and then components are
/// instantiated with a [`Linker`] using the names provided for name resolution
/// of the component's imports.
///
/// # Names and Semver
///
/// Names defined in a [`Linker`] correspond to import names in the Component
/// Model. Names in the Component Model are allowed to be semver-qualified, for
/// example:
///
/// * `wasi:cli/[email protected]`
/// * `wasi:http/[email protected]`
/// * `my:custom/[email protected]`
///
/// These version strings are taken into account when looking up names within a
/// [`Linker`]. You're allowed to define any number of versions within a
/// [`Linker`] still, for example you can define `a:b/[email protected]`, `a:b/[email protected]`,
/// and `a:b/[email protected]` all at the same time.
///
/// Specifically though when names are looked up within a linker, for example
/// during instantiation, semver-compatible names are automatically consulted.
/// This means that if you define `a:b/[email protected]` in a [`Linker`] but a component
/// imports `a:b/[email protected]` then that import will resolve to the `0.2.1` version.
///
/// This lookup behavior relies on hosts being well-behaved when using Semver,
/// specifically that interfaces once defined are never changed. This reflects
/// how Semver works at the Component Model layer, and it's assumed that if
/// versions are present then hosts are respecting this.
///
/// Note that this behavior goes the other direction, too. If a component
/// imports `a:b/[email protected]` and the host has provided `a:b/[email protected]` then that
/// will also resolve correctly. This is because if an API was defined at 0.2.0
/// and 0.2.1 then it must be the same API.
///
/// This behavior is intended to make it easier for hosts to upgrade WASI and
/// for guests to upgrade WASI. So long as the actual "meat" of the
/// functionality is defined then it should align correctly and components can
/// be instantiated.
pub struct Linker<T: 'static> {
engine: Engine,
strings: StringPool,
map: NameMap<Atom, Definition>,
path: Vec<Atom>,
allow_shadowing: bool,
_marker: marker::PhantomData<fn() -> T>,
}
impl<T: 'static> Clone for Linker<T> {
fn clone(&self) -> Linker<T> {
Linker {
engine: self.engine.clone(),
strings: self.strings.clone_panic_on_oom(),
map: self.map.clone_panic_on_oom(),
path: self.path.clone(),
allow_shadowing: self.allow_shadowing,
_marker: self._marker,
}
}
}
/// Structure representing an "instance" being defined within a linker.
///
/// Instances do not need to be actual [`Instance`]s and instead are defined by
/// a "bag of named items", so each [`LinkerInstance`] can further define items
/// internally.
pub struct LinkerInstance<'a, T: 'static> {
engine: &'a Engine,
path: &'a mut Vec<Atom>,
path_len: usize,
strings: &'a mut StringPool,
map: &'a mut NameMap<Atom, Definition>,
allow_shadowing: bool,
_marker: marker::PhantomData<fn() -> T>,
}
#[derive(Debug)]
pub(crate) enum Definition {
Instance(NameMap<Atom, Definition>),
Func(Arc<HostFunc>),
Module(Module),
Resource(ResourceType, Arc<crate::func::HostFunc>),
}
impl TryClone for Definition {
fn try_clone(&self) -> Result<Self, OutOfMemory> {
Ok(match self {
Self::Instance(i) => Self::Instance(i.try_clone()?),
Self::Func(f) => Self::Func(f.try_clone()?),
Self::Module(m) => Self::Module(m.clone()),
Self::Resource(r, f) => Self::Resource(*r, f.try_clone()?),
})
}
}
impl<T: 'static> Linker<T> {
/// Creates a new linker for the [`Engine`] specified with no items defined
/// within it.
pub fn new(engine: &Engine) -> Linker<T> {
Linker {
engine: engine.clone(),
strings: StringPool::default(),
map: NameMap::default(),
allow_shadowing: false,
path: Vec::new(),
_marker: marker::PhantomData,
}
}
/// Returns the [`Engine`] this is connected to.
pub fn engine(&self) -> &Engine {
&self.engine
}
/// Configures whether or not name-shadowing is allowed.
///
/// By default name shadowing is not allowed and it's an error to redefine
/// the same name within a linker.
pub fn allow_shadowing(&mut self, allow: bool) -> &mut Self {
self.allow_shadowing = allow;
self
}
/// Returns the "root instance" of this linker, used to define names into
/// the root namespace.
pub fn root(&mut self) -> LinkerInstance<'_, T> {
LinkerInstance {
engine: &self.engine,
path: &mut self.path,
path_len: 0,
strings: &mut self.strings,
map: &mut self.map,
allow_shadowing: self.allow_shadowing,
_marker: self._marker,
}
}
/// Returns a builder for the named instance specified.
///
/// # Errors
///
/// Returns an error if `name` is already defined within the linker.
pub fn instance(&mut self, name: &str) -> Result<LinkerInstance<'_, T>> {
self.root().into_instance(name)
}
fn typecheck<'a>(&'a self, component: &'a Component) -> Result<TypeChecker<'a>> {
let mut cx = TypeChecker {
engine: &self.engine,
types: component.types(),
strings: &self.strings,
imported_resources: try_new::<Arc<_>>(TryPrimaryMap::new())?,
};
// Walk over the component's list of import names and use that to lookup
// the definition within this linker that it corresponds to. When found
// perform a typecheck against the component's expected type.
let env_component = component.env_component();
for (_idx, (name, ty)) in env_component.import_types.iter() {
let import = self.map.get(name, &self.strings);
cx.definition(&ty.ty, import).with_context(|| {
format!(
"component imports {desc} `{name}`, but \
a matching implementation was not found in the linker",
desc = ty.ty.desc()
)
})?;
}
Ok(cx)
}
/// Returns the [`types::Component`] corresponding to `component` with resource
/// types imported by it replaced using imports present in [`Self`].
///
/// # Errors
///
/// This function will return an [`OutOfMemory`][crate::OutOfMemory] error when
/// memory allocation fails. See the `OutOfMemory` type's documentation for
/// details on Wasmtime's out-of-memory handling.
pub fn substituted_component_type(&self, component: &Component) -> Result<types::Component> {
let cx = self.typecheck(&component)?;
Ok(types::Component::from(
component.ty(),
&InstanceType {
types: cx.types,
resources: Some(&cx.imported_resources),
},
))
}
/// Performs a "pre-instantiation" to resolve the imports of the
/// [`Component`] specified with the items defined within this linker.
///
/// This method will perform as much work as possible short of actually
/// instantiating an instance. Internally this will use the names defined
/// within this linker to satisfy the imports of the [`Component`] provided.
/// Additionally this will perform type-checks against the component's
/// imports against all items defined within this linker.
///
/// Note that unlike internally in components where subtyping at the
/// interface-types layer is supported this is not supported here. Items
/// defined in this linker must match the component's imports precisely.
///
/// # Errors
///
/// Returns an error if this linker doesn't define a name that the
/// `component` imports or if a name defined doesn't match the type of the
/// item imported by the `component` provided.
///
/// Returns an error if `component` was not compiled by the same
/// [`Engine`](crate::Engine) as this linker.
///
/// This function will return an [`OutOfMemory`][crate::OutOfMemory] error when
/// memory allocation fails. See the `OutOfMemory` type's documentation for
/// details on Wasmtime's out-of-memory handling.
pub fn instantiate_pre(&self, component: &Component) -> Result<InstancePre<T>> {
ensure!(
Engine::same(&self.engine, component.engine()),
"cross-`Engine` instantiation is not currently supported"
);
let cx = self.typecheck(&component)?;
// A successful typecheck resolves all of the imported resources used by
// this InstancePre. We keep a clone of this table in the InstancePre
// so that we can construct an InstanceType for typechecking.
let imported_resources = cx.imported_resources.clone();
// Now that all imports are known to be defined and satisfied by this
// linker a list of "flat" import items (aka no instances) is created
// using the import map within the component created at
// component-compile-time.
let env_component = component.env_component();
let mut imports = PrimaryMap::with_capacity(env_component.imports.len());
for (idx, (import, names)) in env_component.imports.iter() {
let (root, _) = &env_component.import_types[*import];
// This is the flattening process where we go from a definition
// optionally through a list of exported names to get to the final
// item.
let mut cur = self.map.get(root, &self.strings).unwrap();
for name in names {
cur = match cur {
Definition::Instance(map) => map.get(&name, &self.strings).unwrap(),
_ => unreachable!(),
};
}
let import = match cur {
Definition::Module(m) => RuntimeImport::Module(m.clone()),
Definition::Func(f) => RuntimeImport::Func(f.clone()),
Definition::Resource(t, dtor) => RuntimeImport::Resource {
ty: *t,
dtor: dtor.clone(),
dtor_funcref: component.resource_drop_func_ref(dtor),
},
// This is guaranteed by the compilation process that "leaf"
// runtime imports are never instances.
Definition::Instance(_) => unreachable!(),
};
let i = imports.push(import);
assert_eq!(i, idx);
}
Ok(unsafe {
InstancePre::new_unchecked(
component.clone(),
try_new::<Arc<_>>(imports)?,
imported_resources,
)
})
}
/// Instantiates the [`Component`] provided into the `store` specified.
///
/// This function will use the items defined within this [`Linker`] to
/// satisfy the imports of the [`Component`] provided as necessary. For more
/// information about this see [`Linker::instantiate_pre`] as well.
///
/// # Errors
///
/// Returns an error if this [`Linker`] doesn't define an import that
/// `component` requires or if it is of the wrong type. Additionally this
/// can return an error if something goes wrong during instantiation such as
/// a runtime trap or a runtime limit being exceeded.
///
/// This function will return an [`OutOfMemory`][crate::OutOfMemory] error when
/// memory allocation fails. See the `OutOfMemory` type's documentation for
/// details on Wasmtime's out-of-memory handling.
pub fn instantiate(
&self,
mut store: impl AsContextMut<Data = T>,
component: &Component,
) -> Result<Instance> {
let store = store.as_context_mut();
store.0.validate_sync_call()?;
self.instantiate_pre(component)?.instantiate(store)
}
/// Instantiates the [`Component`] provided into the `store` specified.
///
/// This is exactly like [`Linker::instantiate`] except for [asynchronous
/// execution](crate#async).
///
/// # Errors
///
/// Returns an error if this [`Linker`] doesn't define an import that
/// `component` requires or if it is of the wrong type. Additionally this
/// can return an error if something goes wrong during instantiation such as
/// a runtime trap or a runtime limit being exceeded.
///
/// This function will return an [`OutOfMemory`][crate::OutOfMemory] error when
/// memory allocation fails. See the `OutOfMemory` type's documentation for
/// details on Wasmtime's out-of-memory handling.
#[cfg(feature = "async")]
pub async fn instantiate_async(
&self,
store: impl AsContextMut<Data = T>,
component: &Component,
) -> Result<Instance>
where
T: Send,
{
self.instantiate_pre(component)?
.instantiate_async(store)
.await
}
/// Implement any imports of the given [`Component`] with a function which traps.
///
/// By default a [`Linker`] will error when unknown imports are encountered when instantiating a [`Component`].
/// This changes this behavior from an instant error to a trap that will happen if the import is called.
///
/// # Errors
///
/// This function will return an [`OutOfMemory`][crate::OutOfMemory] error when
/// memory allocation fails. See the `OutOfMemory` type's documentation for
/// details on Wasmtime's out-of-memory handling.
pub fn define_unknown_imports_as_traps(&mut self, component: &Component) -> Result<()> {
use wasmtime_environ::component::ComponentTypes;
use wasmtime_environ::component::TypeDef;
// Recursively stub out all imports of the component with a function that traps.
fn stub_item<T>(
linker: &mut LinkerInstance<T>,
item_name: &str,
item_def: &TypeDef,
parent_instance: Option<&str>,
types: &ComponentTypes,
) -> Result<()> {
// Skip if the item isn't an instance and has already been defined in the linker.
if !matches!(item_def, TypeDef::ComponentInstance(_)) && linker.get(item_name).is_some()
{
return Ok(());
}
match item_def {
TypeDef::ComponentFunc(_func_idx) => {
let fully_qualified_name = match parent_instance {
Some(parent) => {
let mut s = TryString::new();
s.push_str(parent)?;
s.push('#')?;
s.push_str(item_name)?;
s
}
None => {
let mut s = TryString::new();
s.push_str(item_name)?;
s
}
};
// An `async func`-typed import can never be satisfied by
// `func_new` (only a sync-typed import can) — see
// `typecheck_async`'s doc comment. Stub it with
// `func_new_concurrent` instead so unsatisfied async
// imports can be stubbed-as-traps too, not just sync
// ones; if concurrency support isn't enabled there's no
// way to stub it here, so fall through to `func_new` and
// let instantiation fail with that same explanatory
// error.
#[cfg(feature = "component-model-async")]
if types[*_func_idx].async_ && linker.engine.tunables().concurrency_support {
linker.func_new_concurrent(&item_name, move |_, _, _, _| {
let fully_qualified_name = fully_qualified_name.try_clone();
Box::pin(async move {
let fully_qualified_name = fully_qualified_name?;
bail!(
"unknown import: `{fully_qualified_name}` has not been defined"
)
})
})?;
return Ok(());
}
linker.func_new(&item_name, move |_, _, _, _| {
bail!("unknown import: `{fully_qualified_name}` has not been defined")
})?;
}
TypeDef::ComponentInstance(i) => {
let instance = &types[*i];
let mut linker_instance = linker.instance(item_name)?;
for (export_name, export) in instance.exports.iter() {
stub_item(
&mut linker_instance,
export_name,
&export.ty,
Some(item_name),
types,
)?;
}
}
TypeDef::Resource(_) => {
let ty = crate::component::ResourceType::host::<()>();
linker.resource(item_name, ty, |_, _| Ok(()))?;
}
TypeDef::Component(_) | TypeDef::Module(_) => {
bail!("unable to define {} imports as traps", item_def.desc())
}
_ => {}
}
Ok(())
}
for (_, (import_name, import_type)) in &component.env_component().import_types {
stub_item(
&mut self.root(),
import_name,
&import_type.ty,
None,
component.types(),
)?;
}
Ok(())
}
}
impl<T: 'static> LinkerInstance<'_, T> {
fn as_mut(&mut self) -> LinkerInstance<'_, T> {
LinkerInstance {
engine: self.engine,
path: self.path,
path_len: self.path_len,
strings: self.strings,
map: self.map,
allow_shadowing: self.allow_shadowing,
_marker: self._marker,
}
}
/// Defines a new host-provided function into this [`LinkerInstance`].
///
/// This method is used to give host functions to wasm components. The
/// `func` provided will be callable from linked components with the type
/// signature dictated by `Params` and `Return`. The `Params` is a tuple of
/// types that will come from wasm and `Return` is a value coming from the
/// host going back to wasm.
///
/// Additionally the `func` takes a
/// [`StoreContextMut`](crate::StoreContextMut) as its first parameter.
///
/// Note that `func` must be an `Fn` and must also be `Send + Sync +
/// 'static`. Shared state within a func is typically accessed with the `T`
/// type parameter from [`Store<T>`](crate::Store) which is accessible
/// through the leading [`StoreContextMut<'_, T>`](crate::StoreContextMut)
/// argument which can be provided to the `func` given here.
///
/// # Blocking / Async Behavior
///
/// The host function `func` provided here is a blocking function from the
/// perspective of WebAssembly. WebAssembly, and Rust, will be blocked until
/// `func` completes.
///
/// To define a function which is async on the host, but blocking to the
/// guest, see the [`func_wrap_async`] method.
///
/// [`func_wrap_async`]: LinkerInstance::func_wrap_async
///
/// # Errors
///
/// This function will return an [`OutOfMemory`][crate::OutOfMemory] error when
/// memory allocation fails. See the `OutOfMemory` type's documentation for
/// details on Wasmtime's out-of-memory handling.
//
// TODO: needs more words and examples
pub fn func_wrap<F, Params, Return>(&mut self, name: &str, func: F) -> Result<()>
where
F: Fn(StoreContextMut<T>, Params) -> Result<Return> + Send + Sync + 'static,
Params: ComponentNamedList + Lift + 'static,
Return: ComponentNamedList + Lower + 'static,
{
self.insert(name, Definition::Func(HostFunc::func_wrap(func)?))?;
Ok(())
}
/// Defines a new host-provided async function into this [`LinkerInstance`].
///
/// This function is similar to [`Self::func_wrap`] except it takes an async
/// host function instead of a blocking host function. The `F` function here
/// is intended to be:
///
/// ```ignore
/// F: AsyncFn(StoreContextMut<'_, T>, Params) -> Result<Return>
/// ```
///
/// however the returned future must be `Send` which is not possible to
/// bound at this time. This will be switched to an async closure once Rust
/// supports it.
///
/// # Blocking / Async Behavior
///
/// The function defined which WebAssembly calls will still appear as
/// blocking from the perspective of WebAssembly itself. The host, however,
/// can perform asynchronous operations without blocking the thread
/// performing a call.
///
/// When defining host functions with this function, WebAssembly is invoked
/// on a separate stack within a Wasmtime-managed fiber (through the
/// `call_async`-style of invocation). This means that if the future
/// returned by `F` is not immediately ready then the fiber will be
/// suspended to block WebAssembly but not the host. When the future
/// becomes ready again the fiber will be resumed to continue execution
/// within WebAssembly.
///
/// [`func_wrap_async`]: LinkerInstance::func_wrap_async
#[cfg(feature = "async")]
pub fn func_wrap_async<Params, Return, F>(&mut self, name: &str, f: F) -> Result<()>
where
F: Fn(
StoreContextMut<'_, T>,
Params,
) -> Box<dyn Future<Output = Result<Return>> + Send + '_>
+ Send
+ Sync
+ 'static,
Params: ComponentNamedList + Lift + 'static,
Return: ComponentNamedList + Lower + 'static,
{
self.insert(name, Definition::Func(HostFunc::func_wrap_async(f)?))?;
Ok(())
}
/// Defines a new host-provided async function into this [`LinkerInstance`].
///
/// This function defines a host function available to call from
/// WebAssembly. WebAssembly may additionally make multiple invocations of
/// this function concurrently all at the same time. This function requires
/// the [`Config::wasm_component_model_async`] feature to be enabled.
///
/// The function `f` provided will be invoked when called by WebAssembly.
/// WebAssembly components may then call `f` multiple times while previous
/// invocations of `f` are already running. Additionally while `f` is
/// running other host functions may be invoked.
///
/// The `F` function here is intended to be:
///
/// ```ignore
/// F: AsyncFn(&Accessor<T>, Params) -> Result<Return>
/// ```
///
/// however the returned future must be `Send` which is not possible to
/// bound at this time. This will be switched to an async closure once Rust
/// supports it.
///
/// The closure `f` is provided an [`Accessor`] which can be used to acquire
/// temporary, blocking, access to a [`StoreContextMut`] (through
/// [`Access`](crate::component::Access]). This models how a store is not
/// available to `f` across `await` points but it is temporarily available
/// while actively being polled.
///
/// # Blocking / Async Behavior
///
/// Unlike [`Self::func_wrap`] and [`Self::func_wrap_async`] this function
/// is asynchronous even from the perspective of guest WebAssembly. This
/// means that if `f` is not immediately resolved then the call from
/// WebAssembly will still return immediately (assuming it was lowered with
/// `async`). The closure `f` should not block the current thread and
/// should only perform blocking via `async` meaning that `f` won't block
/// either WebAssembly nor the host.
///
/// Note that WebAssembly components can lower host functions both with and
/// without `async`. That means that even if a host function is defined in
/// the "concurrent" mode here a guest may still lower it synchronously. In
/// this situation Wasmtime will manage blocking the guest while the closure
/// `f` provided here completes. If a guest lowers this function with
/// `async`, though, then no blocking will happen.
///
/// [`Config::wasm_component_model_async`]: crate::Config::wasm_component_model_async
/// [`func_wrap_async`]: LinkerInstance::func_wrap_async
#[cfg(feature = "component-model-async")]
pub fn func_wrap_concurrent<Params, Return, F>(&mut self, name: &str, f: F) -> Result<()>
where
T: 'static,
F: Fn(&Accessor<T>, Params) -> Pin<Box<dyn Future<Output = Result<Return>> + Send + '_>>
+ Send
+ Sync
+ 'static,
Params: ComponentNamedList + Lift + 'static,
Return: ComponentNamedList + Lower + 'static,
{
if !self.engine.tunables().concurrency_support {
bail!("concurrent host functions require `Config::concurrency_support`");
}
self.insert(name, Definition::Func(HostFunc::func_wrap_concurrent(f)?))?;
Ok(())
}
/// Define a new host-provided function using dynamically typed values.
///
/// The `name` provided is the name of the function to define and the
/// `func` provided is the host-defined closure to invoke when this
/// function is called.
///
/// This function is the "dynamic" version of defining a host function as
/// compared to [`LinkerInstance::func_wrap`]. With
/// [`LinkerInstance::func_wrap`] a function's type is statically known but
/// with this method the `func` argument's type isn't known ahead of time.
/// That means that `func` can be by imported component so long as it's
/// imported as a matching name.
///
/// Type information will be available at execution time, however. For
/// example when `func` is invoked the second argument, a `&[Val]` list,
/// contains [`Val`] entries that say what type they are. Additionally the
/// third argument, `&mut [Val]`, is the expected number of results. Note
/// that the expected types of the results cannot be learned during the
/// execution of `func`. Learning that would require runtime introspection
/// of a component.
///
/// Return values, stored in the third argument of `&mut [Val]`, are
/// type-checked at runtime to ensure that they have the appropriate type.
/// A trap will be raised if they do not have the right type.
///
/// # Examples
///
/// ```
/// use wasmtime::{Store, Engine};
/// use wasmtime::component::{Component, Linker, Val};
///
/// # fn main() -> wasmtime::Result<()> {
/// let engine = Engine::default();
/// let component = Component::new(
/// &engine,
/// r#"
/// (component
/// (import "thunk" (func $thunk))
/// (import "is-even" (func $is-even (param "x" u32) (result bool)))
///
/// (core module $m
/// (import "" "thunk" (func $thunk))
/// (import "" "is-even" (func $is-even (param i32) (result i32)))
///
/// (func (export "run")
/// call $thunk
///
/// (call $is-even (i32.const 1))
/// if unreachable end
///
/// (call $is-even (i32.const 2))
/// i32.eqz
/// if unreachable end
/// )
/// )
/// (core func $thunk (canon lower (func $thunk)))
/// (core func $is-even (canon lower (func $is-even)))
/// (core instance $i (instantiate $m
/// (with "" (instance
/// (export "thunk" (func $thunk))
/// (export "is-even" (func $is-even))
/// ))
/// ))
///
/// (func (export "run") (canon lift (core func $i "run")))
/// )
/// "#,
/// )?;
///
/// let mut linker = Linker::<()>::new(&engine);
///
/// // Sample function that takes no arguments.
/// linker.root().func_new("thunk", |_store, _ty, params, results| {
/// assert!(params.is_empty());
/// assert!(results.is_empty());
/// println!("Look ma, host hands!");
/// Ok(())
/// })?;
///
/// // This function takes one argument and returns one result.
/// linker.root().func_new("is-even", |_store, _ty, params, results| {
/// assert_eq!(params.len(), 1);
/// let param = match params[0] {
/// Val::U32(n) => n,
/// _ => panic!("unexpected type"),
/// };
///
/// assert_eq!(results.len(), 1);
/// results[0] = Val::Bool(param % 2 == 0);
/// Ok(())
/// })?;
///
/// let mut store = Store::new(&engine, ());
/// let instance = linker.instantiate(&mut store, &component)?;
/// let run = instance.get_typed_func::<(), ()>(&mut store, "run")?;
/// run.call(&mut store, ())?;
/// # Ok(())
/// # }
/// ```
///
/// # Errors
///
/// This function will return an [`OutOfMemory`][crate::OutOfMemory] error when
/// memory allocation fails. See the `OutOfMemory` type's documentation for
/// details on Wasmtime's out-of-memory handling.
pub fn func_new(
&mut self,
name: &str,
func: impl Fn(StoreContextMut<'_, T>, types::ComponentFunc, &[Val], &mut [Val]) -> Result<()>
+ Send
+ Sync
+ 'static,
) -> Result<()> {
self.insert(name, Definition::Func(HostFunc::func_new(func)?))?;
Ok(())
}
/// Define a new host-provided async function using dynamic types.
///
/// As [`Self::func_wrap_async`] is a dual of [`Self::func_wrap`], this
/// function is the dual of [`Self::func_new`].
///
/// For documentation on blocking behavior see [`Self::func_wrap_async`].
#[cfg(feature = "async")]
pub fn func_new_async<F>(&mut self, name: &str, func: F) -> Result<()>
where
F: for<'a> Fn(
StoreContextMut<'a, T>,
types::ComponentFunc,
&'a [Val],
&'a mut [Val],
) -> Box<dyn Future<Output = Result<()>> + Send + 'a>
+ Send
+ Sync
+ 'static,
{
self.insert(name, Definition::Func(HostFunc::func_new_async(func)?))?;
Ok(())
}
/// Define a new host-provided async function using dynamic types.
///
/// As [`Self::func_wrap_concurrent`] is a dual of [`Self::func_wrap`], this
/// function is the dual of [`Self::func_new`].
///
/// For documentation on async/blocking behavior see
/// [`Self::func_wrap_concurrent`].
#[cfg(feature = "component-model-async")]
pub fn func_new_concurrent<F>(&mut self, name: &str, f: F) -> Result<()>
where
T: 'static,
F: for<'a> Fn(
&'a Accessor<T>,
types::ComponentFunc,
&'a [Val],
&'a mut [Val],
) -> Pin<Box<dyn Future<Output = Result<()>> + Send + 'a>>
+ Send
+ Sync
+ 'static,
{
if !self.engine.tunables().concurrency_support {
bail!("concurrent host functions require `Config::concurrency_support`");
}
self.insert(name, Definition::Func(HostFunc::func_new_concurrent(f)?))?;
Ok(())
}
/// Defines a [`Module`] within this instance.
///
/// This can be used to provide a core wasm [`Module`] as an import to a
/// component. The [`Module`] provided is saved within the linker for the
/// specified `name` in this instance.
///
/// # Errors
///
/// This function will return an [`OutOfMemory`][crate::OutOfMemory] error when
/// memory allocation fails. See the `OutOfMemory` type's documentation for
/// details on Wasmtime's out-of-memory handling.
pub fn module(&mut self, name: &str, module: &Module) -> Result<()> {
self.insert(name, Definition::Module(module.clone()))?;
Ok(())
}
/// Defines a new resource of a given [`ResourceType`] in this linker.
///
/// This function is used to specify resources defined in the host.
///
/// The `name` argument is the name to define the resource within this
/// linker.
///
/// The `dtor` provided is a destructor that will get invoked when an owned
/// version of this resource is destroyed from the guest. Note that this
/// destructor is not called when a host-owned resource is destroyed as it's
/// assumed the host knows how to handle destroying its own resources.
///
/// The `dtor` closure is provided the store state as the first argument
/// along with the representation of the resource that was just destroyed.
///
/// [`Resource<U>`]: crate::component::Resource
///
/// # Errors
///
/// The provided `dtor` closure returns an error if something goes wrong
/// when a guest calls the `dtor` to drop a `Resource<T>` such as
/// a runtime trap or a runtime limit being exceeded.
///
/// This function will return an [`OutOfMemory`][crate::OutOfMemory] error when
/// memory allocation fails. See the `OutOfMemory` type's documentation for
/// details on Wasmtime's out-of-memory handling.
pub fn resource(
&mut self,
name: &str,
ty: ResourceType,
dtor: impl Fn(StoreContextMut<'_, T>, u32) -> Result<()> + Send + Sync + 'static,
) -> Result<()> {
let dtor = try_new::<Arc<_>>(crate::func::HostFunc::wrap(
&self.engine,
move |mut cx: crate::Caller<'_, T>, (param,): (u32,)| dtor(cx.as_context_mut(), param),
)?)?;
self.insert(name, Definition::Resource(ty, dtor))?;
Ok(())
}
/// Identical to [`Self::resource`], except that it takes an async destructor.
#[cfg(feature = "async")]
pub fn resource_async<F>(&mut self, name: &str, ty: ResourceType, dtor: F) -> Result<()>
where
T: Send,
F: Fn(StoreContextMut<'_, T>, u32) -> Box<dyn Future<Output = Result<()>> + Send + '_>
+ Send
+ Sync
+ 'static,
{
let dtor = try_new::<Arc<_>>(crate::func::HostFunc::wrap_async(
&self.engine,
move |cx: crate::Caller<'_, T>, (param,): (u32,)| dtor(cx.into(), param),
)?)?;
self.insert(name, Definition::Resource(ty, dtor))?;
Ok(())
}
/// Identical to [`Self::resource`], except that it takes a concurrent destructor.
#[cfg(feature = "component-model-async")]
pub fn resource_concurrent<F>(&mut self, name: &str, ty: ResourceType, dtor: F) -> Result<()>
where
T: Send + 'static,
F: Fn(&Accessor<T>, u32) -> Pin<Box<dyn Future<Output = Result<()>> + Send + '_>>
+ Send
+ Sync
+ 'static,
{
if !self.engine.tunables().concurrency_support {
bail!("concurrent host functions require `Config::concurrency_support`");
}
// TODO: This isn't really concurrent -- it requires exclusive access to
// the store for the duration of the call, preventing guest code from
// running until it completes. We should make it concurrent and clean
// up the implementation to avoid using e.g. `Accessor::new` and
// `tls::set` directly.
let dtor = Arc::new(dtor);
let dtor = Arc::new(crate::func::HostFunc::wrap_async(
&self.engine,
move |mut cx: crate::Caller<'_, T>, (param,): (u32,)| {
let dtor = dtor.clone();
Box::new(async move {
let mut store = cx.as_context_mut();
let accessor =
&Accessor::new(crate::store::StoreToken::new(store.as_context_mut()));
let mut future = core::pin::pin!(dtor(accessor, param));
core::future::poll_fn(|cx| {
crate::component::concurrent::tls::set(store.0, || future.as_mut().poll(cx))
})
.await
})
},
)?);
self.insert(name, Definition::Resource(ty, dtor))?;
Ok(())
}
/// Defines a nested instance within this instance.
///
/// This can be used to describe arbitrarily nested levels of instances
/// within a linker to satisfy nested instance exports of components.
pub fn instance(&mut self, name: &str) -> Result<LinkerInstance<'_, T>> {
self.as_mut().into_instance(name)
}
/// Same as [`LinkerInstance::instance`] except with different lifetime
/// parameters.
pub fn into_instance(mut self, name: &str) -> Result<Self> {
let atom = self.strings.intern(name)?;
// If this item is already an instance then don't stomp over it with a
// new empty instance (or fail due to shadowing being disallowed).
// Instead continue through to below to explicitly allow re-opening an
// instance multiple times over separate API calls.
//
// If this item isn't defined, or is defined as anything other than an
// instance, however, the insert a fresh new instance and see what
// happens as a result.
match self.map.raw_get_mut(&atom) {
Some(Definition::Instance(_)) => {}
_ => {
self.insert(name, Definition::Instance(NameMap::default()))?;
}
}
self.map = match self.map.raw_get_mut(&atom) {
Some(Definition::Instance(map)) => map,
_ => unreachable!(),
};
self.path.truncate(self.path_len);
self.path.push(atom);
self.path_len += 1;
Ok(self)
}
fn insert(&mut self, name: &str, item: Definition) -> Result<Atom> {
self.map
.insert(name, self.strings, self.allow_shadowing, item)
}
fn get(&self, name: &str) -> Option<&Definition> {
self.map.get(name, self.strings)
}
}
@@ -0,0 +1,221 @@
use crate::Module;
use crate::component::ResourceType;
use crate::component::func::HostFunc;
use crate::component::linker::Definition;
use crate::component::types::{FutureType, StreamType};
use crate::runtime::vm::component::ComponentInstance;
use crate::types::matching;
use crate::{Engine, prelude::*};
use alloc::sync::Arc;
use wasmtime_environ::component::{
ComponentTypes, NameMap, ResourceIndex, TypeComponentInstance, TypeDef, TypeFuncIndex,
TypeFutureTableIndex, TypeModule, TypeResourceTable, TypeResourceTableIndex,
TypeStreamTableIndex,
};
use wasmtime_environ::prelude::TryPrimaryMap;
use wasmtime_environ::{Atom, StringPool};
pub struct TypeChecker<'a> {
pub engine: &'a Engine,
pub types: &'a Arc<ComponentTypes>,
pub strings: &'a StringPool,
pub imported_resources: Arc<TryPrimaryMap<ResourceIndex, ResourceType>>,
}
#[derive(Copy, Clone)]
#[doc(hidden)]
pub struct InstanceType<'a> {
pub types: &'a Arc<ComponentTypes>,
/// Resource type substitutions for resources within `ComponentTypes`, if
/// any.
///
/// If this isn't present then resource types are considered "abstract" and
/// un-intantiated. This doesn't refer to a concrete instantiated component,
/// but rather an abstract component type.
pub resources: Option<&'a Arc<TryPrimaryMap<ResourceIndex, ResourceType>>>,
}
impl TypeChecker<'_> {
pub(crate) fn definition(
&mut self,
expected: &TypeDef,
actual: Option<&Definition>,
) -> Result<()> {
match *expected {
TypeDef::Module(t) => match actual {
Some(Definition::Module(actual)) => self.module(&self.types[t], actual),
Some(actual) => bail!("expected module found {}", actual.desc()),
None => bail!("module implementation is missing"),
},
TypeDef::ComponentInstance(t) => match actual {
Some(Definition::Instance(actual)) => self.instance(&self.types[t], Some(actual)),
None => self.instance(&self.types[t], None),
Some(actual) => bail!("expected instance found {}", actual.desc()),
},
TypeDef::ComponentFunc(t) => match actual {
Some(Definition::Func(actual)) => self.func(t, actual),
Some(actual) => bail!("expected function found {}", actual.desc()),
None => bail!("function implementation is missing"),
},
TypeDef::Component(_) => match actual {
Some(actual) => bail!("expected component found {}", actual.desc()),
None => bail!("component implementation is missing"),
},
TypeDef::Interface(_) => match actual {
Some(actual) => bail!("expected type found {}", actual.desc()),
None => bail!("type implementation is missing"),
},
TypeDef::Resource(i) => {
let i = self.types[i].unwrap_concrete_ty();
let actual = match actual {
Some(Definition::Resource(actual, _dtor)) => actual,
// If a resource is imported yet nothing was supplied then
// that's only successful if the resource has itself
// already been defined. If it's already defined then that
// means that this is an `(eq ...)` import which is not
// required to be satisfied via `Linker` definitions in the
// Wasmtime API.
None if self.imported_resources.get(i).is_some() => return Ok(()),
Some(actual) => bail!("expected resource found {}", actual.desc()),
None => bail!("resource implementation is missing"),
};
match self.imported_resources.get(i) {
// If `i` hasn't been pushed onto `imported_resources` yet
// then that means that it's the first time a new resource
// was introduced, so record the type of this resource. It
// should always be the case that the next index assigned
// is equal to `i` since types should be checked in the
// same order they were assigned into the `Component` type.
//
// Note the `get_mut` here which is expected to always
// succeed since `imported_resources` has not yet been
// cloned.
None => {
let resources = Arc::get_mut(&mut self.imported_resources).unwrap();
let id = resources.push(*actual)?;
assert_eq!(id, i);
}
// If `i` has been defined, however, then that means that
// this is an `(eq ..)` bounded type imported because it's
// referring to a previously defined type. In this
// situation it's not required to provide a type import but
// if it's supplied then it must be equal. In this situation
// it's supplied, so test for equality.
Some(expected) => {
if expected != actual {
bail!("mismatched resource types");
}
}
}
Ok(())
}
// not possible for valid components to import
TypeDef::CoreFunc(_) => unreachable!(),
}
}
fn module(&self, expected: &TypeModule, actual: &Module) -> Result<()> {
let actual = actual.env_module();
// Every export that is expected should be in the actual module we have
for (name, expected) in expected.exports.iter() {
let idx = actual
.strings
.get_atom(name)
.and_then(|atom| actual.exports.get(&atom))
.ok_or_else(|| format_err!("module export `{name}` not defined"))?;
let actual = actual.type_of(*idx);
matching::entity_ty(self.engine, expected, &actual)
.with_context(|| format!("module export `{name}` has the wrong type"))?;
}
// Note the opposite order of checks here. Every import that the actual
// module expects should be imported by the expected module since the
// expected module has the set of items given to the actual module.
// Additionally the "matches" check is inverted here.
for (module, name, actual) in actual.imports() {
// TODO: shouldn't need a `.to_string()` here ideally
let expected = expected
.imports
.get(&(module.to_string(), name.to_string()))
.ok_or_else(|| format_err!("module import `{module}::{name}` not defined"))?;
matching::entity_ty(self.engine, &actual, expected)
.with_context(|| format!("module import `{module}::{name}` has the wrong type"))?;
}
Ok(())
}
fn instance(
&mut self,
expected: &TypeComponentInstance,
actual: Option<&NameMap<Atom, Definition>>,
) -> Result<()> {
// Like modules, every export in the expected type must be present in
// the actual type. It's ok, though, to have extra exports in the actual
// type.
for (name, expected) in expected.exports.iter() {
// Interface types may be exported from a component in order to give them a name, but
// they don't have a definition in the sense that this search is interested in, so
// ignore them.
if let TypeDef::Interface(_) = expected.ty {
continue;
}
let actual = actual.and_then(|actual| actual.get(name, self.strings));
self.definition(&expected.ty, actual)
.with_context(|| format!("instance export `{name}` has the wrong type"))?;
}
Ok(())
}
fn func(&self, expected: TypeFuncIndex, actual: &HostFunc) -> Result<()> {
let instance_type = InstanceType {
types: self.types,
resources: Some(&self.imported_resources),
};
actual.typecheck(expected, &instance_type)
}
}
impl Definition {
fn desc(&self) -> &'static str {
match self {
Definition::Module(_) => "module",
Definition::Func(_) => "func",
Definition::Instance(_) => "instance",
Definition::Resource(..) => "resource",
}
}
}
impl<'a> InstanceType<'a> {
pub fn new(instance: &'a ComponentInstance) -> InstanceType<'a> {
InstanceType {
types: instance.component().types(),
resources: Some(instance.resource_types()),
}
}
pub fn resource_type(&self, index: TypeResourceTableIndex) -> ResourceType {
match self.types[index] {
TypeResourceTable::Concrete { ty, .. } => self
.resources
.and_then(|t| t.get(ty).copied())
.unwrap_or_else(|| ResourceType::uninstantiated(&self.types, ty)),
TypeResourceTable::Abstract(ty) => ResourceType::abstract_(&self.types, ty),
}
}
pub fn future_type(&self, index: TypeFutureTableIndex) -> FutureType {
FutureType::from(self.types[index].ty, self)
}
pub fn stream_type(&self, index: TypeStreamTableIndex) -> StreamType {
StreamType::from(self.types[index].ty, self)
}
}
+812
View File
@@ -0,0 +1,812 @@
//! # Embedding API for the Component Model
//!
//! This module contains the embedding API for the [Component Model] in
//! Wasmtime. This module requires the `component-model` feature to be enabled,
//! which is enabled by default. The embedding API here is mirrored after the
//! core wasm embedding API at the crate root and is intended to have the same
//! look-and-feel while handling concepts of the component model.
//!
//! [Component Model]: https://component-model.bytecodealliance.org
//!
//! The component model is a broad topic which can't be explained here fully, so
//! it's recommended to read over individual items' documentation to see more
//! about the capabilities of the embedding API. At a high-level, however,
//! perhaps the most interesting items in this module are:
//!
//! * [`Component`] - a compiled component ready to be instantiated. Similar to
//! a [`Module`](crate::Module) for core wasm.
//!
//! * [`Linker`] - a component-style location for defining host functions. This
//! is not the same as [`wasmtime::Linker`](crate::Linker) for core wasm
//! modules.
//!
//! * [`bindgen!`] - a macro to generate Rust bindings for a [WIT] [world]. This
//! maps all WIT types into Rust automatically and generates traits for
//! embedders to implement.
//!
//! [WIT]: https://component-model.bytecodealliance.org/design/wit.html
//! [world]: https://component-model.bytecodealliance.org/design/worlds.html
//!
//! Embedders of the component model will typically start by defining their API
//! in [WIT]. This describes what will be available to guests and what needs to
//! be provided to the embedder by the guest. This [`world`][world] that was
//! created is then fed into [`bindgen!`] to generate types and traits for the
//! embedder to use. The embedder then implements these traits, adds
//! functionality via the generated `add_to_linker` method (see [`bindgen!`] for
//! more info), and then instantiates/executes a component.
//!
//! It's recommended to read over the [documentation for the Component
//! Model][Component Model] to get an overview about how to build components
//! from various languages.
//!
//! ## Example Usage
//!
//! Imagine you have the following WIT package definition in a file called world.wit
//! along with a component (my_component.wasm) that targets `my-world`:
//!
//! ```text,ignore
//! package component:my-package;
//!
//! world my-world {
//! import name: func() -> string;
//! export greet: func() -> string;
//! }
//! ```
//!
//! You can instantiate and call the component like so:
//!
//! ```
//! fn main() -> wasmtime::Result<()> {
//! # if true { return Ok(()) }
//! // Instantiate the engine and store
//! let engine = wasmtime::Engine::default();
//! let mut store = wasmtime::Store::new(&engine, ());
//!
//! // Load the component from disk
//! let bytes = std::fs::read("my_component.wasm")?;
//! let component = wasmtime::component::Component::new(&engine, bytes)?;
//!
//! // Configure the linker
//! let mut linker = wasmtime::component::Linker::new(&engine);
//! // The component expects one import `name` that
//! // takes no params and returns a string
//! linker
//! .root()
//! .func_wrap("name", |_store, _params: ()| {
//! Ok((String::from("Alice"),))
//! })?;
//!
//! // Instantiate the component
//! let instance = linker.instantiate(&mut store, &component)?;
//!
//! // Call the `greet` function
//! let func = instance.get_func(&mut store, "greet").expect("greet export not found");
//! let mut result = [wasmtime::component::Val::String("".into())];
//! func.call(&mut store, &[], &mut result)?;
//!
//! // This should print out `Greeting: [String("Hello, Alice!")]`
//! println!("Greeting: {:?}", result);
//!
//! Ok(())
//! }
//! ```
//!
//! Manually configuring the linker and calling untyped component exports is
//! a bit tedious and error prone. The [`bindgen!`] macro can be used to
//! generate bindings eliminating much of this boilerplate.
//!
//! See the docs for [`bindgen!`] for more information on how to use it.
#![allow(
rustdoc::redundant_explicit_links,
reason = "rustdoc appears to lie about a warning above, so squelch it for now"
)]
mod component;
#[cfg(feature = "component-model-async")]
pub(crate) mod concurrent;
mod func;
mod has_data;
mod instance;
mod linker;
mod matching;
mod resource_table;
mod resources;
mod storage;
pub(crate) mod store;
pub mod types;
mod values;
pub use self::component::{Component, ComponentExportIndex, ExportLookup};
#[cfg(feature = "component-model-async")]
pub use self::concurrent::{
Access, Accessor, AccessorTask, AsAccessor, Destination, DirectDestination, DirectSource,
ErrorContext, FuncCallConcurrent, FutureAny, FutureConsumer, FutureProducer, FutureReader,
GuardedFutureReader, GuardedStreamReader, GuestTaskId, JoinHandle, ReadBuffer, Source,
StreamAny, StreamConsumer, StreamProducer, StreamReader, StreamResult, TypedFuncCallConcurrent,
VMComponentAsyncStore, VecBuffer, WriteBuffer,
};
pub use self::func::{
ComponentNamedList, ComponentType, Func, Lift, Lower, TypedFunc, WasmList, WasmStr,
};
pub use self::has_data::*;
pub use self::instance::{Instance, InstancePre};
pub use self::linker::{Linker, LinkerInstance};
pub use self::resource_table::{ResourceTable, ResourceTableError};
pub use self::resources::{Resource, ResourceAny, ResourceDynamic};
pub use self::types::{ResourceType, Type};
pub use self::values::Val;
pub(crate) use self::instance::RuntimeImport;
pub(crate) use self::resources::HostResourceData;
pub(crate) use self::store::{ComponentInstanceId, RuntimeInstance};
// Re-export wasm_wave crate so the compatible version of this dep doesn't have to be
// tracked separately from wasmtime.
#[cfg(feature = "wave")]
pub use wasm_wave;
// Re-export wit-parser crate so the compatible version of this dep doesn't have to be
// tracked separately from wasmtime.
#[cfg(feature = "wit-parser")]
pub use wit_parser;
// These items are used by `#[derive(ComponentType, Lift, Lower)]`, but they are not part of
// Wasmtime's API stability guarantees
#[doc(hidden)]
pub mod __internal {
pub use super::func::{
ComponentVariant, LiftContext, LowerContext, bad_type_info, format_flags, lower_payload,
typecheck_enum, typecheck_flags, typecheck_record, typecheck_variant,
};
pub use super::matching::InstanceType;
pub use crate::MaybeUninitExt;
pub use crate::map_maybe_uninit;
pub use crate::store::StoreOpaque;
pub use alloc::boxed::Box;
pub use alloc::string::String;
pub use alloc::vec::Vec;
pub use core::cell::RefCell;
pub use core::future::Future;
pub use core::mem::transmute;
pub use wasmtime_environ;
pub use wasmtime_environ::component::{CanonicalAbiInfo, ComponentTypes, InterfaceType};
}
pub(crate) use self::store::ComponentStoreData;
/// Generate bindings for a [WIT world].
///
/// [WIT world]: https://component-model.bytecodealliance.org/design/worlds.html
/// [WIT package]: https://component-model.bytecodealliance.org/design/packages.html
///
/// This macro ingests a [WIT world] and will generate all the necessary
/// bindings for instantiating components that ascribe to the `world`. This
/// provides a higher-level representation of working with a component than the
/// raw [`Instance`] type which must be manually-type-checked and manually have
/// its imports provided via the [`Linker`] type.
///
/// # Examples
///
/// Examples for this macro can be found in the [`bindgen_examples`] module
/// documentation. That module has a submodule-per-example which includes the
/// source code, with WIT, used to generate the structures along with the
/// generated code itself in documentation.
///
/// # Debugging and Exploring
///
/// If you need to debug the output of `bindgen!` you can try using the
/// `WASMTIME_DEBUG_BINDGEN=1` environment variable. This will write the
/// generated code to a file on disk so rustc can produce better error messages
/// against the actual generated source instead of the macro invocation itself.
/// This additionally can enable opening up the generated code in an editor and
/// exploring it (through an error message).
///
/// The generated bindings can additionally be explored with `cargo doc` to see
/// what's generated. It's also recommended to browse the [`bindgen_examples`]
/// for example generated structures and example generated code.
///
/// # Syntax
///
/// This procedural macro accepts a few different syntaxes. The primary purpose
/// of this macro is to locate a WIT package, parse it, and then extract a
/// `world` from the parsed package. There are then codegen-specific options to
/// the bindings themselves which can additionally be specified.
///
/// Usage of this macro looks like:
///
/// ```rust
/// # macro_rules! bindgen { ($($t:tt)*) => () }
/// // Parse the `wit/` folder adjacent to this crate's `Cargo.toml` and look
/// // for a single `world` in it. There must be exactly one for this to
/// // succeed.
/// bindgen!();
///
/// // Parse the `wit/` folder adjacent to this crate's `Cargo.toml` and look
/// // for the world `foo` contained in it.
/// bindgen!("foo");
///
/// // Parse the folder `other/wit/folder` adjacent to `Cargo.toml`.
/// bindgen!(in "other/wit/folder");
/// bindgen!("foo" in "other/wit/folder");
///
/// // Parse the file `foo.wit` as a single-file WIT package with no
/// // dependencies.
/// bindgen!("foo" in "foo.wit");
///
/// // Specify a suite of options to the bindings generation, documented below
/// bindgen!({
/// world: "foo",
/// path: "other/path/to/wit",
/// // ...
/// });
/// ```
///
/// # Options Reference
///
/// This is an example listing of all options that this macro supports along
/// with documentation for each option and example syntax for each option.
///
/// ```rust
/// # macro_rules! bindgen { ($($t:tt)*) => () }
/// bindgen!({
/// world: "foo", // not needed if `path` has one `world`
///
/// // same as in `bindgen!(in "other/wit/folder")
/// path: "other/wit/folder",
///
/// // Instead of `path` the WIT document can be provided inline if
/// // desired.
/// inline: "
/// package my:inline;
///
/// world foo {
/// // ...
/// }
/// ",
///
/// // Further configuration of imported functions. This can be used to add
/// // functionality per-function or by default for all imports. Note that
/// // exports are also supported via the `exports` key below.
/// //
/// // Functions in this list are specified as their interface first then
/// // the raw wasm name of the function. Interface versions can be
/// // optionally omitted and prefixes are also supported to configure
/// // entire interfaces at once for example. Only the first matching item
/// // in this list is used to configure a function.
/// //
/// // Configuration for a function is a set of flags which can be added
/// // per-function. Each flag's meaning is documented below and the final
/// // set of flags for a function are calculated by the first matching
/// // rule below unioned with the default flags inferred from the WIT
/// // signature itself (unless below configures the `ignore_wit` flag).
/// //
/// // Specifically the defaults for a normal WIT function are empty,
/// // meaning all flags below are disabled. For a WIT `async` function the
/// // `async | store` flags are enabled by default, but all others are
/// // still disabled.
/// //
/// // Note that unused keys in this map are a compile-time error. All
/// // keys are required to be used and consulted.
/// imports: {
/// // The `async` flag is used to indicate that a Rust-level `async`
/// // function is used on the host. This means that the host is allowed
/// // to do async I/O. Note though that to WebAssembly itself the
/// // function will still be blocking.
/// "wasi:io/poll.poll": async,
///
/// // The `store` flag means that the host function will have access
/// // to the store during its execution. By default host functions take
/// // `&mut self` which only has access to the data in question
/// // implementing the generated traits from `bindgen!`. This
/// // configuration means that in addition to `Self` the entire store
/// // will be accessible if necessary.
/// //
/// // Functions that have access to a `store` are generated in a
/// // `HostWithStore` trait. Functions without a `store` are generated
/// // in a `Host` trait.
/// //
/// // If the WIT function itself is `async`, then the function will
/// // already have this flag and will take an `&Accessor<T, Self>`. If
/// // the WIT function is not `async`, however, then the function will
/// // take `Access<T, Self>`.
/// "wasi:clocks/monotonic-clock.now": store,
///
/// // This is an example of combining flags where the `async` and
/// // `store` flags are combined. This means that the generated
/// // host function is both `async` and additionally has access to
/// // the `store`. Note though that this configuration is not necessary
/// // as the WIT function is itself already marked as `async`. That
/// // means that this is the default already applied meaning that
/// // specifying it here would be redundant.
/// //
/// // "wasi:clocks/monotonic-clock.wait-until": async | store,
///
/// // The `tracing` flag indicates that `tracing!` will be used to log
/// // entries and exits into this host API. This can assist with
/// // debugging or just generally be used to provide logs for the host.
/// //
/// // By default values are traced unless they contain lists, but
/// // tracing of lists can be enabled with `verbose_tracing` below.
/// "my:local/api.foo": tracing,
///
/// // The `verbose_tracing` flag indicates that when combined with
/// // `tracing` the values of parameters/results are added to logs.
/// // This may include lists which may be very large.
/// "my:local/api.other-function": tracing | verbose_tracing,
///
/// // The `trappable` flag indicates that this import is allowed to
/// // generate a trap.
/// //
/// // Imports that may trap have their return types wrapped in
/// // `wasmtime::Result<T>` where the `Err` variant indicates that a
/// // trap will be raised in the guest.
/// //
/// // By default imports cannot trap and the return value is the return
/// // value from the WIT bindings itself.
/// //
/// // Note that the `trappable` configuration can be combined with the
/// // `trappable_error_type` configuration below to avoid having a
/// // host function return `wasmtime::Result<Result<WitOk, WitErr>>`
/// // for example and instead return `Result<WitOk, RustErrorType>`.
/// "my:local/api.fallible": trappable,
///
/// // The `exact` flag ensures that the filter, here "f", only matches
/// // functions exactly. For example "f" here would only refer to
/// // `import f: func()` in a world. Without this flag then "f"
/// // would also configure any package `f:*/*/*` for example.
/// "f": exact,
///
/// // This is used to configure the defaults of all functions if no
/// // other key above matches a function. Note that if specific
/// // functions mentioned above want these flags too then the flags
/// // must be added there too because only one matching rule in this
/// // map is used per-function.
/// default: async | trappable,
/// },
///
/// // Mostly the same as `imports` above, but applies to exported functions.
/// //
/// // The one difference here is that, whereas the `task_exit` flag has no
/// // effect for `imports`, it changes how bindings are generated for
/// // exported functions as described below.
/// exports: {
/// /* ... */
///
/// // The `task_exit` flag indicates that the generated binding for
/// // this function should return a tuple of the result produced by the
/// // callee and a `TaskExit` future which will resolve when the task
/// // (and any transitively created subtasks) have exited.
/// "my:local/api.does-stuff-after-returning": task_exit,
/// },
///
/// // This can be used to translate WIT return values of the form
/// // `result<T, error-type>` into `Result<T, RustErrorType>` in Rust.
/// // Users must define `RustErrorType` and the `Host` trait for the
/// // interface which defines `error-type` will have a method
/// // called `convert_error_type` which converts `RustErrorType`
/// // into `wasmtime::Result<ErrorType>`. This conversion can either
/// // return the raw WIT error (`ErrorType` here) or a trap.
/// //
/// // By default this option is not specified. This option only takes
/// // effect when `trappable` is set for some imports.
/// trappable_error_type: {
/// "wasi:io/streams.stream-error" => RustErrorType,
/// },
///
/// // All generated bindgen types are "owned" meaning types like `String`
/// // are used instead of `&str`, for example. This is the default and
/// // ensures that the same type used in both imports and exports uses the
/// // same generated type.
/// ownership: Owning,
///
/// // Alternative to `Owning` above where borrowed types attempt to be used
/// // instead. The `duplicate_if_necessary` configures whether duplicate
/// // Rust types will be generated for the same WIT type if necessary, for
/// // example when a type is used both as an import and an export.
/// ownership: Borrowing {
/// duplicate_if_necessary: true
/// },
///
/// // Restrict the code generated to what's needed for the interface
/// // imports in the inlined WIT document fragment.
/// interfaces: "
/// import wasi:cli/command;
/// ",
///
/// // Remap imported interfaces or resources to types defined in Rust
/// // elsewhere. Using this option will prevent any code from being
/// // generated for interfaces mentioned here. Resources named here will
/// // not have a type generated to represent the resource.
/// //
/// // Interfaces mapped with this option should be previously generated
/// // with an invocation of this macro. Resources need to be mapped to a
/// // Rust type name.
/// with: {
/// // This can be used to indicate that entire interfaces have
/// // bindings generated elsewhere with a path pointing to the
/// // bindinges-generated module.
/// "wasi:random/random": wasmtime_wasi::p2::bindings::random::random,
///
/// // Similarly entire packages can also be specified.
/// "wasi:cli": wasmtime_wasi::p2::bindings::cli,
///
/// // Or, if applicable, entire namespaces can additionally be mapped.
/// "wasi": wasmtime_wasi::p2::bindings,
///
/// // Versions are supported if multiple versions are in play:
/// "wasi:http/[email protected]": wasmtime_wasi_http::bindings::http::types,
/// "wasi:[email protected]": wasmtime_wasi_http::bindings::http,
///
/// // The `with` key can also be used to specify the `T` used in
/// // import bindings of `Resource<T>`. This can be done to configure
/// // which typed resource shows up in generated bindings and can be
/// // useful when working with the typed methods of `ResourceTable`.
/// "wasi:filesystem/types.descriptor": MyDescriptorType,
/// },
///
/// // Generate an additional set of "named imports" bindings for the listed
/// // interfaces, used together with the component model's
/// // `(implements "...")` annotation.
/// //
/// // For each interface listed here an extra `Host` trait is generated
/// // under a top-level `named_imports` module (mirroring the interface's
/// // normal module path) whose methods each take an additional first
/// // argument: a reference to the host-chosen "id" type given as the value
/// // (here `MyHandlerId`). Alongside the trait a reflection-based
/// // `add_to_linker` is generated:
/// //
/// // ```ignore
/// // fn add_to_linker<T, D>(
/// // linker: &mut Linker<T>,
/// // component: &Component,
/// // lookup: impl FnMut(&str) -> Result<MyHandlerId>,
/// // host_getter: fn(&mut T) -> D::Data<'_>,
/// // ) -> Result<()>;
/// // ```
/// //
/// // This inspects `component`'s imports, and for each one annotated with
/// // `(implements "wasi:http/handler")` calls `lookup` with the import's
/// // name to obtain an id. That id is then cloned into each linker closure
/// // and passed as the first argument to every method call, letting a
/// // single `Host` implementation distinguish between multiple imports
/// // of the same interface.
/// //
/// // The id type must be `Clone + Send + Sync + 'static`. Interfaces that
/// // define a resource are not supported here and cause a compile error.
/// named_imports: {
/// "wasi:http/handler": MyHandlerId,
/// },
///
/// // Additional derive attributes to include on generated types (structs or enums).
/// //
/// // These are deduplicated and attached in a deterministic order.
/// additional_derives: [
/// Hash,
/// serde::Deserialize,
/// serde::Serialize,
/// ],
///
/// // An niche configuration option to require that the `T` in `Store<T>`
/// // is always `Send` in the generated bindings. Typically not needed
/// // but if synchronous bindings depend on asynchronous bindings using
/// // the `with` key then this may be required.
/// require_store_data_send: false,
///
/// // If the `wasmtime` crate is depended on at a nonstandard location
/// // or is renamed then this is the path to the root of the `wasmtime`
/// // crate. Much of the generated code needs to refer to `wasmtime` so
/// // this should be used if the `wasmtime` name is not wasmtime itself.
/// //
/// // By default this is `wasmtime`.
/// wasmtime_crate: path::to::wasmtime,
///
/// // Whether to use `anyhow::Result` for trappable host-defined function
/// // imports, rather than `wasmtime::Result`.
/// //
/// // By default, this is false and `wasmtime::Result` is used instead of
/// // `anyhow::Result`.
/// //
/// // When enabled, the generated code requires the `"anyhow"` cargo feature
/// // to also be enabled in the `wasmtime` crate.
/// anyhow: false,
///
/// // This is an in-source alternative to using `WASMTIME_DEBUG_BINDGEN`.
/// //
/// // Note that if this option is specified then the compiler will always
/// // recompile your bindings. Cargo records the start time of when rustc
/// // is spawned by this will write a file during compilation. To Cargo
/// // that looks like a file was modified after `rustc` was spawned,
/// // so Cargo will always think your project is "dirty" and thus always
/// // recompile it. Recompiling will then overwrite the file again,
/// // starting the cycle anew. This is only recommended for debugging.
/// //
/// // This option defaults to false.
/// include_generated_code_from_file: false,
///
/// // Whether to generate a `COMPONENT_TYPE` constant containing a
/// // binary-encoded description of the WIT world that bindings
/// // were generated for.
/// //
/// // This can be decoded with the `wit_parser::decoding::decode_world`
/// // function to recover the WIT `Resolve` and `WorldId` used to
/// // generate the bindings, for example to type-check components
/// // against the world without keeping the original WIT files around.
/// //
/// // This option defaults to false as the encoded world adds a
/// // nontrivial amount of data to the generated code.
/// include_component_type: false,
/// });
/// ```
pub use wasmtime_component_macro::bindgen;
/// Derive macro to generate implementations of the [`ComponentType`] trait.
///
/// This derive macro can be applied to `struct` and `enum` definitions and is
/// used to bind either a `record`, `enum`, or `variant` in the component model.
///
/// Note you might be looking for [`bindgen!`] rather than this macro as that
/// will generate the entire type for you rather than just a trait
/// implementation.
///
/// This macro supports a `#[component]` attribute which is used to customize
/// how the type is bound to the component model. A top-level `#[component]`
/// attribute is required to specify either `record`, `enum`, or `variant`.
///
/// ## Records
///
/// `record`s in the component model correspond to `struct`s in Rust. An example
/// is:
///
/// ```rust
/// use wasmtime::component::ComponentType;
///
/// #[derive(ComponentType)]
/// #[component(record)]
/// struct Color {
/// r: u8,
/// g: u8,
/// b: u8,
/// }
/// ```
///
/// which corresponds to the WIT type:
///
/// ```wit
/// record color {
/// r: u8,
/// g: u8,
/// b: u8,
/// }
/// ```
///
/// Note that the name `Color` here does not need to match the name in WIT.
/// That's purely used as a name in Rust of what to refer to. The field names
/// must match that in WIT, however. Field names can be customized with the
/// `#[component]` attribute though.
///
/// ```rust
/// use wasmtime::component::ComponentType;
///
/// #[derive(ComponentType)]
/// #[component(record)]
/// struct VerboseColor {
/// #[component(name = "r")]
/// red: u8,
/// #[component(name = "g")]
/// green: u8,
/// #[component(name = "b")]
/// blue: u8,
/// }
/// ```
///
/// Also note that field ordering is significant at this time and must match
/// WIT.
///
/// ## Variants
///
/// `variant`s in the component model correspond to a subset of shapes of a Rust
/// `enum`. Variants in the component model have a single optional payload type
/// which means that not all Rust `enum`s correspond to component model
/// `variant`s. An example variant is:
///
/// ```rust
/// use wasmtime::component::ComponentType;
///
/// #[derive(ComponentType)]
/// #[component(variant)]
/// enum Filter {
/// #[component(name = "none")]
/// None,
/// #[component(name = "all")]
/// All,
/// #[component(name = "some")]
/// Some(Vec<String>),
/// }
/// ```
///
/// which corresponds to the WIT type:
///
/// ```wit
/// variant filter {
/// none,
/// all,
/// some(list<string>),
/// }
/// ```
///
/// The `variant` style of derive allows an optional payload on Rust `enum`
/// variants but it must be a single unnamed field. Variants of the form `Foo(T,
/// U)` or `Foo { name: T }` are not supported at this time.
///
/// Note that the order of variants in Rust must match the order of variants in
/// WIT. Additionally it's likely that `#[component(name = "...")]` is required
/// on all Rust `enum` variants because the name currently defaults to the Rust
/// name which is typically UpperCamelCase whereas WIT uses kebab-case.
///
/// ## Enums
///
/// `enum`s in the component model correspond to C-like `enum`s in Rust. Note
/// that a component model `enum` does not allow any payloads so the Rust `enum`
/// must additionally have no payloads.
///
/// ```rust
/// use wasmtime::component::ComponentType;
///
/// #[derive(ComponentType)]
/// #[component(enum)]
/// #[repr(u8)]
/// enum Setting {
/// #[component(name = "yes")]
/// Yes,
/// #[component(name = "no")]
/// No,
/// #[component(name = "auto")]
/// Auto,
/// }
/// ```
///
/// which corresponds to the WIT type:
///
/// ```wit
/// enum setting {
/// yes,
/// no,
/// auto,
/// }
/// ```
///
/// Note that the order of variants in Rust must match the order of variants in
/// WIT. Additionally it's likely that `#[component(name = "...")]` is required
/// on all Rust `enum` variants because the name currently defaults to the Rust
/// name which is typically UpperCamelCase whereas WIT uses kebab-case.
pub use wasmtime_component_macro::ComponentType;
/// A derive macro for generating implementations of the [`Lift`] trait.
///
/// This macro will likely be applied in conjunction with the
/// [`#[derive(ComponentType)]`](macro@ComponentType) macro along the lines
/// of `#[derive(ComponentType, Lift)]`. This trait enables reading values from
/// WebAssembly.
///
/// Note you might be looking for [`bindgen!`] rather than this macro as that
/// will generate the entire type for you rather than just a trait
/// implementation.
///
/// At this time this derive macro has no configuration.
///
/// ## Examples
///
/// ```rust
/// use wasmtime::component::{ComponentType, Lift};
///
/// #[derive(ComponentType, Lift)]
/// #[component(record)]
/// struct Color {
/// r: u8,
/// g: u8,
/// b: u8,
/// }
/// ```
pub use wasmtime_component_macro::Lift;
/// A derive macro for generating implementations of the [`Lower`] trait.
///
/// This macro will likely be applied in conjunction with the
/// [`#[derive(ComponentType)]`](macro@ComponentType) macro along the lines
/// of `#[derive(ComponentType, Lower)]`. This trait enables passing values to
/// WebAssembly.
///
/// Note you might be looking for [`bindgen!`] rather than this macro as that
/// will generate the entire type for you rather than just a trait
/// implementation.
///
/// At this time this derive macro has no configuration.
///
/// ## Examples
///
/// ```rust
/// use wasmtime::component::{ComponentType, Lower};
///
/// #[derive(ComponentType, Lower)]
/// #[component(record)]
/// struct Color {
/// r: u8,
/// g: u8,
/// b: u8,
/// }
/// ```
pub use wasmtime_component_macro::Lower;
/// A macro to generate a Rust type corresponding to WIT `flags`
///
/// This macro generates a type that implements the [`ComponentType`], [`Lift`],
/// and [`Lower`] traits. The generated Rust type corresponds to the `flags`
/// type in WIT.
///
/// Example usage of this looks like:
///
/// ```rust
/// use wasmtime::component::flags;
///
/// flags! {
/// Permissions {
/// #[component(name = "read")]
/// const READ;
/// #[component(name = "write")]
/// const WRITE;
/// #[component(name = "execute")]
/// const EXECUTE;
/// }
/// }
///
/// fn validate_permissions(permissions: &mut Permissions) {
/// if permissions.contains(Permissions::EXECUTE | Permissions::WRITE) {
/// panic!("cannot enable both writable and executable at the same time");
/// }
///
/// if permissions.contains(Permissions::READ) {
/// panic!("permissions must at least contain read");
/// }
/// }
/// ```
///
/// which corresponds to the WIT type:
///
/// ```wit
/// flags permissions {
/// read,
/// write,
/// execute,
/// }
/// ```
///
/// This generates a structure which is similar to/inspired by the [`bitflags`
/// crate](https://crates.io/crates/bitflags). The `Permissions` structure
/// generated implements the [`PartialEq`], [`Eq`], [`Debug`], [`BitOr`],
/// [`BitOrAssign`], [`BitAnd`], [`BitAndAssign`], [`BitXor`], [`BitXorAssign`],
/// and [`Not`] traits - in addition to the Wasmtime-specific component ones
/// [`ComponentType`], [`Lift`], and [`Lower`].
///
/// [`BitOr`]: std::ops::BitOr
/// [`BitOrAssign`]: std::ops::BitOrAssign
/// [`BitAnd`]: std::ops::BitAnd
/// [`BitAndAssign`]: std::ops::BitAndAssign
/// [`BitXor`]: std::ops::BitXor
/// [`BitXorAssign`]: std::ops::BitXorAssign
/// [`Not`]: std::ops::Not
pub use wasmtime_component_macro::flags;
#[cfg(any(docsrs, test, doctest))]
pub mod bindgen_examples;
// NB: needed for the links in the docs above to work in all `cargo doc`
// configurations and avoid errors.
#[cfg(not(any(docsrs, test, doctest)))]
#[doc(hidden)]
pub mod bindgen_examples {}
#[cfg(not(feature = "component-model-async"))]
pub(crate) mod concurrent_disabled;
#[cfg(not(feature = "component-model-async"))]
pub(crate) use concurrent_disabled as concurrent;
@@ -0,0 +1,502 @@
use super::Resource;
use crate::prelude::*;
use alloc::collections::{BTreeMap, BTreeSet};
use core::any::Any;
use core::fmt;
use core::mem;
#[derive(Debug)]
/// Errors returned by operations on `ResourceTable`
pub enum ResourceTableError {
/// ResourceTable has no free keys
Full,
/// Resource not present in table
NotPresent,
/// Resource present in table, but with a different type
WrongType,
/// Resource cannot be deleted because child resources exist in the table. Consult wit docs for
/// the particular resource to see which methods may return child resources.
HasChildren,
}
impl fmt::Display for ResourceTableError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Full => write!(f, "resource table has no free keys"),
Self::NotPresent => write!(f, "resource not present"),
Self::WrongType => write!(f, "resource is of another type"),
Self::HasChildren => write!(f, "resource has children"),
}
}
}
impl core::error::Error for ResourceTableError {}
/// The `ResourceTable` type maps a `Resource<T>` to its `T`.
pub struct ResourceTable {
entries: Vec<Entry>,
free_head: Option<usize>,
max_capacity: usize,
}
#[derive(Debug)]
enum Entry {
Free { next: Option<usize> },
Occupied { entry: TableEntry },
}
impl Entry {
pub fn occupied(&self) -> Option<&TableEntry> {
match self {
Self::Occupied { entry } => Some(entry),
Self::Free { .. } => None,
}
}
pub fn occupied_mut(&mut self) -> Option<&mut TableEntry> {
match self {
Self::Occupied { entry } => Some(entry),
Self::Free { .. } => None,
}
}
}
struct Tombstone;
// Change this to `true` to assist with handle debugging in development if
// necessary.
const DELETE_WITH_TOMBSTONE: bool = false;
/// Default setting for `ResourceTable::max_capacity`, chosen to be high
/// enough that it doesn't need changing all that often but low enough that
/// exhausting it isn't a massive problem for the host.
const DEFAULT_MAX_CAPACITY: usize = 1_000_000;
/// This structure tracks parent and child relationships for a given table entry.
///
/// Parents and children are referred to by table index. We maintain the
/// following invariants:
/// * the parent must exist when adding a child.
/// * whenever a child is created, its index is added to children.
/// * whenever a child is deleted, its index is removed from children.
/// * an entry with children may not be deleted.
#[derive(Debug)]
struct TableEntry {
/// The entry in the table, as a boxed dynamically-typed object
entry: Box<dyn Any + Send>,
/// The index of the parent of this entry, if it has one.
parent: Option<u32>,
/// The indices of any children of this entry.
children: BTreeSet<u32>,
}
impl TableEntry {
fn new(entry: Box<dyn Any + Send>, parent: Option<u32>) -> Self {
Self {
entry,
parent,
children: BTreeSet::new(),
}
}
fn add_child(&mut self, child: u32) {
debug_assert!(!self.children.contains(&child));
self.children.insert(child);
}
fn remove_child(&mut self, child: u32) {
let was_removed = self.children.remove(&child);
debug_assert!(was_removed);
}
}
impl ResourceTable {
/// Create an empty table
pub fn new() -> Self {
ResourceTable::with_capacity(0)
}
/// Returns whether or not this table is empty.
///
/// Note that this is an `O(n)` operation, where `n` is the number of
/// entries in the backing `Vec`.
pub fn is_empty(&self) -> bool {
self.entries.iter().all(|entry| match entry {
Entry::Free { .. } => true,
Entry::Occupied { entry } => entry.entry.downcast_ref::<Tombstone>().is_some(),
})
}
/// Returns the maximum capacity of this table, in elements, before adding
/// any more will be refused.
pub fn max_capacity(&self) -> usize {
self.max_capacity
}
/// Configures the maximum number of entries that may be present within this
/// table.
///
/// Note that this does not retroactively shrink the table nor evict
/// existing entries should the maximum be smaller than the current size of
/// the entry table.
pub fn set_max_capacity(&mut self, max: usize) {
self.max_capacity = max;
}
/// Create an empty table with at least the specified capacity.
pub fn with_capacity(capacity: usize) -> Self {
ResourceTable {
entries: Vec::with_capacity(capacity),
free_head: None,
max_capacity: DEFAULT_MAX_CAPACITY,
}
}
/// Inserts a new value `T` into this table, returning a corresponding
/// `Resource<T>` which can be used to refer to it after it was inserted.
pub fn push<T>(&mut self, entry: T) -> Result<Resource<T>, ResourceTableError>
where
T: Send + 'static,
{
let idx = self.push_(TableEntry::new(Box::new(entry), None))?;
Ok(Resource::new_own(idx))
}
/// Pop an index off of the free list, if it's not empty.
fn pop_free_list(&mut self) -> Option<usize> {
if let Some(ix) = self.free_head {
// Advance free_head to the next entry if one is available.
match &self.entries[ix] {
Entry::Free { next } => self.free_head = *next,
Entry::Occupied { .. } => unreachable!(),
}
Some(ix)
} else {
None
}
}
/// Free an entry in the table, returning its [`TableEntry`]. Add the index to the free list.
fn free_entry(&mut self, ix: usize, debug: bool) -> TableEntry {
if debug {
// Instead of making this entry available for reuse, we leave a
// tombstone in debug mode. This helps detect use-after-delete and
// double-delete bugs.
match mem::replace(
&mut self.entries[ix],
Entry::Occupied {
entry: TableEntry {
entry: Box::new(Tombstone),
parent: None,
children: BTreeSet::new(),
},
},
) {
Entry::Occupied { entry } => entry,
Entry::Free { .. } => unreachable!(),
}
} else {
let entry = match core::mem::replace(
&mut self.entries[ix],
Entry::Free {
next: self.free_head,
},
) {
Entry::Occupied { entry } => entry,
Entry::Free { .. } => unreachable!(),
};
self.free_head = Some(ix);
entry
}
}
/// Push a new entry into the table, returning its handle. This will prefer to use free entries
/// if they exist, falling back on pushing new entries onto the end of the table.
fn push_(&mut self, e: TableEntry) -> Result<u32, ResourceTableError> {
if let Some(free) = self.pop_free_list() {
self.entries[free] = Entry::Occupied { entry: e };
Ok(free.try_into().unwrap())
} else {
if self.entries.len() >= self.max_capacity {
return Err(ResourceTableError::Full);
}
let ix = self
.entries
.len()
.try_into()
.map_err(|_| ResourceTableError::Full)?;
self.entries.push(Entry::Occupied { entry: e });
Ok(ix)
}
}
fn occupied(&self, key: u32) -> Result<&TableEntry, ResourceTableError> {
self.entries
.get(key as usize)
.and_then(Entry::occupied)
.ok_or(ResourceTableError::NotPresent)
}
fn occupied_mut(&mut self, key: u32) -> Result<&mut TableEntry, ResourceTableError> {
self.entries
.get_mut(key as usize)
.and_then(Entry::occupied_mut)
.ok_or(ResourceTableError::NotPresent)
}
/// Insert a resource at the next available index, and track that it has a
/// parent resource.
///
/// The parent must exist to create a child. All children resources must
/// be destroyed before a parent can be destroyed - otherwise
/// [`ResourceTable::delete`] will fail with
/// [`ResourceTableError::HasChildren`].
///
/// Parent-child relationships are tracked inside the table to ensure that
/// a parent resource is not deleted while it has live children. This
/// allows child resources to hold "references" to a parent by table
/// index, to avoid needing e.g. an `Arc<Mutex<parent>>` and the associated
/// locking overhead and design issues, such as child existence extending
/// lifetime of parent referent even after parent resource is destroyed,
/// possibility for deadlocks.
pub fn push_child<T, U>(
&mut self,
entry: T,
parent: &Resource<U>,
) -> Result<Resource<T>, ResourceTableError>
where
T: Send + 'static,
U: 'static,
{
let parent = parent.rep();
self.occupied(parent)?;
let child = self.push_(TableEntry::new(Box::new(entry), Some(parent)))?;
self.occupied_mut(parent)?.add_child(child);
Ok(Resource::new_own(child))
}
/// Add an already-resident child to a resource.
pub fn add_child<T: 'static, U: 'static>(
&mut self,
child: Resource<T>,
parent: Resource<U>,
) -> Result<(), ResourceTableError> {
let entry = self.occupied_mut(child.rep())?;
assert!(entry.parent.is_none());
entry.parent = Some(parent.rep());
self.occupied_mut(parent.rep())?.add_child(child.rep());
Ok(())
}
/// Remove a child to from a resource (but leave it in the table).
pub fn remove_child<T: 'static, U: 'static>(
&mut self,
child: Resource<T>,
parent: Resource<U>,
) -> Result<(), ResourceTableError> {
let entry = self.occupied_mut(child.rep())?;
assert_eq!(entry.parent, Some(parent.rep()));
entry.parent = None;
self.occupied_mut(parent.rep())?.remove_child(child.rep());
Ok(())
}
/// Get an immutable reference to a resource of a given type at a given
/// index.
///
/// Multiple shared references can be borrowed at any given time.
pub fn get<T: Any + Sized>(&self, key: &Resource<T>) -> Result<&T, ResourceTableError> {
self.get_(key.rep())?
.downcast_ref()
.ok_or(ResourceTableError::WrongType)
}
fn get_(&self, key: u32) -> Result<&dyn Any, ResourceTableError> {
let r = self.occupied(key)?;
Ok(&*r.entry)
}
/// Get an mutable reference to a resource of a given type at a given
/// index.
pub fn get_mut<T: Any + Sized>(
&mut self,
key: &Resource<T>,
) -> Result<&mut T, ResourceTableError> {
self.get_any_mut(key.rep())?
.downcast_mut()
.ok_or(ResourceTableError::WrongType)
}
/// Returns the raw `Any` at the `key` index provided.
pub fn get_any_mut(&mut self, key: u32) -> Result<&mut dyn Any, ResourceTableError> {
let r = self.occupied_mut(key)?;
Ok(&mut *r.entry)
}
/// Remove the specified entry from the table.
pub fn delete<T>(&mut self, resource: Resource<T>) -> Result<T, ResourceTableError>
where
T: Any,
{
self.delete_maybe_debug(resource, DELETE_WITH_TOMBSTONE)
}
fn delete_maybe_debug<T>(
&mut self,
resource: Resource<T>,
debug: bool,
) -> Result<T, ResourceTableError>
where
T: Any,
{
debug_assert!(resource.owned());
let entry = self.delete_entry(resource.rep(), debug)?;
match entry.entry.downcast() {
Ok(t) => Ok(*t),
Err(_e) => Err(ResourceTableError::WrongType),
}
}
fn delete_entry(&mut self, key: u32, debug: bool) -> Result<TableEntry, ResourceTableError> {
if !self.occupied(key)?.children.is_empty() {
return Err(ResourceTableError::HasChildren);
}
let e = self.free_entry(key as usize, debug);
if let Some(parent) = e.parent {
// Remove deleted resource from parent's child list.
// Parent must still be present because it can't be deleted while still having
// children:
self.occupied_mut(parent)
.expect("missing parent")
.remove_child(key);
}
Ok(e)
}
/// Zip the values of the map with mutable references to table entries corresponding to each
/// key. As the keys in the `BTreeMap` are unique, this iterator can give mutable references
/// with the same lifetime as the mutable reference to the [ResourceTable].
pub fn iter_entries<'a, T>(
&'a mut self,
map: BTreeMap<u32, T>,
) -> impl Iterator<Item = (Result<&'a mut dyn Any, ResourceTableError>, T)> {
map.into_iter().map(move |(k, v)| {
let item = self
.occupied_mut(k)
.map(|e| Box::as_mut(&mut e.entry))
// Safety: extending the lifetime of the mutable reference.
.map(|item| unsafe { &mut *(item as *mut dyn Any) });
(item, v)
})
}
/// Iterate over all children belonging to the provided parent
pub fn iter_children<T>(
&self,
parent: &Resource<T>,
) -> Result<impl Iterator<Item = &(dyn Any + Send)> + use<'_, T>, ResourceTableError>
where
T: 'static,
{
let parent_entry = self.occupied(parent.rep())?;
Ok(parent_entry.children.iter().map(|child_index| {
let child = self.occupied(*child_index).expect("missing child");
child.entry.as_ref()
}))
}
/// Iterate over all the entries in this table.
pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut (dyn Any + Send)> {
self.entries.iter_mut().filter_map(|entry| match entry {
Entry::Occupied { entry } => Some(&mut *entry.entry),
Entry::Free { .. } => None,
})
}
}
impl Default for ResourceTable {
fn default() -> Self {
ResourceTable::new()
}
}
impl fmt::Debug for ResourceTable {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "[")?;
let mut wrote = false;
for (index, entry) in self.entries.iter().enumerate() {
if let Entry::Occupied { entry } = entry {
if entry.entry.downcast_ref::<Tombstone>().is_none() {
if wrote {
write!(f, ", ")?;
} else {
wrote = true;
}
write!(f, "{index}")?;
}
}
}
write!(f, "]")
}
}
#[test]
pub fn test_free_list() {
let mut table = ResourceTable::new();
let x = table.push(()).unwrap();
assert_eq!(x.rep(), 0);
let y = table.push(()).unwrap();
assert_eq!(y.rep(), 1);
// Deleting x should put it on the free list, so the next entry should have the same rep.
table.delete_maybe_debug(x, false).unwrap();
let x = table.push(()).unwrap();
assert_eq!(x.rep(), 0);
// Deleting x and then y should yield indices 1 and then 0 for new entries.
table.delete_maybe_debug(x, false).unwrap();
table.delete_maybe_debug(y, false).unwrap();
let y = table.push(()).unwrap();
assert_eq!(y.rep(), 1);
let x = table.push(()).unwrap();
assert_eq!(x.rep(), 0);
// As the free list is empty, this entry will have a new id.
let x = table.push(()).unwrap();
assert_eq!(x.rep(), 2);
}
#[test]
fn test_max_capacity() {
let mut table = ResourceTable::new();
assert_eq!(table.max_capacity(), DEFAULT_MAX_CAPACITY);
table.set_max_capacity(0);
assert_eq!(table.max_capacity(), 0);
assert!(table.push(()).is_err());
table.set_max_capacity(1);
assert_eq!(table.max_capacity(), 1);
let x = table.push(()).unwrap();
assert!(table.push(()).is_err());
table.set_max_capacity(0);
assert!(table.push(()).is_err());
table.delete(x).unwrap();
let x = table.push(()).unwrap();
table.delete(x).unwrap();
table.set_max_capacity(10);
let handles = (0..10).map(|_| table.push(()).unwrap()).collect::<Vec<_>>();
assert!(table.push(()).is_err());
for handle in handles {
table.delete(handle).unwrap();
}
table.push(()).unwrap();
}
@@ -0,0 +1,12 @@
mod any;
mod host;
mod host_dynamic;
mod host_static;
mod host_tables;
mod ty;
pub use any::*;
pub use host_dynamic::*;
pub use host_static::*;
pub use host_tables::*;
pub use ty::*;
@@ -0,0 +1,341 @@
//! This module defines the `ResourceAny` type in the public API of Wasmtime,
//! which represents a dynamically typed resource handle that could either be
//! owned by the guest or the host.
//!
//! This is in contrast with `Resource<T>`, for example, and `ResourceAny` has
//! more "state" behind it. Specifically a `ResourceAny` has a type and a
//! `HostResourceIndex` which points inside of a `HostResourceData` structure
//! inside of a store. The `ResourceAny::resource_drop` method, or a conversion
//! to `Resource<T>`, is required to be called to avoid leaking data within a
//! store.
use crate::component::func::{LiftContext, LowerContext, bad_type_info, desc};
use crate::component::matching::InstanceType;
use crate::component::resources::host::{HostResource, HostResourceType};
use crate::component::resources::{HostResourceIndex, HostResourceTables};
use crate::component::{ComponentType, Lift, Lower, Resource, ResourceDynamic, ResourceType};
use crate::prelude::*;
use crate::runtime::vm::ValRaw;
use crate::{AsContextMut, StoreContextMut, Trap};
use core::mem::MaybeUninit;
use core::ptr::NonNull;
use wasmtime_environ::component::{CanonicalAbiInfo, InterfaceType};
/// Representation of a resource in the component model, either a guest-defined
/// or a host-defined resource.
///
/// This type is similar to [`Resource`] except that it can be used to represent
/// any resource, either host or guest. This type cannot be directly constructed
/// and is only available if the guest returns it to the host (e.g. a function
/// returning a guest-defined resource) or by a conversion from [`Resource`] via
/// [`ResourceAny::try_from_resource`].
/// This type also does not carry a static type parameter `T` for example and
/// does not have as much information about its type.
/// This means that it's possible to get runtime type-errors when
/// using this type because it cannot statically prevent mismatching resource
/// types.
///
/// Like [`Resource`] this type represents either an `own` or a `borrow`
/// resource internally. Unlike [`Resource`], however, a [`ResourceAny`] must
/// always be explicitly destroyed with the [`ResourceAny::resource_drop`]
/// method. This will update internal dynamic state tracking and invoke the
/// WebAssembly-defined destructor for a resource, if any.
///
/// Note that it is required to call `resource_drop` for all instances of
/// [`ResourceAny`]: even borrows. Both borrows and own handles have state
/// associated with them that must be discarded by the time they're done being
/// used.
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
pub struct ResourceAny {
idx: HostResourceIndex,
ty: ResourceType,
owned: bool,
}
impl ResourceAny {
pub(crate) fn new(idx: HostResourceIndex, ty: ResourceType, owned: bool) -> ResourceAny {
ResourceAny { idx, ty, owned }
}
/// Attempts to convert an imported [`Resource`] into [`ResourceAny`].
///
/// * `resource` is the resource to convert.
/// * `store` is the store to place the returned resource into.
///
/// The returned `ResourceAny` will not have a destructor attached to it
/// meaning that if `resource_drop` is called then it will not invoked a
/// host-defined destructor. This is similar to how `Resource<T>` does not
/// have a destructor associated with it.
///
/// # Errors
///
/// This method will return an error if `resource` has already been "taken"
/// and has ownership transferred elsewhere which can happen in situations
/// such as when it's already lowered into a component.
///
/// This function will return an [`OutOfMemory`][crate::OutOfMemory] error when
/// memory allocation fails. See the `OutOfMemory` type's documentation for
/// details on Wasmtime's out-of-memory handling.
pub fn try_from_resource<T: 'static>(
resource: Resource<T>,
store: impl AsContextMut,
) -> Result<Self> {
resource.try_into_resource_any(store)
}
/// See [`Resource::try_from_resource_any`]
///
/// # Errors
///
/// This function will return an [`OutOfMemory`][crate::OutOfMemory] error when
/// memory allocation fails. See the `OutOfMemory` type's documentation for
/// details on Wasmtime's out-of-memory handling.
pub fn try_into_resource<T: 'static>(self, store: impl AsContextMut) -> Result<Resource<T>> {
Resource::try_from_resource_any(self, store)
}
/// See [`ResourceDynamic::try_from_resource_any`]
pub fn try_into_resource_dynamic(self, store: impl AsContextMut) -> Result<ResourceDynamic> {
ResourceDynamic::try_from_resource_any(self, store)
}
/// See [`Resource::try_from_resource_any`]
pub(crate) fn try_into_host_resource<T, D>(
self,
mut store: impl AsContextMut,
) -> Result<HostResource<T, D>>
where
T: HostResourceType<D>,
D: PartialEq + Send + Sync + Copy + 'static,
{
let store = store.as_context_mut();
let mut tables = HostResourceTables::new_host(store.0)?;
let ResourceAny { idx, ty, owned } = self;
let ty = T::typecheck(ty).ok_or_else(|| crate::format_err!("resource type mismatch"))?;
if owned {
let rep = tables.host_resource_lift_own(idx)?;
Ok(HostResource::new_own(rep, ty))
} else {
// For borrowed handles, first acquire the `rep` via lifting the
// borrow. Afterwards though remove any dynamic state associated
// with this borrow. `Resource<T>` doesn't participate in dynamic
// state tracking and it's assumed embedders know what they're
// doing, so the drop call will clear out that a borrow is active
//
// Note that the result of `drop` should always be `None` as it's a
// borrowed handle, so assert so.
let rep = tables.host_resource_lift_borrow(idx)?;
let res = tables.host_resource_drop(idx)?;
assert!(res.is_none());
Ok(HostResource::new_borrow(rep, ty))
}
}
/// Returns the corresponding type associated with this resource, either a
/// host-defined type or a guest-defined type.
///
/// This can be compared against [`ResourceType::host`] for example to see
/// if it's a host-resource or against a type extracted with
/// [`Instance::get_resource`] to see if it's a guest-defined resource.
///
/// [`Instance::get_resource`]: crate::component::Instance::get_resource
pub fn ty(&self) -> ResourceType {
self.ty
}
/// Returns whether this is an owned resource, and if not it's a borrowed
/// resource.
pub fn owned(&self) -> bool {
self.owned
}
/// Destroy this resource and release any state associated with it.
///
/// This is required to be called (or the async version) for all instances
/// of [`ResourceAny`] to ensure that state associated with this resource is
/// properly cleaned up. For owned resources this may execute the
/// guest-defined destructor if applicable (or the host-defined destructor
/// if one was specified).
///
/// # Errors
///
/// This function will return an [`OutOfMemory`][crate::OutOfMemory] error when
/// memory allocation fails. See the `OutOfMemory` type's documentation for
/// details on Wasmtime's out-of-memory handling.
pub fn resource_drop(self, mut store: impl AsContextMut) -> Result<()> {
let mut store = store.as_context_mut();
store.0.validate_sync_call()?;
self.resource_drop_impl(&mut store)
}
/// Same as [`ResourceAny::resource_drop`] except for use with async stores
/// to execute the destructor [asynchronously](crate#async).
///
/// # Errors
///
/// This function will return an [`OutOfMemory`][crate::OutOfMemory] error when
/// memory allocation fails. See the `OutOfMemory` type's documentation for
/// details on Wasmtime's out-of-memory handling.
#[cfg(feature = "async")]
pub async fn resource_drop_async(self, mut store: impl AsContextMut<Data: Send>) -> Result<()> {
let mut store = store.as_context_mut();
store
.on_fiber(|store| self.resource_drop_impl(store))
.await?
}
fn resource_drop_impl<T: 'static>(self, store: &mut StoreContextMut<'_, T>) -> Result<()> {
// Attempt to remove `self.idx` from the host table in `store`.
//
// This could fail if the index is invalid or if this is removing an
// `Own` entry which is currently being borrowed.
let pair = HostResourceTables::new_host(store.0)?.host_resource_drop(self.idx)?;
let (rep, slot) = match (pair, self.owned) {
(Some(pair), true) => pair,
// A `borrow` was removed from the table and no further
// destruction, e.g. the destructor, is required so we're done.
(None, false) => return Ok(()),
_ => unreachable!(),
};
if slot.instance.is_some() && !store.0.may_enter() {
bail!(Trap::CannotEnterComponent);
}
let dtor = match slot.dtor {
Some(dtor) => dtor.as_non_null(),
None => return Ok(()),
};
let mut args = [ValRaw::u32(rep)];
// Setup async-level task infrastructure for this call. This, for
// example, prevents the destructor from blocking.
//
// Note that if `slot.instance` is `None` then this is skipped. That
// means that this is a host resource being destroyed by the host. In
// that case restrictions around blocking and such are exempt.
if let Some(instance) = slot.instance {
store.0.enter_guest_sync_call(None, false, instance)?;
}
// This should be safe because `dtor` has been checked to belong to the
// `store` provided which means it's valid and still alive. Additionally
// destructors have al been previously type-checked and are guaranteed
// to take one i32 argument and return no results, so the parameters
// here should be configured correctly.
unsafe {
crate::Func::call_unchecked_raw(store, dtor, NonNull::from(&mut args))?;
}
if slot.instance.is_some() {
store.0.exit_guest_sync_call()?;
}
Ok(())
}
fn lower_to_index<U>(&self, cx: &mut LowerContext<'_, U>, ty: InterfaceType) -> Result<u32> {
match ty {
InterfaceType::Own(t) => {
if cx.resource_type(t) != self.ty {
bail!("mismatched resource types");
}
let rep = cx.host_resource_lift_own(self.idx)?;
cx.guest_resource_lower_own(t, rep)
}
InterfaceType::Borrow(t) => {
if cx.resource_type(t) != self.ty {
bail!("mismatched resource types");
}
let rep = cx.host_resource_lift_borrow(self.idx)?;
cx.guest_resource_lower_borrow(t, rep)
}
_ => bad_type_info(),
}
}
fn lift_from_index(cx: &mut LiftContext<'_>, ty: InterfaceType, index: u32) -> Result<Self> {
match ty {
InterfaceType::Own(t) => {
let ty = cx.resource_type(t);
let (rep, dtor, flags) = cx.guest_resource_lift_own(t, index)?;
let idx = cx.host_resource_lower_own(rep, dtor, flags)?;
Ok(ResourceAny {
idx,
ty,
owned: true,
})
}
InterfaceType::Borrow(t) => {
let ty = cx.resource_type(t);
let rep = cx.guest_resource_lift_borrow(t, index)?;
let idx = cx.host_resource_lower_borrow(rep)?;
Ok(ResourceAny {
idx,
ty,
owned: false,
})
}
_ => bad_type_info(),
}
}
}
unsafe impl ComponentType for ResourceAny {
const ABI: CanonicalAbiInfo = CanonicalAbiInfo::SCALAR4;
const MAY_REQUIRE_REALLOC: bool = false;
type Lower = <u32 as ComponentType>::Lower;
fn typecheck(ty: &InterfaceType, _types: &InstanceType<'_>) -> Result<()> {
match ty {
InterfaceType::Own(_) | InterfaceType::Borrow(_) => Ok(()),
other => bail!("expected `own` or `borrow`, found `{}`", desc(other)),
}
}
}
unsafe impl Lower for ResourceAny {
fn linear_lower_to_flat<T>(
&self,
cx: &mut LowerContext<'_, T>,
ty: InterfaceType,
dst: &mut MaybeUninit<Self::Lower>,
) -> Result<()> {
self.lower_to_index(cx, ty)?
.linear_lower_to_flat(cx, InterfaceType::U32, dst)
}
fn linear_lower_to_memory<T>(
&self,
cx: &mut LowerContext<'_, T>,
ty: InterfaceType,
offset: usize,
) -> Result<()> {
self.lower_to_index(cx, ty)?
.linear_lower_to_memory(cx, InterfaceType::U32, offset)
}
}
unsafe impl Lift for ResourceAny {
fn linear_lift_from_flat(
cx: &mut LiftContext<'_>,
ty: InterfaceType,
src: &Self::Lower,
) -> Result<Self> {
let index = u32::linear_lift_from_flat(cx, InterfaceType::U32, src)?;
ResourceAny::lift_from_index(cx, ty, index)
}
fn linear_lift_from_memory(
cx: &mut LiftContext<'_>,
ty: InterfaceType,
bytes: &[u8],
) -> Result<Self> {
let index = u32::linear_lift_from_memory(cx, InterfaceType::U32, bytes)?;
ResourceAny::lift_from_index(cx, ty, index)
}
}
@@ -0,0 +1,399 @@
use crate::AsContextMut;
use crate::component::func::{LiftContext, LowerContext, bad_type_info, desc};
use crate::component::matching::InstanceType;
use crate::component::resources::{HostResourceIndex, HostResourceTables};
use crate::component::{ComponentType, Lift, Lower, ResourceAny, ResourceType};
use crate::prelude::*;
use core::fmt;
use core::marker;
use core::mem::MaybeUninit;
use core::sync::atomic::{AtomicU32, Ordering::Relaxed};
use wasmtime_environ::component::{CanonicalAbiInfo, InterfaceType};
/// Internal type in Wasmtime used to represent host-defined resources that are
/// not tracked within the store.
pub struct HostResource<T: HostResourceType<D>, D> {
/// The host-defined 32-bit representation of this resource.
rep: u32,
/// Metadata, if necessary, tracking the type of this resource.
ty: D,
/// Internal state about borrows and such.
state: AtomicResourceState,
_marker: marker::PhantomData<fn() -> T>,
}
// FIXME(rust-lang/rust#110338) the `D` type parameter here should be an
// associated type. In a first attempt at doing that the `wasmtime-wasi-io`
// crate failed to compile with obscure errors. At least this is an internal
// trait for now...
pub trait HostResourceType<D> {
/// Tests whether `ty` matches this resource type.
fn typecheck(ty: ResourceType) -> Option<D>;
/// Converts `Data` to a `ResourceType`.
fn resource_type(data: D) -> ResourceType;
}
/// Internal dynamic state tracking for this resource. This can be one of
/// four different states:
///
/// * `BORROW` / `u64::MAX` - this indicates that this is a borrowed
/// resource. The `rep` doesn't live in the host table and this `Resource`
/// instance is transiently available. It's the host's responsibility to
/// discard this resource when the borrow duration has finished.
///
/// * `NOT_IN_TABLE` / `u64::MAX - 1` - this indicates that this is an owned
/// resource not present in any store's table. This resource is not lent
/// out. It can be passed as an `(own $t)` directly into a guest's table
/// or it can be passed as a borrow to a guest which will insert it into
/// a host store's table for dynamic borrow tracking.
///
/// * `TAKEN` / `u64::MAX - 2` - while the `rep` is available the resource
/// has been dynamically moved into a guest and cannot be moved in again.
/// This is used for example to prevent the same resource from being
/// passed twice to a guest.
///
/// * All other values - any other value indicates that the value is an
/// index into a store's table of host resources. It's guaranteed that the
/// table entry represents a host resource and the resource may have
/// borrow tracking associated with it. The low 32-bits of the value are
/// the table index and the upper 32-bits are the generation.
///
/// Note that this is two `AtomicU32` fields but it's not intended to actually
/// be used in conjunction with threads as generally a `Store<T>` lives on one
/// thread at a time. The pair of `AtomicU32` here is used to ensure that this
/// type is `Send + Sync` when captured as a reference to make async
/// programming more ergonomic.
///
/// Also note that two `AtomicU32` here are used instead of `AtomicU64` to be
/// more portable to platforms without 64-bit atomics.
struct AtomicResourceState {
index: AtomicU32,
generation: AtomicU32,
}
#[derive(Debug, PartialEq, Eq, Copy, Clone)]
enum ResourceState {
Borrow,
NotInTable,
Taken,
Index(HostResourceIndex),
}
impl AtomicResourceState {
const BORROW: Self = AtomicResourceState::new(ResourceState::Borrow);
const NOT_IN_TABLE: Self = AtomicResourceState::new(ResourceState::NotInTable);
const fn new(state: ResourceState) -> AtomicResourceState {
let (index, generation) = state.encode();
Self {
index: AtomicU32::new(index),
generation: AtomicU32::new(generation),
}
}
fn get(&self) -> ResourceState {
ResourceState::decode(self.index.load(Relaxed), self.generation.load(Relaxed))
}
fn swap(&self, state: ResourceState) -> ResourceState {
let (index, generation) = state.encode();
let index_prev = self.index.load(Relaxed);
self.index.store(index, Relaxed);
let generation_prev = self.generation.load(Relaxed);
self.generation.store(generation, Relaxed);
ResourceState::decode(index_prev, generation_prev)
}
}
impl ResourceState {
// See comments on `state` above for info about these values.
const BORROW: u32 = u32::MAX;
const NOT_IN_TABLE: u32 = u32::MAX - 1;
const TAKEN: u32 = u32::MAX - 2;
fn decode(idx: u32, generation: u32) -> ResourceState {
match generation {
Self::BORROW => Self::Borrow,
Self::NOT_IN_TABLE => Self::NotInTable,
Self::TAKEN => Self::Taken,
_ => Self::Index(HostResourceIndex::new(idx, generation)),
}
}
const fn encode(&self) -> (u32, u32) {
match self {
Self::Borrow => (0, Self::BORROW),
Self::NotInTable => (0, Self::NOT_IN_TABLE),
Self::Taken => (0, Self::TAKEN),
Self::Index(index) => (index.index(), index.generation()),
}
}
}
impl<T, D> HostResource<T, D>
where
T: HostResourceType<D>,
D: PartialEq + Send + Sync + Copy + 'static,
{
pub fn new_own(rep: u32, ty: D) -> Self {
HostResource {
state: AtomicResourceState::NOT_IN_TABLE,
rep,
ty,
_marker: marker::PhantomData,
}
}
pub fn new_borrow(rep: u32, ty: D) -> Self {
HostResource {
state: AtomicResourceState::BORROW,
rep,
ty,
_marker: marker::PhantomData,
}
}
pub fn rep(&self) -> u32 {
self.rep
}
pub fn ty(&self) -> D {
self.ty
}
pub fn owned(&self) -> bool {
match self.state.get() {
ResourceState::Borrow => false,
ResourceState::Taken | ResourceState::NotInTable | ResourceState::Index(_) => true,
}
}
fn lower_to_index<U>(&self, cx: &mut LowerContext<'_, U>, ty: InterfaceType) -> Result<u32> {
match ty {
InterfaceType::Own(t) => {
match T::typecheck(cx.resource_type(t)) {
Some(t) if t == self.ty => {}
_ => bail!("resource type mismatch"),
}
let rep = match self.state.get() {
// If this is a borrow resource then this is a dynamic
// error on behalf of the embedder.
ResourceState::Borrow => {
bail!("cannot lower a `borrow` resource into an `own`")
}
// If this resource does not yet live in a table then we're
// dynamically transferring ownership to wasm. Record that
// it's no longer present and then pass through the
// representation.
ResourceState::NotInTable => {
let prev = self.state.swap(ResourceState::Taken);
assert_eq!(prev, ResourceState::NotInTable);
self.rep
}
// This resource has already been moved into wasm so this is
// a dynamic error on behalf of the embedder.
ResourceState::Taken => bail!("host resource already consumed"),
// If this resource lives in a host table then try to take
// it out of the table, which may fail, and on success we
// can move the rep into the guest table.
ResourceState::Index(idx) => cx.host_resource_lift_own(idx)?,
};
cx.guest_resource_lower_own(t, rep)
}
InterfaceType::Borrow(t) => {
match T::typecheck(cx.resource_type(t)) {
Some(t) if t == self.ty => {}
_ => bail!("resource type mismatch"),
}
let rep = match self.state.get() {
// If this is already a borrowed resource, nothing else to
// do and the rep is passed through.
ResourceState::Borrow => self.rep,
// If this resource is already gone, that's a dynamic error
// for the embedder.
ResourceState::Taken => bail!("host resource already consumed"),
// If this resource is not currently in a table then it
// needs to move into a table to participate in state
// related to borrow tracking. Execute the
// `host_resource_lower_own` operation here and update our
// state.
//
// Afterwards this is the same as the `idx` case below.
//
// Note that flags/dtor are passed as `None` here since
// `Resource<T>` doesn't offer destruction support.
ResourceState::NotInTable => {
let idx = cx.host_resource_lower_own(self.rep, None, None)?;
let prev = self.state.swap(ResourceState::Index(idx));
assert_eq!(prev, ResourceState::NotInTable);
cx.host_resource_lift_borrow(idx)?
}
// If this resource lives in a table then it needs to come
// out of the table with borrow-tracking employed.
ResourceState::Index(idx) => cx.host_resource_lift_borrow(idx)?,
};
cx.guest_resource_lower_borrow(t, rep)
}
_ => bad_type_info(),
}
}
fn lift_from_index(cx: &mut LiftContext<'_>, ty: InterfaceType, index: u32) -> Result<Self> {
let (state, rep, ty) = match ty {
// Ownership is being transferred from a guest to the host, so move
// it from the guest table into a new `Resource`. Note that this
// isn't immediately inserted into the host table and that's left
// for the future if it's necessary to take a borrow from this owned
// resource.
InterfaceType::Own(t) => {
let (rep, dtor, flags) = cx.guest_resource_lift_own(t, index)?;
assert!(dtor.is_some());
assert!(flags.is_none());
(AtomicResourceState::NOT_IN_TABLE, rep, t)
}
// The borrow here is lifted from the guest, but note the lack of
// `host_resource_lower_borrow` as it's intentional. Lowering
// a borrow has a special case in the canonical ABI where if the
// receiving module is the owner of the resource then it directly
// receives the `rep` and no other dynamic tracking is employed.
// This effectively mirrors that even though the canonical ABI
// isn't really all that applicable in host context here.
InterfaceType::Borrow(t) => {
let rep = cx.guest_resource_lift_borrow(t, index)?;
(AtomicResourceState::BORROW, rep, t)
}
_ => bad_type_info(),
};
let ty = T::typecheck(cx.resource_type(ty)).unwrap();
Ok(HostResource {
state,
rep,
ty,
_marker: marker::PhantomData,
})
}
pub fn try_into_resource_any(self, mut store: impl AsContextMut) -> Result<ResourceAny> {
let HostResource {
rep,
state,
ty,
_marker: _,
} = self;
let store = store.as_context_mut();
let mut tables = HostResourceTables::new_host(store.0)?;
let (idx, owned) = match state.get() {
ResourceState::Borrow => (tables.host_resource_lower_borrow(rep)?, false),
ResourceState::NotInTable => {
let idx = tables.host_resource_lower_own(rep, None, None)?;
(idx, true)
}
ResourceState::Taken => bail!("host resource already consumed"),
ResourceState::Index(idx) => (idx, true),
};
Ok(ResourceAny::new(idx, T::resource_type(ty), owned))
}
}
unsafe impl<T, D> ComponentType for HostResource<T, D>
where
T: HostResourceType<D>,
D: PartialEq + Send + Sync + Copy + 'static,
{
const ABI: CanonicalAbiInfo = CanonicalAbiInfo::SCALAR4;
const MAY_REQUIRE_REALLOC: bool = false;
type Lower = <u32 as ComponentType>::Lower;
fn typecheck(ty: &InterfaceType, types: &InstanceType<'_>) -> Result<()> {
let resource = match ty {
InterfaceType::Own(t) | InterfaceType::Borrow(t) => *t,
other => bail!("expected `own` or `borrow`, found `{}`", desc(other)),
};
if T::typecheck(types.resource_type(resource)).is_none() {
bail!("resource type mismatch");
}
Ok(())
}
}
unsafe impl<T, D> Lower for HostResource<T, D>
where
T: HostResourceType<D>,
D: PartialEq + Send + Sync + Copy + 'static,
{
fn linear_lower_to_flat<U>(
&self,
cx: &mut LowerContext<'_, U>,
ty: InterfaceType,
dst: &mut MaybeUninit<Self::Lower>,
) -> Result<()> {
self.lower_to_index(cx, ty)?
.linear_lower_to_flat(cx, InterfaceType::U32, dst)
}
fn linear_lower_to_memory<U>(
&self,
cx: &mut LowerContext<'_, U>,
ty: InterfaceType,
offset: usize,
) -> Result<()> {
self.lower_to_index(cx, ty)?
.linear_lower_to_memory(cx, InterfaceType::U32, offset)
}
}
unsafe impl<T, D> Lift for HostResource<T, D>
where
T: HostResourceType<D>,
D: PartialEq + Send + Sync + Copy + 'static,
{
fn linear_lift_from_flat(
cx: &mut LiftContext<'_>,
ty: InterfaceType,
src: &Self::Lower,
) -> Result<Self> {
let index = u32::linear_lift_from_flat(cx, InterfaceType::U32, src)?;
HostResource::lift_from_index(cx, ty, index)
}
fn linear_lift_from_memory(
cx: &mut LiftContext<'_>,
ty: InterfaceType,
bytes: &[u8],
) -> Result<Self> {
let index = u32::linear_lift_from_memory(cx, InterfaceType::U32, bytes)?;
HostResource::lift_from_index(cx, ty, index)
}
}
impl<T, D> fmt::Debug for HostResource<T, D>
where
T: HostResourceType<D>,
D: PartialEq + Send + Sync + Copy + 'static,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let state = match self.state.get() {
ResourceState::Borrow => "borrow",
ResourceState::NotInTable => "own (not in table)",
ResourceState::Taken => "taken",
ResourceState::Index(_) => "own",
};
f.debug_struct("Resource")
.field("rep", &self.rep)
.field("state", &state)
.finish()
}
}
@@ -0,0 +1,200 @@
use crate::AsContextMut;
use crate::component::func::{LiftContext, LowerContext};
use crate::component::matching::InstanceType;
use crate::component::resources::host::{HostResource, HostResourceType};
use crate::component::{ComponentType, Lift, Lower, ResourceAny, ResourceType};
use crate::prelude::*;
use core::fmt;
use core::mem::MaybeUninit;
use wasmtime_environ::component::{CanonicalAbiInfo, InterfaceType};
/// A host-defined resource in the component model with a dynamic runtime value
/// representing its type.
///
/// This type represents a host-owned resource in the same manner as
/// [`Resource`], and almost all of the documentation on that type is applicable
/// to usage of this type as well. Where the two differ is how embedders use
/// these types in a particular embedding.
///
/// # Use cases for [`Resource`]
///
/// The [`Resource`] type is intended to be used by Rust embedders and provides
/// a `T` type parameter to provide a layer of type safety when using the
/// resource. The type parameter prevents mixing up resources of the same type
/// by accident. The [`bindgen!`] macro, for example, uses [`Resource`] by
/// default to represent all resources imported by a wasm guest.
///
/// As the documentation on [`Resource`] indicates the `T` type parameter on
/// [`Resource<T>`] is a hint and `T` isn't stored inside. This means that the
/// host can write a function which takes [`Resource<T>`], for example, and
/// prevent mistakes of passing a wrong-typed-resource to that function.
/// Typically the 32-bit value that [`Resource<T>`] wraps is an index into some
/// sort of table the host manages and the index points to a value of type `T`.
/// The `T` type parameter can assist in writing helper functions to access
/// these types.
///
/// The downside of [`Resource`], however, is that all resource types must be
/// statically assigned at compile time. It's not possible to manufacture more
/// types at runtime in some more dynamic situations. That's where
/// [`ResourceDynamic`] comes in.
///
/// # Use cases for [`ResourceDynamic`]
///
/// The general idea of [`ResourceDynamic`] is very similar to [`Resource`] --
/// it represents a "trusted" 32-bit value that the host defines and assigns
/// meaning to. There is no destructor on [`ResourceDynamic`] and the host has
/// to know how to destroy the associated state, if any, that
/// [`ResourceDynamic`] references. The difference with [`Resource`] is that is
/// has runtime type information instead of static type information, meaning
/// that it's possible to mix these up at compile by accident.
///
/// However a [`ResourceDynamic`] can be constructed dynamically at runtime with
/// a runtime-defined type. For example an embedding that provides generic
/// access to types in the host may want to take advantage of the dynamic nature
/// of this type. Resources of type [`ResourceDynamic`] have a type of
/// [`ResourceType::host_dynamic(ty)`](ResourceType::host_dynamic) where `ty` is
/// the value provided to the constructors of [`ResourceDynamic`].
///
/// A [`ResourceDynamic`] implements [`Lift`] and [`Lower`] in the same manner
/// as [`Resource`], but the implementations may fail after type-checking unlike
/// with [`Resource`] (due to the dynamic nature of the type which can't be
/// fully-checked during type-checking).
///
/// [`Resource`]: crate::component::Resource
/// [`Resource<T>`]: crate::component::Resource
/// [`bindgen!`]: crate::component::bindgen
pub struct ResourceDynamic(HostResource<Dynamic, u32>);
struct Dynamic;
impl HostResourceType<u32> for Dynamic {
fn resource_type(ty: u32) -> ResourceType {
ResourceType::host_dynamic(ty)
}
fn typecheck(ty: ResourceType) -> Option<u32> {
ty.as_host_dynamic()
}
}
impl ResourceDynamic {
/// Creates a new owned resource with the `rep` specified.
///
/// This is the same as [`Resource::new_own`] except that `ty` is an extra
/// parameter for the host-defined type information.
///
/// [`Resource::new_own`]: crate::component::Resource::new_own
pub fn new_own(rep: u32, ty: u32) -> ResourceDynamic {
ResourceDynamic(HostResource::new_own(rep, ty))
}
/// Creates a new borrowed resource which isn't actually rooted in any
/// ownership.
///
/// This is the same as [`Resource::new_borrow`] except that `ty` is an extra
/// parameter for the host-defined type information.
///
/// [`Resource::new_borrow`]: crate::component::Resource::new_borrow
pub fn new_borrow(rep: u32, ty: u32) -> ResourceDynamic {
ResourceDynamic(HostResource::new_borrow(rep, ty))
}
/// Returns the underlying 32-bit representation used to originally create
/// this resource.
///
/// This is the same as [`Resource::rep`].
///
/// [`Resource::rep`]: crate::component::Resource::rep
pub fn rep(&self) -> u32 {
self.0.rep()
}
/// Returns the 32-bit integer indicating the type of this resource.
///
/// This will return the same 32-bit integer provided to the
/// [`ResourceDynamic::new_own`] constructor. The meaning of this integer is
/// left to the host and this only serves as an accessor to provide the
/// value back to the host.
pub fn ty(&self) -> u32 {
self.0.ty()
}
/// Returns whether this is an owned resource or not.
///
/// This is the same as [`Resource::owned`].
///
/// [`Resource::owned`]: crate::component::Resource::owned
pub fn owned(&self) -> bool {
self.0.owned()
}
/// Attempts to convert a [`ResourceAny`] into [`ResourceDynamic`].
///
/// This is the same as [`Resource::try_from_resource_any`].
///
/// [`Resource::try_from_resource_any`]: crate::component::Resource::try_from_resource_any
pub fn try_from_resource_any(resource: ResourceAny, store: impl AsContextMut) -> Result<Self> {
Ok(ResourceDynamic(resource.try_into_host_resource(store)?))
}
/// See [`ResourceAny::try_from_resource`]
pub fn try_into_resource_any(self, store: impl AsContextMut) -> Result<ResourceAny> {
self.0.try_into_resource_any(store)
}
}
unsafe impl ComponentType for ResourceDynamic {
const MAY_REQUIRE_REALLOC: bool = false;
const ABI: CanonicalAbiInfo = HostResource::<Dynamic, u32>::ABI;
type Lower = crate::ValRaw;
fn typecheck(ty: &InterfaceType, types: &InstanceType<'_>) -> Result<()> {
HostResource::<Dynamic, u32>::typecheck(ty, types)
}
}
unsafe impl Lower for ResourceDynamic {
fn linear_lower_to_flat<U>(
&self,
cx: &mut LowerContext<'_, U>,
ty: InterfaceType,
dst: &mut MaybeUninit<Self::Lower>,
) -> Result<()> {
self.0.linear_lower_to_flat(cx, ty, dst)
}
fn linear_lower_to_memory<U>(
&self,
cx: &mut LowerContext<'_, U>,
ty: InterfaceType,
offset: usize,
) -> Result<()> {
self.0.linear_lower_to_memory(cx, ty, offset)
}
}
unsafe impl Lift for ResourceDynamic {
fn linear_lift_from_flat(
cx: &mut LiftContext<'_>,
ty: InterfaceType,
src: &Self::Lower,
) -> Result<Self> {
let host_resource = HostResource::linear_lift_from_flat(cx, ty, src)?;
Ok(ResourceDynamic(host_resource))
}
fn linear_lift_from_memory(
cx: &mut LiftContext<'_>,
ty: InterfaceType,
bytes: &[u8],
) -> Result<Self> {
let host_resource = HostResource::linear_lift_from_memory(cx, ty, bytes)?;
Ok(ResourceDynamic(host_resource))
}
}
impl fmt::Debug for ResourceDynamic {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
@@ -0,0 +1,272 @@
//! This module defines the `Resource<T>` type in the public API of Wasmtime.
//!
//! The purpose of this type is to represent a typed resource on the host where
//! the runtime representation is just a 32-bit integer plus some minor state
//! tracking. Notably the `T` enables statically differentiating resources from
//! one another and enables up-front type-checking where the lift/lower
//! operations need not do any type-checking at all.
//!
//! The actual `T` type parameter is just a guide, no `T` value is ever needed.
use crate::AsContextMut;
use crate::component::func::{LiftContext, LowerContext};
use crate::component::matching::InstanceType;
use crate::component::resources::host::{HostResource, HostResourceType};
use crate::component::{ComponentType, Lift, Lower, ResourceAny, ResourceType};
use crate::prelude::*;
use core::fmt;
use core::mem::MaybeUninit;
use wasmtime_environ::component::{CanonicalAbiInfo, InterfaceType};
/// A host-defined resource in the component model with a statically ascribed
/// type parameter.
///
/// This type can be thought of as roughly a newtype wrapper around `u32` for
/// use as a resource with the component model. The main guarantee that the
/// component model provides is that the `u32` is not forgeable by guests and
/// there are guaranteed semantics about when a `u32` may be in use by the guest
/// and when it's guaranteed no longer needed. This means that it is safe for
/// embedders to consider the internal `u32` representation "trusted" and use it
/// for things like table indices with infallible accessors that panic on
/// out-of-bounds. This should only panic for embedder bugs, not because of any
/// possible behavior in the guest.
///
/// A `Resource<T>` value dynamically represents both an `(own $t)` in the
/// component model as well as a `(borrow $t)`. It can be inspected via
/// [`Resource::owned`] to test whether it is an owned handle. An owned handle
/// which is not actively borrowed can be destroyed at any time as it's
/// guaranteed that the guest does not have access to it. A borrowed handle, on
/// the other hand, may be accessed by the guest so it's not necessarily
/// guaranteed to be able to be destroyed.
///
/// Note that the "own" and "borrow" here refer to the component model, not
/// Rust. The semantics of Rust ownership and borrowing are slightly different
/// than the component model's (but spiritually the same) in that more dynamic
/// tracking is employed as part of the component model. This means that it's
/// possible to get runtime errors when using a `Resource<T>`. For example it is
/// an error to call [`Resource::new_borrow`] and pass that to a component
/// function expecting `(own $t)` and this is not statically disallowed.
///
/// The [`Resource`] type implements both the [`Lift`] and [`Lower`] trait to be
/// used with typed functions in the component model or as part of aggregate
/// structures and datatypes.
///
/// Note that [`Resource`] and [`ResourceDynamic`] are similar and for more
/// discussion of choosing one over the other see the documentation on
/// [`ResourceDynamic`]
///
/// [`ResourceDynamic`]: crate::component::ResourceDynamic
///
/// # Destruction of a resource
///
/// Resources in the component model are optionally defined with a destructor,
/// but this host resource type does not specify a destructor. It is left up to
/// the embedder to be able to determine how best to a destroy a resource when
/// it is owned.
///
/// Note, though, that while [`Resource`] itself does not specify destructors
/// it's still possible to do so via the [`Linker::resource`] definition. When a
/// resource type is defined for a guest component a destructor can be specified
/// which can be used to hook into resource destruction triggered by the guest.
///
/// This means that there are two ways that resource destruction is handled:
///
/// * Host resources destroyed by the guest can hook into the
/// [`Linker::resource`] destructor closure to handle resource destruction.
/// This could, for example, remove table entries.
///
/// * Host resources owned by the host itself have no automatic means of
/// destruction. The host can make its own determination that its own resource
/// is not lent out to the guest and at that time choose to destroy or
/// deallocate it.
///
/// # Dynamic behavior of a resource
///
/// A host-defined [`Resource`] does not necessarily represent a static value.
/// Its internals may change throughout its usage to track the state associated
/// with the resource. The internal 32-bit host-defined representation never
/// changes, however.
///
/// For example if there's a component model function of the form:
///
/// ```wasm
/// (func (param "a" (borrow $t)) (param "b" (own $t)))
/// ```
///
/// Then that can be extracted in Rust with:
///
/// ```rust,ignore
/// let func = instance.get_typed_func::<(&Resource<T>, &Resource<T>), ()>(&mut store, "name")?;
/// ```
///
/// Here the exact same resource can be provided as both arguments but that is
/// not valid to do so because the same resource cannot be actively borrowed and
/// passed by-value as the second parameter at the same time. The internal state
/// in `Resource<T>` will track this information and provide a dynamic runtime
/// error in this situation.
///
/// Mostly it's important to be aware that there is dynamic state associated
/// with a [`Resource<T>`] to provide errors in situations that cannot be
/// statically ruled out.
///
/// # Borrows and host responsibilities
///
/// Borrows to resources in the component model are guaranteed to be transient
/// such that if a borrow is passed as part of a function call then when the
/// function returns it's guaranteed that the guest no longer has access to the
/// resource. This guarantee, however, must be manually upheld by the host when
/// it receives its own borrow.
///
/// As mentioned above the [`Resource<T>`] type can represent a borrowed value
/// in addition to an owned value. This means a guest can provide the host with
/// a borrow, such as an argument to an imported function:
///
/// ```rust,ignore
/// linker.root().func_wrap("name", |_cx, (r,): (Resource<MyType>,)| {
/// assert!(!r.owned());
/// // .. here `r` is a borrowed value provided by the guest and the host
/// // shouldn't continue to access it beyond the scope of this call
/// })?;
/// ```
///
/// In this situation the host should take care to not attempt to persist the
/// resource beyond the scope of the call. It's the host's resource so it
/// technically can do what it wants with it but nothing is statically
/// preventing `r` to stay pinned to the lifetime of the closure invocation.
/// It's considered a mistake that the host performed if `r` is persisted too
/// long and accessed at the wrong time.
///
/// [`Linker::resource`]: crate::component::LinkerInstance::resource
pub struct Resource<T: 'static>(HostResource<Static<T>, ()>);
struct Static<T>(T);
impl<T: 'static> HostResourceType<()> for Static<T> {
fn resource_type((): ()) -> ResourceType {
ResourceType::host::<T>()
}
fn typecheck(ty: ResourceType) -> Option<()> {
if ty.is_host::<T>() { Some(()) } else { None }
}
}
impl<T> Resource<T>
where
T: 'static,
{
/// Creates a new owned resource with the `rep` specified.
///
/// The returned value is suitable for passing to a guest as either a
/// `(borrow $t)` or `(own $t)`.
pub fn new_own(rep: u32) -> Resource<T> {
Resource(HostResource::new_own(rep, ()))
}
/// Creates a new borrowed resource which isn't actually rooted in any
/// ownership.
///
/// This can be used to pass to a guest as a borrowed resource and the
/// embedder will know that the `rep` won't be in use by the guest
/// afterwards. Exactly how the lifetime of `rep` works is up to the
/// embedder.
pub fn new_borrow(rep: u32) -> Resource<T> {
Resource(HostResource::new_borrow(rep, ()))
}
/// Returns the underlying 32-bit representation used to originally create
/// this resource.
pub fn rep(&self) -> u32 {
self.0.rep()
}
/// Returns whether this is an owned resource or not.
///
/// Owned resources can be safely destroyed by the embedder at any time, and
/// borrowed resources have an owner somewhere else on the stack so can only
/// be accessed, not destroyed.
pub fn owned(&self) -> bool {
self.0.owned()
}
/// Attempts to convert a [`ResourceAny`] into [`Resource`].
///
/// This method will check that `resource` has type
/// `ResourceType::host::<T>()` and then convert it into a typed version of
/// the resource.
///
/// # Errors
///
/// This function will return an error if `resource` does not have type
/// `ResourceType::host::<T>()`. This function may also return an error if
/// `resource` is no longer valid, for example it was previously converted.
///
/// # Panics
///
/// This function will panic if `resource` does not belong to the `store`
/// specified.
pub fn try_from_resource_any(resource: ResourceAny, store: impl AsContextMut) -> Result<Self> {
Ok(Resource(resource.try_into_host_resource(store)?))
}
/// See [`ResourceAny::try_from_resource`]
pub fn try_into_resource_any(self, store: impl AsContextMut) -> Result<ResourceAny> {
self.0.try_into_resource_any(store)
}
}
unsafe impl<T: 'static> ComponentType for Resource<T> {
const ABI: CanonicalAbiInfo = HostResource::<Static<T>, ()>::ABI;
const MAY_REQUIRE_REALLOC: bool = false;
type Lower = crate::ValRaw;
fn typecheck(ty: &InterfaceType, types: &InstanceType<'_>) -> Result<()> {
HostResource::<Static<T>, ()>::typecheck(ty, types)
}
}
unsafe impl<T: 'static> Lower for Resource<T> {
fn linear_lower_to_flat<U>(
&self,
cx: &mut LowerContext<'_, U>,
ty: InterfaceType,
dst: &mut MaybeUninit<Self::Lower>,
) -> Result<()> {
self.0.linear_lower_to_flat(cx, ty, dst)
}
fn linear_lower_to_memory<U>(
&self,
cx: &mut LowerContext<'_, U>,
ty: InterfaceType,
offset: usize,
) -> Result<()> {
self.0.linear_lower_to_memory(cx, ty, offset)
}
}
unsafe impl<T: 'static> Lift for Resource<T> {
fn linear_lift_from_flat(
cx: &mut LiftContext<'_>,
ty: InterfaceType,
src: &Self::Lower,
) -> Result<Self> {
let host_resource = HostResource::linear_lift_from_flat(cx, ty, src)?;
Ok(Resource(host_resource))
}
fn linear_lift_from_memory(
cx: &mut LiftContext<'_>,
ty: InterfaceType,
bytes: &[u8],
) -> Result<Self> {
let host_resource = HostResource::linear_lift_from_memory(cx, ty, bytes)?;
Ok(Resource(host_resource))
}
}
impl<T> fmt::Debug for Resource<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
@@ -0,0 +1,318 @@
//! Infrastructure for managing "host tables" of resources in the component
//! model.
//!
//! This module is mostly an implementation detail of `ResourceAny` where it
//! tracks the ability for the host to have a handle to any resource, be it
//! either guest-defined or host-defined. The `HostResourceData` type is
//! stored inside of a `Store<T>` and `HostResourceTables` provides temporary
//! access to this information.
//!
//! This provides operations in terms of "lift" and "lower" terminology where
//! the idea is that a resource is "lowered" to the host meaning that metadata
//! about the resource is inserted into `HostResourceData` through
//! `HostResourceTables`. This produces a `HostResourceIndex` which can
//! then be used to access it later on. The `HostResourceIndex` can later be
//! used to "lift" the resource from the table back into its constituent parts
//! or remove it from the table.
//!
//! This tracks borrow state where you can, for example, lift a borrow from a
//! `HostResourceIndex` that represents an "own". This will activate
//! borrow-tracking infrastructure to ensure that the borrow is deleted before
//! the own is removed or otherwise taken out.
use crate::prelude::*;
use crate::runtime::component::RuntimeInstance;
use crate::runtime::vm::component::{ResourceTables, TypedResource, TypedResourceIndex};
use crate::runtime::vm::{SendSyncPtr, VMFuncRef};
use crate::store::StoreOpaque;
use core::ptr::NonNull;
use wasmtime_environ::component::TypeResourceTableIndex;
/// Metadata tracking the state of resources within a `Store`.
///
/// This is a borrowed structure created from a `Store` piecemeal from below.
/// The `ResourceTables` type holds most of the raw information and this
/// structure tacks on a reference to `HostResourceData` to track generation
/// numbers of host indices.
pub struct HostResourceTables<'a> {
tables: ResourceTables<'a>,
host_resource_data: &'a mut HostResourceData,
}
/// Metadata for host-owned resources owned within a `Store`.
///
/// This metadata is used to prevent the ABA problem with indices handed out as
/// part of `Resource` and `ResourceAny`. Those structures are `Copy` meaning
/// that it's easy to reuse them, possibly accidentally. To prevent issues in
/// the host Wasmtime attaches both an index (within `ResourceTables`) as well
/// as a 32-bit generation counter onto each `HostResourceIndex` which the host
/// actually holds in `Resource` and `ResourceAny`.
///
/// This structure holds a list which is a parallel list to the "list of reps"
/// that's stored within `ResourceTables` elsewhere in the `Store`. This
/// parallel list holds the last known generation of each element in the table.
/// The generation is then compared on access to make sure it's the same.
///
/// Whenever a slot in the table is allocated the `cur_generation` field is
/// pushed at the corresponding index of `generation_of_table_slot`. Whenever
/// a field is accessed the current value of `generation_of_table_slot` is
/// checked against the generation of the index. Whenever a slot is deallocated
/// the generation is incremented. Put together this means that any access of a
/// deallocated slot should deterministically provide an error.
#[derive(Default)]
pub struct HostResourceData {
cur_generation: u32,
table_slot_metadata: TryVec<TableSlot>,
}
#[derive(Copy, Clone)]
pub struct TableSlot {
generation: u32,
pub(super) instance: Option<RuntimeInstance>,
pub(super) dtor: Option<SendSyncPtr<VMFuncRef>>,
}
/// Host representation of an index into a table slot.
///
/// This is morally (u32, u32) but is encoded as a 64-bit integer. The low
/// 32-bits are the table index and the upper 32-bits are the generation
/// counter.
#[derive(PartialEq, Eq, Debug, Copy, Clone)]
#[repr(transparent)]
pub struct HostResourceIndex(u64);
impl HostResourceIndex {
pub(super) fn new(idx: u32, generation: u32) -> HostResourceIndex {
HostResourceIndex(u64::from(idx) | (u64::from(generation) << 32))
}
pub(super) const fn index(&self) -> u32 {
(self.0 & 0xffffffff) as u32
}
pub(super) const fn generation(&self) -> u32 {
(self.0 >> 32) as u32
}
}
impl<'a> HostResourceTables<'a> {
pub fn new_host(store: &'a mut StoreOpaque) -> Result<HostResourceTables<'a>> {
let (tables, data) = store.component_resource_tables_and_host_resource_data(None)?;
Ok(HostResourceTables::from_parts(tables, data))
}
pub fn from_parts(
tables: ResourceTables<'a>,
host_resource_data: &'a mut HostResourceData,
) -> Self {
HostResourceTables {
tables,
host_resource_data,
}
}
/// Lifts an `own` resource that resides in the host's tables at the `idx`
/// specified into its `rep`.
///
/// # Errors
///
/// Returns an error if `idx` doesn't point to a valid owned resource, or
/// if `idx` can't be lifted as an `own` (e.g. it has active borrows).
pub fn host_resource_lift_own(&mut self, idx: HostResourceIndex) -> Result<u32> {
let (idx, _) = self.validate_host_index(idx, true)?;
self.tables.resource_lift_own(TypedResourceIndex::Host(idx))
}
/// See [`HostResourceTables::host_resource_lift_own`].
pub fn host_resource_lift_borrow(&mut self, idx: HostResourceIndex) -> Result<u32> {
let (idx, _) = self.validate_host_index(idx, false)?;
self.tables
.resource_lift_borrow(TypedResourceIndex::Host(idx))
}
/// Lowers an `own` resource to be owned by the host.
///
/// This returns a new index into the host's set of resource tables which
/// will point to the `rep` specified. The returned index is suitable for
/// conversion into either [`Resource`] or [`ResourceAny`].
///
/// The `dtor` and instance `instance` are specified as well to know what
/// destructor to run when this resource is destroyed.
pub fn host_resource_lower_own(
&mut self,
rep: u32,
dtor: Option<NonNull<VMFuncRef>>,
instance: Option<RuntimeInstance>,
) -> Result<HostResourceIndex> {
let idx = self.tables.resource_lower_own(TypedResource::Host(rep))?;
Ok(self.new_host_index(idx, dtor, instance)?)
}
/// See [`HostResourceTables::host_resource_lower_own`].
pub fn host_resource_lower_borrow(&mut self, rep: u32) -> Result<HostResourceIndex> {
let idx = self
.tables
.resource_lower_borrow(TypedResource::Host(rep))?;
Ok(self.new_host_index(idx, None, None)?)
}
/// Validates that `idx` is still valid for the host tables, notably
/// ensuring that the generation listed in `idx` is the same as the
/// last recorded generation of the slot itself.
///
/// The `is_removal` option indicates whether or not this table access will
/// end up removing the element from the host table. In such a situation the
/// current generation number is incremented.
fn validate_host_index(
&mut self,
idx: HostResourceIndex,
is_removal: bool,
) -> Result<(u32, Option<TableSlot>)> {
let actual = usize::try_from(idx.index())
.ok()
.and_then(|i| self.host_resource_data.table_slot_metadata.get(i).copied());
// If `idx` is out-of-bounds then skip returning an error. In such a
// situation the operation that this is guarding will return a more
// precise error, such as a lift operation.
if let Some(actual) = actual {
if actual.generation != idx.generation() {
bail!("host-owned resource was already de-allocated");
}
}
// Bump the current generation of this is a removal to ensure any
// future item placed in this slot can't be pointed to by the `idx`
// provided above.
if is_removal {
self.host_resource_data.cur_generation += 1;
}
Ok((idx.index(), actual))
}
/// Creates a new `HostResourceIndex` which will point to the raw table
/// slot provided by `idx`.
///
/// This will register metadata necessary to track the current generation
/// in the returned `HostResourceIndex` as well.
fn new_host_index(
&mut self,
idx: u32,
dtor: Option<NonNull<VMFuncRef>>,
instance: Option<RuntimeInstance>,
) -> Result<HostResourceIndex> {
let list = &mut self.host_resource_data.table_slot_metadata;
let info = TableSlot {
generation: self.host_resource_data.cur_generation,
instance,
dtor: dtor.map(SendSyncPtr::new),
};
match list.get_mut(idx as usize) {
Some(slot) => *slot = info,
None => {
// Resource handles start at 1, not zero, so push two elements
// for the first resource handle.
if list.is_empty() {
assert_eq!(idx, 1);
list.push(TableSlot {
generation: 0,
instance: None,
dtor: None,
})?;
}
assert_eq!(idx as usize, list.len());
list.push(info)?;
}
}
Ok(HostResourceIndex::new(idx, info.generation))
}
/// Drops a host-owned resource from host tables.
///
/// This method will attempt to interpret `idx` as pointing to either a
/// `borrow` or `own` resource with the `expected` type specified. This
/// method will then return the underlying `rep` if it points to an `own`
/// resource which can then be further processed for destruction.
///
/// # Errors
///
/// Returns an error if `idx` doesn't point to a valid resource, points to
/// an `own` with active borrows, or if it doesn't have the type `expected`
/// in the host tables.
pub(super) fn host_resource_drop(
&mut self,
idx: HostResourceIndex,
) -> Result<Option<(u32, TableSlot)>> {
let (idx, slot) = self.validate_host_index(idx, true)?;
match self.tables.resource_drop(TypedResourceIndex::Host(idx))? {
Some(rep) => Ok(Some((rep, slot.unwrap()))),
None => Ok(None),
}
}
/// Lowers an `own` resource into the guest, converting the `rep` specified
/// into a guest-local index.
///
/// The `ty` provided is which table to put this into.
pub fn guest_resource_lower_own(
&mut self,
rep: u32,
ty: TypeResourceTableIndex,
) -> Result<u32> {
self.tables
.resource_lower_own(TypedResource::Component { ty, rep })
}
/// Lowers a `borrow` resource into the guest, converting the `rep`
/// specified into a guest-local index.
///
/// The `ty` provided is which table to put this into.
///
/// Note that this cannot be used in isolation because lowering a borrow
/// into a guest has a special case where `rep` is returned directly if `ty`
/// belongs to the component being lowered into. That property must be
/// handled by the caller of this function.
pub fn guest_resource_lower_borrow(
&mut self,
rep: u32,
ty: TypeResourceTableIndex,
) -> Result<u32> {
self.tables
.resource_lower_borrow(TypedResource::Component { ty, rep })
}
/// Lifts an `own` resource from the `idx` specified from the table `ty`.
///
/// This will lookup the appropriate table in the guest and return the `rep`
/// corresponding to `idx` if it's valid.
pub fn guest_resource_lift_own(
&mut self,
index: u32,
ty: TypeResourceTableIndex,
) -> Result<u32> {
self.tables
.resource_lift_own(TypedResourceIndex::Component { ty, index })
}
/// Lifts a `borrow` resource from the `idx` specified from the table `ty`.
///
/// This will lookup the appropriate table in the guest and return the `rep`
/// corresponding to `idx` if it's valid.
pub fn guest_resource_lift_borrow(
&mut self,
index: u32,
ty: TypeResourceTableIndex,
) -> Result<u32> {
self.tables
.resource_lift_borrow(TypedResourceIndex::Component { ty, index })
}
/// Completes a call into the component instance, validating that it's ok to
/// complete by ensuring the are no remaining active borrows.
#[inline]
pub fn validate_scope_exit(&mut self) -> Result<()> {
self.tables.validate_scope_exit()
}
}
@@ -0,0 +1,142 @@
//! This module defines the `ResourceType` type in the public API of Wasmtime,
//! which is all possible types of resources.
use crate::runtime::vm::component::ComponentInstance;
use crate::store::StoreId;
use core::any::TypeId;
use wasmtime_environ::component::{
AbstractResourceIndex, ComponentTypes, DefinedResourceIndex, ResourceIndex,
};
/// Representation of a resource type in the component model.
///
/// Resources are currently always represented as 32-bit integers but they have
/// unique types across instantiations and the host. For example instantiating
/// the same component twice means that defined resource types in the component
/// will all be different. Values of this type can be compared to see if
/// resources have the same type.
///
/// Resource types can also be defined on the host in addition to guests. On the
/// host resource types are tied to a `T`, an arbitrary Rust type. Two host
/// resource types are the same if they point to the same `T`.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct ResourceType {
kind: ResourceTypeKind,
}
impl ResourceType {
/// Creates a new host resource type corresponding to `T`.
///
/// Note that `T` is a mostly a phantom type parameter here. It does not
/// need to reflect the actual storage of the resource `T`. For example this
/// is valid:
///
/// ```rust
/// use wasmtime::component::ResourceType;
///
/// struct Foo;
///
/// let ty = ResourceType::host::<Foo>();
/// ```
///
/// A resource type of type `ResourceType::host::<T>()` will match the type
/// of the value produced by `Resource::<T>::new_{own,borrow}`.
pub fn host<T: 'static>() -> ResourceType {
ResourceType {
kind: ResourceTypeKind::Host(TypeId::of::<T>()),
}
}
/// Creates a new host resource type which is identified by the `payload`
/// runtime argument.
///
/// The `payload` argument to this function is an arbitrary 32-bit value
/// that the host can use to distinguish one resource from another.
/// A resource type of type `ResourceType::host_dynamic(2)` will match the
/// type of the value produced by `ResourceDynamic::new_{own,borrow}(_, 2)`,
/// for example.
///
/// This type of resource is disjoint from all other types of resources. For
/// example any resource with type `ResourceType::host::<u32>()` will be a
/// different type than all types created by this function.
pub fn host_dynamic(payload: u32) -> ResourceType {
ResourceType {
kind: ResourceTypeKind::HostDynamic(payload),
}
}
pub(crate) fn guest(
store: StoreId,
instance: &ComponentInstance,
id: DefinedResourceIndex,
) -> ResourceType {
ResourceType {
kind: ResourceTypeKind::Guest {
store,
instance: instance as *const _ as usize,
id,
},
}
}
pub(crate) fn uninstantiated(types: &ComponentTypes, index: ResourceIndex) -> ResourceType {
ResourceType {
kind: ResourceTypeKind::Uninstantiated {
component: types as *const _ as usize,
index,
},
}
}
pub(crate) fn abstract_(types: &ComponentTypes, index: AbstractResourceIndex) -> ResourceType {
ResourceType {
kind: ResourceTypeKind::Abstract {
component: types as *const _ as usize,
index,
},
}
}
pub(crate) fn is_host<T: 'static>(&self) -> bool {
match self.kind {
ResourceTypeKind::Host(id) if id == TypeId::of::<T>() => true,
_ => false,
}
}
pub(crate) fn as_host_dynamic(&self) -> Option<u32> {
match self.kind {
ResourceTypeKind::HostDynamic(payload) => Some(payload),
_ => None,
}
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
enum ResourceTypeKind {
Host(TypeId),
HostDynamic(u32),
Guest {
store: StoreId,
// For now this is the `*mut ComponentInstance` pointer within the store
// that this guest corresponds to. It's used to distinguish different
// instantiations of the same component within the store.
instance: usize,
id: DefinedResourceIndex,
},
Uninstantiated {
// Like `instance` in `Guest` above this is a pointer and is used to
// distinguish between two components. Technically susceptible to ABA
// issues but the consequence is a nonexistent resource would be equal
// to a new resource so there's not really any issue with that.
component: usize,
index: ResourceIndex,
},
/// The type of this resource is considered "abstract" meaning that it
/// doesn't actually correspond to anything at runtime but instead it just
/// needs to be kept distinct from everything but itself.
Abstract {
component: usize,
index: AbstractResourceIndex,
},
}
@@ -0,0 +1,68 @@
use crate::ValRaw;
use core::mem::{self, MaybeUninit};
use core::slice;
fn assert_raw_slice_compat<T>() {
assert!(mem::size_of::<T>() % mem::size_of::<ValRaw>() == 0);
assert!(mem::align_of::<T>() == mem::align_of::<ValRaw>());
}
/// Converts a `<T as ComponentType>::Lower` representation to a slice of
/// `ValRaw`.
pub unsafe fn storage_as_slice<T>(storage: &T) -> &[ValRaw] {
assert_raw_slice_compat::<T>();
unsafe {
slice::from_raw_parts(
(storage as *const T).cast(),
mem::size_of_val(storage) / mem::size_of::<ValRaw>(),
)
}
}
/// Same as `storage_as_slice`, but mutable.
pub unsafe fn storage_as_slice_mut<T>(storage: &mut MaybeUninit<T>) -> &mut [MaybeUninit<ValRaw>] {
assert_raw_slice_compat::<T>();
unsafe {
slice::from_raw_parts_mut(
(storage as *mut MaybeUninit<T>).cast(),
mem::size_of_val(storage) / mem::size_of::<ValRaw>(),
)
}
}
/// Same as `storage_as_slice`, but in reverse and mutable.
pub unsafe fn slice_to_storage_mut<T>(slice: &mut [MaybeUninit<ValRaw>]) -> &mut MaybeUninit<T> {
assert_raw_slice_compat::<T>();
// This is an actual runtime assertion which if performance calls for we may
// need to relax to a debug assertion. This notably tries to ensure that we
// stay within the bounds of the number of actual values given rather than
// reading past the end of an array. This shouldn't actually trip unless
// there's a bug in Wasmtime though.
assert!(
mem::size_of_val(slice) >= mem::size_of::<T>(),
"needed {}; got {}",
mem::size_of::<T>(),
mem::size_of_val(slice)
);
unsafe { &mut *slice.as_mut_ptr().cast() }
}
/// Same as `storage_as_slice`, but in reverse
pub unsafe fn slice_to_storage<T>(slice: &[ValRaw]) -> &T {
assert_raw_slice_compat::<T>();
// See notes above in `slice_to_storage_mut` about how this is an actual
// runtime assertion.
assert!(
mem::size_of_val(slice) >= mem::size_of::<T>(),
"needed {}; got {}",
mem::size_of::<T>(),
mem::size_of_val(slice)
);
unsafe { &*slice.as_ptr().cast() }
}
@@ -0,0 +1,581 @@
use crate::prelude::*;
use crate::runtime::component::{HostResourceData, Instance};
use crate::runtime::vm;
use crate::runtime::vm::component::{
CallContext, ComponentInstance, HandleTable, OwnedComponentInstance,
};
use crate::store::{StoreData, StoreId, StoreOpaque};
use crate::{AsContext, AsContextMut, Engine, Store, StoreContextMut};
use core::pin::Pin;
use wasmtime_environ::component::RuntimeComponentInstanceIndex;
use wasmtime_environ::prelude::TryPrimaryMap;
#[cfg(feature = "component-model-async")]
use crate::{
component::ResourceTable,
component::concurrent::ConcurrentState,
runtime::vm::{VMStore, component::InstanceState},
};
/// Default amount of fuel allowed for all guest-to-host calls in the component
/// model.
///
/// This is the maximal amount of data which will be copied from the guest to
/// the host by default. This is set large enough as to not be hit all that
/// often in theory but also small enough such that if left unconfigured on a
/// host doesn't mean that it's automatically susceptible to DoS for example.
const DEFAULT_HOSTCALL_FUEL: usize = 128 << 20;
/// Extensions to `Store` which are only relevant for component-related
/// information.
pub struct ComponentStoreData {
/// All component instances, in a similar manner to how core wasm instances
/// are managed.
instances: TryPrimaryMap<ComponentInstanceId, Option<OwnedComponentInstance>>,
/// Whether an instance belonging to this store has trapped.
trapped: bool,
/// Total number of component instances in this store, used to track
/// resources in the instance allocator.
num_component_instances: usize,
/// Runtime state for components used in the handling of resources, borrow,
/// and calls. These also interact with the `ResourceAny` type and its
/// internal representation.
component_host_table: HandleTable,
host_resource_data: HostResourceData,
/// Metadata/tasks/etc related to component-model-async and concurrency
/// support.
task_state: ComponentTaskState,
/// Fuel to be used for each time the guest calls the host or transfers data
/// to the host.
///
/// Caps the size of the allocations made on the host to this amount
/// effectively.
hostcall_fuel: usize,
}
/// State tracking for tasks within components.
pub enum ComponentTaskState {
/// Used when `Config::concurrency_support` is disabled. Here there are no
/// async tasks but there's still state for borrows that needs managing.
NotConcurrent(ComponentTasksNotConcurrent),
/// Used when `Config::concurrency_support` is enabled and has
/// full state for all async tasks.
#[cfg(feature = "component-model-async")]
Concurrent(ConcurrentState),
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct ComponentInstanceId(u32);
wasmtime_environ::entity_impl!(ComponentInstanceId);
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub struct RuntimeInstance {
pub instance: ComponentInstanceId,
pub index: RuntimeComponentInstanceIndex,
}
impl ComponentStoreData {
pub fn new(engine: &Engine) -> ComponentStoreData {
ComponentStoreData {
instances: Default::default(),
trapped: false,
num_component_instances: 0,
component_host_table: Default::default(),
host_resource_data: Default::default(),
task_state: if engine.tunables().concurrency_support {
#[cfg(feature = "component-model-async")]
{
ComponentTaskState::Concurrent(Default::default())
}
#[cfg(not(feature = "component-model-async"))]
{
// This should be validated in `Config` where if
// `concurrency_support` is enabled but compile time support
// isn't available then an `Engine` isn't creatable.
unreachable!()
}
} else {
ComponentTaskState::NotConcurrent(Default::default())
},
hostcall_fuel: DEFAULT_HOSTCALL_FUEL,
}
}
/// Hook used just before a `Store` is dropped to dispose of anything
/// necessary.
///
/// Used at this time to deallocate fibers related to concurrency support.
pub fn run_manual_drop_routines<T>(store: StoreContextMut<T>) {
// We need to drop the fibers of each component instance before
// attempting to drop the instances themselves since the fibers may need
// to be resumed and allowed to exit cleanly before we yank the state
// out from under them.
//
// This will also drop any futures which might use a `&Accessor` fields
// in their `Drop::drop` implementations, in which case they'll need to
// be called from with in the context of a `tls::set` closure.
#[cfg(feature = "component-model-async")]
if store.0.component_data().task_state.is_concurrent() {
ComponentStoreData::drop_fibers_and_futures(store.0);
}
#[cfg(not(feature = "component-model-async"))]
let _ = store;
}
pub fn next_component_instance_id(&self) -> ComponentInstanceId {
self.instances.next_key()
}
#[cfg(feature = "component-model-async")]
pub(crate) fn drop_fibers_and_futures(store: &mut dyn VMStore) {
let mut fibers = Vec::new();
let mut futures = Vec::new();
store
.concurrent_state_mut_without_forcing_current_thread()
.take_fibers_and_futures(&mut fibers, &mut futures);
for mut fiber in fibers {
fiber.dispose(store);
}
crate::component::concurrent::tls::set(store, move || drop(futures));
}
#[cfg(feature = "component-model-async")]
pub(crate) fn assert_instance_states_empty(&mut self) {
for (_, instance) in self.instances.iter_mut() {
let Some(instance) = instance.as_mut() else {
continue;
};
assert!(instance.get_mut().instance_states().0.iter_mut().all(
|(_, state): (_, &mut InstanceState)| state.handle_table().is_empty()
&& state.concurrent_state().pending_is_empty()
));
}
}
pub fn decrement_allocator_resources(&mut self, allocator: &dyn vm::InstanceAllocator) {
for _ in 0..self.num_component_instances {
allocator.decrement_component_instance_count();
}
}
#[cfg(all(feature = "component-model-async", feature = "gc"))]
pub fn task_state_mut(&mut self) -> &mut ComponentTaskState {
&mut self.task_state
}
}
/// A type used to represent an allocated `ComponentInstance` located within a
/// store.
///
/// This type is held in various locations as a "safe index" into a store. This
/// encapsulates a `StoreId` which owns the instance as well as the index within
/// the store's list of which instance it's pointing to.
///
/// This type can notably be used to index into a `StoreOpaque` to project out
/// the `ComponentInstance` that is associated with this id.
#[repr(C)] // used by reference in the C API
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct StoreComponentInstanceId {
store_id: StoreId,
instance: ComponentInstanceId,
}
impl StoreComponentInstanceId {
pub(crate) fn new(
store_id: StoreId,
instance: ComponentInstanceId,
) -> StoreComponentInstanceId {
StoreComponentInstanceId { store_id, instance }
}
#[inline]
pub fn assert_belongs_to(&self, store: StoreId) {
self.store_id.assert_belongs_to(store)
}
#[inline]
pub(crate) fn store_id(&self) -> StoreId {
self.store_id
}
#[inline]
pub(crate) fn instance(&self) -> ComponentInstanceId {
self.instance
}
/// Looks up the `vm::ComponentInstance` within `store` that this id points
/// to.
///
/// # Panics
///
/// Panics if `self` does not belong to `store`.
pub(crate) fn get<'a>(&self, store: &'a StoreOpaque) -> &'a ComponentInstance {
self.assert_belongs_to(store.id());
store.component_instance(self.instance)
}
/// Mutable version of `get` above.
///
/// # Panics
///
/// Panics if `self` does not belong to `store`.
pub(crate) fn get_mut<'a>(&self, store: &'a mut StoreOpaque) -> Pin<&'a mut ComponentInstance> {
self.from_data_get_mut(store.store_data_mut())
}
/// Return a mutable `ComponentInstance` and a `ModuleRegistry`
/// from the store.
///
/// # Panics
///
/// Panics if `self` does not belong to `store`.
#[cfg(feature = "component-model-async")]
pub(crate) fn get_mut_and_registry<'a>(
&self,
store: &'a mut StoreOpaque,
) -> (
Pin<&'a mut ComponentInstance>,
&'a crate::module::ModuleRegistry,
) {
let (store_data, registry) = store.store_data_mut_and_registry();
let instance = self.from_data_get_mut(store_data);
(instance, registry)
}
/// Same as `get_mut`, but borrows less of a store.
fn from_data_get_mut<'a>(&self, store: &'a mut StoreData) -> Pin<&'a mut ComponentInstance> {
self.assert_belongs_to(store.id());
store.component_instance_mut(self.instance)
}
}
impl StoreData {
pub(crate) fn push_component_instance(
&mut self,
data: OwnedComponentInstance,
) -> Result<ComponentInstanceId, OutOfMemory> {
let expected = data.get().id();
let ret = self.components.instances.push(Some(data))?;
assert_eq!(expected, ret);
Ok(ret)
}
pub(crate) fn component_instance(&self, id: ComponentInstanceId) -> &ComponentInstance {
self.components.instances[id].as_ref().unwrap().get()
}
pub(crate) fn component_instance_mut(
&mut self,
id: ComponentInstanceId,
) -> Pin<&mut ComponentInstance> {
self.components.instances[id].as_mut().unwrap().get_mut()
}
}
impl StoreOpaque {
pub(crate) fn trapped(&self) -> bool {
self.store_data().components.trapped
}
pub(crate) fn set_trapped(&mut self) {
self.store_data_mut().components.trapped = true;
}
/// Determine whether an instance may be entered from the host.
///
/// We return `false` here only `self` has been poisoned due to a trap.
pub(crate) fn may_enter(&mut self) -> bool {
!self.trapped()
}
pub(crate) fn component_data(&self) -> &ComponentStoreData {
&self.store_data().components
}
pub(crate) fn component_data_mut(&mut self) -> &mut ComponentStoreData {
&mut self.store_data_mut().components
}
pub(crate) fn push_component_instance(&mut self, instance: Instance) {
// We don't actually need the instance itself right now, but it seems
// like something we will almost certainly eventually want to keep
// around, so force callers to provide it.
let _ = instance;
self.component_data_mut().num_component_instances += 1;
}
pub(crate) fn component_instance(&self, id: ComponentInstanceId) -> &ComponentInstance {
self.store_data().component_instance(id)
}
#[cfg(feature = "component-model-async")]
pub(crate) fn component_instance_mut(
&mut self,
id: ComponentInstanceId,
) -> Pin<&mut ComponentInstance> {
self.store_data_mut().component_instance_mut(id)
}
#[cfg(feature = "component-model-async")]
pub(crate) fn concurrent_state_mut_without_forcing_current_thread(
&mut self,
) -> &mut ConcurrentState {
debug_assert!(self.concurrency_support());
self.component_data_mut().task_state.concurrent_state_mut()
}
#[cfg(feature = "component-model-async")]
pub(crate) fn concurrent_state_mut_already_forced_current_thread(
&mut self,
) -> &mut ConcurrentState {
debug_assert!(self.concurrency_support());
debug_assert!(
!self
.vm_store_context_mut()
.current_thread_mut()
.is_deferred()
);
self.concurrent_state_mut_without_forcing_current_thread()
}
#[cfg(feature = "component-model-async")]
pub(crate) fn concurrent_state_mut(&mut self) -> Result<&mut ConcurrentState> {
debug_assert!(self.concurrency_support());
self.current_thread()?;
Ok(self.component_data_mut().task_state.concurrent_state_mut())
}
#[inline]
#[cfg(feature = "component-model-async")]
pub(crate) fn concurrency_support(&self) -> bool {
let support = self.component_data().task_state.is_concurrent();
debug_assert_eq!(support, self.engine().tunables().concurrency_support);
support
}
pub(crate) fn lift_context_parts(
&mut self,
instance: Instance,
) -> (
&mut ComponentTaskState,
&mut HandleTable,
&mut HostResourceData,
Pin<&mut ComponentInstance>,
) {
let instance = instance.id();
instance.assert_belongs_to(self.id());
let data = self.component_data_mut();
(
&mut data.task_state,
&mut data.component_host_table,
&mut data.host_resource_data,
data.instances[instance.instance]
.as_mut()
.unwrap()
.get_mut(),
)
}
pub(crate) fn component_resource_tables(
&mut self,
instance: Option<Instance>,
) -> Result<vm::component::ResourceTables<'_>> {
Ok(self
.component_resource_tables_and_host_resource_data(instance)?
.0)
}
pub(crate) fn component_resource_tables_and_host_resource_data(
&mut self,
instance: Option<Instance>,
) -> Result<(
vm::component::ResourceTables<'_>,
&mut crate::component::HostResourceData,
)> {
let current_scope_id = self.current_scope_id()?;
let store_id = self.id();
let data = self.component_data_mut();
let guest = instance.map(|i| {
let i = i.id();
i.assert_belongs_to(store_id);
data.instances[i.instance]
.as_mut()
.unwrap()
.get_mut()
.instance_states()
});
Ok((
vm::component::ResourceTables {
host_table: &mut data.component_host_table,
task_state: &mut data.task_state,
guest,
current_scope_id,
},
&mut data.host_resource_data,
))
}
pub(crate) fn enter_call_not_concurrent(&mut self) -> Result<()> {
let state = match &mut self.component_data_mut().task_state {
ComponentTaskState::NotConcurrent(state) => state,
#[cfg(feature = "component-model-async")]
ComponentTaskState::Concurrent(_) => unreachable!(),
};
state.scopes.push(CallContext::default())?;
Ok(())
}
pub(crate) fn exit_call_not_concurrent(&mut self) {
let state = match &mut self.component_data_mut().task_state {
ComponentTaskState::NotConcurrent(state) => state,
#[cfg(feature = "component-model-async")]
ComponentTaskState::Concurrent(_) => unreachable!(),
};
state.scopes.pop();
}
pub(crate) fn hostcall_fuel(&self) -> usize {
self.component_data().hostcall_fuel
}
pub(crate) fn set_hostcall_fuel(&mut self, fuel: usize) {
self.component_data_mut().hostcall_fuel = fuel;
}
#[cfg(feature = "component-model-async")]
fn concurrent_resource_table(&mut self) -> Option<&mut ResourceTable> {
if self.concurrency_support() {
Some(
self.concurrent_state_mut_without_forcing_current_thread()
.table(),
)
} else {
None
}
}
pub(crate) fn current_scope_id_not_concurrent(&mut self) -> Result<Option<u32>> {
match &mut self.component_data_mut().task_state {
ComponentTaskState::NotConcurrent(state) => match state.scopes.len().checked_sub(1) {
Some(i) => Ok(Some(u32::try_from(i)?)),
None => Ok(None),
},
#[cfg(feature = "component-model-async")]
ComponentTaskState::Concurrent(_) => crate::bail_bug!("should not be reachable"),
}
}
}
impl<T> Store<T> {
/// Returns the amount of "hostcall fuel" used for guest-to-host component
/// calls.
///
/// This is either the default amount if it hasn't been configured or
/// returns the last value passed to [`Store::set_hostcall_fuel`].
///
/// See [`Store::set_hostcall_fuel`] `for more details.
pub fn hostcall_fuel(&self) -> usize {
self.as_context().0.hostcall_fuel()
}
/// Sets the amount of "hostcall fuel" used for guest-to-host component
/// calls.
///
/// Whenever the guest calls the host it often wants to transfer some data
/// as well, such as strings or lists. This configured fuel value can be
/// used to limit the amount of data that the host allocates on behalf of
/// the guest. This is a DoS mitigation mechanism to prevent a malicious
/// guest from causing the host to allocate an unbounded amount of memory
/// for example.
///
/// Fuel is considered distinct for each host call. The host is responsible
/// for ensuring it retains a proper amount of data between host calls if
/// applicable. The `fuel` provided here will be the initial value for each
/// time the guest calls the host.
///
/// The `fuel` value here should roughly corresponds to the maximal number
/// of bytes that the guest may transfer to the host in one call.
///
/// Note that data transferred from the host to the guest is not limited
/// because it's already resident on the host itself. Only data from the
/// guest to the host is limited.
///
/// The default value for this is 128 MiB.
pub fn set_hostcall_fuel(&mut self, fuel: usize) {
self.as_context_mut().set_hostcall_fuel(fuel)
}
/// Returns the underlying [`ResourceTable`] that the implementation of
/// concurrency in the component model is using.
///
/// Returns `None` if [`Config::concurrency_support`] is disabled.
///
/// [`Config::concurrency_support`]: crate::Config::concurrency_support
#[cfg(feature = "component-model-async")]
pub fn concurrent_resource_table(&mut self) -> Option<&mut ResourceTable> {
self.as_context_mut().0.concurrent_resource_table()
}
}
impl<T> StoreContextMut<'_, T> {
/// See [`Store::hostcall_fuel`].
pub fn hostcall_fuel(&self) -> usize {
self.0.hostcall_fuel()
}
/// See [`Store::set_hostcall_fuel`].
pub fn set_hostcall_fuel(&mut self, fuel: usize) {
self.0.set_hostcall_fuel(fuel)
}
/// See [`Store::concurrent_resource_table`].
#[cfg(feature = "component-model-async")]
pub fn concurrent_resource_table(&mut self) -> Option<&mut ResourceTable> {
self.0.concurrent_resource_table()
}
}
#[derive(Default)]
pub struct ComponentTasksNotConcurrent {
scopes: TryVec<CallContext>,
}
impl ComponentTaskState {
pub fn call_context(&mut self, id: u32) -> Result<&mut CallContext> {
match self {
ComponentTaskState::NotConcurrent(state) => Ok(&mut state.scopes[id as usize]),
#[cfg(feature = "component-model-async")]
ComponentTaskState::Concurrent(state) => state.call_context(id),
}
}
#[cfg(feature = "component-model-async")]
pub fn concurrent_state_mut(&mut self) -> &mut ConcurrentState {
match self {
ComponentTaskState::Concurrent(state) => state,
ComponentTaskState::NotConcurrent(_) => {
panic!("expected concurrent state to be present")
}
}
}
#[cfg(feature = "component-model-async")]
fn is_concurrent(&self) -> bool {
match self {
ComponentTaskState::Concurrent(_) => true,
ComponentTaskState::NotConcurrent(_) => false,
}
}
}
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+383
View File
@@ -0,0 +1,383 @@
use crate::hash_map::HashMap;
use crate::prelude::*;
use crate::{
AsContextMut, FrameInfo, Global, HeapTopType, Instance, Memory, Module, StoreContextMut, Val,
ValType, WasmBacktrace, store::StoreOpaque,
};
use std::fmt;
/// Representation of a core dump of a WebAssembly module
///
/// When the Config::coredump_on_trap option is enabled this structure is
/// attached to the [`Error`](crate::Error) returned from many Wasmtime functions
/// that execute WebAssembly such as [`Instance::new`] or [`Func::call`]. This
/// can be acquired with the [`Error::downcast`](crate::Error::downcast) family
/// of methods to programmatically inspect the coredump. Otherwise since it's
/// part of the error returned this will get printed along with the rest of the
/// error when the error is logged.
///
/// Note that some state, such as Wasm locals or values on the operand stack,
/// may be optimized away by the compiler or otherwise not recovered in the
/// coredump.
///
/// Capturing of wasm coredumps can be configured through the
/// [`Config::coredump_on_trap`][crate::Config::coredump_on_trap] method.
///
/// For more information about errors in wasmtime see the documentation of the
/// [`Trap`][crate::Trap] type.
///
/// [`Func::call`]: crate::Func::call
/// [`Instance::new`]: crate::Instance::new
pub struct WasmCoreDump {
name: String,
modules: Vec<Module>,
instances: Vec<Instance>,
memories: Vec<Memory>,
globals: Vec<Global>,
backtrace: WasmBacktrace,
}
impl WasmCoreDump {
pub(crate) fn new(store: &mut StoreOpaque, backtrace: WasmBacktrace) -> WasmCoreDump {
let modules = store
.modules()
.all_modules()
.map(|(_, m)| m.clone())
.collect::<Vec<_>>();
let instances: Vec<Instance> = store.all_instances().collect();
let store_memories: Vec<Memory> =
store.all_memories().filter_map(|m| m.unshared()).collect();
let mut store_globals: Vec<Global> = vec![];
store.for_each_global(|_store, global| store_globals.push(global));
WasmCoreDump {
name: String::from("store_name"),
modules,
instances,
memories: store_memories,
globals: store_globals,
backtrace,
}
}
/// The stack frames for this core dump.
///
/// Frames appear in callee to caller order, that is youngest to oldest
/// frames.
pub fn frames(&self) -> &[FrameInfo] {
self.backtrace.frames()
}
/// All modules instantiated inside the store when the core dump was
/// created.
pub fn modules(&self) -> &[Module] {
self.modules.as_ref()
}
/// All instances within the store when the core dump was created.
pub fn instances(&self) -> &[Instance] {
self.instances.as_ref()
}
/// All globals, instance- or host-defined, within the store when the core
/// dump was created.
pub fn globals(&self) -> &[Global] {
self.globals.as_ref()
}
/// All memories, instance- or host-defined, within the store when the core
/// dump was created.
pub fn memories(&self) -> &[Memory] {
self.memories.as_ref()
}
/// Serialize this core dump into [the standard core dump binary
/// format][spec].
///
/// The `name` parameter may be a file path, URL, or arbitrary name for the
/// "main" Wasm service or executable that was running in this store.
///
/// Once serialized, you can write this core dump to disk, send it over the
/// network, or pass it to other debugging tools that consume Wasm core
/// dumps.
///
/// [spec]: https://github.com/WebAssembly/tool-conventions/blob/main/Coredump.md
pub fn serialize(&self, mut store: impl AsContextMut, name: &str) -> Vec<u8> {
let store = store.as_context_mut();
self._serialize(store, name)
}
fn _serialize<T: 'static>(&self, mut store: StoreContextMut<'_, T>, name: &str) -> Vec<u8> {
let mut core_dump = wasm_encoder::Module::new();
core_dump.section(&wasm_encoder::CoreDumpSection::new(name));
// A map from each memory to its index in the core dump's memories
// section.
let mut memory_to_idx = HashMap::new();
let mut data = wasm_encoder::DataSection::new();
{
let mut memories = wasm_encoder::MemorySection::new();
for mem in self.memories() {
let memory_idx = memories.len();
memory_to_idx.insert(mem.hash_key(&store.0), memory_idx);
let ty = mem.ty(&store);
memories.memory(wasm_encoder::MemoryType {
minimum: mem.size(&store),
maximum: ty.maximum(),
memory64: ty.is_64(),
shared: ty.is_shared(),
page_size_log2: None,
});
// Attach the memory data, balancing number of data segments and
// binary size. We don't want to attach the whole memory in one
// big segment, since it likely contains a bunch of large runs
// of zeroes. But we can't encode the data without any potential
// runs of zeroes (i.e. including only non-zero data in our
// segments) because we can run up against the implementation
// limits for number of segments in a Wasm module this way. So
// to balance these conflicting desires, we break the memory up
// into reasonably-sized chunks and then trim runs of zeroes
// from the start and end of each chunk.
const CHUNK_SIZE: usize = 4096;
for (i, chunk) in mem.data(&store).chunks_exact(CHUNK_SIZE).enumerate() {
if let Some(start) = chunk.iter().position(|byte| *byte != 0) {
let end = chunk.iter().rposition(|byte| *byte != 0).unwrap() + 1;
let offset = i * CHUNK_SIZE + start;
let offset = if ty.is_64() {
let offset = u64::try_from(offset).unwrap();
wasm_encoder::ConstExpr::i64_const(offset as i64)
} else {
let offset = u32::try_from(offset).unwrap();
wasm_encoder::ConstExpr::i32_const(offset as i32)
};
data.active(memory_idx, &offset, chunk[start..end].iter().copied());
}
}
}
core_dump.section(&memories);
}
// A map from each global to its index in the core dump's globals
// section.
let mut global_to_idx = HashMap::new();
{
let mut globals = wasm_encoder::GlobalSection::new();
for g in self.globals() {
global_to_idx.insert(g.hash_key(&store.0), globals.len());
let ty = g.ty(&store);
let mutable = matches!(ty.mutability(), crate::Mutability::Var);
let val_type = match ty.content() {
ValType::I32 => wasm_encoder::ValType::I32,
ValType::I64 => wasm_encoder::ValType::I64,
ValType::F32 => wasm_encoder::ValType::F32,
ValType::F64 => wasm_encoder::ValType::F64,
ValType::V128 => wasm_encoder::ValType::V128,
// We encode all references as null in the core dump, so
// choose the common super type of all the actual function
// reference types. This lets us avoid needing to figure out
// what a concrete type reference's index is in the local
// core dump index space.
ValType::Ref(r) => match r.heap_type().top() {
HeapTopType::Extern => wasm_encoder::ValType::EXTERNREF,
HeapTopType::Func => wasm_encoder::ValType::FUNCREF,
HeapTopType::Any => {
wasm_encoder::ValType::Ref(wasm_encoder::RefType::ANYREF)
}
HeapTopType::Exn => {
wasm_encoder::ValType::Ref(wasm_encoder::RefType::EXNREF)
}
HeapTopType::Cont => {
wasm_encoder::ValType::Ref(wasm_encoder::RefType::new_abstract(
wasm_encoder::AbstractHeapType::Cont,
true,
false,
))
}
},
};
let init = match g.get(&mut store) {
Val::I32(x) => wasm_encoder::ConstExpr::i32_const(x),
Val::I64(x) => wasm_encoder::ConstExpr::i64_const(x),
Val::F32(x) => wasm_encoder::ConstExpr::f32_const(f32::from_bits(x).into()),
Val::F64(x) => wasm_encoder::ConstExpr::f64_const(f64::from_bits(x).into()),
Val::V128(x) => wasm_encoder::ConstExpr::v128_const(x.as_u128() as i128),
Val::FuncRef(_) => {
wasm_encoder::ConstExpr::ref_null(wasm_encoder::HeapType::FUNC)
}
Val::ExternRef(_) => {
wasm_encoder::ConstExpr::ref_null(wasm_encoder::HeapType::EXTERN)
}
Val::AnyRef(_) => {
wasm_encoder::ConstExpr::ref_null(wasm_encoder::HeapType::ANY)
}
Val::ExnRef(_) => {
wasm_encoder::ConstExpr::ref_null(wasm_encoder::HeapType::Abstract {
shared: false,
ty: wasm_encoder::AbstractHeapType::Exn,
})
}
Val::ContRef(_) => {
wasm_encoder::ConstExpr::ref_null(wasm_encoder::HeapType::Abstract {
shared: false,
ty: wasm_encoder::AbstractHeapType::Cont,
})
}
};
globals.global(
wasm_encoder::GlobalType {
val_type,
mutable,
shared: false,
},
&init,
);
}
core_dump.section(&globals);
}
core_dump.section(&data);
drop(data);
// A map from module id to its index within the core dump's modules
// section.
let mut module_to_index = HashMap::new();
{
let mut modules = wasm_encoder::CoreDumpModulesSection::new();
for module in self.modules() {
module_to_index.insert(module.id(), modules.len());
match module.name() {
Some(name) => modules.module(name),
None => modules.module(&format!("<anonymous-module-{}>", modules.len())),
};
}
core_dump.section(&modules);
}
// TODO: We can't currently recover instances from stack frames. We can
// recover module via the frame's PC, but if there are multiple
// instances of the same module, we don't know which instance the frame
// is associated with. Therefore, we do a best effort job: remember the
// last instance of each module and always choose that one. We record
// that information here.
let mut module_to_instance = HashMap::new();
{
let mut instances = wasm_encoder::CoreDumpInstancesSection::new();
for instance in self.instances() {
let module = instance.module(&store);
module_to_instance.insert(module.id(), instances.len());
let module_index = module_to_index[&module.id()];
// Core dumps are best-effort and may not capture every memory
// referenced by an instance. In particular, shared memories
// are intentionally omitted because their data cannot be
// safely read through `Memory`. Use an invalid index for any
// absent memory instead of panicking while serializing.
let memories = instance
.all_memories(store.0)
.filter_map(|(_, m)| m.unshared())
.map(|memory| {
memory_to_idx
.get(&memory.hash_key(&store.0))
.copied()
.unwrap_or(u32::MAX)
})
.collect::<Vec<_>>();
// Component adapter modules can import runtime-managed globals,
// such as component instance flags, whose definitions are not
// enumerated by `StoreOpaque::for_each_global`. These globals
// are visible through `Instance::all_globals` but absent from
// the dump's globals section, so use an invalid index rather
// than panicking while serializing.
let globals = instance
.all_globals(store.0)
.collect::<Vec<_>>()
.into_iter()
.map(|(_i, global)| {
global_to_idx
.get(&global.hash_key(&store.0))
.copied()
.unwrap_or(u32::MAX)
})
.collect::<Vec<_>>();
instances.instance(module_index, memories, globals);
}
core_dump.section(&instances);
}
{
let thread_name = "main";
let mut stack = wasm_encoder::CoreDumpStackSection::new(thread_name);
for frame in self.frames() {
// This isn't necessarily the right instance if there are
// multiple instances of the same module. See comment above
// `module_to_instance` for details.
let instance = module_to_instance[&frame.module().id()];
let func = frame.func_index();
let offset = frame
.func_offset()
.and_then(|o| u32::try_from(o).ok())
.unwrap_or(0);
// We can't currently recover locals and the operand stack. We
// should eventually be able to do that with Winch though.
let locals = [];
let operand_stack = [];
stack.frame(instance, func, offset, locals, operand_stack);
}
core_dump.section(&stack);
}
core_dump.finish()
}
}
impl fmt::Display for WasmCoreDump {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "wasm coredump generated while executing {}:", self.name)?;
writeln!(f, "modules:")?;
for module in self.modules.iter() {
writeln!(f, " {}", module.name().unwrap_or("<module>"))?;
}
writeln!(f, "instances:")?;
for instance in self.instances.iter() {
writeln!(f, " {instance:?}")?;
}
writeln!(f, "memories:")?;
for memory in self.memories.iter() {
writeln!(f, " {memory:?}")?;
}
writeln!(f, "globals:")?;
for global in self.globals.iter() {
writeln!(f, " {global:?}")?;
}
writeln!(f, "backtrace:")?;
write!(f, "{}", self.backtrace)?;
Ok(())
}
}
impl fmt::Debug for WasmCoreDump {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "<wasm core dump>")
}
}
File diff suppressed because it is too large. Load diff
+54
View File
@@ -0,0 +1,54 @@
//! Types for the public API around exceptions.
//!
//! To allow host code to interact with exceptions, Wasmtime provides
//! two basic areas of API:
//!
//! - The [`crate::ExnRef`] type and associated types allow the host
//! to create exception objects. In the Wasm execution model, every
//! thrown exception is a unique instance of an exception object,
//! which carries a reference to the associated tag and any payload
//! values specified by the exception's signature.
//!
//! - The [`crate::Store::throw`] method to throw an exception, and
//! associated methods to take ([`crate::Store::take_exception`]) or
//! peek at ([`crate::Store::peek_exception`]) a thrown exception,
//! along with the `Error` type [`ThrownException`] that indicates
//! an exception is being thrown. This API allows access to a
//! "pending exception" slot on the `Store` which roots an exception
//! object and allows it to be propagated through Wasm and hostcall
//! layers. If Wasm code throws an uncaught exception, it will be
//! set as the pending exception and the call into Wasm will return
//! an `Err(ThrownException.into())`; if a hostcall wishes to throw
//! an exception to be caught by Wasm (or the outer call into Wasm
//! by the host), it can call `Store::throw` and return the
//! associated error.
/// An error type that represents that a pending WebAssembly exception
/// is set on the associated `Store`.
///
/// When used as an error type and returned from a Wasm-to-host call,
/// or host-to-Wasm call, it indicates that the caller should either
/// continue propagating the error upward, or take and handle the
/// exception using [`Store::take_pending_exception`].
///
/// [`Store::take_pending_exception`]: crate::Store::take_pending_exception
///
/// Wasmtime uses an error type *without* payload, and stores the
/// exception itself on the store, to maintain proper GC rooting;
/// otherwise, it is difficult to get exception propagation up the
/// stack right in the presence of nested handle scopes. A pending
/// exception on the store is safely rooted as long as it is stored
/// there.
#[derive(Debug)]
pub struct ThrownException;
/// We need to implement Error for `ThrownException` so it can be boxed up into
/// a `wasmtime::Error`.
impl core::error::Error for ThrownException {}
/// `Error` requires `Display`.
impl core::fmt::Display for ThrownException {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
write!(f, "thrown Wasm exception")
}
}
+269
View File
@@ -0,0 +1,269 @@
use crate::store::StoreOpaque;
use crate::{AsContext, Engine, ExternType, Func, Memory, SharedMemory};
mod global;
mod table;
mod tag;
pub use global::Global;
pub use table::Table;
pub use tag::Tag;
// Externals
/// An external item to a WebAssembly module, or a list of what can possibly be
/// exported from a wasm module.
///
/// This is both returned from [`Instance::exports`](crate::Instance::exports)
/// as well as required by [`Instance::new`](crate::Instance::new). In other
/// words, this is the type of extracted values from an instantiated module, and
/// it's also used to provide imported values when instantiating a module.
#[derive(Clone, Debug)]
pub enum Extern {
/// A WebAssembly `func` which can be called.
Func(Func),
/// A WebAssembly `global` which acts like a `Cell<T>` of sorts, supporting
/// `get` and `set` operations.
Global(Global),
/// A WebAssembly `table` which is an array of `Val` reference types.
Table(Table),
/// A WebAssembly linear memory.
Memory(Memory),
/// A WebAssembly shared memory; these are handled separately from
/// [`Memory`].
SharedMemory(SharedMemory),
/// A WebAssembly exception or control tag which can be referenced
/// when raising an exception or stack switching.
Tag(Tag),
}
impl Extern {
/// Returns the underlying `Func`, if this external is a function.
///
/// Returns `None` if this is not a function.
#[inline]
pub fn into_func(self) -> Option<Func> {
match self {
Extern::Func(func) => Some(func),
_ => None,
}
}
/// Returns the underlying `Global`, if this external is a global.
///
/// Returns `None` if this is not a global.
#[inline]
pub fn into_global(self) -> Option<Global> {
match self {
Extern::Global(global) => Some(global),
_ => None,
}
}
/// Returns the underlying `Table`, if this external is a table.
///
/// Returns `None` if this is not a table.
#[inline]
pub fn into_table(self) -> Option<Table> {
match self {
Extern::Table(table) => Some(table),
_ => None,
}
}
/// Returns the underlying `Memory`, if this external is a memory.
///
/// Returns `None` if this is not a memory.
#[inline]
pub fn into_memory(self) -> Option<Memory> {
match self {
Extern::Memory(memory) => Some(memory),
_ => None,
}
}
/// Returns the underlying `SharedMemory`, if this external is a shared
/// memory.
///
/// Returns `None` if this is not a shared memory.
#[inline]
pub fn into_shared_memory(self) -> Option<SharedMemory> {
match self {
Extern::SharedMemory(memory) => Some(memory),
_ => None,
}
}
/// Returns the underlying `Tag`, if this external is a tag.
///
/// Returns `None` if this is not a tag.
#[inline]
pub fn into_tag(self) -> Option<Tag> {
match self {
Extern::Tag(tag) => Some(tag),
_ => None,
}
}
/// Returns the type associated with this `Extern`.
///
/// The `store` argument provided must own this `Extern` and is used to look
/// up type information.
///
/// # Panics
///
/// Panics if this item does not belong to the `store` provided.
pub fn ty(&self, store: impl AsContext) -> ExternType {
let store = store.as_context();
match self {
Extern::Func(ft) => ExternType::Func(ft.ty(store)),
Extern::Memory(ft) => ExternType::Memory(ft.ty(store)),
Extern::SharedMemory(ft) => ExternType::Memory(ft.ty()),
Extern::Table(tt) => ExternType::Table(tt.ty(store)),
Extern::Global(gt) => ExternType::Global(gt.ty(store)),
Extern::Tag(tt) => ExternType::Tag(tt.ty(store)),
}
}
pub(crate) fn from_wasmtime_export(
wasmtime_export: crate::runtime::vm::Export,
engine: &Engine,
) -> Extern {
match wasmtime_export {
crate::runtime::vm::Export::Function(f) => Extern::Func(f),
crate::runtime::vm::Export::Memory(m) => Extern::Memory(m),
crate::runtime::vm::Export::SharedMemory(m, _) => {
Extern::SharedMemory(crate::SharedMemory::from_raw(m, engine.clone()))
}
crate::runtime::vm::Export::Global(g) => Extern::Global(g),
crate::runtime::vm::Export::Table(t) => Extern::Table(t),
crate::runtime::vm::Export::Tag(t) => Extern::Tag(t),
}
}
pub(crate) fn comes_from_same_store(&self, store: &StoreOpaque) -> bool {
match self {
Extern::Func(f) => f.comes_from_same_store(store),
Extern::Global(g) => g.comes_from_same_store(store),
Extern::Memory(m) => m.comes_from_same_store(store),
Extern::SharedMemory(m) => Engine::same(m.engine(), store.engine()),
Extern::Table(t) => t.comes_from_same_store(store),
Extern::Tag(t) => t.comes_from_same_store(store),
}
}
}
impl From<Func> for Extern {
fn from(r: Func) -> Self {
Extern::Func(r)
}
}
impl From<Global> for Extern {
fn from(r: Global) -> Self {
Extern::Global(r)
}
}
impl From<Memory> for Extern {
fn from(r: Memory) -> Self {
Extern::Memory(r)
}
}
impl From<SharedMemory> for Extern {
fn from(r: SharedMemory) -> Self {
Extern::SharedMemory(r)
}
}
impl From<Table> for Extern {
fn from(r: Table) -> Self {
Extern::Table(r)
}
}
impl From<Tag> for Extern {
fn from(t: Tag) -> Self {
Extern::Tag(t)
}
}
// Exports
/// An exported WebAssembly value.
///
/// This type is primarily accessed from the
/// [`Instance::exports`](crate::Instance::exports) accessor and describes what
/// names and items are exported from a wasm instance.
#[derive(Clone)]
pub struct Export<'instance> {
/// The name of the export.
name: &'instance str,
/// The definition of the export.
definition: Extern,
}
impl<'instance> Export<'instance> {
/// Creates a new export which is exported with the given `name` and has the
/// given `definition`.
pub(crate) fn new(name: &'instance str, definition: Extern) -> Export<'instance> {
Export { name, definition }
}
/// Returns the name by which this export is known.
pub fn name(&self) -> &'instance str {
self.name
}
/// Return the `ExternType` of this export.
///
/// # Panics
///
/// Panics if `store` does not own this `Extern`.
pub fn ty(&self, store: impl AsContext) -> ExternType {
self.definition.ty(store)
}
/// Consume this `Export` and return the contained `Extern`.
pub fn into_extern(self) -> Extern {
self.definition
}
/// Consume this `Export` and return the contained `Func`, if it's a function,
/// or `None` otherwise.
pub fn into_func(self) -> Option<Func> {
self.definition.into_func()
}
/// Consume this `Export` and return the contained `Table`, if it's a table,
/// or `None` otherwise.
pub fn into_table(self) -> Option<Table> {
self.definition.into_table()
}
/// Consume this `Export` and return the contained `Memory`, if it's a memory,
/// or `None` otherwise.
pub fn into_memory(self) -> Option<Memory> {
self.definition.into_memory()
}
/// Consume this `Export` and return the contained `SharedMemory`, if it's
/// a shared memory, or `None` otherwise.
pub fn into_shared_memory(self) -> Option<SharedMemory> {
self.definition.into_shared_memory()
}
/// Consume this `Export` and return the contained `Global`, if it's a global,
/// or `None` otherwise.
pub fn into_global(self) -> Option<Global> {
self.definition.into_global()
}
/// Consume this `Export` and return the contained `Tag`, if it's a tag,
/// or `None` otherwise.
pub fn into_tag(self) -> Option<Tag> {
self.definition.into_tag()
}
}
@@ -0,0 +1,489 @@
use crate::prelude::*;
use crate::runtime::vm::{self, VMGlobalDefinition, VMGlobalKind, VMOpaqueContext};
use crate::{
AnyRef, AsContext, AsContextMut, ExnRef, ExternRef, Func, GlobalType, HeapType, Mutability,
Ref, Val, ValType,
store::{AutoAssertNoGc, InstanceId, StoreId, StoreInstanceId, StoreOpaque},
trampoline::generate_global_export,
};
use core::ptr;
use core::ptr::NonNull;
use wasmtime_environ::DefinedGlobalIndex;
/// A WebAssembly `global` value which can be read and written to.
///
/// A `global` in WebAssembly is sort of like a global variable within an
/// [`Instance`](crate::Instance). The `global.get` and `global.set`
/// instructions will modify and read global values in a wasm module. Globals
/// can either be imported or exported from wasm modules.
///
/// A [`Global`] "belongs" to the store that it was originally created within
/// (either via [`Global::new`] or via instantiating a
/// [`Module`](crate::Module)). Operations on a [`Global`] only work with the
/// store it belongs to, and if another store is passed in by accident then
/// methods will panic.
#[derive(Copy, Clone, Debug)]
#[repr(C)] // here for the C API
pub struct Global {
/// The store that this global belongs to.
store: StoreId,
/// Either `InstanceId` or `ComponentInstanceId` internals depending on
/// `kind` below.
instance: u32,
/// Which method of definition was used when creating this global.
kind: VMGlobalKind,
}
// Double-check that the C representation in `extern.h` matches our in-Rust
// representation here in terms of size/alignment/etc.
const _: () = {
#[repr(C)]
struct C(u64, u32, u32, u32);
assert!(core::mem::size_of::<C>() == core::mem::size_of::<Global>());
assert!(core::mem::align_of::<C>() == core::mem::align_of::<Global>());
assert!(core::mem::offset_of!(Global, store) == 0);
};
impl Global {
/// Creates a new WebAssembly `global` value with the provide type `ty` and
/// initial value `val`.
///
/// The `store` argument will be the owner of the [`Global`] returned. Using
/// the returned [`Global`] other items in the store may access this global.
/// For example this could be provided as an argument to
/// [`Instance::new`](crate::Instance::new) or
/// [`Linker::define`](crate::Linker::define).
///
/// # Errors
///
/// Returns an error if the `ty` provided does not match the type of the
/// value `val`, or if `val` comes from a different store than `store`.
///
/// Returns an error if the content type of `ty` was not created with the
/// same [`Engine`](crate::Engine) as `store`.
///
/// This function will return an [`OutOfMemory`][crate::OutOfMemory] error when
/// memory allocation fails. See the `OutOfMemory` type's documentation for
/// details on Wasmtime's out-of-memory handling.
///
/// # Examples
///
/// ```
/// # use wasmtime::*;
/// # fn main() -> Result<()> {
/// let engine = Engine::default();
/// let mut store = Store::new(&engine, ());
///
/// let ty = GlobalType::new(ValType::I32, Mutability::Const);
/// let i32_const = Global::new(&mut store, ty, 1i32.into())?;
/// let ty = GlobalType::new(ValType::F64, Mutability::Var);
/// let f64_mut = Global::new(&mut store, ty, 2.0f64.into())?;
///
/// let module = Module::new(
/// &engine,
/// "(module
/// (global (import \"\" \"i32-const\") i32)
/// (global (import \"\" \"f64-mut\") (mut f64))
/// )"
/// )?;
///
/// let mut linker = Linker::new(&engine);
/// linker.define(&store, "", "i32-const", i32_const)?;
/// linker.define(&store, "", "f64-mut", f64_mut)?;
///
/// let instance = linker.instantiate(&mut store, &module)?;
/// // ...
/// # Ok(())
/// # }
/// ```
pub fn new(mut store: impl AsContextMut, ty: GlobalType, val: Val) -> Result<Global> {
Global::_new(store.as_context_mut().0, ty, val)
}
fn _new(store: &mut StoreOpaque, ty: GlobalType, val: Val) -> Result<Global> {
val.ensure_matches_ty(store, ty.content()).context(
"type mismatch: initial value provided does not match the type of this global",
)?;
generate_global_export(store, ty, val)
}
pub(crate) fn new_host(store: &StoreOpaque, index: DefinedGlobalIndex) -> Global {
Global {
store: store.id(),
instance: 0,
kind: VMGlobalKind::Host(index),
}
}
pub(crate) fn new_instance(
store: &StoreOpaque,
instance: InstanceId,
index: DefinedGlobalIndex,
) -> Global {
Global {
store: store.id(),
instance: instance.as_u32(),
kind: VMGlobalKind::Instance(index),
}
}
/// Returns the underlying type of this `global`.
///
/// # Panics
///
/// Panics if `store` does not own this global.
pub fn ty(&self, store: impl AsContext) -> GlobalType {
self._ty(store.as_context().0)
}
pub(crate) fn _ty(&self, store: &StoreOpaque) -> GlobalType {
GlobalType::from_wasmtime_global(store.engine(), self.wasmtime_ty(store))
}
/// Returns the current [`Val`] of this global.
///
/// # Panics
///
/// Panics if `store` does not own this global.
pub fn get(&self, mut store: impl AsContextMut) -> Val {
let mut store = AutoAssertNoGc::new(store.as_context_mut().0);
self._get(&mut store)
}
pub(crate) fn _get(&self, store: &mut AutoAssertNoGc<'_>) -> Val {
unsafe {
let definition = self.definition(store).as_ref();
match self._ty(&store).content() {
ValType::I32 => Val::from(*definition.as_i32()),
ValType::I64 => Val::from(*definition.as_i64()),
ValType::F32 => Val::F32(*definition.as_u32()),
ValType::F64 => Val::F64(*definition.as_u64()),
ValType::V128 => Val::V128(definition.get_u128().into()),
ValType::Ref(ref_ty) => {
let reference: Ref = match ref_ty.heap_type() {
HeapType::Func | HeapType::ConcreteFunc(_) => {
Func::_from_raw(store, definition.as_func_ref().cast()).into()
}
HeapType::NoFunc => Ref::Func(None),
HeapType::Extern => Ref::Extern(definition.as_gc_ref().map(|r| {
let r = store.clone_gc_ref(r);
ExternRef::from_cloned_gc_ref(store, r)
})),
HeapType::NoCont | HeapType::ConcreteCont(_) | HeapType::Cont => {
// TODO(#10248) Required to support stack switching in the embedder API.
unimplemented!()
}
HeapType::NoExtern => Ref::Extern(None),
HeapType::Exn | HeapType::ConcreteExn(_) => definition
.as_gc_ref()
.map(|r| {
let r = store.clone_gc_ref(r);
ExnRef::from_cloned_gc_ref(store, r)
})
.into(),
HeapType::Any
| HeapType::Eq
| HeapType::I31
| HeapType::Struct
| HeapType::ConcreteStruct(_)
| HeapType::Array
| HeapType::ConcreteArray(_) => definition
.as_gc_ref()
.map(|r| {
let r = store.clone_gc_ref(r);
AnyRef::from_cloned_gc_ref(store, r)
})
.into(),
HeapType::NoExn => Ref::Exn(None),
HeapType::None => Ref::Any(None),
};
debug_assert!(
ref_ty.is_nullable() || !reference.is_null(),
"if the type is non-nullable, we better have a non-null reference"
);
reference.into()
}
}
}
}
/// Attempts to set the current value of this global to [`Val`].
///
/// # Errors
///
/// Returns an error if this global has a different type than `Val`, if
/// it's not a mutable global, or if `val` comes from a different store than
/// the one provided.
///
/// This function will return an [`OutOfMemory`][crate::OutOfMemory] error when
/// memory allocation fails. See the `OutOfMemory` type's documentation for
/// details on Wasmtime's out-of-memory handling.
///
/// # Panics
///
/// Panics if `store` does not own this global.
pub fn set(&self, mut store: impl AsContextMut, val: Val) -> Result<()> {
self._set(store.as_context_mut().0, val)
}
pub(crate) fn _set(&self, store: &mut StoreOpaque, val: Val) -> Result<()> {
let global_ty = self._ty(&store);
if global_ty.mutability() != Mutability::Var {
bail!("immutable global cannot be set");
}
val.ensure_matches_ty(&store, global_ty.content())
.context("type mismatch: attempt to set global to value of wrong type")?;
// SAFETY: mutability and a type-check above makes this safe to perform.
unsafe { self.set_unchecked(store, &val) }
}
/// Sets this global to `val`.
///
/// # Safety
///
/// This function requires that `val` is of the correct type for this
/// global. Furthermore this requires that the global is mutable or this is
/// the first time the global is initialized.
pub(crate) unsafe fn set_unchecked(&self, store: &mut StoreOpaque, val: &Val) -> Result<()> {
let mut store = AutoAssertNoGc::new(store);
unsafe {
let definition = self.definition(&store).as_mut();
match val {
Val::I32(i) => *definition.as_i32_mut() = *i,
Val::I64(i) => *definition.as_i64_mut() = *i,
Val::F32(f) => *definition.as_u32_mut() = *f,
Val::F64(f) => *definition.as_u64_mut() = *f,
Val::V128(i) => definition.set_u128((*i).into()),
Val::FuncRef(f) => {
*definition.as_func_ref_mut() =
f.map_or(ptr::null_mut(), |f| f.vm_func_ref(&store).as_ptr().cast());
}
Val::ExternRef(e) => {
let new = match e {
None => None,
Some(e) => Some(e.try_gc_ref(&store)?.unchecked_copy()),
};
let new = new.as_ref();
definition.write_gc_ref(&mut store, new)?;
}
Val::AnyRef(a) => {
let new = match a {
None => None,
Some(a) => Some(a.try_gc_ref(&store)?.unchecked_copy()),
};
let new = new.as_ref();
definition.write_gc_ref(&mut store, new)?;
}
Val::ExnRef(e) => {
let new = match e {
None => None,
Some(e) => Some(e.try_gc_ref(&store)?.unchecked_copy()),
};
let new = new.as_ref();
definition.write_gc_ref(&mut store, new)?;
}
Val::ContRef(None) => {
// Allow null continuation references for globals - these are just placeholders
definition.write_gc_ref(&mut store, None)?;
}
Val::ContRef(Some(_)) => {
// TODO(#10248): Implement non-null global continuation reference handling
return Err(crate::format_err!(
"setting non-null continuation references in globals not yet supported"
));
}
}
}
Ok(())
}
#[cfg(feature = "gc")]
pub(crate) fn trace_root(&self, store: &mut StoreOpaque, gc_roots_list: &mut vm::GcRootsList) {
if let Some(ref_ty) = self._ty(store).content().as_ref() {
if !ref_ty.is_vmgcref_type_and_points_to_object() {
return;
}
if let Some(gc_ref) = unsafe { self.definition(store).as_mut().as_gc_ref_mut() } {
unsafe {
gc_roots_list.add_vmgcref_root(gc_ref.into(), "Wasm global");
}
}
}
}
pub(crate) fn from_host(store: StoreId, index: DefinedGlobalIndex) -> Global {
Global {
store,
instance: 0,
kind: VMGlobalKind::Host(index),
}
}
pub(crate) fn from_core(instance: StoreInstanceId, index: DefinedGlobalIndex) -> Global {
Global {
store: instance.store_id(),
instance: instance.instance().as_u32(),
kind: VMGlobalKind::Instance(index),
}
}
#[cfg(feature = "component-model")]
pub(crate) fn from_component_flags(
instance: crate::component::store::StoreComponentInstanceId,
index: wasmtime_environ::component::RuntimeComponentInstanceIndex,
) -> Global {
Global {
store: instance.store_id(),
instance: instance.instance().as_u32(),
kind: VMGlobalKind::ComponentFlags(index),
}
}
pub(crate) fn wasmtime_ty<'a>(&self, store: &'a StoreOpaque) -> &'a wasmtime_environ::Global {
self.store.assert_belongs_to(store.id());
match self.kind {
VMGlobalKind::Instance(index) => {
let instance = InstanceId::from_u32(self.instance);
let module = store.instance(instance).env_module();
let index = module.global_index(index);
&module.globals[index]
}
VMGlobalKind::Host(index) => unsafe { &store.host_globals()[index].get().as_ref().ty },
#[cfg(feature = "component-model")]
VMGlobalKind::ComponentFlags(_) => {
const TY: wasmtime_environ::Global = wasmtime_environ::Global {
mutability: true,
wasm_ty: wasmtime_environ::WasmValType::I32,
};
&TY
}
}
}
pub(crate) fn vmimport(&self, store: &StoreOpaque) -> vm::VMGlobalImport {
let vmctx = match self.kind {
VMGlobalKind::Instance(_) => {
let instance = InstanceId::from_u32(self.instance);
Some(VMOpaqueContext::from_vmcontext(store.instance(instance).vmctx()).into())
}
VMGlobalKind::Host(_) => None,
#[cfg(feature = "component-model")]
VMGlobalKind::ComponentFlags(_) => {
let instance = crate::component::ComponentInstanceId::from_u32(self.instance);
Some(
VMOpaqueContext::from_vmcomponent(store.component_instance(instance).vmctx())
.into(),
)
}
};
vm::VMGlobalImport {
from: self.definition(store).into(),
vmctx,
kind: self.kind,
}
}
pub(crate) fn comes_from_same_store(&self, store: &StoreOpaque) -> bool {
store.id() == self.store
}
/// Returns a stable identifier for this global within its store.
///
/// This allows distinguishing globals when introspecting them
/// e.g. via debug APIs.
#[cfg(feature = "debug")]
pub fn debug_index_in_store(&self) -> u64 {
match self.kind {
VMGlobalKind::Instance(idx) => u64::from(self.instance) << 32 | u64::from(idx.as_u32()),
VMGlobalKind::Host(idx) => u64::from(u32::MAX) << 32 | u64::from(idx.as_u32()),
#[cfg(feature = "component-model")]
VMGlobalKind::ComponentFlags(idx) => {
u64::from(self.instance) << 32 | u64::from(idx.as_u32())
}
}
}
/// Get a stable hash key for this global.
///
/// Even if the same underlying global definition is added to the
/// `StoreData` multiple times and becomes multiple `wasmtime::Global`s,
/// this hash key will be consistent across all of these globals.
#[cfg(feature = "coredump")]
pub(crate) fn hash_key(&self, store: &StoreOpaque) -> impl core::hash::Hash + Eq + use<> {
self.definition(store).as_ptr().addr()
}
fn definition(&self, store: &StoreOpaque) -> NonNull<VMGlobalDefinition> {
self.store.assert_belongs_to(store.id());
match self.kind {
VMGlobalKind::Instance(index) => {
let instance = InstanceId::from_u32(self.instance);
store.instance(instance).global_ptr(index)
}
VMGlobalKind::Host(index) => unsafe {
NonNull::from(&mut store.host_globals()[index].get().as_mut().global)
},
#[cfg(feature = "component-model")]
VMGlobalKind::ComponentFlags(index) => {
let instance = crate::component::ComponentInstanceId::from_u32(self.instance);
store
.component_instance(instance)
.instance_flags(index)
.as_raw()
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{Instance, Module, Store};
#[test]
fn hash_key_is_stable_across_duplicate_store_data_entries() -> Result<()> {
let mut store = Store::<()>::default();
let module = Module::new(
store.engine(),
r#"
(module
(global (export "g") (mut i32) (i32.const 0))
)
"#,
)?;
let instance = Instance::new(&mut store, &module, &[])?;
// Each time we `get_global`, we call `Global::from_wasmtime` which adds
// a new entry to `StoreData`, so `g1` and `g2` will have different
// indices into `StoreData`.
let g1 = instance.get_global(&mut store, "g").unwrap();
let g2 = instance.get_global(&mut store, "g").unwrap();
// That said, they really point to the same global.
assert_eq!(g1.get(&mut store).unwrap_i32(), 0);
assert_eq!(g2.get(&mut store).unwrap_i32(), 0);
g1.set(&mut store, Val::I32(42))?;
assert_eq!(g1.get(&mut store).unwrap_i32(), 42);
assert_eq!(g2.get(&mut store).unwrap_i32(), 42);
// And therefore their hash keys are the same.
assert!(g1.hash_key(&store.as_context().0) == g2.hash_key(&store.as_context().0));
// But the hash keys are different from different globals.
let instance2 = Instance::new(&mut store, &module, &[])?;
let g3 = instance2.get_global(&mut store, "g").unwrap();
assert!(g1.hash_key(&store.as_context().0) != g3.hash_key(&store.as_context().0));
Ok(())
}
}
@@ -0,0 +1,695 @@
use crate::prelude::*;
use crate::runtime::vm::{self, GcStore, TableElementType, VMFuncRef, VMGcRef, VMStore};
use crate::store::{AutoAssertNoGc, StoreInstanceId, StoreOpaque, StoreResourceLimiter};
use crate::trampoline::generate_table_export;
use crate::{
AnyRef, AsContext, AsContextMut, ExnRef, ExternRef, Func, HeapTopType, Ref, RefType,
StoreContextMut, TableType, Trap,
};
use core::iter;
use core::ptr::NonNull;
use wasmtime_environ::DefinedTableIndex;
/// A WebAssembly `table`, or an array of values.
///
/// Like [`Memory`][crate::Memory] a table is an indexed array of values, but
/// unlike [`Memory`][crate::Memory] it's an array of WebAssembly reference type
/// values rather than bytes. One of the most common usages of a table is a
/// function table for wasm modules (a `funcref` table), where each element has
/// the `ValType::FuncRef` type.
///
/// A [`Table`] "belongs" to the store that it was originally created within
/// (either via [`Table::new`] or via instantiating a
/// [`Module`](crate::Module)). Operations on a [`Table`] only work with the
/// store it belongs to, and if another store is passed in by accident then
/// methods will panic.
#[derive(Copy, Clone, Debug)]
#[repr(C)] // here for the C API
pub struct Table {
instance: StoreInstanceId,
index: DefinedTableIndex,
}
// Double-check that the C representation in `extern.h` matches our in-Rust
// representation here in terms of size/alignment/etc.
const _: () = {
#[repr(C)]
struct Tmp(u64, u32);
#[repr(C)]
struct C(Tmp, u32);
assert!(core::mem::size_of::<C>() == core::mem::size_of::<Table>());
assert!(core::mem::align_of::<C>() == core::mem::align_of::<Table>());
assert!(core::mem::offset_of!(Table, instance) == 0);
};
impl Table {
/// Creates a new [`Table`] with the given parameters.
///
/// * `store` - the owner of the resulting [`Table`]
/// * `ty` - the type of this table, containing both the element type as
/// well as the initial size and maximum size, if any.
/// * `init` - the initial value to fill all table entries with, if the
/// table starts with an initial size.
///
/// # Errors
///
/// Returns an error if `init` does not match the element type of the table,
/// or if `init` does not belong to the `store` provided.
///
/// Returns an error if the element type of `ty` was not created with the
/// same [`Engine`](crate::Engine) as `store`.
///
/// This function will also return an error when used with a
/// [`Store`](`crate::Store`) which has a
/// [`ResourceLimiterAsync`](`crate::ResourceLimiterAsync`) (see also:
/// [`Store::limiter_async`](`crate::Store::limiter_async`). When using an
/// async resource limiter, use [`Table::new_async`] instead.
///
/// This function will return an [`OutOfMemory`][crate::OutOfMemory] error when
/// memory allocation fails. See the `OutOfMemory` type's documentation for
/// details on Wasmtime's out-of-memory handling.
///
/// # Examples
///
/// ```
/// # use wasmtime::*;
/// # fn main() -> Result<()> {
/// let engine = Engine::default();
/// let mut store = Store::new(&engine, ());
///
/// let ty = TableType::new(RefType::FUNCREF, 2, None);
/// let table = Table::new(&mut store, ty, Ref::Func(None))?;
///
/// let module = Module::new(
/// &engine,
/// "(module
/// (table (import \"\" \"\") 2 funcref)
/// (func $f (result i32)
/// i32.const 10)
/// (elem (i32.const 0) $f)
/// )"
/// )?;
///
/// let instance = Instance::new(&mut store, &module, &[table.into()])?;
/// // ...
/// # Ok(())
/// # }
/// ```
pub fn new(mut store: impl AsContextMut, ty: TableType, init: Ref) -> Result<Table> {
let (mut limiter, store) = store
.as_context_mut()
.0
.validate_sync_resource_limiter_and_store_opaque()?;
vm::assert_ready(Table::_new(store, limiter.as_mut(), ty, init))
}
/// Async variant of [`Table::new`].
///
/// You must use this variant with [`Store`](`crate::Store`)s which have a
/// [`ResourceLimiterAsync`](`crate::ResourceLimiterAsync`).
///
/// # Errors
///
/// Returns an error if the element type of `ty` was not created with the
/// same [`Engine`](crate::Engine) as `store`.
///
/// This function will return an [`OutOfMemory`][crate::OutOfMemory] error when
/// memory allocation fails. See the `OutOfMemory` type's documentation for
/// details on Wasmtime's out-of-memory handling.
#[cfg(feature = "async")]
pub async fn new_async(
mut store: impl AsContextMut,
ty: TableType,
init: Ref,
) -> Result<Table> {
let (mut limiter, store) = store.as_context_mut().0.resource_limiter_and_store_opaque();
Table::_new(store, limiter.as_mut(), ty, init).await
}
async fn _new(
store: &mut StoreOpaque,
limiter: Option<&mut StoreResourceLimiter<'_>>,
ty: TableType,
init: Ref,
) -> Result<Table> {
init.ensure_matches_ty(store, ty.element())
.context("type mismatch: value does not match table element type")?;
let table = generate_table_export(store, limiter, &ty).await?;
// Tables are always allocated as all zeroes, so skip the fill below if
// the value being inserted is all zeros.
//
// Note that this is applicable for funcref tables when
// `tunables.table_lazy_init` is enabled as well. In that situation the
// null image means "go check the instance" and the instance created by
// `generate_table_export` says everything is null.
//
// In all cases a zero-initialized table will reflect all-null elements
// at runtime.
if init.is_zero_pattern() {
if cfg!(debug_assertions) {
let (table, _) = table.wasmtime_table(store, None);
table.debug_assert_all_zero();
}
return Ok(table);
}
table._fill(store, 0, init, ty.minimum())?;
Ok(table)
}
/// Returns the underlying type of this table, including its element type as
/// well as the maximum/minimum lower bounds.
///
/// # Panics
///
/// Panics if `store` does not own this table.
pub fn ty(&self, store: impl AsContext) -> TableType {
self.ty_(store.as_context().0)
}
pub(crate) fn ty_(&self, store: &StoreOpaque) -> TableType {
TableType::from_wasmtime_table(store.engine(), self.wasmtime_ty(store))
}
/// Returns the `vm::Table` within `store` as well as the optional
/// `GcStore` in use within `store`.
///
/// # Panics
///
/// Panics if this table does not belong to `store`.
fn wasmtime_table<'a>(
&self,
store: &'a mut StoreOpaque,
lazy_init_range: impl IntoIterator<Item = u64>,
) -> (&'a mut vm::Table, Option<&'a mut GcStore>) {
self.instance.assert_belongs_to(store.id());
let (store, registry, instance) =
store.optional_gc_store_and_registry_and_instance_mut(self.instance.instance());
(
instance.get_defined_table_with_lazy_init(registry, self.index, lazy_init_range),
store,
)
}
/// Returns the table element value at `index`.
///
/// Returns `None` if `index` is out of bounds.
///
/// # Panics
///
/// Panics if `store` does not own this table.
pub fn get(&self, mut store: impl AsContextMut, index: u64) -> Option<Ref> {
let mut store = AutoAssertNoGc::new(store.as_context_mut().0);
let (table, _gc_store) = self.wasmtime_table(&mut store, [index]);
match table.element_type() {
TableElementType::Func => {
let ptr = table.get_func(index).ok()?;
Some(
// SAFETY: `store` owns this table, so therefore it owns all
// functions within the table too.
ptr.map(|p| unsafe { Func::from_vm_func_ref(store.id(), p) })
.into(),
)
}
TableElementType::GcRef => {
let gc_ref = table
.get_gc_ref(index)
.ok()?
.map(|r| r.unchecked_copy())
.map(|r| store.clone_gc_ref(&r));
Some(match self.ty_(&store).element().heap_type().top() {
HeapTopType::Extern => {
Ref::Extern(gc_ref.map(|r| ExternRef::from_cloned_gc_ref(&mut store, r)))
}
HeapTopType::Any => {
Ref::Any(gc_ref.map(|r| AnyRef::from_cloned_gc_ref(&mut store, r)))
}
HeapTopType::Exn => {
Ref::Exn(gc_ref.map(|r| ExnRef::from_cloned_gc_ref(&mut store, r)))
}
HeapTopType::Func | HeapTopType::Cont => unreachable!("not GC refs"),
})
}
// TODO(#10248) Required to support stack switching in the embedder
// API.
TableElementType::Cont => panic!("unimplemented table for cont"),
}
}
/// Writes the `val` provided into `index` within this table.
///
/// # Errors
///
/// Returns an error if `index` is out of bounds, if `val` does not have
/// the right type to be stored in this table, or if `val` belongs to a
/// different store.
///
/// This function will return an [`OutOfMemory`][crate::OutOfMemory] error when
/// memory allocation fails. See the `OutOfMemory` type's documentation for
/// details on Wasmtime's out-of-memory handling.
///
/// # Panics
///
/// Panics if `store` does not own this table.
pub fn set(&self, mut store: impl AsContextMut, index: u64, val: Ref) -> Result<()> {
self.set_(store.as_context_mut().0, index, val)
}
pub(crate) fn set_(&self, store: &mut StoreOpaque, index: u64, val: Ref) -> Result<()> {
let ty = self.ty_(store);
match element_type(&ty) {
TableElementType::Func => {
let element = val.into_table_func(store, ty.element())?;
let (table, _gc_store) = self.wasmtime_table(store, iter::empty());
table.set_func(index, element)?;
}
TableElementType::GcRef => {
let mut store = AutoAssertNoGc::new(store);
let element = val.into_table_gc_ref(&mut store, ty.element())?;
// Note that `unchecked_copy` should be ok as we're under an
// `AutoAssertNoGc` which means that despite this not being
// rooted we don't have to worry about it going away.
let element = element.map(|r| r.unchecked_copy());
let (table, gc_store) = self.wasmtime_table(&mut store, iter::empty());
table.set_gc_ref(gc_store, index, element.as_ref())?;
}
// TODO(#10248) Required to support stack switching in the embedder
// API.
TableElementType::Cont => bail!("unimplemented table for cont"),
}
Ok(())
}
/// Returns the current size of this table.
///
/// # Panics
///
/// Panics if `store` does not own this table.
pub fn size(&self, store: impl AsContext) -> u64 {
self.size_(store.as_context().0)
}
pub(crate) fn size_(&self, store: &StoreOpaque) -> u64 {
// unwrap here should be ok because the runtime should always guarantee
// that we can fit the number of elements in a 64-bit integer.
u64::try_from(store[self.instance].table(self.index).current_elements).unwrap()
}
/// Grows the size of this table by `delta` more elements, initialization
/// all new elements to `init`.
///
/// Returns the previous size of this table if successful.
///
/// # Errors
///
/// Returns an error if the table cannot be grown by `delta`, for example
/// if it would cause the table to exceed its maximum size. Also returns an
/// error if `init` is not of the right type or if `init` does not belong to
/// `store`.
///
/// This function also returns an error when used with a
/// [`Store`](`crate::Store`) which has a
/// [`ResourceLimiterAsync`](`crate::ResourceLimiterAsync`) (see also:
/// [`Store::limiter_async`](`crate::Store::limiter_async`)). When using an
/// async resource limiter, use [`Table::grow_async`] instead.
///
/// This function will return an [`OutOfMemory`][crate::OutOfMemory] error when
/// memory allocation fails. See the `OutOfMemory` type's documentation for
/// details on Wasmtime's out-of-memory handling.
///
/// # Panics
///
/// Panics if `store` does not own this table.
pub fn grow(&self, mut store: impl AsContextMut, delta: u64, init: Ref) -> Result<u64> {
let store = store.as_context_mut();
store.0.validate_sync_resource_limiter_and_store_opaque()?;
vm::assert_ready(self._grow(store, delta, init))
}
async fn _grow<T>(&self, store: StoreContextMut<'_, T>, delta: u64, init: Ref) -> Result<u64> {
let store = store.0;
let (mut limiter, store) = store.resource_limiter_and_store_opaque();
let limiter = limiter.as_mut();
// First, type-check to make sure that `init` does indeed match this
// table's element type.
let ty = self.ty_(store);
init.ensure_matches_ty(store, ty.element())
.context("type mismatch: value does not match table element type")?;
// SAFETY: the requirement here is that the new table elements, on
// success, are filled in with an appropriately typed value. That's done
// below in `_fill`.
let result = unsafe {
self.instance
.get_mut(store)
.defined_table_grow(self.index, limiter, delta)
.await?
};
let start = match result {
// unwrap here should be ok because the runtime should always
// guarantee that we can fit the table size in a 64-bit integer.
Some(size) => u64::try_from(size).unwrap(),
None => bail!("failed to grow table by `{delta}`"),
};
// This should be in-bounds and well-typed, meaning that it should not
// fail, hence the unwrap. Note that this is required for the safety of
// this operation because this table's type may be non-nullable elements
// which means this must happen after growth.
self._fill(store, start, init, delta).unwrap();
Ok(start)
}
/// Async variant of [`Table::grow`].
///
/// Required when using a
/// [`ResourceLimiterAsync`](`crate::ResourceLimiterAsync`).
///
/// # Errors
///
/// This function will return an [`OutOfMemory`][crate::OutOfMemory] error when
/// memory allocation fails. See the `OutOfMemory` type's documentation for
/// details on Wasmtime's out-of-memory handling.
///
/// # Panics
///
/// This function will panic when if the store doesn't own the table.
#[cfg(feature = "async")]
pub async fn grow_async(
&self,
mut store: impl AsContextMut,
delta: u64,
init: Ref,
) -> Result<u64> {
self._grow(store.as_context_mut(), delta, init).await
}
/// Copy `len` elements from `src_table[src_index..]` into
/// `dst_table[dst_index..]`.
///
/// # Errors
///
/// Returns an error if the range is out of bounds of either the source or
/// destination tables, or if the source table's element type does not match
/// the destination table's element type.
///
/// This function will return an [`OutOfMemory`][crate::OutOfMemory] error when
/// memory allocation fails. See the `OutOfMemory` type's documentation for
/// details on Wasmtime's out-of-memory handling.
///
/// # Panics
///
/// Panics if `store` does not own either `dst_table` or `src_table`.
pub fn copy(
mut store: impl AsContextMut,
dst_table: &Table,
dst_index: u64,
src_table: &Table,
src_index: u64,
len: u64,
) -> Result<()> {
let store = store.as_context_mut().0;
let src_range = src_index..src_index.checked_add(len).ok_or(Trap::TableOutOfBounds)?;
let dst_range = dst_index..dst_index.checked_add(len).ok_or(Trap::TableOutOfBounds)?;
// Bounds-check up front before any modifications to ensure everything
// is in-bounds.
if src_range.end > src_table.size_(store) || dst_range.end > dst_table.size_(store) {
return Err(Trap::TableOutOfBounds.into());
}
let dst_ty = dst_table.ty(&store);
let src_ty = src_table.ty(&store);
src_ty
.element()
.ensure_matches(store.engine(), dst_ty.element())
.context(
"type mismatch: source table's element type does not match \
destination table's element type",
)?;
// Do a forwards or backwards copy depending on the indices involved to
// ensure that elements that are part of the copy aren't accidentally
// clobbered.
if dst_index < src_index {
for (src, dst) in src_range.zip(dst_range) {
let val = src_table
.get(&mut *store, src)
.ok_or(Trap::TableOutOfBounds)?;
dst_table.set(&mut *store, dst, val)?;
}
} else {
for (src, dst) in src_range.rev().zip(dst_range.rev()) {
let val = src_table
.get(&mut *store, src)
.ok_or(Trap::TableOutOfBounds)?;
dst_table.set(&mut *store, dst, val)?;
}
}
Ok(())
}
/// Fill `table[dst..(dst + len)]` with the given value.
///
/// # Errors
///
/// Returns an error if
///
/// * `val` is not of the same type as this table's
/// element type,
///
/// * the region to be filled is out of bounds, or
///
/// * `val` comes from a different `Store` from this table.
///
/// This function will return an [`OutOfMemory`][crate::OutOfMemory] error when
/// memory allocation fails. See the `OutOfMemory` type's documentation for
/// details on Wasmtime's out-of-memory handling.
///
/// # Panics
///
/// Panics if `store` does not own either `dst_table` or `src_table`.
pub fn fill(&self, mut store: impl AsContextMut, dst: u64, val: Ref, len: u64) -> Result<()> {
self._fill(store.as_context_mut().0, dst, val, len)
}
pub(crate) fn _fill(
&self,
store: &mut StoreOpaque,
dst: u64,
val: Ref,
len: u64,
) -> Result<()> {
let ty = self.ty_(store);
val.ensure_matches_ty(store, ty.element())
.context("type mismatch: value does not match table element type")?;
let end = dst.checked_add(len).ok_or(Trap::TableOutOfBounds)?;
if end > self.size_(store) {
bail!(Trap::TableOutOfBounds);
}
for i in dst..dst + len {
self.set_(&mut *store, i, val.clone())?;
}
Ok(())
}
#[cfg(feature = "gc")]
pub(crate) fn trace_roots(&self, store: &mut StoreOpaque, gc_roots_list: &mut vm::GcRootsList) {
if !self
.ty_(store)
.element()
.is_vmgcref_type_and_points_to_object()
{
return;
}
let (table, _) = self.wasmtime_table(store, iter::empty());
for gc_ref in table.gc_refs_mut() {
if let Some(gc_ref) = gc_ref {
unsafe {
gc_roots_list.add_vmgcref_root(gc_ref.into(), "Wasm table element");
}
}
}
}
pub(crate) fn from_raw(instance: StoreInstanceId, index: DefinedTableIndex) -> Table {
Table { instance, index }
}
pub(crate) fn wasmtime_ty<'a>(&self, store: &'a StoreOpaque) -> &'a wasmtime_environ::Table {
let module = store[self.instance].env_module();
let index = module.table_index(self.index);
&module.tables[index]
}
pub(crate) fn vmimport(&self, store: &StoreOpaque) -> vm::VMTableImport {
let instance = &store[self.instance];
vm::VMTableImport {
from: instance.table_ptr(self.index).into(),
vmctx: instance.vmctx().into(),
index: self.index,
}
}
pub(crate) fn comes_from_same_store(&self, store: &StoreOpaque) -> bool {
store.id() == self.instance.store_id()
}
/// Returns a stable identifier for this table within its store.
///
/// This allows distinguishing tables when introspecting them
/// e.g. via debug APIs.
#[cfg(feature = "debug")]
pub fn debug_index_in_store(&self) -> u64 {
u64::from(self.instance.instance().as_u32()) << 32 | u64::from(self.index.as_u32())
}
/// Get a stable hash key for this table.
///
/// Even if the same underlying table definition is added to the
/// `StoreData` multiple times and becomes multiple `wasmtime::Table`s,
/// this hash key will be consistent across all of these tables.
#[cfg_attr(
not(test),
expect(dead_code, reason = "Not used yet, but added for consistency")
)]
pub(crate) fn hash_key(&self, store: &StoreOpaque) -> impl core::hash::Hash + Eq + use<'_> {
store[self.instance].table_ptr(self.index).as_ptr().addr()
}
}
fn element_type(ty: &TableType) -> TableElementType {
match ty.element().heap_type().top() {
HeapTopType::Func => TableElementType::Func,
HeapTopType::Exn | HeapTopType::Extern | HeapTopType::Any => TableElementType::GcRef,
HeapTopType::Cont => TableElementType::Cont,
}
}
impl Ref {
fn into_table_func(
self,
store: &mut StoreOpaque,
ty: &RefType,
) -> Result<Option<NonNull<VMFuncRef>>> {
self.ensure_matches_ty(store, &ty)
.context("type mismatch: value does not match table element type")?;
match (self, ty.heap_type().top()) {
(Ref::Func(None), HeapTopType::Func) => {
assert!(ty.is_nullable());
Ok(None)
}
(Ref::Func(Some(f)), HeapTopType::Func) => {
debug_assert!(
f.comes_from_same_store(store),
"checked in `ensure_matches_ty`"
);
Ok(Some(f.vm_func_ref(store)))
}
_ => unreachable!("checked that the value matches the type above"),
}
}
fn into_table_gc_ref<'a>(
self,
store: &'a mut AutoAssertNoGc<'_>,
ty: &RefType,
) -> Result<Option<&'a VMGcRef>> {
self.ensure_matches_ty(store, &ty)
.context("type mismatch: value does not match table element type")?;
match (self, ty.heap_type().top()) {
(Ref::Extern(e), HeapTopType::Extern) => match e {
None => {
assert!(ty.is_nullable());
Ok(None)
}
Some(e) => Ok(Some(e.try_gc_ref(store)?)),
},
(Ref::Any(a), HeapTopType::Any) => match a {
None => {
assert!(ty.is_nullable());
Ok(None)
}
Some(a) => Ok(Some(a.try_gc_ref(store)?)),
},
(Ref::Exn(e), HeapTopType::Exn) => match e {
None => {
assert!(ty.is_nullable());
Ok(None)
}
Some(e) => Ok(Some(e.try_gc_ref(store)?)),
},
_ => unreachable!("checked that the value matches the type above"),
}
}
fn is_zero_pattern(&self) -> bool {
match self {
Ref::Extern(None) | Ref::Any(None) | Ref::Exn(None) | Ref::Func(None) => true,
Ref::Extern(Some(_)) | Ref::Any(Some(_)) | Ref::Exn(Some(_)) | Ref::Func(Some(_)) => {
false
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{Instance, Module, Store};
#[test]
fn hash_key_is_stable_across_duplicate_store_data_entries() -> Result<()> {
let mut store = Store::<()>::default();
let module = Module::new(
store.engine(),
r#"
(module
(table (export "t") 1 1 externref)
)
"#,
)?;
let instance = Instance::new(&mut store, &module, &[])?;
// Each time we `get_table`, we call `Table::from_wasmtime` which adds
// a new entry to `StoreData`, so `t1` and `t2` will have different
// indices into `StoreData`.
let t1 = instance.get_table(&mut store, "t").unwrap();
let t2 = instance.get_table(&mut store, "t").unwrap();
// That said, they really point to the same table.
assert!(t1.get(&mut store, 0).unwrap().unwrap_extern().is_none());
assert!(t2.get(&mut store, 0).unwrap().unwrap_extern().is_none());
let e = ExternRef::new(&mut store, 42)?;
t1.set(&mut store, 0, e.into())?;
assert!(t1.get(&mut store, 0).unwrap().unwrap_extern().is_some());
assert!(t2.get(&mut store, 0).unwrap().unwrap_extern().is_some());
// And therefore their hash keys are the same.
assert!(t1.hash_key(&store.as_context().0) == t2.hash_key(&store.as_context().0));
// But the hash keys are different from different tables.
let instance2 = Instance::new(&mut store, &module, &[])?;
let t3 = instance2.get_table(&mut store, "t").unwrap();
assert!(t1.hash_key(&store.as_context().0) != t3.hash_key(&store.as_context().0));
Ok(())
}
#[test]
fn grow_is_send() {
fn _assert_send<T: Send>(_: T) {}
fn _grow(table: &Table, store: &mut Store<()>, init: Ref) {
_assert_send(table.grow(store, 0, init))
}
}
}
+122
View File
@@ -0,0 +1,122 @@
use crate::Result;
use crate::runtime::types::TagType;
use crate::trampoline::generate_tag_export;
use crate::{
AsContext, AsContextMut,
store::{StoreInstanceId, StoreOpaque},
};
use wasmtime_environ::DefinedTagIndex;
#[cfg(feature = "gc")]
use crate::store::InstanceId;
/// A WebAssembly `tag`.
#[derive(Copy, Clone, Debug)]
#[repr(C)] // here for the C API in the future
pub struct Tag {
instance: StoreInstanceId,
index: DefinedTagIndex,
}
impl Tag {
pub(crate) fn from_raw(instance: StoreInstanceId, index: DefinedTagIndex) -> Tag {
Tag { instance, index }
}
/// Create a new tag instance from a given TagType.
///
/// # Errors
///
/// Returns an error if `ty` was not created with the same
/// [`Engine`](crate::Engine) as `store`.
///
/// This function will return an [`OutOfMemory`][crate::OutOfMemory] error when
/// memory allocation fails. See the `OutOfMemory` type's documentation for
/// details on Wasmtime's out-of-memory handling.
pub fn new(mut store: impl AsContextMut, ty: &TagType) -> Result<Tag> {
generate_tag_export(store.as_context_mut().0, ty)
}
/// Returns the underlying type of this `tag`.
///
/// # Panics
///
/// Panics if `store` does not own this tag.
pub fn ty(&self, store: impl AsContext) -> TagType {
self._ty(store.as_context().0)
}
pub(crate) fn _ty(&self, store: &StoreOpaque) -> TagType {
TagType::from_wasmtime_tag(store.engine(), self.wasmtime_ty(store))
}
pub(crate) fn wasmtime_ty<'a>(&self, store: &'a StoreOpaque) -> &'a wasmtime_environ::Tag {
let module = store[self.instance].env_module();
let index = module.tag_index(self.index);
&module.tags[index]
}
pub(crate) fn vmimport(&self, store: &StoreOpaque) -> crate::runtime::vm::VMTagImport {
let instance = &store[self.instance];
crate::runtime::vm::VMTagImport {
from: instance.tag_ptr(self.index).into(),
vmctx: instance.vmctx().into(),
index: self.index,
}
}
pub(crate) fn comes_from_same_store(&self, store: &StoreOpaque) -> bool {
store.id() == self.instance.store_id()
}
/// Returns a stable identifier for this tag within its store.
///
/// This allows distinguishing tags when introspecting them
/// e.g. via debug APIs.
#[cfg(feature = "debug")]
pub fn debug_index_in_store(&self) -> u64 {
u64::from(self.instance.instance().as_u32()) << 32 | u64::from(self.index.as_u32())
}
/// Determines whether this tag is reference equal to the other
/// given tag in the given store.
///
/// # Panics
///
/// Panics if either tag do not belong to the given `store`.
pub fn eq(a: &Tag, b: &Tag, store: impl AsContext) -> bool {
// make sure both tags belong to the store
let store = store.as_context();
let _ = &store[a.instance];
let _ = &store[b.instance];
// then compare to see if they have the same definition
a.instance == b.instance && a.index == b.index
}
/// Get the "index coordinates" for this `Tag`: the raw instance
/// ID and defined-tag index within that instance. This can be
/// used to "serialize" the tag as safe (tamper-proof,
/// bounds-checked) values, e.g. within the GC store for an
/// exception object.
#[cfg(feature = "gc")]
pub(crate) fn to_raw_indices(&self) -> (InstanceId, DefinedTagIndex) {
(self.instance.instance(), self.index)
}
/// Create a new `Tag` from known raw indices as produced by
/// `to_raw_indices()`.
///
/// # Panics
///
/// Panics if the indices are out-of-bounds in the given store.
#[cfg(feature = "gc")]
pub(crate) fn from_raw_indices(
store: &StoreOpaque,
instance: InstanceId,
index: DefinedTagIndex,
) -> Tag {
let instance = StoreInstanceId::new(store.id(), instance);
Tag { instance, index }
}
}
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+795
View File
@@ -0,0 +1,795 @@
use super::invoke_wasm_and_catch_traps;
use crate::prelude::*;
use crate::runtime::vm::VMFuncRef;
use crate::store::{AutoAssertNoGc, StoreOpaque};
use crate::{
AsContext, AsContextMut, Engine, Func, FuncType, HeapType, NoFunc, RefType, StoreContextMut,
ValRaw, ValType,
};
use core::ffi::c_void;
use core::marker;
use core::mem::{self, MaybeUninit};
use core::ptr::{self, NonNull};
use wasmtime_environ::VMSharedTypeIndex;
/// A statically typed WebAssembly function.
///
/// Values of this type represent statically type-checked WebAssembly functions.
/// The function within a [`TypedFunc`] is statically known to have `Params` as its
/// parameters and `Results` as its results.
///
/// This structure is created via [`Func::typed`] or [`TypedFunc::new_unchecked`].
/// For more documentation about this see those methods.
pub struct TypedFunc<Params, Results> {
_a: marker::PhantomData<fn(Params) -> Results>,
ty: FuncType,
func: Func,
}
impl<Params, Results> Clone for TypedFunc<Params, Results> {
fn clone(&self) -> TypedFunc<Params, Results> {
Self {
_a: marker::PhantomData,
ty: self.ty.clone(),
func: self.func,
}
}
}
impl<Params, Results> TypedFunc<Params, Results>
where
Params: WasmParams,
Results: WasmResults,
{
/// An unchecked version of [`Func::typed`] which does not perform a
/// typecheck and simply assumes that the type declared here matches the
/// type of this function.
///
/// The semantics of this function are the same as [`Func::typed`] except
/// that no error is returned because no typechecking is done.
///
/// # Unsafety
///
/// This function only safe to call if `typed` would otherwise return `Ok`
/// for the same `Params` and `Results` specified. If `typed` would return
/// an error then the returned `TypedFunc` is memory unsafe to invoke.
pub unsafe fn new_unchecked(store: impl AsContext, func: Func) -> TypedFunc<Params, Results> {
let store = store.as_context().0;
unsafe { Self::_new_unchecked(store, func) }
}
pub(crate) unsafe fn _new_unchecked(
store: &StoreOpaque,
func: Func,
) -> TypedFunc<Params, Results> {
let ty = func.load_ty(store);
TypedFunc {
_a: marker::PhantomData,
ty,
func,
}
}
/// Returns the underlying [`Func`] that this is wrapping, losing the static
/// type information in the process.
pub fn func(&self) -> &Func {
&self.func
}
/// Invokes this WebAssembly function with the specified parameters.
///
/// Returns either the results of the call, or a [`Trap`] if one happened.
///
/// For more information, see the [`Func::typed`] and [`Func::call`]
/// documentation.
///
/// # Errors
///
/// For more information on errors see the documentation on [`Func::call`].
///
/// # Panics
///
/// Panics if `store` does not contain this function.
///
/// [`Trap`]: crate::Trap
///
/// This function will return an [`OutOfMemory`][crate::OutOfMemory] error when
/// memory allocation fails. See the `OutOfMemory` type's documentation for
/// details on Wasmtime's out-of-memory handling.
#[inline]
pub fn call(&self, mut store: impl AsContextMut, params: Params) -> Result<Results> {
let mut store = store.as_context_mut();
store.0.validate_sync_call()?;
let func = self.func.vm_func_ref(store.0);
unsafe { Self::call_raw(&mut store, &self.ty, func, params) }
}
/// Invokes this WebAssembly function with the specified parameters.
///
/// Returns either the results of the call, or a [`Trap`] if one happened.
///
/// For more information, see the [`Func::typed`] and [`Func::call_async`]
/// documentation.
///
/// # Errors
///
/// For more information on errors see the documentation on [`Func::call`].
///
/// # Panics
///
/// This function will panic if it is called when the underlying [`Func`] is
/// connected to a synchronous store.
///
/// [`Trap`]: crate::Trap
///
/// This function will return an [`OutOfMemory`][crate::OutOfMemory] error when
/// memory allocation fails. See the `OutOfMemory` type's documentation for
/// details on Wasmtime's out-of-memory handling.
#[cfg(feature = "async")]
pub async fn call_async(
&self,
mut store: impl AsContextMut<Data: Send>,
params: Params,
) -> Result<Results>
where
Params: Sync,
Results: Sync,
{
let mut store = store.as_context_mut();
store
.on_fiber(|store| {
let func = self.func.vm_func_ref(store.0);
unsafe { Self::call_raw(store, &self.ty, func, params) }
})
.await?
}
/// Do a raw call of a typed function.
///
/// # Safety
///
/// `func` must be of the given type, and it additionally must be a valid
/// store-owned pointer within the `store` provided.
pub(crate) unsafe fn call_raw<T>(
store: &mut StoreContextMut<'_, T>,
ty: &FuncType,
func: ptr::NonNull<VMFuncRef>,
params: Params,
) -> Result<Results> {
// double-check that params/results match for this function's type in
// debug mode.
//
// SAFETY: this function requires that `ptr` is a valid function
// pointer.
unsafe {
if cfg!(debug_assertions) {
Self::debug_typecheck(store.0, func.as_ref().type_index);
}
}
// Validate that all runtime values flowing into this store indeed
// belong within this store, otherwise it would be unsafe for store
// values to cross each other.
union Storage<T: Copy, U: Copy> {
params: MaybeUninit<T>,
results: U,
}
let mut storage = Storage::<Params::ValRawStorage, Results::ValRawStorage> {
params: MaybeUninit::uninit(),
};
{
let mut store = AutoAssertNoGc::new(store.0);
// SAFETY: it's safe to use a union field here as the field itself
// is `MaybeUninit<_>` meaning nothing is accidentally considered
// initialized.
let dst: &mut MaybeUninit<_> = unsafe { &mut storage.params };
params.store(&mut store, ty, dst)?;
}
// Try to capture only a single variable (a tuple) in the closure below.
// This means the size of the closure is one pointer and is much more
// efficient to move in memory. This closure is actually invoked on the
// other side of a C++ shim, so it can never be inlined enough to make
// the memory go away, so the size matters here for performance.
let mut captures = (func, storage);
let result = invoke_wasm_and_catch_traps(store, |caller, vm| {
let (func_ref, storage) = &mut captures;
let storage_len = mem::size_of_val::<Storage<_, _>>(storage) / mem::size_of::<ValRaw>();
let storage: *mut Storage<_, _> = storage;
let storage = storage.cast::<ValRaw>();
let storage = core::ptr::slice_from_raw_parts_mut(storage, storage_len);
let storage = NonNull::new(storage).unwrap();
// SAFETY: this function's own contract is that `func_ref` is safe
// to call and additionally that the params/results are correctly
// ascribed for this function call to be safe.
unsafe { VMFuncRef::array_call(*func_ref, vm, caller, storage) }
});
let (_, storage) = captures;
result?;
let mut store = AutoAssertNoGc::new(store.0);
// SAFETY: this function is itself unsafe to ensure that the result type
// ascription is correct for `Results` and matches the actual function.
// Additionally given the correct type ascription all of the `results`
// accessed here should be validly initialized.
unsafe { Ok(Results::load(&mut store, &storage.results)) }
}
/// Purely a debug-mode assertion, not actually used in release builds.
fn debug_typecheck(store: &StoreOpaque, func: VMSharedTypeIndex) {
let ty = FuncType::from_shared_type_index(store.engine(), func);
Params::typecheck(store.engine(), ty.params(), TypeCheckPosition::Param)
.expect("params should match");
Results::typecheck(store.engine(), ty.results(), TypeCheckPosition::Result)
.expect("results should match");
}
}
#[doc(hidden)]
#[derive(Copy, Clone)]
pub enum TypeCheckPosition {
Param,
Result,
}
/// A trait implemented for types which can be arguments and results for
/// closures passed to [`Func::wrap`] as well as parameters to [`Func::typed`].
///
/// This trait should not be implemented by user types. This trait may change at
/// any time internally. The types which implement this trait, however, are
/// stable over time.
///
/// For more information see [`Func::wrap`] and [`Func::typed`]
pub unsafe trait WasmTy: Send {
// Do a "static" (aka at time of `func.typed::<P, R>()`) ahead-of-time type
// check for this type at the given position. You probably don't need to
// override this trait method.
#[doc(hidden)]
#[inline]
fn typecheck(engine: &Engine, actual: ValType, position: TypeCheckPosition) -> Result<()> {
let expected = Self::valtype();
debug_assert!(expected.comes_from_same_engine(engine));
debug_assert!(actual.comes_from_same_engine(engine));
match position {
// The caller is expecting to receive a `T` and the callee is
// actually returning a `U`, so ensure that `U <: T`.
TypeCheckPosition::Result => actual.ensure_matches(engine, &expected),
// The caller is expecting to pass a `T` and the callee is expecting
// to receive a `U`, so ensure that `T <: U`.
TypeCheckPosition::Param => match (expected.as_ref(), actual.as_ref()) {
// ... except that this technically-correct check would overly
// restrict the usefulness of our typed function APIs for the
// specific case of concrete reference types. Let's work through
// an example.
//
// Consider functions that take a `(ref param $some_func_type)`
// parameter:
//
// * We cannot have a static `wasmtime::SomeFuncTypeRef` type
// that implements `WasmTy` specifically for `(ref null
// $some_func_type)` because Wasm modules, and their types,
// are loaded dynamically at runtime.
//
// * Therefore the embedder's only option for `T <: (ref null
// $some_func_type)` is `T = (ref null nofunc)` aka
// `Option<wasmtime::NoFunc>`.
//
// * But that static type means they can *only* pass in the null
// function reference as an argument to the typed function.
// This is way too restrictive! For ergonomics, we want them
// to be able to pass in a `wasmtime::Func` whose type is
// `$some_func_type`!
//
// To lift this constraint and enable better ergonomics for
// embedders, we allow `top(T) <: top(U)` -- i.e. they are part
// of the same type hierarchy and a dynamic cast could possibly
// succeed -- for the specific case of concrete heap type
// parameters, and fall back to dynamic type checks on the
// arguments passed to each invocation, as necessary.
(Some(expected_ref), Some(actual_ref)) if actual_ref.heap_type().is_concrete() => {
let expected_top = HeapType::from(expected_ref.heap_type().top());
let actual_top = HeapType::from(actual_ref.heap_type().top());
expected_top.ensure_matches(engine, &actual_top)
}
_ => expected.ensure_matches(engine, &actual),
},
}
}
// The value type that this Type represents.
#[doc(hidden)]
fn valtype() -> ValType;
#[doc(hidden)]
fn may_gc() -> bool {
match Self::valtype() {
ValType::Ref(_) => true,
ValType::I32 | ValType::I64 | ValType::F32 | ValType::F64 | ValType::V128 => false,
}
}
// Dynamic checks that this value is being used with the correct store
// context.
#[doc(hidden)]
fn compatible_with_store(&self, store: &StoreOpaque) -> bool;
// Dynamic checks that `self <: actual` for concrete type arguments. See the
// comment above in `WasmTy::typecheck`.
//
// Only ever called for concrete reference type arguments, so any type which
// is not in a type hierarchy with concrete reference types can implement
// this with `unreachable!()`.
#[doc(hidden)]
fn dynamic_concrete_type_check(
&self,
store: &StoreOpaque,
nullable: bool,
actual: &HeapType,
) -> Result<()>;
// Is this a GC-managed reference that actually points to a GC object? That
// is, `self` is *not* an `i31`, null reference, or uninhabited type.
//
// Note that it is okay if this returns false positives (i.e. `true` for
// `Rooted<AnyRef>` without actually looking up the rooted `anyref` in the
// store and reflecting on it to determine whether it is actually an
// `i31`). However, it is not okay if this returns false negatives.
#[doc(hidden)]
#[inline]
fn is_vmgcref_and_points_to_object(&self) -> bool {
Self::valtype().is_vmgcref_type_and_points_to_object()
}
// Store `self` into `ptr`.
//
// NB: We _must not_ trigger a GC when passing refs from host code into Wasm
// (e.g. returned from a host function or passed as arguments to a Wasm
// function). After insertion into the activations table, the reference is
// no longer rooted. If multiple references are being sent from the host
// into Wasm and we allowed GCs during insertion, then the following events
// could happen:
//
// * Reference A is inserted into the activations table. This does not
// trigger a GC, but does fill the table to capacity.
//
// * The caller's reference to A is removed. Now the only reference to A is
// from the activations table.
//
// * Reference B is inserted into the activations table. Because the table
// is at capacity, a GC is triggered.
//
// * A is reclaimed because the only reference keeping it alive was the
// activation table's reference (it isn't inside any Wasm frames on the
// stack yet, so stack scanning and stack maps don't increment its
// reference count).
//
// * We transfer control to Wasm, giving it A and B. Wasm uses A. That's a
// use-after-free bug.
//
// In conclusion, to prevent uses-after-free bugs, we cannot GC while
// converting types into their raw ABI forms.
#[doc(hidden)]
fn store(self, store: &mut AutoAssertNoGc<'_>, ptr: &mut MaybeUninit<ValRaw>) -> Result<()>;
// Load a version of `Self` from the `ptr` provided.
//
// # Safety
//
// This function is unsafe as it's up to the caller to ensure that `ptr` is
// valid for this given type.
#[doc(hidden)]
unsafe fn load(store: &mut AutoAssertNoGc<'_>, ptr: &ValRaw) -> Self;
}
macro_rules! integers {
($($primitive:ident/$get_primitive:ident => $ty:ident)*) => ($(
unsafe impl WasmTy for $primitive {
#[inline]
fn valtype() -> ValType {
ValType::$ty
}
#[inline]
fn compatible_with_store(&self, _: &StoreOpaque) -> bool {
true
}
#[inline]
fn dynamic_concrete_type_check(&self, _: &StoreOpaque, _: bool, _: &HeapType) -> Result<()> {
unreachable!()
}
#[inline]
fn store(self, _store: &mut AutoAssertNoGc<'_>, ptr: &mut MaybeUninit<ValRaw>) -> Result<()> {
ptr.write(ValRaw::$primitive(self));
Ok(())
}
#[inline]
unsafe fn load(_store: &mut AutoAssertNoGc<'_>, ptr: &ValRaw) -> Self {
ptr.$get_primitive()
}
}
)*)
}
integers! {
i32/get_i32 => I32
i64/get_i64 => I64
u32/get_u32 => I32
u64/get_u64 => I64
}
macro_rules! floats {
($($float:ident/$int:ident/$get_float:ident => $ty:ident)*) => ($(
unsafe impl WasmTy for $float {
#[inline]
fn valtype() -> ValType {
ValType::$ty
}
#[inline]
fn compatible_with_store(&self, _: &StoreOpaque) -> bool {
true
}
#[inline]
fn dynamic_concrete_type_check(&self, _: &StoreOpaque, _: bool, _: &HeapType) -> Result<()> {
unreachable!()
}
#[inline]
fn store(self, _store: &mut AutoAssertNoGc<'_>, ptr: &mut MaybeUninit<ValRaw>) -> Result<()> {
ptr.write(ValRaw::$float(self.to_bits()));
Ok(())
}
#[inline]
unsafe fn load(_store: &mut AutoAssertNoGc<'_>, ptr: &ValRaw) -> Self {
$float::from_bits(ptr.$get_float())
}
}
)*)
}
floats! {
f32/u32/get_f32 => F32
f64/u64/get_f64 => F64
}
unsafe impl WasmTy for NoFunc {
#[inline]
fn valtype() -> ValType {
ValType::Ref(RefType::new(false, HeapType::NoFunc))
}
#[inline]
fn compatible_with_store(&self, _store: &StoreOpaque) -> bool {
match self._inner {}
}
#[inline]
fn dynamic_concrete_type_check(&self, _: &StoreOpaque, _: bool, _: &HeapType) -> Result<()> {
match self._inner {}
}
#[inline]
fn is_vmgcref_and_points_to_object(&self) -> bool {
match self._inner {}
}
#[inline]
fn store(self, _store: &mut AutoAssertNoGc<'_>, _ptr: &mut MaybeUninit<ValRaw>) -> Result<()> {
match self._inner {}
}
#[inline]
unsafe fn load(_store: &mut AutoAssertNoGc<'_>, _ptr: &ValRaw) -> Self {
unreachable!("NoFunc is uninhabited")
}
}
unsafe impl WasmTy for Option<NoFunc> {
#[inline]
fn valtype() -> ValType {
ValType::Ref(RefType::new(true, HeapType::NoFunc))
}
#[inline]
fn compatible_with_store(&self, _store: &StoreOpaque) -> bool {
true
}
#[inline]
fn dynamic_concrete_type_check(
&self,
_: &StoreOpaque,
nullable: bool,
ty: &HeapType,
) -> Result<()> {
if nullable {
// `(ref null nofunc) <: (ref null $f)` for all function types `$f`.
Ok(())
} else {
bail!("argument type mismatch: expected non-nullable (ref {ty}), found null reference")
}
}
#[inline]
fn store(self, _store: &mut AutoAssertNoGc<'_>, ptr: &mut MaybeUninit<ValRaw>) -> Result<()> {
ptr.write(ValRaw::funcref(ptr::null_mut()));
Ok(())
}
#[inline]
unsafe fn load(_store: &mut AutoAssertNoGc<'_>, _ptr: &ValRaw) -> Self {
None
}
}
unsafe impl WasmTy for Func {
#[inline]
fn valtype() -> ValType {
ValType::Ref(RefType::new(false, HeapType::Func))
}
#[inline]
fn compatible_with_store(&self, store: &StoreOpaque) -> bool {
self.store == store.id()
}
#[inline]
fn dynamic_concrete_type_check(
&self,
store: &StoreOpaque,
_nullable: bool,
expected: &HeapType,
) -> Result<()> {
let expected = expected.unwrap_concrete_func();
self.ensure_matches_ty(store, expected)
.context("argument type mismatch for reference to concrete type")
}
#[inline]
fn store(self, store: &mut AutoAssertNoGc<'_>, ptr: &mut MaybeUninit<ValRaw>) -> Result<()> {
let abi = self.vm_func_ref(store);
ptr.write(ValRaw::funcref(abi.cast::<c_void>().as_ptr()));
Ok(())
}
#[inline]
unsafe fn load(store: &mut AutoAssertNoGc<'_>, ptr: &ValRaw) -> Self {
let p = NonNull::new(ptr.get_funcref()).unwrap().cast();
// SAFETY: it's an unsafe contract of `load` that it's only provided
// valid wasm values owned by `store`.
unsafe { Func::from_vm_func_ref(store.id(), p) }
}
}
unsafe impl WasmTy for Option<Func> {
#[inline]
fn valtype() -> ValType {
ValType::FUNCREF
}
#[inline]
fn compatible_with_store(&self, store: &StoreOpaque) -> bool {
if let Some(f) = self {
f.compatible_with_store(store)
} else {
true
}
}
fn dynamic_concrete_type_check(
&self,
store: &StoreOpaque,
nullable: bool,
expected: &HeapType,
) -> Result<()> {
if let Some(f) = self {
let expected = expected.unwrap_concrete_func();
f.ensure_matches_ty(store, expected)
.context("argument type mismatch for reference to concrete type")
} else if nullable {
Ok(())
} else {
bail!(
"argument type mismatch: expected non-nullable (ref {expected}), found null reference"
)
}
}
#[inline]
fn store(self, store: &mut AutoAssertNoGc<'_>, ptr: &mut MaybeUninit<ValRaw>) -> Result<()> {
let raw = if let Some(f) = self {
f.vm_func_ref(store).as_ptr()
} else {
ptr::null_mut()
};
ptr.write(ValRaw::funcref(raw.cast::<c_void>()));
Ok(())
}
#[inline]
unsafe fn load(store: &mut AutoAssertNoGc<'_>, ptr: &ValRaw) -> Self {
let ptr = NonNull::new(ptr.get_funcref())?.cast();
// SAFETY: it's an unsafe contract of `load` that it's only provided
// valid wasm values owned by `store`.
unsafe { Some(Func::from_vm_func_ref(store.id(), ptr)) }
}
}
/// A trait used for [`Func::typed`] and with [`TypedFunc`] to represent the set of
/// parameters for wasm functions.
///
/// This is implemented for bare types that can be passed to wasm as well as
/// tuples of those types.
pub unsafe trait WasmParams: Send {
#[doc(hidden)]
type ValRawStorage: Copy;
#[doc(hidden)]
fn typecheck(
engine: &Engine,
params: impl ExactSizeIterator<Item = crate::ValType>,
position: TypeCheckPosition,
) -> Result<()>;
#[doc(hidden)]
fn vmgcref_pointing_to_object_count(&self) -> usize;
#[doc(hidden)]
fn store(
self,
store: &mut AutoAssertNoGc<'_>,
func_ty: &FuncType,
dst: &mut MaybeUninit<Self::ValRawStorage>,
) -> Result<()>;
}
// Forward an impl from `T` to `(T,)` for convenience if there's only one
// parameter.
unsafe impl<T> WasmParams for T
where
T: WasmTy,
{
type ValRawStorage = <(T,) as WasmParams>::ValRawStorage;
fn typecheck(
engine: &Engine,
params: impl ExactSizeIterator<Item = crate::ValType>,
position: TypeCheckPosition,
) -> Result<()> {
<(T,) as WasmParams>::typecheck(engine, params, position)
}
#[inline]
fn vmgcref_pointing_to_object_count(&self) -> usize {
T::is_vmgcref_and_points_to_object(self) as usize
}
#[inline]
fn store(
self,
store: &mut AutoAssertNoGc<'_>,
func_ty: &FuncType,
dst: &mut MaybeUninit<Self::ValRawStorage>,
) -> Result<()> {
<(T,) as WasmParams>::store((self,), store, func_ty, dst)
}
}
macro_rules! impl_wasm_params {
($n:tt $($t:ident)*) => {
#[allow(non_snake_case, reason = "macro-generated code")]
unsafe impl<$($t: WasmTy,)*> WasmParams for ($($t,)*) {
type ValRawStorage = [ValRaw; $n];
fn typecheck(
_engine: &Engine,
mut params: impl ExactSizeIterator<Item = crate::ValType>,
_position: TypeCheckPosition,
) -> Result<()> {
let mut _n = 0;
$(
match params.next() {
Some(t) => {
_n += 1;
$t::typecheck(_engine, t, _position)?
},
None => bail!("expected {} types, found {}", $n, params.len() + _n),
}
)*
match params.next() {
None => Ok(()),
Some(_) => {
_n += 1;
bail!("expected {} types, found {}", $n, params.len() + _n)
},
}
}
#[inline]
fn vmgcref_pointing_to_object_count(&self) -> usize {
let ($($t,)*) = self;
0 $(
+ $t.is_vmgcref_and_points_to_object() as usize
)*
}
#[inline]
fn store(
self,
_store: &mut AutoAssertNoGc<'_>,
_func_ty: &FuncType,
_ptr: &mut MaybeUninit<Self::ValRawStorage>,
) -> Result<()> {
let ($($t,)*) = self;
let mut _i = 0;
$(
if !$t.compatible_with_store(_store) {
bail!("attempt to pass cross-`Store` value to Wasm as function argument");
}
if $t::valtype().is_ref() {
let param_ty = _func_ty.param(_i).unwrap();
let ref_ty = param_ty.unwrap_ref();
let heap_ty = ref_ty.heap_type();
if heap_ty.is_concrete() {
$t.dynamic_concrete_type_check(_store, ref_ty.is_nullable(), heap_ty)?;
}
}
let dst = map_maybe_uninit!(_ptr[_i]);
$t.store(_store, dst)?;
_i += 1;
)*
Ok(())
}
}
};
}
for_each_function_signature!(impl_wasm_params);
/// A trait used for [`Func::typed`] and with [`TypedFunc`] to represent the set of
/// results for wasm functions.
pub unsafe trait WasmResults: WasmParams {
#[doc(hidden)]
unsafe fn load(store: &mut AutoAssertNoGc<'_>, abi: &Self::ValRawStorage) -> Self;
}
// Forwards from a bare type `T` to the 1-tuple type `(T,)`
unsafe impl<T: WasmTy> WasmResults for T {
unsafe fn load(store: &mut AutoAssertNoGc<'_>, abi: &Self::ValRawStorage) -> Self {
// SAFETY: the one-element tuple and single-type impls behave the same
// way.
unsafe { <(T,) as WasmResults>::load(store, abi).0 }
}
}
macro_rules! impl_wasm_results {
($n:tt $($t:ident)*) => {
#[allow(non_snake_case, reason = "macro-generated code")]
unsafe impl<$($t: WasmTy,)*> WasmResults for ($($t,)*) {
unsafe fn load(_store: &mut AutoAssertNoGc<'_>, abi: &Self::ValRawStorage) -> Self {
let [$($t,)*] = abi;
(
// SAFETY: this is forwarding the unsafe contract of the outer
// function to the inner functions here.
$(unsafe { $t::load(_store, $t) },)*
)
}
}
};
}
for_each_function_signature!(impl_wasm_results);
+125
View File
@@ -0,0 +1,125 @@
#[cfg(feature = "gc")]
mod enabled;
#[cfg(feature = "gc")]
pub use enabled::*;
#[cfg(not(feature = "gc"))]
mod disabled;
#[cfg(not(feature = "gc"))]
pub use disabled::*;
mod noextern;
pub use noextern::NoExtern;
mod none_ref;
pub use none_ref::NoneRef;
use core::fmt;
use core::ops::Deref;
/// A common trait implemented by all garbage-collected reference types.
///
/// This is a sealed trait, and may not be implemented for any types outside of
/// the `wasmtime` crate.
pub trait GcRef: GcRefImpl {}
impl<T> GcRef for T where T: GcRefImpl {}
/// A trait implemented for GC references that are guaranteed to be rooted:
///
/// * [`Rooted<T>`][crate::Rooted]
/// * [`OwnedRooted<T>`][crate::OwnedRooted]
///
/// You can use this to abstract over the different kinds of rooted GC
/// references. Note that `Deref<Target = T>` is a supertrait for
/// `RootedGcRef<T>`, so all rooted GC references deref to their underlying `T`,
/// allowing you to call its methods.
///
/// This is a sealed trait, and may not be implemented for any types outside of
/// the `wasmtime` crate.
pub trait RootedGcRef<T>: RootedGcRefImpl<T> + Deref<Target = T>
where
T: GcRef,
{
}
impl<T, U> RootedGcRef<T> for U
where
T: GcRef,
U: RootedGcRefImpl<T> + Deref<Target = T>,
{
}
/// An error returned when attempting to allocate a GC-managed object, but the
/// GC heap is out of memory.
///
/// This error wraps an inner `T` value -- which is the host value, if any, that
/// was passed to [`ExternRef::new`][crate::ExternRef::new] -- and you can
/// recover this value via the
/// [`into_inner`][crate::GcHeapOutOfMemory::into_inner] method. This lets you
/// try to allocate the `externref` again, after performing a GC to hopefully
/// free up space in the heap, or otherwise do whatever you want with the inner
/// value.
///
/// For errors that occur when attempting to allocate non-`externref` objects
/// when the GC heap is at capacity, the `T` type parameter is just the unit
/// type `()`.
pub struct GcHeapOutOfMemory<T> {
inner: T,
bytes_needed: u64,
}
impl<T> fmt::Debug for GcHeapOutOfMemory<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(self, f)
}
}
impl<T> fmt::Display for GcHeapOutOfMemory<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"GC heap out of memory: no capacity for allocation of {} bytes",
self.bytes_needed
)
}
}
impl<T> core::error::Error for GcHeapOutOfMemory<T> {}
impl<T> GcHeapOutOfMemory<T> {
pub(crate) fn new(inner: T, bytes_needed: u64) -> Self {
Self {
inner,
bytes_needed,
}
}
#[cfg(feature = "gc")]
pub(crate) fn bytes_needed(&self) -> u64 {
self.bytes_needed
}
#[cfg(feature = "gc")]
pub(crate) fn map_inner<U>(self, f: impl FnOnce(T) -> U) -> GcHeapOutOfMemory<U> {
GcHeapOutOfMemory {
inner: f(self.inner),
bytes_needed: self.bytes_needed,
}
}
/// Recover this error's inner host value.
pub fn into_inner(self) -> T {
self.inner
}
/// Take this error's inner host value, but also retain this
/// `GcHeapOutOfMemory` with `T` replaced with `()`.
///
/// This allows you to both extract the inner `T` if necessary, and also
/// pass the `GcHeapOutOfMemory` error to [`Store::gc`][crate::Store::gc]
/// calls.
pub fn take_inner(self) -> (T, GcHeapOutOfMemory<()>) {
(self.inner, GcHeapOutOfMemory::new((), self.bytes_needed))
}
}
+27
View File
@@ -0,0 +1,27 @@
//! The dummy implementation of garbage-collection, for when the `gc` Cargo
//! feature is disabled.
//!
//! We provide dummy/uninhabited types so that downstream users (and the rest of
//! Wasmtime) need to do fewer `#[cfg(...)]`s for when GC is enabled versus
//! disabled at compile time. While we implement dummy methods for these types'
//! public methods, we do not, however, create dummy constructors constructors.
#![expect(missing_docs, unreachable_code, reason = "dummy module")]
mod anyref;
mod arrayref;
mod eqref;
mod exnref;
mod externref;
mod i31;
mod rooting;
mod structref;
pub use anyref::*;
pub use arrayref::*;
pub use eqref::*;
pub use exnref::*;
pub use externref::*;
pub use i31::*;
pub use rooting::*;
pub use structref::*;
@@ -0,0 +1,165 @@
use crate::runtime::vm::VMGcRef;
use crate::{
ArrayRef, AsContext, AsContextMut, EqRef, GcRefImpl, HeapType, I31, OwnedRooted, Result,
Rooted, StructRef,
store::{AutoAssertNoGc, StoreOpaque},
};
/// Support for `anyref` disabled at compile time because the `gc` cargo feature
/// was not enabled.
pub enum AnyRef {}
impl From<Rooted<EqRef>> for Rooted<AnyRef> {
#[inline]
fn from(s: Rooted<EqRef>) -> Self {
match s.inner {}
}
}
impl From<OwnedRooted<EqRef>> for OwnedRooted<AnyRef> {
#[inline]
fn from(s: OwnedRooted<EqRef>) -> Self {
match s.inner {}
}
}
impl From<Rooted<StructRef>> for Rooted<AnyRef> {
#[inline]
fn from(s: Rooted<StructRef>) -> Self {
match s.inner {}
}
}
impl From<OwnedRooted<StructRef>> for OwnedRooted<AnyRef> {
#[inline]
fn from(s: OwnedRooted<StructRef>) -> Self {
match s.inner {}
}
}
impl From<Rooted<ArrayRef>> for Rooted<AnyRef> {
#[inline]
fn from(s: Rooted<ArrayRef>) -> Self {
match s.inner {}
}
}
impl From<OwnedRooted<ArrayRef>> for OwnedRooted<AnyRef> {
#[inline]
fn from(s: OwnedRooted<ArrayRef>) -> Self {
match s.inner {}
}
}
impl GcRefImpl for AnyRef {}
impl AnyRef {
pub(crate) fn from_cloned_gc_ref(
_store: &mut AutoAssertNoGc<'_>,
_gc_ref: VMGcRef,
) -> Rooted<Self> {
unreachable!()
}
pub fn from_raw(_store: impl AsContextMut, raw: u32) -> Option<Rooted<Self>> {
assert_eq!(raw, 0);
None
}
pub fn _from_raw(_store: &mut AutoAssertNoGc<'_>, raw: u32) -> Option<Rooted<Self>> {
assert_eq!(raw, 0);
None
}
pub fn to_raw(&self, _store: impl AsContextMut) -> Result<u32> {
match *self {}
}
pub(crate) fn _to_raw(&self, _store: &mut AutoAssertNoGc<'_>) -> Result<u32> {
match *self {}
}
pub fn ty(&self, _store: impl AsContext) -> Result<HeapType> {
match *self {}
}
pub(crate) fn _ty(&self, _store: &StoreOpaque) -> Result<HeapType> {
match *self {}
}
pub fn matches_ty(&self, _store: impl AsContext, _ty: &HeapType) -> Result<bool> {
match *self {}
}
pub fn is_eqref(&self, _store: impl AsContext) -> Result<bool> {
match *self {}
}
pub(crate) fn _is_eqref(&self, _store: &StoreOpaque) -> Result<bool> {
match *self {}
}
pub fn as_eqref(&self, _store: impl AsContext) -> Result<Option<EqRef>> {
match *self {}
}
pub fn unwrap_eqref(&self, _store: impl AsContext) -> Result<EqRef> {
match *self {}
}
pub fn is_i31(&self, _store: impl AsContext) -> Result<bool> {
match *self {}
}
pub(crate) fn _is_i31(&self, _store: &StoreOpaque) -> Result<bool> {
match *self {}
}
pub fn as_i31(&self, _store: impl AsContext) -> Result<Option<I31>> {
match *self {}
}
pub fn unwrap_i31(&self, _store: impl AsContext) -> Result<I31> {
match *self {}
}
pub fn is_struct(&self, _store: impl AsContext) -> Result<bool> {
match *self {}
}
pub(crate) fn _is_struct(&self, _store: &StoreOpaque) -> Result<bool> {
match *self {}
}
pub fn as_struct(&self, _store: impl AsContext) -> Result<Option<StructRef>> {
match *self {}
}
pub(crate) fn _as_struct(&self, _store: &StoreOpaque) -> Result<Option<StructRef>> {
match *self {}
}
pub fn unwrap_struct(&self, _store: impl AsContext) -> Result<StructRef> {
match *self {}
}
pub fn is_array(&self, _store: impl AsContext) -> Result<bool> {
match *self {}
}
pub(crate) fn _is_array(&self, _store: &StoreOpaque) -> Result<bool> {
match *self {}
}
pub fn as_array(&self, _store: impl AsContext) -> Result<Option<ArrayRef>> {
match *self {}
}
pub(crate) fn _as_array(&self, _store: &StoreOpaque) -> Result<Option<ArrayRef>> {
match *self {}
}
pub fn unwrap_array(&self, _store: impl AsContext) -> Result<ArrayRef> {
match *self {}
}
}
@@ -0,0 +1,48 @@
use crate::{
ArrayType, AsContext, AsContextMut, GcRefImpl, Result, Val,
store::{StoreContextMut, StoreOpaque},
};
/// Support for `ArrayRefPre` disabled at compile time because the `gc` cargo
/// feature was not enabled.
pub enum ArrayRefPre {}
/// Support for `arrayref` disabled at compile time because the `gc` cargo feature
/// was not enabled.
pub enum ArrayRef {}
impl GcRefImpl for ArrayRef {}
impl ArrayRef {
pub fn ty(&self, _store: impl AsContext) -> Result<ArrayType> {
match *self {}
}
pub fn matches_ty(&self, _store: impl AsContext, _ty: &ArrayType) -> Result<bool> {
match *self {}
}
pub(crate) fn _matches_ty(&self, _store: &StoreOpaque, _ty: &ArrayType) -> Result<bool> {
match *self {}
}
pub fn len(&self, _store: impl AsContext) -> Result<u32> {
match *self {}
}
pub fn elems<'a, T: 'static>(
&self,
_store: impl Into<StoreContextMut<'a, T>>,
) -> Result<impl ExactSizeIterator<Item = Val> + 'a + '_> {
match *self {}
Ok([].into_iter())
}
pub fn get(&self, _store: impl AsContextMut, _index: usize) -> Result<Val> {
match *self {}
}
pub fn set(&self, _store: impl AsContextMut, _index: usize, _value: Val) -> Result<()> {
match *self {}
}
}
@@ -0,0 +1,108 @@
use crate::{
ArrayRef, AsContext, GcRefImpl, HeapType, I31, OwnedRooted, Result, Rooted, StructRef,
store::StoreOpaque,
};
/// Support for `eqref` disabled at compile time because the `gc` cargo feature
/// was not enabled.
pub enum EqRef {}
impl From<Rooted<StructRef>> for Rooted<EqRef> {
#[inline]
fn from(s: Rooted<StructRef>) -> Self {
match s.inner {}
}
}
impl From<OwnedRooted<StructRef>> for OwnedRooted<EqRef> {
#[inline]
fn from(s: OwnedRooted<StructRef>) -> Self {
match s.inner {}
}
}
impl From<Rooted<ArrayRef>> for Rooted<EqRef> {
#[inline]
fn from(s: Rooted<ArrayRef>) -> Self {
match s.inner {}
}
}
impl From<OwnedRooted<ArrayRef>> for OwnedRooted<EqRef> {
#[inline]
fn from(s: OwnedRooted<ArrayRef>) -> Self {
match s.inner {}
}
}
impl GcRefImpl for EqRef {}
impl EqRef {
pub fn ty(&self, _store: impl AsContext) -> Result<HeapType> {
match *self {}
}
pub(crate) fn _ty(&self, _store: &StoreOpaque) -> Result<HeapType> {
match *self {}
}
pub fn matches_ty(&self, _store: impl AsContext, _ty: &HeapType) -> Result<bool> {
match *self {}
}
pub fn is_i31(&self, _store: impl AsContext) -> Result<bool> {
match *self {}
}
pub(crate) fn _is_i31(&self, _store: &StoreOpaque) -> Result<bool> {
match *self {}
}
pub fn as_i31(&self, _store: impl AsContext) -> Result<Option<I31>> {
match *self {}
}
pub fn unwrap_i31(&self, _store: impl AsContext) -> Result<I31> {
match *self {}
}
pub fn is_struct(&self, _store: impl AsContext) -> Result<bool> {
match *self {}
}
pub(crate) fn _is_struct(&self, _store: &StoreOpaque) -> Result<bool> {
match *self {}
}
pub fn as_struct(&self, _store: impl AsContext) -> Result<Option<StructRef>> {
match *self {}
}
pub(crate) fn _as_struct(&self, _store: &StoreOpaque) -> Result<Option<StructRef>> {
match *self {}
}
pub fn unwrap_struct(&self, _store: impl AsContext) -> Result<StructRef> {
match *self {}
}
pub fn is_array(&self, _store: impl AsContext) -> Result<bool> {
match *self {}
}
pub(crate) fn _is_array(&self, _store: &StoreOpaque) -> Result<bool> {
match *self {}
}
pub fn as_array(&self, _store: impl AsContext) -> Result<Option<ArrayRef>> {
match *self {}
}
pub(crate) fn _as_array(&self, _store: &StoreOpaque) -> Result<Option<ArrayRef>> {
match *self {}
}
pub fn unwrap_array(&self, _store: impl AsContext) -> Result<ArrayRef> {
match *self {}
}
}
@@ -0,0 +1,78 @@
//! `exnref` implementation stubs when GC is disabled.
use crate::{
AsContext, AsContextMut, ExnType, GcRefImpl, HeapType, Result, Rooted, Tag, Val,
store::{AutoAssertNoGc, StoreContextMut, StoreOpaque},
vm::VMGcRef,
};
/// Support for `ExnRefPre` disabled at compile time because the `gc`
/// cargo feature was not enabled.
pub enum ExnRefPre {}
/// Support for `exnref` disabled at compile time because the `gc`
/// cargo feature was not enabled.
pub enum ExnRef {}
impl GcRefImpl for ExnRef {}
impl ExnRef {
pub(crate) fn from_cloned_gc_ref(
_store: &mut AutoAssertNoGc<'_>,
_gc_ref: VMGcRef,
) -> Rooted<Self> {
unimplemented!()
}
pub fn from_raw(_store: impl AsContextMut, _raw: u32) -> Option<Rooted<Self>> {
None
}
pub(crate) fn _from_raw(_store: &mut AutoAssertNoGc, _raw: u32) -> Option<Rooted<Self>> {
None
}
pub fn to_raw(&self, _store: impl AsContextMut) -> Result<u32> {
Ok(0)
}
pub(crate) fn _to_raw(&self, _store: &mut AutoAssertNoGc<'_>) -> Result<u32> {
Ok(0)
}
pub fn ty(&self, _store: impl AsContext) -> Result<ExnType> {
match *self {}
}
pub(crate) fn _ty(&self, _store: &StoreOpaque) -> Result<ExnType> {
match *self {}
}
pub fn matches_ty(&self, _store: impl AsContext, _ty: &HeapType) -> Result<bool> {
match *self {}
}
pub(crate) fn _matches_ty(&self, _store: &StoreOpaque, _ty: &HeapType) -> Result<bool> {
match *self {}
}
pub fn tag(&self, _store: impl AsContextMut) -> Result<Tag> {
match *self {}
}
pub fn fields<'a, T: 'static>(
&self,
_store: impl Into<StoreContextMut<'a, T>>,
) -> Result<impl ExactSizeIterator<Item = Val> + 'a + '_> {
match *self {}
Ok([].into_iter())
}
pub fn field(&self, _store: impl AsContextMut, _index: usize) -> Result<Val> {
match *self {}
}
pub fn set_field(&self, _store: impl AsContextMut, _index: usize, _value: Val) -> Result<()> {
match *self {}
}
}
Loaded 100 of 266 files, more files were not shown because too many files have changed in this diff. Show more