mirror of
https://github.com/HeyPuter/gex.git
synced 2026-11-13 20:12:43 -10:00
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:
commit
fd8be1d161
266 files changed
+124167
-366
No files matched your search
Generated
+422
-300
File diff suppressed because it is too large.
Load diff
@@ -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" }
|
||||
@@ -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"] }
|
||||
@@ -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)
|
||||
|
||||
@@ -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"] }
|
||||
@@ -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"]
|
||||
@@ -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`.
|
||||
|
||||
@@ -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 => {
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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(())
|
||||
}
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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};
|
||||
|
||||
@@ -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
@@ -0,0 +1 @@
|
||||
{"v":1}
|
||||
+2362
File diff suppressed because it is too large.
Load diff
+563
@@ -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
@@ -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"
|
||||
]
|
||||
Vendored
+220
@@ -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
@@ -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
|
||||
Vendored
+137
@@ -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");
|
||||
}
|
||||
+8
@@ -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)])
|
||||
+9
@@ -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 }
|
||||
+1224
File diff suppressed because it is too large.
Load diff
@@ -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);
|
||||
}
|
||||
}
|
||||
+313
@@ -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
@@ -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
@@ -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 ¤t_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]]);
|
||||
}
|
||||
}
|
||||
+5141
File diff suppressed because it is too large.
Load diff
+1043
File diff suppressed because it is too large.
Load diff
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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]
|
||||
}
|
||||
}
|
||||
@@ -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(§ions, 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(§ion, 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()
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
bindgen!({
|
||||
inline: r#"
|
||||
package my:project;
|
||||
world hello-world {
|
||||
import name: func() -> string;
|
||||
export greet: func();
|
||||
}
|
||||
"#,
|
||||
});
|
||||
+17
@@ -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();
|
||||
}
|
||||
}
|
||||
"#,
|
||||
});
|
||||
+18
@@ -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();
|
||||
}
|
||||
}
|
||||
"#,
|
||||
});
|
||||
+20
@@ -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;
|
||||
}
|
||||
"#,
|
||||
});
|
||||
Vendored
+45
@@ -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,
|
||||
}
|
||||
Vendored
+29
@@ -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;
|
||||
}
|
||||
"#,
|
||||
});
|
||||
Vendored
+26
@@ -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);
|
||||
}
|
||||
}
|
||||
"#,
|
||||
});
|
||||
+47
@@ -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,
|
||||
}
|
||||
+23
@@ -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();
|
||||
}
|
||||
}
|
||||
+8
@@ -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, ¶ms, 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(¶ms).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, ¶ms)
|
||||
})?;
|
||||
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()
|
||||
}
|
||||
}
|
||||
+458
@@ -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")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+5113
File diff suppressed because it is too large.
Load diff
Vendored
+500
@@ -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, ¶ms_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(
|
||||
¶m_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)
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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>")
|
||||
}
|
||||
}
|
||||
+1300
File diff suppressed because it is too large.
Load diff
@@ -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")
|
||||
}
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 }
|
||||
}
|
||||
}
|
||||
+1026
File diff suppressed because it is too large.
Load diff
+2834
File diff suppressed because it is too large.
Load diff
@@ -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
@@ -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))
|
||||
}
|
||||
}
|
||||
@@ -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
Reference in new issue
Block a user