Merge master (fd8be1d1: wasmtime 49.0.2) into merge/unity

wasmtime 27 and its vendored copy are gone. U's additions follow master's
port: host functions of the 64-bit provider imports (libprov register64,
wasmjit) return wasmtime::Result through crate::host; the 64-bit memory's
reservation is Config::memory_reservation; keep_bad and fjit's compile
convert wasmtime::Error to anyhow.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Claude-Session: https://claude.ai/code/session_01PeRutwFgZ66XpxjasKrPn1
This commit is contained in:
velzieandClaude Opus 5.5 committed 2026-10-05 01:34:58 -04:00
commit 3718d0ca10
346 files changed
+88421 -40154

No files matched your search

Generated
+422 -298
View File
File diff suppressed because it is too large. Load diff
+2 -2
View File
@@ -75,10 +75,10 @@ gex-eject = { path = "crates/gex-eject" }
opt-level = 3
debug = 1
# wasmtime 27 with one change (third_party/wasmtime-27.0.0/src/runtime/store.rs,
# 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-27.0.0" }
wasmtime = { path = "third_party/wasmtime-49.0.2" }
+1 -1
View File
@@ -6,4 +6,4 @@ edition.workspace = true
[dependencies]
anyhow = "1"
gex-key.workspace = true
wasmtime = { version = "27", default-features = false, features = ["cranelift", "runtime", "std", "threads"] }
wasmtime = { version = "49", default-features = false, features = ["cranelift", "runtime", "std", "threads", "gc", "gc-null", "anyhow"] }
+4 -4
View File
@@ -264,8 +264,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);
@@ -297,8 +297,8 @@ pub fn load_or_compile_at(engine: &Engine, path: &Path, wasm: &[u8]) -> Result<M
}
}
}
let m = Module::new(engine, wasm).context("validating the module")?;
if let (Ok(bytes), Some(dir)) = (m.serialize(), path.parent()) {
let m = Module::new(engine, wasm).map_err(anyhow::Error::from).context("validating the module")?;
if let (Ok(bytes), Some(dir)) = (m.serialize().map_err(anyhow::Error::from), path.parent()) {
store(dir, path, &bytes);
}
Ok(m)
+1 -1
View File
@@ -4,4 +4,4 @@ version.workspace = true
edition.workspace = true
[dependencies]
wasmtime = { version = "27", default-features = false, features = ["cranelift", "runtime", "std", "threads"] }
wasmtime = { version = "49", default-features = false, features = ["cranelift", "runtime", "std", "threads", "gc", "gc-null", "anyhow"] }
+6
View File
@@ -0,0 +1,6 @@
# wasmtime 49 needs rustc 1.96 (the native engine, with wasm exception
# handling for native Wine's setjmp: docs/wine-native.md §4.2).
[toolchain]
channel = "1.96.0"
# the browser shim (shims/browser) builds for wasm32-unknown-unknown
targets = ["wasm32-unknown-unknown"]
+1 -1
View File
@@ -28,7 +28,7 @@ anyhow = "1"
# module with one function per guest basic block -- fifty thousand of them for
# bash, half a million for Mesa -- which is close to the ideal shape for
# compiling in parallel, and it was using one core out of twenty-four.
wasmtime = { version = "27", default-features = false, features = ["cranelift", "runtime", "std", "threads", "parallel-compilation"] }
wasmtime = { version = "49", default-features = false, features = ["cranelift", "runtime", "std", "threads", "gc", "gc-null", "anyhow", "parallel-compilation"] }
[features]
# Host an x86-64 guest. See `gex_abi::X86`.
+8 -2
View File
@@ -28,6 +28,7 @@
//! [`REPL_THREADS`] stacks of [`REPL_STACK_BYTES`], one per guest thread by
//! context slot. The region is recorded as a mapping so `fork` copies it.
use crate::WasmtimeContext;
use super::{read_u64, write_u64, HostState};
use anyhow::{anyhow, bail, Context, Result};
use gex_abi::ctx;
@@ -499,6 +500,11 @@ fn engine_config() -> wasmtime::Config {
let mut config = wasmtime::Config::new();
config.wasm_tail_call(true);
config.wasm_threads(true);
// wasmtime 49 creates no SharedMemory unless asked: the guest's memory is one.
config.shared_memory(true);
// Wasm exception handling: setjmp/longjmp in native Wine and wasm32
// programs (clang's SjLj lowering), docs/wine-native.md §4.2.
config.wasm_exceptions(true);
config.wasm_bulk_memory(true);
config.wasm_multi_memory(true);
config
@@ -609,9 +615,9 @@ pub(crate) fn call(caller: &mut Caller<'_, HostState>, ctx_addr: i32, pc: u64, i
Abi::Ctx | Abi::Entry => {
if let Err(e) = func.call(&mut *caller, &[Val::I32(ctx_addr)], &mut []) {
if e.is::<super::StopExecution>() {
return Err(e);
return Err(anyhow::Error::from(e));
}
return Err(e.context(format!("replacement `{}` for {}", r.export, r.symbol)));
return Err(anyhow::Error::from(e).context(format!("replacement `{}` for {}", r.export, r.symbol)));
}
}
Abi::C => {
+2 -2
View File
@@ -104,14 +104,14 @@ pub(crate) fn override_imports(linker: &mut Linker<HostState>) -> Result<()> {
linker.func_wrap(
IMPORT_MODULE,
imports::DISPATCH_MISS,
|mut caller: Caller<'_, HostState>, _ctx: i32, pc: i64| -> Result<i32> { Err(missing(&mut caller, pc as u64, "a dispatch")) },
|mut caller: Caller<'_, HostState>, _ctx: i32, pc: i64| -> wasmtime::Result<i32> { crate::host((|| -> anyhow::Result<i32> { Err(missing(&mut caller, pc as u64, "a dispatch")) })()) },
)?;
// Leave: the root looks the target up, and stops the run if nothing
// built ahead of time holds it.
linker.func_wrap(
IMPORT_MODULE,
imports::CALL_OUT,
|_caller: Caller<'_, HostState>, _ctx: i32, _target: i64, _ret: i64, _sp: i64| -> Result<i32> { Ok(0) },
|_caller: Caller<'_, HostState>, _ctx: i32, _target: i64, _ret: i64, _sp: i64| -> wasmtime::Result<i32> { crate::host((|| -> anyhow::Result<i32> { Ok(0) })()) },
)?;
Ok(())
}
+4 -4
View File
@@ -210,14 +210,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;
@@ -753,7 +753,7 @@ fn compile_translation(j: &Job, wasm: &[u8]) -> Result<Module> {
let key = gex_cache::key(build, [b"fjit".as_slice(), wasm]);
gex_cache::load_or_compile_at(&j.engine, &dir.join("fjit").join(format!("{key}.cwasm")), wasm)
}
None => Module::new(&j.engine, wasm),
None => Module::new(&j.engine, wasm).map_err(anyhow::Error::from),
}
}
+67 -33
View File
@@ -29,6 +29,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 {
@@ -556,6 +580,11 @@ 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);
@@ -564,7 +593,7 @@ pub fn validate_module(wasm: &[u8]) -> Result<()> {
config.wasm_memory64(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)> {
@@ -602,6 +631,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
@@ -612,6 +643,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);
@@ -620,7 +656,7 @@ pub fn engine() -> Result<Engine> {
// wasm32 process keeps the engine it always had.
if gex_abi::mem64() {
config.wasm_memory64(true);
config.static_memory_maximum_size(gex_abi::LIMIT64);
config.memory_reservation(gex_abi::LIMIT64);
}
// gex's output is already optimised; Cranelift only adds register
// allocation and peepholes, whose time dominates on a large program (half
@@ -1569,11 +1605,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)?;
}
@@ -2794,7 +2828,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");
@@ -3046,27 +3080,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.
@@ -3167,13 +3201,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() {
@@ -3229,13 +3263,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.
@@ -3342,7 +3376,7 @@ fn register_imports(linker: &mut Linker<HostState>) -> Result<()> {
internal: false,
});
Err(StopExecution.into())
},
})()) },
)?;
linker.func_wrap(
@@ -3371,7 +3405,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);
@@ -3431,13 +3465,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()
@@ -3552,7 +3586,7 @@ fn register_imports(linker: &mut Linker<HostState>) -> Result<()> {
)?;
}
Ok(())
},
})()) },
)?;
// Fused multiply-add. The host has one; wasm does not, and a multiply
@@ -3584,7 +3618,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()
@@ -3596,13 +3630,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);
}
@@ -3681,7 +3715,7 @@ fn register_imports(linker: &mut Linker<HostState>) -> Result<()> {
Err(StopExecution.into())
}
}
},
})()) },
)?;
Ok(())
@@ -3845,7 +3879,7 @@ fn translate_and_install(caller: &mut Caller<'_, HostState>, pc: u64) -> Result<
l.module
}
None => Module::new(&engine, &translated_module.wasm)
.map_err(keep_bad)
.map_err(|e| keep_bad(anyhow::Error::from(e)))
.context("validating runtime-translated wasm")?,
};
jit_stats::COMPILE_US.fetch_add(t0.elapsed().as_micros() as u64, std::sync::atomic::Ordering::Relaxed);
@@ -4254,7 +4288,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;
@@ -4281,7 +4315,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
}
@@ -4412,7 +4446,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 {
@@ -4427,12 +4461,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);
@@ -4447,7 +4481,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() {
+30 -30
View File
@@ -302,18 +302,18 @@ pub(crate) fn register(linker: &mut Linker<HostState>) -> Result<()> {
if gex_abi::mem64() {
return register64(linker);
}
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 {
@@ -329,7 +329,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() {
@@ -337,16 +337,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;
@@ -354,7 +354,7 @@ pub(crate) fn register(linker: &mut Linker<HostState>) -> Result<()> {
}
}
Ok(())
})?;
})()) })?;
Ok(())
}
@@ -362,20 +362,20 @@ pub(crate) fn register(linker: &mut Linker<HostState>) -> Result<()> {
/// addresses, sizes and function-table indices (stream U, additive).
fn register64(linker: &mut Linker<HostState>) -> Result<()> {
crate::wasmjit::register64(linker)?;
linker.func_wrap(IMPORT_MODULE, "provider_try", |mut caller: Caller<'_, HostState>, f: i64, arg: i64| -> Result<i32> {
linker.func_wrap(IMPORT_MODULE, "provider_try", |mut caller: Caller<'_, HostState>, f: i64, arg: i64| -> wasmtime::Result<i32> { crate::host((|| -> anyhow::Result<i32> {
let func = ftab::table_func(&mut caller, f as u32)?;
let typed = func.typed::<i64, ()>(&caller).context("gex.provider_try: the function is not void(void *)")?;
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()) })()) })?;
linker.func_wrap(IMPORT_MODULE, "provider_ctx", |caller: Caller<'_, HostState>| -> i32 { caller.data().thread.ctx_addr as i32 })?;
linker.func_wrap(IMPORT_MODULE, "provider_missing", |mut caller: Caller<'_, HostState>, addr: i64| -> Result<()> {
linker.func_wrap(IMPORT_MODULE, "provider_missing", |mut caller: Caller<'_, HostState>, addr: i64| -> wasmtime::Result<()> { crate::host((|| -> anyhow::Result<()> {
let addr = addr 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() {
@@ -383,11 +383,11 @@ fn register64(linker: &mut Linker<HostState>) -> Result<()> {
None => format!("{addr:#x}"),
};
Err(crate::aot_only::stop(&mut caller, addr, &format!("gex: 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: i64| -> Result<i64> {
})()) })?;
linker.func_wrap(IMPORT_MODULE, "provider_alloc", |mut caller: Caller<'_, HostState>, size: i64| -> wasmtime::Result<i64> { crate::host((|| -> anyhow::Result<i64> {
Ok(alloc(caller.data_mut(), size as u64, "[provider heap]")? as i64)
})?;
linker.func_wrap(IMPORT_MODULE, "provider_log", |caller: Caller<'_, HostState>, ptr: i64, len: i64| -> Result<()> {
})()) })?;
linker.func_wrap(IMPORT_MODULE, "provider_log", |caller: Caller<'_, HostState>, ptr: i64, len: i64| -> wasmtime::Result<()> { crate::host((|| -> anyhow::Result<()> {
if let Some(m) = caller.data().memory.as_ref() {
if let Some(b) = m.bytes().get(ptr as usize..(ptr as usize).saturating_add(len as usize)) {
use std::io::Write;
@@ -395,10 +395,10 @@ fn register64(linker: &mut Linker<HostState>) -> Result<()> {
}
}
Ok(())
})?;
})()) })?;
// This thread's provider C stack (`stack`): its lowest address; it is
// `STACK` bytes. musl reports it as the thread's stack.
linker.func_wrap(IMPORT_MODULE, "provider_stack", |mut caller: Caller<'_, HostState>| -> Result<i64> {
linker.func_wrap(IMPORT_MODULE, "provider_stack", |mut caller: Caller<'_, HostState>| -> wasmtime::Result<i64> { crate::host((|| -> anyhow::Result<i64> {
use wasmtime::AsContextMut;
let mut cx = caller.as_context_mut();
let g = stack(&mut cx)?;
@@ -408,7 +408,7 @@ fn register64(linker: &mut Linker<HostState>) -> Result<()> {
_ => 0,
};
Ok((top + 16 - STACK) as i64)
})?;
})()) })?;
// Constants of gex's ABI a provider calling guest code needs.
linker.func_wrap(IMPORT_MODULE, "provider_abi", |_caller: Caller<'_, HostState>, which: i32| -> i64 {
match which {
@@ -428,7 +428,7 @@ fn register64(linker: &mut Linker<HostState>) -> Result<()> {
// The guest address of the function `name` exports from the first
// mapped Linux object that defines it (as RTLD_DEFAULT would find it), or
// 0: how a provider finds the guest's own dlopen/dlsym.
linker.func_wrap(IMPORT_MODULE, "provider_guest_sym", |mut caller: Caller<'_, HostState>, ptr: i64, len: i64| -> Result<i64> {
linker.func_wrap(IMPORT_MODULE, "provider_guest_sym", |mut caller: Caller<'_, HostState>, ptr: i64, len: i64| -> wasmtime::Result<i64> { crate::host((|| -> anyhow::Result<i64> {
let name = {
let m = caller.data().memory.clone().ok_or_else(|| anyhow!("guest memory unavailable"))?;
let b = m.bytes();
@@ -436,7 +436,7 @@ fn register64(linker: &mut Linker<HostState>) -> Result<()> {
String::from_utf8_lossy(bytes).into_owned()
};
Ok(guest_sym(caller.data_mut(), &name).unwrap_or(0) as i64)
})?;
})()) })?;
// Stopping the guest's threads around a provider runtime's collection
// (`GuestStop`).
linker.func_wrap(IMPORT_MODULE, "provider_thread_register", |caller: Caller<'_, HostState>, in_guest: i64, stop: i64| {
@@ -448,16 +448,16 @@ fn register64(linker: &mut Linker<HostState>) -> Result<()> {
guest_stop().park_if_stopping(caller.data().thread.tid as u32, m.bytes(), true);
}
})?;
linker.func_wrap(IMPORT_MODULE, "provider_stop_guest", |caller: Caller<'_, HostState>| -> Result<()> {
linker.func_wrap(IMPORT_MODULE, "provider_stop_guest", |caller: Caller<'_, HostState>| -> wasmtime::Result<()> { crate::host((|| -> anyhow::Result<()> {
let m = caller.data().memory.clone().ok_or_else(|| anyhow!("guest memory unavailable"))?;
guest_stop().stop(caller.data().thread.tid as u32, m.bytes());
Ok(())
})?;
linker.func_wrap(IMPORT_MODULE, "provider_start_guest", |caller: Caller<'_, HostState>| -> Result<()> {
})()) })?;
linker.func_wrap(IMPORT_MODULE, "provider_start_guest", |caller: Caller<'_, HostState>| -> wasmtime::Result<()> { crate::host((|| -> anyhow::Result<()> {
let m = caller.data().memory.clone().ok_or_else(|| anyhow!("guest memory unavailable"))?;
guest_stop().start(m.bytes());
Ok(())
})?;
})()) })?;
// The end of the mapping holding guest stack pointer `sp` (a thread's
// stack top, for scanning it).
linker.func_wrap(IMPORT_MODULE, "provider_guest_stack_end", |mut caller: Caller<'_, HostState>, sp: i64| -> i64 {
+7 -7
View File
@@ -709,7 +709,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 {
@@ -722,12 +722,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);
@@ -742,7 +742,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?;
@@ -920,7 +920,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
@@ -934,7 +934,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));
}
}
}
@@ -955,6 +955,6 @@ fn nested_loop(caller: &mut Caller<'_, HostState>, p: &Arc<nt::Process>) -> Resu
pc = read_u64(memory.bytes(), (ctx_addr + ctx::PC as u64) as usize);
continue;
}
return Err(e);
return Err(anyhow::Error::from(e));
}
}
+1
View File
@@ -15,6 +15,7 @@
//! its counts in at its next safepoint or syscall; a thread blocked forever in
//! the kernel contributes when it wakes or ends.
use crate::WasmtimeContext;
use crate::HostState;
use anyhow::{anyhow, Context, Result};
use gex_xlate::profile::{self as gp, FileEntry, ObjectEntry, Placements, Profile, Session};
+6 -7
View File
@@ -218,8 +218,7 @@ pub(crate) fn register64(linker: &mut Linker<HostState>) -> Result<()> {
names: i64,
n: i64,
errbuf: i64,
errcap: i64|
-> Result<i32> {
errcap: i64| -> wasmtime::Result<i32> { crate::host((|| -> anyhow::Result<i32> {
match instantiate(&mut caller, bytes, len, cnames, consts, nconsts, slots, names, n) {
Ok(()) => {
if trace() {
@@ -236,9 +235,9 @@ pub(crate) fn register64(linker: &mut Linker<HostState>) -> Result<()> {
Ok(0)
}
}
},
})()) },
)?;
linker.func_wrap(IMPORT_MODULE, "provider_jit_table", |mut caller: Caller<'_, HostState>, op: i32, a: i64, b: i64| -> Result<i64> {
linker.func_wrap(IMPORT_MODULE, "provider_jit_table", |mut caller: Caller<'_, HostState>, op: i32, a: i64, b: i64| -> wasmtime::Result<i64> { crate::host((|| -> anyhow::Result<i64> {
crate::ftab::catch_up(&mut caller)?;
let table = table_of(&caller)?;
let (a, b) = (a as u64, b as u64);
@@ -264,8 +263,8 @@ pub(crate) fn register64(linker: &mut Linker<HostState>) -> Result<()> {
}
_ => bail!("provider_jit_table: unknown op {op}"),
}
})?;
linker.func_wrap(IMPORT_MODULE, "provider_jit_reserve", |mut caller: Caller<'_, HostState>, n: i64, _fill: i64| -> Result<i64> {
})()) })?;
linker.func_wrap(IMPORT_MODULE, "provider_jit_reserve", |mut caller: Caller<'_, HostState>, n: i64, _fill: i64| -> wasmtime::Result<i64> { crate::host((|| -> anyhow::Result<i64> {
let (shared, engine) = {
let jit = caller.data().jit.as_ref().ok_or_else(|| anyhow!("wasmjit: no function table"))?;
(Arc::clone(&jit.shared), jit.engine.clone())
@@ -279,6 +278,6 @@ pub(crate) fn register64(linker: &mut Linker<HostState>) -> Result<()> {
eprintln!("gex-wasmjit: reserved {n} table slots at {base}");
}
Ok(base as i64)
})?;
})()) })?;
Ok(())
}
+24
View File
@@ -0,0 +1,24 @@
/* setjmp/longjmp in a wasm32 program: clang lowers them onto wasm
* exception handling (needs an engine with it: wasmtime 49 natively). */
#include <setjmp.h>
#include <stdio.h>
static jmp_buf jb;
static int depth;
__attribute__((noinline)) static void dive(int n)
{
depth = n;
if (n == 5) longjmp(jb, 42);
dive(n + 1);
}
int main(void)
{
volatile int count = 0;
int r = setjmp(jb);
count++;
if (!r) dive(0);
printf("longjmp returned %d after depth %d, setjmp returned %d times\n", r, depth, count);
return 0;
}
-38
View File
@@ -1,38 +0,0 @@
fn main() {
println!("cargo:rerun-if-changed=build.rs");
#[cfg(feature = "runtime")]
build_c_helpers();
}
#[cfg(feature = "runtime")]
fn build_c_helpers() {
use wasmtime_versioned_export_macros::versioned_suffix;
// NB: duplicating a workaround in the wasmtime-fiber build script.
println!("cargo:rustc-check-cfg=cfg(asan)");
match std::env::var("CARGO_CFG_SANITIZE") {
Ok(s) if s == "address" => {
println!("cargo:rustc-cfg=asan");
}
_ => {}
}
// If this platform is neither unix nor windows then there's no default need
// for a C helper library since `helpers.c` is tailored for just these
// platforms currently.
if std::env::var("CARGO_CFG_UNIX").is_err() && std::env::var("CARGO_CFG_WINDOWS").is_err() {
return;
}
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!()));
println!("cargo:rerun-if-changed=src/runtime/vm/helpers.c");
build.file("src/runtime/vm/helpers.c");
build.compile("wasmtime-helpers");
}
-914
View File
@@ -1,914 +0,0 @@
//! Wasm compilation orchestration.
//!
//! It works roughly like this:
//!
//! * We walk over the Wasm module/component and make a list of all the things
//! we need to compile. This is a `CompileInputs`.
//!
//! * The `CompileInputs::compile` method compiles each of these in parallel,
//! producing a `UnlinkedCompileOutputs`. This is an unlinked set of compiled
//! functions, bucketed by type of function.
//!
//! * The `UnlinkedCompileOutputs::pre_link` method re-arranges the compiled
//! functions into a flat list. This is the order we will place them within
//! the ELF file, so we must also keep track of all the functions' indices
//! within this list, because we will need them for resolving
//! relocations. These indices are kept track of in the resulting
//! `FunctionIndices`.
//!
//! * The `FunctionIndices::link_and_append_code` method appends the functions
//! to the given ELF file and resolves relocations. It produces an `Artifacts`
//! which contains the data needed at runtime to find and call Wasm
//! functions. It is up to the caller to serialize the relevant parts of the
//! `Artifacts` into the ELF file.
use crate::hash_map::HashMap;
use crate::hash_set::HashSet;
use crate::prelude::*;
use crate::Engine;
use std::{
any::Any,
borrow::Cow,
collections::{btree_map, BTreeMap, BTreeSet},
mem,
};
#[cfg(feature = "component-model")]
use wasmtime_environ::component::Translator;
use wasmtime_environ::{
BuiltinFunctionIndex, CompiledFunctionInfo, CompiledModuleInfo, Compiler, DefinedFuncIndex,
FinishedObject, FunctionBodyData, ModuleEnvironment, ModuleInternedTypeIndex,
ModuleTranslation, ModuleTypes, ModuleTypesBuilder, ObjectKind, PrimaryMap, RelocationTarget,
StaticModuleIndex, WasmFunctionInfo,
};
mod code_builder;
pub use self::code_builder::{CodeBuilder, CodeHint, HashedEngineCompileEnv};
#[cfg(feature = "runtime")]
mod runtime;
/// Converts an input binary-encoded WebAssembly module to compilation
/// artifacts and type information.
///
/// This is where compilation actually happens of WebAssembly modules and
/// translation/parsing/validation of the binary input occurs. The binary
/// artifact represented in the `MmapVec` returned here is an in-memory ELF
/// file in an owned area of virtual linear memory where permissions (such
/// as the executable bit) can be applied.
///
/// Additionally compilation returns an `Option` here which is always
/// `Some`, notably compiled metadata about the module in addition to the
/// type information found within.
pub(crate) fn build_artifacts<T: FinishedObject>(
engine: &Engine,
wasm: &[u8],
dwarf_package: Option<&[u8]>,
) -> Result<(T, Option<(CompiledModuleInfo, ModuleTypes)>)> {
let tunables = engine.tunables();
// First a `ModuleEnvironment` is created which records type information
// about the wasm module. This is where the WebAssembly is parsed and
// validated. Afterwards `types` will have all the type information for
// this module.
let mut parser = wasmparser::Parser::new(0);
let mut validator = wasmparser::Validator::new_with_features(engine.features());
parser.set_features(*validator.features());
let mut types = ModuleTypesBuilder::new(&validator);
let mut translation = ModuleEnvironment::new(tunables, &mut validator, &mut types)
.translate(parser, wasm)
.context("failed to parse WebAssembly module")?;
let functions = mem::take(&mut translation.function_body_inputs);
let compile_inputs =
CompileInputs::for_module(engine.compiler().triple(), &types, &translation, functions);
let unlinked_compile_outputs = compile_inputs.compile(engine)?;
let (compiled_funcs, function_indices) = unlinked_compile_outputs.pre_link();
// Emplace all compiled functions into the object file with any other
// sections associated with code as well.
let mut object = engine.compiler().object(ObjectKind::Module)?;
// Insert `Engine` and type-level information into the compiled
// artifact so if this module is deserialized later it contains all
// information necessary.
//
// Note that `append_compiler_info` and `append_types` here in theory
// can both be skipped if this module will never get serialized.
// They're only used during deserialization and not during runtime for
// the module itself. Currently there's no need for that, however, so
// it's left as an exercise for later.
engine.append_compiler_info(&mut object);
engine.append_bti(&mut object);
let (mut object, compilation_artifacts) = function_indices.link_and_append_code(
&types,
object,
engine,
compiled_funcs,
std::iter::once(translation).collect(),
dwarf_package,
)?;
let info = compilation_artifacts.unwrap_as_module_info();
let types = types.finish();
object.serialize_info(&(&info, &types));
let result = T::finish_object(object)?;
Ok((result, Some((info, types))))
}
/// Performs the compilation phase for a component, translating and
/// validating the provided wasm binary to machine code.
///
/// This method will compile all nested core wasm binaries in addition to
/// any necessary extra functions required for operation with components.
/// The output artifact here is the serialized object file contained within
/// an owned mmap along with metadata about the compilation itself.
#[cfg(feature = "component-model")]
pub(crate) fn build_component_artifacts<T: FinishedObject>(
engine: &Engine,
binary: &[u8],
_dwarf_package: Option<&[u8]>,
) -> Result<(T, Option<wasmtime_environ::component::ComponentArtifacts>)> {
use wasmtime_environ::component::{
CompiledComponentInfo, ComponentArtifacts, ComponentTypesBuilder,
};
use wasmtime_environ::ScopeVec;
let tunables = engine.tunables();
let compiler = engine.compiler();
let scope = ScopeVec::new();
let mut validator = wasmparser::Validator::new_with_features(engine.features());
let mut types = ComponentTypesBuilder::new(&validator);
let (component, mut module_translations) =
Translator::new(tunables, &mut validator, &mut types, &scope)
.translate(binary)
.context("failed to parse WebAssembly module")?;
let compile_inputs = CompileInputs::for_component(
engine,
&types,
&component,
module_translations.iter_mut().map(|(i, translation)| {
let functions = mem::take(&mut translation.function_body_inputs);
(i, &*translation, functions)
}),
);
let unlinked_compile_outputs = compile_inputs.compile(&engine)?;
let (compiled_funcs, function_indices) = unlinked_compile_outputs.pre_link();
let mut object = compiler.object(ObjectKind::Component)?;
engine.append_compiler_info(&mut object);
engine.append_bti(&mut object);
let (mut object, compilation_artifacts) = function_indices.link_and_append_code(
types.module_types_builder(),
object,
engine,
compiled_funcs,
module_translations,
None, // TODO: Support dwarf packages for components.
)?;
let (types, ty) = types.finish(&component.component);
let info = CompiledComponentInfo {
component: component.component,
trampolines: compilation_artifacts.trampolines,
resource_drop_wasm_to_array_trampoline: compilation_artifacts
.resource_drop_wasm_to_array_trampoline,
};
let artifacts = ComponentArtifacts {
info,
ty,
types,
static_modules: compilation_artifacts.modules,
};
object.serialize_info(&artifacts);
let result = T::finish_object(object)?;
Ok((result, Some(artifacts)))
}
type CompileInput<'a> = Box<dyn FnOnce(&dyn Compiler) -> Result<CompileOutput> + Send + 'a>;
/// A sortable, comparable key for a compilation output.
///
/// Two `u32`s to align with `cranelift_codegen::ir::UserExternalName`.
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
struct CompileKey {
// The namespace field is bitpacked like:
//
// [ kind:i3 module:i29 ]
namespace: u32,
index: u32,
}
impl CompileKey {
const KIND_BITS: u32 = 3;
const KIND_OFFSET: u32 = 32 - Self::KIND_BITS;
const KIND_MASK: u32 = ((1 << Self::KIND_BITS) - 1) << Self::KIND_OFFSET;
fn kind(&self) -> u32 {
self.namespace & Self::KIND_MASK
}
fn module(&self) -> StaticModuleIndex {
StaticModuleIndex::from_u32(self.namespace & !Self::KIND_MASK)
}
const WASM_FUNCTION_KIND: u32 = Self::new_kind(0);
const ARRAY_TO_WASM_TRAMPOLINE_KIND: u32 = Self::new_kind(1);
const WASM_TO_ARRAY_TRAMPOLINE_KIND: u32 = Self::new_kind(2);
const WASM_TO_BUILTIN_TRAMPOLINE_KIND: u32 = Self::new_kind(3);
const fn new_kind(kind: u32) -> u32 {
assert!(kind < (1 << Self::KIND_BITS));
kind << Self::KIND_OFFSET
}
// NB: more kinds in the other `impl` block.
fn wasm_function(module: StaticModuleIndex, index: DefinedFuncIndex) -> Self {
debug_assert_eq!(module.as_u32() & Self::KIND_MASK, 0);
Self {
namespace: Self::WASM_FUNCTION_KIND | module.as_u32(),
index: index.as_u32(),
}
}
fn array_to_wasm_trampoline(module: StaticModuleIndex, index: DefinedFuncIndex) -> Self {
debug_assert_eq!(module.as_u32() & Self::KIND_MASK, 0);
Self {
namespace: Self::ARRAY_TO_WASM_TRAMPOLINE_KIND | module.as_u32(),
index: index.as_u32(),
}
}
fn wasm_to_array_trampoline(index: ModuleInternedTypeIndex) -> Self {
Self {
namespace: Self::WASM_TO_ARRAY_TRAMPOLINE_KIND,
index: index.as_u32(),
}
}
fn wasm_to_builtin_trampoline(index: BuiltinFunctionIndex) -> Self {
Self {
namespace: Self::WASM_TO_BUILTIN_TRAMPOLINE_KIND,
index: index.index(),
}
}
}
#[cfg(feature = "component-model")]
impl CompileKey {
const TRAMPOLINE_KIND: u32 = Self::new_kind(4);
const RESOURCE_DROP_WASM_TO_ARRAY_KIND: u32 = Self::new_kind(5);
fn trampoline(index: wasmtime_environ::component::TrampolineIndex) -> Self {
Self {
namespace: Self::TRAMPOLINE_KIND,
index: index.as_u32(),
}
}
fn resource_drop_wasm_to_array_trampoline() -> Self {
Self {
namespace: Self::RESOURCE_DROP_WASM_TO_ARRAY_KIND,
index: 0,
}
}
}
#[derive(Clone, Copy)]
enum CompiledFunction<T> {
Function(T),
#[cfg(feature = "component-model")]
AllCallFunc(wasmtime_environ::component::AllCallFunc<T>),
}
impl<T> CompiledFunction<T> {
fn unwrap_function(self) -> T {
match self {
Self::Function(f) => f,
#[cfg(feature = "component-model")]
Self::AllCallFunc(_) => panic!("CompiledFunction::unwrap_function"),
}
}
#[cfg(feature = "component-model")]
fn unwrap_all_call_func(self) -> wasmtime_environ::component::AllCallFunc<T> {
match self {
Self::AllCallFunc(f) => f,
Self::Function(_) => panic!("CompiledFunction::unwrap_all_call_func"),
}
}
}
#[cfg(feature = "component-model")]
impl<T> From<wasmtime_environ::component::AllCallFunc<T>> for CompiledFunction<T> {
fn from(f: wasmtime_environ::component::AllCallFunc<T>) -> Self {
Self::AllCallFunc(f)
}
}
struct CompileOutput {
key: CompileKey,
symbol: String,
function: CompiledFunction<Box<dyn Any + Send>>,
info: Option<WasmFunctionInfo>,
}
/// The collection of things we need to compile for a Wasm module or component.
#[derive(Default)]
struct CompileInputs<'a> {
// Whether or not we need to compile wasm-to-native trampolines.
need_wasm_to_array_trampolines: bool,
inputs: Vec<CompileInput<'a>>,
}
impl<'a> CompileInputs<'a> {
fn push_input(&mut self, f: impl FnOnce(&dyn Compiler) -> Result<CompileOutput> + Send + 'a) {
self.inputs.push(Box::new(f));
}
/// Create the `CompileInputs` for a core Wasm module.
fn for_module(
triple: &target_lexicon::Triple,
types: &'a ModuleTypesBuilder,
translation: &'a ModuleTranslation<'a>,
functions: PrimaryMap<DefinedFuncIndex, FunctionBodyData<'a>>,
) -> Self {
let mut ret = CompileInputs {
need_wasm_to_array_trampolines: !matches!(
triple.architecture,
target_lexicon::Architecture::Pulley32 | target_lexicon::Architecture::Pulley64
),
inputs: vec![],
};
let module_index = StaticModuleIndex::from_u32(0);
ret.collect_inputs_in_translations(types, [(module_index, translation, functions)]);
ret
}
/// Create a `CompileInputs` for a component.
#[cfg(feature = "component-model")]
fn for_component(
engine: &'a Engine,
types: &'a wasmtime_environ::component::ComponentTypesBuilder,
component: &'a wasmtime_environ::component::ComponentTranslation,
module_translations: impl IntoIterator<
Item = (
StaticModuleIndex,
&'a ModuleTranslation<'a>,
PrimaryMap<DefinedFuncIndex, FunctionBodyData<'a>>,
),
>,
) -> Self {
let triple = engine.compiler().triple();
let mut ret = CompileInputs {
need_wasm_to_array_trampolines: !matches!(
triple.architecture,
target_lexicon::Architecture::Pulley32 | target_lexicon::Architecture::Pulley64
),
inputs: vec![],
};
ret.collect_inputs_in_translations(types.module_types_builder(), module_translations);
let tunables = engine.tunables();
for (idx, trampoline) in component.trampolines.iter() {
ret.push_input(move |compiler| {
Ok(CompileOutput {
key: CompileKey::trampoline(idx),
symbol: trampoline.symbol_name(),
function: compiler
.component_compiler()
.compile_trampoline(component, types, idx, tunables)?
.into(),
info: None,
})
});
}
// If there are any resources defined within this component, the
// signature for `resource.drop` is mentioned somewhere, and the
// wasm-to-native trampoline for `resource.drop` hasn't been created yet
// then insert that here. This is possibly required by destruction of
// resources from the embedder and otherwise won't be explicitly
// requested through initializers above or such.
if component.component.num_resources > 0 {
if let Some(sig) = types.find_resource_drop_signature() {
ret.push_input(move |compiler| {
let trampoline =
compiler.compile_wasm_to_array_trampoline(types[sig].unwrap_func())?;
Ok(CompileOutput {
key: CompileKey::resource_drop_wasm_to_array_trampoline(),
symbol: "resource_drop_trampoline".to_string(),
function: CompiledFunction::Function(trampoline),
info: None,
})
});
}
}
ret
}
fn clean_symbol(name: &str) -> Cow<str> {
/// Maximum length of symbols generated in objects.
const MAX_SYMBOL_LEN: usize = 96;
// Just to be on the safe side, filter out characters that could
// pose issues to tools such as "perf" or "objdump". To avoid
// having to update a list of allowed characters for each different
// language that compiles to Wasm, allows only graphic ASCII
// characters; replace runs of everything else with a "?".
let bad_char = |c: char| !c.is_ascii_graphic();
if name.chars().any(bad_char) {
let mut last_char_seen = '\u{0000}';
Cow::Owned(
name.chars()
.map(|c| if bad_char(c) { '?' } else { c })
.filter(|c| {
let skip = last_char_seen == '?' && *c == '?';
last_char_seen = *c;
!skip
})
.take(MAX_SYMBOL_LEN)
.collect::<String>(),
)
} else if name.len() <= MAX_SYMBOL_LEN {
Cow::Borrowed(&name[..])
} else {
Cow::Borrowed(&name[..MAX_SYMBOL_LEN])
}
}
fn collect_inputs_in_translations(
&mut self,
types: &'a ModuleTypesBuilder,
translations: impl IntoIterator<
Item = (
StaticModuleIndex,
&'a ModuleTranslation<'a>,
PrimaryMap<DefinedFuncIndex, FunctionBodyData<'a>>,
),
>,
) {
for (module, translation, functions) in translations {
for (def_func_index, func_body) in functions {
self.push_input(move |compiler| {
let func_index = translation.module.func_index(def_func_index);
let (info, function) =
compiler.compile_function(translation, def_func_index, func_body, types)?;
let symbol = match translation
.debuginfo
.name_section
.func_names
.get(&func_index)
{
Some(name) => format!(
"wasm[{}]::function[{}]::{}",
module.as_u32(),
func_index.as_u32(),
Self::clean_symbol(&name)
),
None => format!(
"wasm[{}]::function[{}]",
module.as_u32(),
func_index.as_u32()
),
};
Ok(CompileOutput {
key: CompileKey::wasm_function(module, def_func_index),
symbol,
function: CompiledFunction::Function(function),
info: Some(info),
})
});
let func_index = translation.module.func_index(def_func_index);
if translation.module.functions[func_index].is_escaping() {
self.push_input(move |compiler| {
let func_index = translation.module.func_index(def_func_index);
let trampoline = compiler.compile_array_to_wasm_trampoline(
translation,
types,
def_func_index,
)?;
Ok(CompileOutput {
key: CompileKey::array_to_wasm_trampoline(module, def_func_index),
symbol: format!(
"wasm[{}]::array_to_wasm_trampoline[{}]",
module.as_u32(),
func_index.as_u32()
),
function: CompiledFunction::Function(trampoline),
info: None,
})
});
}
}
}
if self.need_wasm_to_array_trampolines {
let mut trampoline_types_seen = HashSet::new();
for (_func_type_index, trampoline_type_index) in types.trampoline_types() {
let is_new = trampoline_types_seen.insert(trampoline_type_index);
if !is_new {
continue;
}
let trampoline_func_ty = types[trampoline_type_index].unwrap_func();
self.push_input(move |compiler| {
let trampoline =
compiler.compile_wasm_to_array_trampoline(trampoline_func_ty)?;
Ok(CompileOutput {
key: CompileKey::wasm_to_array_trampoline(trampoline_type_index),
symbol: format!(
"signatures[{}]::wasm_to_array_trampoline",
trampoline_type_index.as_u32()
),
function: CompiledFunction::Function(trampoline),
info: None,
})
});
}
}
}
/// Compile these `CompileInput`s (maybe in parallel) and return the
/// resulting `UnlinkedCompileOutput`s.
fn compile(self, engine: &Engine) -> Result<UnlinkedCompileOutputs> {
let compiler = engine.compiler();
// Compile each individual input in parallel.
let mut raw_outputs = engine.run_maybe_parallel(self.inputs, |f| f(compiler))?;
// Now that all functions have been compiled see if any
// wasmtime-builtin functions are necessary. If so those need to be
// collected and then those trampolines additionally need to be
// compiled.
compile_required_builtins(engine, &mut raw_outputs)?;
// Bucket the outputs by kind.
let mut outputs: BTreeMap<u32, Vec<CompileOutput>> = BTreeMap::new();
for output in raw_outputs {
outputs.entry(output.key.kind()).or_default().push(output);
}
Ok(UnlinkedCompileOutputs { outputs })
}
}
fn compile_required_builtins(engine: &Engine, raw_outputs: &mut Vec<CompileOutput>) -> Result<()> {
let compiler = engine.compiler();
let mut builtins = HashSet::new();
let mut new_inputs: Vec<CompileInput<'_>> = Vec::new();
let compile_builtin = |builtin: BuiltinFunctionIndex| {
Box::new(move |compiler: &dyn Compiler| {
let symbol = format!("wasmtime_builtin_{}", builtin.name());
Ok(CompileOutput {
key: CompileKey::wasm_to_builtin_trampoline(builtin),
symbol,
function: CompiledFunction::Function(compiler.compile_wasm_to_builtin(builtin)?),
info: None,
})
})
};
for output in raw_outputs.iter() {
let f = match &output.function {
CompiledFunction::Function(f) => f,
#[cfg(feature = "component-model")]
CompiledFunction::AllCallFunc(_) => continue,
};
for reloc in compiler.compiled_function_relocation_targets(&**f) {
match reloc {
RelocationTarget::Builtin(i) => {
if builtins.insert(i) {
new_inputs.push(compile_builtin(i));
}
}
_ => {}
}
}
}
raw_outputs.extend(engine.run_maybe_parallel(new_inputs, |c| c(compiler))?);
Ok(())
}
#[derive(Default)]
struct UnlinkedCompileOutputs {
// A map from kind to `CompileOutput`.
outputs: BTreeMap<u32, Vec<CompileOutput>>,
}
impl UnlinkedCompileOutputs {
/// Flatten all our functions into a single list and remember each of their
/// indices within it.
fn pre_link(self) -> (Vec<(String, Box<dyn Any + Send>)>, FunctionIndices) {
// The order the functions end up within `compiled_funcs` is the order
// that they will be laid out in the ELF file, so try and group hot and
// cold functions together as best we can. However, because we bucket by
// kind, we shouldn't have any issues with, e.g., cold trampolines
// appearing in between hot Wasm functions.
let mut compiled_funcs = vec![];
let mut indices = FunctionIndices::default();
for x in self.outputs.into_iter().flat_map(|(_kind, xs)| xs) {
let index = match x.function {
CompiledFunction::Function(f) => {
let index = compiled_funcs.len();
compiled_funcs.push((x.symbol, f));
CompiledFunction::Function(index)
}
#[cfg(feature = "component-model")]
CompiledFunction::AllCallFunc(f) => {
let array_call = compiled_funcs.len();
compiled_funcs.push((format!("{}_array_call", x.symbol), f.array_call));
let wasm_call = compiled_funcs.len();
compiled_funcs.push((format!("{}_wasm_call", x.symbol), f.wasm_call));
CompiledFunction::AllCallFunc(wasmtime_environ::component::AllCallFunc {
array_call,
wasm_call,
})
}
};
if x.key.kind() == CompileKey::WASM_FUNCTION_KIND
|| x.key.kind() == CompileKey::ARRAY_TO_WASM_TRAMPOLINE_KIND
{
indices
.compiled_func_index_to_module
.insert(index.unwrap_function(), x.key.module());
if let Some(info) = x.info {
indices.wasm_function_infos.insert(x.key, info);
}
}
indices
.indices
.entry(x.key.kind())
.or_default()
.insert(x.key, index);
}
(compiled_funcs, indices)
}
}
#[derive(Default)]
struct FunctionIndices {
// A reverse map from an index in `compiled_funcs` to the
// `StaticModuleIndex` for that function.
compiled_func_index_to_module: HashMap<usize, StaticModuleIndex>,
// A map from Wasm functions' compile keys to their infos.
wasm_function_infos: HashMap<CompileKey, WasmFunctionInfo>,
// The index of each compiled function, bucketed by compile key kind.
indices: BTreeMap<u32, BTreeMap<CompileKey, CompiledFunction<usize>>>,
}
impl FunctionIndices {
/// Link the compiled functions together, resolving relocations, and append
/// them to the given ELF file.
fn link_and_append_code<'a>(
mut self,
types: &ModuleTypesBuilder,
mut obj: object::write::Object<'static>,
engine: &'a Engine,
compiled_funcs: Vec<(String, Box<dyn Any + Send>)>,
translations: PrimaryMap<StaticModuleIndex, ModuleTranslation<'_>>,
dwarf_package_bytes: Option<&[u8]>,
) -> Result<(wasmtime_environ::ObjectBuilder<'a>, Artifacts)> {
// Append all the functions to the ELF file.
//
// The result is a vector parallel to `compiled_funcs` where
// `symbol_ids_and_locs[i]` is the symbol ID and function location of
// `compiled_funcs[i]`.
let compiler = engine.compiler();
let tunables = engine.tunables();
let symbol_ids_and_locs = compiler.append_code(
&mut obj,
&compiled_funcs,
&|caller_index: usize, callee: RelocationTarget| match callee {
RelocationTarget::Wasm(callee_index) => {
let module = self
.compiled_func_index_to_module
.get(&caller_index)
.copied()
.expect("should only reloc inside wasm function callers");
let def_func_index = translations[module]
.module
.defined_func_index(callee_index)
.unwrap();
self.indices[&CompileKey::WASM_FUNCTION_KIND]
[&CompileKey::wasm_function(module, def_func_index)]
.unwrap_function()
}
RelocationTarget::Builtin(builtin) => self.indices
[&CompileKey::WASM_TO_BUILTIN_TRAMPOLINE_KIND]
[&CompileKey::wasm_to_builtin_trampoline(builtin)]
.unwrap_function(),
RelocationTarget::HostLibcall(_) => {
unreachable!("relocation is resolved at runtime, not compile time");
}
},
)?;
// If requested, generate and add DWARF information.
if tunables.generate_native_debuginfo {
compiler.append_dwarf(
&mut obj,
&translations,
&|module, func| {
let bucket = &self.indices[&CompileKey::WASM_FUNCTION_KIND];
let i = bucket[&CompileKey::wasm_function(module, func)].unwrap_function();
(symbol_ids_and_locs[i].0, &*compiled_funcs[i].1)
},
dwarf_package_bytes,
tunables,
)?;
}
let mut obj =
wasmtime_environ::ObjectBuilder::new(obj, tunables, compiler.triple().clone());
let mut artifacts = Artifacts::default();
// Remove this as it's not needed by anything below and we'll debug
// assert `self.indices` is empty, so this is acknowledgement that this
// is a pure runtime implementation detail and not needed in any
// metadata generated below.
self.indices
.remove(&CompileKey::WASM_TO_BUILTIN_TRAMPOLINE_KIND);
// Finally, build our binary artifacts that map things like `FuncIndex`
// to a function location and all of that using the indices we saved
// earlier and the function locations we just received after appending
// the code.
let mut wasm_functions = self
.indices
.remove(&CompileKey::WASM_FUNCTION_KIND)
.unwrap_or_default()
.into_iter()
.peekable();
fn wasm_functions_for_module(
wasm_functions: &mut std::iter::Peekable<
btree_map::IntoIter<CompileKey, CompiledFunction<usize>>,
>,
module: StaticModuleIndex,
) -> impl Iterator<Item = (CompileKey, CompiledFunction<usize>)> + '_ {
std::iter::from_fn(move || {
let (key, _) = wasm_functions.peek()?;
if key.module() == module {
wasm_functions.next()
} else {
None
}
})
}
let mut array_to_wasm_trampolines = self
.indices
.remove(&CompileKey::ARRAY_TO_WASM_TRAMPOLINE_KIND)
.unwrap_or_default();
// NB: unlike the above maps this is not emptied out during iteration
// since each module may reach into different portions of this map.
let wasm_to_array_trampolines = self
.indices
.remove(&CompileKey::WASM_TO_ARRAY_TRAMPOLINE_KIND)
.unwrap_or_default();
artifacts.modules = translations
.into_iter()
.map(|(module, mut translation)| {
// If configured attempt to use static memory initialization which
// can either at runtime be implemented as a single memcpy to
// initialize memory or otherwise enabling virtual-memory-tricks
// such as mmap'ing from a file to get copy-on-write.
if engine.config().memory_init_cow {
let align = compiler.page_size_align();
let max_always_allowed = engine.config().memory_guaranteed_dense_image_size;
translation.try_static_init(align, max_always_allowed);
}
// Attempt to convert table initializer segments to
// FuncTable representation where possible, to enable
// table lazy init.
if engine.tunables().table_lazy_init {
translation.try_func_table_init();
}
let funcs: PrimaryMap<DefinedFuncIndex, CompiledFunctionInfo> =
wasm_functions_for_module(&mut wasm_functions, module)
.map(|(key, wasm_func_index)| {
let wasm_func_index = wasm_func_index.unwrap_function();
let wasm_func_loc = symbol_ids_and_locs[wasm_func_index].1;
let wasm_func_info = self.wasm_function_infos.remove(&key).unwrap();
let array_to_wasm_trampoline = array_to_wasm_trampolines
.remove(&CompileKey::array_to_wasm_trampoline(
key.module(),
DefinedFuncIndex::from_u32(key.index),
))
.map(|x| symbol_ids_and_locs[x.unwrap_function()].1);
CompiledFunctionInfo {
wasm_func_info,
wasm_func_loc,
array_to_wasm_trampoline,
}
})
.collect();
let wasm_to_array_trampolines = match engine.compiler().triple().architecture {
target_lexicon::Architecture::Pulley32
| target_lexicon::Architecture::Pulley64 => vec![],
_ => {
let unique_and_sorted_trampoline_sigs = translation
.module
.types
.iter()
.map(|(_, ty)| *ty)
.filter(|idx| types[*idx].is_func())
.map(|idx| types.trampoline_type(idx))
.collect::<BTreeSet<_>>();
unique_and_sorted_trampoline_sigs
.iter()
.map(|idx| {
let trampoline = types.trampoline_type(*idx);
let key = CompileKey::wasm_to_array_trampoline(trampoline);
let compiled = wasm_to_array_trampolines[&key];
(*idx, symbol_ids_and_locs[compiled.unwrap_function()].1)
})
.collect()
}
};
obj.append(translation, funcs, wasm_to_array_trampolines)
})
.collect::<Result<PrimaryMap<_, _>>>()?;
#[cfg(feature = "component-model")]
{
artifacts.trampolines = self
.indices
.remove(&CompileKey::TRAMPOLINE_KIND)
.unwrap_or_default()
.into_iter()
.map(|(_id, x)| x.unwrap_all_call_func().map(|i| symbol_ids_and_locs[i].1))
.collect();
let map = self
.indices
.remove(&CompileKey::RESOURCE_DROP_WASM_TO_ARRAY_KIND)
.unwrap_or_default();
assert!(map.len() <= 1);
artifacts.resource_drop_wasm_to_array_trampoline = map
.into_iter()
.next()
.map(|(_id, x)| symbol_ids_and_locs[x.unwrap_function()].1);
}
debug_assert!(
self.indices.is_empty(),
"Should have processed all compile outputs"
);
Ok((obj, artifacts))
}
}
/// The artifacts necessary for finding and calling Wasm functions at runtime,
/// to be serialized into an ELF file.
#[derive(Default)]
struct Artifacts {
modules: PrimaryMap<StaticModuleIndex, CompiledModuleInfo>,
#[cfg(feature = "component-model")]
trampolines: PrimaryMap<
wasmtime_environ::component::TrampolineIndex,
wasmtime_environ::component::AllCallFunc<wasmtime_environ::FunctionLoc>,
>,
#[cfg(feature = "component-model")]
resource_drop_wasm_to_array_trampoline: Option<wasmtime_environ::FunctionLoc>,
}
impl Artifacts {
/// Assuming this compilation was for a single core Wasm module, get the
/// resulting `CompiledModuleInfo`.
fn unwrap_as_module_info(self) -> CompiledModuleInfo {
assert_eq!(self.modules.len(), 1);
#[cfg(feature = "component-model")]
assert!(self.trampolines.is_empty());
self.modules.into_iter().next().unwrap().1
}
}
-317
View File
@@ -1,317 +0,0 @@
use crate::prelude::*;
use crate::Engine;
use std::borrow::Cow;
use std::path::Path;
/// 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>>,
}
/// 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) -> CodeBuilder<'a> {
CodeBuilder {
engine,
wasm: None,
wasm_path: None,
dwarf_package: None,
dwarf_package_path: None,
}
}
/// 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)
}
}
pub(super) fn get_wasm(&self) -> Result<&[u8]> {
self.wasm
.as_deref()
.ok_or_else(|| anyhow!("no wasm bytes have been configured"))
}
/// 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>> {
let wasm = self.get_wasm()?;
let dwarf_package = self.get_dwarf_package();
let (v, _) = super::build_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 bytes = self.get_wasm()?;
let (v, _) = super::build_component_artifacts(self.engine, &bytes, None)?;
Ok(v)
}
}
/// 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.
let 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);
}
}
-167
View File
@@ -1,167 +0,0 @@
use crate::compile::HashedEngineCompileEnv;
#[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, ObjectKind};
impl<'a> CodeBuilder<'a> {
fn compile_cached<T>(
&self,
build_artifacts: fn(&Engine, &[u8], Option<&[u8]>) -> Result<(MmapVecWrapper, Option<T>)>,
) -> Result<(Arc<CodeMemory>, Option<T>)> {
let wasm = self.get_wasm()?;
let dwarf_package = self.get_dwarf_package();
self.engine
.check_compatible_with_native_host()
.context("compilation settings are not compatible with the native host")?;
#[cfg(feature = "cache")]
{
let state = (
HashedEngineCompileEnv(self.engine),
&wasm,
&dwarf_package,
// Don't hash this as it's just its own "pure" function pointer.
NotHashed(build_artifacts),
);
let (code, info_and_types) =
wasmtime_cache::ModuleCacheEntry::new("wasmtime", self.engine.cache_config())
.get_data_raw(
&state,
// Cache miss, compute the actual artifacts
|(engine, wasm, dwarf_package, build_artifacts)| -> Result<_> {
let (mmap, info) =
(build_artifacts.0)(engine.0, wasm, dwarf_package.as_deref())?;
let code = publish_mmap(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) {
ObjectKind::Component
} else {
ObjectKind::Module
};
let code = engine.0.load_code_bytes(&serialized_bytes, kind).ok()?;
Some((code, None))
},
)?;
return Ok((code, info_and_types));
}
#[cfg(not(feature = "cache"))]
{
let (mmap, info_and_types) =
build_artifacts(self.engine, &wasm, dwarf_package.as_deref())?;
let code = publish_mmap(mmap.0)?;
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) {}
}
}
/// 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> {
let (code, info_and_types) = self.compile_cached(super::build_artifacts)?;
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 (code, artifacts) = self.compile_cached(super::build_component_artifacts)?;
Component::from_parts(self.engine, code, artifacts)
}
}
fn publish_mmap(mmap: MmapVec) -> Result<Arc<CodeMemory>> {
let mut code = CodeMemory::new(mmap)?;
code.publish()?;
Ok(Arc::new(code))
}
pub(crate) struct MmapVecWrapper(pub MmapVec);
impl FinishedObject for MmapVecWrapper {
fn finish_object(obj: ObjectBuilder<'_>) -> Result<Self> {
let mut result = ObjectMmap::default();
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.into()),
},
};
/// 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,
err: Option<Error>,
}
impl WritableBuffer for ObjectMmap {
fn len(&self) -> usize {
self.len
}
fn reserve(&mut self, additional: usize) -> Result<(), ()> {
assert!(self.mmap.is_none(), "cannot reserve twice");
self.mmap = match MmapVec::with_capacity(additional) {
Ok(mmap) => Some(mmap),
Err(e) => {
self.err = Some(e);
return Err(());
}
};
Ok(())
}
fn resize(&mut self, new_len: usize) {
// Resizing always appends 0 bytes and since new mmaps start out as 0
// bytes we don't actually need to do anything as part of this other
// than update our own length.
if new_len <= self.len {
return;
}
self.len = new_len;
}
fn write_bytes(&mut self, val: &[u8]) {
let mmap = self.mmap.as_mut().expect("write before reserve");
mmap[self.len..][..val.len()].copy_from_slice(val);
self.len += val.len();
}
}
}
}
File diff suppressed because it is too large. Load diff
@@ -1,48 +0,0 @@
use crate::prelude::*;
use crate::profiling_agent::ProfilingAgent;
use std::io::{self, BufWriter, 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<BufWriter<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());
*file = Some(BufWriter::new(File::create(filename)?));
}
Ok(Box::new(PerfMapAgent))
}
impl PerfMapAgent {
fn make_line(writer: &mut dyn Write, 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', "_");
write!(
writer,
"{:p} {:x} {}\n",
code.as_ptr(),
code.len(),
sanitized_name
)?;
writer.flush()?;
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}");
}
}
}
-102
View File
@@ -1,102 +0,0 @@
use crate::{code_memory::CodeMemory, type_registry::TypeCollection};
use alloc::sync::Arc;
#[cfg(feature = "component-model")]
use wasmtime_environ::component::ComponentTypes;
use wasmtime_environ::ModuleTypes;
/// Metadata in Wasmtime about a loaded compiled artifact in memory which is
/// ready 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`.
pub struct CodeObject {
/// Actual underlying mmap 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.
mmap: 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 CodeObject {
pub fn new(mmap: Arc<CodeMemory>, signatures: TypeCollection, types: Types) -> CodeObject {
// The corresponding unregister for this is below in `Drop for
// CodeObject`.
crate::module::register_code(&mmap);
CodeObject {
mmap,
signatures,
types,
}
}
pub fn code_memory(&self) -> &Arc<CodeMemory> {
&self.mmap
}
#[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
}
}
impl Drop for CodeObject {
fn drop(&mut self) {
crate::module::unregister_code(&self.mmap);
}
}
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)
}
}
-372
View File
@@ -1,372 +0,0 @@
//! Memory management for executable code.
use crate::prelude::*;
use crate::runtime::vm::{libcalls, MmapVec, UnwindRegistration};
use core::ops::Range;
use object::endian::NativeEndian;
use object::read::{elf::ElfFile64, Object, ObjectSection};
use object::ObjectSymbol;
use wasmtime_environ::{lookup_trap_code, obj, Trap};
use wasmtime_jit_icache_coherence as icache_coherence;
/// 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,
unwind_registration: Option<UnwindRegistration>,
#[cfg(feature = "debug-builtins")]
debug_registration: Option<crate::runtime::vm::GdbJitImageRegistration>,
published: bool,
enable_branch_protection: bool,
#[cfg(feature = "debug-builtins")]
has_native_debug_info: bool,
relocations: Vec<(usize, obj::LibCall)>,
// 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>,
func_name_data: Range<usize>,
info_data: Range<usize>,
wasm_dwarf: Range<usize>,
}
impl Drop for CodeMemory {
fn drop(&mut self) {
// Drop the registrations before `self.mmap` since they (implicitly) refer to it.
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>();
}
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(mmap: MmapVec) -> Result<Self> {
let obj = ElfFile64::<NativeEndian>::parse(&mmap[..])
.err2anyhow()
.with_context(|| "failed to parse internal compilation artifact")?;
let mut relocations = Vec::new();
let mut text = 0..0;
let mut unwind = 0..0;
let mut enable_branch_protection = None;
#[cfg(feature = "debug-builtins")]
let mut has_native_debug_info = false;
let mut trap_data = 0..0;
let mut wasm_data = 0..0;
let mut address_map_data = 0..0;
let mut func_name_data = 0..0;
let mut info_data = 0..0;
let mut wasm_dwarf = 0..0;
for section in obj.sections() {
let data = section.data().err2anyhow()?;
let name = section.name().err2anyhow()?;
let range = subslice_range(data, &mmap);
// Double-check that sections are all aligned properly.
if section.align() != 0 && data.len() != 0 {
if (data.as_ptr() as u64 - mmap.as_ptr() as u64) % section.align() != 0 {
bail!(
"section `{}` isn't aligned to {:#x}",
section.name().unwrap_or("ERROR"),
section.align()
);
}
}
match name {
obj::ELF_WASM_BTI => match data.len() {
1 => enable_branch_protection = Some(data[0] != 0),
_ => bail!("invalid `{name}` section"),
},
".text" => {
text = range;
// The text section might have relocations for things like
// libcalls which need to be applied, so handle those here.
//
// Note that only a small subset of possible relocations are
// handled. Only those required by the compiler side of
// things are processed.
for (offset, reloc) in section.relocations() {
assert_eq!(reloc.kind(), object::RelocationKind::Absolute);
assert_eq!(reloc.encoding(), object::RelocationEncoding::Generic);
assert_eq!(usize::from(reloc.size()), core::mem::size_of::<usize>() * 8);
assert_eq!(reloc.addend(), 0);
let sym = match reloc.target() {
object::RelocationTarget::Symbol(id) => id,
other => panic!("unknown relocation target {other:?}"),
};
let sym = obj.symbol_by_index(sym).unwrap().name().unwrap();
let libcall = obj::LibCall::from_str(sym)
.unwrap_or_else(|| panic!("unknown symbol relocation: {sym}"));
let offset = usize::try_from(offset).unwrap();
relocations.push((offset, libcall));
}
}
UnwindRegistration::SECTION_NAME => unwind = range,
obj::ELF_WASM_DATA => wasm_data = range,
obj::ELF_WASMTIME_ADDRMAP => address_map_data = range,
obj::ELF_WASMTIME_TRAPS => trap_data = range,
obj::ELF_NAME_DATA => func_name_data = range,
obj::ELF_WASMTIME_INFO => info_data = range,
obj::ELF_WASMTIME_DWARF => wasm_dwarf = range,
#[cfg(feature = "debug-builtins")]
".debug_info" => has_native_debug_info = true,
_ => log::debug!("ignoring section {name}"),
}
}
Ok(Self {
mmap,
unwind_registration: None,
#[cfg(feature = "debug-builtins")]
debug_registration: None,
published: false,
enable_branch_protection: enable_branch_protection
.ok_or_else(|| anyhow!("missing `{}` section", obj::ELF_WASM_BTI))?,
#[cfg(feature = "debug-builtins")]
has_native_debug_info,
text,
unwind,
trap_data,
address_map_data,
func_name_data,
wasm_dwarf,
info_data,
wasm_data,
relocations,
})
}
/// 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 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()]
}
/// Publishes the internal ELF image to be ready for execution.
///
/// This method can only be called once and will panic if called twice. 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.
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 {
// First, if necessary, apply relocations. This can happen for
// things like libcalls which happen late in the lowering process
// that don't go through the Wasm-based libcalls layer that's
// indirected through the `VMContext`. Note that most modules won't
// have relocations, so this typically doesn't do anything.
self.apply_relocations()?;
// 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.
self.mmap.make_readonly(0..self.mmap.len())?;
let text = self.text();
// Clear the newly allocated code from cache if the processor requires it
//
// Do this before marking the memory as R+X, technically we should be able to do it after
// but there are some CPU's that have had errata about doing this with read only memory.
icache_coherence::clear_cache(text.as_ptr().cast(), text.len())
.expect("Failed cache clear");
// Switch the executable portion from readonly to read/execute.
self.mmap
.make_executable(self.text.clone(), self.enable_branch_protection)
.context("unable to make memory executable")?;
// Flush any in-flight instructions from the pipeline
icache_coherence::pipeline_flush_mt().expect("Failed pipeline flush");
// 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()?;
}
Ok(())
}
unsafe fn apply_relocations(&mut self) -> Result<()> {
if self.relocations.is_empty() {
return Ok(());
}
for (offset, libcall) in self.relocations.iter() {
let offset = self.text.start + offset;
let libcall = match libcall {
obj::LibCall::FloorF32 => libcalls::relocs::floorf32 as usize,
obj::LibCall::FloorF64 => libcalls::relocs::floorf64 as usize,
obj::LibCall::NearestF32 => libcalls::relocs::nearestf32 as usize,
obj::LibCall::NearestF64 => libcalls::relocs::nearestf64 as usize,
obj::LibCall::CeilF32 => libcalls::relocs::ceilf32 as usize,
obj::LibCall::CeilF64 => libcalls::relocs::ceilf64 as usize,
obj::LibCall::TruncF32 => libcalls::relocs::truncf32 as usize,
obj::LibCall::TruncF64 => libcalls::relocs::truncf64 as usize,
obj::LibCall::FmaF32 => libcalls::relocs::fmaf32 as usize,
obj::LibCall::FmaF64 => libcalls::relocs::fmaf64 as usize,
#[cfg(target_arch = "x86_64")]
obj::LibCall::X86Pshufb => libcalls::relocs::x86_pshufb as usize,
#[cfg(not(target_arch = "x86_64"))]
obj::LibCall::X86Pshufb => unreachable!(),
};
self.mmap
.as_mut_ptr()
.add(offset)
.cast::<usize>()
.write_unaligned(libcall);
}
Ok(())
}
unsafe fn register_unwind_info(&mut self) -> Result<()> {
if self.unwind.len() == 0 {
return Ok(());
}
let text = self.text();
let unwind_info = &self.mmap[self.unwind.clone()];
let registration =
UnwindRegistration::new(text.as_ptr(), unwind_info.as_ptr(), unwind_info.len())
.context("failed to create unwind info registration")?;
self.unwind_registration = Some(registration);
Ok(())
}
#[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::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<Trap> {
lookup_trap_code(self.trap_data(), text_offset)
}
}
/// 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()
}
@@ -1,688 +0,0 @@
use crate::component::instance::{Instance, InstanceData};
use crate::component::storage::storage_as_slice;
use crate::component::types::Type;
use crate::component::values::Val;
use crate::prelude::*;
use crate::runtime::vm::component::ResourceTables;
use crate::runtime::vm::{Export, ExportFunction};
use crate::store::{StoreOpaque, Stored};
use crate::{AsContext, AsContextMut, StoreContextMut, ValRaw};
use alloc::sync::Arc;
use core::mem::{self, MaybeUninit};
use core::ptr::NonNull;
use wasmtime_environ::component::{
CanonicalOptions, ComponentTypes, CoreDef, InterfaceType, RuntimeComponentInstanceIndex,
TypeFuncIndex, TypeTuple, MAX_FLAT_PARAMS, MAX_FLAT_RESULTS,
};
mod host;
mod options;
mod typed;
pub use self::host::*;
pub use self::options::*;
pub use self::typed::*;
#[repr(C)]
union ParamsAndResults<Params: Copy, Return: Copy> {
params: Params,
ret: Return,
}
/// 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)]
pub struct Func(Stored<FuncData>);
#[doc(hidden)]
pub struct FuncData {
export: ExportFunction,
ty: TypeFuncIndex,
types: Arc<ComponentTypes>,
options: Options,
instance: Instance,
component_instance: RuntimeComponentInstanceIndex,
post_return: Option<ExportFunction>,
post_return_arg: Option<ValRaw>,
}
impl Func {
pub(crate) fn from_lifted_func(
store: &mut StoreOpaque,
instance: &Instance,
data: &InstanceData,
ty: TypeFuncIndex,
func: &CoreDef,
options: &CanonicalOptions,
) -> Func {
let export = match data.lookup_def(store, func) {
Export::Function(f) => f,
_ => unreachable!(),
};
let memory = options
.memory
.map(|i| NonNull::new(data.instance().runtime_memory(i)).unwrap());
let realloc = options.realloc.map(|i| data.instance().runtime_realloc(i));
let post_return = options.post_return.map(|i| {
let func_ref = data.instance().runtime_post_return(i);
ExportFunction { func_ref }
});
let component_instance = options.instance;
let options = unsafe { Options::new(store.id(), memory, realloc, options.string_encoding) };
Func(store.store_data_mut().insert(FuncData {
export,
options,
ty,
types: data.component_types().clone(),
instance: *instance,
component_instance,
post_return,
post_return_arg: None,
}))
}
/// 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.
///
/// # 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<()>) -> anyhow::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<()>) -> anyhow::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<()>) -> anyhow::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<&InstanceData>,
) -> 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<&InstanceData>,
) -> Result<()>
where
Params: ComponentNamedList + Lower,
Return: ComponentNamedList + Lift,
{
let data = &store[self.0];
let cx = instance
.unwrap_or_else(|| &store[data.instance.0].as_ref().unwrap())
.ty();
let ty = &cx.types[data.ty];
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 parameter types for this function.
pub fn params(&self, store: impl AsContext) -> Box<[Type]> {
let store = store.as_context();
let data = &store[self.0];
let instance = store[data.instance.0].as_ref().unwrap();
data.types[data.types[data.ty].params]
.types
.iter()
.map(|ty| Type::from(ty, &instance.ty()))
.collect()
}
/// Get the result types for this function.
pub fn results(&self, store: impl AsContext) -> Box<[Type]> {
let store = store.as_context();
let data = &store[self.0];
let instance = store[data.instance.0].as_ref().unwrap();
data.types[data.types[data.ty].results]
.types
.iter()
.map(|ty| Type::from(ty, &instance.ty()))
.collect()
}
/// 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.
///
/// Note that after a function is invoked the embedder needs to invoke
/// [`Func::post_return`] to execute any final cleanup required by the
/// guest. This function call is required to either call the function again
/// or to call another function.
///
/// 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
///
/// See [`TypedFunc::call`] for more information in addition to
/// [`wasmtime::Func::call`](crate::Func::call).
///
/// # Panics
///
/// Panics if this is called on a function in an asynchronous store. This
/// only works with functions defined within a synchronous store. Also
/// 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();
assert!(
!store.0.async_support(),
"must use `call_async` when async support is enabled on the config"
);
self.call_impl(&mut store.as_context_mut(), params, results)
}
/// Exactly like [`Self::call`] except for use on async stores.
///
/// Note that after this [`Func::post_return_async`] will be used instead of
/// the synchronous version at [`Func::post_return`].
///
/// # Panics
///
/// Panics if this is called on a function in a synchronous store. This
/// only works with functions defined within an asynchronous store. Also
/// panics if `store` does not own this function.
#[cfg(feature = "async")]
pub async fn call_async<T>(
&self,
mut store: impl AsContextMut<Data = T>,
params: &[Val],
results: &mut [Val],
) -> Result<()>
where
T: Send,
{
let mut store = store.as_context_mut();
assert!(
store.0.async_support(),
"cannot use `call_async` without enabling async support in the config"
);
store
.on_fiber(|store| self.call_impl(store, params, results))
.await?
}
fn call_impl(
&self,
mut store: impl AsContextMut,
params: &[Val],
results: &mut [Val],
) -> Result<()> {
let store = &mut store.as_context_mut();
let param_tys = self.params(&store);
let result_tys = self.results(&store);
if param_tys.len() != params.len() {
bail!(
"expected {} argument(s), got {}",
param_tys.len(),
params.len()
);
}
if result_tys.len() != results.len() {
bail!(
"expected {} results(s), got {}",
result_tys.len(),
results.len()
);
}
self.call_raw(
store,
params,
|cx, params, params_ty, dst: &mut MaybeUninit<[ValRaw; MAX_FLAT_PARAMS]>| {
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 unsafe {
mem::transmute::<_, &mut [MaybeUninit<ValRaw>; MAX_FLAT_PARAMS]>(dst)
}
.iter_mut();
params
.iter()
.zip(params_ty.types.iter())
.try_for_each(|(param, ty)| param.lower(cx, *ty, dst))
} else {
self.store_args(cx, &params_ty, params, dst)
}
},
|cx, results_ty, src: &[ValRaw; MAX_FLAT_RESULTS]| {
let results_ty = match results_ty {
InterfaceType::Tuple(i) => &cx.types[i],
_ => unreachable!(),
};
if results_ty.abi.flat_count(MAX_FLAT_RESULTS).is_some() {
let mut flat = src.iter();
for (ty, slot) in results_ty.types.iter().zip(results) {
*slot = Val::lift(cx, *ty, &mut flat)?;
}
Ok(())
} else {
Self::load_results(cx, results_ty, results, &mut src.iter())
}
},
)
}
/// 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.
fn call_raw<T, Params: ?Sized, Return, LowerParams, LowerReturn>(
&self,
store: &mut StoreContextMut<'_, T>,
params: &Params,
lower: impl FnOnce(
&mut LowerContext<'_, T>,
&Params,
InterfaceType,
&mut MaybeUninit<LowerParams>,
) -> Result<()>,
lift: impl FnOnce(&mut LiftContext<'_>, InterfaceType, &LowerReturn) -> Result<Return>,
) -> Result<Return>
where
LowerParams: Copy,
LowerReturn: Copy,
{
let FuncData {
export,
options,
instance,
component_instance,
ty,
..
} = store.0[self.0];
let space = &mut MaybeUninit::<ParamsAndResults<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);
let instance = store.0[instance.0].as_ref().unwrap();
let types = instance.component_types().clone();
let mut flags = instance.instance().instance_flags(component_instance);
unsafe {
// Test the "may enter" flag which is a "lock" on this instance.
// This is immediately set to `false` afterwards and note that
// there's no on-cleanup setting this flag back to true. That's an
// intentional design aspect where if anything goes wrong internally
// from this point on the instance is considered "poisoned" and can
// never be entered again. The only time this flag is set to `true`
// again is after post-return logic has completed successfully.
if !flags.may_enter() {
bail!(crate::Trap::CannotEnterComponent);
}
flags.set_may_enter(false);
debug_assert!(flags.may_leave());
flags.set_may_leave(false);
let instance_ptr = instance.instance_ptr();
let mut cx = LowerContext::new(store.as_context_mut(), &options, &types, instance_ptr);
cx.enter_call();
let result = lower(
&mut cx,
params,
InterfaceType::Tuple(types[ty].params),
map_maybe_uninit!(space.params),
);
flags.set_may_leave(true);
result?;
// This is unsafe as 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.
crate::Func::call_unchecked_raw(
store,
export.func_ref,
space.as_mut_ptr().cast(),
mem::size_of_val(space) / mem::size_of::<ValRaw>(),
)?;
// Note that `.assume_init_ref()` here is unsafe but 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 = map_maybe_uninit!(space.ret).assume_init_ref();
// Lift the result into the host while managing post-return state
// here as well.
//
// After a successful lift the return value of the function, which
// is currently required to be 0 or 1 values according to the
// canonical ABI, is saved within the `Store`'s `FuncData`. This'll
// later get used in post-return.
flags.set_needs_post_return(true);
let val = lift(
&mut LiftContext::new(store.0, &options, &types, instance_ptr),
InterfaceType::Tuple(types[ty].results),
ret,
)?;
let ret_slice = storage_as_slice(ret);
let data = &mut store.0[self.0];
assert!(data.post_return_arg.is_none());
match ret_slice.len() {
0 => data.post_return_arg = Some(ValRaw::i32(0)),
1 => data.post_return_arg = Some(ret_slice[0]),
_ => unreachable!(),
}
return Ok(val);
}
}
/// Invokes the `post-return` canonical ABI option, if specified, after a
/// [`Func::call`] has finished.
///
/// This function is a required method call after a [`Func::call`] completes
/// successfully. After the embedder has finished processing the return
/// value then this function must be invoked.
///
/// # Errors
///
/// This function will return an error in the case of a WebAssembly trap
/// happening during the execution of the `post-return` function, if
/// specified.
///
/// # Panics
///
/// This function will panic if it's not called under the correct
/// conditions. This can only be called after a previous invocation of
/// [`Func::call`] completes successfully, and this function can only
/// be called for the same [`Func`] that was `call`'d.
///
/// If this function is called when [`Func::call`] was not previously
/// called, then it will panic. If a different [`Func`] for the same
/// component instance was invoked then this function will also panic
/// because the `post-return` needs to happen for the other function.
///
/// Panics if this is called on a function in an asynchronous store.
/// This only works with functions defined within a synchronous store.
#[inline]
pub fn post_return(&self, mut store: impl AsContextMut) -> Result<()> {
let store = store.as_context_mut();
assert!(
!store.0.async_support(),
"must use `post_return_async` when async support is enabled on the config"
);
self.post_return_impl(store)
}
/// Exactly like [`Self::post_return`] except for use on async stores.
///
/// # Panics
///
/// Panics if this is called on a function in a synchronous store. This
/// only works with functions defined within an asynchronous store.
#[cfg(feature = "async")]
pub async fn post_return_async<T: Send>(
&self,
mut store: impl AsContextMut<Data = T>,
) -> Result<()> {
let mut store = store.as_context_mut();
assert!(
store.0.async_support(),
"cannot use `call_async` without enabling async support in the config"
);
// Future optimization opportunity: conditionally use a fiber here since
// some func's post_return will not need the async context (i.e. end up
// calling async host functionality)
store.on_fiber(|store| self.post_return_impl(store)).await?
}
fn post_return_impl(&self, mut store: impl AsContextMut) -> Result<()> {
let mut store = store.as_context_mut();
let data = &mut store.0[self.0];
let instance = data.instance;
let post_return = data.post_return;
let component_instance = data.component_instance;
let post_return_arg = data.post_return_arg.take();
let instance = store.0[instance.0].as_ref().unwrap().instance_ptr();
unsafe {
let mut flags = (*instance).instance_flags(component_instance);
// First assert that the instance is in a "needs post return" state.
// This will ensure that the previous action on the instance was a
// function call above. This flag is only set after a component
// function returns so this also can't be called (as expected)
// during a host import for example.
//
// Note, though, that this assert is not sufficient because it just
// means some function on this instance needs its post-return
// called. We need a precise post-return for a particular function
// which is the second assert here (the `.expect`). That will assert
// that this function itself needs to have its post-return called.
//
// The theory at least is that these two asserts ensure component
// model semantics are upheld where the host properly calls
// `post_return` on the right function despite the call being a
// separate step in the API.
assert!(
flags.needs_post_return(),
"post_return can only be called after a function has previously been called",
);
let post_return_arg = post_return_arg.expect("calling post_return on wrong function");
// This is a sanity-check assert which shouldn't ever trip.
assert!(!flags.may_enter());
// Unset the "needs post return" flag now that post-return is being
// processed. This will cause future invocations of this method to
// panic, even if the function call below traps.
flags.set_needs_post_return(false);
// If the function actually had a `post-return` configured in its
// canonical options that's executed here.
//
// Note that if this traps (returns an error) this function
// intentionally leaves the instance in a "poisoned" state where it
// can no longer be entered because `may_enter` is `false`.
if let Some(func) = post_return {
crate::Func::call_unchecked_raw(
&mut store,
func.func_ref,
&post_return_arg as *const ValRaw as *mut ValRaw,
1,
)?;
}
// And finally if everything completed successfully then the "may
// enter" flag is set to `true` again here which enables further use
// of the component.
flags.set_may_enter(true);
let (calls, host_table, _) = store.0.component_resource_state();
ResourceTables {
calls,
host_table: Some(host_table),
tables: Some((*instance).component_resource_tables()),
}
.exit_call()?;
}
Ok(())
}
fn store_args<T>(
&self,
cx: &mut LowerContext<'_, T>,
params_ty: &TypeTuple,
args: &[Val],
dst: &mut MaybeUninit<[ValRaw; MAX_FLAT_PARAMS]>,
) -> 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))?;
}
map_maybe_uninit!(dst[0]).write(ValRaw::i64(ptr as i64));
Ok(())
}
fn load_results(
cx: &mut LiftContext<'_>,
results_ty: &TypeTuple,
results: &mut [Val],
src: &mut core::slice::Iter<'_, ValRaw>,
) -> Result<()> {
// FIXME: needs to read an i64 for memory64
let ptr = usize::try_from(src.next().unwrap().get_u32()).err2anyhow()?;
if ptr % usize::try_from(results_ty.abi.align32).err2anyhow()? != 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(|| anyhow::anyhow!("pointer out of bounds of memory"))?;
let mut offset = 0;
for (ty, slot) in results_ty.types.iter().zip(results) {
let abi = cx.types.canonical_abi(ty);
let offset = abi.next_field32_size(&mut offset);
*slot = Val::load(cx, *ty, &bytes[offset..][..abi.size32 as usize])?;
}
Ok(())
}
}
@@ -1,458 +0,0 @@
use crate::component::func::{LiftContext, LowerContext, Options};
use crate::component::matching::InstanceType;
use crate::component::storage::slice_to_storage_mut;
use crate::component::{ComponentNamedList, ComponentType, Lift, Lower, Val};
use crate::prelude::*;
use crate::runtime::vm::component::{
ComponentInstance, InstanceFlags, VMComponentContext, VMLowering, VMLoweringCallee,
};
use crate::runtime::vm::{VMFuncRef, VMMemoryDefinition, VMOpaqueContext};
use crate::{AsContextMut, CallHook, StoreContextMut, ValRaw};
use alloc::sync::Arc;
use core::any::Any;
use core::mem::{self, MaybeUninit};
use core::ptr::NonNull;
use wasmtime_environ::component::{
CanonicalAbiInfo, ComponentTypes, InterfaceType, StringEncoding, TypeFuncIndex,
MAX_FLAT_PARAMS, MAX_FLAT_RESULTS,
};
pub struct HostFunc {
entrypoint: VMLoweringCallee,
typecheck: Box<dyn (Fn(TypeFuncIndex, &InstanceType<'_>) -> Result<()>) + Send + Sync>,
func: Box<dyn Any + Send + Sync>,
}
impl HostFunc {
pub(crate) fn from_closure<T, F, P, R>(func: F) -> Arc<HostFunc>
where
F: Fn(StoreContextMut<T>, P) -> Result<R> + Send + Sync + 'static,
P: ComponentNamedList + Lift + 'static,
R: ComponentNamedList + Lower + 'static,
{
let entrypoint = Self::entrypoint::<T, F, P, R>;
Arc::new(HostFunc {
entrypoint,
typecheck: Box::new(typecheck::<P, R>),
func: Box::new(func),
})
}
extern "C" fn entrypoint<T, F, P, R>(
cx: *mut VMOpaqueContext,
data: *mut u8,
ty: TypeFuncIndex,
flags: InstanceFlags,
memory: *mut VMMemoryDefinition,
realloc: *mut VMFuncRef,
string_encoding: StringEncoding,
storage: *mut MaybeUninit<ValRaw>,
storage_len: usize,
) where
F: Fn(StoreContextMut<T>, P) -> Result<R>,
P: ComponentNamedList + Lift + 'static,
R: ComponentNamedList + Lower + 'static,
{
let data = data as *const F;
unsafe {
call_host_and_handle_result::<T>(cx, |instance, types, store| {
call_host::<_, _, _, _>(
instance,
types,
store,
ty,
flags,
memory,
realloc,
string_encoding,
core::slice::from_raw_parts_mut(storage, storage_len),
|store, args| (*data)(store, args),
)
})
}
}
pub(crate) fn new_dynamic<T, F>(func: F) -> Arc<HostFunc>
where
F: Fn(StoreContextMut<'_, T>, &[Val], &mut [Val]) -> Result<()> + Send + Sync + 'static,
{
Arc::new(HostFunc {
entrypoint: dynamic_entrypoint::<T, F>,
// This function performs dynamic type checks and subsequently does
// not need to perform up-front type checks. Instead everything is
// dynamically managed at runtime.
typecheck: Box::new(move |_expected_index, _expected_types| Ok(())),
func: Box::new(func),
})
}
pub fn typecheck(&self, ty: TypeFuncIndex, types: &InstanceType<'_>) -> Result<()> {
(self.typecheck)(ty, types)
}
pub fn lowering(&self) -> VMLowering {
let data = &*self.func as *const (dyn Any + Send + Sync) as *mut u8;
VMLowering {
callee: self.entrypoint,
data,
}
}
}
fn typecheck<P, R>(ty: TypeFuncIndex, types: &InstanceType<'_>) -> Result<()>
where
P: ComponentNamedList + Lift,
R: ComponentNamedList + Lower,
{
let ty = &types.types[ty];
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(())
}
/// The "meat" of calling a host function from wasm.
///
/// This function is delegated to from implementations of
/// `HostFunc::from_closure`. Most of the arguments from the `entrypoint` are
/// forwarded here except for the `data` pointer which is encapsulated in the
/// `closure` argument here.
///
/// This function is parameterized over:
///
/// * `T` - the type of store this function works with (an unsafe assertion)
/// * `Params` - the parameters to the host function, viewed as a tuple
/// * `Return` - the result of the host function
/// * `F` - the `closure` to actually receive the `Params` and return the
/// `Return`
///
/// It's expected that `F` will "un-tuple" the arguments to pass to a host
/// closure.
///
/// This function is in general `unsafe` as the validity of all the parameters
/// must be upheld. Generally that's done by ensuring this is only called from
/// the select few places it's intended to be called from.
unsafe fn call_host<T, Params, Return, F>(
instance: *mut ComponentInstance,
types: &Arc<ComponentTypes>,
mut cx: StoreContextMut<'_, T>,
ty: TypeFuncIndex,
mut flags: InstanceFlags,
memory: *mut VMMemoryDefinition,
realloc: *mut VMFuncRef,
string_encoding: StringEncoding,
storage: &mut [MaybeUninit<ValRaw>],
closure: F,
) -> Result<()>
where
Params: Lift,
Return: Lower,
F: FnOnce(StoreContextMut<'_, T>, Params) -> Result<Return>,
{
/// Representation of arguments to this function when a return pointer is in
/// use, namely the argument list is followed by a single value which is the
/// return pointer.
#[repr(C)]
struct ReturnPointer<T> {
args: T,
retptr: ValRaw,
}
/// Representation of arguments to this function when the return value is
/// returned directly, namely the arguments and return value all start from
/// the beginning (aka this is a `union`, not a `struct`).
#[repr(C)]
union ReturnStack<T: Copy, U: Copy> {
args: T,
ret: U,
}
let options = Options::new(
cx.0.id(),
NonNull::new(memory),
NonNull::new(realloc),
string_encoding,
);
// Perform a dynamic check that this instance can indeed be left. Exiting
// the component is disallowed, for example, when the `realloc` function
// calls a canonical import.
if !flags.may_leave() {
bail!("cannot leave component instance");
}
let ty = &types[ty];
let param_tys = InterfaceType::Tuple(ty.params);
let result_tys = InterfaceType::Tuple(ty.results);
// There's a 2x2 matrix of whether parameters and results are stored on the
// stack or on the heap. Each of the 4 branches here have a different
// representation of the storage of arguments/returns.
//
// Also note that while four branches are listed here only one is taken for
// any particular `Params` and `Return` combination. This should be
// trivially DCE'd by LLVM. Perhaps one day with enough const programming in
// Rust we can make monomorphizations of this function codegen only one
// branch, but today is not that day.
let mut storage: Storage<'_, Params, Return> = if Params::flatten_count() <= MAX_FLAT_PARAMS {
if Return::flatten_count() <= MAX_FLAT_RESULTS {
Storage::Direct(slice_to_storage_mut(storage))
} else {
Storage::ResultsIndirect(slice_to_storage_mut(storage).assume_init_ref())
}
} else {
if Return::flatten_count() <= MAX_FLAT_RESULTS {
Storage::ParamsIndirect(slice_to_storage_mut(storage))
} else {
Storage::Indirect(slice_to_storage_mut(storage).assume_init_ref())
}
};
let mut lift = LiftContext::new(cx.0, &options, types, instance);
lift.enter_call();
let params = storage.lift_params(&mut lift, param_tys)?;
let ret = closure(cx.as_context_mut(), params)?;
flags.set_may_leave(false);
let mut lower = LowerContext::new(cx, &options, types, instance);
storage.lower_results(&mut lower, result_tys, ret)?;
flags.set_may_leave(true);
lower.exit_call()?;
return Ok(());
enum Storage<'a, P: ComponentType, R: ComponentType> {
Direct(&'a mut MaybeUninit<ReturnStack<P::Lower, R::Lower>>),
ParamsIndirect(&'a mut MaybeUninit<ReturnStack<ValRaw, R::Lower>>),
ResultsIndirect(&'a ReturnPointer<P::Lower>),
Indirect(&'a ReturnPointer<ValRaw>),
}
impl<P, R> Storage<'_, P, R>
where
P: ComponentType + Lift,
R: ComponentType + Lower,
{
unsafe fn lift_params(&self, cx: &mut LiftContext<'_>, ty: InterfaceType) -> Result<P> {
match self {
Storage::Direct(storage) => P::lift(cx, ty, &storage.assume_init_ref().args),
Storage::ResultsIndirect(storage) => P::lift(cx, ty, &storage.args),
Storage::ParamsIndirect(storage) => {
let ptr = validate_inbounds::<P>(cx.memory(), &storage.assume_init_ref().args)?;
P::load(cx, ty, &cx.memory()[ptr..][..P::SIZE32])
}
Storage::Indirect(storage) => {
let ptr = validate_inbounds::<P>(cx.memory(), &storage.args)?;
P::load(cx, ty, &cx.memory()[ptr..][..P::SIZE32])
}
}
}
unsafe fn lower_results<T>(
&mut self,
cx: &mut LowerContext<'_, T>,
ty: InterfaceType,
ret: R,
) -> Result<()> {
match self {
Storage::Direct(storage) => ret.lower(cx, ty, map_maybe_uninit!(storage.ret)),
Storage::ParamsIndirect(storage) => {
ret.lower(cx, ty, map_maybe_uninit!(storage.ret))
}
Storage::ResultsIndirect(storage) => {
let ptr = validate_inbounds::<R>(cx.as_slice_mut(), &storage.retptr)?;
ret.store(cx, ty, ptr)
}
Storage::Indirect(storage) => {
let ptr = validate_inbounds::<R>(cx.as_slice_mut(), &storage.retptr)?;
ret.store(cx, ty, ptr)
}
}
}
}
}
fn validate_inbounds<T: ComponentType>(memory: &[u8], ptr: &ValRaw) -> Result<usize> {
// FIXME: needs memory64 support
let ptr = usize::try_from(ptr.get_u32()).err2anyhow()?;
if ptr % usize::try_from(T::ALIGN32).err2anyhow()? != 0 {
bail!("pointer not aligned");
}
let end = match ptr.checked_add(T::SIZE32) {
Some(n) => n,
None => bail!("pointer size overflow"),
};
if end > memory.len() {
bail!("pointer out of bounds")
}
Ok(ptr)
}
unsafe fn call_host_and_handle_result<T>(
cx: *mut VMOpaqueContext,
func: impl FnOnce(
*mut ComponentInstance,
&Arc<ComponentTypes>,
StoreContextMut<'_, T>,
) -> Result<()>,
) {
let cx = VMComponentContext::from_opaque(cx);
let instance = (*cx).instance();
let types = (*instance).component_types();
let raw_store = (*instance).store();
let mut store = StoreContextMut::from_raw(raw_store);
let res = crate::runtime::vm::catch_unwind_and_longjmp(|| {
store.0.call_hook(CallHook::CallingHost)?;
let res = func(instance, types, store.as_context_mut());
store.0.call_hook(CallHook::ReturningFromHost)?;
res
});
match res {
Ok(()) => {}
Err(e) => crate::trap::raise(e),
}
}
unsafe fn call_host_dynamic<T, F>(
instance: *mut ComponentInstance,
types: &Arc<ComponentTypes>,
mut store: StoreContextMut<'_, T>,
ty: TypeFuncIndex,
mut flags: InstanceFlags,
memory: *mut VMMemoryDefinition,
realloc: *mut VMFuncRef,
string_encoding: StringEncoding,
storage: &mut [MaybeUninit<ValRaw>],
closure: F,
) -> Result<()>
where
F: FnOnce(StoreContextMut<'_, T>, &[Val], &mut [Val]) -> Result<()>,
{
let options = Options::new(
store.0.id(),
NonNull::new(memory),
NonNull::new(realloc),
string_encoding,
);
// Perform a dynamic check that this instance can indeed be left. Exiting
// the component is disallowed, for example, when the `realloc` function
// calls a canonical import.
if !flags.may_leave() {
bail!("cannot leave component instance");
}
let args;
let ret_index;
let func_ty = &types[ty];
let param_tys = &types[func_ty.params];
let result_tys = &types[func_ty.results];
let mut cx = LiftContext::new(store.0, &options, types, instance);
cx.enter_call();
if let Some(param_count) = param_tys.abi.flat_count(MAX_FLAT_PARAMS) {
// NB: can use `MaybeUninit::slice_assume_init_ref` when that's stable
let mut iter =
mem::transmute::<&[MaybeUninit<ValRaw>], &[ValRaw]>(&storage[..param_count]).iter();
args = param_tys
.types
.iter()
.map(|ty| Val::lift(&mut cx, *ty, &mut iter))
.collect::<Result<Box<[_]>>>()?;
ret_index = param_count;
assert!(iter.next().is_none());
} else {
let mut offset =
validate_inbounds_dynamic(&param_tys.abi, cx.memory(), storage[0].assume_init_ref())?;
args = param_tys
.types
.iter()
.map(|ty| {
let abi = types.canonical_abi(ty);
let size = usize::try_from(abi.size32).unwrap();
let memory = &cx.memory()[abi.next_field32_size(&mut offset)..][..size];
Val::load(&mut cx, *ty, memory)
})
.collect::<Result<Box<[_]>>>()?;
ret_index = 1;
};
let mut result_vals = Vec::with_capacity(result_tys.types.len());
for _ in result_tys.types.iter() {
result_vals.push(Val::Bool(false));
}
closure(store.as_context_mut(), &args, &mut result_vals)?;
flags.set_may_leave(false);
let mut cx = LowerContext::new(store, &options, types, instance);
if let Some(cnt) = result_tys.abi.flat_count(MAX_FLAT_RESULTS) {
let mut dst = storage[..cnt].iter_mut();
for (val, ty) in result_vals.iter().zip(result_tys.types.iter()) {
val.lower(&mut cx, *ty, &mut dst)?;
}
assert!(dst.next().is_none());
} else {
let ret_ptr = storage[ret_index].assume_init_ref();
let mut ptr = validate_inbounds_dynamic(&result_tys.abi, cx.as_slice_mut(), ret_ptr)?;
for (val, ty) in result_vals.iter().zip(result_tys.types.iter()) {
let offset = types.canonical_abi(ty).next_field32_size(&mut ptr);
val.store(&mut cx, *ty, offset)?;
}
}
flags.set_may_leave(true);
cx.exit_call()?;
return Ok(());
}
fn validate_inbounds_dynamic(abi: &CanonicalAbiInfo, memory: &[u8], ptr: &ValRaw) -> Result<usize> {
// FIXME: needs memory64 support
let ptr = usize::try_from(ptr.get_u32()).err2anyhow()?;
if ptr % usize::try_from(abi.align32).err2anyhow()? != 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)
}
extern "C" fn dynamic_entrypoint<T, F>(
cx: *mut VMOpaqueContext,
data: *mut u8,
ty: TypeFuncIndex,
flags: InstanceFlags,
memory: *mut VMMemoryDefinition,
realloc: *mut VMFuncRef,
string_encoding: StringEncoding,
storage: *mut MaybeUninit<ValRaw>,
storage_len: usize,
) where
F: Fn(StoreContextMut<'_, T>, &[Val], &mut [Val]) -> Result<()> + Send + Sync + 'static,
{
let data = data as *const F;
unsafe {
call_host_and_handle_result(cx, |instance, types, store| {
call_host_dynamic::<T, _>(
instance,
types,
store,
ty,
flags,
memory,
realloc,
string_encoding,
core::slice::from_raw_parts_mut(storage, storage_len),
|store, params, results| (*data)(store, params, results),
)
})
}
}
@@ -1,552 +0,0 @@
use crate::component::matching::InstanceType;
use crate::component::resources::{HostResourceData, HostResourceIndex, HostResourceTables};
use crate::component::ResourceType;
use crate::prelude::*;
use crate::runtime::vm::component::{
CallContexts, ComponentInstance, InstanceFlags, ResourceTable, ResourceTables,
};
use crate::runtime::vm::{VMFuncRef, VMMemoryDefinition};
use crate::store::{StoreId, StoreOpaque};
use crate::{FuncType, StoreContextMut};
use alloc::sync::Arc;
use core::ptr::NonNull;
use wasmtime_environ::component::{ComponentTypes, StringEncoding, TypeResourceTableIndex};
/// Runtime representation of canonical ABI options in the component model.
///
/// This structure packages up the runtime representation of each option from
/// memories to reallocs to string encodings. Note that this is a "standalone"
/// structure which has raw pointers internally. This allows it to be created
/// out of thin air for a host function import, for example. The `store_id`
/// field, however, is what is used to pair this set of options with a store
/// reference to actually use the pointers.
#[derive(Copy, Clone)]
pub struct Options {
/// The store from which this options originated from.
store_id: StoreId,
/// An optional pointer for the memory that this set of options is referring
/// to. This option is not required to be specified in the canonical ABI
/// hence the `Option`.
///
/// Note that this pointer cannot be safely dereferenced unless a store,
/// verified with `self.store_id`, has the appropriate borrow available.
memory: Option<NonNull<VMMemoryDefinition>>,
/// Similar to `memory` but corresponds to the `canonical_abi_realloc`
/// function.
///
/// Safely using this pointer has the same restrictions as `memory` above.
realloc: Option<NonNull<VMFuncRef>>,
/// The encoding used for strings, if found.
///
/// This defaults to utf-8 but can be changed if necessary.
string_encoding: StringEncoding,
}
// The `Options` structure stores raw pointers but they're never used unless a
// `Store` is available so this should be threadsafe and largely inherit the
// thread-safety story of `Store<T>` itself.
unsafe impl Send for Options {}
unsafe impl Sync for Options {}
impl Options {
// TODO: prevent a ctor where the memory is memory64
/// Creates a new set of options with the specified components.
///
/// # Unsafety
///
/// This is unsafety as there is no way to statically verify the validity of
/// the arguments. For example pointers must be valid pointers, the
/// `StoreId` must be valid for the pointers, etc.
pub unsafe fn new(
store_id: StoreId,
memory: Option<NonNull<VMMemoryDefinition>>,
realloc: Option<NonNull<VMFuncRef>>,
string_encoding: StringEncoding,
) -> Options {
Options {
store_id,
memory,
realloc,
string_encoding,
}
}
fn realloc<'a, T>(
&self,
store: &'a mut StoreContextMut<'_, T>,
realloc_ty: &FuncType,
old: usize,
old_size: usize,
old_align: u32,
new_size: usize,
) -> Result<(&'a mut [u8], usize)> {
self.store_id.assert_belongs_to(store.0.id());
let realloc = self.realloc.unwrap();
let params = (
u32::try_from(old).err2anyhow()?,
u32::try_from(old_size).err2anyhow()?,
old_align,
u32::try_from(new_size).err2anyhow()?,
);
type ReallocFunc = crate::TypedFunc<(u32, u32, u32, u32), u32>;
// This call doesn't take any GC refs, and therefore we shouldn't ever
// need to GC before entering Wasm.
debug_assert!(!ReallocFunc::need_gc_before_call_raw(store.0, &params));
// Invoke the wasm malloc function using its raw and statically known
// signature.
let result = unsafe { ReallocFunc::call_raw(store, realloc_ty, realloc, params)? };
if result % old_align != 0 {
bail!("realloc return: result not aligned");
}
let result = usize::try_from(result).err2anyhow()?;
let memory = self.memory_mut(store.0);
let result_slice = match memory.get_mut(result..).and_then(|s| s.get_mut(..new_size)) {
Some(end) => end,
None => bail!("realloc return: beyond end of memory"),
};
Ok((result_slice, result))
}
/// Asserts that this function has an associated memory attached to it and
/// then returns the slice of memory tied to the lifetime of the provided
/// store.
pub fn memory<'a>(&self, store: &'a StoreOpaque) -> &'a [u8] {
self.store_id.assert_belongs_to(store.id());
// The unsafety here is intended to be encapsulated by the two
// preceding assertions. Namely we assert that the `store` is the same
// as the original store of this `Options`, meaning that we safely have
// either a shared reference or a mutable reference (as below) which
// means it's safe to view the memory (aka it's not a different store
// where our original store is on some other thread or something like
// that).
//
// Additionally the memory itself is asserted to be present as memory
// is an optional configuration in canonical ABI options.
unsafe {
let memory = self.memory.unwrap().as_ref();
core::slice::from_raw_parts(memory.base, memory.current_length())
}
}
/// Same as above, just `_mut`
pub fn memory_mut<'a>(&self, store: &'a mut StoreOpaque) -> &'a mut [u8] {
self.store_id.assert_belongs_to(store.id());
// See comments in `memory` about the unsafety
unsafe {
let memory = self.memory.unwrap().as_ref();
core::slice::from_raw_parts_mut(memory.base, memory.current_length())
}
}
/// Returns the underlying encoding used for strings in this
/// lifting/lowering.
pub fn string_encoding(&self) -> StringEncoding {
self.string_encoding
}
/// Returns the id of the store that this `Options` is connected to.
pub fn store_id(&self) -> StoreId {
self.store_id
}
}
/// 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> {
/// 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.
pub options: &'a Options,
/// 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,
/// A raw unsafe pointer to the component instance that's being lowered
/// into.
///
/// This pointer is required to be owned by the `store` provided.
instance: *mut ComponentInstance,
}
#[doc(hidden)]
impl<'a, T> LowerContext<'a, T> {
/// Creates a new lowering context from the specified parameters.
///
/// # Unsafety
///
/// This function is unsafe as it needs to be guaranteed by the caller that
/// the `instance` here is is valid within `store` and is a valid component
/// instance.
pub unsafe fn new(
store: StoreContextMut<'a, T>,
options: &'a Options,
types: &'a ComponentTypes,
instance: *mut ComponentInstance,
) -> LowerContext<'a, T> {
LowerContext {
store,
options,
types,
instance,
}
}
/// 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.options.memory_mut(self.store.0)
}
/// 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> {
let realloc_func_ty = Arc::clone(unsafe { (*self.instance).realloc_func_ty() });
let realloc_func_ty = realloc_func_ty.downcast_ref::<FuncType>().unwrap();
self.options
.realloc(
&mut self.store,
&realloc_func_ty,
old,
old_size,
old_align,
new_size,
)
.map(|(_, ptr)| ptr)
}
/// 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.
//
// Note that the unsafety here should be valid given the contract of
// `LowerContext::new`.
if unsafe { (*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>>,
flags: Option<InstanceFlags>,
) -> Result<HostResourceIndex> {
self.resource_tables()
.host_resource_lower_own(rep, dtor, flags)
}
/// 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<'_> {
// Note that the unsafety here should be valid given the contract of
// `LowerContext::new`.
InstanceType::new(unsafe { &*self.instance })
}
fn resource_tables(&mut self) -> HostResourceTables<'_> {
let (calls, host_table, host_resource_data) = self.store.0.component_resource_state();
HostResourceTables::from_parts(
ResourceTables {
host_table: Some(host_table),
calls,
// Note that the unsafety here should be valid given the contract of
// `LowerContext::new`.
tables: Some(unsafe { (*self.instance).component_resource_tables() }),
},
host_resource_data,
)
}
/// See [`HostResourceTables::enter_call`].
#[inline]
pub fn enter_call(&mut self) {
self.resource_tables().enter_call()
}
/// See [`HostResourceTables::exit_call`].
#[inline]
pub fn exit_call(&mut self) -> Result<()> {
self.resource_tables().exit_call()
}
}
/// 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> {
/// Like lowering, lifting always has options configured.
pub options: &'a Options,
/// Instance type information, like with lowering.
pub types: &'a Arc<ComponentTypes>,
memory: Option<&'a [u8]>,
instance: *mut ComponentInstance,
host_table: &'a mut ResourceTable,
host_resource_data: &'a mut HostResourceData,
calls: &'a mut CallContexts,
}
#[doc(hidden)]
impl<'a> LiftContext<'a> {
/// Creates a new lifting context given the provided context.
///
/// # Unsafety
///
/// This is unsafe for the same reasons as `LowerContext::new` where the
/// validity of `instance` is required to be upheld by the caller.
#[inline]
pub unsafe fn new(
store: &'a mut StoreOpaque,
options: &'a Options,
types: &'a Arc<ComponentTypes>,
instance: *mut ComponentInstance,
) -> LiftContext<'a> {
// From `&mut StoreOpaque` provided the goal here is to project out
// three different disjoint fields owned by the store: memory,
// `CallContexts`, and `ResourceTable`. 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 (calls, host_table, host_resource_data) =
(&mut *(store as *mut StoreOpaque)).component_resource_state();
let memory = options.memory.map(|_| options.memory(store));
LiftContext {
memory,
options,
types,
instance,
calls,
host_table,
host_resource_data,
}
}
/// 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.unwrap()
}
/// Returns an identifier for the store from which this `LiftContext` was
/// created.
pub fn store_id(&self) -> StoreId {
self.options.store_id
}
/// Returns the component instance raw pointer that is being lifted from.
pub fn instance_ptr(&self) -> *mut ComponentInstance {
self.instance
}
/// 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<InstanceFlags>)> {
let idx = self.resource_tables().guest_resource_lift_own(idx, ty)?;
// Note that the unsafety here should be valid given the contract of
// `LiftContext::new`.
let (dtor, flags) = unsafe { (*self.instance).dtor_and_flags(ty) };
Ok((idx, dtor, flags))
}
/// 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>>,
flags: Option<InstanceFlags>,
) -> Result<HostResourceIndex> {
self.resource_tables()
.host_resource_lower_own(rep, dtor, flags)
}
/// 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<'_> {
// Note that the unsafety here should be valid given the contract of
// `LiftContext::new`.
InstanceType::new(unsafe { &*self.instance })
}
fn resource_tables(&mut self) -> HostResourceTables<'_> {
HostResourceTables::from_parts(
ResourceTables {
host_table: Some(self.host_table),
calls: self.calls,
// Note that the unsafety here should be valid given the contract of
// `LiftContext::new`.
tables: Some(unsafe { (*self.instance).component_resource_tables() }),
},
self.host_resource_data,
)
}
/// See [`HostResourceTables::enter_call`].
#[inline]
pub fn enter_call(&mut self) {
self.resource_tables().enter_call()
}
/// See [`HostResourceTables::exit_call`].
#[inline]
pub fn exit_call(&mut self) -> Result<()> {
self.resource_tables().exit_call()
}
}
@@ -1,846 +0,0 @@
use crate::component::func::HostFunc;
use crate::component::matching::InstanceType;
use crate::component::{
Component, ComponentExportIndex, ComponentNamedList, Func, Lift, Lower, ResourceType, TypedFunc,
};
use crate::instance::OwnedImports;
use crate::linker::DefinitionType;
use crate::prelude::*;
use crate::runtime::vm::component::{ComponentInstance, OwnedComponentInstance};
use crate::runtime::vm::{CompiledModuleId, VMFuncRef};
use crate::store::{StoreOpaque, Stored};
use crate::{AsContextMut, Engine, Module, StoreContextMut};
use alloc::sync::Arc;
use core::marker;
use core::ptr::{self, NonNull};
use wasmtime_environ::{component::*, EngineOrModuleTypeIndex};
use wasmtime_environ::{EntityIndex, EntityType, Global, PrimaryMap, WasmValType};
/// An instantiated component.
///
/// This type represents an instantiated [`Component`](super::Component).
/// Instances have exports which can be accessed through functions such as
/// [`Instance::get_func`] or [`Instance::get_export`]. Instances are owned by a
/// [`Store`](crate::Store) and all methods require a handle to the store.
///
/// Component instances are created through
/// [`Linker::instantiate`](super::Linker::instantiate) and its family of
/// methods.
///
/// This type is similar to the core wasm version
/// [`wasmtime::Instance`](crate::Instance) except that it represents an
/// instantiated component instead of an instantiated module.
#[derive(Copy, Clone)]
pub struct Instance(pub(crate) Stored<Option<Box<InstanceData>>>);
pub(crate) struct InstanceData {
instances: PrimaryMap<RuntimeInstanceIndex, crate::Instance>,
// NB: in the future if necessary it would be possible to avoid storing an
// entire `Component` here and instead storing only information such as:
//
// * Some reference to `Arc<ComponentTypes>`
// * Necessary references to closed-over modules which are exported from the
// component itself.
//
// Otherwise the full guts of this component should only ever be used during
// the instantiation of this instance, meaning that after instantiation much
// of the component can be thrown away (theoretically).
component: Component,
state: OwnedComponentInstance,
/// Arguments that this instance used to be instantiated.
///
/// Strong references are stored to these arguments since pointers are saved
/// into the structures such as functions within the
/// `OwnedComponentInstance` but it's our job to keep them alive.
///
/// One purpose of this storage is to enable embedders to drop a `Linker`,
/// for example, after a component is instantiated. In that situation if the
/// arguments weren't held here then they might be dropped, and structures
/// such as `.lowering()` which point back into the original function would
/// become stale and use-after-free conditions when used. By preserving the
/// entire list here though we're guaranteed that nothing is lost for the
/// duration of the lifetime of this instance.
imports: Arc<PrimaryMap<RuntimeImportIndex, RuntimeImport>>,
}
impl Instance {
/// Looks up an exported function by name within this [`Instance`].
///
/// The `store` argument provided must be the store that this instance
/// lives within and the `name` argument is the lookup key by which to find
/// the exported function. If the function is found then `Some` is returned
/// and otherwise `None` is returned.
///
/// The `name` here can be a string such as `&str` or it can be a
/// [`ComponentExportIndex`] which is loaded prior from a [`Component`].
///
/// # Panics
///
/// Panics if `store` does not own this instance.
///
/// # Examples
///
/// Looking up a function which is exported from the root of a component:
///
/// ```
/// use wasmtime::{Engine, Store};
/// use wasmtime::component::{Component, Linker};
///
/// # fn main() -> wasmtime::Result<()> {
/// let engine = Engine::default();
/// let component = Component::new(
/// &engine,
/// r#"
/// (component
/// (core module $m
/// (func (export "f"))
/// )
/// (core instance $i (instantiate $m))
/// (func (export "f")
/// (canon lift (core func $i "f")))
/// )
/// "#,
/// )?;
///
/// // Look up the function by name
/// let mut store = Store::new(&engine, ());
/// let instance = Linker::new(&engine).instantiate(&mut store, &component)?;
/// let func = instance.get_func(&mut store, "f").unwrap();
///
/// // The function can also be looked up by an index via a precomputed index.
/// let (_, export) = component.export_index(None, "f").unwrap();
/// let func = instance.get_func(&mut store, &export).unwrap();
/// # Ok(())
/// # }
/// ```
///
/// Looking up a function which is exported from a nested instance:
///
/// ```
/// use wasmtime::{Engine, Store};
/// use wasmtime::component::{Component, Linker};
///
/// # fn main() -> wasmtime::Result<()> {
/// let engine = Engine::default();
/// let component = Component::new(
/// &engine,
/// r#"
/// (component
/// (core module $m
/// (func (export "f"))
/// )
/// (core instance $i (instantiate $m))
/// (func $f
/// (canon lift (core func $i "f")))
///
/// (instance $i
/// (export "f" (func $f)))
/// (export "i" (instance $i))
/// )
/// "#,
/// )?;
///
/// // First look up the exported instance, then use that to lookup the
/// // exported function.
/// let (_, instance_index) = component.export_index(None, "i").unwrap();
/// let (_, func_index) = component.export_index(Some(&instance_index), "f").unwrap();
///
/// // Then use `func_index` at runtime.
/// let mut store = Store::new(&engine, ());
/// let instance = Linker::new(&engine).instantiate(&mut store, &component)?;
/// let func = instance.get_func(&mut store, &func_index).unwrap();
///
/// // Alternatively the `instance` can be used directly in conjunction with
/// // the `get_export` method.
/// let instance_index = instance.get_export(&mut store, None, "i").unwrap();
/// let func_index = instance.get_export(&mut store, Some(&instance_index), "f").unwrap();
/// let func = instance.get_func(&mut store, &func_index).unwrap();
/// # Ok(())
/// # }
/// ```
pub fn get_func(
&self,
mut store: impl AsContextMut,
name: impl InstanceExportLookup,
) -> Option<Func> {
let store = store.as_context_mut().0;
let data = store[self.0].take().unwrap();
let ret = name.lookup(&data.component).and_then(|index| {
match &data.component.env_component().export_items[index] {
Export::LiftedFunction { ty, func, options } => Some(Func::from_lifted_func(
store, self, &data, *ty, func, options,
)),
_ => None,
}
});
store[self.0] = Some(data);
ret
}
/// Looks up an exported [`Func`] value by name and with its type.
///
/// This function is a convenience wrapper over [`Instance::get_func`] and
/// [`Func::typed`]. For more information see the linked documentation.
///
/// Returns an error if `name` isn't a function export or if the export's
/// type did not match `Params` or `Results`
///
/// # Panics
///
/// Panics if `store` does not own this instance.
pub fn get_typed_func<Params, Results>(
&self,
mut store: impl AsContextMut,
name: impl InstanceExportLookup,
) -> Result<TypedFunc<Params, Results>>
where
Params: ComponentNamedList + Lower,
Results: ComponentNamedList + Lift,
{
let f = self
.get_func(store.as_context_mut(), name)
.ok_or_else(|| anyhow!("failed to find function export"))?;
Ok(f.typed::<Params, Results>(store)
.with_context(|| format!("failed to convert function to given type"))?)
}
/// Looks up an exported module by name within this [`Instance`].
///
/// The `store` argument provided must be the store that this instance
/// lives within and the `name` argument is the lookup key by which to find
/// the exported module. If the module is found then `Some` is returned
/// and otherwise `None` is returned.
///
/// The `name` here can be a string such as `&str` or it can be a
/// [`ComponentExportIndex`] which is loaded prior from a [`Component`].
///
/// For some examples see [`Instance::get_func`] for loading values from a
/// component.
///
/// # Panics
///
/// Panics if `store` does not own this instance.
pub fn get_module(
&self,
mut store: impl AsContextMut,
name: impl InstanceExportLookup,
) -> Option<Module> {
let store = store.as_context_mut().0;
let (data, export, _) = self.lookup_export(store, name)?;
match export {
Export::ModuleStatic { index, .. } => {
Some(data.component.static_module(*index).clone())
}
Export::ModuleImport { import, .. } => match &data.imports[*import] {
RuntimeImport::Module(m) => Some(m.clone()),
_ => unreachable!(),
},
_ => None,
}
}
/// Looks up an exported resource type by name within this [`Instance`].
///
/// The `store` argument provided must be the store that this instance
/// lives within and the `name` argument is the lookup key by which to find
/// the exported resource. If the resource is found then `Some` is returned
/// and otherwise `None` is returned.
///
/// The `name` here can be a string such as `&str` or it can be a
/// [`ComponentExportIndex`] which is loaded prior from a [`Component`].
///
/// For some examples see [`Instance::get_func`] for loading values from a
/// component.
///
/// # Panics
///
/// Panics if `store` does not own this instance.
pub fn get_resource(
&self,
mut store: impl AsContextMut,
name: impl InstanceExportLookup,
) -> Option<ResourceType> {
let store = store.as_context_mut().0;
let (data, export, _) = self.lookup_export(store, name)?;
match export {
Export::Type(TypeDef::Resource(id)) => Some(data.ty().resource_type(*id)),
Export::Type(_)
| Export::LiftedFunction { .. }
| Export::ModuleStatic { .. }
| Export::ModuleImport { .. }
| Export::Instance { .. } => None,
}
}
/// A methods similar to [`Component::export_index`] except for this
/// instance.
///
/// This method will lookup the `name` provided within the `instance`
/// provided and return a [`ComponentExportIndex`] which can be used to
/// pass to other `get_*` functions like [`Instance::get_func`].
///
/// # Panics
///
/// Panics if `store` does not own this instance.
pub fn get_export(
&self,
mut store: impl AsContextMut,
instance: Option<&ComponentExportIndex>,
name: &str,
) -> Option<ComponentExportIndex> {
self._get_export(store.as_context_mut().0, instance, name)
}
fn _get_export(
&self,
store: &StoreOpaque,
instance: Option<&ComponentExportIndex>,
name: &str,
) -> Option<ComponentExportIndex> {
let data = store[self.0].as_ref().unwrap();
let index = data.component.lookup_export_index(instance, name)?;
Some(ComponentExportIndex {
id: data.component_id(),
index,
})
}
fn lookup_export<'a>(
&self,
store: &'a StoreOpaque,
name: impl InstanceExportLookup,
) -> Option<(&'a InstanceData, &'a Export, ExportIndex)> {
let data = store[self.0].as_ref().unwrap();
let index = name.lookup(&data.component)?;
Some((
data,
&data.component.env_component().export_items[index],
index,
))
}
}
/// Trait used to lookup the export of a component instance.
///
/// This trait is used as an implementation detail of [`Instance::get_func`]
/// and related `get_*` methods. Notable implementors of this trait are:
///
/// * `str`
/// * `String`
/// * [`ComponentExportIndex`]
///
/// Note that this is intended to be a `wasmtime`-sealed trait so it shouldn't
/// need to be implemented externally.
pub trait InstanceExportLookup {
#[doc(hidden)]
fn lookup(&self, component: &Component) -> Option<ExportIndex>;
}
impl<T> InstanceExportLookup for &T
where
T: InstanceExportLookup + ?Sized,
{
fn lookup(&self, component: &Component) -> Option<ExportIndex> {
T::lookup(self, component)
}
}
impl InstanceExportLookup for str {
fn lookup(&self, component: &Component) -> Option<ExportIndex> {
component
.env_component()
.exports
.get(self, &NameMapNoIntern)
.copied()
}
}
impl InstanceExportLookup for String {
fn lookup(&self, component: &Component) -> Option<ExportIndex> {
str::lookup(self, component)
}
}
impl InstanceData {
pub fn lookup_def(&self, store: &mut StoreOpaque, def: &CoreDef) -> crate::runtime::vm::Export {
match def {
CoreDef::Export(e) => self.lookup_export(store, e),
CoreDef::Trampoline(idx) => {
crate::runtime::vm::Export::Function(crate::runtime::vm::ExportFunction {
func_ref: self.state.trampoline_func_ref(*idx),
})
}
CoreDef::InstanceFlags(idx) => {
crate::runtime::vm::Export::Global(crate::runtime::vm::ExportGlobal {
definition: self.state.instance_flags(*idx).as_raw(),
vmctx: ptr::null_mut(),
global: Global {
wasm_ty: WasmValType::I32,
mutability: true,
},
})
}
}
}
pub fn lookup_export<T>(
&self,
store: &mut StoreOpaque,
item: &CoreExport<T>,
) -> crate::runtime::vm::Export
where
T: Copy + Into<EntityIndex>,
{
let instance = &self.instances[item.instance];
let id = instance.id(store);
let instance = store.instance_mut(id);
let idx = match &item.item {
ExportItem::Index(idx) => (*idx).into(),
// FIXME: ideally at runtime we don't actually do any name lookups
// here. This will only happen when the host supplies an imported
// module so while the structure can't be known at compile time we
// do know at `InstancePre` time, for example, what all the host
// imports are. In theory we should be able to, as part of
// `InstancePre` construction, perform all name=>index mappings
// during that phase so the actual instantiation of an `InstancePre`
// skips all string lookups. This should probably only be
// investigated if this becomes a performance issue though.
ExportItem::Name(name) => instance.module().exports[name],
};
instance.get_export_by_index(idx)
}
#[inline]
pub fn instance(&self) -> &ComponentInstance {
&self.state
}
#[inline]
pub fn instance_ptr(&self) -> *mut ComponentInstance {
self.state.instance_ptr()
}
#[inline]
pub fn component_types(&self) -> &Arc<ComponentTypes> {
self.component.types()
}
#[inline]
pub fn component_id(&self) -> CompiledModuleId {
self.component.id()
}
#[inline]
pub fn ty(&self) -> InstanceType<'_> {
InstanceType::new(self.instance())
}
// NB: This method is only intended to be called during the instantiation
// process because the `Arc::get_mut` here is fallible and won't generally
// succeed once the instance has been handed to the embedder. Before that
// though it should be guaranteed that the single owning reference currently
// lives within the `ComponentInstance` that's being built.
fn resource_types_mut(&mut self) -> &mut ImportedResources {
Arc::get_mut(self.state.resource_types_mut())
.unwrap()
.downcast_mut()
.unwrap()
}
}
struct Instantiator<'a> {
component: &'a Component,
data: InstanceData,
core_imports: OwnedImports,
imports: &'a PrimaryMap<RuntimeImportIndex, RuntimeImport>,
}
pub(crate) enum RuntimeImport {
Func(Arc<HostFunc>),
Module(Module),
Resource {
ty: ResourceType,
// A strong reference to the host function that represents the
// destructor for this resource. At this time all resources here are
// host-defined resources. Note that this is itself never read because
// the funcref below points to it.
//
// Also note that the `Arc` here is used to support the same host
// function being used across multiple instances simultaneously. Or
// otherwise this makes `InstancePre::instantiate` possible to create
// separate instances all sharing the same host function.
_dtor: Arc<crate::func::HostFunc>,
// A raw function which is filled out (including `wasm_call`) which
// points to the internals of the `_dtor` field. This is read and
// possibly executed by wasm.
dtor_funcref: VMFuncRef,
},
}
pub type ImportedResources = PrimaryMap<ResourceIndex, ResourceType>;
impl<'a> Instantiator<'a> {
fn new(
component: &'a Component,
store: &mut StoreOpaque,
imports: &'a Arc<PrimaryMap<RuntimeImportIndex, RuntimeImport>>,
) -> Instantiator<'a> {
let env_component = component.env_component();
store.modules_mut().register_component(component);
let imported_resources: ImportedResources =
PrimaryMap::with_capacity(env_component.imported_resources.len());
Instantiator {
component,
imports,
core_imports: OwnedImports::empty(),
data: InstanceData {
instances: PrimaryMap::with_capacity(env_component.num_runtime_instances as usize),
component: component.clone(),
state: OwnedComponentInstance::new(
component.runtime_info(),
Arc::new(imported_resources),
store.traitobj(),
),
imports: imports.clone(),
},
}
}
fn run<T>(&mut self, store: &mut StoreContextMut<'_, T>) -> Result<()> {
let env_component = self.component.env_component();
// Before all initializers are processed configure all destructors for
// host-defined resources. No initializer will correspond to these and
// it's required to happen before they're needed, so execute this first.
for (idx, import) in env_component.imported_resources.iter() {
let (ty, func_ref) = match &self.imports[*import] {
RuntimeImport::Resource {
ty, dtor_funcref, ..
} => (*ty, NonNull::from(dtor_funcref)),
_ => unreachable!(),
};
let i = self.data.resource_types_mut().push(ty);
assert_eq!(i, idx);
self.data.state.set_resource_destructor(idx, Some(func_ref));
}
// Next configure all `VMFuncRef`s for trampolines that this component
// will require. These functions won't actually get used until their
// associated state has been initialized through the global initializers
// below, but the funcrefs can all be configured here.
for (idx, sig) in env_component.trampolines.iter() {
let ptrs = self.component.trampoline_ptrs(idx);
let signature = match self.component.signatures().shared_type(*sig) {
Some(s) => s,
None => panic!("found unregistered signature: {sig:?}"),
};
self.data
.state
.set_trampoline(idx, ptrs.wasm_call, ptrs.array_call, signature);
}
for initializer in env_component.initializers.iter() {
match initializer {
GlobalInitializer::InstantiateModule(m) => {
let module;
let imports = match m {
// Since upvars are statically know we know that the
// `args` list is already in the right order.
InstantiateModule::Static(idx, args) => {
module = self.component.static_module(*idx);
self.build_imports(store.0, module, args.iter())
}
// With imports, unlike upvars, we need to do runtime
// lookups with strings to determine the order of the
// imports since it's whatever the actual module
// requires.
//
// FIXME: see the note in `ExportItem::Name` handling
// above for how we ideally shouldn't do string lookup
// here.
InstantiateModule::Import(idx, args) => {
module = match &self.imports[*idx] {
RuntimeImport::Module(m) => m,
_ => unreachable!(),
};
let args = module
.imports()
.map(|import| &args[import.module()][import.name()]);
self.build_imports(store.0, module, args)
}
};
// Note that the unsafety here should be ok because the
// validity of the component means that type-checks have
// already been performed. This means that the unsafety due
// to imports having the wrong type should not happen here.
//
// Also note we are calling new_started_impl because we have
// already checked for asyncness and are running on a fiber
// if required.
let i = unsafe {
crate::Instance::new_started_impl(store, module, imports.as_ref())?
};
self.data.instances.push(i);
}
GlobalInitializer::LowerImport { import, index } => {
let func = match &self.imports[*import] {
RuntimeImport::Func(func) => func,
_ => unreachable!(),
};
self.data.state.set_lowering(*index, func.lowering());
}
GlobalInitializer::ExtractMemory(mem) => self.extract_memory(store.0, mem),
GlobalInitializer::ExtractRealloc(realloc) => {
self.extract_realloc(store.0, realloc)
}
GlobalInitializer::ExtractPostReturn(post_return) => {
self.extract_post_return(store.0, post_return)
}
GlobalInitializer::Resource(r) => self.resource(store.0, r),
}
}
Ok(())
}
fn resource(&mut self, store: &mut StoreOpaque, resource: &Resource) {
let dtor = resource
.dtor
.as_ref()
.map(|dtor| self.data.lookup_def(store, dtor));
let dtor = dtor.map(|export| match export {
crate::runtime::vm::Export::Function(f) => f.func_ref,
_ => unreachable!(),
});
let index = self
.component
.env_component()
.resource_index(resource.index);
self.data.state.set_resource_destructor(index, dtor);
let ty = ResourceType::guest(store.id(), &self.data.state, resource.index);
let i = self.data.resource_types_mut().push(ty);
debug_assert_eq!(i, index);
}
fn extract_memory(&mut self, store: &mut StoreOpaque, memory: &ExtractMemory) {
let mem = match self.data.lookup_export(store, &memory.export) {
crate::runtime::vm::Export::Memory(m) => m,
_ => unreachable!(),
};
self.data
.state
.set_runtime_memory(memory.index, mem.definition);
}
fn extract_realloc(&mut self, store: &mut StoreOpaque, realloc: &ExtractRealloc) {
let func_ref = match self.data.lookup_def(store, &realloc.def) {
crate::runtime::vm::Export::Function(f) => f.func_ref,
_ => unreachable!(),
};
self.data.state.set_runtime_realloc(realloc.index, func_ref);
}
fn extract_post_return(&mut self, store: &mut StoreOpaque, post_return: &ExtractPostReturn) {
let func_ref = match self.data.lookup_def(store, &post_return.def) {
crate::runtime::vm::Export::Function(f) => f.func_ref,
_ => unreachable!(),
};
self.data
.state
.set_runtime_post_return(post_return.index, func_ref);
}
fn build_imports<'b>(
&mut self,
store: &mut StoreOpaque,
module: &Module,
args: impl Iterator<Item = &'b CoreDef>,
) -> &OwnedImports {
self.core_imports.clear();
self.core_imports.reserve(module);
let mut imports = module.compiled_module().module().imports();
for arg in args {
// The general idea of Wasmtime is that at runtime type-checks for
// core wasm instantiations internally within a component are
// unnecessary and superfluous. Naturally though mistakes may be
// made, so double-check this property of wasmtime in debug mode.
if cfg!(debug_assertions) {
let (imp_module, imp_name, expected) = imports.next().unwrap();
self.assert_type_matches(store, module, arg, imp_module, imp_name, expected);
}
// The unsafety here should be ok since the `export` is loaded
// directly from an instance which should only give us valid export
// items.
let export = self.data.lookup_def(store, arg);
unsafe {
self.core_imports.push_export(&export);
}
}
debug_assert!(imports.next().is_none());
&self.core_imports
}
fn assert_type_matches(
&self,
store: &mut StoreOpaque,
module: &Module,
arg: &CoreDef,
imp_module: &str,
imp_name: &str,
expected: EntityType,
) {
let export = self.data.lookup_def(store, arg);
// If this value is a core wasm function then the type check is inlined
// here. This can otherwise fail `Extern::from_wasmtime_export` because
// there's no guarantee that there exists a trampoline for `f` so this
// can't fall through to the case below
if let crate::runtime::vm::Export::Function(f) = &export {
let expected = match expected.unwrap_func() {
EngineOrModuleTypeIndex::Engine(e) => Some(e),
EngineOrModuleTypeIndex::Module(m) => module.signatures().shared_type(m),
EngineOrModuleTypeIndex::RecGroup(_) => unreachable!(),
};
let actual = unsafe { f.func_ref.as_ref().type_index };
assert_eq!(
expected,
Some(actual),
"type mismatch for import {imp_module:?} {imp_name:?}!!!\n\n\
expected {:#?}\n\n\
found {:#?}",
expected.and_then(|e| store.engine().signatures().borrow(e)),
store.engine().signatures().borrow(actual)
);
return;
}
let val = unsafe { crate::Extern::from_wasmtime_export(export, store) };
let ty = DefinitionType::from(store, &val);
crate::types::matching::MatchCx::new(module.engine())
.definition(&expected, &ty)
.expect("unexpected typecheck failure");
}
}
/// A "pre-instantiated" [`Instance`] which has all of its arguments already
/// supplied and is ready to instantiate.
///
/// This structure represents an efficient form of instantiation where import
/// type-checking and import lookup has all been resolved by the time that this
/// type is created. This type is primarily created through the
/// [`Linker::instantiate_pre`](crate::component::Linker::instantiate_pre)
/// method.
pub struct InstancePre<T> {
component: Component,
imports: Arc<PrimaryMap<RuntimeImportIndex, RuntimeImport>>,
_marker: marker::PhantomData<fn() -> T>,
}
// `InstancePre`'s clone does not require `T: Clone`
impl<T> Clone for InstancePre<T> {
fn clone(&self) -> Self {
Self {
component: self.component.clone(),
imports: self.imports.clone(),
_marker: self._marker,
}
}
}
impl<T> InstancePre<T> {
/// This function is `unsafe` since there's no guarantee that the
/// `RuntimeImport` items provided are guaranteed to work with the `T` of
/// the store.
///
/// Additionally there is no static guarantee that the `imports` provided
/// satisfy the imports of the `component` provided.
pub(crate) unsafe fn new_unchecked(
component: Component,
imports: PrimaryMap<RuntimeImportIndex, RuntimeImport>,
) -> InstancePre<T> {
InstancePre {
component,
imports: Arc::new(imports),
_marker: marker::PhantomData,
}
}
/// Returns the underlying component that will be instantiated.
pub fn component(&self) -> &Component {
&self.component
}
/// Returns the underlying engine.
pub fn engine(&self) -> &Engine {
self.component.engine()
}
/// Performs the instantiation process into the store specified.
//
// TODO: needs more docs
pub fn instantiate(&self, store: impl AsContextMut<Data = T>) -> Result<Instance> {
assert!(
!store.as_context().async_support(),
"must use async instantiation when async support is enabled"
);
self.instantiate_impl(store)
}
/// Performs the instantiation process into the store specified.
///
/// Exactly like [`Self::instantiate`] except for use on async stores.
//
// TODO: needs more docs
#[cfg(feature = "async")]
pub async fn instantiate_async(
&self,
mut store: impl AsContextMut<Data = T>,
) -> Result<Instance>
where
T: Send,
{
let mut store = store.as_context_mut();
assert!(
store.0.async_support(),
"must use sync instantiation when async support is disabled"
);
store.on_fiber(|store| self.instantiate_impl(store)).await?
}
fn instantiate_impl(&self, mut store: impl AsContextMut<Data = T>) -> Result<Instance> {
let mut store = store.as_context_mut();
store
.engine()
.allocator()
.increment_component_instance_count()?;
let mut instantiator = Instantiator::new(&self.component, store.0, &self.imports);
instantiator.run(&mut store).map_err(|e| {
store
.engine()
.allocator()
.decrement_component_instance_count();
e
})?;
let data = Box::new(instantiator.data);
let instance = Instance(store.0.store_data_mut().insert(Some(data)));
store.0.push_component_instance(instance);
Ok(instance)
}
}
File diff suppressed because it is too large. Load diff
@@ -1,43 +0,0 @@
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>();
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 T) -> &mut [ValRaw] {
assert_raw_slice_compat::<T>();
slice::from_raw_parts_mut(
(storage as *mut 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>());
&mut *slice.as_mut_ptr().cast()
}
@@ -1,29 +0,0 @@
use crate::prelude::*;
use crate::store::{StoreData, StoredData};
macro_rules! component_store_data {
($($field:ident => $t:ty,)*) => (
#[derive(Default)]
pub struct ComponentStoreData {
$($field: Vec<$t>,)*
}
$(
impl StoredData for $t {
#[inline]
fn list(data: &StoreData) -> &Vec<Self> {
&data.components.$field
}
#[inline]
fn list_mut(data: &mut StoreData) -> &mut Vec<Self> {
&mut data.components.$field
}
}
)*
)
}
component_store_data! {
funcs => crate::component::func::FuncData,
instances => Option<Box<crate::component::instance::InstanceData>>,
}
@@ -1,312 +0,0 @@
use crate::prelude::*;
use crate::runtime::vm::{GcRootsList, SendSyncPtr};
use crate::{
store::{AutoAssertNoGc, StoreData, StoreOpaque, Stored},
trampoline::generate_global_export,
AnyRef, AsContext, AsContextMut, ExternRef, Func, GlobalType, HeapType, Mutability, Ref,
RootedGcRefImpl, Val, ValType,
};
use core::ptr;
use core::ptr::NonNull;
use wasmtime_environ::TypeTrace;
/// 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(transparent)] // here for the C API
pub struct Global(pub(super) Stored<crate::runtime::vm::ExportGlobal>);
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`.
///
/// # Examples
///
/// ```
/// # use wasmtime::*;
/// # fn main() -> anyhow::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",
)?;
unsafe {
let wasmtime_export = generate_global_export(store, ty, val)?;
Ok(Global::from_wasmtime_global(wasmtime_export, store))
}
}
/// 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 {
let ty = &store[self.0].global;
GlobalType::from_wasmtime_global(store.engine(), &ty)
}
/// 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 {
unsafe {
let store = store.as_context_mut();
let mut store = AutoAssertNoGc::new(store.0);
let definition = &*store[self.0].definition;
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.as_u128()).into()),
ValType::Ref(ref_ty) => {
let reference: Ref = match ref_ty.heap_type() {
HeapType::Func | HeapType::ConcreteFunc(_) => {
Func::_from_raw(&mut 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.unwrap_gc_store_mut().clone_gc_ref(r);
ExternRef::from_cloned_gc_ref(&mut store, r)
})
.into(),
),
HeapType::NoExtern => Ref::Extern(None),
HeapType::Any
| HeapType::Eq
| HeapType::I31
| HeapType::Struct
| HeapType::ConcreteStruct(_)
| HeapType::Array
| HeapType::ConcreteArray(_) => definition
.as_gc_ref()
.map(|r| {
let r = store.unwrap_gc_store_mut().clone_gc_ref(r);
AnyRef::from_cloned_gc_ref(&mut store, r)
})
.into(),
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.
///
/// # Panics
///
/// Panics if `store` does not own this global.
pub fn set(&self, mut store: impl AsContextMut, val: Val) -> Result<()> {
let mut store = AutoAssertNoGc::new(store.as_context_mut().0);
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")?;
unsafe {
let definition = &mut *store[self.0].definition;
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.as_u128_mut() = i.into(),
Val::FuncRef(f) => {
*definition.as_func_ref_mut() = f.map_or(ptr::null_mut(), |f| {
f.vm_func_ref(&mut store).as_ptr().cast()
});
}
Val::ExternRef(e) => {
let new = match e {
None => None,
#[cfg_attr(not(feature = "gc"), allow(unreachable_patterns))]
Some(e) => Some(e.try_gc_ref(&store)?.unchecked_copy()),
};
let new = new.as_ref();
definition.write_gc_ref(store.unwrap_gc_store_mut(), new);
}
Val::AnyRef(a) => {
let new = match a {
None => None,
#[cfg_attr(not(feature = "gc"), allow(unreachable_patterns))]
Some(a) => Some(a.try_gc_ref(&store)?.unchecked_copy()),
};
let new = new.as_ref();
definition.write_gc_ref(store.unwrap_gc_store_mut(), new);
}
}
}
Ok(())
}
pub(crate) fn trace_root(&self, store: &mut StoreOpaque, gc_roots_list: &mut 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 { (*store[self.0].definition).as_gc_ref() } {
let gc_ref = NonNull::from(gc_ref);
let gc_ref = SendSyncPtr::new(gc_ref);
unsafe {
gc_roots_list.add_root(gc_ref, "Wasm global");
}
}
}
}
pub(crate) unsafe fn from_wasmtime_global(
mut wasmtime_export: crate::runtime::vm::ExportGlobal,
store: &mut StoreOpaque,
) -> Global {
wasmtime_export
.global
.wasm_ty
.canonicalize_for_runtime_usage(&mut |module_index| {
crate::runtime::vm::Instance::from_vmctx(wasmtime_export.vmctx, |instance| {
instance.engine_type_index(module_index)
})
});
Global(store.store_data_mut().insert(wasmtime_export))
}
pub(crate) fn wasmtime_ty<'a>(&self, data: &'a StoreData) -> &'a wasmtime_environ::Global {
&data[self.0].global
}
pub(crate) fn vmimport(&self, store: &StoreOpaque) -> crate::runtime::vm::VMGlobalImport {
crate::runtime::vm::VMGlobalImport {
from: store[self.0].definition,
}
}
/// 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.
pub(crate) fn hash_key(&self, store: &StoreOpaque) -> impl core::hash::Hash + Eq {
store[self.0].definition as usize
}
}
#[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(())
}
}
@@ -1,484 +0,0 @@
use crate::prelude::*;
use crate::runtime::vm::{self as runtime};
use crate::store::{AutoAssertNoGc, StoreData, StoreOpaque, Stored};
use crate::trampoline::generate_table_export;
use crate::{AnyRef, AsContext, AsContextMut, ExternRef, Func, HeapType, Ref, TableType};
use core::iter;
use core::ptr::NonNull;
use runtime::{GcRootsList, SendSyncPtr};
use wasmtime_environ::TypeTrace;
/// 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(transparent)] // here for the C API
pub struct Table(pub(super) Stored<crate::runtime::vm::ExportTable>);
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.
///
/// # Panics
///
/// This function will panic 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.
///
/// # Examples
///
/// ```
/// # use wasmtime::*;
/// # fn main() -> anyhow::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> {
Table::_new(store.as_context_mut().0, ty, init)
}
/// Async variant of [`Table::new`]. You must use this variant with
/// [`Store`](`crate::Store`)s which have a
/// [`ResourceLimiterAsync`](`crate::ResourceLimiterAsync`).
///
/// # Panics
///
/// This function will panic when used with a non-async
/// [`Store`](`crate::Store`)
#[cfg(feature = "async")]
pub async fn new_async<T>(
mut store: impl AsContextMut<Data = T>,
ty: TableType,
init: Ref,
) -> Result<Table>
where
T: Send,
{
let mut store = store.as_context_mut();
assert!(
store.0.async_support(),
"cannot use `new_async` without enabling async support on the config"
);
store
.on_fiber(|store| Table::_new(store.0, ty, init))
.await?
}
fn _new(store: &mut StoreOpaque, ty: TableType, init: Ref) -> Result<Table> {
let wasmtime_export = generate_table_export(store, &ty)?;
let init = init.into_table_element(store, ty.element())?;
unsafe {
let table = Table::from_wasmtime_table(wasmtime_export, store);
let wasmtime_table = table.wasmtime_table(store, iter::empty());
(*wasmtime_table)
.fill(store.optional_gc_store_mut()?, 0, init, ty.minimum())
.err2anyhow()?;
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)
}
fn _ty(&self, store: &StoreOpaque) -> TableType {
let ty = &store[self.0].table;
TableType::from_wasmtime_table(store.engine(), ty)
}
fn wasmtime_table(
&self,
store: &mut StoreOpaque,
lazy_init_range: impl Iterator<Item = u64>,
) -> *mut runtime::Table {
unsafe {
let export = &store[self.0];
crate::runtime::vm::Instance::from_vmctx(export.vmctx, |handle| {
let idx = handle.table_index(&*export.definition);
handle.get_defined_table_with_lazy_init(idx, lazy_init_range)
})
}
}
/// 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 = self.wasmtime_table(&mut store, iter::once(index));
let gc_store = store.optional_gc_store_mut().ok().and_then(|s| s);
unsafe {
match (*table).get(gc_store, index)? {
runtime::TableElement::FuncRef(f) => {
let func = Func::from_vm_func_ref(&mut store, f);
Some(func.into())
}
runtime::TableElement::UninitFunc => {
unreachable!("lazy init above should have converted UninitFunc")
}
runtime::TableElement::GcRef(None) => {
Some(Ref::null(self._ty(&store).element().heap_type()))
}
#[cfg_attr(not(feature = "gc"), allow(unreachable_code, unused_variables))]
runtime::TableElement::GcRef(Some(x)) => {
match self._ty(&store).element().heap_type().top() {
HeapType::Any => {
let x = AnyRef::from_cloned_gc_ref(&mut store, x);
Some(x.into())
}
HeapType::Extern => {
let x = ExternRef::from_cloned_gc_ref(&mut store, x);
Some(x.into())
}
HeapType::Func => {
unreachable!("never have TableElement::GcRef for func tables")
}
ty => unreachable!("not a top type: {ty:?}"),
}
}
}
}
}
/// 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.
///
/// # Panics
///
/// Panics if `store` does not own this table.
pub fn set(&self, mut store: impl AsContextMut, index: u64, val: Ref) -> Result<()> {
let store = store.as_context_mut().0;
let ty = self.ty(&store);
let val = val.into_table_element(store, ty.element())?;
let table = self.wasmtime_table(store, iter::empty());
unsafe {
(*table)
.set(index, val)
.map_err(|()| anyhow!("table element index out of bounds"))
}
}
/// 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.internal_size(store.as_context().0)
}
pub(crate) fn internal_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.
unsafe { u64::try_from((*store[self.0].definition).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`.
///
/// # Panics
///
/// Panics if `store` does not own this table.
///
/// This function will panic 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.
pub fn grow(&self, mut store: impl AsContextMut, delta: u64, init: Ref) -> Result<u64> {
let store = store.as_context_mut().0;
let ty = self.ty(&store);
let init = init.into_table_element(store, ty.element())?;
let table = self.wasmtime_table(store, iter::empty());
unsafe {
match (*table).grow(delta, init, store)? {
Some(size) => {
let vm = (*table).vmtable();
*store[self.0].definition = vm;
// unwrap here should be ok because the runtime should always guarantee
// that we can fit the table size in a 64-bit integer.
Ok(u64::try_from(size).unwrap())
}
None => bail!("failed to grow table by `{}`", delta),
}
}
}
/// Async variant of [`Table::grow`]. Required when using a
/// [`ResourceLimiterAsync`](`crate::ResourceLimiterAsync`).
///
/// # Panics
///
/// This function will panic when used with a non-async
/// [`Store`](`crate::Store`).
#[cfg(feature = "async")]
pub async fn grow_async<T>(
&self,
mut store: impl AsContextMut<Data = T>,
delta: u64,
init: Ref,
) -> Result<u64>
where
T: Send,
{
let mut store = store.as_context_mut();
assert!(
store.0.async_support(),
"cannot use `grow_async` without enabling async support on the config"
);
store
.on_fiber(|store| self.grow(store, 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.
///
/// # 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 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",
)?;
let dst_table = dst_table.wasmtime_table(store, iter::empty());
let src_range = src_index..(src_index.checked_add(len).unwrap_or(u64::MAX));
let src_table = src_table.wasmtime_table(store, src_range);
unsafe {
runtime::Table::copy(
store.optional_gc_store_mut()?,
dst_table,
src_table,
dst_index,
src_index,
len,
)
.err2anyhow()?;
}
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.
///
/// # 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<()> {
let store = store.as_context_mut().0;
let ty = self.ty(&store);
let val = val.into_table_element(store, ty.element())?;
let table = self.wasmtime_table(store, iter::empty());
unsafe {
(*table)
.fill(store.optional_gc_store_mut()?, dst, val, len)
.err2anyhow()?;
}
Ok(())
}
pub(crate) fn trace_roots(&self, store: &mut StoreOpaque, gc_roots_list: &mut 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 unsafe { (*table).gc_refs_mut() } {
if let Some(gc_ref) = gc_ref {
let gc_ref = NonNull::from(gc_ref);
let gc_ref = SendSyncPtr::new(gc_ref);
unsafe {
gc_roots_list.add_root(gc_ref, "Wasm table element");
}
}
}
}
pub(crate) unsafe fn from_wasmtime_table(
mut wasmtime_export: crate::runtime::vm::ExportTable,
store: &mut StoreOpaque,
) -> Table {
// Ensure that the table's type is engine-level canonicalized.
wasmtime_export
.table
.ref_type
.canonicalize_for_runtime_usage(&mut |module_index| {
crate::runtime::vm::Instance::from_vmctx(wasmtime_export.vmctx, |instance| {
instance.engine_type_index(module_index)
})
});
Table(store.store_data_mut().insert(wasmtime_export))
}
pub(crate) fn wasmtime_ty<'a>(&self, data: &'a StoreData) -> &'a wasmtime_environ::Table {
&data[self.0].table
}
pub(crate) fn vmimport(&self, store: &StoreOpaque) -> crate::runtime::vm::VMTableImport {
let export = &store[self.0];
crate::runtime::vm::VMTableImport {
from: export.definition,
vmctx: export.vmctx,
}
}
/// 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.
#[allow(dead_code)] // Not used yet, but added for consistency.
pub(crate) fn hash_key(&self, store: &StoreOpaque) -> impl core::hash::Hash + Eq {
store[self.0].definition as usize
}
}
#[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(())
}
}
@@ -1,343 +0,0 @@
//! Implements a registry of modules for a store.
use crate::code::CodeObject;
#[cfg(feature = "component-model")]
use crate::component::Component;
use crate::prelude::*;
use crate::runtime::vm::VMWasmCallFunction;
use crate::sync::{OnceLock, RwLock};
use crate::{code_memory::CodeMemory, FrameInfo, Module};
use alloc::collections::btree_map::{BTreeMap, Entry};
use alloc::sync::Arc;
use core::ptr::NonNull;
use wasmtime_environ::VMSharedTypeIndex;
/// Used for registering modules with a store.
///
/// Note that the primary reason for this registry is to ensure that everything
/// in `Module` is kept alive for the duration of a `Store`. At this time we
/// need "basically everything" within a `Module` to stay alive once it's
/// instantiated within a store. While there's some smaller portions that could
/// theoretically be omitted as they're not needed by the store they're
/// currently small enough to not worry much about.
#[derive(Default)]
pub struct ModuleRegistry {
// Keyed by the end address of a `CodeObject`.
//
// The value here is the start address and the information about what's
// loaded at that address.
loaded_code: BTreeMap<usize, (usize, LoadedCode)>,
// Preserved for keeping data segments alive or similar
modules_without_code: Vec<Module>,
}
struct LoadedCode {
/// Kept alive here in the store to have a strong reference to keep the
/// relevant code mapped while the store is alive.
_code: Arc<CodeObject>,
/// Modules found within `self.code`, keyed by start address here of the
/// address of the first function in the module.
modules: BTreeMap<usize, Module>,
}
/// An identifier of a module that has previously been inserted into a
/// `ModuleRegistry`.
#[derive(Clone, Copy)]
pub enum RegisteredModuleId {
/// Index into `ModuleRegistry::modules_without_code`.
WithoutCode(usize),
/// Start address of the module's code so that we can get it again via
/// `ModuleRegistry::lookup_module`.
LoadedCode(usize),
}
impl ModuleRegistry {
/// Get a previously-registered module by id.
pub fn lookup_module_by_id(&self, id: RegisteredModuleId) -> Option<&Module> {
match id {
RegisteredModuleId::WithoutCode(idx) => self.modules_without_code.get(idx),
RegisteredModuleId::LoadedCode(pc) => {
let (module, _) = self.module_and_offset(pc)?;
Some(module)
}
}
}
/// Fetches a registered module given a program counter value.
pub fn lookup_module_by_pc(&self, pc: usize) -> Option<&Module> {
let (module, _) = self.module_and_offset(pc)?;
Some(module)
}
fn code(&self, pc: usize) -> Option<(&LoadedCode, usize)> {
let (end, (start, code)) = self.loaded_code.range(pc..).next()?;
if pc < *start || *end < pc {
return None;
}
Some((code, pc - *start))
}
fn module_and_offset(&self, pc: usize) -> Option<(&Module, usize)> {
let (code, offset) = self.code(pc)?;
Some((code.module(pc)?, offset))
}
/// Gets an iterator over all modules in the registry.
pub fn all_modules(&self) -> impl Iterator<Item = &'_ Module> + '_ {
self.loaded_code
.values()
.flat_map(|(_, code)| code.modules.values())
.chain(self.modules_without_code.iter())
}
/// Registers a new module with the registry.
pub fn register_module(&mut self, module: &Module) -> RegisteredModuleId {
self.register(module.code_object(), Some(module)).unwrap()
}
#[cfg(feature = "component-model")]
pub fn register_component(&mut self, component: &Component) {
self.register(component.code_object(), None);
}
/// Registers a new module with the registry.
fn register(
&mut self,
code: &Arc<CodeObject>,
module: Option<&Module>,
) -> Option<RegisteredModuleId> {
let text = code.code_memory().text();
// If there's not actually any functions in this module then we may
// still need to preserve it for its data segments. Instances of this
// module will hold a pointer to the data stored in the module itself,
// and for schemes that perform lazy initialization which could use the
// module in the future. For that reason we continue to register empty
// modules and retain them.
if text.is_empty() {
return module.map(|module| {
let id = RegisteredModuleId::WithoutCode(self.modules_without_code.len());
self.modules_without_code.push(module.clone());
id
});
}
// The module code range is exclusive for end, so make it inclusive as
// it may be a valid PC value
let start_addr = text.as_ptr() as usize;
let end_addr = start_addr + text.len() - 1;
let id = module.map(|_| RegisteredModuleId::LoadedCode(start_addr));
// If this module is already present in the registry then that means
// it's either an overlapping image, for example for two modules
// found within a component, or it's a second instantiation of the same
// module. Delegate to `push_module` to find out.
if let Some((other_start, prev)) = self.loaded_code.get_mut(&end_addr) {
assert_eq!(*other_start, start_addr);
if let Some(module) = module {
prev.push_module(module);
}
return id;
}
// Assert that this module's code doesn't collide with any other
// registered modules
if let Some((_, (prev_start, _))) = self.loaded_code.range(start_addr..).next() {
assert!(*prev_start > end_addr);
}
if let Some((prev_end, _)) = self.loaded_code.range(..=start_addr).next_back() {
assert!(*prev_end < start_addr);
}
let mut item = LoadedCode {
_code: code.clone(),
modules: Default::default(),
};
if let Some(module) = module {
item.push_module(module);
}
let prev = self.loaded_code.insert(end_addr, (start_addr, item));
assert!(prev.is_none());
id
}
/// Fetches frame information about a program counter in a backtrace.
///
/// Returns an object if this `pc` is known to some previously registered
/// module, or returns `None` if no information can be found. The first
/// boolean returned indicates whether the original module has unparsed
/// debug information due to the compiler's configuration. The second
/// boolean indicates whether the engine used to compile this module is
/// using environment variables to control debuginfo parsing.
pub(crate) fn lookup_frame_info(&self, pc: usize) -> Option<(FrameInfo, &Module)> {
let (module, offset) = self.module_and_offset(pc)?;
let info = FrameInfo::new(module.clone(), offset)?;
Some((info, module))
}
pub fn wasm_to_array_trampoline(
&self,
sig: VMSharedTypeIndex,
) -> Option<NonNull<VMWasmCallFunction>> {
// TODO: We are doing a linear search over each module. This is fine for
// now because we typically have very few modules per store (almost
// always one, in fact). If this linear search ever becomes a
// bottleneck, we could avoid it by incrementally and lazily building a
// `VMSharedSignatureIndex` to `SignatureIndex` map.
//
// See also the comment in `ModuleInner::wasm_to_native_trampoline`.
for (_, code) in self.loaded_code.values() {
for module in code.modules.values() {
if let Some(trampoline) = module.wasm_to_array_trampoline(sig) {
return Some(trampoline);
}
}
}
None
}
}
impl LoadedCode {
fn push_module(&mut self, module: &Module) {
let func = match module.compiled_module().finished_functions().next() {
Some((_, func)) => func,
// There are no compiled functions in this module so there's no
// need to push onto `self.modules` which is only used for frame
// information lookup for a trap which only symbolicates defined
// functions.
None => return,
};
let start = func.as_ptr() as usize;
match self.modules.entry(start) {
// This module is already present, and it should be the same as
// `module`.
Entry::Occupied(m) => {
debug_assert!(Arc::ptr_eq(&module.inner, &m.get().inner));
}
// This module was not already present, so now it's time to insert.
Entry::Vacant(v) => {
v.insert(module.clone());
}
}
}
fn module(&self, pc: usize) -> Option<&Module> {
// The `modules` map is keyed on the start address of the first
// function in the module, so find the first module whose start address
// is less than the `pc`. That may be the wrong module but lookup
// within the module should fail in that case.
let (_start, module) = self.modules.range(..=pc).next_back()?;
Some(module)
}
}
// This is the global code registry that stores information for all loaded code
// objects that are currently in use by any `Store` in the current process.
//
// The purpose of this map is to be called from signal handlers to determine
// whether a program counter is a wasm trap or not. Specifically macOS has
// no contextual information about the thread available, hence the necessity
// for global state rather than using thread local state.
//
// This is similar to `ModuleRegistry` except that it has less information and
// supports removal. Any time anything is registered with a `ModuleRegistry`
// it is also automatically registered with the singleton global module
// registry. When a `ModuleRegistry` is destroyed then all of its entries
// are removed from the global registry.
fn global_code() -> &'static RwLock<GlobalRegistry> {
static GLOBAL_CODE: OnceLock<RwLock<GlobalRegistry>> = OnceLock::new();
GLOBAL_CODE.get_or_init(Default::default)
}
type GlobalRegistry = BTreeMap<usize, (usize, Arc<CodeMemory>)>;
/// Find which registered region of code contains the given program counter, and
/// what offset that PC is within that module's code.
pub fn lookup_code(pc: usize) -> Option<(Arc<CodeMemory>, usize)> {
let all_modules = global_code().read();
let (_end, (start, module)) = all_modules.range(pc..).next()?;
let text_offset = pc.checked_sub(*start)?;
Some((module.clone(), text_offset))
}
/// Registers a new region of code.
///
/// Must not have been previously registered and must be `unregister`'d to
/// prevent leaking memory.
///
/// This is required to enable traps to work correctly since the signal handler
/// will lookup in the `GLOBAL_CODE` list to determine which a particular pc
/// is a trap or not.
pub fn register_code(code: &Arc<CodeMemory>) {
let text = code.text();
if text.is_empty() {
return;
}
let start = text.as_ptr() as usize;
let end = start + text.len() - 1;
let prev = global_code().write().insert(end, (start, code.clone()));
assert!(prev.is_none());
}
/// Unregisters a code mmap from the global map.
///
/// Must have been previously registered with `register`.
pub fn unregister_code(code: &Arc<CodeMemory>) {
let text = code.text();
if text.is_empty() {
return;
}
let end = (text.as_ptr() as usize) + text.len() - 1;
let code = global_code().write().remove(&end);
assert!(code.is_some());
}
#[test]
#[cfg_attr(miri, ignore)]
fn test_frame_info() -> Result<(), anyhow::Error> {
use crate::*;
let mut store = Store::<()>::default();
let module = Module::new(
store.engine(),
r#"
(module
(func (export "add") (param $x i32) (param $y i32) (result i32) (i32.add (local.get $x) (local.get $y)))
(func (export "sub") (param $x i32) (param $y i32) (result i32) (i32.sub (local.get $x) (local.get $y)))
(func (export "mul") (param $x i32) (param $y i32) (result i32) (i32.mul (local.get $x) (local.get $y)))
(func (export "div_s") (param $x i32) (param $y i32) (result i32) (i32.div_s (local.get $x) (local.get $y)))
(func (export "div_u") (param $x i32) (param $y i32) (result i32) (i32.div_u (local.get $x) (local.get $y)))
(func (export "rem_s") (param $x i32) (param $y i32) (result i32) (i32.rem_s (local.get $x) (local.get $y)))
(func (export "rem_u") (param $x i32) (param $y i32) (result i32) (i32.rem_u (local.get $x) (local.get $y)))
)
"#,
)?;
// Create an instance to ensure the frame information is registered.
Instance::new(&mut store, &module, &[])?;
for (i, alloc) in module.compiled_module().finished_functions() {
let (start, end) = {
let ptr = alloc.as_ptr();
let len = alloc.len();
(ptr as usize, ptr as usize + len)
};
for pc in start..end {
let (frame, _) = store
.as_context()
.0
.modules()
.lookup_frame_info(pc)
.unwrap();
assert!(
frame.func_index() == i.as_u32(),
"lookup of {:#x} returned {}, expected {}",
pc,
frame.func_index(),
i.as_u32()
);
}
}
Ok(())
}
File diff suppressed because it is too large. Load diff
-301
View File
@@ -1,301 +0,0 @@
use crate::prelude::*;
use crate::store::StoreOpaque;
use crate::{StoreContext, StoreContextMut};
use core::fmt;
use core::marker;
use core::num::NonZeroU64;
use core::ops::{Index, IndexMut};
use core::sync::atomic::{AtomicU64, Ordering::Relaxed};
// This is defined here, in a private submodule, so we can explicitly reexport
// it only as `pub(crate)`. This avoids a ton of
// crate-private-type-in-public-interface errors that aren't really too
// interesting to deal with.
#[derive(Copy, Clone)]
pub struct InstanceId(pub(super) usize);
impl InstanceId {
pub fn from_index(idx: usize) -> InstanceId {
InstanceId(idx)
}
}
pub struct StoreData {
id: StoreId,
funcs: Vec<crate::func::FuncData>,
tables: Vec<crate::runtime::vm::ExportTable>,
globals: Vec<crate::runtime::vm::ExportGlobal>,
instances: Vec<crate::instance::InstanceData>,
memories: Vec<crate::runtime::vm::ExportMemory>,
#[cfg(feature = "component-model")]
pub(crate) components: crate::component::ComponentStoreData,
}
pub trait StoredData: Sized {
fn list(data: &StoreData) -> &Vec<Self>;
fn list_mut(data: &mut StoreData) -> &mut Vec<Self>;
}
macro_rules! impl_store_data {
($($field:ident => $t:ty,)*) => ($(
impl StoredData for $t {
#[inline]
fn list(data: &StoreData) -> &Vec<Self> { &data.$field }
#[inline]
fn list_mut(data: &mut StoreData) -> &mut Vec<Self> { &mut data.$field }
}
)*)
}
impl_store_data! {
funcs => crate::func::FuncData,
tables => crate::runtime::vm::ExportTable,
globals => crate::runtime::vm::ExportGlobal,
instances => crate::instance::InstanceData,
memories => crate::runtime::vm::ExportMemory,
}
impl StoreData {
pub fn new() -> StoreData {
StoreData {
id: StoreId::allocate(),
funcs: Vec::new(),
tables: Vec::new(),
globals: Vec::new(),
instances: Vec::new(),
memories: Vec::new(),
#[cfg(feature = "component-model")]
components: Default::default(),
}
}
pub fn id(&self) -> StoreId {
self.id
}
pub fn insert<T>(&mut self, data: T) -> Stored<T>
where
T: StoredData,
{
let list = T::list_mut(self);
let index = list.len();
list.push(data);
Stored::new(self.id, index)
}
pub fn next_id<T>(&self) -> Stored<T>
where
T: StoredData,
{
Stored::new(self.id, T::list(self).len())
}
pub fn contains<T>(&self, id: Stored<T>) -> bool
where
T: StoredData,
{
if id.store_id != self.id {
return false;
}
// This should be true as an invariant of our API, but double-check with
// debug assertions enabled.
debug_assert!(id.index() < T::list(self).len());
true
}
pub fn iter<T>(&self) -> impl ExactSizeIterator<Item = Stored<T>>
where
T: StoredData,
{
let id = self.id;
(0..T::list(self).len()).map(move |i| Stored::new(id, i))
}
pub(crate) fn reserve_funcs(&mut self, count: usize) {
self.funcs.reserve(count);
}
}
impl<T> Index<Stored<T>> for StoreData
where
T: StoredData,
{
type Output = T;
#[inline]
fn index(&self, index: Stored<T>) -> &Self::Output {
index.assert_belongs_to(self.id);
// Note that if this is ever a performance bottleneck it should be safe
// to use unchecked indexing here because presence of a `Stored<T>` is
// proof of an item having been inserted into a store and lists in
// stores are never shrunk. After the store check above the actual index
// should always be valid.
&T::list(self)[index.index()]
}
}
impl<T> IndexMut<Stored<T>> for StoreData
where
T: StoredData,
{
#[inline]
fn index_mut(&mut self, index: Stored<T>) -> &mut Self::Output {
index.assert_belongs_to(self.id);
// Note that this could be unchecked indexing, see the note in `Index`
// above.
&mut T::list_mut(self)[index.index()]
}
}
// forward StoreContext => StoreData
impl<I, T> Index<I> for StoreContext<'_, T>
where
StoreData: Index<I>,
{
type Output = <StoreData as Index<I>>::Output;
#[inline]
fn index(&self, index: I) -> &Self::Output {
self.0.store_data.index(index)
}
}
// forward StoreContextMut => StoreData
impl<I, T> Index<I> for StoreContextMut<'_, T>
where
StoreData: Index<I>,
{
type Output = <StoreData as Index<I>>::Output;
#[inline]
fn index(&self, index: I) -> &Self::Output {
self.0.store_data.index(index)
}
}
// forward StoreOpaque => StoreData
impl<I> Index<I> for StoreOpaque
where
StoreData: Index<I>,
{
type Output = <StoreData as Index<I>>::Output;
#[inline]
fn index(&self, index: I) -> &Self::Output {
self.store_data().index(index)
}
}
impl<I> IndexMut<I> for StoreOpaque
where
StoreData: IndexMut<I>,
{
#[inline]
fn index_mut(&mut self, index: I) -> &mut Self::Output {
self.store_data_mut().index_mut(index)
}
}
/// A unique identifier to get attached to a store.
///
/// This identifier is embedded into the `Stored<T>` structure and is used to
/// identify the original store that items come from. For example a `Memory` is
/// owned by a `Store` and will embed a `StoreId` internally to say which store
/// it came from. Comparisons with this value are how panics are generated for
/// mismatching the item that a store belongs to.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
#[repr(transparent)] // NB: relied on in the C API
pub struct StoreId(NonZeroU64);
impl StoreId {
/// Allocates a new unique identifier for a store that has never before been
/// used in this process.
pub fn allocate() -> StoreId {
static NEXT_ID: AtomicU64 = AtomicU64::new(0);
// Only allow 2^63 stores at which point we start panicking to prevent
// overflow.
//
// If a store is created once per microsecond then this will last the
// current process for 584,540 years before overflowing.
//
// Also note the usage of `Relaxed` ordering here which should be ok
// since we're only looking for atomicity on this counter and this
// otherwise isn't used to synchronize memory stored anywhere else.
let id = NEXT_ID.fetch_add(1, Relaxed);
if id & (1 << 63) != 0 {
NEXT_ID.store(1 << 63, Relaxed);
panic!("store id allocator overflow");
}
StoreId(NonZeroU64::new(id + 1).unwrap())
}
#[inline]
pub fn assert_belongs_to(&self, store: StoreId) {
if *self == store {
return;
}
store_id_mismatch();
}
/// Raw accessor for the C API.
pub fn as_raw(&self) -> NonZeroU64 {
self.0
}
/// Raw constructor for the C API.
pub fn from_raw(id: NonZeroU64) -> StoreId {
StoreId(id)
}
}
#[repr(C)] // used by reference in the C API, also in `wasmtime_func_t`.
pub struct Stored<T> {
store_id: StoreId,
index: usize,
_marker: marker::PhantomData<fn() -> T>,
}
impl<T> Stored<T> {
fn new(store_id: StoreId, index: usize) -> Stored<T> {
Stored {
store_id,
index,
_marker: marker::PhantomData,
}
}
#[inline]
pub fn assert_belongs_to(&self, store: StoreId) {
self.store_id.assert_belongs_to(store)
}
fn index(&self) -> usize {
self.index
}
}
#[cold]
fn store_id_mismatch() {
panic!("object used with the wrong store");
}
impl<T> PartialEq for Stored<T> {
fn eq(&self, other: &Stored<T>) -> bool {
self.store_id == other.store_id && self.index == other.index
}
}
impl<T> Copy for Stored<T> {}
impl<T> Clone for Stored<T> {
fn clone(&self) -> Self {
*self
}
}
impl<T> fmt::Debug for Stored<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "store={}, index={}", self.store_id.0, self.index())
}
}
@@ -1,90 +0,0 @@
//! Lifetime management of `VMFuncRef`s inside of stores, and filling in their
//! trampolines.
use crate::module::ModuleRegistry;
use crate::prelude::*;
use crate::runtime::vm::{SendSyncPtr, VMArrayCallHostFuncContext, VMFuncRef};
use alloc::sync::Arc;
use core::ptr::NonNull;
/// An arena of `VMFuncRef`s.
///
/// Allows a store to pin and own funcrefs so that it can patch in trampolines
/// for `VMFuncRef`s that are missing a `wasm_call` trampoline and
/// need Wasm to supply it.
#[derive(Default)]
pub struct FuncRefs {
/// A bump allocation arena where we allocate `VMFuncRef`s such
/// that they are pinned and owned.
bump: SendSyncBump,
/// Pointers into `self.bump` for entries that need `wasm_call` field filled
/// in.
with_holes: Vec<SendSyncPtr<VMFuncRef>>,
/// Pinned `VMFuncRef`s that had their `wasm_call` field
/// pre-patched when constructing an `InstancePre`, and which we need to
/// keep alive for our owning store's lifetime.
instance_pre_func_refs: Vec<Arc<[VMFuncRef]>>,
}
use send_sync_bump::SendSyncBump;
mod send_sync_bump {
#[derive(Default)]
pub struct SendSyncBump(bumpalo::Bump);
impl SendSyncBump {
pub fn alloc<T>(&mut self, val: T) -> &mut T {
self.0.alloc(val)
}
}
// Safety: We require `&mut self` on the only public method, which means it
// is safe to send `&SendSyncBump` references across threads because they
// can't actually do anything with it.
unsafe impl Sync for SendSyncBump {}
}
impl FuncRefs {
/// Push the given `VMFuncRef` into this arena, returning a
/// pinned pointer to it.
///
/// # Safety
///
/// You may only access the return value on the same thread as this
/// `FuncRefs` and only while the store holding this `FuncRefs` exists.
pub unsafe fn push(&mut self, func_ref: VMFuncRef) -> NonNull<VMFuncRef> {
debug_assert!(func_ref.wasm_call.is_none());
// Debug assert that the vmctx is a `VMArrayCallHostFuncContext` as
// that is the only kind that can have holes.
let _ = unsafe { VMArrayCallHostFuncContext::from_opaque(func_ref.vmctx) };
let func_ref = self.bump.alloc(func_ref);
let unpatched = SendSyncPtr::from(func_ref);
let ret = unpatched.as_non_null();
self.with_holes.push(unpatched);
ret
}
/// Patch any `VMFuncRef::wasm_call`s that need filling in.
pub fn fill(&mut self, modules: &ModuleRegistry) {
self.with_holes.retain_mut(|f| {
unsafe {
let func_ref = f.as_mut();
debug_assert!(func_ref.wasm_call.is_none());
// Debug assert that the vmctx is a `VMArrayCallHostFuncContext` as
// that is the only kind that can have holes.
let _ = VMArrayCallHostFuncContext::from_opaque(func_ref.vmctx);
func_ref.wasm_call = modules.wasm_to_array_trampoline(func_ref.type_index);
func_ref.wasm_call.is_none()
}
});
}
/// Push pre-patched `VMFuncRef`s from an `InstancePre`.
pub fn push_instance_pre_func_refs(&mut self, func_refs: Arc<[VMFuncRef]>) {
self.instance_pre_func_refs.push(func_refs);
}
}
-76
View File
@@ -1,76 +0,0 @@
//! Utility module to create trampolines in/out WebAssembly module.
mod func;
mod global;
mod memory;
mod table;
pub use self::func::*;
pub use self::global::*;
pub(crate) use memory::MemoryCreatorProxy;
use self::memory::create_memory;
use self::table::create_table;
use crate::prelude::*;
use crate::runtime::vm::{
Imports, InstanceAllocationRequest, InstanceAllocator, ModuleRuntimeInfo,
OnDemandInstanceAllocator, SharedMemory, StorePtr, VMFunctionImport,
};
use crate::store::{InstanceId, StoreOpaque};
use crate::{MemoryType, TableType};
use alloc::sync::Arc;
use core::any::Any;
use wasmtime_environ::{MemoryIndex, Module, TableIndex, VMSharedTypeIndex};
fn create_handle(
module: Module,
store: &mut StoreOpaque,
host_state: Box<dyn Any + Send + Sync>,
func_imports: &[VMFunctionImport],
one_signature: Option<VMSharedTypeIndex>,
) -> Result<InstanceId> {
let mut imports = Imports::default();
imports.functions = func_imports;
unsafe {
let config = store.engine().config();
// Use the on-demand allocator when creating handles associated with host objects
// The configured instance allocator should only be used when creating module instances
// as we don't want host objects to count towards instance limits.
let module = Arc::new(module);
let runtime_info = &ModuleRuntimeInfo::bare_maybe_imported_func(module, one_signature);
let allocator = OnDemandInstanceAllocator::new(config.mem_creator.clone(), 0);
let handle = allocator.allocate_module(InstanceAllocationRequest {
imports,
host_state,
store: StorePtr::new(store.traitobj()),
runtime_info,
wmemcheck: false,
pkey: None,
tunables: store.engine().tunables(),
})?;
Ok(store.add_dummy_instance(handle))
}
}
pub fn generate_memory_export(
store: &mut StoreOpaque,
m: &MemoryType,
preallocation: Option<&SharedMemory>,
) -> Result<crate::runtime::vm::ExportMemory> {
let instance = create_memory(store, m, preallocation)?;
Ok(store
.instance_mut(instance)
.get_exported_memory(MemoryIndex::from_u32(0)))
}
pub fn generate_table_export(
store: &mut StoreOpaque,
t: &TableType,
) -> Result<crate::runtime::vm::ExportTable> {
let instance = create_table(store, t)?;
Ok(store
.instance_mut(instance)
.get_exported_table(TableIndex::from_u32(0)))
}
@@ -1,93 +0,0 @@
//! Support for a calling of an imported function.
use crate::prelude::*;
use crate::runtime::vm::{
StoreBox, VMArrayCallHostFuncContext, VMContext, VMFuncRef, VMOpaqueContext,
};
use crate::type_registry::RegisteredType;
use crate::{FuncType, ValRaw};
use core::ptr;
struct TrampolineState<F> {
func: F,
// Need to keep our `VMSharedTypeIndex` registered in the engine.
#[allow(dead_code)]
sig: RegisteredType,
}
/// Shim to call a host-defined function that uses the array calling convention.
///
/// Together with `VMArrayCallHostFuncContext`, this implements the transition
/// from a raw, non-closure function pointer to a Rust closure that associates
/// data and function together.
///
/// Also shepherds panics and traps across Wasm.
unsafe extern "C" fn array_call_shim<F>(
vmctx: *mut VMOpaqueContext,
caller_vmctx: *mut VMOpaqueContext,
values_vec: *mut ValRaw,
values_vec_len: usize,
) where
F: Fn(*mut VMContext, &mut [ValRaw]) -> Result<()> + 'static,
{
// Here we are careful to use `catch_unwind` to ensure Rust panics don't
// unwind past us. The primary reason for this is that Rust considers it UB
// to unwind past an `extern "C"` function. Here we are in an `extern "C"`
// function and the cross into wasm was through an `extern "C"` function at
// the base of the stack as well. We'll need to wait for assorted RFCs and
// language features to enable this to be done in a sound and stable fashion
// before avoiding catching the panic here.
//
// Also note that there are intentionally no local variables on this stack
// frame. The reason for that is that some of the "raise" functions we have
// below will trigger a longjmp, which won't run local destructors if we
// have any. To prevent leaks we avoid having any local destructors by
// avoiding local variables.
let result = crate::runtime::vm::catch_unwind_and_longjmp(|| {
let vmctx = VMArrayCallHostFuncContext::from_opaque(vmctx);
// Double-check ourselves in debug mode, but we control
// the `Any` here so an unsafe downcast should also
// work.
let state = (*vmctx).host_state();
debug_assert!(state.is::<TrampolineState<F>>());
let state = &*(state as *const _ as *const TrampolineState<F>);
let values_vec = core::slice::from_raw_parts_mut(values_vec, values_vec_len);
(state.func)(VMContext::from_opaque(caller_vmctx), values_vec)
});
match result {
Ok(()) => {}
// If a trap was raised (an error returned from the imported function)
// then we smuggle the trap through `Box<dyn Error>` through to the
// call-site, which gets unwrapped in `Trap::from_runtime` later on as we
// convert from the internal `Trap` type to our own `Trap` type in this
// crate.
Err(trap) => crate::trap::raise(trap.into()),
}
}
pub fn create_array_call_function<F>(
ft: &FuncType,
func: F,
) -> Result<StoreBox<VMArrayCallHostFuncContext>>
where
F: Fn(*mut VMContext, &mut [ValRaw]) -> Result<()> + Send + Sync + 'static,
{
let array_call = array_call_shim::<F>;
let sig = ft.clone().into_registered_type();
unsafe {
Ok(VMArrayCallHostFuncContext::new(
VMFuncRef {
array_call,
wasm_call: None,
type_index: sig.index(),
vmctx: ptr::null_mut(),
},
Box::new(TrampolineState { func, sig }),
))
}
}
@@ -1,70 +0,0 @@
use crate::runtime::vm::{StoreBox, VMGlobalDefinition};
use crate::store::{AutoAssertNoGc, StoreOpaque};
use crate::{GlobalType, Mutability, Result, RootedGcRefImpl, Val};
use core::ptr;
#[repr(C)]
pub struct VMHostGlobalContext {
pub(crate) ty: GlobalType,
pub(crate) global: VMGlobalDefinition,
}
pub fn generate_global_export(
store: &mut StoreOpaque,
ty: GlobalType,
val: Val,
) -> Result<crate::runtime::vm::ExportGlobal> {
let global = wasmtime_environ::Global {
wasm_ty: ty.content().to_wasm_type(),
mutability: match ty.mutability() {
Mutability::Const => false,
Mutability::Var => true,
},
};
let ctx = StoreBox::new(VMHostGlobalContext {
ty,
global: VMGlobalDefinition::new(),
});
let mut store = AutoAssertNoGc::new(store);
let definition = unsafe {
let global = &mut (*ctx.get()).global;
match val {
Val::I32(x) => *global.as_i32_mut() = x,
Val::I64(x) => *global.as_i64_mut() = x,
Val::F32(x) => *global.as_f32_bits_mut() = x,
Val::F64(x) => *global.as_f64_bits_mut() = x,
Val::V128(x) => *global.as_u128_mut() = x.into(),
Val::FuncRef(f) => {
*global.as_func_ref_mut() =
f.map_or(ptr::null_mut(), |f| f.vm_func_ref(&mut store).as_ptr());
}
Val::ExternRef(x) => {
let new = match x {
None => None,
#[cfg_attr(not(feature = "gc"), allow(unreachable_patterns))]
Some(x) => Some(x.try_gc_ref(&store)?.unchecked_copy()),
};
let new = new.as_ref();
global.write_gc_ref(store.gc_store_mut()?, new);
}
Val::AnyRef(a) => {
let new = match a {
None => None,
#[cfg_attr(not(feature = "gc"), allow(unreachable_patterns))]
Some(a) => Some(a.try_gc_ref(&store)?.unchecked_copy()),
};
let new = new.as_ref();
global.write_gc_ref(store.gc_store_mut()?, new);
}
}
global
};
store.host_globals().push(ctx);
Ok(crate::runtime::vm::ExportGlobal {
definition,
vmctx: ptr::null_mut(),
global,
})
}
@@ -1,26 +0,0 @@
use crate::prelude::*;
use crate::store::{InstanceId, StoreOpaque};
use crate::trampoline::create_handle;
use crate::TableType;
use wasmtime_environ::{EntityIndex, Module, TypeTrace};
pub fn create_table(store: &mut StoreOpaque, table: &TableType) -> Result<InstanceId> {
let mut module = Module::new();
let wasmtime_table = *table.wasmtime_table();
debug_assert!(
wasmtime_table.ref_type.is_canonicalized_for_runtime_usage(),
"should be canonicalized for runtime usage: {:?}",
wasmtime_table.ref_type
);
let table_id = module.tables.push(wasmtime_table);
// TODO: can this `exports.insert` get removed?
module
.exports
.insert(String::new(), EntityIndex::Table(table_id));
create_handle(module, store, Box::new(()), &[], None)
}
File diff suppressed because it is too large. Load diff
@@ -1,5 +0,0 @@
/// An uninhabited type.
///
/// Useful for modeling Wasm's bottom types or `cfg`'d off features.
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub(crate) enum Uninhabited {}
-409
View File
@@ -1,409 +0,0 @@
//! Runtime library support for Wasmtime.
#![deny(missing_docs)]
// See documentation in crates/wasmtime/src/runtime.rs for why this is
// selectively enabled here.
#![warn(clippy::cast_sign_loss)]
use crate::prelude::*;
use crate::store::StoreOpaque;
use alloc::sync::Arc;
use core::fmt;
use core::mem;
use core::ops::Deref;
use core::ops::DerefMut;
use core::ptr::NonNull;
use core::sync::atomic::{AtomicUsize, Ordering};
use wasmtime_environ::{
DefinedFuncIndex, DefinedMemoryIndex, HostPtr, ModuleInternedTypeIndex, VMOffsets,
VMSharedTypeIndex,
};
mod arch;
mod async_yield;
#[cfg(feature = "component-model")]
pub mod component;
mod const_expr;
mod export;
mod gc;
mod imports;
mod instance;
mod memory;
mod mmap;
mod mmap_vec;
mod send_sync_ptr;
mod send_sync_unsafe_cell;
mod store_box;
mod sys;
mod table;
mod traphandlers;
mod vmcontext;
mod threads;
pub use self::threads::*;
#[cfg(feature = "debug-builtins")]
pub mod debug_builtins;
pub mod libcalls;
pub mod mpk;
#[cfg(feature = "debug-builtins")]
pub use wasmtime_jit_debug::gdb_jit_int::GdbJitImageRegistration;
pub use crate::runtime::vm::arch::get_stack_pointer;
pub use crate::runtime::vm::async_yield::*;
pub use crate::runtime::vm::export::*;
pub use crate::runtime::vm::gc::*;
pub use crate::runtime::vm::imports::Imports;
pub use crate::runtime::vm::instance::{
GcHeapAllocationIndex, Instance, InstanceAllocationRequest, InstanceAllocator,
InstanceAllocatorImpl, InstanceHandle, MemoryAllocationIndex, OnDemandInstanceAllocator,
StorePtr, TableAllocationIndex,
};
#[cfg(feature = "pooling-allocator")]
pub use crate::runtime::vm::instance::{
InstanceLimits, PoolConcurrencyLimitError, PoolingInstanceAllocator,
PoolingInstanceAllocatorConfig,
};
pub use crate::runtime::vm::memory::{Memory, RuntimeLinearMemory, RuntimeMemoryCreator};
pub use crate::runtime::vm::mmap::Mmap;
pub use crate::runtime::vm::mmap_vec::MmapVec;
pub use crate::runtime::vm::mpk::MpkEnabled;
pub use crate::runtime::vm::store_box::*;
pub use crate::runtime::vm::sys::unwind::UnwindRegistration;
pub use crate::runtime::vm::table::{Table, TableElement};
pub use crate::runtime::vm::traphandlers::*;
pub use crate::runtime::vm::vmcontext::{
VMArrayCallFunction, VMArrayCallHostFuncContext, VMContext, VMFuncRef, VMFunctionBody,
VMFunctionImport, VMGlobalDefinition, VMGlobalImport, VMMemoryDefinition, VMMemoryImport,
VMOpaqueContext, VMRuntimeLimits, VMTableImport, VMWasmCallFunction, ValRaw,
};
pub use send_sync_ptr::SendSyncPtr;
pub use send_sync_unsafe_cell::SendSyncUnsafeCell;
mod module_id;
pub use module_id::CompiledModuleId;
mod cow;
pub use crate::runtime::vm::cow::{MemoryImage, MemoryImageSlot, ModuleMemoryImages};
/// Dynamic runtime functionality needed by this crate throughout the execution
/// of a wasm instance.
///
/// This trait is used to store a raw pointer trait object within each
/// `VMContext`. This raw pointer trait object points back to the
/// `wasmtime::Store` internally but is type-erased so this `wasmtime-runtime`
/// crate doesn't need the entire `wasmtime` crate to build.
///
/// Note that this is an extra-unsafe trait because no heed is paid to the
/// lifetime of this store or the Send/Sync-ness of this store. All of that must
/// be respected by embedders (e.g. the `wasmtime::Store` structure). The theory
/// is that `wasmtime::Store` handles all this correctly.
pub unsafe trait VMStore {
/// Get a shared borrow of this store's `StoreOpaque`.
fn store_opaque(&self) -> &StoreOpaque;
/// Get an exclusive borrow of this store's `StoreOpaque`.
fn store_opaque_mut(&mut self) -> &mut StoreOpaque;
/// Callback invoked to allow the store's resource limiter to reject a
/// memory grow operation.
fn memory_growing(
&mut self,
current: usize,
desired: usize,
maximum: Option<usize>,
) -> Result<bool, Error>;
/// Callback invoked to notify the store's resource limiter that a memory
/// grow operation has failed.
///
/// Note that this is not invoked if `memory_growing` returns an error.
fn memory_grow_failed(&mut self, error: Error) -> Result<()>;
/// Callback invoked to allow the store's resource limiter to reject a
/// table grow operation.
fn table_growing(
&mut self,
current: usize,
desired: usize,
maximum: Option<usize>,
) -> Result<bool, Error>;
/// Callback invoked to notify the store's resource limiter that a table
/// grow operation has failed.
///
/// Note that this is not invoked if `table_growing` returns an error.
fn table_grow_failed(&mut self, error: Error) -> Result<()>;
/// Callback invoked whenever fuel runs out by a wasm instance. If an error
/// is returned that's raised as a trap. Otherwise wasm execution will
/// continue as normal.
fn out_of_gas(&mut self) -> Result<(), Error>;
/// Callback invoked whenever an instance observes a new epoch
/// number. Cannot fail; cooperative epoch-based yielding is
/// completely semantically transparent. Returns the new deadline.
fn new_epoch(&mut self) -> Result<u64, Error>;
/// Callback invoked whenever an instance needs to trigger a GC.
///
/// Optionally given a GC reference that is rooted for the collection, and
/// then whose updated GC reference is returned.
///
/// Cooperative, async-yielding (if configured) is completely transparent.
///
/// If the async GC was cancelled, returns an error. This should be raised
/// as a trap to clean up Wasm execution.
fn gc(&mut self, root: Option<VMGcRef>) -> Result<Option<VMGcRef>>;
/// Metadata required for resources for the component model.
#[cfg(feature = "component-model")]
fn component_calls(&mut self) -> &mut component::CallContexts;
}
impl Deref for dyn VMStore {
type Target = StoreOpaque;
fn deref(&self) -> &Self::Target {
self.store_opaque()
}
}
impl DerefMut for dyn VMStore {
fn deref_mut(&mut self) -> &mut Self::Target {
self.store_opaque_mut()
}
}
/// Functionality required by this crate for a particular module. This
/// is chiefly needed for lazy initialization of various bits of
/// instance state.
///
/// When an instance is created, it holds an `Arc<dyn ModuleRuntimeInfo>`
/// so that it can get to signatures, metadata on functions, memory and
/// funcref-table images, etc. All of these things are ordinarily known
/// by the higher-level layers of Wasmtime. Specifically, the main
/// implementation of this trait is provided by
/// `wasmtime::module::ModuleInner`. Since the runtime crate sits at
/// the bottom of the dependence DAG though, we don't know or care about
/// that; we just need some implementor of this trait for each
/// allocation request.
#[derive(Clone)]
pub enum ModuleRuntimeInfo {
Module(crate::Module),
Bare(Box<BareModuleInfo>),
}
/// A barebones implementation of ModuleRuntimeInfo that is useful for
/// cases where a purpose-built environ::Module is used and a full
/// CompiledModule does not exist (for example, for tests or for the
/// default-callee instance).
#[derive(Clone)]
pub struct BareModuleInfo {
module: Arc<wasmtime_environ::Module>,
one_signature: Option<VMSharedTypeIndex>,
offsets: VMOffsets<HostPtr>,
}
impl ModuleRuntimeInfo {
pub(crate) fn bare(module: Arc<wasmtime_environ::Module>) -> Self {
ModuleRuntimeInfo::bare_maybe_imported_func(module, None)
}
pub(crate) fn bare_maybe_imported_func(
module: Arc<wasmtime_environ::Module>,
one_signature: Option<VMSharedTypeIndex>,
) -> Self {
ModuleRuntimeInfo::Bare(Box::new(BareModuleInfo {
offsets: VMOffsets::new(HostPtr, &module),
module,
one_signature,
}))
}
/// The underlying Module.
pub(crate) fn env_module(&self) -> &Arc<wasmtime_environ::Module> {
match self {
ModuleRuntimeInfo::Module(m) => m.env_module(),
ModuleRuntimeInfo::Bare(b) => &b.module,
}
}
/// Translate a module-level interned type index into an engine-level
/// interned type index.
fn engine_type_index(&self, module_index: ModuleInternedTypeIndex) -> VMSharedTypeIndex {
match self {
ModuleRuntimeInfo::Module(m) => m
.code_object()
.signatures()
.shared_type(module_index)
.expect("bad module-level interned type index"),
ModuleRuntimeInfo::Bare(_) => unreachable!(),
}
}
/// Returns the address, in memory, that the function `index` resides at.
fn function(&self, index: DefinedFuncIndex) -> NonNull<VMWasmCallFunction> {
let module = match self {
ModuleRuntimeInfo::Module(m) => m,
ModuleRuntimeInfo::Bare(_) => unreachable!(),
};
let ptr = module
.compiled_module()
.finished_function(index)
.as_ptr()
.cast::<VMWasmCallFunction>()
.cast_mut();
NonNull::new(ptr).unwrap()
}
/// Returns the address, in memory, of the trampoline that allows the given
/// defined Wasm function to be called by the array calling convention.
///
/// Returns `None` for Wasm functions which do not escape, and therefore are
/// not callable from outside the Wasm module itself.
fn array_to_wasm_trampoline(&self, index: DefinedFuncIndex) -> Option<VMArrayCallFunction> {
let m = match self {
ModuleRuntimeInfo::Module(m) => m,
ModuleRuntimeInfo::Bare(_) => unreachable!(),
};
let ptr = m
.compiled_module()
.array_to_wasm_trampoline(index)?
.as_ptr();
Some(unsafe { mem::transmute::<*const u8, VMArrayCallFunction>(ptr) })
}
/// Returns the `MemoryImage` structure used for copy-on-write
/// initialization of the memory, if it's applicable.
fn memory_image(
&self,
memory: DefinedMemoryIndex,
) -> anyhow::Result<Option<&Arc<MemoryImage>>> {
match self {
ModuleRuntimeInfo::Module(m) => {
let images = m.memory_images()?;
Ok(images.and_then(|images| images.get_memory_image(memory)))
}
ModuleRuntimeInfo::Bare(_) => Ok(None),
}
}
/// A unique ID for this particular module. This can be used to
/// allow for fastpaths to optimize a "re-instantiate the same
/// module again" case.
fn unique_id(&self) -> Option<CompiledModuleId> {
match self {
ModuleRuntimeInfo::Module(m) => Some(m.id()),
ModuleRuntimeInfo::Bare(_) => None,
}
}
/// A slice pointing to all data that is referenced by this instance.
fn wasm_data(&self) -> &[u8] {
match self {
ModuleRuntimeInfo::Module(m) => m.compiled_module().code_memory().wasm_data(),
ModuleRuntimeInfo::Bare(_) => &[],
}
}
/// Returns an array, indexed by `ModuleInternedTypeIndex` of all
/// `VMSharedSignatureIndex` entries corresponding to the `SignatureIndex`.
fn type_ids(&self) -> &[VMSharedTypeIndex] {
match self {
ModuleRuntimeInfo::Module(m) => m
.code_object()
.signatures()
.as_module_map()
.values()
.as_slice(),
ModuleRuntimeInfo::Bare(b) => match &b.one_signature {
Some(s) => core::slice::from_ref(s),
None => &[],
},
}
}
/// Offset information for the current host.
pub(crate) fn offsets(&self) -> &VMOffsets<HostPtr> {
match self {
ModuleRuntimeInfo::Module(m) => m.offsets(),
ModuleRuntimeInfo::Bare(b) => &b.offsets,
}
}
}
/// Returns the host OS page size, in bytes.
pub fn host_page_size() -> usize {
static PAGE_SIZE: AtomicUsize = AtomicUsize::new(0);
return match PAGE_SIZE.load(Ordering::Relaxed) {
0 => {
let size = sys::vm::get_page_size();
assert!(size != 0);
PAGE_SIZE.store(size, Ordering::Relaxed);
size
}
n => n,
};
}
/// Is `bytes` a multiple of the host page size?
pub fn usize_is_multiple_of_host_page_size(bytes: usize) -> bool {
bytes % host_page_size() == 0
}
/// Round the given byte size up to a multiple of the host OS page size.
///
/// Returns an error if rounding up overflows.
pub fn round_u64_up_to_host_pages(bytes: u64) -> Result<u64> {
let page_size = u64::try_from(crate::runtime::vm::host_page_size()).err2anyhow()?;
debug_assert!(page_size.is_power_of_two());
bytes
.checked_add(page_size - 1)
.ok_or_else(|| anyhow!(
"{bytes} is too large to be rounded up to a multiple of the host page size of {page_size}"
))
.map(|val| val & !(page_size - 1))
}
/// Same as `round_u64_up_to_host_pages` but for `usize`s.
pub fn round_usize_up_to_host_pages(bytes: usize) -> Result<usize> {
let bytes = u64::try_from(bytes).err2anyhow()?;
let rounded = round_u64_up_to_host_pages(bytes)?;
Ok(usize::try_from(rounded).err2anyhow()?)
}
/// Result of `Memory::atomic_wait32` and `Memory::atomic_wait64`
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub enum WaitResult {
/// Indicates that a `wait` completed by being awoken by a different thread.
/// This means the thread went to sleep and didn't time out.
Ok = 0,
/// Indicates that `wait` did not complete and instead returned due to the
/// value in memory not matching the expected value.
Mismatch = 1,
/// Indicates that `wait` completed with a timeout, meaning that the
/// original value matched as expected but nothing ever called `notify`.
TimedOut = 2,
}
/// Description about a fault that occurred in WebAssembly.
#[derive(Debug)]
pub struct WasmFault {
/// The size of memory, in bytes, at the time of the fault.
pub memory_size: usize,
/// The WebAssembly address at which the fault occurred.
pub wasm_address: u64,
}
impl fmt::Display for WasmFault {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"memory fault at wasm address 0x{:x} in linear memory of size 0x{:x}",
self.wasm_address, self.memory_size,
)
}
}
@@ -1,63 +0,0 @@
//! Arm64-specific definitions of architecture-specific functions in Wasmtime.
#[inline]
#[allow(missing_docs)]
pub fn get_stack_pointer() -> usize {
let stack_pointer: usize;
unsafe {
core::arch::asm!(
"mov {}, sp",
out(reg) stack_pointer,
options(nostack,nomem),
);
}
stack_pointer
}
// The aarch64 calling conventions save the return PC one i64 above the FP and
// the previous FP is pointed to by the current FP:
//
// > Each frame shall link to the frame of its caller by means of a frame record
// > of two 64-bit values on the stack [...] The frame record for the innermost
// > frame [...] shall be pointed to by the frame pointer register (FP). The
// > lowest addressed double-word shall point to the previous frame record and the
// > highest addressed double-word shall contain the value passed in LR on entry
// > to the current function.
//
// - AAPCS64 section 6.2.3 The Frame Pointer[0]
pub unsafe fn get_next_older_pc_from_fp(fp: usize) -> usize {
let mut pc = *(fp as *mut usize).offset(1);
// The return address might be signed, so we need to strip the highest bits
// (where the authentication code might be located) in order to obtain a
// valid address. We use the `XPACLRI` instruction, which is executed as a
// no-op by processors that do not support pointer authentication, so that
// the implementation is backward-compatible and there is no duplication.
// However, this instruction requires the LR register for both its input and
// output.
core::arch::asm!(
"mov lr, {pc}",
"xpaclri",
"mov {pc}, lr",
pc = inout(reg) pc,
out("lr") _,
options(nomem, nostack, preserves_flags, pure),
);
pc
}
// And the current frame pointer points to the next older frame pointer.
pub const NEXT_OLDER_FP_FROM_FP_OFFSET: usize = 0;
pub fn assert_fp_is_aligned(_fp: usize) {
// From AAPCS64, section 6.2.3 The Frame Pointer[0]:
//
// > The location of the frame record within a stack frame is not specified.
//
// So this presumably means that the FP can have any alignment, as its
// location is not specified and nothing further is said about constraining
// alignment.
//
// [0]: https://github.com/ARM-software/abi-aa/blob/2022Q1/aapcs64/aapcs64.rst#the-frame-pointer
}
-45
View File
@@ -1,45 +0,0 @@
//! Architecture-specific support required by Wasmtime.
//!
//! This crate houses any architecture-specific tidbits required when running
//! Wasmtime. Each architecture has its own file in the `arch` folder which is
//! referenced here.
//!
//! All architectures have the same interface when exposed to the rest of the
//! crate.
cfg_if::cfg_if! {
if #[cfg(target_arch = "x86_64")] {
mod x86_64;
use x86_64 as imp;
} else if #[cfg(target_arch = "aarch64")] {
mod aarch64;
use aarch64 as imp;
} else if #[cfg(target_arch = "s390x")] {
mod s390x;
use s390x as imp;
} else if #[cfg(target_arch = "riscv64")] {
mod riscv64;
use riscv64 as imp;
} else {
mod unsupported;
use unsupported as imp;
}
}
// Functions defined in this module but all the implementations delegate to each
// `imp` module. This exists to assert that each module internally provides the
// same set of functionality with the same types for all architectures.
pub fn get_stack_pointer() -> usize {
imp::get_stack_pointer()
}
pub unsafe fn get_next_older_pc_from_fp(fp: usize) -> usize {
imp::get_next_older_pc_from_fp(fp)
}
pub const NEXT_OLDER_FP_FROM_FP_OFFSET: usize = imp::NEXT_OLDER_FP_FROM_FP_OFFSET;
pub fn assert_fp_is_aligned(fp: usize) {
imp::assert_fp_is_aligned(fp)
}
@@ -1,26 +0,0 @@
//! Riscv64-specific definitions of architecture-specific functions in Wasmtime.
#[inline]
#[allow(missing_docs)]
pub fn get_stack_pointer() -> usize {
let stack_pointer: usize;
unsafe {
core::arch::asm!(
"mv {}, sp",
out(reg) stack_pointer,
options(nostack,nomem),
);
}
stack_pointer
}
pub unsafe fn get_next_older_pc_from_fp(fp: usize) -> usize {
*(fp as *mut usize).offset(1)
}
// And the current frame pointer points to the next older frame pointer.
pub const NEXT_OLDER_FP_FROM_FP_OFFSET: usize = 0;
pub fn assert_fp_is_aligned(fp: usize) {
assert_eq!(fp % 16, 0, "stack should always be aligned to 16");
}
-70
View File
@@ -1,70 +0,0 @@
// Currently `global_asm!` isn't stable on s390x, so this is an external
// assembler file built with the `build.rs`.
.machine z13
.text
.hidden host_to_wasm_trampoline
.globl host_to_wasm_trampoline
.type host_to_wasm_trampoline,@function
.p2align 2
#define CONCAT2(a, b) a ## b
#define CONCAT(a, b) CONCAT2(a , b)
#define VERSIONED_SYMBOL(a) CONCAT(a, VERSIONED_SUFFIX)
#define LIBCALL_TRAMPOLINE(libcall, libcall_impl) \
.hidden VERSIONED_SYMBOL(libcall) ; \
.globl VERSIONED_SYMBOL(libcall) ; \
.type VERSIONED_SYMBOL(libcall),@function ; \
.p2align 2 ; \
VERSIONED_SYMBOL(libcall): ; \
.cfi_startproc ; \
\
/* Load the pointer to `VMRuntimeLimits` in `%r1`. */ \
lg %r1, 8(%r2) ; \
\
/* Store the last Wasm FP into the `last_wasm_exit_fp` in the limits. */ \
lg %r0, 0(%r15) ; \
stg %r0, 24(%r1) ; \
\
/* Store the last Wasm PC into the `last_wasm_exit_pc` in the limits. */ \
stg %r14, 32(%r1) ; \
\
/* Tail call to the actual implementation of this libcall. */ \
jg VERSIONED_SYMBOL(libcall_impl) ; \
\
.cfi_endproc ; \
.size VERSIONED_SYMBOL(libcall),.-VERSIONED_SYMBOL(libcall)
LIBCALL_TRAMPOLINE(memory32_grow, impl_memory32_grow)
LIBCALL_TRAMPOLINE(table_grow_func_ref, impl_table_grow_func_ref)
LIBCALL_TRAMPOLINE(table_grow_externref, impl_table_grow_externref)
LIBCALL_TRAMPOLINE(table_fill_func_ref, impl_table_fill_func_ref)
LIBCALL_TRAMPOLINE(table_fill_externref, impl_table_fill_externref)
LIBCALL_TRAMPOLINE(table_copy, impl_table_copy)
LIBCALL_TRAMPOLINE(table_init, impl_table_init)
LIBCALL_TRAMPOLINE(elem_drop, impl_elem_drop)
LIBCALL_TRAMPOLINE(memory_copy, impl_memory_copy)
LIBCALL_TRAMPOLINE(memory_fill, impl_memory_fill)
LIBCALL_TRAMPOLINE(memory_init, impl_memory_init)
LIBCALL_TRAMPOLINE(ref_func, impl_ref_func)
LIBCALL_TRAMPOLINE(data_drop, impl_data_drop)
LIBCALL_TRAMPOLINE(table_get_lazy_init_func_ref, impl_table_get_lazy_init_func_ref)
LIBCALL_TRAMPOLINE(drop_gc_ref, impl_drop_gc_ref)
LIBCALL_TRAMPOLINE(gc, gc)
LIBCALL_TRAMPOLINE(gc_ref_global_get, impl_gc_ref_global_get)
LIBCALL_TRAMPOLINE(gc_ref_global_set, impl_gc_ref_global_set)
LIBCALL_TRAMPOLINE(memory_atomic_notify, impl_memory_atomic_notify)
LIBCALL_TRAMPOLINE(memory_atomic_wait32, impl_memory_atomic_wait32)
LIBCALL_TRAMPOLINE(memory_atomic_wait64, impl_memory_atomic_wait64)
LIBCALL_TRAMPOLINE(out_of_gas, impl_out_of_gas)
LIBCALL_TRAMPOLINE(new_epoch, impl_new_epoch)
LIBCALL_TRAMPOLINE(check_malloc, impl_check_malloc)
LIBCALL_TRAMPOLINE(check_free, impl_check_free)
LIBCALL_TRAMPOLINE(check_load, impl_check_load)
LIBCALL_TRAMPOLINE(check_store, impl_check_store)
LIBCALL_TRAMPOLINE(malloc_start, impl_malloc_start)
LIBCALL_TRAMPOLINE(free_start, impl_free_start)
LIBCALL_TRAMPOLINE(update_stack_pointer, impl_update_stack_pointer)
LIBCALL_TRAMPOLINE(update_mem_size, impl_update_mem_size)
@@ -1,22 +0,0 @@
//! s390x-specific definitions of architecture-specific functions in Wasmtime.
#[inline]
#[allow(missing_docs)]
pub fn get_stack_pointer() -> usize {
psm::stack_pointer() as usize
}
pub unsafe fn get_next_older_pc_from_fp(fp: usize) -> usize {
// The next older PC can be found in register %r14 at function entry, which
// was saved into slot 14 of the register save area pointed to by "FP" (the
// backchain pointer).
*(fp as *mut usize).offset(14)
}
// The next older "FP" (backchain pointer) was saved in the slot pointed to
// by the current "FP".
pub const NEXT_OLDER_FP_FROM_FP_OFFSET: usize = 0;
pub fn assert_fp_is_aligned(fp: usize) {
assert_eq!(fp % 8, 0, "stack should always be aligned to 8");
}
@@ -1,36 +0,0 @@
compile_error!("Wasmtime's runtime is being compiled for an architecture that it does not support");
cfg_if::cfg_if! {
if #[cfg(target_arch = "x86")] {
compile_error!("\
the tracking issue for i686 support is https://github.com/bytecodealliance/wasmtime/issues/1980 \
");
} else if #[cfg(target_arch = "arm")] {
compile_error!("\
the tracking issue for arm support is https://github.com/bytecodealliance/wasmtime/issues/1173 \
");
} else if #[cfg(target_arch = "riscv32")] {
compile_error!("\
the tracking issue for riscv32 support is https://github.com/bytecodealliance/wasmtime/issues/8768 \
");
} else {
compile_error!("\
if you'd like feel free to file an issue for platform support at
https://github.com/bytecodealliance/wasmtime/issues/new
");
}
}
pub fn get_stack_pointer() -> usize {
panic!()
}
pub unsafe fn get_next_older_pc_from_fp(_fp: usize) -> usize {
panic!()
}
pub const NEXT_OLDER_FP_FROM_FP_OFFSET: usize = 0;
pub fn assert_fp_is_aligned(_fp: usize) {
panic!()
}
@@ -1,28 +0,0 @@
//! x86_64-specific definitions of architecture-specific functions in Wasmtime.
#[inline]
#[allow(missing_docs)]
pub fn get_stack_pointer() -> usize {
let stack_pointer: usize;
unsafe {
core::arch::asm!(
"mov {}, rsp",
out(reg) stack_pointer,
options(nostack,nomem),
);
}
stack_pointer
}
pub unsafe fn get_next_older_pc_from_fp(fp: usize) -> usize {
// The calling convention always pushes the return pointer (aka the PC of
// the next older frame) just before this frame.
*(fp as *mut usize).offset(1)
}
// And the current frame pointer points to the next older frame pointer.
pub const NEXT_OLDER_FP_FROM_FP_OFFSET: usize = 0;
pub fn assert_fp_is_aligned(fp: usize) {
assert_eq!(fp % 16, 0, "stack should always be aligned to 16");
}
@@ -1,35 +0,0 @@
use core::{
future::Future,
pin::Pin,
task::{Context, Poll},
};
/// A small future that yields once and then returns.
#[derive(Default)]
pub struct Yield {
yielded: bool,
}
impl Yield {
/// Create a new `Yield`.
pub fn new() -> Self {
Self::default()
}
}
impl Future for Yield {
type Output = ();
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> {
if self.yielded {
Poll::Ready(())
} else {
// Flag ourselves as yielded to return next time, and also
// flag the waker that we're already ready to get
// re-enqueued for another poll.
self.yielded = true;
cx.waker().wake_by_ref();
Poll::Pending
}
}
}
-859
View File
@@ -1,859 +0,0 @@
//! Runtime support for the component model in Wasmtime
//!
//! Currently this runtime support includes a `VMComponentContext` which is
//! similar in purpose to `VMContext`. The context is read from
//! cranelift-generated trampolines when entering the host from a wasm module.
//! Eventually it's intended that module-to-module calls, which would be
//! cranelift-compiled adapters, will use this `VMComponentContext` as well.
use crate::prelude::*;
use crate::runtime::vm::{
SendSyncPtr, VMArrayCallFunction, VMFuncRef, VMGlobalDefinition, VMMemoryDefinition,
VMOpaqueContext, VMStore, VMWasmCallFunction, ValRaw,
};
use alloc::alloc::Layout;
use alloc::sync::Arc;
use core::any::Any;
use core::marker;
use core::mem;
use core::mem::offset_of;
use core::ops::Deref;
use core::ptr::{self, NonNull};
use sptr::Strict;
use wasmtime_environ::component::*;
use wasmtime_environ::{HostPtr, PrimaryMap, VMSharedTypeIndex};
#[allow(clippy::cast_possible_truncation)] // it's intended this is truncated on
// 32-bit platforms
const INVALID_PTR: usize = 0xdead_dead_beef_beef_u64 as usize;
mod libcalls;
mod resources;
pub use self::resources::{CallContexts, ResourceTable, ResourceTables};
/// Runtime representation of a component instance and all state necessary for
/// the instance itself.
///
/// This type never exists by-value, but rather it's always behind a pointer.
/// The size of the allocation for `ComponentInstance` includes the trailing
/// `VMComponentContext` which is variably sized based on the `offsets`
/// contained within.
#[repr(C)]
pub struct ComponentInstance {
/// Size and offset information for the trailing `VMComponentContext`.
offsets: VMComponentOffsets<HostPtr>,
/// For more information about this see the documentation on
/// `Instance::vmctx_self_reference`.
vmctx_self_reference: SendSyncPtr<VMComponentContext>,
/// Runtime type information about this component.
runtime_info: Arc<dyn ComponentRuntimeInfo>,
/// State of resources for all `TypeResourceTableIndex` values for this
/// component.
///
/// This is paired with other information to create a `ResourceTables` which
/// is how this field is manipulated.
component_resource_tables: PrimaryMap<TypeResourceTableIndex, ResourceTable>,
/// Storage for the type information about resources within this component
/// instance.
///
/// This is actually `Arc<PrimaryMap<ResourceIndex, ResourceType>>` but that
/// can't be in this crate because `ResourceType` isn't here. Not using `dyn
/// Any` is left as an exercise for a future refactoring.
resource_types: Arc<dyn Any + Send + Sync>,
/// A zero-sized field which represents the end of the struct for the actual
/// `VMComponentContext` to be allocated behind.
vmctx: VMComponentContext,
}
/// Type signature for host-defined trampolines that are called from
/// WebAssembly.
///
/// This function signature is invoked from a cranelift-compiled trampoline that
/// adapts from the core wasm System-V ABI into the ABI provided here:
///
/// * `vmctx` - this is the first argument to the wasm import, and should always
/// end up being a `VMComponentContext`.
/// * `data` - this is the data pointer associated with the `VMLowering` for
/// which this function pointer was registered.
/// * `ty` - the type index, relative to the tables in `vmctx`, that is the
/// type of the function being called.
/// * `flags` - the component flags for may_enter/leave corresponding to the
/// component instance that the lowering happened within.
/// * `opt_memory` - this nullable pointer represents the memory configuration
/// option for the canonical ABI options.
/// * `opt_realloc` - this nullable pointer represents the realloc configuration
/// option for the canonical ABI options.
/// * `string_encoding` - this is the configured string encoding for the
/// canonical ABI this lowering corresponds to.
/// * `args_and_results` - pointer to stack-allocated space in the caller where
/// all the arguments are stored as well as where the results will be written
/// to. The size and initialized bytes of this depends on the core wasm type
/// signature that this callee corresponds to.
/// * `nargs_and_results` - the size, in units of `ValRaw`, of
/// `args_and_results`.
//
// FIXME: 9 arguments is probably too many. The `data` through `string-encoding`
// parameters should probably get packaged up into the `VMComponentContext`.
// Needs benchmarking one way or another though to figure out what the best
// balance is here.
pub type VMLoweringCallee = extern "C" fn(
vmctx: *mut VMOpaqueContext,
data: *mut u8,
ty: TypeFuncIndex,
flags: InstanceFlags,
opt_memory: *mut VMMemoryDefinition,
opt_realloc: *mut VMFuncRef,
string_encoding: StringEncoding,
args_and_results: *mut mem::MaybeUninit<ValRaw>,
nargs_and_results: usize,
);
/// Structure describing a lowered host function stored within a
/// `VMComponentContext` per-lowering.
#[derive(Copy, Clone)]
#[repr(C)]
pub struct VMLowering {
/// The host function pointer that is invoked when this lowering is
/// invoked.
pub callee: VMLoweringCallee,
/// The host data pointer (think void* pointer) to get passed to `callee`.
pub data: *mut u8,
}
/// This is a marker type to represent the underlying allocation of a
/// `VMComponentContext`.
///
/// This type is similar to `VMContext` for core wasm and is allocated once per
/// component instance in Wasmtime. While the static size of this type is 0 the
/// actual runtime size is variable depending on the shape of the component that
/// this corresponds to. This structure always trails a `ComponentInstance`
/// allocation and the allocation/liftetime of this allocation is managed by
/// `ComponentInstance`.
#[repr(C)]
// Set an appropriate alignment for this structure where the most-aligned value
// internally right now `VMGlobalDefinition` which has an alignment of 16 bytes.
#[repr(align(16))]
pub struct VMComponentContext {
/// For more information about this see the equivalent field in `VMContext`
_marker: marker::PhantomPinned,
}
impl ComponentInstance {
/// Converts the `vmctx` provided into a `ComponentInstance` and runs the
/// provided closure with that instance.
///
/// # Unsafety
///
/// This is `unsafe` because `vmctx` cannot be guaranteed to be a valid
/// pointer and it cannot be proven statically that it's safe to get a
/// mutable reference at this time to the instance from `vmctx`.
pub unsafe fn from_vmctx<R>(
vmctx: *mut VMComponentContext,
f: impl FnOnce(&mut ComponentInstance) -> R,
) -> R {
let ptr = vmctx
.byte_sub(mem::size_of::<ComponentInstance>())
.cast::<ComponentInstance>();
f(&mut *ptr)
}
/// Returns the layout corresponding to what would be an allocation of a
/// `ComponentInstance` for the `offsets` provided.
///
/// The returned layout has space for both the `ComponentInstance` and the
/// trailing `VMComponentContext`.
fn alloc_layout(offsets: &VMComponentOffsets<HostPtr>) -> Layout {
let size = mem::size_of::<Self>()
.checked_add(usize::try_from(offsets.size_of_vmctx()).unwrap())
.unwrap();
let align = mem::align_of::<Self>();
Layout::from_size_align(size, align).unwrap()
}
/// Initializes an uninitialized pointer to a `ComponentInstance` in
/// addition to its trailing `VMComponentContext`.
///
/// The `ptr` provided must be valid for `alloc_size` bytes and will be
/// entirely overwritten by this function call. The `offsets` correspond to
/// the shape of the component being instantiated and `store` is a pointer
/// back to the Wasmtime store for host functions to have access to.
unsafe fn new_at(
ptr: NonNull<ComponentInstance>,
alloc_size: usize,
offsets: VMComponentOffsets<HostPtr>,
runtime_info: Arc<dyn ComponentRuntimeInfo>,
resource_types: Arc<dyn Any + Send + Sync>,
store: *mut dyn VMStore,
) {
assert!(alloc_size >= Self::alloc_layout(&offsets).size());
let num_tables = runtime_info.component().num_resource_tables;
let mut component_resource_tables = PrimaryMap::with_capacity(num_tables);
for _ in 0..num_tables {
component_resource_tables.push(ResourceTable::default());
}
ptr::write(
ptr.as_ptr(),
ComponentInstance {
offsets,
vmctx_self_reference: SendSyncPtr::new(
NonNull::new(
ptr.as_ptr()
.byte_add(mem::size_of::<ComponentInstance>())
.cast(),
)
.unwrap(),
),
component_resource_tables,
runtime_info,
resource_types,
vmctx: VMComponentContext {
_marker: marker::PhantomPinned,
},
},
);
(*ptr.as_ptr()).initialize_vmctx(store);
}
fn vmctx(&self) -> *mut VMComponentContext {
let addr = core::ptr::addr_of!(self.vmctx);
Strict::with_addr(self.vmctx_self_reference.as_ptr(), Strict::addr(addr))
}
unsafe fn vmctx_plus_offset<T>(&self, offset: u32) -> *const T {
self.vmctx()
.byte_add(usize::try_from(offset).unwrap())
.cast()
}
unsafe fn vmctx_plus_offset_mut<T>(&mut self, offset: u32) -> *mut T {
self.vmctx()
.byte_add(usize::try_from(offset).unwrap())
.cast()
}
/// Returns a pointer to the "may leave" flag for this instance specified
/// for canonical lowering and lifting operations.
#[inline]
pub fn instance_flags(&self, instance: RuntimeComponentInstanceIndex) -> InstanceFlags {
unsafe {
let ptr = self
.vmctx_plus_offset::<VMGlobalDefinition>(self.offsets.instance_flags(instance))
.cast_mut();
InstanceFlags(SendSyncPtr::new(NonNull::new(ptr).unwrap()))
}
}
/// Returns the store that this component was created with.
pub fn store(&self) -> *mut dyn VMStore {
unsafe {
let ret = *self.vmctx_plus_offset::<*mut dyn VMStore>(self.offsets.store());
assert!(!ret.is_null());
ret
}
}
/// Returns the runtime memory definition corresponding to the index of the
/// memory provided.
///
/// This can only be called after `idx` has been initialized at runtime
/// during the instantiation process of a component.
pub fn runtime_memory(&self, idx: RuntimeMemoryIndex) -> *mut VMMemoryDefinition {
unsafe {
let ret = *self.vmctx_plus_offset(self.offsets.runtime_memory(idx));
debug_assert!(ret as usize != INVALID_PTR);
ret
}
}
/// Returns the realloc pointer corresponding to the index provided.
///
/// This can only be called after `idx` has been initialized at runtime
/// during the instantiation process of a component.
pub fn runtime_realloc(&self, idx: RuntimeReallocIndex) -> NonNull<VMFuncRef> {
unsafe {
let ret = *self.vmctx_plus_offset::<NonNull<_>>(self.offsets.runtime_realloc(idx));
debug_assert!(ret.as_ptr() as usize != INVALID_PTR);
ret
}
}
/// Returns the post-return pointer corresponding to the index provided.
///
/// This can only be called after `idx` has been initialized at runtime
/// during the instantiation process of a component.
pub fn runtime_post_return(&self, idx: RuntimePostReturnIndex) -> NonNull<VMFuncRef> {
unsafe {
let ret = *self.vmctx_plus_offset::<NonNull<_>>(self.offsets.runtime_post_return(idx));
debug_assert!(ret.as_ptr() as usize != INVALID_PTR);
ret
}
}
/// Returns the host information for the lowered function at the index
/// specified.
///
/// This can only be called after `idx` has been initialized at runtime
/// during the instantiation process of a component.
pub fn lowering(&self, idx: LoweredIndex) -> VMLowering {
unsafe {
let ret = *self.vmctx_plus_offset::<VMLowering>(self.offsets.lowering(idx));
debug_assert!(ret.callee as usize != INVALID_PTR);
debug_assert!(ret.data as usize != INVALID_PTR);
ret
}
}
/// Returns the core wasm `funcref` corresponding to the trampoline
/// specified.
///
/// The returned function is suitable to pass directly to a wasm module
/// instantiation and the function contains cranelift-compiled trampolines.
///
/// This can only be called after `idx` has been initialized at runtime
/// during the instantiation process of a component.
pub fn trampoline_func_ref(&self, idx: TrampolineIndex) -> NonNull<VMFuncRef> {
unsafe {
let offset = self.offsets.trampoline_func_ref(idx);
let ret = self.vmctx_plus_offset::<VMFuncRef>(offset);
debug_assert!(
mem::transmute::<Option<NonNull<VMWasmCallFunction>>, usize>((*ret).wasm_call)
!= INVALID_PTR
);
debug_assert!((*ret).vmctx as usize != INVALID_PTR);
NonNull::new(ret.cast_mut()).unwrap()
}
}
/// Stores the runtime memory pointer at the index specified.
///
/// This is intended to be called during the instantiation process of a
/// component once a memory is available, which may not be until part-way
/// through component instantiation.
///
/// Note that it should be a property of the component model that the `ptr`
/// here is never needed prior to it being configured here in the instance.
pub fn set_runtime_memory(&mut self, idx: RuntimeMemoryIndex, ptr: *mut VMMemoryDefinition) {
unsafe {
debug_assert!(!ptr.is_null());
let storage = self.vmctx_plus_offset_mut(self.offsets.runtime_memory(idx));
debug_assert!(*storage as usize == INVALID_PTR);
*storage = ptr;
}
}
/// Same as `set_runtime_memory` but for realloc function pointers.
pub fn set_runtime_realloc(&mut self, idx: RuntimeReallocIndex, ptr: NonNull<VMFuncRef>) {
unsafe {
let storage = self.vmctx_plus_offset_mut(self.offsets.runtime_realloc(idx));
debug_assert!(*storage as usize == INVALID_PTR);
*storage = ptr.as_ptr();
}
}
/// Same as `set_runtime_memory` but for post-return function pointers.
pub fn set_runtime_post_return(
&mut self,
idx: RuntimePostReturnIndex,
ptr: NonNull<VMFuncRef>,
) {
unsafe {
let storage = self.vmctx_plus_offset_mut(self.offsets.runtime_post_return(idx));
debug_assert!(*storage as usize == INVALID_PTR);
*storage = ptr.as_ptr();
}
}
/// Configures host runtime lowering information associated with imported f
/// functions for the `idx` specified.
pub fn set_lowering(&mut self, idx: LoweredIndex, lowering: VMLowering) {
unsafe {
debug_assert!(
*self.vmctx_plus_offset::<usize>(self.offsets.lowering_callee(idx)) == INVALID_PTR
);
debug_assert!(
*self.vmctx_plus_offset::<usize>(self.offsets.lowering_data(idx)) == INVALID_PTR
);
*self.vmctx_plus_offset_mut(self.offsets.lowering(idx)) = lowering;
}
}
/// Same as `set_lowering` but for the resource.drop functions.
pub fn set_trampoline(
&mut self,
idx: TrampolineIndex,
wasm_call: NonNull<VMWasmCallFunction>,
array_call: VMArrayCallFunction,
type_index: VMSharedTypeIndex,
) {
unsafe {
let offset = self.offsets.trampoline_func_ref(idx);
debug_assert!(*self.vmctx_plus_offset::<usize>(offset) == INVALID_PTR);
let vmctx = VMOpaqueContext::from_vmcomponent(self.vmctx());
*self.vmctx_plus_offset_mut(offset) = VMFuncRef {
wasm_call: Some(wasm_call),
array_call,
type_index,
vmctx,
};
}
}
/// Configures the destructor for a resource at the `idx` specified.
///
/// This is required to be called for each resource as it's defined within a
/// component during the instantiation process.
pub fn set_resource_destructor(
&mut self,
idx: ResourceIndex,
dtor: Option<NonNull<VMFuncRef>>,
) {
unsafe {
let offset = self.offsets.resource_destructor(idx);
debug_assert!(*self.vmctx_plus_offset::<usize>(offset) == INVALID_PTR);
*self.vmctx_plus_offset_mut(offset) = dtor;
}
}
/// Returns the destructor, if any, for `idx`.
///
/// This is only valid to call after `set_resource_destructor`, or typically
/// after instantiation.
pub fn resource_destructor(&self, idx: ResourceIndex) -> Option<NonNull<VMFuncRef>> {
unsafe {
let offset = self.offsets.resource_destructor(idx);
debug_assert!(*self.vmctx_plus_offset::<usize>(offset) != INVALID_PTR);
*self.vmctx_plus_offset(offset)
}
}
unsafe fn initialize_vmctx(&mut self, store: *mut dyn VMStore) {
*self.vmctx_plus_offset_mut(self.offsets.magic()) = VMCOMPONENT_MAGIC;
*self.vmctx_plus_offset_mut(self.offsets.libcalls()) = &libcalls::VMComponentLibcalls::INIT;
*self.vmctx_plus_offset_mut(self.offsets.store()) = store;
*self.vmctx_plus_offset_mut(self.offsets.limits()) = (*store).vmruntime_limits();
for i in 0..self.offsets.num_runtime_component_instances {
let i = RuntimeComponentInstanceIndex::from_u32(i);
let mut def = VMGlobalDefinition::new();
*def.as_i32_mut() = FLAG_MAY_ENTER | FLAG_MAY_LEAVE;
*self.instance_flags(i).as_raw() = def;
}
// In debug mode set non-null bad values to all "pointer looking" bits
// and pices related to lowering and such. This'll help detect any
// erroneous usage and enable debug assertions above as well to prevent
// loading these before they're configured or setting them twice.
if cfg!(debug_assertions) {
for i in 0..self.offsets.num_lowerings {
let i = LoweredIndex::from_u32(i);
let offset = self.offsets.lowering_callee(i);
*self.vmctx_plus_offset_mut(offset) = INVALID_PTR;
let offset = self.offsets.lowering_data(i);
*self.vmctx_plus_offset_mut(offset) = INVALID_PTR;
}
for i in 0..self.offsets.num_trampolines {
let i = TrampolineIndex::from_u32(i);
let offset = self.offsets.trampoline_func_ref(i);
*self.vmctx_plus_offset_mut(offset) = INVALID_PTR;
}
for i in 0..self.offsets.num_runtime_memories {
let i = RuntimeMemoryIndex::from_u32(i);
let offset = self.offsets.runtime_memory(i);
*self.vmctx_plus_offset_mut(offset) = INVALID_PTR;
}
for i in 0..self.offsets.num_runtime_reallocs {
let i = RuntimeReallocIndex::from_u32(i);
let offset = self.offsets.runtime_realloc(i);
*self.vmctx_plus_offset_mut(offset) = INVALID_PTR;
}
for i in 0..self.offsets.num_runtime_post_returns {
let i = RuntimePostReturnIndex::from_u32(i);
let offset = self.offsets.runtime_post_return(i);
*self.vmctx_plus_offset_mut(offset) = INVALID_PTR;
}
for i in 0..self.offsets.num_resources {
let i = ResourceIndex::from_u32(i);
let offset = self.offsets.resource_destructor(i);
*self.vmctx_plus_offset_mut(offset) = INVALID_PTR;
}
}
}
/// Returns a reference to the component type information for this instance.
pub fn component(&self) -> &Component {
self.runtime_info.component()
}
/// Returns the type information that this instance is instantiated with.
pub fn component_types(&self) -> &Arc<ComponentTypes> {
self.runtime_info.component_types()
}
/// Get the canonical ABI's `realloc` function's runtime type.
pub fn realloc_func_ty(&self) -> &Arc<dyn Any + Send + Sync> {
self.runtime_info.realloc_func_type()
}
/// Returns a reference to the resource type information as a `dyn Any`.
///
/// Wasmtime is the one which then downcasts this to the appropriate type.
pub fn resource_types(&self) -> &Arc<dyn Any + Send + Sync> {
&self.resource_types
}
/// Returns whether the resource that `ty` points to is owned by the
/// instance that `ty` correspond to.
///
/// This is used when lowering borrows to skip table management and instead
/// thread through the underlying representation directly.
pub fn resource_owned_by_own_instance(&self, ty: TypeResourceTableIndex) -> bool {
let resource = &self.component_types()[ty];
let component = self.component();
let idx = match component.defined_resource_index(resource.ty) {
Some(idx) => idx,
None => return false,
};
resource.instance == component.defined_resource_instances[idx]
}
/// Implementation of the `resource.new` intrinsic for `i32`
/// representations.
pub fn resource_new32(&mut self, resource: TypeResourceTableIndex, rep: u32) -> Result<u32> {
self.resource_tables().resource_new(Some(resource), rep)
}
/// Implementation of the `resource.rep` intrinsic for `i32`
/// representations.
pub fn resource_rep32(&mut self, resource: TypeResourceTableIndex, idx: u32) -> Result<u32> {
self.resource_tables().resource_rep(Some(resource), idx)
}
/// Implementation of the `resource.drop` intrinsic.
pub fn resource_drop(
&mut self,
resource: TypeResourceTableIndex,
idx: u32,
) -> Result<Option<u32>> {
self.resource_tables().resource_drop(Some(resource), idx)
}
/// NB: this is intended to be a private method. This does not have
/// `host_table` information at this time meaning it's only suitable for
/// working with resources specified to this component which is currently
/// all that this is used for.
///
/// If necessary though it's possible to enhance the `Store` trait to thread
/// through the relevant information and get `host_table` to be `Some` here.
fn resource_tables(&mut self) -> ResourceTables<'_> {
ResourceTables {
host_table: None,
calls: unsafe { (&mut *self.store()).component_calls() },
tables: Some(&mut self.component_resource_tables),
}
}
/// Returns the runtime state of resources associated with this component.
#[inline]
pub fn component_resource_tables(
&mut self,
) -> &mut PrimaryMap<TypeResourceTableIndex, ResourceTable> {
&mut self.component_resource_tables
}
/// Returns the destructor and instance flags for the specified resource
/// table type.
///
/// This will lookup the origin definition of the `ty` table and return the
/// destructor/flags for that.
pub fn dtor_and_flags(
&self,
ty: TypeResourceTableIndex,
) -> (Option<NonNull<VMFuncRef>>, Option<InstanceFlags>) {
let resource = self.component_types()[ty].ty;
let dtor = self.resource_destructor(resource);
let component = self.component();
let flags = component.defined_resource_index(resource).map(|i| {
let instance = component.defined_resource_instances[i];
self.instance_flags(instance)
});
(dtor, flags)
}
pub(crate) fn resource_transfer_own(
&mut self,
idx: u32,
src: TypeResourceTableIndex,
dst: TypeResourceTableIndex,
) -> Result<u32> {
let mut tables = self.resource_tables();
let rep = tables.resource_lift_own(Some(src), idx)?;
tables.resource_lower_own(Some(dst), rep)
}
pub(crate) fn resource_transfer_borrow(
&mut self,
idx: u32,
src: TypeResourceTableIndex,
dst: TypeResourceTableIndex,
) -> Result<u32> {
let dst_owns_resource = self.resource_owned_by_own_instance(dst);
let mut tables = self.resource_tables();
let rep = tables.resource_lift_borrow(Some(src), idx)?;
// Implement `lower_borrow`'s special case here where if a borrow's
// resource type is owned by `dst` then the destination receives the
// representation directly rather than a handle to the representation.
//
// This can perhaps become a different libcall in the future to avoid
// this check at runtime since we know at compile time whether the
// destination type owns the resource, but that's left as a future
// refactoring if truly necessary.
if dst_owns_resource {
return Ok(rep);
}
tables.resource_lower_borrow(Some(dst), rep)
}
pub(crate) fn resource_enter_call(&mut self) {
self.resource_tables().enter_call()
}
pub(crate) fn resource_exit_call(&mut self) -> Result<()> {
self.resource_tables().exit_call()
}
}
impl VMComponentContext {
/// Moves the `self` pointer backwards to the `ComponentInstance` pointer
/// that this `VMComponentContext` trails.
pub fn instance(&self) -> *mut ComponentInstance {
unsafe {
(self as *const Self as *mut u8)
.offset(-(offset_of!(ComponentInstance, vmctx) as isize))
as *mut ComponentInstance
}
}
}
/// An owned version of `ComponentInstance` which is akin to
/// `Box<ComponentInstance>`.
///
/// This type can be dereferenced to `ComponentInstance` to access the
/// underlying methods.
pub struct OwnedComponentInstance {
ptr: SendSyncPtr<ComponentInstance>,
}
impl OwnedComponentInstance {
/// Allocates a new `ComponentInstance + VMComponentContext` pair on the
/// heap with `malloc` and configures it for the `component` specified.
pub fn new(
runtime_info: Arc<dyn ComponentRuntimeInfo>,
resource_types: Arc<dyn Any + Send + Sync>,
store: *mut dyn VMStore,
) -> OwnedComponentInstance {
let component = runtime_info.component();
let offsets = VMComponentOffsets::new(HostPtr, component);
let layout = ComponentInstance::alloc_layout(&offsets);
unsafe {
// Technically it is not required to `alloc_zeroed` here. The
// primary reason for doing this is because a component context
// start is a "partly initialized" state where pointers and such are
// configured as the instantiation process continues. The component
// model should guarantee that we never access uninitialized memory
// in the context, but to help protect against possible bugs a
// zeroed allocation is done here to try to contain
// use-before-initialized issues.
let ptr = alloc::alloc::alloc_zeroed(layout) as *mut ComponentInstance;
let ptr = NonNull::new(ptr).unwrap();
ComponentInstance::new_at(
ptr,
layout.size(),
offsets,
runtime_info,
resource_types,
store,
);
let ptr = SendSyncPtr::new(ptr);
OwnedComponentInstance { ptr }
}
}
// Note that this is technically unsafe due to the fact that it enables
// `mem::swap`-ing two component instances which would get all the offsets
// mixed up and cause issues. This is scoped to just this module though as a
// convenience to forward to `&mut` methods on `ComponentInstance`.
unsafe fn instance_mut(&mut self) -> &mut ComponentInstance {
&mut *self.ptr.as_ptr()
}
/// Returns the underlying component instance's raw pointer.
pub fn instance_ptr(&self) -> *mut ComponentInstance {
self.ptr.as_ptr()
}
/// See `ComponentInstance::set_runtime_memory`
pub fn set_runtime_memory(&mut self, idx: RuntimeMemoryIndex, ptr: *mut VMMemoryDefinition) {
unsafe { self.instance_mut().set_runtime_memory(idx, ptr) }
}
/// See `ComponentInstance::set_runtime_realloc`
pub fn set_runtime_realloc(&mut self, idx: RuntimeReallocIndex, ptr: NonNull<VMFuncRef>) {
unsafe { self.instance_mut().set_runtime_realloc(idx, ptr) }
}
/// See `ComponentInstance::set_runtime_post_return`
pub fn set_runtime_post_return(
&mut self,
idx: RuntimePostReturnIndex,
ptr: NonNull<VMFuncRef>,
) {
unsafe { self.instance_mut().set_runtime_post_return(idx, ptr) }
}
/// See `ComponentInstance::set_lowering`
pub fn set_lowering(&mut self, idx: LoweredIndex, lowering: VMLowering) {
unsafe { self.instance_mut().set_lowering(idx, lowering) }
}
/// See `ComponentInstance::set_resource_drop`
pub fn set_trampoline(
&mut self,
idx: TrampolineIndex,
wasm_call: NonNull<VMWasmCallFunction>,
array_call: VMArrayCallFunction,
type_index: VMSharedTypeIndex,
) {
unsafe {
self.instance_mut()
.set_trampoline(idx, wasm_call, array_call, type_index)
}
}
/// See `ComponentInstance::set_resource_destructor`
pub fn set_resource_destructor(
&mut self,
idx: ResourceIndex,
dtor: Option<NonNull<VMFuncRef>>,
) {
unsafe { self.instance_mut().set_resource_destructor(idx, dtor) }
}
/// See `ComponentInstance::resource_types`
pub fn resource_types_mut(&mut self) -> &mut Arc<dyn Any + Send + Sync> {
unsafe { &mut (*self.ptr.as_ptr()).resource_types }
}
}
impl Deref for OwnedComponentInstance {
type Target = ComponentInstance;
fn deref(&self) -> &ComponentInstance {
unsafe { &*self.ptr.as_ptr() }
}
}
impl Drop for OwnedComponentInstance {
fn drop(&mut self) {
let layout = ComponentInstance::alloc_layout(&self.offsets);
unsafe {
ptr::drop_in_place(self.ptr.as_ptr());
alloc::alloc::dealloc(self.ptr.as_ptr().cast(), layout);
}
}
}
impl VMComponentContext {
/// Helper function to cast between context types using a debug assertion to
/// protect against some mistakes.
#[inline]
pub unsafe fn from_opaque(opaque: *mut VMOpaqueContext) -> *mut VMComponentContext {
// See comments in `VMContext::from_opaque` for this debug assert
debug_assert_eq!((*opaque).magic, VMCOMPONENT_MAGIC);
opaque.cast()
}
}
impl VMOpaqueContext {
/// Helper function to clearly indicate the cast desired
#[inline]
pub fn from_vmcomponent(ptr: *mut VMComponentContext) -> *mut VMOpaqueContext {
ptr.cast()
}
}
#[allow(missing_docs)]
#[repr(transparent)]
#[derive(Copy, Clone)]
pub struct InstanceFlags(SendSyncPtr<VMGlobalDefinition>);
#[allow(missing_docs)]
impl InstanceFlags {
#[inline]
pub unsafe fn may_leave(&self) -> bool {
*(*self.as_raw()).as_i32() & FLAG_MAY_LEAVE != 0
}
#[inline]
pub unsafe fn set_may_leave(&mut self, val: bool) {
if val {
*(*self.as_raw()).as_i32_mut() |= FLAG_MAY_LEAVE;
} else {
*(*self.as_raw()).as_i32_mut() &= !FLAG_MAY_LEAVE;
}
}
#[inline]
pub unsafe fn may_enter(&self) -> bool {
*(*self.as_raw()).as_i32() & FLAG_MAY_ENTER != 0
}
#[inline]
pub unsafe fn set_may_enter(&mut self, val: bool) {
if val {
*(*self.as_raw()).as_i32_mut() |= FLAG_MAY_ENTER;
} else {
*(*self.as_raw()).as_i32_mut() &= !FLAG_MAY_ENTER;
}
}
#[inline]
pub unsafe fn needs_post_return(&self) -> bool {
*(*self.as_raw()).as_i32() & FLAG_NEEDS_POST_RETURN != 0
}
#[inline]
pub unsafe fn set_needs_post_return(&mut self, val: bool) {
if val {
*(*self.as_raw()).as_i32_mut() |= FLAG_NEEDS_POST_RETURN;
} else {
*(*self.as_raw()).as_i32_mut() &= !FLAG_NEEDS_POST_RETURN;
}
}
#[inline]
pub fn as_raw(&self) -> *mut VMGlobalDefinition {
self.0.as_ptr()
}
}
/// Runtime information about a component stored locally for reflection.
pub trait ComponentRuntimeInfo: Send + Sync + 'static {
/// Returns the type information about the compiled component.
fn component(&self) -> &Component;
/// Returns a handle to the tables of type information for this component.
fn component_types(&self) -> &Arc<ComponentTypes>;
/// Get the `wasmtime::FuncType` for the canonical ABI's `realloc` function.
fn realloc_func_type(&self) -> &Arc<dyn Any + Send + Sync>;
}
@@ -1,572 +0,0 @@
//! Implementation of string transcoding required by the component model.
use crate::prelude::*;
use crate::runtime::vm::component::{ComponentInstance, VMComponentContext};
use core::cell::Cell;
use core::slice;
use wasmtime_environ::component::TypeResourceTableIndex;
const UTF16_TAG: usize = 1 << 31;
#[repr(C)] // this is read by Cranelift code so it's layout must be as-written
pub struct VMComponentLibcalls {
builtins: VMComponentBuiltins,
transcoders: VMBuiltinTranscodeArray,
}
impl VMComponentLibcalls {
pub const INIT: VMComponentLibcalls = VMComponentLibcalls {
builtins: VMComponentBuiltins::INIT,
transcoders: VMBuiltinTranscodeArray::INIT,
};
}
macro_rules! signature {
(@ty size) => (usize);
(@ty ptr_u8) => (*mut u8);
(@ty ptr_u16) => (*mut u16);
(@ty ptr_size) => (*mut usize);
(@ty u8) => (u8);
(@ty u32) => (u32);
(@ty u64) => (u64);
(@ty vmctx) => (*mut VMComponentContext);
}
/// Defines a `VMComponentBuiltins` structure which contains any builtins such
/// as resource-related intrinsics.
macro_rules! define_builtins {
(
$(
$( #[$attr:meta] )*
$name:ident( $( $pname:ident: $param:ident ),* ) $( -> $result:ident )?;
)*
) => {
/// An array that stores addresses of builtin functions. We translate code
/// to use indirect calls. This way, we don't have to patch the code.
#[repr(C)]
struct VMComponentBuiltins {
$(
$name: unsafe extern "C" fn(
$(signature!(@ty $param),)*
) $( -> signature!(@ty $result))?,
)*
}
impl VMComponentBuiltins {
const INIT: VMComponentBuiltins = VMComponentBuiltins {
$($name: trampolines::$name,)*
};
}
};
}
wasmtime_environ::foreach_builtin_component_function!(define_builtins);
/// Macro to define the `VMBuiltinTranscodeArray` type which contains all of the
/// function pointers to the actual transcoder functions. This structure is read
/// by Cranelift-generated code, hence the `repr(C)`.
///
/// Note that this references the `trampolines` module rather than the functions
/// below as the `trampolines` module has the raw ABI.
///
/// This is modeled after the similar macros and usages in `libcalls.rs` and
/// `vmcontext.rs`
macro_rules! define_transcoders {
(
$(
$( #[$attr:meta] )*
$name:ident( $( $pname:ident: $param:ident ),* ) $( -> $result:ident )?;
)*
) => {
/// An array that stores addresses of builtin functions. We translate code
/// to use indirect calls. This way, we don't have to patch the code.
#[repr(C)]
struct VMBuiltinTranscodeArray {
$(
$name: unsafe extern "C" fn(
$(signature!(@ty $param),)*
) $( -> signature!(@ty $result))?,
)*
}
impl VMBuiltinTranscodeArray {
const INIT: VMBuiltinTranscodeArray = VMBuiltinTranscodeArray {
$($name: trampolines::$name,)*
};
}
};
}
wasmtime_environ::foreach_transcoder!(define_transcoders);
/// Submodule with macro-generated constants which are the actual libcall
/// transcoders that are invoked by Cranelift. These functions have a specific
/// ABI defined by the macro itself and will defer to the actual bodies of each
/// implementation following this submodule.
#[allow(improper_ctypes_definitions)]
mod trampolines {
use super::VMComponentContext;
macro_rules! shims {
(
$(
$( #[$attr:meta] )*
$name:ident( $( $pname:ident: $param:ident ),* ) $( -> $result:ident )?;
)*
) => (
$(
pub unsafe extern "C" fn $name(
$($pname : signature!(@ty $param),)*
) $( -> signature!(@ty $result))? {
$(shims!(@validate_param $pname $param);)*
// Always catch panics to avoid trying to unwind from Rust
// into Cranelift-generated code which would lead to a Bad
// Time.
//
// Additionally assume that every function below returns a
// `Result` where errors turn into traps.
let result = crate::runtime::vm::traphandlers::catch_unwind_and_longjmp(|| {
shims!(@invoke $name() $($pname)*)
});
match result {
Ok(ret) => shims!(@convert_ret ret $($pname: $param)*),
Err(err) => crate::runtime::vm::traphandlers::raise_trap(
crate::runtime::vm::traphandlers::TrapReason::User {
error: err,
needs_backtrace: true,
},
),
}
}
)*
);
// Helper macro to convert a 2-tuple automatically when the last
// parameter is a `ptr_size` argument.
(@convert_ret $ret:ident) => ($ret);
(@convert_ret $ret:ident $retptr:ident: ptr_size) => ({
let (a, b) = $ret;
*$retptr = b;
a
});
(@convert_ret $ret:ident $name:ident: $ty:ident $($rest:tt)*) => (
shims!(@convert_ret $ret $($rest)*)
);
(@validate_param $arg:ident ptr_u16) => ({
// This should already be guaranteed by the canonical ABI and our
// adapter modules, but double-check here to be extra-sure. If this
// is a perf concern it can become a `debug_assert!`.
assert!(($arg as usize) % 2 == 0, "unaligned 16-bit pointer");
});
(@validate_param $arg:ident $ty:ident) => ();
// Helper macro to invoke `$m` with all of the tokens passed except for
// any argument named `ret2`
(@invoke $m:ident ($($args:tt)*)) => (super::$m($($args)*));
// ignore `ret2`-named arguments
(@invoke $m:ident ($($args:tt)*) ret2 $($rest:tt)*) => (
shims!(@invoke $m ($($args)*) $($rest)*)
);
// move all other arguments into the `$args` list
(@invoke $m:ident ($($args:tt)*) $param:ident $($rest:tt)*) => (
shims!(@invoke $m ($($args)* $param,) $($rest)*)
);
}
wasmtime_environ::foreach_builtin_component_function!(shims);
wasmtime_environ::foreach_transcoder!(shims);
}
/// This property should already be guaranteed by construction in the component
/// model but assert it here to be extra sure. Nothing below is sound if regions
/// can overlap.
fn assert_no_overlap<T, U>(a: &[T], b: &[U]) {
let a_start = a.as_ptr() as usize;
let a_end = a_start + (a.len() * core::mem::size_of::<T>());
let b_start = b.as_ptr() as usize;
let b_end = b_start + (b.len() * core::mem::size_of::<U>());
if a_start < b_start {
assert!(a_end < b_start);
} else {
assert!(b_end < a_start);
}
}
/// Converts a utf8 string to a utf8 string.
///
/// The length provided is length of both the source and the destination
/// buffers. No value is returned other than whether an invalid string was
/// found.
unsafe fn utf8_to_utf8(src: *mut u8, len: usize, dst: *mut u8) -> Result<()> {
let src = slice::from_raw_parts(src, len);
let dst = slice::from_raw_parts_mut(dst, len);
assert_no_overlap(src, dst);
log::trace!("utf8-to-utf8 {len}");
let src = core::str::from_utf8(src).map_err(|_| anyhow!("invalid utf8 encoding"))?;
dst.copy_from_slice(src.as_bytes());
Ok(())
}
/// Converts a utf16 string to a utf16 string.
///
/// The length provided is length of both the source and the destination
/// buffers. No value is returned other than whether an invalid string was
/// found.
unsafe fn utf16_to_utf16(src: *mut u16, len: usize, dst: *mut u16) -> Result<()> {
let src = slice::from_raw_parts(src, len);
let dst = slice::from_raw_parts_mut(dst, len);
assert_no_overlap(src, dst);
log::trace!("utf16-to-utf16 {len}");
run_utf16_to_utf16(src, dst)?;
Ok(())
}
/// Transcodes utf16 to itself, returning whether all code points were inside of
/// the latin1 space.
fn run_utf16_to_utf16(src: &[u16], mut dst: &mut [u16]) -> Result<bool> {
let mut all_latin1 = true;
for ch in core::char::decode_utf16(src.iter().map(|i| u16::from_le(*i))) {
let ch = ch.map_err(|_| anyhow!("invalid utf16 encoding"))?;
all_latin1 = all_latin1 && u8::try_from(u32::from(ch)).is_ok();
let result = ch.encode_utf16(dst);
let size = result.len();
for item in result {
*item = item.to_le();
}
dst = &mut dst[size..];
}
Ok(all_latin1)
}
/// Converts a latin1 string to a latin1 string.
///
/// Given that all byte sequences are valid latin1 strings this is simply a
/// memory copy.
unsafe fn latin1_to_latin1(src: *mut u8, len: usize, dst: *mut u8) -> Result<()> {
let src = slice::from_raw_parts(src, len);
let dst = slice::from_raw_parts_mut(dst, len);
assert_no_overlap(src, dst);
log::trace!("latin1-to-latin1 {len}");
dst.copy_from_slice(src);
Ok(())
}
/// Converts a latin1 string to a utf16 string.
///
/// This simply inflates the latin1 characters to the u16 code points. The
/// length provided is the same length of the source and destination buffers.
unsafe fn latin1_to_utf16(src: *mut u8, len: usize, dst: *mut u16) -> Result<()> {
let src = slice::from_raw_parts(src, len);
let dst = slice::from_raw_parts_mut(dst, len);
assert_no_overlap(src, dst);
for (src, dst) in src.iter().zip(dst) {
*dst = u16::from(*src).to_le();
}
log::trace!("latin1-to-utf16 {len}");
Ok(())
}
/// Converts utf8 to utf16.
///
/// The length provided is the same unit length of both buffers, and the
/// returned value from this function is how many u16 units were written.
unsafe fn utf8_to_utf16(src: *mut u8, len: usize, dst: *mut u16) -> Result<usize> {
let src = slice::from_raw_parts(src, len);
let dst = slice::from_raw_parts_mut(dst, len);
assert_no_overlap(src, dst);
let result = run_utf8_to_utf16(src, dst)?;
log::trace!("utf8-to-utf16 {len} => {result}");
Ok(result)
}
fn run_utf8_to_utf16(src: &[u8], dst: &mut [u16]) -> Result<usize> {
let src = core::str::from_utf8(src).map_err(|_| anyhow!("invalid utf8 encoding"))?;
let mut amt = 0;
for (i, dst) in src.encode_utf16().zip(dst) {
*dst = i.to_le();
amt += 1;
}
Ok(amt)
}
/// Converts utf16 to utf8.
///
/// Each buffer is specified independently here and the returned value is a pair
/// of the number of code units read and code units written. This might perform
/// a partial transcode if the destination buffer is not large enough to hold
/// the entire contents.
unsafe fn utf16_to_utf8(
src: *mut u16,
src_len: usize,
dst: *mut u8,
dst_len: usize,
) -> Result<(usize, usize)> {
let src = slice::from_raw_parts(src, src_len);
let mut dst = slice::from_raw_parts_mut(dst, dst_len);
assert_no_overlap(src, dst);
// This iterator will convert to native endianness and additionally count
// how many items have been read from the iterator so far. This
// count is used to return how many of the source code units were read.
let src_iter_read = Cell::new(0);
let src_iter = src.iter().map(|i| {
src_iter_read.set(src_iter_read.get() + 1);
u16::from_le(*i)
});
let mut src_read = 0;
let mut dst_written = 0;
for ch in core::char::decode_utf16(src_iter) {
let ch = ch.map_err(|_| anyhow!("invalid utf16 encoding"))?;
// If the destination doesn't have enough space for this character
// then the loop is ended and this function will be called later with a
// larger destination buffer.
if dst.len() < 4 && dst.len() < ch.len_utf8() {
break;
}
// Record that characters were read and then convert the `char` to
// utf-8, advancing the destination buffer.
src_read = src_iter_read.get();
let len = ch.encode_utf8(dst).len();
dst_written += len;
dst = &mut dst[len..];
}
log::trace!("utf16-to-utf8 {src_len}/{dst_len} => {src_read}/{dst_written}");
Ok((src_read, dst_written))
}
/// Converts latin1 to utf8.
///
/// Receives the independent size of both buffers and returns the number of code
/// units read and code units written (both bytes in this case).
///
/// This may perform a partial encoding if the destination is not large enough.
unsafe fn latin1_to_utf8(
src: *mut u8,
src_len: usize,
dst: *mut u8,
dst_len: usize,
) -> Result<(usize, usize)> {
let src = slice::from_raw_parts(src, src_len);
let dst = slice::from_raw_parts_mut(dst, dst_len);
assert_no_overlap(src, dst);
let (read, written) = encoding_rs::mem::convert_latin1_to_utf8_partial(src, dst);
log::trace!("latin1-to-utf8 {src_len}/{dst_len} => ({read}, {written})");
Ok((read, written))
}
/// Converts utf16 to "latin1+utf16", probably using a utf16 encoding.
///
/// The length specified is the length of both the source and destination
/// buffers. If the source string has any characters that don't fit in the
/// latin1 code space (0xff and below) then a utf16-tagged length will be
/// returned. Otherwise the string is "deflated" from a utf16 string to a latin1
/// string and the latin1 length is returned.
unsafe fn utf16_to_compact_probably_utf16(
src: *mut u16,
len: usize,
dst: *mut u16,
) -> Result<usize> {
let src = slice::from_raw_parts(src, len);
let dst = slice::from_raw_parts_mut(dst, len);
assert_no_overlap(src, dst);
let all_latin1 = run_utf16_to_utf16(src, dst)?;
if all_latin1 {
let (left, dst, right) = dst.align_to_mut::<u8>();
assert!(left.is_empty());
assert!(right.is_empty());
for i in 0..len {
dst[i] = dst[2 * i];
}
log::trace!("utf16-to-compact-probably-utf16 {len} => latin1 {len}");
Ok(len)
} else {
log::trace!("utf16-to-compact-probably-utf16 {len} => utf16 {len}");
Ok(len | UTF16_TAG)
}
}
/// Converts a utf8 string to latin1.
///
/// The length specified is the same length of both the input and the output
/// buffers.
///
/// Returns the number of code units read from the source and the number of code
/// units written to the destination.
///
/// Note that this may not convert the entire source into the destination if the
/// original utf8 string has usvs not representable in latin1.
unsafe fn utf8_to_latin1(src: *mut u8, len: usize, dst: *mut u8) -> Result<(usize, usize)> {
let src = slice::from_raw_parts(src, len);
let dst = slice::from_raw_parts_mut(dst, len);
assert_no_overlap(src, dst);
let read = encoding_rs::mem::utf8_latin1_up_to(src);
let written = encoding_rs::mem::convert_utf8_to_latin1_lossy(&src[..read], dst);
log::trace!("utf8-to-latin1 {len} => ({read}, {written})");
Ok((read, written))
}
/// Converts a utf16 string to latin1
///
/// This is the same as `utf8_to_latin1` in terms of parameters/results.
unsafe fn utf16_to_latin1(src: *mut u16, len: usize, dst: *mut u8) -> Result<(usize, usize)> {
let src = slice::from_raw_parts(src, len);
let dst = slice::from_raw_parts_mut(dst, len);
assert_no_overlap(src, dst);
let mut size = 0;
for (src, dst) in src.iter().zip(dst) {
let src = u16::from_le(*src);
match u8::try_from(src) {
Ok(src) => *dst = src,
Err(_) => break,
}
size += 1;
}
log::trace!("utf16-to-latin1 {len} => {size}");
Ok((size, size))
}
/// Converts a utf8 string to a utf16 string which has been partially converted
/// as latin1 prior.
///
/// The original string has already been partially transcoded with
/// `utf8_to_latin1` and that was determined to not be able to transcode the
/// entire string. The substring of the source that couldn't be encoded into
/// latin1 is passed here via `src` and `src_len`.
///
/// The destination buffer is specified by `dst` and `dst_len`. The first
/// `latin1_bytes_so_far` bytes (not code units) of the `dst` buffer have
/// already been filled in with latin1 characters and need to be inflated
/// in-place to their utf16 equivalents.
///
/// After the initial latin1 code units have been inflated the entirety of `src`
/// is then transcoded into the remaining space within `dst`.
unsafe fn utf8_to_compact_utf16(
src: *mut u8,
src_len: usize,
dst: *mut u16,
dst_len: usize,
latin1_bytes_so_far: usize,
) -> Result<usize> {
let src = slice::from_raw_parts(src, src_len);
let dst = slice::from_raw_parts_mut(dst, dst_len);
assert_no_overlap(src, dst);
let dst = inflate_latin1_bytes(dst, latin1_bytes_so_far);
let result = run_utf8_to_utf16(src, dst)?;
log::trace!("utf8-to-compact-utf16 {src_len}/{dst_len}/{latin1_bytes_so_far} => {result}");
Ok(result + latin1_bytes_so_far)
}
/// Same as `utf8_to_compact_utf16` but for utf16 source strings.
unsafe fn utf16_to_compact_utf16(
src: *mut u16,
src_len: usize,
dst: *mut u16,
dst_len: usize,
latin1_bytes_so_far: usize,
) -> Result<usize> {
let src = slice::from_raw_parts(src, src_len);
let dst = slice::from_raw_parts_mut(dst, dst_len);
assert_no_overlap(src, dst);
let dst = inflate_latin1_bytes(dst, latin1_bytes_so_far);
run_utf16_to_utf16(src, dst)?;
let result = src.len();
log::trace!("utf16-to-compact-utf16 {src_len}/{dst_len}/{latin1_bytes_so_far} => {result}");
Ok(result + latin1_bytes_so_far)
}
/// Inflates the `latin1_bytes_so_far` number of bytes written to the beginning
/// of `dst` into u16 codepoints.
///
/// Returns the remaining space in the destination that can be transcoded into,
/// slicing off the prefix of the string that was inflated from the latin1
/// bytes.
fn inflate_latin1_bytes(dst: &mut [u16], latin1_bytes_so_far: usize) -> &mut [u16] {
// Note that `latin1_bytes_so_far` is a byte measure while `dst` is a region
// of u16 units. This `split_at_mut` uses the byte index as an index into
// the u16 unit because each of the latin1 bytes will become a whole code
// unit in the destination which is 2 bytes large.
let (to_inflate, rest) = dst.split_at_mut(latin1_bytes_so_far);
// Use a byte-oriented view to inflate the original latin1 bytes.
let (left, mid, right) = unsafe { to_inflate.align_to_mut::<u8>() };
assert!(left.is_empty());
assert!(right.is_empty());
for i in (0..latin1_bytes_so_far).rev() {
mid[2 * i] = mid[i];
mid[2 * i + 1] = 0;
}
return rest;
}
unsafe fn resource_new32(vmctx: *mut VMComponentContext, resource: u32, rep: u32) -> Result<u32> {
let resource = TypeResourceTableIndex::from_u32(resource);
ComponentInstance::from_vmctx(vmctx, |instance| instance.resource_new32(resource, rep))
}
unsafe fn resource_rep32(vmctx: *mut VMComponentContext, resource: u32, idx: u32) -> Result<u32> {
let resource = TypeResourceTableIndex::from_u32(resource);
ComponentInstance::from_vmctx(vmctx, |instance| instance.resource_rep32(resource, idx))
}
unsafe fn resource_drop(vmctx: *mut VMComponentContext, resource: u32, idx: u32) -> Result<u64> {
let resource = TypeResourceTableIndex::from_u32(resource);
ComponentInstance::from_vmctx(vmctx, |instance| {
Ok(match instance.resource_drop(resource, idx)? {
Some(rep) => (u64::from(rep) << 1) | 1,
None => 0,
})
})
}
unsafe fn resource_transfer_own(
vmctx: *mut VMComponentContext,
src_idx: u32,
src_table: u32,
dst_table: u32,
) -> Result<u32> {
let src_table = TypeResourceTableIndex::from_u32(src_table);
let dst_table = TypeResourceTableIndex::from_u32(dst_table);
ComponentInstance::from_vmctx(vmctx, |instance| {
instance.resource_transfer_own(src_idx, src_table, dst_table)
})
}
unsafe fn resource_transfer_borrow(
vmctx: *mut VMComponentContext,
src_idx: u32,
src_table: u32,
dst_table: u32,
) -> Result<u32> {
let src_table = TypeResourceTableIndex::from_u32(src_table);
let dst_table = TypeResourceTableIndex::from_u32(dst_table);
ComponentInstance::from_vmctx(vmctx, |instance| {
instance.resource_transfer_borrow(src_idx, src_table, dst_table)
})
}
unsafe fn resource_enter_call(vmctx: *mut VMComponentContext) -> Result<()> {
ComponentInstance::from_vmctx(vmctx, |instance| Ok(instance.resource_enter_call()))
}
unsafe fn resource_exit_call(vmctx: *mut VMComponentContext) -> Result<()> {
ComponentInstance::from_vmctx(vmctx, |instance| instance.resource_exit_call())
}
unsafe fn trap(_vmctx: *mut VMComponentContext, code: u8) -> Result<()> {
Err(wasmtime_environ::Trap::from_u8(code).unwrap()).err2anyhow()
}
@@ -1,351 +0,0 @@
//! Implementation of the canonical-ABI related intrinsics for resources in the
//! component model.
//!
//! This module contains all the relevant gory details of the
//! component model related to lifting and lowering resources. For example
//! intrinsics like `resource.new` will bottom out in calling this file, and
//! this is where resource tables are actually defined and modified.
//!
//! The main types in this file are:
//!
//! * `ResourceTables` - the "here's everything" context which is required to
//! perform canonical ABI operations.
//!
//! * `ResourceTable` - an individual instance of a table of resources,
//! basically "just a slab" though.
//!
//! * `CallContexts` - store-local information about active calls and borrows
//! and runtime state tracking that to ensure that everything is handled
//! correctly.
//!
//! Individual operations are exposed through methods on `ResourceTables` for
//! lifting/lowering/etc. This does mean though that some other fiddly bits
//! about ABI details can be found in lifting/lowering throughout Wasmtime,
//! namely in the `Resource<T>` and `ResourceAny` types.
use crate::prelude::*;
use core::mem;
use wasmtime_environ::component::TypeResourceTableIndex;
use wasmtime_environ::PrimaryMap;
/// The maximum handle value is specified in
/// <https://github.com/WebAssembly/component-model/blob/main/design/mvp/CanonicalABI.md>
/// currently and keeps the upper bit free for use in the component.
const MAX_RESOURCE_HANDLE: u32 = 1 << 30;
/// Contextual state necessary to perform resource-related operations.
///
/// This state a bit odd since it has a few optional bits, but the idea is that
/// whenever this is constructed the bits required to perform operations are
/// always `Some`. For example:
///
/// * During lifting and lowering both `tables` and `host_table` are `Some`.
/// * During wasm's own intrinsics only `tables` is `Some`.
/// * During embedder-invoked resource destruction calls only `host_table` is
/// `Some`.
///
/// This is all packaged up into one state though to make it easier to operate
/// on and to centralize handling of the state related to resources due to how
/// critical it is for correctness.
pub struct ResourceTables<'a> {
/// Runtime state for all resources defined in a component.
///
/// This is required whenever a `TypeResourceTableIndex` is provided as it's
/// the lookup where that happens. Not present during embedder-originating
/// operations though such as `ResourceAny::resource_drop` which won't
/// consult this table as it's only operating over the host table.
pub tables: Option<&'a mut PrimaryMap<TypeResourceTableIndex, ResourceTable>>,
/// Runtime state for resources currently owned by the host.
///
/// This is the single table used by the host stored within `Store<T>`. Host
/// resources will point into this and effectively have the same semantics
/// as-if they're in-component resources. The major distinction though is
/// that this is a heterogeneous table instead of only containing a single
/// type.
pub host_table: Option<&'a mut ResourceTable>,
/// Scope information about calls actively in use to track information such
/// as borrow counts.
pub calls: &'a mut CallContexts,
}
/// An individual slab of resources used for a single table within a component.
/// Not much fancier than a general slab data structure.
#[derive(Default)]
pub struct ResourceTable {
/// Next slot to allocate, or `self.slots.len()` if they're all full.
next: u32,
/// Runtime state of all slots.
slots: Vec<Slot>,
}
enum Slot {
/// This slot is free and points to the next free slot, forming a linked
/// list of free slots.
Free { next: u32 },
/// This slot contains an owned resource with the listed representation.
///
/// The `lend_count` tracks how many times this has been lent out as a
/// `borrow` and if nonzero this can't be removed.
Own { rep: u32, lend_count: u32 },
/// This slot contains a `borrow` resource that's connected to the `scope`
/// provided. The `rep` is listed and dropping this borrow will decrement
/// the borrow count of the `scope`.
Borrow { rep: u32, scope: usize },
}
/// State related to borrows and calls within a component.
///
/// This is created once per `Store` and updated and modified throughout the
/// lifetime of the store. This primarily tracks borrow counts and what slots
/// should be updated when calls go out of scope.
#[derive(Default)]
pub struct CallContexts {
scopes: Vec<CallContext>,
}
#[derive(Default)]
struct CallContext {
lenders: Vec<Lender>,
borrow_count: u32,
}
#[derive(Copy, Clone)]
struct Lender {
ty: Option<TypeResourceTableIndex>,
idx: u32,
}
impl ResourceTables<'_> {
fn table(&mut self, ty: Option<TypeResourceTableIndex>) -> &mut ResourceTable {
match ty {
None => self.host_table.as_mut().unwrap(),
Some(idx) => &mut self.tables.as_mut().unwrap()[idx],
}
}
/// Implementation of the `resource.new` canonical intrinsic.
///
/// Note that this is the same as `resource_lower_own`.
pub fn resource_new(&mut self, ty: Option<TypeResourceTableIndex>, rep: u32) -> Result<u32> {
self.table(ty).insert(Slot::Own { rep, lend_count: 0 })
}
/// Implementation of the `resource.rep` canonical intrinsic.
///
/// This one's one of the simpler ones: "just get the rep please"
pub fn resource_rep(&mut self, ty: Option<TypeResourceTableIndex>, idx: u32) -> Result<u32> {
self.table(ty).rep(idx)
}
/// Implementation of the `resource.drop` canonical intrinsic minus the
/// actual invocation of the destructor.
///
/// This will drop the handle at the `idx` specified, removing it from the
/// specified table. This operation can fail if:
///
/// * The index is invalid.
/// * The index points to an `own` resource which has active borrows.
///
/// Otherwise this will return `Some(rep)` if the destructor for `rep` needs
/// to run. If `None` is returned then that means a `borrow` handle was
/// removed and no destructor is necessary.
pub fn resource_drop(
&mut self,
ty: Option<TypeResourceTableIndex>,
idx: u32,
) -> Result<Option<u32>> {
match self.table(ty).remove(idx)? {
Slot::Own { rep, lend_count: 0 } => Ok(Some(rep)),
Slot::Own { .. } => bail!("cannot remove owned resource while borrowed"),
Slot::Borrow { scope, .. } => {
self.calls.scopes[scope].borrow_count -= 1;
Ok(None)
}
Slot::Free { .. } => unreachable!(),
}
}
/// Inserts a new "own" handle into the specified table.
///
/// This will insert the specified representation into the specified type
/// table.
///
/// Note that this operation is infallible, and additionally that this is
/// the same as `resource_new` implementation-wise.
///
/// This is an implementation of the canonical ABI `lower_own` function.
pub fn resource_lower_own(
&mut self,
ty: Option<TypeResourceTableIndex>,
rep: u32,
) -> Result<u32> {
self.table(ty).insert(Slot::Own { rep, lend_count: 0 })
}
/// Attempts to remove an "own" handle from the specified table and its
/// index.
///
/// This operation will fail if `idx` is invalid, if it's a `borrow` handle,
/// or if the own handle has currently been "lent" as a borrow.
///
/// This is an implementation of the canonical ABI `lift_own` function.
pub fn resource_lift_own(
&mut self,
ty: Option<TypeResourceTableIndex>,
idx: u32,
) -> Result<u32> {
match self.table(ty).remove(idx)? {
Slot::Own { rep, lend_count: 0 } => Ok(rep),
Slot::Own { .. } => bail!("cannot remove owned resource while borrowed"),
Slot::Borrow { .. } => bail!("cannot lift own resource from a borrow"),
Slot::Free { .. } => unreachable!(),
}
}
/// Extracts the underlying resource representation by lifting a "borrow"
/// from the tables.
///
/// This primarily employs dynamic tracking when a borrow is created from an
/// "own" handle to ensure that the "own" handle isn't dropped while the
/// borrow is active and additionally that when the current call scope
/// returns the lend operation is undone.
///
/// This is an implementation of the canonical ABI `lift_borrow` function.
pub fn resource_lift_borrow(
&mut self,
ty: Option<TypeResourceTableIndex>,
idx: u32,
) -> Result<u32> {
match self.table(ty).get_mut(idx)? {
Slot::Own { rep, lend_count } => {
// The decrement to this count happens in `exit_call`.
*lend_count = lend_count.checked_add(1).unwrap();
let rep = *rep;
let scope = self.calls.scopes.last_mut().unwrap();
scope.lenders.push(Lender { ty, idx });
Ok(rep)
}
Slot::Borrow { rep, .. } => Ok(*rep),
Slot::Free { .. } => unreachable!(),
}
}
/// Records a new `borrow` resource with the given representation within the
/// current call scope.
///
/// This requires that a call scope is active. Additionally the number of
/// active borrows in the latest scope will be increased and must be
/// decreased through a future use of `resource_drop` before the current
/// call scope exits.
///
/// This some of the implementation of the canonical ABI `lower_borrow`
/// function. The other half of this implementation is located on
/// `VMComponentContext` which handles the special case of avoiding borrow
/// tracking entirely.
pub fn resource_lower_borrow(
&mut self,
ty: Option<TypeResourceTableIndex>,
rep: u32,
) -> Result<u32> {
let scope = self.calls.scopes.len() - 1;
let borrow_count = &mut self.calls.scopes.last_mut().unwrap().borrow_count;
*borrow_count = borrow_count.checked_add(1).unwrap();
self.table(ty).insert(Slot::Borrow { rep, scope })
}
/// Enters a new calling context, starting a fresh count of borrows and
/// such.
#[inline]
pub fn enter_call(&mut self) {
self.calls.scopes.push(CallContext::default());
}
/// Exits the previously pushed calling context.
///
/// This requires all information to be available within this
/// `ResourceTables` and is only called during lowering/lifting operations
/// at this time.
#[inline]
pub fn exit_call(&mut self) -> Result<()> {
let cx = self.calls.scopes.pop().unwrap();
if cx.borrow_count > 0 {
bail!("borrow handles still remain at the end of the call")
}
for lender in cx.lenders.iter() {
// Note the panics here which should never get triggered in theory
// due to the dynamic tracking of borrows and such employed for
// resources.
match self.table(lender.ty).get_mut(lender.idx).unwrap() {
Slot::Own { lend_count, .. } => {
*lend_count -= 1;
}
_ => unreachable!(),
}
}
Ok(())
}
}
impl ResourceTable {
fn insert(&mut self, new: Slot) -> Result<u32> {
let next = self.next as usize;
if next == self.slots.len() {
self.slots.push(Slot::Free {
next: self.next.checked_add(1).unwrap(),
});
}
let ret = self.next;
self.next = match mem::replace(&mut self.slots[next], new) {
Slot::Free { next } => next,
_ => unreachable!(),
};
// The component model reserves index 0 as never allocatable so add one
// to the table index to start the numbering at 1 instead. Also note
// that the component model places an upper-limit per-table on the
// maximum allowed index.
let ret = ret + 1;
if ret >= MAX_RESOURCE_HANDLE {
bail!("cannot allocate another handle: index overflow");
}
Ok(ret)
}
fn handle_index_to_table_index(&self, idx: u32) -> Option<usize> {
// NB: `idx` is decremented by one to account for the `+1` above during
// allocation.
let idx = idx.checked_sub(1)?;
usize::try_from(idx).ok()
}
fn rep(&self, idx: u32) -> Result<u32> {
let slot = self
.handle_index_to_table_index(idx)
.and_then(|i| self.slots.get(i));
match slot {
None | Some(Slot::Free { .. }) => bail!("unknown handle index {idx}"),
Some(Slot::Own { rep, .. } | Slot::Borrow { rep, .. }) => Ok(*rep),
}
}
fn get_mut(&mut self, idx: u32) -> Result<&mut Slot> {
let slot = self
.handle_index_to_table_index(idx)
.and_then(|i| self.slots.get_mut(i));
match slot {
None | Some(Slot::Free { .. }) => bail!("unknown handle index {idx}"),
Some(other) => Ok(other),
}
}
fn remove(&mut self, idx: u32) -> Result<Slot> {
let to_fill = Slot::Free { next: self.next };
let ret = mem::replace(self.get_mut(idx)?, to_fill);
self.next = idx - 1;
Ok(ret)
}
}
-395
View File
@@ -1,395 +0,0 @@
//! Evaluating const expressions.
use crate::runtime::vm::{Instance, VMGcRef, ValRaw, I31};
use crate::store::AutoAssertNoGc;
use crate::{
prelude::*, ArrayRef, ArrayRefPre, ArrayType, StructRef, StructRefPre, StructType, Val,
};
use smallvec::SmallVec;
use wasmtime_environ::{
ConstExpr, ConstOp, FuncIndex, GlobalIndex, ModuleInternedTypeIndex, WasmCompositeInnerType,
WasmCompositeType, WasmSubType,
};
/// An interpreter for const expressions.
///
/// This can be reused across many const expression evaluations to reuse
/// allocated resources, if any.
#[derive(Default)]
pub struct ConstExprEvaluator {
stack: SmallVec<[ValRaw; 2]>,
}
/// The context within which a particular const expression is evaluated.
pub struct ConstEvalContext<'a> {
pub(crate) instance: &'a mut Instance,
}
impl<'a> ConstEvalContext<'a> {
/// Create a new context.
pub fn new(instance: &'a mut Instance) -> Self {
Self { instance }
}
fn global_get(&mut self, store: &mut AutoAssertNoGc<'_>, index: GlobalIndex) -> Result<ValRaw> {
unsafe {
let global = self
.instance
.defined_or_imported_global_ptr(index)
.as_ref()
.unwrap();
global.to_val_raw(store, self.instance.env_module().globals[index].wasm_ty)
}
}
fn ref_func(&mut self, index: FuncIndex) -> Result<ValRaw> {
Ok(ValRaw::funcref(
self.instance.get_func_ref(index).unwrap().cast(),
))
}
#[cfg(feature = "gc")]
fn struct_fields_len(&self, struct_type_index: ModuleInternedTypeIndex) -> usize {
let module = self
.instance
.runtime_module()
.expect("should never be allocating a struct type defined in a dummy module");
let struct_ty = match &module.types()[struct_type_index].composite_type.inner {
WasmCompositeInnerType::Struct(s) => s,
_ => unreachable!(),
};
struct_ty.fields.len()
}
/// Safety: field values must be of the correct types.
#[cfg(feature = "gc")]
unsafe fn struct_new(
&mut self,
store: &mut AutoAssertNoGc<'_>,
struct_type_index: ModuleInternedTypeIndex,
fields: &[ValRaw],
) -> Result<ValRaw> {
let module = self
.instance
.runtime_module()
.expect("should never be allocating a struct type defined in a dummy module");
let shared_ty = module
.signatures()
.shared_type(struct_type_index)
.expect("should have an engine type for module type");
let struct_ty = StructType::from_shared_type_index(store.engine(), shared_ty);
let fields = fields
.iter()
.zip(struct_ty.fields())
.map(|(raw, ty)| {
let ty = ty.element_type().unpack();
Val::_from_raw(store, *raw, ty)
})
.collect::<Vec<_>>();
let allocator = StructRefPre::_new(store, struct_ty);
let struct_ref = StructRef::_new(store, &allocator, &fields)?;
let raw = struct_ref.to_anyref()._to_raw(store)?;
Ok(ValRaw::anyref(raw))
}
#[cfg(feature = "gc")]
fn struct_new_default(
&mut self,
store: &mut AutoAssertNoGc<'_>,
struct_type_index: ModuleInternedTypeIndex,
) -> Result<ValRaw> {
let module = self
.instance
.runtime_module()
.expect("should never be allocating a struct type defined in a dummy module");
let shared_ty = module
.signatures()
.shared_type(struct_type_index)
.expect("should have an engine type for module type");
let borrowed = module
.engine()
.signatures()
.borrow(shared_ty)
.expect("should have a registered type for struct");
let WasmSubType {
composite_type:
WasmCompositeType {
shared: false,
inner: WasmCompositeInnerType::Struct(struct_ty),
},
..
} = &*borrowed
else {
unreachable!("registered type should be a struct");
};
let fields = struct_ty
.fields
.iter()
.map(|ty| match &ty.element_type {
wasmtime_environ::WasmStorageType::I8 | wasmtime_environ::WasmStorageType::I16 => {
ValRaw::i32(0)
}
wasmtime_environ::WasmStorageType::Val(v) => match v {
wasmtime_environ::WasmValType::I32 => ValRaw::i32(0),
wasmtime_environ::WasmValType::I64 => ValRaw::i64(0),
wasmtime_environ::WasmValType::F32 => ValRaw::f32(0.0f32.to_bits()),
wasmtime_environ::WasmValType::F64 => ValRaw::f64(0.0f64.to_bits()),
wasmtime_environ::WasmValType::V128 => ValRaw::v128(0),
wasmtime_environ::WasmValType::Ref(r) => {
assert!(r.nullable);
ValRaw::null()
}
},
})
.collect::<SmallVec<[_; 8]>>();
unsafe { self.struct_new(store, struct_type_index, &fields) }
}
}
impl ConstExprEvaluator {
/// Evaluate the given const expression in the given context.
///
/// # Unsafety
///
/// The given const expression must be valid within the given context,
/// e.g. the const expression must be well-typed and the context must return
/// global values of the expected types. This evaluator operates directly on
/// untyped `ValRaw`s and does not and cannot check that its operands are of
/// the correct type.
pub unsafe fn eval(
&mut self,
context: &mut ConstEvalContext<'_>,
expr: &ConstExpr,
) -> Result<ValRaw> {
log::trace!("evaluating const expr: {:?}", expr);
self.stack.clear();
let mut store = (*context.instance.store()).store_opaque_mut();
// Ensure that we don't permanently root any GC references we allocate
// during const evaluation, keeping them alive for the duration of the
// store's lifetime.
#[cfg(feature = "gc")]
let mut store = crate::OpaqueRootScope::new(&mut store);
// We cannot allow GC during const evaluation because the stack of
// `ValRaw`s are not rooted. If we had a GC reference on our stack, and
// then performed a collection, that on-stack reference's object could
// be reclaimed or relocated by the collector, and then when we use the
// reference again we would basically get a use-after-free bug.
let mut store = AutoAssertNoGc::new(&mut store);
for op in expr.ops() {
match op {
ConstOp::I32Const(i) => self.stack.push(ValRaw::i32(*i)),
ConstOp::I64Const(i) => self.stack.push(ValRaw::i64(*i)),
ConstOp::F32Const(f) => self.stack.push(ValRaw::f32(*f)),
ConstOp::F64Const(f) => self.stack.push(ValRaw::f64(*f)),
ConstOp::V128Const(v) => self.stack.push(ValRaw::v128(*v)),
ConstOp::GlobalGet(g) => self.stack.push(context.global_get(&mut store, *g)?),
ConstOp::RefNull => self.stack.push(ValRaw::null()),
ConstOp::RefFunc(f) => self.stack.push(context.ref_func(*f)?),
ConstOp::RefI31 => {
let i = self.pop()?.get_i32();
let i31 = I31::wrapping_i32(i);
let raw = VMGcRef::from_i31(i31).as_raw_u32();
self.stack.push(ValRaw::anyref(raw));
}
ConstOp::I32Add => {
let b = self.pop()?.get_i32();
let a = self.pop()?.get_i32();
self.stack.push(ValRaw::i32(a.wrapping_add(b)));
}
ConstOp::I32Sub => {
let b = self.pop()?.get_i32();
let a = self.pop()?.get_i32();
self.stack.push(ValRaw::i32(a.wrapping_sub(b)));
}
ConstOp::I32Mul => {
let b = self.pop()?.get_i32();
let a = self.pop()?.get_i32();
self.stack.push(ValRaw::i32(a.wrapping_mul(b)));
}
ConstOp::I64Add => {
let b = self.pop()?.get_i64();
let a = self.pop()?.get_i64();
self.stack.push(ValRaw::i64(a.wrapping_add(b)));
}
ConstOp::I64Sub => {
let b = self.pop()?.get_i64();
let a = self.pop()?.get_i64();
self.stack.push(ValRaw::i64(a.wrapping_sub(b)));
}
ConstOp::I64Mul => {
let b = self.pop()?.get_i64();
let a = self.pop()?.get_i64();
self.stack.push(ValRaw::i64(a.wrapping_mul(b)));
}
#[cfg(not(feature = "gc"))]
ConstOp::StructNew { .. }
| ConstOp::StructNewDefault { .. }
| ConstOp::ArrayNew { .. }
| ConstOp::ArrayNewDefault { .. }
| ConstOp::ArrayNewFixed { .. } => {
bail!(
"const expr evaluation error: struct operations are not \
supported without the `gc` feature"
)
}
#[cfg(feature = "gc")]
ConstOp::StructNew { struct_type_index } => {
let interned_type_index =
context.instance.env_module().types[*struct_type_index];
let len = context.struct_fields_len(interned_type_index);
if self.stack.len() < len {
bail!(
"const expr evaluation error: expected at least {len} values on the stack, found {}",
self.stack.len()
)
}
let start = self.stack.len() - len;
let s = context.struct_new(
&mut store,
interned_type_index,
&self.stack[start..],
)?;
self.stack.truncate(start);
self.stack.push(s);
}
#[cfg(feature = "gc")]
ConstOp::StructNewDefault { struct_type_index } => {
let interned_type_index =
context.instance.env_module().types[*struct_type_index];
self.stack
.push(context.struct_new_default(&mut store, interned_type_index)?);
}
#[cfg(feature = "gc")]
ConstOp::ArrayNew { array_type_index } => {
let interned_type_index =
context.instance.env_module().types[*array_type_index];
let module = context.instance.runtime_module().expect(
"should never be allocating a struct type defined in a dummy module",
);
let shared_ty = module
.signatures()
.shared_type(interned_type_index)
.expect("should have an engine type for module type");
let ty = ArrayType::from_shared_type_index(store.engine(), shared_ty);
#[allow(clippy::cast_sign_loss)]
let len = self.pop()?.get_i32() as u32;
let elem = Val::_from_raw(&mut store, self.pop()?, ty.element_type().unpack());
let pre = ArrayRefPre::_new(&mut store, ty);
let array = ArrayRef::_new(&mut store, &pre, &elem, len)?;
self.stack
.push(ValRaw::anyref(array.to_anyref()._to_raw(&mut store)?));
}
#[cfg(feature = "gc")]
ConstOp::ArrayNewDefault { array_type_index } => {
let interned_type_index =
context.instance.env_module().types[*array_type_index];
let module = context.instance.runtime_module().expect(
"should never be allocating a struct type defined in a dummy module",
);
let shared_ty = module
.signatures()
.shared_type(interned_type_index)
.expect("should have an engine type for module type");
let ty = ArrayType::from_shared_type_index(store.engine(), shared_ty);
#[allow(clippy::cast_sign_loss)]
let len = self.pop()?.get_i32() as u32;
let elem = Val::default_for_ty(ty.element_type().unpack())
.expect("type should have a default value");
let pre = ArrayRefPre::_new(&mut store, ty);
let array = ArrayRef::_new(&mut store, &pre, &elem, len)?;
self.stack
.push(ValRaw::anyref(array.to_anyref()._to_raw(&mut store)?));
}
#[cfg(feature = "gc")]
ConstOp::ArrayNewFixed {
array_type_index,
array_size,
} => {
let interned_type_index =
context.instance.env_module().types[*array_type_index];
let module = context.instance.runtime_module().expect(
"should never be allocating a struct type defined in a dummy module",
);
let shared_ty = module
.signatures()
.shared_type(interned_type_index)
.expect("should have an engine type for module type");
let ty = ArrayType::from_shared_type_index(store.engine(), shared_ty);
let array_size = usize::try_from(*array_size).unwrap();
if self.stack.len() < array_size {
bail!(
"const expr evaluation error: expected at least {array_size} values on the stack, found {}",
self.stack.len()
)
}
let start = self.stack.len() - array_size;
let elem_ty = ty.element_type();
let elem_ty = elem_ty.unpack();
let elems = self
.stack
.drain(start..)
.map(|raw| Val::_from_raw(&mut store, raw, elem_ty))
.collect::<SmallVec<[_; 8]>>();
let pre = ArrayRefPre::_new(&mut store, ty);
let array = ArrayRef::_new_fixed(&mut store, &pre, &elems)?;
self.stack
.push(ValRaw::anyref(array.to_anyref()._to_raw(&mut store)?));
}
}
}
if self.stack.len() == 1 {
log::trace!("const expr evaluated to {:?}", self.stack[0]);
Ok(self.stack[0])
} else {
bail!(
"const expr evaluation error: expected 1 resulting value, found {}",
self.stack.len()
)
}
}
fn pop(&mut self) -> Result<ValRaw> {
self.stack.pop().ok_or_else(|| {
anyhow!(
"const expr evaluation error: attempted to pop from an empty \
evaluation stack"
)
})
}
}
File diff suppressed because it is too large. Load diff
@@ -1,59 +0,0 @@
#![doc(hidden)]
use crate::runtime::vm::instance::Instance;
use crate::runtime::vm::vmcontext::VMContext;
use wasmtime_environ::{EntityRef, MemoryIndex};
use wasmtime_versioned_export_macros::versioned_export;
static mut VMCTX_AND_MEMORY: (*mut VMContext, usize) = (std::ptr::null_mut(), 0);
#[versioned_export]
pub unsafe extern "C" fn resolve_vmctx_memory(ptr: usize) -> *const u8 {
Instance::from_vmctx(VMCTX_AND_MEMORY.0, |handle| {
assert!(
VMCTX_AND_MEMORY.1 < handle.env_module().memories.len(),
"memory index for debugger is out of bounds"
);
let index = MemoryIndex::new(VMCTX_AND_MEMORY.1);
let mem = handle.get_memory(index);
mem.base.add(ptr)
})
}
#[versioned_export]
pub unsafe extern "C" fn resolve_vmctx_memory_ptr(p: *const u32) -> *const u8 {
let ptr = std::ptr::read(p);
assert!(
!VMCTX_AND_MEMORY.0.is_null(),
"must call `__vmctx->set()` before resolving Wasm pointers"
);
Instance::from_vmctx(VMCTX_AND_MEMORY.0, |handle| {
assert!(
VMCTX_AND_MEMORY.1 < handle.env_module().memories.len(),
"memory index for debugger is out of bounds"
);
let index = MemoryIndex::new(VMCTX_AND_MEMORY.1);
let mem = handle.get_memory(index);
mem.base.add(ptr as usize)
})
}
#[versioned_export]
pub unsafe extern "C" fn set_vmctx_memory(vmctx_ptr: *mut VMContext) {
// TODO multi-memory
VMCTX_AND_MEMORY = (vmctx_ptr, 0);
}
// Ensures that set_vmctx_memory and resolve_vmctx_memory_ptr are linked and
// exported as symbols. It is a workaround: the executable normally ignores
// `pub extern "C"`, see rust-lang/rust#25057.
pub fn ensure_exported() {
if cfg!(miri) {
return;
}
unsafe {
std::ptr::read_volatile(resolve_vmctx_memory_ptr as *const u8);
std::ptr::read_volatile(set_vmctx_memory as *const u8);
std::ptr::read_volatile(resolve_vmctx_memory as *const u8);
}
}
-108
View File
@@ -1,108 +0,0 @@
use crate::runtime::vm::vmcontext::{
VMContext, VMFuncRef, VMGlobalDefinition, VMMemoryDefinition, VMTableDefinition,
};
use core::ptr::NonNull;
use wasmtime_environ::{DefinedMemoryIndex, Global, Memory, Table};
/// The value of an export passed from one instance to another.
pub enum Export {
/// A function export value.
Function(ExportFunction),
/// A table export value.
Table(ExportTable),
/// A memory export value.
Memory(ExportMemory),
/// A global export value.
Global(ExportGlobal),
}
/// A function export value.
#[derive(Debug, Clone, Copy)]
pub struct ExportFunction {
/// The `VMFuncRef` for this exported function.
///
/// Note that exported functions cannot be a null funcref, so this is a
/// non-null pointer.
pub func_ref: NonNull<VMFuncRef>,
}
// As part of the contract for using `ExportFunction`, synchronization
// properties must be upheld. Therefore, despite containing raw pointers,
// it is declared as Send/Sync.
unsafe impl Send for ExportFunction {}
unsafe impl Sync for ExportFunction {}
impl From<ExportFunction> for Export {
fn from(func: ExportFunction) -> Export {
Export::Function(func)
}
}
/// A table export value.
#[derive(Debug, Clone)]
pub struct ExportTable {
/// The address of the table descriptor.
pub definition: *mut VMTableDefinition,
/// Pointer to the containing `VMContext`.
pub vmctx: *mut VMContext,
/// The table declaration, used for compatibility checking.
pub table: Table,
}
// See docs on send/sync for `ExportFunction` above.
unsafe impl Send for ExportTable {}
unsafe impl Sync for ExportTable {}
impl From<ExportTable> for Export {
fn from(func: ExportTable) -> Export {
Export::Table(func)
}
}
/// A memory export value.
#[derive(Debug, Clone)]
pub struct ExportMemory {
/// The address of the memory descriptor.
pub definition: *mut VMMemoryDefinition,
/// Pointer to the containing `VMContext`.
pub vmctx: *mut VMContext,
/// The memory declaration, used for compatibility checking.
pub memory: Memory,
/// The index at which the memory is defined within the `vmctx`.
pub index: DefinedMemoryIndex,
}
// See docs on send/sync for `ExportFunction` above.
unsafe impl Send for ExportMemory {}
unsafe impl Sync for ExportMemory {}
impl From<ExportMemory> for Export {
fn from(func: ExportMemory) -> Export {
Export::Memory(func)
}
}
/// A global export value.
#[derive(Debug, Clone)]
pub struct ExportGlobal {
/// The address of the global storage.
pub definition: *mut VMGlobalDefinition,
/// Pointer to the containing `VMContext`. May be null for host-created
/// globals.
pub vmctx: *mut VMContext,
/// The global declaration, used for compatibility checking.
pub global: Global,
}
// See docs on send/sync for `ExportFunction` above.
unsafe impl Send for ExportGlobal {}
unsafe impl Sync for ExportGlobal {}
impl From<ExportGlobal> for Export {
fn from(func: ExportGlobal) -> Export {
Export::Global(func)
}
}
-257
View File
@@ -1,257 +0,0 @@
#[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 func_ref;
mod gc_ref;
mod gc_runtime;
mod host_data;
mod i31;
pub use func_ref::*;
pub use gc_ref::*;
pub use gc_runtime::*;
pub use host_data::*;
pub use i31::*;
use crate::prelude::*;
use crate::runtime::vm::GcHeapAllocationIndex;
use core::alloc::Layout;
use core::any::Any;
use core::mem::MaybeUninit;
use wasmtime_environ::{GcArrayLayout, GcStructLayout, VMGcKind, VMSharedTypeIndex};
/// GC-related data that is one-to-one with a `wasmtime::Store`.
///
/// Contains everything we need to do collections, invoke barriers, etc...
///
/// In general, exposes a very similar interface to `GcHeap`, but fills in some
/// of the context arguments for callers (such as the `ExternRefHostDataTable`)
/// since they are all stored together inside `GcStore`.
pub struct GcStore {
/// This GC heap's allocation index (primarily used for integrating with the
/// pooling allocator).
pub allocation_index: GcHeapAllocationIndex,
/// The actual GC heap.
pub gc_heap: Box<dyn GcHeap>,
/// The `externref` host data table for this GC heap.
pub host_data_table: ExternRefHostDataTable,
/// The function-references table for this GC heap.
pub func_ref_table: FuncRefTable,
}
impl GcStore {
/// Create a new `GcStore`.
pub fn new(allocation_index: GcHeapAllocationIndex, gc_heap: Box<dyn GcHeap>) -> Self {
let host_data_table = ExternRefHostDataTable::default();
let func_ref_table = FuncRefTable::default();
Self {
allocation_index,
gc_heap,
host_data_table,
func_ref_table,
}
}
/// Perform garbage collection within this heap.
pub fn gc(&mut self, roots: GcRootsIter<'_>) {
let mut collection = self.gc_heap.gc(roots, &mut self.host_data_table);
collection.collect();
}
/// Asynchronously perform garbage collection within this heap.
#[cfg(feature = "async")]
pub async fn gc_async(&mut self, roots: GcRootsIter<'_>) {
let collection = self.gc_heap.gc(roots, &mut self.host_data_table);
collect_async(collection).await;
}
/// Get the kind of the given GC reference.
pub fn kind(&self, gc_ref: &VMGcRef) -> VMGcKind {
debug_assert!(!gc_ref.is_i31());
self.header(gc_ref).kind()
}
/// Get the header of the given GC reference.
pub fn header(&self, gc_ref: &VMGcRef) -> &VMGcHeader {
debug_assert!(!gc_ref.is_i31());
self.gc_heap.header(gc_ref)
}
/// Clone a GC reference, calling GC write barriers as necessary.
pub fn clone_gc_ref(&mut self, gc_ref: &VMGcRef) -> VMGcRef {
if gc_ref.is_i31() {
gc_ref.unchecked_copy()
} else {
self.gc_heap.clone_gc_ref(gc_ref)
}
}
/// Write the `source` GC reference into the uninitialized `destination`
/// slot, performing write barriers as necessary.
pub fn init_gc_ref(
&mut self,
destination: &mut MaybeUninit<Option<VMGcRef>>,
source: Option<&VMGcRef>,
) {
// Initialize the destination to `None`, at which point the regular GC
// write barrier is safe to reuse.
let destination = destination.write(None);
self.write_gc_ref(destination, source);
}
/// Write the `source` GC reference into the `destination` slot, performing
/// write barriers as necessary.
pub fn write_gc_ref(&mut self, destination: &mut Option<VMGcRef>, source: Option<&VMGcRef>) {
// If neither the source nor destination actually point to a GC object
// (that is, they are both either null or `i31ref`s) then we can skip
// the GC barrier.
if destination.as_ref().map_or(true, |d| d.is_i31())
&& source.as_ref().map_or(true, |s| s.is_i31())
{
*destination = source.map(|s| s.unchecked_copy());
return;
}
self.gc_heap
.write_gc_ref(&mut self.host_data_table, destination, source);
}
/// Drop the given GC reference, performing drop barriers as necessary.
pub fn drop_gc_ref(&mut self, gc_ref: VMGcRef) {
if !gc_ref.is_i31() {
self.gc_heap.drop_gc_ref(&mut self.host_data_table, gc_ref);
}
}
/// Hook to call whenever a GC reference is about to be exposed to Wasm.
pub fn expose_gc_ref_to_wasm(&mut self, gc_ref: VMGcRef) {
if !gc_ref.is_i31() {
log::trace!("exposing GC ref to Wasm: {gc_ref:p}");
self.gc_heap.expose_gc_ref_to_wasm(gc_ref);
}
}
/// Allocate a new `externref`.
///
/// Returns:
///
/// * `Ok(Ok(_))`: Successfully allocated the `externref`.
///
/// * `Ok(Err(value))`: Failed to allocate the `externref`, but doing a GC
/// and then trying again may succeed. Returns the given `value` as the
/// error payload.
///
/// * `Err(_)`: Unrecoverable allocation failure.
pub fn alloc_externref(
&mut self,
value: Box<dyn Any + Send + Sync>,
) -> Result<Result<VMExternRef, Box<dyn Any + Send + Sync>>> {
let host_data_id = self.host_data_table.alloc(value);
match self.gc_heap.alloc_externref(host_data_id)? {
#[cfg_attr(not(feature = "gc"), allow(unreachable_patterns))]
Some(x) => Ok(Ok(x)),
None => Ok(Err(self.host_data_table.dealloc(host_data_id))),
}
}
/// Get a shared borrow of the given `externref`'s host data.
///
/// Passing invalid `VMExternRef`s (eg garbage values or `externref`s
/// associated with a different heap is memory safe but will lead to general
/// incorrectness such as panics and wrong results.
pub fn externref_host_data(&self, externref: &VMExternRef) -> &(dyn Any + Send + Sync) {
let host_data_id = self.gc_heap.externref_host_data(externref);
self.host_data_table.get(host_data_id)
}
/// Get a mutable borrow of the given `externref`'s host data.
///
/// Passing invalid `VMExternRef`s (eg garbage values or `externref`s
/// associated with a different heap is memory safe but will lead to general
/// incorrectness such as panics and wrong results.
pub fn externref_host_data_mut(
&mut self,
externref: &VMExternRef,
) -> &mut (dyn Any + Send + Sync) {
let host_data_id = self.gc_heap.externref_host_data(externref);
self.host_data_table.get_mut(host_data_id)
}
/// Allocate a raw object with the given header and layout.
pub fn alloc_raw(&mut self, header: VMGcHeader, layout: Layout) -> Result<Option<VMGcRef>> {
self.gc_heap.alloc_raw(header, layout)
}
/// Allocate an uninitialized struct with the given type index and layout.
///
/// This does NOT check that the index is currently allocated in the types
/// registry or that the layout matches the index's type. Failure to uphold
/// those invariants is memory safe, but will lead to general incorrectness
/// such as panics and wrong results.
pub fn alloc_uninit_struct(
&mut self,
ty: VMSharedTypeIndex,
layout: &GcStructLayout,
) -> Result<Option<VMStructRef>> {
self.gc_heap.alloc_uninit_struct(ty, layout)
}
/// Deallocate an uninitialized struct.
pub fn dealloc_uninit_struct(&mut self, structref: VMStructRef) {
self.gc_heap.dealloc_uninit_struct(structref);
}
/// Get the data for the given object reference.
///
/// Panics when the structref and its size is out of the GC heap bounds.
pub fn gc_object_data(&mut self, gc_ref: &VMGcRef) -> VMGcObjectDataMut<'_> {
self.gc_heap.gc_object_data(gc_ref)
}
/// Get the object datas for the given pair of object references.
///
/// Panics if `a` and `b` are the same reference or either is out of bounds.
pub fn gc_object_data_pair(
&mut self,
a: &VMGcRef,
b: &VMGcRef,
) -> (VMGcObjectDataMut<'_>, VMGcObjectDataMut<'_>) {
assert_ne!(a, b);
self.gc_heap.gc_object_data_pair(a, b)
}
/// Allocate an uninitialized array with the given type index.
///
/// This does NOT check that the index is currently allocated in the types
/// registry or that the layout matches the index's type. Failure to uphold
/// those invariants is memory safe, but will lead to general incorrectness
/// such as panics and wrong results.
pub fn alloc_uninit_array(
&mut self,
ty: VMSharedTypeIndex,
len: u32,
layout: &GcArrayLayout,
) -> Result<Option<VMArrayRef>> {
self.gc_heap.alloc_uninit_array(ty, len, layout)
}
/// Deallocate an uninitialized array.
pub fn dealloc_uninit_array(&mut self, arrayref: VMArrayRef) {
self.gc_heap.dealloc_uninit_array(arrayref);
}
/// Get the length of the given array.
pub fn array_len(&self, arrayref: &VMArrayRef) -> u32 {
self.gc_heap.array_len(arrayref)
}
}
@@ -1,31 +0,0 @@
//! Dummy GC types for when the `gc` cargo feature is disabled.
//!
//! To reduce `#[cfg(...)]`s, this provides all the same methods as the real
//! `VMExternRef` except for constructors.
#![allow(missing_docs)]
use crate::prelude::*;
use crate::runtime::vm::{GcHeap, GcRuntime};
use wasmtime_environ::{
GcArrayLayout, GcStructLayout, GcTypeLayouts, WasmArrayType, WasmStructType,
};
pub enum VMExternRef {}
pub enum VMEqRef {}
pub enum VMStructRef {}
pub enum VMArrayRef {}
pub struct VMGcObjectDataMut<'a> {
inner: VMStructRef,
_phantom: core::marker::PhantomData<&'a mut ()>,
}
impl VMGcObjectDataMut<'_> {
pub fn new(_data: &mut [u8]) -> Self {
unreachable!()
}
}
@@ -1,44 +0,0 @@
//! Implementation of garbage collection and GC types in Wasmtime.
mod arrayref;
mod data;
mod externref;
mod free_list;
mod structref;
pub use arrayref::*;
pub use data::*;
pub use externref::*;
pub use structref::*;
#[cfg(feature = "gc-drc")]
mod drc;
#[cfg(feature = "gc-drc")]
pub use drc::*;
#[cfg(feature = "gc-null")]
mod null;
#[cfg(feature = "gc-null")]
pub use null::*;
use crate::runtime::vm::GcRuntime;
/// The default GC heap capacity: 512KiB.
#[cfg(not(miri))]
const DEFAULT_GC_HEAP_CAPACITY: usize = 1 << 19;
/// The default GC heap capacity for miri: 64KiB.
#[cfg(miri)]
const DEFAULT_GC_HEAP_CAPACITY: usize = 1 << 16;
// Explicit methods with `#[allow]` to clearly indicate that truncation is
// desired when used.
#[allow(clippy::cast_possible_truncation)]
fn truncate_i32_to_i16(a: i32) -> i16 {
a as i16
}
#[allow(clippy::cast_possible_truncation)]
fn truncate_i32_to_i8(a: i32) -> i8 {
a as i8
}
@@ -1,370 +0,0 @@
use super::{truncate_i32_to_i16, truncate_i32_to_i8};
use crate::{
prelude::*,
runtime::vm::{GcHeap, GcStore, VMGcRef},
store::{AutoAssertNoGc, StoreOpaque},
vm::{FuncRefTableId, SendSyncPtr},
AnyRef, ExternRef, Func, HeapType, RootedGcRefImpl, StorageType, Val, ValType,
};
use core::fmt;
use wasmtime_environ::{GcArrayLayout, VMGcKind};
/// A `VMGcRef` that we know points to a `array`.
///
/// Create a `VMArrayRef` via `VMGcRef::into_arrayref` and
/// `VMGcRef::as_arrayref`, or their untyped equivalents
/// `VMGcRef::into_arrayref_unchecked` and `VMGcRef::as_arrayref_unchecked`.
///
/// Note: This is not a `TypedGcRef<_>` because each collector can have a
/// different concrete representation of `arrayref` that they allocate inside
/// their heaps.
#[derive(Debug, PartialEq, Eq, Hash)]
#[repr(transparent)]
pub struct VMArrayRef(VMGcRef);
impl fmt::Pointer for VMArrayRef {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Pointer::fmt(&self.0, f)
}
}
impl From<VMArrayRef> for VMGcRef {
#[inline]
fn from(x: VMArrayRef) -> Self {
x.0
}
}
impl VMGcRef {
/// Is this `VMGcRef` pointing to a `array`?
pub fn is_arrayref(&self, gc_heap: &(impl GcHeap + ?Sized)) -> bool {
if self.is_i31() {
return false;
}
let header = gc_heap.header(&self);
header.kind().matches(VMGcKind::ArrayRef)
}
/// Create a new `VMArrayRef` from the given `gc_ref`.
///
/// If this is not a GC reference to an `arrayref`, `Err(self)` is
/// returned.
pub fn into_arrayref(self, gc_heap: &(impl GcHeap + ?Sized)) -> Result<VMArrayRef, VMGcRef> {
if self.is_arrayref(gc_heap) {
Ok(self.into_arrayref_unchecked())
} else {
Err(self)
}
}
/// Create a new `VMArrayRef` from `self` without actually checking that
/// `self` is an `arrayref`.
///
/// This method does not check that `self` is actually an `arrayref`, but
/// it should be. Failure to uphold this invariant is memory safe but will
/// result in general incorrectness down the line such as panics or wrong
/// results.
#[inline]
pub fn into_arrayref_unchecked(self) -> VMArrayRef {
debug_assert!(!self.is_i31());
VMArrayRef(self)
}
/// Get this GC reference as an `arrayref` reference, if it actually is an
/// `arrayref` reference.
pub fn as_arrayref(&self, gc_heap: &(impl GcHeap + ?Sized)) -> Option<&VMArrayRef> {
if self.is_arrayref(gc_heap) {
Some(self.as_arrayref_unchecked())
} else {
None
}
}
/// Get this GC reference as an `arrayref` reference without checking if it
/// actually is an `arrayref` reference.
///
/// Calling this method on a non-`arrayref` reference is memory safe, but
/// will lead to general incorrectness like panics and wrong results.
pub fn as_arrayref_unchecked(&self) -> &VMArrayRef {
debug_assert!(!self.is_i31());
let ptr = self as *const VMGcRef;
let ret = unsafe { &*ptr.cast() };
assert!(matches!(ret, VMArrayRef(VMGcRef { .. })));
ret
}
}
impl VMArrayRef {
/// Get the underlying `VMGcRef`.
pub fn as_gc_ref(&self) -> &VMGcRef {
&self.0
}
/// Clone this `VMArrayRef`, running any GC barriers as necessary.
pub fn clone(&self, gc_store: &mut GcStore) -> Self {
Self(gc_store.clone_gc_ref(&self.0))
}
/// Explicitly drop this `arrayref`, running GC drop barriers as necessary.
pub fn drop(self, gc_store: &mut GcStore) {
gc_store.drop_gc_ref(self.0);
}
/// Copy this `VMArrayRef` without running the GC's clone barriers.
///
/// Prefer calling `clone(&mut GcStore)` instead! This is mostly an internal
/// escape hatch for collector implementations.
///
/// Failure to run GC barriers when they would otherwise be necessary can
/// lead to leaks, panics, and wrong results. It cannot lead to memory
/// unsafety, however.
pub fn unchecked_copy(&self) -> Self {
Self(self.0.unchecked_copy())
}
/// Get the length of this array.
pub fn len(&self, store: &StoreOpaque) -> u32 {
store.unwrap_gc_store().array_len(self)
}
/// Read an element of the given `StorageType` into a `Val`.
///
/// `i8` and `i16` fields are zero-extended into `Val::I32(_)`s.
///
/// Does not check that this array's elements are actually of type
/// `ty`. That is the caller's responsibility. Failure to do so is memory
/// safe, but will lead to general incorrectness such as panics and wrong
/// results.
///
/// Panics on out-of-bounds accesses.
pub fn read_elem(
&self,
store: &mut AutoAssertNoGc,
layout: &GcArrayLayout,
ty: &StorageType,
index: u32,
) -> Val {
let offset = layout.elem_offset(index);
let data = store.unwrap_gc_store_mut().gc_object_data(self.as_gc_ref());
match ty {
StorageType::I8 => Val::I32(data.read_u8(offset).into()),
StorageType::I16 => Val::I32(data.read_u16(offset).into()),
StorageType::ValType(ValType::I32) => Val::I32(data.read_i32(offset)),
StorageType::ValType(ValType::I64) => Val::I64(data.read_i64(offset)),
StorageType::ValType(ValType::F32) => Val::F32(data.read_u32(offset)),
StorageType::ValType(ValType::F64) => Val::F64(data.read_u64(offset)),
StorageType::ValType(ValType::V128) => Val::V128(data.read_v128(offset)),
StorageType::ValType(ValType::Ref(r)) => match r.heap_type().top() {
HeapType::Extern => {
let raw = data.read_u32(offset);
Val::ExternRef(ExternRef::_from_raw(store, raw))
}
HeapType::Any => {
let raw = data.read_u32(offset);
Val::AnyRef(AnyRef::_from_raw(store, raw))
}
HeapType::Func => {
let func_ref_id = data.read_u32(offset);
let func_ref_id = FuncRefTableId::from_raw(func_ref_id);
let func_ref = store
.unwrap_gc_store()
.func_ref_table
.get_untyped(func_ref_id);
Val::FuncRef(unsafe {
Func::from_vm_func_ref(
store,
func_ref.map_or(core::ptr::null_mut(), |f| f.as_ptr()),
)
})
}
otherwise => unreachable!("not a top type: {otherwise:?}"),
},
}
}
/// Write the given value into this array at the given offset.
///
/// Returns an error if `val` is a GC reference that has since been
/// unrooted.
///
/// Does not check that `val` matches `ty`, nor that the field is actually
/// of type `ty`. Checking those things is the caller's responsibility.
/// Failure to do so is memory safe, but will lead to general incorrectness
/// such as panics and wrong results.
///
/// Panics on out-of-bounds accesses.
pub fn write_elem(
&self,
store: &mut AutoAssertNoGc,
layout: &GcArrayLayout,
ty: &StorageType,
index: u32,
val: Val,
) -> Result<()> {
debug_assert!(val._matches_ty(&store, &ty.unpack())?);
let offset = layout.elem_offset(index);
let mut data = store.unwrap_gc_store_mut().gc_object_data(self.as_gc_ref());
match val {
Val::I32(i) if ty.is_i8() => data.write_i8(offset, truncate_i32_to_i8(i)),
Val::I32(i) if ty.is_i16() => data.write_i16(offset, truncate_i32_to_i16(i)),
Val::I32(i) => data.write_i32(offset, i),
Val::I64(i) => data.write_i64(offset, i),
Val::F32(f) => data.write_u32(offset, f),
Val::F64(f) => data.write_u64(offset, f),
Val::V128(v) => data.write_v128(offset, v),
// For GC-managed references, we need to take care to run the
// appropriate barriers, even when we are writing null references
// into the array.
//
// POD-read the old value into a local copy, run the GC write
// barrier on that local copy, and then POD-write the updated
// value back into the array. This avoids transmuting the inner
// data, which would probably be fine, but this approach is
// Obviously Correct and should get us by for now. If LLVM isn't
// able to elide some of these unnecessary copies, and this
// method is ever hot enough, we can always come back and clean
// it up in the future.
Val::ExternRef(e) => {
let raw = data.read_u32(offset);
let mut gc_ref = VMGcRef::from_raw_u32(raw);
let e = match e {
Some(e) => Some(e.try_gc_ref(store)?.unchecked_copy()),
None => None,
};
store.gc_store_mut()?.write_gc_ref(&mut gc_ref, e.as_ref());
let mut data = store.gc_store_mut()?.gc_object_data(self.as_gc_ref());
data.write_u32(offset, gc_ref.map_or(0, |r| r.as_raw_u32()));
}
Val::AnyRef(a) => {
let raw = data.read_u32(offset);
let mut gc_ref = VMGcRef::from_raw_u32(raw);
let a = match a {
Some(a) => Some(a.try_gc_ref(store)?.unchecked_copy()),
None => None,
};
store.gc_store_mut()?.write_gc_ref(&mut gc_ref, a.as_ref());
let mut data = store.gc_store_mut()?.gc_object_data(self.as_gc_ref());
data.write_u32(offset, gc_ref.map_or(0, |r| r.as_raw_u32()));
}
Val::FuncRef(f) => {
let func_ref = match f {
Some(f) => Some(SendSyncPtr::new(f.vm_func_ref(store))),
None => None,
};
let id = unsafe { store.gc_store_mut()?.func_ref_table.intern(func_ref) };
store
.gc_store_mut()?
.gc_object_data(self.as_gc_ref())
.write_u32(offset, id.into_raw());
}
}
Ok(())
}
/// Initialize an element in this arrayref that is currently uninitialized.
///
/// The difference between this method and `write_elem` is that GC barriers
/// are handled differently. When overwriting an initialized element (aka
/// `write_elem`) we need to call the full write GC write barrier, which
/// logically drops the old GC reference and clones the new GC
/// reference. When we are initializing an element for the first time, there
/// is no old GC reference that is being overwritten and which we need to
/// drop, so we only need to clone the new GC reference.
///
/// Calling this method on a arrayref that has already had the associated
/// element initialized will result in GC bugs. These are memory safe but
/// will lead to generally incorrect behavior such as panics, leaks, and
/// incorrect results.
///
/// Does not check that `val` matches `ty`, nor that the field is actually
/// of type `ty`. Checking those things is the caller's responsibility.
/// Failure to do so is memory safe, but will lead to general incorrectness
/// such as panics and wrong results.
///
/// Returns an error if `val` is a GC reference that has since been
/// unrooted.
///
/// Panics on out-of-bounds accesses.
pub fn initialize_elem(
&self,
store: &mut AutoAssertNoGc,
layout: &GcArrayLayout,
ty: &StorageType,
index: u32,
val: Val,
) -> Result<()> {
debug_assert!(val._matches_ty(&store, &ty.unpack())?);
let offset = layout.elem_offset(index);
match val {
Val::I32(i) if ty.is_i8() => store
.gc_store_mut()?
.gc_object_data(self.as_gc_ref())
.write_i8(offset, truncate_i32_to_i8(i)),
Val::I32(i) if ty.is_i16() => store
.gc_store_mut()?
.gc_object_data(self.as_gc_ref())
.write_i16(offset, truncate_i32_to_i16(i)),
Val::I32(i) => store
.gc_store_mut()?
.gc_object_data(self.as_gc_ref())
.write_i32(offset, i),
Val::I64(i) => store
.gc_store_mut()?
.gc_object_data(self.as_gc_ref())
.write_i64(offset, i),
Val::F32(f) => store
.gc_store_mut()?
.gc_object_data(self.as_gc_ref())
.write_u32(offset, f),
Val::F64(f) => store
.gc_store_mut()?
.gc_object_data(self.as_gc_ref())
.write_u64(offset, f),
Val::V128(v) => store
.gc_store_mut()?
.gc_object_data(self.as_gc_ref())
.write_v128(offset, v),
// NB: We don't need to do a write barrier when initializing a
// field, because there is nothing being overwritten. Therefore, we
// just the clone barrier.
Val::ExternRef(x) => {
let x = match x {
None => 0,
Some(x) => x.try_clone_gc_ref(store)?.as_raw_u32(),
};
store
.gc_store_mut()?
.gc_object_data(self.as_gc_ref())
.write_u32(offset, x);
}
Val::AnyRef(x) => {
let x = match x {
None => 0,
Some(x) => x.try_clone_gc_ref(store)?.as_raw_u32(),
};
store
.gc_store_mut()?
.gc_object_data(self.as_gc_ref())
.write_u32(offset, x);
}
Val::FuncRef(f) => {
let func_ref = match f {
Some(f) => Some(SendSyncPtr::new(f.vm_func_ref(store))),
None => None,
};
let id = unsafe { store.gc_store_mut()?.func_ref_table.intern(func_ref) };
store
.gc_store_mut()?
.gc_object_data(self.as_gc_ref())
.write_u32(offset, id.into_raw());
}
}
Ok(())
}
}
@@ -1,186 +0,0 @@
//! Unchecked methods for working with the data inside GC objects.
use crate::V128;
use core::mem;
/// A plain-old-data type that can be stored in a `ValType` or a `StorageType`.
pub trait PodValType<const SIZE: usize>: Copy {
/// Read an instance of `Self` from the given native-endian bytes.
fn read_le(le_bytes: &[u8; SIZE]) -> Self;
/// Write `self` into the given memory location, as native-endian bytes.
fn write_le(&self, into: &mut [u8; SIZE]);
}
macro_rules! impl_pod_val_type {
( $( $t:ty , )* ) => {
$(
impl PodValType<{mem::size_of::<$t>()}> for $t {
fn read_le(le_bytes: &[u8; mem::size_of::<$t>()]) -> Self {
<$t>::from_le_bytes(*le_bytes)
}
fn write_le(&self, into: &mut [u8; mem::size_of::<$t>()]) {
*into = self.to_le_bytes();
}
}
)*
};
}
impl_pod_val_type! {
u8,
u16,
u32,
u64,
i8,
i16,
i32,
i64,
}
impl PodValType<{ mem::size_of::<V128>() }> for V128 {
fn read_le(le_bytes: &[u8; mem::size_of::<V128>()]) -> Self {
u128::from_le_bytes(*le_bytes).into()
}
fn write_le(&self, into: &mut [u8; mem::size_of::<V128>()]) {
*into = self.as_u128().to_le_bytes();
}
}
/// The backing storage for a GC-managed object.
///
/// Methods on this type do not, generally, check against things like type
/// mismatches or that the given offset to read from even falls on a field
/// boundary. Omitting these checks is memory safe, due to our untrusted,
/// indexed GC heaps. Providing incorrect offsets will result in general
/// incorrectness, such as wrong answers or even panics, however.
///
/// Finally, these methods *will* panic on out-of-bounds accesses, either out of
/// the GC heap's bounds or out of this object's bounds. The former is necessary
/// for preserving the memory safety of indexed GC heaps in the face of (for
/// example) collector bugs, but the latter is just a defensive technique to
/// catch bugs early and prevent action at a distance as much as possible.
pub struct VMGcObjectDataMut<'a> {
data: &'a mut [u8],
}
macro_rules! impl_pod_methods {
( $( $t:ty, $read:ident, $write:ident; )* ) => {
$(
/// Read a `
#[doc = stringify!($t)]
/// ` field this object.
///
/// Panics on out-of-bounds accesses.
#[inline]
pub fn $read(&self, offset: u32) -> $t {
self.read_pod::<{ mem::size_of::<$t>() }, $t>(offset)
}
/// Write a `
#[doc = stringify!($t)]
/// ` into this object.
///
/// Panics on out-of-bounds accesses.
#[inline]
pub fn $write(&mut self, offset: u32, val: $t) {
self.write_pod::<{ mem::size_of::<$t>() }, $t>(offset, val);
}
)*
};
}
impl<'a> VMGcObjectDataMut<'a> {
/// Construct a `VMStructDataMut` from the given slice of bytes.
#[inline]
pub fn new(data: &'a mut [u8]) -> Self {
Self { data }
}
/// Read a POD field out of this object.
///
/// Panics on out-of-bounds accesses.
///
/// Don't generally use this method, use `read_u8`, `read_i64`,
/// etc... instead.
#[inline]
fn read_pod<const N: usize, T>(&self, offset: u32) -> T
where
T: PodValType<N>,
{
assert_eq!(N, mem::size_of::<T>());
let offset = usize::try_from(offset).unwrap();
let end = offset.checked_add(N).unwrap();
let bytes = self.data.get(offset..end).expect("out of bounds field");
T::read_le(bytes.try_into().unwrap())
}
/// Read a POD field out of this object.
///
/// Panics on out-of-bounds accesses.
///
/// Don't generally use this method, use `write_u8`, `write_i64`,
/// etc... instead.
#[inline]
fn write_pod<const N: usize, T>(&mut self, offset: u32, val: T)
where
T: PodValType<N>,
{
assert_eq!(N, mem::size_of::<T>());
let offset = usize::try_from(offset).unwrap();
let end = offset.checked_add(N).unwrap();
let into = match self.data.get_mut(offset..end) {
Some(into) => into,
None => panic!(
"out of bounds field! field range = {offset:#x}..{end:#x}; object len = {:#x}",
self.data.len(),
),
};
val.write_le(into.try_into().unwrap());
}
/// Get a slice of this object's data.
///
/// Panics on out-of-bounds accesses.
#[inline]
pub fn slice(&self, offset: u32, len: u32) -> &[u8] {
let start = usize::try_from(offset).unwrap();
let len = usize::try_from(len).unwrap();
let end = start.checked_add(len).unwrap();
self.data.get(start..end).expect("out of bounds slice")
}
/// Get a mutable slice of this object's data.
///
/// Panics on out-of-bounds accesses.
#[inline]
pub fn slice_mut(&mut self, offset: u32, len: u32) -> &mut [u8] {
let start = usize::try_from(offset).unwrap();
let len = usize::try_from(len).unwrap();
let end = start.checked_add(len).unwrap();
self.data.get_mut(start..end).expect("out of bounds slice")
}
/// Copy the given slice into this object's data at the given offset.
///
/// Panics on out-of-bounds accesses.
#[inline]
pub fn copy_from_slice(&mut self, offset: u32, src: &[u8]) {
let offset = usize::try_from(offset).unwrap();
let end = offset.checked_add(src.len()).unwrap();
let into = self.data.get_mut(offset..end).expect("out of bounds copy");
into.copy_from_slice(src);
}
impl_pod_methods! {
u8, read_u8, write_u8;
u16, read_u16, write_u16;
u32, read_u32, write_u32;
u64, read_u64, write_u64;
i8, read_i8, write_i8;
i16, read_i16, write_i16;
i32, read_i32, write_i32;
i64, read_i64, write_i64;
V128, read_v128, write_v128;
}
}
File diff suppressed because it is too large. Load diff
@@ -1,777 +0,0 @@
use crate::prelude::*;
use alloc::collections::BTreeMap;
use core::cmp;
use core::{alloc::Layout, num::NonZeroU32, ops::Bound};
/// A very simple first-fit free list for use by our garbage collectors.
pub(crate) struct FreeList {
/// The total capacity of the contiguous range of memory we are managing.
capacity: usize,
/// Our free blocks, as a map from index to length of the free block at that
/// index.
free_block_index_to_len: BTreeMap<u32, u32>,
}
/// Our minimum and maximum supported alignment. Every allocation is aligned to
/// this.
const ALIGN_U32: u32 = 8;
const ALIGN_USIZE: usize = ALIGN_U32 as usize;
/// Our minimum allocation size.
const MIN_BLOCK_SIZE: u32 = 24;
impl FreeList {
/// Create a new `Layout` from the given `size` with an alignment that is
/// compatible with this free list.
pub fn layout(size: usize) -> Layout {
Layout::from_size_align(size, ALIGN_USIZE).unwrap()
}
/// Create a new `FreeList` for a contiguous region of memory of the given
/// size.
pub fn new(capacity: usize) -> Self {
let mut free_list = FreeList {
capacity,
free_block_index_to_len: BTreeMap::new(),
};
free_list.reset();
free_list
}
fn max_size(&self) -> usize {
let cap = cmp::min(self.capacity, usize::try_from(u32::MAX).unwrap());
round_usize_down_to_pow2(cap.saturating_sub(ALIGN_USIZE), ALIGN_USIZE)
}
/// Check the given layout for compatibility with this free list and return
/// the actual block size we will use for this layout.
fn check_layout(&self, layout: Layout) -> Result<u32> {
ensure!(
layout.align() <= ALIGN_USIZE,
"requested allocation's alignment of {} is greater than max supported alignment of {ALIGN_USIZE}",
layout.align(),
);
ensure!(
layout.size() <= self.max_size(),
"requested allocation's size of {} is greater than the max supported size of {}",
layout.size(),
self.max_size(),
);
let alloc_size = u32::try_from(layout.size())
.err2anyhow()
.context("requested allocation's size does not fit in a u32")?;
alloc_size
.checked_next_multiple_of(ALIGN_U32)
.ok_or_else(|| {
anyhow!(
"failed to round allocation size of {alloc_size} up to next \
multiple of {ALIGN_USIZE}"
)
})
}
/// Find the first free block that can hold an allocation of the given size
/// and remove it from the free list.
fn first_fit(&mut self, alloc_size: u32) -> Option<(u32, u32)> {
debug_assert_eq!(alloc_size % ALIGN_U32, 0);
let (&block_index, &block_len) = self
.free_block_index_to_len
.iter()
.find(|(_idx, len)| **len >= alloc_size)?;
debug_assert_eq!(block_index % ALIGN_U32, 0);
debug_assert_eq!(block_len % ALIGN_U32, 0);
let entry = self.free_block_index_to_len.remove(&block_index);
debug_assert!(entry.is_some());
Some((block_index, block_len))
}
/// If the given allocated block is large enough such that we can split it
/// and still have enough space left for future allocations, then split it.
///
/// Returns the new length of the allocated block.
fn maybe_split(&mut self, alloc_size: u32, block_index: u32, block_len: u32) -> u32 {
debug_assert_eq!(alloc_size % ALIGN_U32, 0);
debug_assert_eq!(block_index % ALIGN_U32, 0);
debug_assert_eq!(block_len % ALIGN_U32, 0);
if block_len - alloc_size < MIN_BLOCK_SIZE {
// The block is not large enough to split.
return block_len;
}
// The block is large enough to split. Split the block at exactly the
// requested allocation size and put the tail back in the free list.
let new_block_len = alloc_size;
let split_start = block_index + alloc_size;
let split_len = block_len - alloc_size;
debug_assert_eq!(new_block_len % ALIGN_U32, 0);
debug_assert_eq!(split_start % ALIGN_U32, 0);
debug_assert_eq!(split_len % ALIGN_U32, 0);
self.free_block_index_to_len.insert(split_start, split_len);
new_block_len
}
/// Allocate space for an object of the given layout.
///
/// Returns:
///
/// * `Ok(Some(_))`: Allocation succeeded.
///
/// * `Ok(None)`: Can't currently fulfill the allocation request, but might
/// be able to if some stuff was reallocated.
///
/// * `Err(_)`:
pub fn alloc(&mut self, layout: Layout) -> Result<Option<NonZeroU32>> {
let alloc_size = self.check_layout(layout)?;
debug_assert_eq!(alloc_size % ALIGN_U32, 0);
let (block_index, block_len) = match self.first_fit(alloc_size) {
None => return Ok(None),
Some(tup) => tup,
};
debug_assert_ne!(block_index, 0);
debug_assert_eq!(block_index % ALIGN_U32, 0);
debug_assert!(block_len >= alloc_size);
debug_assert_eq!(block_len % ALIGN_U32, 0);
let block_len = self.maybe_split(alloc_size, block_index, block_len);
debug_assert!(block_len >= alloc_size);
debug_assert_eq!(block_len % ALIGN_U32, 0);
// After we've mutated the free list, double check its integrity.
#[cfg(debug_assertions)]
self.check_integrity();
Ok(Some(unsafe { NonZeroU32::new_unchecked(block_index) }))
}
/// Deallocate an object with the given layout.
pub fn dealloc(&mut self, index: NonZeroU32, layout: Layout) {
let index = index.get();
debug_assert_eq!(index % ALIGN_U32, 0);
let alloc_size = self.check_layout(layout).unwrap();
debug_assert_eq!(alloc_size % ALIGN_U32, 0);
let prev_block = self
.free_block_index_to_len
.range((Bound::Unbounded, Bound::Excluded(index)))
.next_back()
.map(|(idx, len)| (*idx, *len));
let next_block = self
.free_block_index_to_len
.range((Bound::Excluded(index), Bound::Unbounded))
.next()
.map(|(idx, len)| (*idx, *len));
// Try and merge this block with its previous and next blocks in the
// free list, if any and if they are contiguous.
match (prev_block, next_block) {
// The prev, this, and next blocks are all contiguous: merge this
// and next into prev.
(Some((prev_index, prev_len)), Some((next_index, next_len)))
if blocks_are_contiguous(prev_index, prev_len, index)
&& blocks_are_contiguous(index, alloc_size, next_index) =>
{
self.free_block_index_to_len.remove(&next_index);
let merged_block_len = next_index + next_len - prev_index;
debug_assert_eq!(merged_block_len % ALIGN_U32, 0);
*self.free_block_index_to_len.get_mut(&prev_index).unwrap() = merged_block_len;
}
// The prev and this blocks are contiguous: merge this into prev.
(Some((prev_index, prev_len)), _)
if blocks_are_contiguous(prev_index, prev_len, index) =>
{
let merged_block_len = index + alloc_size - prev_index;
debug_assert_eq!(merged_block_len % ALIGN_U32, 0);
*self.free_block_index_to_len.get_mut(&prev_index).unwrap() = merged_block_len;
}
// The this and next blocks are contiguous: merge next into this.
(_, Some((next_index, next_len)))
if blocks_are_contiguous(index, alloc_size, next_index) =>
{
self.free_block_index_to_len.remove(&next_index);
let merged_block_len = next_index + next_len - index;
debug_assert_eq!(merged_block_len % ALIGN_U32, 0);
self.free_block_index_to_len.insert(index, merged_block_len);
}
// None of the blocks are contiguous: insert this block into the
// free list.
(_, _) => {
self.free_block_index_to_len.insert(index, alloc_size);
}
}
// After we've added to/mutated the free list, double check its
// integrity.
#[cfg(debug_assertions)]
self.check_integrity();
}
/// Assert that the free list is valid:
///
/// 1. All blocks are within `ALIGN..self.capacity`
///
/// 2. No blocks are overlapping.
///
/// 3. All blocks are aligned to `ALIGN`
///
/// 4. All block sizes are a multiple of `ALIGN`
#[cfg(debug_assertions)]
fn check_integrity(&self) {
let mut prev_end = None;
for (&index, &len) in self.free_block_index_to_len.iter() {
// (1)
let end = index + len;
assert!(usize::try_from(end).unwrap() <= self.capacity);
// (2)
if let Some(prev_end) = prev_end {
// We could assert `prev_end <= index`, and that would be
// correct, but it would also mean that we missed an opportunity
// to merge the previous block and this current block
// together. We don't want to allow that kind of fragmentation,
// so do the stricter `prev_end < index` assert here.
assert!(prev_end < index);
}
// (3)
assert_eq!(index % ALIGN_U32, 0);
// (4)
assert_eq!(len % ALIGN_U32, 0);
prev_end = Some(end);
}
}
/// Reset this free list, making the whole range available for allocation.
pub fn reset(&mut self) {
let end = u32::try_from(self.capacity).unwrap_or_else(|_| {
assert!(self.capacity > usize::try_from(u32::MAX).unwrap());
u32::MAX
});
// Don't start at `0`. Reserve that for "null pointers" and this way we
// can use `NonZeroU32` as out pointer type, giving us some more
// bitpacking opportunities.
let start = ALIGN_U32;
let len = round_u32_down_to_pow2(end.saturating_sub(start), ALIGN_U32);
let entire_range = if len >= MIN_BLOCK_SIZE {
Some((start, len))
} else {
None
};
self.free_block_index_to_len.clear();
self.free_block_index_to_len.extend(entire_range);
}
}
#[inline]
fn blocks_are_contiguous(prev_index: u32, prev_len: u32, next_index: u32) -> bool {
// NB: We might have decided *not* to split the prev block if it was larger
// than the requested allocation size but not large enough such that if we
// split it, the remainder could fulfill future allocations. In such cases,
// the size of the `Layout` given to us upon deallocation (aka `prev_len`)
// is smaller than the actual size of the block we allocated.
let end_of_prev = prev_index + prev_len;
debug_assert!(next_index >= end_of_prev);
let delta_to_next = next_index - end_of_prev;
delta_to_next < MIN_BLOCK_SIZE
}
#[inline]
fn round_u32_down_to_pow2(value: u32, divisor: u32) -> u32 {
debug_assert!(divisor > 0);
debug_assert!(divisor.is_power_of_two());
value & !(divisor - 1)
}
#[inline]
fn round_usize_down_to_pow2(value: usize, divisor: usize) -> usize {
debug_assert!(divisor > 0);
debug_assert!(divisor.is_power_of_two());
value & !(divisor - 1)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::hash_map::HashMap;
use proptest::prelude::*;
use std::num::NonZeroUsize;
fn free_list_block_len_and_size(free_list: &FreeList) -> (usize, Option<usize>) {
let len = free_list.free_block_index_to_len.len();
let size = free_list
.free_block_index_to_len
.values()
.next()
.map(|s| usize::try_from(*s).unwrap());
(len, size)
}
proptest! {
/// This property test ensures that `FreeList` doesn't suffer from
/// permanent fragmentation. That is, it can always merge neighboring
/// free blocks together into a single, larger free block that can be
/// used to satisfy larger allocations than either of those smaller
/// blocks could have. In the limit, once we've freed all blocks, that
/// means we should end up with a single block that represents the whole
/// range of memory that the `FreeList` is portioning out (just like
/// what we started with when we initially created the `FreeList`).
#[test]
#[cfg_attr(miri, ignore)]
fn check_no_fragmentation((capacity, ops) in ops()) {
// Map from allocation id to ptr.
let mut live = HashMap::new();
// Set of deferred deallocations, where the strategy told us to
// deallocate an id before it was allocated. These simply get
// deallocated en-mass at the end.
let mut deferred = vec![];
// The free list we are testing.
let mut free_list = FreeList::new(capacity.get());
let (initial_len, initial_size) = free_list_block_len_and_size(&free_list);
assert!(initial_len == 0 || initial_len == 1);
assert!(initial_size.unwrap_or(0) <= capacity.get());
assert_eq!(initial_size.unwrap_or(0), free_list.max_size());
// Run through the generated ops and perform each operation.
for (id, op) in ops {
match op {
Op::Alloc(layout) => {
if let Ok(Some(ptr)) = free_list.alloc(layout) {
live.insert(id, ptr);
}
}
Op::Dealloc(layout) => {
if let Some(ptr) = live.remove(&id) {
free_list.dealloc(ptr, layout);
} else {
deferred.push((id, layout));
}
}
}
}
// Now that we've completed all allocations, perform the deferred
// deallocations.
for (id, layout) in deferred {
// NB: not all IDs necessarily got successful allocations, so
// there might not be a live pointer for this ID, even after
// we've already performed all the allocation operations.
if let Some(ptr) = live.remove(&id) {
free_list.dealloc(ptr, layout);
}
}
// Now we can assert various properties that should hold after we
// have deallocated everything that was allocated.
//
// First, assert we did in fact deallocate everything.
assert!(live.is_empty());
let (final_len, final_size) = free_list_block_len_and_size(&free_list);
// The free list should have a single chunk again (or no chunks if
// the capacity was too small).
assert_eq!(final_len, initial_len);
// And the size of that chunk should be the same as the initial size.
assert_eq!(final_size, initial_size);
}
}
#[derive(Clone, Debug)]
enum Op {
Alloc(Layout),
Dealloc(Layout),
}
/// Map an arbitrary `x` to a power of 2 that is less than or equal to
/// `max`, but with as little bias as possible (e.g. rounding `min(x, max)`
/// to the nearest power of 2 is unacceptable because it would majorly bias
/// the distribution towards `max` when `max` is much smaller than
/// `usize::MAX`).
fn clamp_to_pow2_in_range(x: usize, max: usize) -> usize {
let log_x = max.ilog2() as usize;
if log_x == 0 {
return 1;
}
let divisor = usize::MAX / log_x;
let y = 1_usize << (x / divisor);
assert!(y.is_power_of_two(), "{y} is not a power of two");
assert!(y <= max, "{y} is larger than {max}");
y
}
/// Helper to turn a pair of arbitrary `usize`s into a valid `Layout` of
/// reasonable size for use with quickchecks.
fn arbitrary_layout(max_size: NonZeroUsize, size: usize, align: usize) -> Layout {
// The maximum size cannot be larger than `isize::MAX` because `Layout`
// imposes that constraint on its size.
let max_size = std::cmp::min(max_size.get(), usize::try_from(isize::MAX).unwrap());
// Ensure that the alignment is a power of 2 that is less than or equal
// to the maximum alignment that `FreeList` supports.
let align = clamp_to_pow2_in_range(align, super::ALIGN_USIZE);
// Ensure that `size` is less than or equal to `max_size`.
let size = size % (max_size + 1);
// Ensure that `size` is a multiple of `align`.
//
// NB: We round `size` *down* to the previous multiple of `align` to
// preserve `size <= max_size`.
let size = round_usize_down_to_pow2(size, align);
assert!(size <= max_size);
// Double check that we satisfied all of `Layout::from_size_align`'s
// success requirements.
assert_ne!(align, 0);
assert!(align.is_power_of_two());
assert_eq!(size % align, 0);
assert!(size <= usize::try_from(isize::MAX).unwrap());
Layout::from_size_align(size, align).unwrap()
}
/// Proptest strategy to generate a free list capacity and a series of
/// allocation operations to perform in a free list of that capacity.
fn ops() -> impl Strategy<Value = (NonZeroUsize, Vec<(usize, Op)>)> {
any::<usize>().prop_flat_map(|capacity| {
let capacity =
NonZeroUsize::new(capacity).unwrap_or_else(|| NonZeroUsize::new(1 << 31).unwrap());
(
Just(capacity),
(any::<usize>(), any::<usize>(), any::<usize>())
.prop_flat_map(move |(id, size, align)| {
let layout = arbitrary_layout(capacity, size, align);
vec![
Just((id, Op::Alloc(layout))),
Just((id, Op::Dealloc(layout))),
]
})
.prop_shuffle(),
)
})
}
#[test]
fn allocate_no_split() {
// Create a free list with the capacity to allocate two blocks of size
// `MIN_BLOCK_SIZE`.
let mut free_list =
FreeList::new(ALIGN_USIZE + usize::try_from(MIN_BLOCK_SIZE).unwrap() * 2);
assert_eq!(free_list.free_block_index_to_len.len(), 1);
assert_eq!(
free_list.max_size(),
usize::try_from(MIN_BLOCK_SIZE).unwrap() * 2
);
// Allocate a block such that the remainder is not worth splitting.
free_list
.alloc(
Layout::from_size_align(
usize::try_from(MIN_BLOCK_SIZE).unwrap() + ALIGN_USIZE,
ALIGN_USIZE,
)
.unwrap(),
)
.expect("allocation within 'static' free list limits")
.expect("have free space available for allocation");
// Should not have split the block.
assert_eq!(free_list.free_block_index_to_len.len(), 0);
}
#[test]
fn allocate_and_split() {
// Create a free list with the capacity to allocate three blocks of size
// `MIN_BLOCK_SIZE`.
let mut free_list =
FreeList::new(ALIGN_USIZE + usize::try_from(MIN_BLOCK_SIZE).unwrap() * 3);
assert_eq!(free_list.free_block_index_to_len.len(), 1);
assert_eq!(
free_list.max_size(),
usize::try_from(MIN_BLOCK_SIZE).unwrap() * 3
);
// Allocate a block such that the remainder is not worth splitting.
free_list
.alloc(
Layout::from_size_align(
usize::try_from(MIN_BLOCK_SIZE).unwrap() + ALIGN_USIZE,
ALIGN_USIZE,
)
.unwrap(),
)
.expect("allocation within 'static' free list limits")
.expect("have free space available for allocation");
// Should have split the block.
assert_eq!(free_list.free_block_index_to_len.len(), 1);
}
#[test]
fn dealloc_merge_prev_and_next() {
let layout =
Layout::from_size_align(usize::try_from(MIN_BLOCK_SIZE).unwrap(), ALIGN_USIZE).unwrap();
let mut free_list =
FreeList::new(ALIGN_USIZE + usize::try_from(MIN_BLOCK_SIZE).unwrap() * 100);
assert_eq!(
free_list.free_block_index_to_len.len(),
1,
"initially one big free block"
);
let a = free_list
.alloc(layout)
.expect("allocation within 'static' free list limits")
.expect("have free space available for allocation");
assert_eq!(
free_list.free_block_index_to_len.len(),
1,
"should have split the block to allocate `a`"
);
let b = free_list
.alloc(layout)
.expect("allocation within 'static' free list limits")
.expect("have free space available for allocation");
assert_eq!(
free_list.free_block_index_to_len.len(),
1,
"should have split the block to allocate `b`"
);
free_list.dealloc(a, layout);
assert_eq!(
free_list.free_block_index_to_len.len(),
2,
"should have two non-contiguous free blocks after deallocating `a`"
);
free_list.dealloc(b, layout);
assert_eq!(
free_list.free_block_index_to_len.len(),
1,
"should have merged `a` and `b` blocks with the rest to form a \
single, contiguous free block after deallocating `b`"
);
}
#[test]
fn dealloc_merge_with_prev_and_not_next() {
let layout =
Layout::from_size_align(usize::try_from(MIN_BLOCK_SIZE).unwrap(), ALIGN_USIZE).unwrap();
let mut free_list =
FreeList::new(ALIGN_USIZE + usize::try_from(MIN_BLOCK_SIZE).unwrap() * 100);
assert_eq!(
free_list.free_block_index_to_len.len(),
1,
"initially one big free block"
);
let a = free_list
.alloc(layout)
.expect("allocation within 'static' free list limits")
.expect("have free space available for allocation");
let b = free_list
.alloc(layout)
.expect("allocation within 'static' free list limits")
.expect("have free space available for allocation");
let c = free_list
.alloc(layout)
.expect("allocation within 'static' free list limits")
.expect("have free space available for allocation");
assert_eq!(
free_list.free_block_index_to_len.len(),
1,
"should have split the block to allocate `a`, `b`, and `c`"
);
free_list.dealloc(a, layout);
assert_eq!(
free_list.free_block_index_to_len.len(),
2,
"should have two non-contiguous free blocks after deallocating `a`"
);
free_list.dealloc(b, layout);
assert_eq!(
free_list.free_block_index_to_len.len(),
2,
"should have merged `a` and `b` blocks, but not merged with the \
rest of the free space"
);
let _ = c;
}
#[test]
fn dealloc_merge_with_next_and_not_prev() {
let layout =
Layout::from_size_align(usize::try_from(MIN_BLOCK_SIZE).unwrap(), ALIGN_USIZE).unwrap();
let mut free_list =
FreeList::new(ALIGN_USIZE + usize::try_from(MIN_BLOCK_SIZE).unwrap() * 100);
assert_eq!(
free_list.free_block_index_to_len.len(),
1,
"initially one big free block"
);
let a = free_list
.alloc(layout)
.expect("allocation within 'static' free list limits")
.expect("have free space available for allocation");
let b = free_list
.alloc(layout)
.expect("allocation within 'static' free list limits")
.expect("have free space available for allocation");
let c = free_list
.alloc(layout)
.expect("allocation within 'static' free list limits")
.expect("have free space available for allocation");
assert_eq!(
free_list.free_block_index_to_len.len(),
1,
"should have split the block to allocate `a`, `b`, and `c`"
);
free_list.dealloc(a, layout);
assert_eq!(
free_list.free_block_index_to_len.len(),
2,
"should have two non-contiguous free blocks after deallocating `a`"
);
free_list.dealloc(c, layout);
assert_eq!(
free_list.free_block_index_to_len.len(),
2,
"should have merged `c` block with rest of the free space, but not \
with `a` block"
);
let _ = b;
}
#[test]
fn dealloc_no_merge() {
let layout =
Layout::from_size_align(usize::try_from(MIN_BLOCK_SIZE).unwrap(), ALIGN_USIZE).unwrap();
let mut free_list =
FreeList::new(ALIGN_USIZE + usize::try_from(MIN_BLOCK_SIZE).unwrap() * 100);
assert_eq!(
free_list.free_block_index_to_len.len(),
1,
"initially one big free block"
);
let a = free_list
.alloc(layout)
.expect("allocation within 'static' free list limits")
.expect("have free space available for allocation");
let b = free_list
.alloc(layout)
.expect("allocation within 'static' free list limits")
.expect("have free space available for allocation");
let c = free_list
.alloc(layout)
.expect("allocation within 'static' free list limits")
.expect("have free space available for allocation");
let d = free_list
.alloc(layout)
.expect("allocation within 'static' free list limits")
.expect("have free space available for allocation");
assert_eq!(
free_list.free_block_index_to_len.len(),
1,
"should have split the block to allocate `a`, `b`, `c`, and `d`"
);
free_list.dealloc(a, layout);
assert_eq!(
free_list.free_block_index_to_len.len(),
2,
"should have two non-contiguous free blocks after deallocating `a`"
);
free_list.dealloc(c, layout);
assert_eq!(
free_list.free_block_index_to_len.len(),
3,
"should not have merged `c` block `a` block or rest of the free \
space"
);
let _ = (b, d);
}
#[test]
fn alloc_size_too_large() {
// Free list with room for 10 min-sized blocks.
let mut free_list =
FreeList::new(ALIGN_USIZE + usize::try_from(MIN_BLOCK_SIZE).unwrap() * 10);
assert_eq!(
free_list.max_size(),
usize::try_from(MIN_BLOCK_SIZE).unwrap() * 10
);
// Attempt to allocate something that is 20 times the size of our
// min-sized block.
assert!(free_list
.alloc(
Layout::from_size_align(usize::try_from(MIN_BLOCK_SIZE).unwrap() * 20, ALIGN_USIZE)
.unwrap(),
)
.is_err());
}
#[test]
fn alloc_align_too_large() {
// Free list with room for 10 min-sized blocks.
let mut free_list =
FreeList::new(ALIGN_USIZE + usize::try_from(MIN_BLOCK_SIZE).unwrap() * 10);
assert_eq!(
free_list.max_size(),
usize::try_from(MIN_BLOCK_SIZE).unwrap() * 10
);
// Attempt to allocate something that requires larger alignment than
// `FreeList` supports.
assert!(free_list
.alloc(
Layout::from_size_align(usize::try_from(MIN_BLOCK_SIZE).unwrap(), ALIGN_USIZE * 2)
.unwrap(),
)
.is_err());
}
}
@@ -1,358 +0,0 @@
use super::{truncate_i32_to_i16, truncate_i32_to_i8};
use crate::{
prelude::*,
runtime::vm::{GcHeap, GcStore, VMGcRef},
store::AutoAssertNoGc,
vm::{FuncRefTableId, SendSyncPtr},
AnyRef, ExternRef, Func, HeapType, RootedGcRefImpl, StorageType, Val, ValType,
};
use core::fmt;
use wasmtime_environ::{GcStructLayout, VMGcKind};
/// A `VMGcRef` that we know points to a `struct`.
///
/// Create a `VMStructRef` via `VMGcRef::into_structref` and
/// `VMGcRef::as_structref`, or their untyped equivalents
/// `VMGcRef::into_structref_unchecked` and `VMGcRef::as_structref_unchecked`.
///
/// Note: This is not a `TypedGcRef<_>` because each collector can have a
/// different concrete representation of `structref` that they allocate inside
/// their heaps.
#[derive(Debug, PartialEq, Eq, Hash)]
#[repr(transparent)]
pub struct VMStructRef(VMGcRef);
impl fmt::Pointer for VMStructRef {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Pointer::fmt(&self.0, f)
}
}
impl From<VMStructRef> for VMGcRef {
#[inline]
fn from(x: VMStructRef) -> Self {
x.0
}
}
impl VMGcRef {
/// Is this `VMGcRef` pointing to a `struct`?
pub fn is_structref(&self, gc_heap: &(impl GcHeap + ?Sized)) -> bool {
if self.is_i31() {
return false;
}
let header = gc_heap.header(&self);
header.kind().matches(VMGcKind::StructRef)
}
/// Create a new `VMStructRef` from the given `gc_ref`.
///
/// If this is not a GC reference to an `structref`, `Err(self)` is
/// returned.
pub fn into_structref(self, gc_heap: &impl GcHeap) -> Result<VMStructRef, VMGcRef> {
if self.is_structref(gc_heap) {
Ok(self.into_structref_unchecked())
} else {
Err(self)
}
}
/// Create a new `VMStructRef` from `self` without actually checking that
/// `self` is an `structref`.
///
/// This method does not check that `self` is actually an `structref`, but
/// it should be. Failure to uphold this invariant is memory safe but will
/// result in general incorrectness down the line such as panics or wrong
/// results.
#[inline]
pub fn into_structref_unchecked(self) -> VMStructRef {
debug_assert!(!self.is_i31());
VMStructRef(self)
}
/// Get this GC reference as an `structref` reference, if it actually is an
/// `structref` reference.
pub fn as_structref(&self, gc_heap: &(impl GcHeap + ?Sized)) -> Option<&VMStructRef> {
if self.is_structref(gc_heap) {
Some(self.as_structref_unchecked())
} else {
None
}
}
/// Get this GC reference as an `structref` reference without checking if it
/// actually is an `structref` reference.
///
/// Calling this method on a non-`structref` reference is memory safe, but
/// will lead to general incorrectness like panics and wrong results.
pub fn as_structref_unchecked(&self) -> &VMStructRef {
debug_assert!(!self.is_i31());
let ptr = self as *const VMGcRef;
let ret = unsafe { &*ptr.cast() };
assert!(matches!(ret, VMStructRef(VMGcRef { .. })));
ret
}
}
impl VMStructRef {
/// Get the underlying `VMGcRef`.
pub fn as_gc_ref(&self) -> &VMGcRef {
&self.0
}
/// Clone this `VMStructRef`, running any GC barriers as necessary.
pub fn clone(&self, gc_store: &mut GcStore) -> Self {
Self(gc_store.clone_gc_ref(&self.0))
}
/// Explicitly drop this `structref`, running GC drop barriers as necessary.
pub fn drop(self, gc_store: &mut GcStore) {
gc_store.drop_gc_ref(self.0);
}
/// Copy this `VMStructRef` without running the GC's clone barriers.
///
/// Prefer calling `clone(&mut GcStore)` instead! This is mostly an internal
/// escape hatch for collector implementations.
///
/// Failure to run GC barriers when they would otherwise be necessary can
/// lead to leaks, panics, and wrong results. It cannot lead to memory
/// unsafety, however.
pub fn unchecked_copy(&self) -> Self {
Self(self.0.unchecked_copy())
}
/// Read a field of the given `StorageType` into a `Val`.
///
/// `i8` and `i16` fields are zero-extended into `Val::I32(_)`s.
///
/// Does not check that the field is actually of type `ty`. That is the
/// caller's responsibility. Failure to do so is memory safe, but will lead
/// to general incorrectness such as panics and wrong results.
///
/// Panics on out-of-bounds accesses.
pub fn read_field(
&self,
store: &mut AutoAssertNoGc,
layout: &GcStructLayout,
ty: &StorageType,
field: usize,
) -> Val {
let offset = layout.fields[field];
let data = store.unwrap_gc_store_mut().gc_object_data(self.as_gc_ref());
match ty {
StorageType::I8 => Val::I32(data.read_u8(offset).into()),
StorageType::I16 => Val::I32(data.read_u16(offset).into()),
StorageType::ValType(ValType::I32) => Val::I32(data.read_i32(offset)),
StorageType::ValType(ValType::I64) => Val::I64(data.read_i64(offset)),
StorageType::ValType(ValType::F32) => Val::F32(data.read_u32(offset)),
StorageType::ValType(ValType::F64) => Val::F64(data.read_u64(offset)),
StorageType::ValType(ValType::V128) => Val::V128(data.read_v128(offset)),
StorageType::ValType(ValType::Ref(r)) => match r.heap_type().top() {
HeapType::Extern => {
let raw = data.read_u32(offset);
Val::ExternRef(ExternRef::_from_raw(store, raw))
}
HeapType::Any => {
let raw = data.read_u32(offset);
Val::AnyRef(AnyRef::_from_raw(store, raw))
}
HeapType::Func => {
let func_ref_id = data.read_u32(offset);
let func_ref_id = FuncRefTableId::from_raw(func_ref_id);
let func_ref = store
.unwrap_gc_store()
.func_ref_table
.get_untyped(func_ref_id);
Val::FuncRef(unsafe {
Func::from_vm_func_ref(
store,
func_ref.map_or(core::ptr::null_mut(), |f| f.as_ptr()),
)
})
}
otherwise => unreachable!("not a top type: {otherwise:?}"),
},
}
}
/// Write the given value into this struct at the given offset.
///
/// Returns an error if `val` is a GC reference that has since been
/// unrooted.
///
/// Does not check that `val` matches `ty`, nor that the field is actually
/// of type `ty`. Checking those things is the caller's responsibility.
/// Failure to do so is memory safe, but will lead to general incorrectness
/// such as panics and wrong results.
///
/// Panics on out-of-bounds accesses.
pub fn write_field(
&self,
store: &mut AutoAssertNoGc,
layout: &GcStructLayout,
ty: &StorageType,
field: usize,
val: Val,
) -> Result<()> {
debug_assert!(val._matches_ty(&store, &ty.unpack())?);
let offset = layout.fields[field];
let mut data = store.gc_store_mut()?.gc_object_data(self.as_gc_ref());
match val {
Val::I32(i) if ty.is_i8() => data.write_i8(offset, truncate_i32_to_i8(i)),
Val::I32(i) if ty.is_i16() => data.write_i16(offset, truncate_i32_to_i16(i)),
Val::I32(i) => data.write_i32(offset, i),
Val::I64(i) => data.write_i64(offset, i),
Val::F32(f) => data.write_u32(offset, f),
Val::F64(f) => data.write_u64(offset, f),
Val::V128(v) => data.write_v128(offset, v),
// For GC-managed references, we need to take care to run the
// appropriate barriers, even when we are writing null references
// into the struct.
//
// POD-read the old value into a local copy, run the GC write
// barrier on that local copy, and then POD-write the updated
// value back into the struct. This avoids transmuting the inner
// data, which would probably be fine, but this approach is
// Obviously Correct and should get us by for now. If LLVM isn't
// able to elide some of these unnecessary copies, and this
// method is ever hot enough, we can always come back and clean
// it up in the future.
Val::ExternRef(e) => {
let raw = data.read_u32(offset);
let mut gc_ref = VMGcRef::from_raw_u32(raw);
let e = match e {
Some(e) => Some(e.try_gc_ref(store)?.unchecked_copy()),
None => None,
};
store.gc_store_mut()?.write_gc_ref(&mut gc_ref, e.as_ref());
let mut data = store.gc_store_mut()?.gc_object_data(self.as_gc_ref());
data.write_u32(offset, gc_ref.map_or(0, |r| r.as_raw_u32()));
}
Val::AnyRef(a) => {
let raw = data.read_u32(offset);
let mut gc_ref = VMGcRef::from_raw_u32(raw);
let a = match a {
Some(a) => Some(a.try_gc_ref(store)?.unchecked_copy()),
None => None,
};
store.gc_store_mut()?.write_gc_ref(&mut gc_ref, a.as_ref());
let mut data = store.gc_store_mut()?.gc_object_data(self.as_gc_ref());
data.write_u32(offset, gc_ref.map_or(0, |r| r.as_raw_u32()));
}
Val::FuncRef(f) => {
let f = f.map(|f| SendSyncPtr::new(f.vm_func_ref(store)));
let id = unsafe { store.gc_store_mut()?.func_ref_table.intern(f) };
store
.gc_store_mut()?
.gc_object_data(self.as_gc_ref())
.write_u32(offset, id.into_raw());
}
}
Ok(())
}
/// Initialize a field in this structref that is currently uninitialized.
///
/// The difference between this method and `write_field` is that GC barriers
/// are handled differently. When overwriting an initialized field (aka
/// `write_field`) we need to call the full write GC write barrier, which
/// logically drops the old GC reference and clones the new GC
/// reference. When we are initializing a field for the first time, there is
/// no old GC reference that is being overwritten and which we need to drop,
/// so we only need to clone the new GC reference.
///
/// Calling this method on a structref that has already had the associated
/// field initialized will result in GC bugs. These are memory safe but will
/// lead to generally incorrect behavior such as panics, leaks, and
/// incorrect results.
///
/// Does not check that `val` matches `ty`, nor that the field is actually
/// of type `ty`. Checking those things is the caller's responsibility.
/// Failure to do so is memory safe, but will lead to general incorrectness
/// such as panics and wrong results.
///
/// Returns an error if `val` is a GC reference that has since been
/// unrooted.
///
/// Panics on out-of-bounds accesses.
pub fn initialize_field(
&self,
store: &mut AutoAssertNoGc,
layout: &GcStructLayout,
ty: &StorageType,
field: usize,
val: Val,
) -> Result<()> {
debug_assert!(val._matches_ty(&store, &ty.unpack())?);
let offset = layout.fields[field];
match val {
Val::I32(i) if ty.is_i8() => store
.gc_store_mut()?
.gc_object_data(self.as_gc_ref())
.write_i8(offset, truncate_i32_to_i8(i)),
Val::I32(i) if ty.is_i16() => store
.gc_store_mut()?
.gc_object_data(self.as_gc_ref())
.write_i16(offset, truncate_i32_to_i16(i)),
Val::I32(i) => store
.gc_store_mut()?
.gc_object_data(self.as_gc_ref())
.write_i32(offset, i),
Val::I64(i) => store
.gc_store_mut()?
.gc_object_data(self.as_gc_ref())
.write_i64(offset, i),
Val::F32(f) => store
.gc_store_mut()?
.gc_object_data(self.as_gc_ref())
.write_u32(offset, f),
Val::F64(f) => store
.gc_store_mut()?
.gc_object_data(self.as_gc_ref())
.write_u64(offset, f),
Val::V128(v) => store
.gc_store_mut()?
.gc_object_data(self.as_gc_ref())
.write_v128(offset, v),
// NB: We don't need to do a write barrier when initializing a
// field, because there is nothing being overwritten. Therefore, we
// just the clone barrier.
Val::ExternRef(x) => {
let x = match x {
None => 0,
Some(x) => x.try_clone_gc_ref(store)?.as_raw_u32(),
};
store
.gc_store_mut()?
.gc_object_data(self.as_gc_ref())
.write_u32(offset, x);
}
Val::AnyRef(x) => {
let x = match x {
None => 0,
Some(x) => x.try_clone_gc_ref(store)?.as_raw_u32(),
};
store
.gc_store_mut()?
.gc_object_data(self.as_gc_ref())
.write_u32(offset, x);
}
Val::FuncRef(f) => {
let f = f.map(|f| SendSyncPtr::new(f.vm_func_ref(store)));
let id = unsafe { store.gc_store_mut()?.func_ref_table.intern(f) };
store
.gc_store_mut()?
.gc_object_data(self.as_gc_ref())
.write_u32(offset, id.into_raw());
}
}
Ok(())
}
}
-130
View File
@@ -1,130 +0,0 @@
// When using _FORTIFY_SOURCE with `longjmp` causes longjmp_chk to be used
// instead. longjmp_chk ensures that the jump target is on the existing stack.
// For our use case of jumping between stacks we need to disable it.
#undef _FORTIFY_SOURCE
#include <setjmp.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#if (defined(__GNUC__) && !defined(__clang__))
#define WASMTIME_GCC 1
#endif
#ifdef CFG_TARGET_OS_windows
// Windows is required to use normal `setjmp` and `longjmp`.
#define platform_setjmp(buf) setjmp(buf)
#define platform_longjmp(buf, arg) longjmp(buf, arg)
typedef jmp_buf platform_jmp_buf;
#elif defined(WASMTIME_GCC) || defined(__x86_64__)
// clang-format off
// GCC and Clang on x86_64 provide `__builtin_setjmp`/`__builtin_longjmp`, which
// differ from plain `setjmp` and `longjmp` in that they're implemented by
// the compiler inline rather than in libc, and the compiler can avoid saving
// and restoring most of the registers. See the [GCC docs] and [clang docs]
// for more information.
//
// Per the caveat in the GCC docs, this assumes that the host compiler (which
// may be compiling for a generic architecture family) knows about all the
// register state that Cranelift (which may be specializing for the hardware at
// runtime) is assuming is callee-saved.
//
// [GCC docs]: https://gcc.gnu.org/onlinedocs/gcc/Nonlocal-Gotos.html
// [clang docs]: https://llvm.org/docs/ExceptionHandling.html#llvm-eh-sjlj-setjmp
// clang-format on
#define platform_setjmp(buf) __builtin_setjmp(buf)
#define platform_longjmp(buf, arg) __builtin_longjmp(buf, arg)
typedef void *platform_jmp_buf[5]; // this is the documented size; see the docs
// links for details.
#else
// All other platforms/compilers funnel in here.
//
// Note that `sigsetjmp` and `siglongjmp` are used here where possible to
// explicitly pass a 0 argument to `sigsetjmp` that we don't need to preserve
// the process signal mask. This should make this call a bit faster b/c it
// doesn't need to touch the kernel signal handling routines.
#define platform_setjmp(buf) sigsetjmp(buf, 0)
#define platform_longjmp(buf, arg) siglongjmp(buf, arg)
typedef sigjmp_buf platform_jmp_buf;
#endif
#define CONCAT2(a, b) a##b
#define CONCAT(a, b) CONCAT2(a, b)
#define VERSIONED_SYMBOL(a) CONCAT(a, VERSIONED_SUFFIX)
int VERSIONED_SYMBOL(wasmtime_setjmp)(void **buf_storage,
void (*body)(void *, void *),
void *payload, void *callee) {
platform_jmp_buf buf;
if (platform_setjmp(buf) != 0) {
return 0;
}
*buf_storage = &buf;
body(payload, callee);
return 1;
}
void VERSIONED_SYMBOL(wasmtime_longjmp)(void *JmpBuf) {
platform_jmp_buf *buf = (platform_jmp_buf *)JmpBuf;
platform_longjmp(*buf, 1);
}
#ifdef CFG_TARGET_OS_windows
// export required for external access.
__declspec(dllexport)
#else
// Note the `weak` linkage here, though, which is intended to let other code
// override this symbol if it's defined elsewhere, since this definition doesn't
// matter.
// Just in case cross-language LTO is enabled we set the `noinline` attribute
// and also try to have some sort of side effect in this function with a dummy
// `asm` statement.
__attribute__((weak, noinline))
#endif
void __jit_debug_register_code() {
#ifndef CFG_TARGET_OS_windows
__asm__("");
#endif
}
struct JITDescriptor {
uint32_t version_;
uint32_t action_flag_;
void *relevant_entry_;
void *first_entry_;
};
#ifdef CFG_TARGET_OS_windows
// export required for external access.
__declspec(dllexport)
#else
// Note the `weak` linkage here which is the same purpose as above. We want to
// let other runtimes be able to override this since our own definition isn't
// important.
__attribute__((weak))
#endif
struct JITDescriptor __jit_debug_descriptor = {1, 0, NULL, NULL};
struct JITDescriptor *VERSIONED_SYMBOL(wasmtime_jit_debug_descriptor)() {
return &__jit_debug_descriptor;
}
// For more information about this see `unix/unwind.rs` and the
// `using_libunwind` function. The basic idea is that weak symbols aren't stable
// in Rust so we use a bit of C to work around that.
#ifndef CFG_TARGET_OS_windows
__attribute__((weak)) extern void __unw_add_dynamic_fde();
bool VERSIONED_SYMBOL(wasmtime_using_libunwind)() {
return __unw_add_dynamic_fde != NULL;
}
#endif
File diff suppressed because it is too large. Load diff
@@ -1,842 +0,0 @@
use crate::prelude::*;
use crate::runtime::vm::const_expr::{ConstEvalContext, ConstExprEvaluator};
use crate::runtime::vm::imports::Imports;
use crate::runtime::vm::instance::{Instance, InstanceHandle};
use crate::runtime::vm::memory::Memory;
use crate::runtime::vm::mpk::ProtectionKey;
use crate::runtime::vm::table::Table;
use crate::runtime::vm::{CompiledModuleId, ModuleRuntimeInfo, VMFuncRef, VMGcRef, VMStore};
use crate::store::AutoAssertNoGc;
use crate::vm::VMGlobalDefinition;
use core::{any::Any, mem, ptr};
use wasmtime_environ::{
DefinedMemoryIndex, DefinedTableIndex, HostPtr, InitMemory, MemoryInitialization,
MemoryInitializer, Module, PrimaryMap, SizeOverflow, TableInitialValue, Trap, Tunables,
VMOffsets, WasmHeapTopType,
};
#[cfg(feature = "gc")]
use crate::runtime::vm::{GcHeap, GcRuntime};
#[cfg(feature = "component-model")]
use wasmtime_environ::{
component::{Component, VMComponentOffsets},
StaticModuleIndex,
};
mod on_demand;
pub use self::on_demand::OnDemandInstanceAllocator;
#[cfg(feature = "pooling-allocator")]
mod pooling;
#[cfg(feature = "pooling-allocator")]
pub use self::pooling::{
InstanceLimits, PoolConcurrencyLimitError, PoolingInstanceAllocator,
PoolingInstanceAllocatorConfig,
};
/// Represents a request for a new runtime instance.
pub struct InstanceAllocationRequest<'a> {
/// The info related to the compiled version of this module,
/// needed for instantiation: function metadata, JIT code
/// addresses, precomputed images for lazy memory and table
/// initialization, and the like. This Arc is cloned and held for
/// the lifetime of the instance.
pub runtime_info: &'a ModuleRuntimeInfo,
/// The imports to use for the instantiation.
pub imports: Imports<'a>,
/// The host state to associate with the instance.
pub host_state: Box<dyn Any + Send + Sync>,
/// A pointer to the "store" for this instance to be allocated. The store
/// correlates with the `Store` in wasmtime itself, and lots of contextual
/// information about the execution of wasm can be learned through the
/// store.
///
/// Note that this is a raw pointer and has a static lifetime, both of which
/// are a bit of a lie. This is done purely so a store can learn about
/// itself when it gets called as a host function, and additionally so this
/// runtime can access internals as necessary (such as the
/// VMExternRefActivationsTable or the resource limiter methods).
///
/// Note that this ends up being a self-pointer to the instance when stored.
/// The reason is that the instance itself is then stored within the store.
/// We use a number of `PhantomPinned` declarations to indicate this to the
/// compiler. More info on this in `wasmtime/src/store.rs`
pub store: StorePtr,
/// Indicates '--wmemcheck' flag.
#[cfg_attr(not(feature = "wmemcheck"), allow(dead_code))]
pub wmemcheck: bool,
/// Request that the instance's memories be protected by a specific
/// protection key.
pub pkey: Option<ProtectionKey>,
/// Tunable configuration options the engine is using.
pub tunables: &'a Tunables,
}
/// A pointer to a Store. This Option<*mut dyn Store> is wrapped in a struct
/// so that the function to create a &mut dyn Store is a method on a member of
/// InstanceAllocationRequest, rather than on a &mut InstanceAllocationRequest
/// itself, because several use-sites require a split mut borrow on the
/// InstanceAllocationRequest.
pub struct StorePtr(Option<*mut dyn VMStore>);
impl StorePtr {
/// A pointer to no Store.
pub fn empty() -> Self {
Self(None)
}
/// A pointer to a Store.
pub fn new(ptr: *mut dyn VMStore) -> Self {
Self(Some(ptr))
}
/// The raw contents of this struct
pub fn as_raw(&self) -> Option<*mut dyn VMStore> {
self.0
}
/// Use the StorePtr as a mut ref to the Store.
///
/// Safety: must not be used outside the original lifetime of the borrow.
pub(crate) unsafe fn get(&mut self) -> Option<&mut dyn VMStore> {
match self.0 {
Some(ptr) => Some(&mut *ptr),
None => None,
}
}
}
/// The index of a memory allocation within an `InstanceAllocator`.
#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord)]
pub struct MemoryAllocationIndex(u32);
impl Default for MemoryAllocationIndex {
fn default() -> Self {
// A default `MemoryAllocationIndex` that can be used with
// `InstanceAllocator`s that don't actually need indices.
MemoryAllocationIndex(u32::MAX)
}
}
impl MemoryAllocationIndex {
/// Get the underlying index of this `MemoryAllocationIndex`.
pub fn index(&self) -> usize {
self.0 as usize
}
}
/// The index of a table allocation within an `InstanceAllocator`.
#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord)]
pub struct TableAllocationIndex(u32);
impl Default for TableAllocationIndex {
fn default() -> Self {
// A default `TableAllocationIndex` that can be used with
// `InstanceAllocator`s that don't actually need indices.
TableAllocationIndex(u32::MAX)
}
}
impl TableAllocationIndex {
/// Get the underlying index of this `TableAllocationIndex`.
pub fn index(&self) -> usize {
self.0 as usize
}
}
/// The index of a table allocation within an `InstanceAllocator`.
#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd, Ord)]
pub struct GcHeapAllocationIndex(u32);
impl Default for GcHeapAllocationIndex {
fn default() -> Self {
// A default `GcHeapAllocationIndex` that can be used with
// `InstanceAllocator`s that don't actually need indices.
GcHeapAllocationIndex(u32::MAX)
}
}
impl GcHeapAllocationIndex {
/// Get the underlying index of this `GcHeapAllocationIndex`.
pub fn index(&self) -> usize {
self.0 as usize
}
}
/// Trait that represents the hooks needed to implement an instance allocator.
///
/// Implement this trait when implementing new instance allocators, but don't
/// use this trait when you need an instance allocator. Instead use the
/// `InstanceAllocator` trait for that, which has additional helper methods and
/// a blanket implementation for all types that implement this trait.
///
/// # Safety
///
/// This trait is unsafe as it requires knowledge of Wasmtime's runtime
/// internals to implement correctly.
pub unsafe trait InstanceAllocatorImpl {
/// Validate whether a component (including all of its contained core
/// modules) is allocatable by this instance allocator.
#[cfg(feature = "component-model")]
fn validate_component_impl<'a>(
&self,
component: &Component,
offsets: &VMComponentOffsets<HostPtr>,
get_module: &'a dyn Fn(StaticModuleIndex) -> &'a Module,
) -> Result<()>;
/// Validate whether a module is allocatable by this instance allocator.
fn validate_module_impl(&self, module: &Module, offsets: &VMOffsets<HostPtr>) -> Result<()>;
/// Increment the count of concurrent component instances that are currently
/// allocated, if applicable.
///
/// Not all instance allocators will have limits for the maximum number of
/// concurrent component instances that can be live at the same time, and
/// these allocators may implement this method with a no-op.
//
// Note: It would be nice to have an associated type that on construction
// does the increment and on drop does the decrement but there are two
// problems with this:
//
// 1. This trait's implementations are always used as trait objects, and
// associated types are not object safe.
//
// 2. We would want a parameterized `Drop` implementation so that we could
// pass in the `InstanceAllocatorImpl` on drop, but this doesn't exist in
// Rust. Therefore, we would be forced to add reference counting and
// stuff like that to keep a handle on the instance allocator from this
// theoretical type. That's a bummer.
fn increment_component_instance_count(&self) -> Result<()>;
/// The dual of `increment_component_instance_count`.
fn decrement_component_instance_count(&self);
/// Increment the count of concurrent core module instances that are
/// currently allocated, if applicable.
///
/// Not all instance allocators will have limits for the maximum number of
/// concurrent core module instances that can be live at the same time, and
/// these allocators may implement this method with a no-op.
fn increment_core_instance_count(&self) -> Result<()>;
/// The dual of `increment_core_instance_count`.
fn decrement_core_instance_count(&self);
/// Allocate a memory for an instance.
///
/// # Unsafety
///
/// The memory and its associated module must have already been validated by
/// `Self::validate_module` and passed that validation.
unsafe fn allocate_memory(
&self,
request: &mut InstanceAllocationRequest,
ty: &wasmtime_environ::Memory,
tunables: &Tunables,
memory_index: DefinedMemoryIndex,
) -> Result<(MemoryAllocationIndex, Memory)>;
/// Deallocate an instance's previously allocated memory.
///
/// # Unsafety
///
/// The memory must have previously been allocated by
/// `Self::allocate_memory`, be at the given index, and must currently be
/// allocated. It must never be used again.
unsafe fn deallocate_memory(
&self,
memory_index: DefinedMemoryIndex,
allocation_index: MemoryAllocationIndex,
memory: Memory,
);
/// Allocate a table for an instance.
///
/// # Unsafety
///
/// The table and its associated module must have already been validated by
/// `Self::validate_module` and passed that validation.
unsafe fn allocate_table(
&self,
req: &mut InstanceAllocationRequest,
table: &wasmtime_environ::Table,
tunables: &Tunables,
table_index: DefinedTableIndex,
) -> Result<(TableAllocationIndex, Table)>;
/// Deallocate an instance's previously allocated table.
///
/// # Unsafety
///
/// The table must have previously been allocated by `Self::allocate_table`,
/// be at the given index, and must currently be allocated. It must never be
/// used again.
unsafe fn deallocate_table(
&self,
table_index: DefinedTableIndex,
allocation_index: TableAllocationIndex,
table: Table,
);
/// Allocates a fiber stack for calling async functions on.
#[cfg(feature = "async")]
fn allocate_fiber_stack(&self) -> Result<wasmtime_fiber::FiberStack>;
/// Deallocates a fiber stack that was previously allocated with
/// `allocate_fiber_stack`.
///
/// # Safety
///
/// The provided stack is required to have been allocated with
/// `allocate_fiber_stack`.
#[cfg(feature = "async")]
unsafe fn deallocate_fiber_stack(&self, stack: wasmtime_fiber::FiberStack);
/// Allocate a GC heap for allocating Wasm GC objects within.
#[cfg(feature = "gc")]
fn allocate_gc_heap(
&self,
gc_runtime: &dyn GcRuntime,
) -> Result<(GcHeapAllocationIndex, Box<dyn GcHeap>)>;
/// Deallocate a GC heap that was previously allocated with
/// `allocate_gc_heap`.
#[cfg(feature = "gc")]
fn deallocate_gc_heap(&self, allocation_index: GcHeapAllocationIndex, gc_heap: Box<dyn GcHeap>);
/// Purges all lingering resources related to `module` from within this
/// allocator.
///
/// Primarily present for the pooling allocator to remove mappings of
/// this module from slots in linear memory.
fn purge_module(&self, module: CompiledModuleId);
/// Use the next available protection key.
///
/// The pooling allocator can use memory protection keys (MPK) for
/// compressing the guard regions protecting against OOB. Each
/// pool-allocated store needs its own key.
fn next_available_pkey(&self) -> Option<ProtectionKey>;
/// Restrict access to memory regions protected by `pkey`.
///
/// This is useful for the pooling allocator, which can use memory
/// protection keys (MPK). Note: this may still allow access to other
/// protection keys, such as the default kernel key; see implementations of
/// this.
fn restrict_to_pkey(&self, pkey: ProtectionKey);
/// Allow access to memory regions protected by any protection key.
fn allow_all_pkeys(&self);
}
/// A thing that can allocate instances.
///
/// Don't implement this trait directly, instead implement
/// `InstanceAllocatorImpl` and you'll get this trait for free via a blanket
/// impl.
pub trait InstanceAllocator: InstanceAllocatorImpl {
/// Validate whether a component (including all of its contained core
/// modules) is allocatable with this instance allocator.
#[cfg(feature = "component-model")]
fn validate_component<'a>(
&self,
component: &Component,
offsets: &VMComponentOffsets<HostPtr>,
get_module: &'a dyn Fn(StaticModuleIndex) -> &'a Module,
) -> Result<()> {
InstanceAllocatorImpl::validate_component_impl(self, component, offsets, get_module)
}
/// Validate whether a core module is allocatable with this instance
/// allocator.
fn validate_module(&self, module: &Module, offsets: &VMOffsets<HostPtr>) -> Result<()> {
InstanceAllocatorImpl::validate_module_impl(self, module, offsets)
}
/// Allocates a fresh `InstanceHandle` for the `req` given.
///
/// This will allocate memories and tables internally from this allocator
/// and weave that altogether into a final and complete `InstanceHandle`
/// ready to be registered with a store.
///
/// Note that the returned instance must still have `.initialize(..)` called
/// on it to complete the instantiation process.
///
/// # Unsafety
///
/// The request's associated module, memories, tables, and vmctx must have
/// already have been validated by `Self::validate_module`.
unsafe fn allocate_module(
&self,
mut request: InstanceAllocationRequest,
) -> Result<InstanceHandle> {
let module = request.runtime_info.env_module();
#[cfg(debug_assertions)]
InstanceAllocatorImpl::validate_module_impl(self, module, request.runtime_info.offsets())
.expect("module should have already been validated before allocation");
self.increment_core_instance_count()?;
let num_defined_memories = module.num_defined_memories();
let mut memories = PrimaryMap::with_capacity(num_defined_memories);
let num_defined_tables = module.num_defined_tables();
let mut tables = PrimaryMap::with_capacity(num_defined_tables);
match (|| {
self.allocate_memories(&mut request, &mut memories)?;
self.allocate_tables(&mut request, &mut tables)?;
Ok(())
})() {
Ok(_) => Ok(Instance::new(request, memories, tables, &module.memories)),
Err(e) => {
self.deallocate_memories(&mut memories);
self.deallocate_tables(&mut tables);
self.decrement_core_instance_count();
Err(e)
}
}
}
/// Deallocates the provided instance.
///
/// This will null-out the pointer within `handle` and otherwise reclaim
/// resources such as tables, memories, and the instance memory itself.
///
/// # Unsafety
///
/// The instance must have previously been allocated by `Self::allocate`.
unsafe fn deallocate_module(&self, handle: &mut InstanceHandle) {
self.deallocate_memories(&mut handle.instance_mut().memories);
self.deallocate_tables(&mut handle.instance_mut().tables);
let layout = Instance::alloc_layout(handle.instance().offsets());
let ptr = handle.instance.take().unwrap();
ptr::drop_in_place(ptr.as_ptr());
alloc::alloc::dealloc(ptr.as_ptr().cast(), layout);
self.decrement_core_instance_count();
}
/// Allocate the memories for the given instance allocation request, pushing
/// them into `memories`.
///
/// # Unsafety
///
/// The request's associated module and memories must have previously been
/// validated by `Self::validate_module`.
unsafe fn allocate_memories(
&self,
request: &mut InstanceAllocationRequest,
memories: &mut PrimaryMap<DefinedMemoryIndex, (MemoryAllocationIndex, Memory)>,
) -> Result<()> {
let module = request.runtime_info.env_module();
#[cfg(debug_assertions)]
InstanceAllocatorImpl::validate_module_impl(self, module, request.runtime_info.offsets())
.expect("module should have already been validated before allocation");
for (memory_index, ty) in module.memories.iter().skip(module.num_imported_memories) {
let memory_index = module
.defined_memory_index(memory_index)
.expect("should be a defined memory since we skipped imported ones");
memories.push(self.allocate_memory(request, ty, request.tunables, memory_index)?);
}
Ok(())
}
/// Deallocate all the memories in the given primary map.
///
/// # Unsafety
///
/// The memories must have previously been allocated by
/// `Self::allocate_memories`.
unsafe fn deallocate_memories(
&self,
memories: &mut PrimaryMap<DefinedMemoryIndex, (MemoryAllocationIndex, Memory)>,
) {
for (memory_index, (allocation_index, memory)) in mem::take(memories) {
// Because deallocating memory is infallible, we don't need to worry
// about leaking subsequent memories if the first memory failed to
// deallocate. If deallocating memory ever becomes fallible, we will
// need to be careful here!
self.deallocate_memory(memory_index, allocation_index, memory);
}
}
/// Allocate tables for the given instance allocation request, pushing them
/// into `tables`.
///
/// # Unsafety
///
/// The request's associated module and tables must have previously been
/// validated by `Self::validate_module`.
unsafe fn allocate_tables(
&self,
request: &mut InstanceAllocationRequest,
tables: &mut PrimaryMap<DefinedTableIndex, (TableAllocationIndex, Table)>,
) -> Result<()> {
let module = request.runtime_info.env_module();
#[cfg(debug_assertions)]
InstanceAllocatorImpl::validate_module_impl(self, module, request.runtime_info.offsets())
.expect("module should have already been validated before allocation");
for (index, table) in module.tables.iter().skip(module.num_imported_tables) {
let def_index = module
.defined_table_index(index)
.expect("should be a defined table since we skipped imported ones");
tables.push(self.allocate_table(request, table, request.tunables, def_index)?);
}
Ok(())
}
/// Deallocate all the tables in the given primary map.
///
/// # Unsafety
///
/// The tables must have previously been allocated by
/// `Self::allocate_tables`.
unsafe fn deallocate_tables(
&self,
tables: &mut PrimaryMap<DefinedTableIndex, (TableAllocationIndex, Table)>,
) {
for (table_index, (allocation_index, table)) in mem::take(tables) {
self.deallocate_table(table_index, allocation_index, table);
}
}
}
// Every `InstanceAllocatorImpl` is an `InstanceAllocator` when used
// correctly. Also, no one is allowed to override this trait's methods, they
// must use the defaults. This blanket impl provides both of those things.
impl<T: InstanceAllocatorImpl> InstanceAllocator for T {}
fn check_table_init_bounds(instance: &mut Instance, module: &Module) -> Result<()> {
let mut const_evaluator = ConstExprEvaluator::default();
for segment in module.table_initialization.segments.iter() {
let table = unsafe { &*instance.get_table(segment.table_index) };
let mut context = ConstEvalContext::new(instance);
let start = unsafe {
const_evaluator
.eval(&mut context, &segment.offset)
.expect("const expression should be valid")
};
let start = usize::try_from(start.get_u32()).unwrap();
let end = start.checked_add(usize::try_from(segment.elements.len()).unwrap());
match end {
Some(end) if end <= table.size() => {
// Initializer is in bounds
}
_ => {
bail!("table out of bounds: elements segment does not fit")
}
}
}
Ok(())
}
fn initialize_tables(
context: &mut ConstEvalContext<'_>,
const_evaluator: &mut ConstExprEvaluator,
module: &Module,
) -> Result<()> {
for (table, init) in module.table_initialization.initial_values.iter() {
match init {
// Tables are always initially null-initialized at this time
TableInitialValue::Null { precomputed: _ } => {}
TableInitialValue::Expr(expr) => {
let raw = unsafe {
const_evaluator
.eval(context, expr)
.expect("const expression should be valid")
};
let idx = module.table_index(table);
let table = unsafe { context.instance.get_defined_table(table).as_mut().unwrap() };
match module.tables[idx].ref_type.heap_type.top() {
WasmHeapTopType::Extern => {
let gc_ref = VMGcRef::from_raw_u32(raw.get_externref());
let gc_store = unsafe { (*context.instance.store()).gc_store_mut()? };
let items = (0..table.size())
.map(|_| gc_ref.as_ref().map(|r| gc_store.clone_gc_ref(r)));
table.init_gc_refs(0, items).err2anyhow()?;
}
WasmHeapTopType::Any => {
let gc_ref = VMGcRef::from_raw_u32(raw.get_anyref());
let gc_store = unsafe { (*context.instance.store()).gc_store_mut()? };
let items = (0..table.size())
.map(|_| gc_ref.as_ref().map(|r| gc_store.clone_gc_ref(r)));
table.init_gc_refs(0, items).err2anyhow()?;
}
WasmHeapTopType::Func => {
let funcref = raw.get_funcref().cast::<VMFuncRef>();
let items = (0..table.size()).map(|_| funcref);
table.init_func(0, items).err2anyhow()?;
}
}
}
}
}
// Note: if the module's table initializer state is in
// FuncTable mode, we will lazily initialize tables based on
// any statically-precomputed image of FuncIndexes, but there
// may still be "leftover segments" that could not be
// incorporated. So we have a unified handler here that
// iterates over all segments (Segments mode) or leftover
// segments (FuncTable mode) to initialize.
for segment in module.table_initialization.segments.iter() {
let start = unsafe {
const_evaluator
.eval(context, &segment.offset)
.expect("const expression should be valid")
};
context
.instance
.table_init_segment(
const_evaluator,
segment.table_index,
&segment.elements,
start.get_u64(),
0,
segment.elements.len(),
)
.err2anyhow()?;
}
Ok(())
}
fn get_memory_init_start(init: &MemoryInitializer, instance: &mut Instance) -> Result<u64> {
let mut context = ConstEvalContext::new(instance);
let mut const_evaluator = ConstExprEvaluator::default();
unsafe { const_evaluator.eval(&mut context, &init.offset) }.map(|v| {
match instance.env_module().memories[init.memory_index].idx_type {
wasmtime_environ::IndexType::I32 => v.get_u32().into(),
wasmtime_environ::IndexType::I64 => v.get_u64(),
}
})
}
fn check_memory_init_bounds(
instance: &mut Instance,
initializers: &[MemoryInitializer],
) -> Result<()> {
for init in initializers {
let memory = instance.get_memory(init.memory_index);
let start = get_memory_init_start(init, instance)?;
let end = usize::try_from(start)
.ok()
.and_then(|start| start.checked_add(init.data.len()));
match end {
Some(end) if end <= memory.current_length() => {
// Initializer is in bounds
}
_ => {
bail!("memory out of bounds: data segment does not fit")
}
}
}
Ok(())
}
fn initialize_memories(
context: &mut ConstEvalContext<'_>,
const_evaluator: &mut ConstExprEvaluator,
module: &Module,
) -> Result<()> {
// Delegates to the `init_memory` method which is sort of a duplicate of
// `instance.memory_init_segment` but is used at compile-time in other
// contexts so is shared here to have only one method of memory
// initialization.
//
// This call to `init_memory` notably implements all the bells and whistles
// so errors only happen if an out-of-bounds segment is found, in which case
// a trap is returned.
struct InitMemoryAtInstantiation<'a, 'b> {
module: &'a Module,
context: &'a mut ConstEvalContext<'b>,
const_evaluator: &'a mut ConstExprEvaluator,
}
impl InitMemory for InitMemoryAtInstantiation<'_, '_> {
fn memory_size_in_bytes(
&mut self,
memory: wasmtime_environ::MemoryIndex,
) -> Result<u64, SizeOverflow> {
let len = self.context.instance.get_memory(memory).current_length();
let len = u64::try_from(len).unwrap();
Ok(len)
}
fn eval_offset(
&mut self,
memory: wasmtime_environ::MemoryIndex,
expr: &wasmtime_environ::ConstExpr,
) -> Option<u64> {
let val = unsafe { self.const_evaluator.eval(self.context, expr) }
.expect("const expression should be valid");
Some(
match self.context.instance.env_module().memories[memory].idx_type {
wasmtime_environ::IndexType::I32 => val.get_u32().into(),
wasmtime_environ::IndexType::I64 => val.get_u64(),
},
)
}
fn write(
&mut self,
memory_index: wasmtime_environ::MemoryIndex,
init: &wasmtime_environ::StaticMemoryInitializer,
) -> bool {
// If this initializer applies to a defined memory but that memory
// doesn't need initialization, due to something like copy-on-write
// pre-initializing it via mmap magic, then this initializer can be
// skipped entirely.
if let Some(memory_index) = self.module.defined_memory_index(memory_index) {
if !self.context.instance.memories[memory_index].1.needs_init() {
return true;
}
}
let memory = self.context.instance.get_memory(memory_index);
unsafe {
let src = self.context.instance.wasm_data(init.data.clone());
let offset = usize::try_from(init.offset).unwrap();
let dst = memory.base.add(offset);
assert!(offset + src.len() <= memory.current_length());
// FIXME audit whether this is safe in the presence of shared
// memory
// (https://github.com/bytecodealliance/wasmtime/issues/4203).
ptr::copy_nonoverlapping(src.as_ptr(), dst, src.len())
}
true
}
}
let ok = module
.memory_initialization
.init_memory(&mut InitMemoryAtInstantiation {
module,
context,
const_evaluator,
});
if !ok {
return Err(Trap::MemoryOutOfBounds).err2anyhow();
}
Ok(())
}
fn check_init_bounds(instance: &mut Instance, module: &Module) -> Result<()> {
check_table_init_bounds(instance, module)?;
match &module.memory_initialization {
MemoryInitialization::Segmented(initializers) => {
check_memory_init_bounds(instance, initializers)?;
}
// Statically validated already to have everything in-bounds.
MemoryInitialization::Static { .. } => {}
}
Ok(())
}
fn initialize_globals(
context: &mut ConstEvalContext<'_>,
const_evaluator: &mut ConstExprEvaluator,
module: &Module,
) -> Result<()> {
assert!(core::ptr::eq(&**context.instance.env_module(), module));
for (index, init) in module.global_initializers.iter() {
let raw = unsafe {
const_evaluator
.eval(context, init)
.expect("should be a valid const expr")
};
let to = context.instance.global_ptr(index);
let wasm_ty = module.globals[module.global_index(index)].wasm_ty;
#[cfg(feature = "wmemcheck")]
if index.as_bits() == 0 && wasm_ty == wasmtime_environ::WasmValType::I32 {
if let Some(wmemcheck) = &mut context.instance.wmemcheck_state {
let size = usize::try_from(raw.get_i32()).unwrap();
wmemcheck.set_stack_size(size);
}
}
let store = unsafe { (*context.instance.store()).store_opaque_mut() };
let mut store = AutoAssertNoGc::new(store);
// This write is safe because we know we have the correct module for
// this instance and its vmctx due to the assert above.
unsafe {
ptr::write(
to,
VMGlobalDefinition::from_val_raw(&mut store, wasm_ty, raw)?,
)
};
}
Ok(())
}
pub(super) fn initialize_instance(
instance: &mut Instance,
module: &Module,
is_bulk_memory: bool,
) -> Result<()> {
// If bulk memory is not enabled, bounds check the data and element segments before
// making any changes. With bulk memory enabled, initializers are processed
// in-order and side effects are observed up to the point of an out-of-bounds
// initializer, so the early checking is not desired.
if !is_bulk_memory {
check_init_bounds(instance, module)?;
}
let mut context = ConstEvalContext::new(instance);
let mut const_evaluator = ConstExprEvaluator::default();
initialize_globals(&mut context, &mut const_evaluator, module)?;
initialize_tables(&mut context, &mut const_evaluator, module)?;
initialize_memories(&mut context, &mut const_evaluator, &module)?;
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn allocator_traits_are_object_safe() {
fn _instance_allocator(_: &dyn InstanceAllocatorImpl) {}
fn _instance_allocator_ext(_: &dyn InstanceAllocator) {}
}
}
@@ -1,797 +0,0 @@
//! Implements the pooling instance allocator.
//!
//! The pooling instance allocator maps memory in advance and allocates
//! instances, memories, tables, and stacks from a pool of available resources.
//! Using the pooling instance allocator can speed up module instantiation when
//! modules can be constrained based on configurable limits
//! ([`InstanceLimits`]). Each new instance is stored in a "slot"; as instances
//! are allocated and freed, these slots are either filled or emptied:
//!
//! ```text
//! ┌──────┬──────┬──────┬──────┬──────┐
//! │Slot 0│Slot 1│Slot 2│Slot 3│......│
//! └──────┴──────┴──────┴──────┴──────┘
//! ```
//!
//! Each slot has a "slot ID"--an index into the pool. Slot IDs are handed out
//! by the [`index_allocator`] module. Note that each kind of pool-allocated
//! item is stored in its own separate pool: [`memory_pool`], [`table_pool`],
//! [`stack_pool`]. See those modules for more details.
mod decommit_queue;
mod index_allocator;
mod memory_pool;
mod table_pool;
#[cfg(feature = "gc")]
mod gc_heap_pool;
#[cfg(all(feature = "async"))]
mod generic_stack_pool;
#[cfg(all(feature = "async", unix, not(miri)))]
mod unix_stack_pool;
#[cfg(all(feature = "async"))]
cfg_if::cfg_if! {
if #[cfg(all(unix, not(miri), not(asan)))] {
use unix_stack_pool as stack_pool;
} else {
use generic_stack_pool as stack_pool;
}
}
use self::decommit_queue::DecommitQueue;
use self::memory_pool::MemoryPool;
use self::table_pool::TablePool;
use super::{
InstanceAllocationRequest, InstanceAllocatorImpl, MemoryAllocationIndex, TableAllocationIndex,
};
use crate::prelude::*;
use crate::runtime::vm::{
instance::Instance,
mpk::{self, MpkEnabled, ProtectionKey, ProtectionMask},
CompiledModuleId, Memory, Table,
};
use std::borrow::Cow;
use std::fmt::Display;
use std::sync::{Mutex, MutexGuard};
use std::{
mem,
sync::atomic::{AtomicU64, Ordering},
};
use wasmtime_environ::{
DefinedMemoryIndex, DefinedTableIndex, HostPtr, Module, Tunables, VMOffsets,
};
#[cfg(feature = "gc")]
use super::GcHeapAllocationIndex;
#[cfg(feature = "gc")]
use crate::runtime::vm::{GcHeap, GcRuntime};
#[cfg(feature = "gc")]
use gc_heap_pool::GcHeapPool;
#[cfg(feature = "async")]
use stack_pool::StackPool;
#[cfg(feature = "component-model")]
use wasmtime_environ::{
component::{Component, VMComponentOffsets},
StaticModuleIndex,
};
fn round_up_to_pow2(n: usize, to: usize) -> usize {
debug_assert!(to > 0);
debug_assert!(to.is_power_of_two());
(n + to - 1) & !(to - 1)
}
/// Instance-related limit configuration for pooling.
///
/// More docs on this can be found at `wasmtime::PoolingAllocationConfig`.
#[derive(Debug, Copy, Clone)]
pub struct InstanceLimits {
/// The maximum number of component instances that may be allocated
/// concurrently.
pub total_component_instances: u32,
/// The maximum size of a component's `VMComponentContext`, not including
/// any of its inner core modules' `VMContext` sizes.
pub component_instance_size: usize,
/// The maximum number of core module instances that may be allocated
/// concurrently.
pub total_core_instances: u32,
/// The maximum number of core module instances that a single component may
/// transitively contain.
pub max_core_instances_per_component: u32,
/// The maximum number of Wasm linear memories that a component may
/// transitively contain.
pub max_memories_per_component: u32,
/// The maximum number of tables that a component may transitively contain.
pub max_tables_per_component: u32,
/// The total number of linear memories in the pool, across all instances.
pub total_memories: u32,
/// The total number of tables in the pool, across all instances.
pub total_tables: u32,
/// The total number of async stacks in the pool, across all instances.
#[cfg(feature = "async")]
pub total_stacks: u32,
/// Maximum size of a core instance's `VMContext`.
pub core_instance_size: usize,
/// Maximum number of tables per instance.
pub max_tables_per_module: u32,
/// Maximum number of table elements per table.
pub table_elements: usize,
/// Maximum number of linear memories per instance.
pub max_memories_per_module: u32,
/// Maximum byte size of a linear memory, must be smaller than
/// `static_memory_reservation` in `Tunables`.
pub max_memory_size: usize,
/// The total number of GC heaps in the pool, across all instances.
#[cfg(feature = "gc")]
pub total_gc_heaps: u32,
}
impl Default for InstanceLimits {
fn default() -> Self {
// See doc comments for `wasmtime::PoolingAllocationConfig` for these
// default values
Self {
total_component_instances: 1000,
component_instance_size: 1 << 20, // 1 MiB
total_core_instances: 1000,
max_core_instances_per_component: u32::MAX,
max_memories_per_component: u32::MAX,
max_tables_per_component: u32::MAX,
total_memories: 1000,
total_tables: 1000,
#[cfg(feature = "async")]
total_stacks: 1000,
core_instance_size: 1 << 20, // 1 MiB
max_tables_per_module: 1,
// NB: in #8504 it was seen that a C# module in debug module can
// have 10k+ elements.
table_elements: 20_000,
max_memories_per_module: 1,
#[cfg(target_pointer_width = "64")]
max_memory_size: 1 << 32, // 4G,
#[cfg(target_pointer_width = "32")]
max_memory_size: usize::MAX,
#[cfg(feature = "gc")]
total_gc_heaps: 1000,
}
}
}
/// Configuration options for the pooling instance allocator supplied at
/// construction.
#[derive(Copy, Clone, Debug)]
pub struct PoolingInstanceAllocatorConfig {
/// See `PoolingAllocatorConfig::max_unused_warm_slots` in `wasmtime`
pub max_unused_warm_slots: u32,
/// The target number of decommits to do per batch. This is not precise, as
/// we can queue up decommits at times when we aren't prepared to
/// immediately flush them, and so we may go over this target size
/// occasionally.
pub decommit_batch_size: usize,
/// The size, in bytes, of async stacks to allocate (not including the guard
/// page).
pub stack_size: usize,
/// The limits to apply to instances allocated within this allocator.
pub limits: InstanceLimits,
/// Whether or not async stacks are zeroed after use.
pub async_stack_zeroing: bool,
/// If async stack zeroing is enabled and the host platform is Linux this is
/// how much memory to zero out with `memset`.
///
/// The rest of memory will be zeroed out with `madvise`.
pub async_stack_keep_resident: usize,
/// How much linear memory, in bytes, to keep resident after resetting for
/// use with the next instance. This much memory will be `memset` to zero
/// when a linear memory is deallocated.
///
/// Memory exceeding this amount in the wasm linear memory will be released
/// with `madvise` back to the kernel.
///
/// Only applicable on Linux.
pub linear_memory_keep_resident: usize,
/// Same as `linear_memory_keep_resident` but for tables.
pub table_keep_resident: usize,
/// Whether to enable memory protection keys.
pub memory_protection_keys: MpkEnabled,
/// How many memory protection keys to allocate.
pub max_memory_protection_keys: usize,
}
impl Default for PoolingInstanceAllocatorConfig {
fn default() -> PoolingInstanceAllocatorConfig {
PoolingInstanceAllocatorConfig {
max_unused_warm_slots: 100,
decommit_batch_size: 1,
stack_size: 2 << 20,
limits: InstanceLimits::default(),
async_stack_zeroing: false,
async_stack_keep_resident: 0,
linear_memory_keep_resident: 0,
table_keep_resident: 0,
memory_protection_keys: MpkEnabled::Disable,
max_memory_protection_keys: 16,
}
}
}
/// An error returned when the pooling allocator cannot allocate a table,
/// memory, etc... because the maximum number of concurrent allocations for that
/// entity has been reached.
#[derive(Debug)]
pub struct PoolConcurrencyLimitError {
limit: usize,
kind: Cow<'static, str>,
}
impl std::error::Error for PoolConcurrencyLimitError {}
impl Display for PoolConcurrencyLimitError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let limit = self.limit;
let kind = &self.kind;
write!(f, "maximum concurrent limit of {limit} for {kind} reached")
}
}
impl PoolConcurrencyLimitError {
fn new(limit: usize, kind: impl Into<Cow<'static, str>>) -> Self {
Self {
limit,
kind: kind.into(),
}
}
}
/// Implements the pooling instance allocator.
///
/// This allocator internally maintains pools of instances, memories, tables,
/// and stacks.
///
/// Note: the resource pools are manually dropped so that the fault handler
/// terminates correctly.
#[derive(Debug)]
pub struct PoolingInstanceAllocator {
decommit_batch_size: usize,
limits: InstanceLimits,
// The number of live core module and component instances at any given
// time. Note that this can temporarily go over the configured limit. This
// doesn't mean we have actually overshot, but that we attempted to allocate
// a new instance and incremented the counter, we've seen (or are about to
// see) that the counter is beyond the configured threshold, and are going
// to decrement the counter and return an error but haven't done so yet. See
// the increment trait methods for more details.
live_core_instances: AtomicU64,
live_component_instances: AtomicU64,
decommit_queue: Mutex<DecommitQueue>,
memories: MemoryPool,
tables: TablePool,
#[cfg(feature = "gc")]
gc_heaps: GcHeapPool,
#[cfg(feature = "async")]
stacks: StackPool,
}
#[cfg(debug_assertions)]
impl Drop for PoolingInstanceAllocator {
fn drop(&mut self) {
// NB: when cfg(not(debug_assertions)) it is okay that we don't flush
// the queue, as the sub-pools will unmap those ranges anyways, so
// there's no point in decommitting them. But we do need to flush the
// queue when debug assertions are enabled to make sure that all
// entities get returned to their associated sub-pools and we can
// differentiate between a leaking slot and an enqueued-for-decommit
// slot.
let queue = self.decommit_queue.lock().unwrap();
self.flush_decommit_queue(queue);
debug_assert_eq!(self.live_component_instances.load(Ordering::Acquire), 0);
debug_assert_eq!(self.live_core_instances.load(Ordering::Acquire), 0);
debug_assert!(self.memories.is_empty());
debug_assert!(self.tables.is_empty());
#[cfg(feature = "gc")]
debug_assert!(self.gc_heaps.is_empty());
#[cfg(feature = "async")]
debug_assert!(self.stacks.is_empty());
}
}
impl PoolingInstanceAllocator {
/// Creates a new pooling instance allocator with the given strategy and limits.
pub fn new(config: &PoolingInstanceAllocatorConfig, tunables: &Tunables) -> Result<Self> {
Ok(Self {
decommit_batch_size: config.decommit_batch_size,
limits: config.limits,
live_component_instances: AtomicU64::new(0),
live_core_instances: AtomicU64::new(0),
decommit_queue: Mutex::new(DecommitQueue::default()),
memories: MemoryPool::new(config, tunables)?,
tables: TablePool::new(config)?,
#[cfg(feature = "gc")]
gc_heaps: GcHeapPool::new(config)?,
#[cfg(feature = "async")]
stacks: StackPool::new(config)?,
})
}
fn core_instance_size(&self) -> usize {
round_up_to_pow2(self.limits.core_instance_size, mem::align_of::<Instance>())
}
fn validate_table_plans(&self, module: &Module) -> Result<()> {
self.tables.validate(module)
}
fn validate_memory_plans(&self, module: &Module) -> Result<()> {
self.memories.validate(module)
}
fn validate_core_instance_size(&self, offsets: &VMOffsets<HostPtr>) -> Result<()> {
let layout = Instance::alloc_layout(offsets);
if layout.size() <= self.core_instance_size() {
return Ok(());
}
// If this `module` exceeds the allocation size allotted to it then an
// error will be reported here. The error of "required N bytes but
// cannot allocate that" is pretty opaque, however, because it's not
// clear what the breakdown of the N bytes are and what to optimize
// next. To help provide a better error message here some fancy-ish
// logic is done here to report the breakdown of the byte request into
// the largest portions and where it's coming from.
let mut message = format!(
"instance allocation for this module \
requires {} bytes which exceeds the configured maximum \
of {} bytes; breakdown of allocation requirement:\n\n",
layout.size(),
self.core_instance_size(),
);
let mut remaining = layout.size();
let mut push = |name: &str, bytes: usize| {
assert!(remaining >= bytes);
remaining -= bytes;
// If the `name` region is more than 5% of the allocation request
// then report it here, otherwise ignore it. We have less than 20
// fields so we're guaranteed that something should be reported, and
// otherwise it's not particularly interesting to learn about 5
// different fields that are all 8 or 0 bytes. Only try to report
// the "major" sources of bytes here.
if bytes > layout.size() / 20 {
message.push_str(&format!(
" * {:.02}% - {} bytes - {}\n",
((bytes as f32) / (layout.size() as f32)) * 100.0,
bytes,
name,
));
}
};
// The `Instance` itself requires some size allocated to it.
push("instance state management", mem::size_of::<Instance>());
// Afterwards the `VMContext`'s regions are why we're requesting bytes,
// so ask it for descriptions on each region's byte size.
for (desc, size) in offsets.region_sizes() {
push(desc, size as usize);
}
// double-check we accounted for all the bytes
assert_eq!(remaining, 0);
bail!("{}", message)
}
#[cfg(feature = "component-model")]
fn validate_component_instance_size(
&self,
offsets: &VMComponentOffsets<HostPtr>,
) -> Result<()> {
if usize::try_from(offsets.size_of_vmctx()).unwrap() <= self.limits.component_instance_size
{
return Ok(());
}
// TODO: Add context with detailed accounting of what makes up all the
// `VMComponentContext`'s space like we do for module instances.
bail!(
"instance allocation for this component requires {} bytes of `VMComponentContext` \
space which exceeds the configured maximum of {} bytes",
offsets.size_of_vmctx(),
self.limits.component_instance_size
)
}
fn flush_decommit_queue(&self, mut locked_queue: MutexGuard<'_, DecommitQueue>) -> bool {
// Take the queue out of the mutex and drop the lock, to minimize
// contention.
let queue = mem::take(&mut *locked_queue);
drop(locked_queue);
queue.flush(self)
}
/// Execute `f` and if it returns `Err(PoolConcurrencyLimitError)`, then try
/// flushing the decommit queue. If flushing the queue freed up slots, then
/// try running `f` again.
fn with_flush_and_retry<T>(&self, mut f: impl FnMut() -> Result<T>) -> Result<T> {
f().or_else(|e| {
if e.is::<PoolConcurrencyLimitError>() {
let queue = self.decommit_queue.lock().unwrap();
if self.flush_decommit_queue(queue) {
return f();
}
}
Err(e)
})
}
fn merge_or_flush(&self, mut local_queue: DecommitQueue) {
match local_queue.raw_len() {
// If we didn't enqueue any regions for decommit, then we must have
// either memset the whole entity or eagerly remapped it to zero
// because we don't have linux's `madvise(DONTNEED)` semantics. In
// either case, the entity slot is ready for reuse immediately.
0 => {
local_queue.flush(self);
}
// We enqueued at least our batch size of regions for decommit, so
// flush the local queue immediately. Don't bother inspecting (or
// locking!) the shared queue.
n if n >= self.decommit_batch_size => {
local_queue.flush(self);
}
// If we enqueued some regions for decommit, but did not reach our
// batch size, so we don't want to flush it yet, then merge the
// local queue into the shared queue.
n => {
debug_assert!(n < self.decommit_batch_size);
let mut shared_queue = self.decommit_queue.lock().unwrap();
shared_queue.append(&mut local_queue);
// And if the shared queue now has at least as many regions
// enqueued for decommit as our batch size, then we can flush
// it.
if shared_queue.raw_len() >= self.decommit_batch_size {
self.flush_decommit_queue(shared_queue);
}
}
}
}
}
unsafe impl InstanceAllocatorImpl for PoolingInstanceAllocator {
#[cfg(feature = "component-model")]
fn validate_component_impl<'a>(
&self,
component: &Component,
offsets: &VMComponentOffsets<HostPtr>,
get_module: &'a dyn Fn(StaticModuleIndex) -> &'a Module,
) -> Result<()> {
self.validate_component_instance_size(offsets)?;
let mut num_core_instances = 0;
let mut num_memories = 0;
let mut num_tables = 0;
for init in &component.initializers {
use wasmtime_environ::component::GlobalInitializer::*;
use wasmtime_environ::component::InstantiateModule;
match init {
InstantiateModule(InstantiateModule::Import(_, _)) => {
num_core_instances += 1;
// Can't statically account for the total vmctx size, number
// of memories, and number of tables in this component.
}
InstantiateModule(InstantiateModule::Static(static_module_index, _)) => {
let module = get_module(*static_module_index);
let offsets = VMOffsets::new(HostPtr, &module);
self.validate_module_impl(module, &offsets)?;
num_core_instances += 1;
num_memories += module.num_defined_memories();
num_tables += module.num_defined_tables();
}
LowerImport { .. }
| ExtractMemory(_)
| ExtractRealloc(_)
| ExtractPostReturn(_)
| Resource(_) => {}
}
}
if num_core_instances
> usize::try_from(self.limits.max_core_instances_per_component).unwrap()
{
bail!(
"The component transitively contains {num_core_instances} core module instances, \
which exceeds the configured maximum of {}",
self.limits.max_core_instances_per_component
);
}
if num_memories > usize::try_from(self.limits.max_memories_per_component).unwrap() {
bail!(
"The component transitively contains {num_memories} Wasm linear memories, which \
exceeds the configured maximum of {}",
self.limits.max_memories_per_component
);
}
if num_tables > usize::try_from(self.limits.max_tables_per_component).unwrap() {
bail!(
"The component transitively contains {num_tables} tables, which exceeds the \
configured maximum of {}",
self.limits.max_tables_per_component
);
}
Ok(())
}
fn validate_module_impl(&self, module: &Module, offsets: &VMOffsets<HostPtr>) -> Result<()> {
self.validate_memory_plans(module)?;
self.validate_table_plans(module)?;
self.validate_core_instance_size(offsets)?;
Ok(())
}
fn increment_component_instance_count(&self) -> Result<()> {
let old_count = self.live_component_instances.fetch_add(1, Ordering::AcqRel);
if old_count >= u64::from(self.limits.total_component_instances) {
self.decrement_component_instance_count();
return Err(PoolConcurrencyLimitError::new(
usize::try_from(self.limits.total_component_instances).unwrap(),
"component instances",
)
.into());
}
Ok(())
}
fn decrement_component_instance_count(&self) {
self.live_component_instances.fetch_sub(1, Ordering::AcqRel);
}
fn increment_core_instance_count(&self) -> Result<()> {
let old_count = self.live_core_instances.fetch_add(1, Ordering::AcqRel);
if old_count >= u64::from(self.limits.total_core_instances) {
self.decrement_core_instance_count();
return Err(PoolConcurrencyLimitError::new(
usize::try_from(self.limits.total_core_instances).unwrap(),
"core instances",
)
.into());
}
Ok(())
}
fn decrement_core_instance_count(&self) {
self.live_core_instances.fetch_sub(1, Ordering::AcqRel);
}
unsafe fn allocate_memory(
&self,
request: &mut InstanceAllocationRequest,
ty: &wasmtime_environ::Memory,
tunables: &Tunables,
memory_index: DefinedMemoryIndex,
) -> Result<(MemoryAllocationIndex, Memory)> {
self.with_flush_and_retry(|| self.memories.allocate(request, ty, tunables, memory_index))
}
unsafe fn deallocate_memory(
&self,
_memory_index: DefinedMemoryIndex,
allocation_index: MemoryAllocationIndex,
memory: Memory,
) {
// Reset the image slot. If there is any error clearing the
// image, just drop it here, and let the drop handler for the
// slot unmap in a way that retains the address space
// reservation.
let mut image = memory.unwrap_static_image();
let mut queue = DecommitQueue::default();
image
.clear_and_remain_ready(self.memories.keep_resident, |ptr, len| {
queue.push_raw(ptr, len);
})
.expect("failed to reset memory image");
queue.push_memory(allocation_index, image);
self.merge_or_flush(queue);
}
unsafe fn allocate_table(
&self,
request: &mut InstanceAllocationRequest,
ty: &wasmtime_environ::Table,
tunables: &Tunables,
_table_index: DefinedTableIndex,
) -> Result<(super::TableAllocationIndex, Table)> {
self.with_flush_and_retry(|| self.tables.allocate(request, ty, tunables))
}
unsafe fn deallocate_table(
&self,
_table_index: DefinedTableIndex,
allocation_index: TableAllocationIndex,
mut table: Table,
) {
let mut queue = DecommitQueue::default();
self.tables
.reset_table_pages_to_zero(allocation_index, &mut table, |ptr, len| {
queue.push_raw(ptr, len);
});
queue.push_table(allocation_index, table);
self.merge_or_flush(queue);
}
#[cfg(feature = "async")]
fn allocate_fiber_stack(&self) -> Result<wasmtime_fiber::FiberStack> {
self.with_flush_and_retry(|| self.stacks.allocate())
}
#[cfg(feature = "async")]
unsafe fn deallocate_fiber_stack(&self, mut stack: wasmtime_fiber::FiberStack) {
let mut queue = DecommitQueue::default();
self.stacks
.zero_stack(&mut stack, |ptr, len| queue.push_raw(ptr, len));
queue.push_stack(stack);
self.merge_or_flush(queue);
}
fn purge_module(&self, module: CompiledModuleId) {
self.memories.purge_module(module);
}
fn next_available_pkey(&self) -> Option<ProtectionKey> {
self.memories.next_available_pkey()
}
fn restrict_to_pkey(&self, pkey: ProtectionKey) {
mpk::allow(ProtectionMask::zero().or(pkey));
}
fn allow_all_pkeys(&self) {
mpk::allow(ProtectionMask::all());
}
#[cfg(feature = "gc")]
fn allocate_gc_heap(
&self,
gc_runtime: &dyn GcRuntime,
) -> Result<(GcHeapAllocationIndex, Box<dyn GcHeap>)> {
self.gc_heaps.allocate(gc_runtime)
}
#[cfg(feature = "gc")]
fn deallocate_gc_heap(
&self,
allocation_index: GcHeapAllocationIndex,
gc_heap: Box<dyn GcHeap>,
) {
self.gc_heaps.deallocate(allocation_index, gc_heap);
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_pooling_allocator_with_memory_pages_exceeded() {
let config = PoolingInstanceAllocatorConfig {
limits: InstanceLimits {
total_memories: 1,
max_memory_size: 0x100010000,
..Default::default()
},
..PoolingInstanceAllocatorConfig::default()
};
assert_eq!(
PoolingInstanceAllocator::new(
&config,
&Tunables {
static_memory_reservation: 0x10000,
..Tunables::default_host()
},
)
.map_err(|e| e.to_string())
.expect_err("expected a failure constructing instance allocator"),
"maximum memory size of 0x100010000 bytes exceeds the configured \
static memory reservation of 0x10000 bytes"
);
}
#[cfg(all(unix, target_pointer_width = "64", feature = "async", not(miri)))]
#[test]
fn test_stack_zeroed() -> Result<()> {
let config = PoolingInstanceAllocatorConfig {
max_unused_warm_slots: 0,
limits: InstanceLimits {
total_stacks: 1,
total_memories: 0,
total_tables: 0,
..Default::default()
},
stack_size: 128,
async_stack_zeroing: true,
..PoolingInstanceAllocatorConfig::default()
};
let allocator = PoolingInstanceAllocator::new(&config, &Tunables::default_host())?;
unsafe {
for _ in 0..255 {
let stack = allocator.allocate_fiber_stack()?;
// The stack pointer is at the top, so decrement it first
let addr = stack.top().unwrap().sub(1);
assert_eq!(*addr, 0);
*addr = 1;
allocator.deallocate_fiber_stack(stack);
}
}
Ok(())
}
#[cfg(all(unix, target_pointer_width = "64", feature = "async", not(miri)))]
#[test]
fn test_stack_unzeroed() -> Result<()> {
let config = PoolingInstanceAllocatorConfig {
max_unused_warm_slots: 0,
limits: InstanceLimits {
total_stacks: 1,
total_memories: 0,
total_tables: 0,
..Default::default()
},
stack_size: 128,
async_stack_zeroing: false,
..PoolingInstanceAllocatorConfig::default()
};
let allocator = PoolingInstanceAllocator::new(&config, &Tunables::default_host())?;
unsafe {
for i in 0..255 {
let stack = allocator.allocate_fiber_stack()?;
// The stack pointer is at the top, so decrement it first
let addr = stack.top().unwrap().sub(1);
assert_eq!(*addr, i);
*addr = i + 1;
allocator.deallocate_fiber_stack(stack);
}
}
Ok(())
}
}
@@ -1,95 +0,0 @@
use super::index_allocator::{SimpleIndexAllocator, SlotId};
use super::GcHeapAllocationIndex;
use crate::prelude::*;
use crate::runtime::vm::{GcHeap, GcRuntime, PoolingInstanceAllocatorConfig, Result};
use std::sync::Mutex;
/// A pool of reusable GC heaps.
pub struct GcHeapPool {
max_gc_heaps: usize,
index_allocator: SimpleIndexAllocator,
heaps: Mutex<Vec<Option<Box<dyn GcHeap>>>>,
}
impl std::fmt::Debug for GcHeapPool {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("GcHeapPool")
.field("max_gc_heaps", &self.max_gc_heaps)
.field("index_allocator", &self.index_allocator)
.field("heaps", &"..")
.finish()
}
}
impl GcHeapPool {
/// Create a new `GcHeapPool` with the given configuration.
pub fn new(config: &PoolingInstanceAllocatorConfig) -> Result<Self> {
let index_allocator = SimpleIndexAllocator::new(config.limits.total_gc_heaps);
let max_gc_heaps = usize::try_from(config.limits.total_gc_heaps).unwrap();
// Each individual GC heap in the pool is lazily allocated. See the
// `allocate` method.
let heaps = Mutex::new((0..max_gc_heaps).map(|_| None).collect());
Ok(Self {
max_gc_heaps,
index_allocator,
heaps,
})
}
/// Are there zero slots in use right now?
#[allow(unused)] // some cfgs don't use this
pub fn is_empty(&self) -> bool {
self.index_allocator.is_empty()
}
/// Allocate a single table for the given instance allocation request.
pub fn allocate(
&self,
gc_runtime: &dyn GcRuntime,
) -> Result<(GcHeapAllocationIndex, Box<dyn GcHeap>)> {
let allocation_index = self
.index_allocator
.alloc()
.map(|slot| GcHeapAllocationIndex(slot.0))
.ok_or_else(|| {
anyhow!(
"maximum concurrent GC heap limit of {} reached",
self.max_gc_heaps
)
})?;
debug_assert_ne!(allocation_index, GcHeapAllocationIndex::default());
let heap = match {
let mut heaps = self.heaps.lock().unwrap();
heaps[allocation_index.index()].take()
} {
// If we already have a heap at this slot, reuse it.
Some(heap) => heap,
// Otherwise, we haven't forced this slot's lazily allocated heap
// yet. So do that now.
None => gc_runtime.new_gc_heap()?,
};
Ok((allocation_index, heap))
}
/// Deallocate a previously-allocated GC heap.
pub fn deallocate(&self, allocation_index: GcHeapAllocationIndex, mut heap: Box<dyn GcHeap>) {
debug_assert_ne!(allocation_index, GcHeapAllocationIndex::default());
heap.reset();
// NB: Replace the heap before freeing the index. If we did it in the
// opposite order, a concurrent allocation request could reallocate the
// index before we have replaced the heap.
{
let mut heaps = self.heaps.lock().unwrap();
let old_entry = std::mem::replace(&mut heaps[allocation_index.index()], Some(heap));
debug_assert!(old_entry.is_none());
}
self.index_allocator.free(SlotId(allocation_index.0));
}
}
@@ -1,241 +0,0 @@
use super::{
index_allocator::{SimpleIndexAllocator, SlotId},
round_up_to_pow2, TableAllocationIndex,
};
use crate::prelude::*;
use crate::runtime::vm::{
InstanceAllocationRequest, Mmap, PoolingInstanceAllocatorConfig, SendSyncPtr, Table,
};
use crate::{runtime::vm::sys::vm::commit_pages, vm::round_usize_up_to_host_pages};
use std::mem;
use std::ptr::NonNull;
use wasmtime_environ::{Module, Tunables};
/// Represents a pool of WebAssembly tables.
///
/// Each instance index into the pool returns an iterator over the base addresses
/// of the instance's tables.
#[derive(Debug)]
pub struct TablePool {
index_allocator: SimpleIndexAllocator,
mapping: Mmap,
table_size: usize,
max_total_tables: usize,
tables_per_instance: usize,
page_size: usize,
keep_resident: usize,
table_elements: usize,
}
impl TablePool {
/// Create a new `TablePool`.
pub fn new(config: &PoolingInstanceAllocatorConfig) -> Result<Self> {
let page_size = crate::runtime::vm::host_page_size();
let table_size = round_up_to_pow2(
mem::size_of::<*mut u8>()
.checked_mul(config.limits.table_elements)
.ok_or_else(|| anyhow!("table size exceeds addressable memory"))?,
page_size,
);
let max_total_tables = usize::try_from(config.limits.total_tables).unwrap();
let tables_per_instance = usize::try_from(config.limits.max_tables_per_module).unwrap();
let allocation_size = table_size
.checked_mul(max_total_tables)
.ok_or_else(|| anyhow!("total size of tables exceeds addressable memory"))?;
let mapping = Mmap::accessible_reserved(allocation_size, allocation_size)
.context("failed to create table pool mapping")?;
Ok(Self {
index_allocator: SimpleIndexAllocator::new(config.limits.total_tables),
mapping,
table_size,
max_total_tables,
tables_per_instance,
page_size,
keep_resident: round_usize_up_to_host_pages(config.table_keep_resident)?,
table_elements: usize::try_from(config.limits.table_elements).unwrap(),
})
}
/// Validate whether this module's tables are allocatable by this pool.
pub fn validate(&self, module: &Module) -> Result<()> {
let tables = module.num_defined_tables();
if tables > usize::try_from(self.tables_per_instance).unwrap() {
bail!(
"defined tables count of {} exceeds the per-instance limit of {}",
tables,
self.tables_per_instance,
);
}
if tables > self.max_total_tables {
bail!(
"defined tables count of {} exceeds the total tables limit of {}",
tables,
self.max_total_tables,
);
}
for (i, table) in module.tables.iter().skip(module.num_imported_tables) {
if table.limits.min > u64::try_from(self.table_elements)? {
bail!(
"table index {} has a minimum element size of {} which exceeds the limit of {}",
i.as_u32(),
table.limits.min,
self.table_elements,
);
}
}
Ok(())
}
/// Are there zero slots in use right now?
#[allow(unused)] // some cfgs don't use this
pub fn is_empty(&self) -> bool {
self.index_allocator.is_empty()
}
/// Get the base pointer of the given table allocation.
fn get(&self, table_index: TableAllocationIndex) -> *mut u8 {
assert!(table_index.index() < self.max_total_tables);
unsafe {
self.mapping
.as_ptr()
.add(table_index.index() * self.table_size)
.cast_mut()
}
}
/// Allocate a single table for the given instance allocation request.
pub fn allocate(
&self,
request: &mut InstanceAllocationRequest,
ty: &wasmtime_environ::Table,
tunables: &Tunables,
) -> Result<(TableAllocationIndex, Table)> {
let allocation_index = self
.index_allocator
.alloc()
.map(|slot| TableAllocationIndex(slot.0))
.ok_or_else(|| {
super::PoolConcurrencyLimitError::new(self.max_total_tables, "tables")
})?;
match (|| {
let base = self.get(allocation_index);
unsafe {
commit_pages(base, self.table_elements * mem::size_of::<*mut u8>())?;
}
let ptr = NonNull::new(std::ptr::slice_from_raw_parts_mut(
base.cast(),
self.table_elements * mem::size_of::<*mut u8>(),
))
.unwrap();
unsafe {
Table::new_static(
ty,
tunables,
SendSyncPtr::new(ptr),
&mut *request.store.get().unwrap(),
)
}
})() {
Ok(table) => Ok((allocation_index, table)),
Err(e) => {
self.index_allocator.free(SlotId(allocation_index.0));
Err(e)
}
}
}
/// Deallocate a previously-allocated table.
///
/// # Safety
///
/// The table must have been previously-allocated by this pool and assigned
/// the given allocation index, it must currently be allocated, and it must
/// never be used again.
///
/// The caller must have already called `reset_table_pages_to_zero` on the
/// memory and flushed any enqueued decommits for this table's memory.
pub unsafe fn deallocate(&self, allocation_index: TableAllocationIndex, table: Table) {
assert!(table.is_static());
drop(table);
self.index_allocator.free(SlotId(allocation_index.0));
}
/// Reset the given table's memory to zero.
///
/// Invokes the given `decommit` function for each region of memory that
/// needs to be decommitted. It is the caller's responsibility to actually
/// perform that decommit before this table is reused.
///
/// # Safety
///
/// This table must not be in active use, and ready for returning to the
/// table pool once it is zeroed and decommitted.
pub unsafe fn reset_table_pages_to_zero(
&self,
allocation_index: TableAllocationIndex,
table: &mut Table,
mut decommit: impl FnMut(*mut u8, usize),
) {
assert!(table.is_static());
let base = self.get(allocation_index);
let size = round_up_to_pow2(table.size() * mem::size_of::<*mut u8>(), self.page_size);
// `memset` the first `keep_resident` bytes.
let size_to_memset = size.min(self.keep_resident);
std::ptr::write_bytes(base, 0, size_to_memset);
// And decommit the rest of it.
decommit(base.add(size_to_memset), size - size_to_memset)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::runtime::vm::InstanceLimits;
#[cfg(target_pointer_width = "64")]
#[test]
fn test_table_pool() -> Result<()> {
let pool = TablePool::new(&PoolingInstanceAllocatorConfig {
limits: InstanceLimits {
total_tables: 7,
table_elements: 100,
max_memory_size: 0,
max_memories_per_module: 0,
..Default::default()
},
..Default::default()
})?;
let host_page_size = crate::runtime::vm::host_page_size();
assert_eq!(pool.table_size, host_page_size);
assert_eq!(pool.max_total_tables, 7);
assert_eq!(pool.page_size, host_page_size);
assert_eq!(pool.table_elements, 100);
let base = pool.mapping.as_ptr() as usize;
for i in 0..7 {
let index = TableAllocationIndex(i);
let ptr = pool.get(index);
assert_eq!(ptr as usize - base, i as usize * pool.table_size);
}
Ok(())
}
}
File diff suppressed because it is too large. Load diff
-822
View File
@@ -1,822 +0,0 @@
//! Memory management for linear memories.
//!
//! `RuntimeLinearMemory` is to WebAssembly linear memories what `Table` is to WebAssembly tables.
use crate::prelude::*;
use crate::runtime::vm::mmap::Mmap;
use crate::runtime::vm::vmcontext::VMMemoryDefinition;
use crate::runtime::vm::{
round_usize_up_to_host_pages, usize_is_multiple_of_host_page_size, MemoryImage,
MemoryImageSlot, SendSyncPtr, SharedMemory, VMStore, WaitResult,
};
use alloc::sync::Arc;
use core::ops::Range;
use core::ptr::NonNull;
use core::time::Duration;
use wasmtime_environ::{MemoryStyle, Trap, Tunables};
/// A memory allocator
pub trait RuntimeMemoryCreator: Send + Sync {
/// Create new RuntimeLinearMemory
fn new_memory(
&self,
ty: &wasmtime_environ::Memory,
tunables: &Tunables,
minimum: usize,
maximum: Option<usize>,
// Optionally, a memory image for CoW backing.
memory_image: Option<&Arc<MemoryImage>>,
) -> Result<Box<dyn RuntimeLinearMemory>>;
}
/// A default memory allocator used by Wasmtime
pub struct DefaultMemoryCreator;
impl RuntimeMemoryCreator for DefaultMemoryCreator {
/// Create new MmapMemory
fn new_memory(
&self,
ty: &wasmtime_environ::Memory,
tunables: &Tunables,
minimum: usize,
maximum: Option<usize>,
memory_image: Option<&Arc<MemoryImage>>,
) -> Result<Box<dyn RuntimeLinearMemory>> {
Ok(Box::new(MmapMemory::new(
ty,
tunables,
minimum,
maximum,
memory_image,
)?))
}
}
/// A linear memory and its backing storage.
pub trait RuntimeLinearMemory: Send + Sync {
/// Returns the log2 of this memory's page size, in bytes.
fn page_size_log2(&self) -> u8;
/// Returns this memory's page size, in bytes.
fn page_size(&self) -> u64 {
let log2 = self.page_size_log2();
debug_assert!(log2 == 16 || log2 == 0);
1 << self.page_size_log2()
}
/// Returns the number of allocated bytes.
fn byte_size(&self) -> usize;
/// Returns the maximum number of bytes the memory can grow to.
/// Returns `None` if the memory is unbounded.
fn maximum_byte_size(&self) -> Option<usize>;
/// Grows a memory by `delta_pages`.
///
/// This performs the necessary checks on the growth before delegating to
/// the underlying `grow_to` implementation. A default implementation of
/// this memory is provided here since this is assumed to be the same for
/// most kinds of memory; one exception is shared memory, which must perform
/// all the steps of the default implementation *plus* the required locking.
///
/// The `store` is used only for error reporting.
fn grow(
&mut self,
delta_pages: u64,
mut store: Option<&mut dyn VMStore>,
) -> Result<Option<(usize, usize)>, Error> {
let old_byte_size = self.byte_size();
// Wasm spec: when growing by 0 pages, always return the current size.
if delta_pages == 0 {
return Ok(Some((old_byte_size, old_byte_size)));
}
let page_size = usize::try_from(self.page_size()).unwrap();
// The largest wasm-page-aligned region of memory is possible to
// represent in a `usize`. This will be impossible for the system to
// actually allocate.
let absolute_max = 0usize.wrapping_sub(page_size);
// Calculate the byte size of the new allocation. Let it overflow up to
// `usize::MAX`, then clamp it down to `absolute_max`.
let new_byte_size = usize::try_from(delta_pages)
.unwrap_or(usize::MAX)
.saturating_mul(page_size)
.saturating_add(old_byte_size)
.min(absolute_max);
let maximum = self.maximum_byte_size();
// Store limiter gets first chance to reject memory_growing.
if let Some(store) = &mut store {
if !store.memory_growing(old_byte_size, new_byte_size, maximum)? {
return Ok(None);
}
}
// Never exceed maximum, even if limiter permitted it.
if let Some(max) = maximum {
if new_byte_size > max {
if let Some(store) = store {
// FIXME: shared memories may not have an associated store
// to report the growth failure to but the error should not
// be dropped
// (https://github.com/bytecodealliance/wasmtime/issues/4240).
store.memory_grow_failed(format_err!("Memory maximum size exceeded"))?;
}
return Ok(None);
}
}
match self.grow_to(new_byte_size) {
Ok(_) => Ok(Some((old_byte_size, new_byte_size))),
Err(e) => {
// FIXME: shared memories may not have an associated store to
// report the growth failure to but the error should not be
// dropped
// (https://github.com/bytecodealliance/wasmtime/issues/4240).
if let Some(store) = store {
store.memory_grow_failed(e)?;
}
Ok(None)
}
}
}
/// Grow memory to the specified amount of bytes.
///
/// Returns an error if memory can't be grown by the specified amount
/// of bytes.
fn grow_to(&mut self, size: usize) -> Result<()>;
/// Return a `VMMemoryDefinition` for exposing the memory to compiled wasm
/// code.
fn vmmemory(&mut self) -> VMMemoryDefinition;
/// Does this memory need initialization? It may not if it already
/// has initial contents courtesy of the `MemoryImage` passed to
/// `RuntimeMemoryCreator::new_memory()`.
fn needs_init(&self) -> bool;
/// Used for optional dynamic downcasting.
fn as_any_mut(&mut self) -> &mut dyn core::any::Any;
/// Returns the range of addresses that may be reached by WebAssembly.
///
/// This starts at the base of linear memory and ends at the end of the
/// guard pages, if any.
fn wasm_accessible(&self) -> Range<usize>;
}
/// A linear memory instance.
#[derive(Debug)]
pub struct MmapMemory {
// The underlying allocation.
mmap: Mmap,
// The current length of this Wasm memory, in bytes.
//
// This region starts at `pre_guard_size` offset from the base of `mmap`. It
// is always accessible, which means that if the Wasm page size is smaller
// than the host page size, there may be some trailing region in the `mmap`
// that is accessible but should not be accessed. (We rely on explicit
// bounds checks in the compiled code to protect this region.)
len: usize,
// The optional maximum accessible size, in bytes, for this linear memory.
//
// Note that this maximum does not factor in guard pages, so this isn't the
// maximum size of the linear address space reservation for this memory.
//
// This is *not* always a multiple of the host page size, and
// `self.accessible()` may go past `self.maximum` when Wasm is using a small
// custom page size due to `self.accessible()`'s rounding up to the host
// page size.
maximum: Option<usize>,
// The log2 of this Wasm memory's page size, in bytes.
page_size_log2: u8,
// The amount of extra bytes to reserve whenever memory grows. This is
// specified so that the cost of repeated growth is amortized.
extra_to_reserve_on_growth: usize,
// Size in bytes of extra guard pages before the start and after the end to
// optimize loads and stores with constant offsets.
pre_guard_size: usize,
offset_guard_size: usize,
// An optional CoW mapping that provides the initial content of this
// MmapMemory, if mapped.
memory_image: Option<MemoryImageSlot>,
}
impl MmapMemory {
/// Create a new linear memory instance with specified minimum and maximum
/// number of wasm pages.
pub fn new(
ty: &wasmtime_environ::Memory,
tunables: &Tunables,
minimum: usize,
mut maximum: Option<usize>,
memory_image: Option<&Arc<MemoryImage>>,
) -> Result<Self> {
let (style, offset_guard_size) = MemoryStyle::for_memory(*ty, tunables);
// It's a programmer error for these two configuration values to exceed
// the host available address space, so panic if such a configuration is
// found (mostly an issue for hypothetical 32-bit hosts).
let offset_guard_bytes = usize::try_from(offset_guard_size).unwrap();
let pre_guard_bytes = if tunables.guard_before_linear_memory {
offset_guard_bytes
} else {
0
};
// Ensure that our guard regions are multiples of the host page size.
let offset_guard_bytes = round_usize_up_to_host_pages(offset_guard_bytes)?;
let pre_guard_bytes = round_usize_up_to_host_pages(pre_guard_bytes)?;
let (alloc_bytes, extra_to_reserve_on_growth) = match style {
// Dynamic memories start with the minimum size plus the `reserve`
// amount specified to grow into.
MemoryStyle::Dynamic { reserve } => (
round_usize_up_to_host_pages(minimum)?,
round_usize_up_to_host_pages(usize::try_from(reserve).unwrap())?,
),
// Static memories will never move in memory and consequently get
// their entire allocation up-front with no extra room to grow into.
// Note that the `maximum` is adjusted here to whatever the smaller
// of the two is, the `maximum` given or the `bound` specified for
// this memory.
MemoryStyle::Static { byte_reservation } => {
assert!(byte_reservation >= ty.minimum_byte_size().unwrap());
let bound_bytes = usize::try_from(byte_reservation).unwrap();
let bound_bytes = round_usize_up_to_host_pages(bound_bytes)?;
maximum = Some(bound_bytes.min(maximum.unwrap_or(usize::MAX)));
(bound_bytes, 0)
}
};
assert!(usize_is_multiple_of_host_page_size(alloc_bytes));
let request_bytes = pre_guard_bytes
.checked_add(alloc_bytes)
.and_then(|i| i.checked_add(extra_to_reserve_on_growth))
.and_then(|i| i.checked_add(offset_guard_bytes))
.ok_or_else(|| format_err!("cannot allocate {} with guard regions", minimum))?;
assert!(usize_is_multiple_of_host_page_size(request_bytes));
let mut mmap = Mmap::accessible_reserved(0, request_bytes)?;
if minimum > 0 {
let accessible = round_usize_up_to_host_pages(minimum)?;
mmap.make_accessible(pre_guard_bytes, accessible)?;
}
// If a memory image was specified, try to create the MemoryImageSlot on
// top of our mmap.
let memory_image = match memory_image {
Some(image) => {
let base = unsafe { mmap.as_mut_ptr().add(pre_guard_bytes) };
let mut slot = MemoryImageSlot::create(
base.cast(),
minimum,
alloc_bytes + extra_to_reserve_on_growth,
);
slot.instantiate(minimum, Some(image), ty, tunables)?;
// On drop, we will unmap our mmap'd range that this slot was
// mapped on top of, so there is no need for the slot to wipe
// it with an anonymous mapping first.
slot.no_clear_on_drop();
Some(slot)
}
None => None,
};
Ok(Self {
mmap,
len: minimum,
maximum,
page_size_log2: ty.page_size_log2,
pre_guard_size: pre_guard_bytes,
offset_guard_size: offset_guard_bytes,
extra_to_reserve_on_growth,
memory_image,
})
}
/// Get the length of the accessible portion of the underlying `mmap`. This
/// is the same region as `self.len` but rounded up to a multiple of the
/// host page size.
fn accessible(&self) -> usize {
let accessible =
round_usize_up_to_host_pages(self.len).expect("accessible region always fits in usize");
debug_assert!(accessible <= self.mmap.len() - self.offset_guard_size - self.pre_guard_size);
accessible
}
}
impl RuntimeLinearMemory for MmapMemory {
fn page_size_log2(&self) -> u8 {
self.page_size_log2
}
fn byte_size(&self) -> usize {
self.len
}
fn maximum_byte_size(&self) -> Option<usize> {
self.maximum
}
fn grow_to(&mut self, new_size: usize) -> Result<()> {
assert!(usize_is_multiple_of_host_page_size(self.offset_guard_size));
assert!(usize_is_multiple_of_host_page_size(self.pre_guard_size));
assert!(usize_is_multiple_of_host_page_size(self.mmap.len()));
let new_accessible = round_usize_up_to_host_pages(new_size)?;
if new_accessible > self.mmap.len() - self.offset_guard_size - self.pre_guard_size {
// If the new size of this heap exceeds the current size of the
// allocation we have, then this must be a dynamic heap. Use
// `new_size` to calculate a new size of an allocation, allocate it,
// and then copy over the memory from before.
let request_bytes = self
.pre_guard_size
.checked_add(new_accessible)
.and_then(|s| s.checked_add(self.extra_to_reserve_on_growth))
.and_then(|s| s.checked_add(self.offset_guard_size))
.ok_or_else(|| format_err!("overflow calculating size of memory allocation"))?;
assert!(usize_is_multiple_of_host_page_size(request_bytes));
let mut new_mmap = Mmap::accessible_reserved(0, request_bytes)?;
new_mmap.make_accessible(self.pre_guard_size, new_accessible)?;
// This method has an exclusive reference to `self.mmap` and just
// created `new_mmap` so it should be safe to acquire references
// into both of them and copy between them.
unsafe {
let range = self.pre_guard_size..self.pre_guard_size + self.len;
let src = self.mmap.slice(range.clone());
let dst = new_mmap.slice_mut(range);
dst.copy_from_slice(src);
}
// Now drop the MemoryImageSlot, if any. We've lost the CoW
// advantages by explicitly copying all data, but we have
// preserved all of its content; so we no longer need the
// mapping. We need to do this before we (implicitly) drop the
// `mmap` field by overwriting it below.
drop(self.memory_image.take());
self.mmap = new_mmap;
} else if let Some(image) = self.memory_image.as_mut() {
// MemoryImageSlot has its own growth mechanisms; defer to its
// implementation.
image.set_heap_limit(new_size)?;
} else {
// If the new size of this heap fits within the existing allocation
// then all we need to do is to make the new pages accessible. This
// can happen either for "static" heaps which always hit this case,
// or "dynamic" heaps which have some space reserved after the
// initial allocation to grow into before the heap is moved in
// memory.
assert!(new_size > self.len);
assert!(self.maximum.map_or(true, |max| new_size <= max));
assert!(new_size <= self.mmap.len() - self.offset_guard_size - self.pre_guard_size);
let new_accessible = round_usize_up_to_host_pages(new_size)?;
assert!(
new_accessible <= self.mmap.len() - self.offset_guard_size - self.pre_guard_size,
);
// If the Wasm memory's page size is smaller than the host's page
// size, then we might not need to actually change permissions,
// since we are forced to round our accessible range up to the
// host's page size.
if new_accessible > self.accessible() {
self.mmap.make_accessible(
self.pre_guard_size + self.accessible(),
new_accessible - self.accessible(),
)?;
}
}
self.len = new_size;
Ok(())
}
fn vmmemory(&mut self) -> VMMemoryDefinition {
VMMemoryDefinition {
base: unsafe { self.mmap.as_mut_ptr().add(self.pre_guard_size) },
current_length: self.len.into(),
}
}
fn needs_init(&self) -> bool {
// If we're using a CoW mapping, then no initialization
// is needed.
self.memory_image.is_none()
}
fn as_any_mut(&mut self) -> &mut dyn core::any::Any {
self
}
fn wasm_accessible(&self) -> Range<usize> {
let base = self.mmap.as_ptr() as usize + self.pre_guard_size;
let end = base + (self.mmap.len() - self.pre_guard_size);
base..end
}
}
/// A "static" memory where the lifetime of the backing memory is managed
/// elsewhere. Currently used with the pooling allocator.
struct StaticMemory {
/// The base pointer of this static memory, wrapped up in a send/sync
/// wrapper.
base: SendSyncPtr<u8>,
/// The byte capacity of the `base` pointer.
capacity: usize,
/// The current size, in bytes, of this memory.
size: usize,
/// The log2 of this memory's page size.
page_size_log2: u8,
/// The size, in bytes, of the virtual address allocation starting at `base`
/// and going to the end of the guard pages at the end of the linear memory.
memory_and_guard_size: usize,
/// The image management, if any, for this memory. Owned here and
/// returned to the pooling allocator when termination occurs.
memory_image: MemoryImageSlot,
}
impl StaticMemory {
fn new(
base_ptr: *mut u8,
base_capacity: usize,
initial_size: usize,
maximum_size: Option<usize>,
page_size_log2: u8,
memory_image: MemoryImageSlot,
memory_and_guard_size: usize,
) -> Result<Self> {
if base_capacity < initial_size {
bail!(
"initial memory size of {} exceeds the pooling allocator's \
configured maximum memory size of {} bytes",
initial_size,
base_capacity,
);
}
// Only use the part of the slice that is necessary.
let base_capacity = match maximum_size {
Some(max) if max < base_capacity => max,
_ => base_capacity,
};
Ok(Self {
base: SendSyncPtr::new(NonNull::new(base_ptr).unwrap()),
capacity: base_capacity,
size: initial_size,
page_size_log2,
memory_image,
memory_and_guard_size,
})
}
}
impl RuntimeLinearMemory for StaticMemory {
fn page_size_log2(&self) -> u8 {
self.page_size_log2
}
fn byte_size(&self) -> usize {
self.size
}
fn maximum_byte_size(&self) -> Option<usize> {
Some(self.capacity)
}
fn grow_to(&mut self, new_byte_size: usize) -> Result<()> {
// Never exceed the static memory size; this check should have been made
// prior to arriving here.
assert!(new_byte_size <= self.capacity);
self.memory_image.set_heap_limit(new_byte_size)?;
// Update our accounting of the available size.
self.size = new_byte_size;
Ok(())
}
fn vmmemory(&mut self) -> VMMemoryDefinition {
VMMemoryDefinition {
base: self.base.as_ptr(),
current_length: self.size.into(),
}
}
fn needs_init(&self) -> bool {
!self.memory_image.has_image()
}
fn as_any_mut(&mut self) -> &mut dyn core::any::Any {
self
}
fn wasm_accessible(&self) -> Range<usize> {
let base = self.base.as_ptr() as usize;
let end = base + self.memory_and_guard_size;
base..end
}
}
/// Representation of a runtime wasm linear memory.
pub struct Memory(pub(crate) Box<dyn RuntimeLinearMemory>);
impl Memory {
/// Create a new dynamic (movable) memory instance for the specified plan.
pub fn new_dynamic(
ty: &wasmtime_environ::Memory,
tunables: &Tunables,
creator: &dyn RuntimeMemoryCreator,
store: &mut dyn VMStore,
memory_image: Option<&Arc<MemoryImage>>,
) -> Result<Self> {
let (minimum, maximum) = Self::limit_new(ty, Some(store))?;
let allocation = creator.new_memory(ty, tunables, minimum, maximum, memory_image)?;
let allocation = if ty.shared {
Box::new(SharedMemory::wrap(ty, tunables, allocation)?)
} else {
allocation
};
Ok(Memory(allocation))
}
/// Create a new static (immovable) memory instance for the specified plan.
pub fn new_static(
ty: &wasmtime_environ::Memory,
base_ptr: *mut u8,
base_capacity: usize,
memory_image: MemoryImageSlot,
memory_and_guard_size: usize,
store: &mut dyn VMStore,
) -> Result<Self> {
let (minimum, maximum) = Self::limit_new(ty, Some(store))?;
let pooled_memory = StaticMemory::new(
base_ptr,
base_capacity,
minimum,
maximum,
ty.page_size_log2,
memory_image,
memory_and_guard_size,
)?;
let allocation = Box::new(pooled_memory);
let allocation: Box<dyn RuntimeLinearMemory> = if ty.shared {
// FIXME(#4244): not supported with the pooling allocator (which
// `new_static` is always used with), see `MemoryPool::validate` as
// well).
todo!("using shared memory with the pooling allocator is a work in progress");
} else {
allocation
};
Ok(Memory(allocation))
}
/// Calls the `store`'s limiter to optionally prevent a memory from being allocated.
///
/// Returns a tuple of the minimum size, optional maximum size, and log(page
/// size) of the memory, all in bytes.
pub(crate) fn limit_new(
ty: &wasmtime_environ::Memory,
store: Option<&mut dyn VMStore>,
) -> Result<(usize, Option<usize>)> {
let page_size = usize::try_from(ty.page_size()).unwrap();
// This is the absolute possible maximum that the module can try to
// allocate, which is our entire address space minus a wasm page. That
// shouldn't ever actually work in terms of an allocation because
// presumably the kernel wants *something* for itself, but this is used
// to pass to the `store`'s limiter for a requested size
// to approximate the scale of the request that the wasm module is
// making. This is necessary because the limiter works on `usize` bytes
// whereas we're working with possibly-overflowing `u64` calculations
// here. To actually faithfully represent the byte requests of modules
// we'd have to represent things as `u128`, but that's kinda
// overkill for this purpose.
let absolute_max = 0usize.wrapping_sub(page_size);
// Sanity-check what should already be true from wasm module validation.
if let Ok(size) = ty.minimum_byte_size() {
assert!(size <= u64::try_from(absolute_max).unwrap());
}
if let Ok(max) = ty.maximum_byte_size() {
assert!(max <= u64::try_from(absolute_max).unwrap());
}
// If the minimum memory size overflows the size of our own address
// space, then we can't satisfy this request, but defer the error to
// later so the `store` can be informed that an effective oom is
// happening.
let minimum = ty
.minimum_byte_size()
.ok()
.and_then(|m| usize::try_from(m).ok());
// The plan stores the maximum size in units of wasm pages, but we
// use units of bytes. Unlike for the `minimum` size we silently clamp
// the effective maximum size to the limits of what we can track. If the
// maximum size exceeds `usize` or `u64` then there's no need to further
// keep track of it as some sort of runtime limit will kick in long
// before we reach the statically declared maximum size.
let maximum = ty
.maximum_byte_size()
.ok()
.and_then(|m| usize::try_from(m).ok());
// Inform the store's limiter what's about to happen. This will let the
// limiter reject anything if necessary, and this also guarantees that
// we should call the limiter for all requested memories, even if our
// `minimum` calculation overflowed. This means that the `minimum` we're
// informing the limiter is lossy and may not be 100% accurate, but for
// now the expected uses of limiter means that's ok.
if let Some(store) = store {
// We ignore the store limits for shared memories since they are
// technically not created within a store (though, trickily, they
// may be associated with one in order to get a `vmctx`).
if !ty.shared {
if !store.memory_growing(0, minimum.unwrap_or(absolute_max), maximum)? {
bail!(
"memory minimum size of {} pages exceeds memory limits",
ty.limits.min
);
}
}
}
// At this point we need to actually handle overflows, so bail out with
// an error if we made it this far.
let minimum = minimum.ok_or_else(|| {
format_err!(
"memory minimum size of {} pages exceeds memory limits",
ty.limits.min
)
})?;
Ok((minimum, maximum))
}
/// Returns this memory's page size, in bytes.
pub fn page_size(&self) -> u64 {
self.0.page_size()
}
/// Returns the number of allocated wasm pages.
pub fn byte_size(&self) -> usize {
self.0.byte_size()
}
/// Returns whether or not this memory needs initialization. It
/// may not if it already has initial content thanks to a CoW
/// mechanism.
pub(crate) fn needs_init(&self) -> bool {
self.0.needs_init()
}
/// Grow memory by the specified amount of wasm pages.
///
/// Returns `None` if memory can't be grown by the specified amount
/// of wasm pages. Returns `Some` with the old size of memory, in bytes, on
/// successful growth.
///
/// # Safety
///
/// Resizing the memory can reallocate the memory buffer for dynamic memories.
/// An instance's `VMContext` may have pointers to the memory's base and will
/// need to be fixed up after growing the memory.
///
/// Generally, prefer using `InstanceHandle::memory_grow`, which encapsulates
/// this unsafety.
///
/// Ensure that the provided Store is not used to get access any Memory
/// which lives inside it.
pub unsafe fn grow(
&mut self,
delta_pages: u64,
store: Option<&mut dyn VMStore>,
) -> Result<Option<usize>, Error> {
self.0
.grow(delta_pages, store)
.map(|opt| opt.map(|(old, _new)| old))
}
/// Return a `VMMemoryDefinition` for exposing the memory to compiled wasm code.
pub fn vmmemory(&mut self) -> VMMemoryDefinition {
self.0.vmmemory()
}
/// Consume the memory, returning its [`MemoryImageSlot`] if any is present.
/// The image should only be present for a subset of memories created with
/// [`Memory::new_static()`].
#[cfg(feature = "pooling-allocator")]
pub fn unwrap_static_image(mut self) -> MemoryImageSlot {
let mem = self.0.as_any_mut().downcast_mut::<StaticMemory>().unwrap();
core::mem::replace(&mut mem.memory_image, MemoryImageSlot::dummy())
}
/// If the [Memory] is a [SharedMemory], unwrap it and return a clone to
/// that shared memory.
pub fn as_shared_memory(&mut self) -> Option<&mut SharedMemory> {
let as_any = self.0.as_any_mut();
if let Some(m) = as_any.downcast_mut::<SharedMemory>() {
Some(m)
} else {
None
}
}
/// Implementation of `memory.atomic.notify` for all memories.
pub fn atomic_notify(&mut self, addr: u64, count: u32) -> Result<u32, Trap> {
match self.0.as_any_mut().downcast_mut::<SharedMemory>() {
Some(m) => m.atomic_notify(addr, count),
None => {
validate_atomic_addr(&self.vmmemory(), addr, 4, 4)?;
Ok(0)
}
}
}
/// Implementation of `memory.atomic.wait32` for all memories.
pub fn atomic_wait32(
&mut self,
addr: u64,
expected: u32,
timeout: Option<Duration>,
) -> Result<WaitResult, Trap> {
match self.0.as_any_mut().downcast_mut::<SharedMemory>() {
Some(m) => m.atomic_wait32(addr, expected, timeout),
None => {
validate_atomic_addr(&self.vmmemory(), addr, 4, 4)?;
Err(Trap::AtomicWaitNonSharedMemory)
}
}
}
/// Implementation of `memory.atomic.wait64` for all memories.
pub fn atomic_wait64(
&mut self,
addr: u64,
expected: u64,
timeout: Option<Duration>,
) -> Result<WaitResult, Trap> {
match self.0.as_any_mut().downcast_mut::<SharedMemory>() {
Some(m) => m.atomic_wait64(addr, expected, timeout),
None => {
validate_atomic_addr(&self.vmmemory(), addr, 8, 8)?;
Err(Trap::AtomicWaitNonSharedMemory)
}
}
}
/// Returns the range of bytes that WebAssembly should be able to address in
/// this linear memory. Note that this includes guard pages which wasm can
/// hit.
pub fn wasm_accessible(&self) -> Range<usize> {
self.0.wasm_accessible()
}
}
/// In the configurations where bounds checks were elided in JIT code (because
/// we are using static memories with virtual memory guard pages) this manual
/// check is here so we don't segfault from Rust. For other configurations,
/// these checks are required anyways.
pub fn validate_atomic_addr(
def: &VMMemoryDefinition,
addr: u64,
access_size: u64,
access_alignment: u64,
) -> Result<*mut u8, Trap> {
debug_assert!(access_alignment.is_power_of_two());
if !(addr % access_alignment == 0) {
return Err(Trap::HeapMisaligned);
}
let length = u64::try_from(def.current_length()).unwrap();
if !(addr.saturating_add(access_size) < length) {
return Err(Trap::MemoryOutOfBounds);
}
let addr = usize::try_from(addr).unwrap();
Ok(def.base.wrapping_add(addr))
}
-217
View File
@@ -1,217 +0,0 @@
//! Low-level abstraction for allocating and managing zero-filled pages
//! of memory.
use crate::runtime::vm::sys::mmap;
use crate::{prelude::*, vm::usize_is_multiple_of_host_page_size};
use core::ops::Range;
#[cfg(feature = "std")]
use std::{fs::File, path::Path, sync::Arc};
/// A simple struct consisting of a page-aligned pointer to page-aligned
/// and initially-zeroed memory and a length.
#[derive(Debug)]
pub struct Mmap {
sys: mmap::Mmap,
#[cfg(feature = "std")]
file: Option<Arc<File>>,
}
impl Mmap {
/// Create a new `Mmap` pointing to at least `size` bytes of page-aligned
/// accessible memory.
pub fn with_at_least(size: usize) -> Result<Self> {
let rounded_size = crate::runtime::vm::round_usize_up_to_host_pages(size)?;
Self::accessible_reserved(rounded_size, rounded_size)
}
/// Creates a new `Mmap` by opening the file located at `path` and mapping
/// it into memory.
///
/// The memory is mapped in read-only mode for the entire file. If portions
/// of the file need to be modified then the `region` crate can be use to
/// alter permissions of each page.
///
/// The memory mapping and the length of the file within the mapping are
/// returned.
#[cfg(feature = "std")]
pub fn from_file(path: &Path) -> Result<Self> {
let (sys, file) = mmap::Mmap::from_file(path)?;
Ok(Mmap {
sys,
file: Some(Arc::new(file)),
})
}
/// Create a new `Mmap` pointing to `accessible_size` bytes of page-aligned
/// accessible memory, within a reserved mapping of `mapping_size` bytes.
/// `accessible_size` and `mapping_size` must be native page-size multiples.
///
/// # Panics
///
/// This function will panic if `accessible_size` is greater than
/// `mapping_size` or if either of them are not page-aligned.
pub fn accessible_reserved(accessible_size: usize, mapping_size: usize) -> Result<Self> {
assert!(accessible_size <= mapping_size);
assert!(usize_is_multiple_of_host_page_size(mapping_size));
assert!(usize_is_multiple_of_host_page_size(accessible_size));
if mapping_size == 0 {
Ok(Mmap {
sys: mmap::Mmap::new_empty(),
#[cfg(feature = "std")]
file: None,
})
} else if accessible_size == mapping_size {
Ok(Mmap {
sys: mmap::Mmap::new(mapping_size)
.context(format!("mmap failed to allocate {mapping_size:#x} bytes"))?,
#[cfg(feature = "std")]
file: None,
})
} else {
let mut result = Mmap {
sys: mmap::Mmap::reserve(mapping_size)
.context(format!("mmap failed to reserve {mapping_size:#x} bytes"))?,
#[cfg(feature = "std")]
file: None,
};
if accessible_size > 0 {
result.make_accessible(0, accessible_size).context(format!(
"mmap failed to allocate {accessible_size:#x} bytes"
))?;
}
Ok(result)
}
}
/// Make the memory starting at `start` and extending for `len` bytes
/// accessible. `start` and `len` must be native page-size multiples and
/// describe a range within `self`'s reserved memory.
///
/// # Panics
///
/// This function will panic if `start` or `len` is not page aligned or if
/// either are outside the bounds of this mapping.
pub fn make_accessible(&mut self, start: usize, len: usize) -> Result<()> {
let page_size = crate::runtime::vm::host_page_size();
assert_eq!(start & (page_size - 1), 0);
assert_eq!(len & (page_size - 1), 0);
assert!(len <= self.len());
assert!(start <= self.len() - len);
self.sys.make_accessible(start, len)
}
/// Return the allocated memory as a slice of u8.
///
/// # Safety
///
/// The caller must ensure that the range of bytes is accessible to the
/// program and additionally has previously been initialized.
///
/// # Panics
///
/// Panics of the `range` provided is outside of the limits of this mmap.
#[inline]
pub unsafe fn slice(&self, range: Range<usize>) -> &[u8] {
assert!(range.start <= range.end);
assert!(range.end <= self.len());
core::slice::from_raw_parts(self.as_ptr().add(range.start), range.end - range.start)
}
/// Return the allocated memory as a mutable slice of u8.
///
/// # Safety
///
/// The caller must ensure that the range of bytes is accessible to the
/// program and additionally has previously been initialized.
///
/// # Panics
///
/// Panics of the `range` provided is outside of the limits of this mmap.
pub unsafe fn slice_mut(&mut self, range: Range<usize>) -> &mut [u8] {
assert!(range.start <= range.end);
assert!(range.end <= self.len());
core::slice::from_raw_parts_mut(self.as_mut_ptr().add(range.start), range.end - range.start)
}
/// Return the allocated memory as a pointer to u8.
#[inline]
pub fn as_ptr(&self) -> *const u8 {
self.sys.as_ptr()
}
/// Return the allocated memory as a mutable pointer to u8.
#[inline]
pub fn as_mut_ptr(&mut self) -> *mut u8 {
self.sys.as_mut_ptr()
}
/// Return the length of the allocated memory.
///
/// This is the byte length of this entire mapping which includes both
/// addressable and non-addressable memory.
#[inline]
pub fn len(&self) -> usize {
self.sys.len()
}
/// Return whether any memory has been allocated or reserved.
pub fn is_empty(&self) -> bool {
self.len() == 0
}
/// Makes the specified `range` within this `Mmap` to be read/execute.
///
/// # Unsafety
///
/// This method is unsafe as it's generally not valid to simply make memory
/// executable, so it's up to the caller to ensure that everything is in
/// order and this doesn't overlap with other memory that should only be
/// read or only read/write.
///
/// # Panics
///
/// Panics of `range` is out-of-bounds or not page-aligned.
pub unsafe fn make_executable(
&self,
range: Range<usize>,
enable_branch_protection: bool,
) -> Result<()> {
assert!(range.start <= self.len());
assert!(range.end <= self.len());
assert!(range.start <= range.end);
assert!(
range.start % crate::runtime::vm::host_page_size() == 0,
"changing of protections isn't page-aligned",
);
self.sys
.make_executable(range, enable_branch_protection)
.context("failed to make memory executable")
}
/// Makes the specified `range` within this `Mmap` to be readonly.
pub unsafe fn make_readonly(&self, range: Range<usize>) -> Result<()> {
assert!(range.start <= self.len());
assert!(range.end <= self.len());
assert!(range.start <= range.end);
assert!(
range.start % crate::runtime::vm::host_page_size() == 0,
"changing of protections isn't page-aligned",
);
self.sys
.make_readonly(range)
.context("failed to make memory readonly")
}
/// Returns the underlying file that this mmap is mapping, if present.
#[cfg(feature = "std")]
pub fn original_file(&self) -> Option<&Arc<File>> {
self.file.as_ref()
}
}
fn _assert() {
fn _assert_send_sync<T: Send + Sync>() {}
_assert_send_sync::<Mmap>();
}
-159
View File
@@ -1,159 +0,0 @@
use crate::prelude::*;
use crate::runtime::vm::Mmap;
use alloc::sync::Arc;
use core::ops::{Deref, DerefMut, Range};
#[cfg(feature = "std")]
use std::{fs::File, path::Path};
/// A type akin to `Vec<u8>`, but backed by `mmap` and able to be split.
///
/// This type is a non-growable owned list of bytes. It can be segmented into
/// disjoint separately owned views akin to the `split_at` method on slices in
/// Rust. An `MmapVec` is backed by an OS-level memory allocation and is not
/// suitable for lots of small allocation (since it works at the page
/// granularity).
///
/// An `MmapVec` is an owned value which means that owners have the ability to
/// get exclusive access to the underlying bytes, enabling mutation.
pub struct MmapVec {
mmap: Arc<Mmap>,
range: Range<usize>,
}
impl MmapVec {
/// Consumes an existing `mmap` and wraps it up into an `MmapVec`.
///
/// The returned `MmapVec` will have the `size` specified, which can be
/// smaller than the region mapped by the `Mmap`. The returned `MmapVec`
/// will only have at most `size` bytes accessible.
pub fn new(mmap: Mmap, size: usize) -> MmapVec {
assert!(size <= mmap.len());
MmapVec {
mmap: Arc::new(mmap),
range: 0..size,
}
}
/// Creates a new zero-initialized `MmapVec` with the given `size`.
///
/// This commit will return a new `MmapVec` suitably sized to hold `size`
/// bytes. All bytes will be initialized to zero since this is a fresh OS
/// page allocation.
pub fn with_capacity(size: usize) -> Result<MmapVec> {
Ok(MmapVec::new(Mmap::with_at_least(size)?, size))
}
/// Creates a new `MmapVec` from the contents of an existing `slice`.
///
/// A new `MmapVec` is allocated to hold the contents of `slice` and then
/// `slice` is copied into the new mmap. It's recommended to avoid this
/// method if possible to avoid the need to copy data around.
pub fn from_slice(slice: &[u8]) -> Result<MmapVec> {
let mut result = MmapVec::with_capacity(slice.len())?;
result.copy_from_slice(slice);
Ok(result)
}
/// Creates a new `MmapVec` which is the `path` specified mmap'd into
/// memory.
///
/// This function will attempt to open the file located at `path` and will
/// then use that file to learn about its size and map the full contents
/// into memory. This will return an error if the file doesn't exist or if
/// it's too large to be fully mapped into memory.
#[cfg(feature = "std")]
pub fn from_file(path: &Path) -> Result<MmapVec> {
let mmap = Mmap::from_file(path)
.with_context(|| format!("failed to create mmap for file: {}", path.display()))?;
let len = mmap.len();
Ok(MmapVec::new(mmap, len))
}
/// Makes the specified `range` within this `mmap` to be read/execute.
pub unsafe fn make_executable(
&self,
range: Range<usize>,
enable_branch_protection: bool,
) -> Result<()> {
assert!(range.start <= range.end);
assert!(range.end <= self.range.len());
self.mmap.make_executable(
range.start + self.range.start..range.end + self.range.start,
enable_branch_protection,
)
}
/// Makes the specified `range` within this `mmap` to be read-only.
pub unsafe fn make_readonly(&self, range: Range<usize>) -> Result<()> {
assert!(range.start <= range.end);
assert!(range.end <= self.range.len());
self.mmap
.make_readonly(range.start + self.range.start..range.end + self.range.start)
}
/// Returns the underlying file that this mmap is mapping, if present.
#[cfg(feature = "std")]
pub fn original_file(&self) -> Option<&Arc<File>> {
self.mmap.original_file()
}
/// Returns the offset within the original mmap that this `MmapVec` is
/// created from.
pub fn original_offset(&self) -> usize {
self.range.start
}
/// Returns the bounds, in host memory, of where this mmap
/// image resides.
pub fn image_range(&self) -> Range<*const u8> {
let base = self.as_ptr();
let len = self.len();
base..base.wrapping_add(len)
}
}
impl Deref for MmapVec {
type Target = [u8];
#[inline]
fn deref(&self) -> &[u8] {
// SAFETY: this mmap owns its own range of the underlying mmap so it
// should be all good-to-read.
unsafe { self.mmap.slice(self.range.clone()) }
}
}
impl DerefMut for MmapVec {
fn deref_mut(&mut self) -> &mut [u8] {
// SAFETY: The underlying mmap is protected behind an `Arc` which means
// there there can be many references to it. We are guaranteed, though,
// that each reference to the underlying `mmap` has a disjoint `range`
// listed that it can access. This means that despite having shared
// access to the mmap itself we have exclusive ownership of the bytes
// specified in `self.range`. This should allow us to safely hand out
// mutable access to these bytes if so desired.
unsafe {
let slice =
core::slice::from_raw_parts_mut(self.mmap.as_ptr().cast_mut(), self.mmap.len());
&mut slice[self.range.clone()]
}
}
}
#[cfg(test)]
mod tests {
use super::MmapVec;
#[test]
fn smoke() {
let mut mmap = MmapVec::with_capacity(10).unwrap();
assert_eq!(mmap.len(), 10);
assert_eq!(&mmap[..], &[0; 10]);
mmap[0] = 1;
mmap[2] = 3;
assert!(mmap.get(10).is_none());
assert_eq!(mmap[0], 1);
assert_eq!(mmap[2], 3);
}
}
@@ -1,43 +0,0 @@
//! Noop implementations of MPK primitives for environments that do not support
//! the feature.
#![allow(missing_docs)]
use crate::prelude::*;
pub fn is_supported() -> bool {
false
}
pub fn keys(_: usize) -> &'static [ProtectionKey] {
&[]
}
pub fn allow(_: ProtectionMask) {}
pub fn current_mask() -> ProtectionMask {
ProtectionMask
}
#[derive(Clone, Copy, Debug)]
pub enum ProtectionKey {}
impl ProtectionKey {
pub fn protect(&self, _: &mut [u8]) -> Result<()> {
match *self {}
}
pub fn as_stripe(&self) -> usize {
match *self {}
}
}
#[derive(Clone, Copy, Debug)]
pub struct ProtectionMask;
impl ProtectionMask {
pub fn all() -> Self {
Self
}
pub fn zero() -> Self {
Self
}
pub fn or(self, _: ProtectionKey) -> Self {
Self
}
}
@@ -1,112 +0,0 @@
use super::cvt;
use crate::prelude::*;
use crate::runtime::vm::sys::capi;
use crate::runtime::vm::SendSyncPtr;
use core::ops::Range;
use core::ptr::{self, NonNull};
#[cfg(feature = "std")]
use std::{fs::File, path::Path};
#[derive(Debug)]
pub struct Mmap {
memory: SendSyncPtr<[u8]>,
}
impl Mmap {
pub fn new_empty() -> Mmap {
Mmap {
memory: crate::vm::sys::empty_mmap(),
}
}
pub fn new(size: usize) -> Result<Self> {
let mut ptr = ptr::null_mut();
cvt(unsafe {
capi::wasmtime_mmap_new(size, capi::PROT_READ | capi::PROT_WRITE, &mut ptr)
})?;
let memory = ptr::slice_from_raw_parts_mut(ptr.cast(), size);
let memory = SendSyncPtr::new(NonNull::new(memory).unwrap());
Ok(Mmap { memory })
}
pub fn reserve(size: usize) -> Result<Self> {
let mut ptr = ptr::null_mut();
cvt(unsafe { capi::wasmtime_mmap_new(size, 0, &mut ptr) })?;
let memory = ptr::slice_from_raw_parts_mut(ptr.cast(), size);
let memory = SendSyncPtr::new(NonNull::new(memory).unwrap());
Ok(Mmap { memory })
}
#[cfg(feature = "std")]
pub fn from_file(_path: &Path) -> Result<(Self, File)> {
anyhow::bail!("not supported on this platform");
}
pub fn make_accessible(&mut self, start: usize, len: usize) -> Result<()> {
let ptr = self.memory.as_ptr();
unsafe {
cvt(capi::wasmtime_mprotect(
ptr.byte_add(start).cast(),
len,
capi::PROT_READ | capi::PROT_WRITE,
))?;
}
Ok(())
}
#[inline]
pub fn as_ptr(&self) -> *const u8 {
self.memory.as_ptr() as *const u8
}
#[inline]
pub fn as_mut_ptr(&mut self) -> *mut u8 {
self.memory.as_ptr().cast()
}
#[inline]
pub fn len(&self) -> usize {
self.memory.as_ptr().len()
}
pub unsafe fn make_executable(
&self,
range: Range<usize>,
enable_branch_protection: bool,
) -> Result<()> {
let base = self.memory.as_ptr().byte_add(range.start).cast();
let len = range.end - range.start;
// not mapped into the C API at this time.
let _ = enable_branch_protection;
cvt(capi::wasmtime_mprotect(
base,
len,
capi::PROT_READ | capi::PROT_EXEC,
))?;
Ok(())
}
pub unsafe fn make_readonly(&self, range: Range<usize>) -> Result<()> {
let base = self.memory.as_ptr().byte_add(range.start).cast();
let len = range.end - range.start;
cvt(capi::wasmtime_mprotect(base, len, capi::PROT_READ))?;
Ok(())
}
}
impl Drop for Mmap {
fn drop(&mut self) {
unsafe {
let ptr = self.memory.as_ptr().cast();
let len = self.memory.as_ptr().len();
if len == 0 {
return;
}
cvt(capi::wasmtime_munmap(ptr, len)).unwrap();
}
}
}
@@ -1,98 +0,0 @@
//! A "dummy" implementation of mmaps for miri where "we do the best we can"
//!
//! Namely this uses `alloc` to allocate memory for the "mmap" specifically to
//! create page-aligned allocations. This allocation doesn't handle operations
//! like becoming executable or becoming readonly or being created from files,
//! but it's enough to get various tests running relying on memories and such.
use crate::prelude::*;
use crate::runtime::vm::SendSyncPtr;
use std::alloc::{self, Layout};
use std::fs::File;
use std::ops::Range;
use std::path::Path;
use std::ptr::NonNull;
#[derive(Debug)]
pub struct Mmap {
memory: SendSyncPtr<[u8]>,
}
impl Mmap {
pub fn new_empty() -> Mmap {
Mmap {
memory: crate::vm::sys::empty_mmap(),
}
}
pub fn new(size: usize) -> Result<Self> {
let mut ret = Mmap::reserve(size)?;
ret.make_accessible(0, size)?;
Ok(ret)
}
pub fn reserve(size: usize) -> Result<Self> {
if size > 1 << 32 {
bail!("failed to allocate memory");
}
let layout = Layout::from_size_align(size, crate::runtime::vm::host_page_size()).unwrap();
let ptr = unsafe { alloc::alloc(layout) };
if ptr.is_null() {
bail!("failed to allocate memory");
}
let memory = std::ptr::slice_from_raw_parts_mut(ptr.cast(), size);
let memory = SendSyncPtr::new(NonNull::new(memory).unwrap());
Ok(Mmap { memory })
}
pub fn from_file(_path: &Path) -> Result<(Self, File)> {
bail!("not supported on miri");
}
pub fn make_accessible(&mut self, start: usize, len: usize) -> Result<()> {
// The memory is technically always accessible but this marks it as
// initialized for miri-level checking.
unsafe {
std::ptr::write_bytes(self.as_mut_ptr().add(start), 0u8, len);
}
Ok(())
}
pub fn as_ptr(&self) -> *const u8 {
self.memory.as_ptr() as *const u8
}
pub fn as_mut_ptr(&mut self) -> *mut u8 {
self.memory.as_ptr().cast()
}
pub fn len(&self) -> usize {
self.memory.as_ptr().len()
}
pub unsafe fn make_executable(
&self,
_range: Range<usize>,
_enable_branch_protection: bool,
) -> Result<()> {
Ok(())
}
pub unsafe fn make_readonly(&self, _range: Range<usize>) -> Result<()> {
Ok(())
}
}
impl Drop for Mmap {
fn drop(&mut self) {
if self.len() == 0 {
return;
}
unsafe {
let layout =
Layout::from_size_align(self.len(), crate::runtime::vm::host_page_size()).unwrap();
alloc::dealloc(self.as_mut_ptr(), layout);
}
}
}
@@ -1,48 +0,0 @@
// For MIRI, there's no way to implement longjmp/setjmp. The only possible way
// to implement this is with panic/catch_panic, but the entrypoint into Rust
// from wasm is defined as `extern "C"` which isn't allowed to panic. That
// means that panicking here triggers UB which gets routed to `libc::abort()`.
//
// This maens that on MIRI all tests which trap are configured to be skipped at
// this time.
//
// Note that no actual JIT code runs in MIRI so this is purely here for
// host-to-host calls.
use crate::runtime::vm::VMContext;
pub fn wasmtime_setjmp(
_jmp_buf: *mut *const u8,
callback: extern "C" fn(*mut u8, *mut VMContext),
payload: *mut u8,
callee: *mut VMContext,
) -> i32 {
callback(payload, callee);
1
}
pub fn wasmtime_longjmp(_jmp_buf: *const u8) -> ! {
unsafe {
libc::abort();
}
}
#[allow(missing_docs)]
pub type SignalHandler<'a> = dyn Fn() + Send + Sync + 'a;
pub struct TrapHandler;
impl TrapHandler {
pub unsafe fn new(_macos_use_mach_ports: bool) -> TrapHandler {
TrapHandler
}
pub fn validate_config(&self, _macos_use_mach_ports: bool) {}
}
pub fn lazy_per_thread_init() {}
#[cfg(target_os = "macos")]
pub fn using_mach_ports() -> bool {
false
}
@@ -1,39 +0,0 @@
/// Whether or not macOS is using mach ports.
#[cfg(target_os = "macos")]
static mut USE_MACH_PORTS: bool = false;
pub use super::signals::{wasmtime_longjmp, wasmtime_setjmp, SignalHandler};
pub enum TrapHandler {
Signals(super::signals::TrapHandler),
#[allow(dead_code)] // used for its drop
MachPorts(super::machports::TrapHandler),
}
impl TrapHandler {
pub unsafe fn new(macos_use_mach_ports: bool) -> TrapHandler {
USE_MACH_PORTS = macos_use_mach_ports;
if macos_use_mach_ports {
TrapHandler::MachPorts(super::machports::TrapHandler::new())
} else {
TrapHandler::Signals(super::signals::TrapHandler::new(false))
}
}
pub fn validate_config(&self, macos_use_mach_ports: bool) {
match self {
TrapHandler::Signals(t) => t.validate_config(macos_use_mach_ports),
TrapHandler::MachPorts(_) => assert!(macos_use_mach_ports),
}
}
}
pub fn lazy_per_thread_init() {
unsafe {
if USE_MACH_PORTS {
super::machports::lazy_per_thread_init();
} else {
super::signals::lazy_per_thread_init();
}
}
}
@@ -1,35 +0,0 @@
//! Implementation of Wasmtime's system primitives for Unix-like operating
//! systems.
//!
//! This module handles Linux and macOS for example.
use core::cell::Cell;
pub mod mmap;
pub mod unwind;
pub mod vm;
pub mod signals;
cfg_if::cfg_if! {
if #[cfg(target_os = "macos")] {
pub mod machports;
pub mod macos_traphandlers;
pub use macos_traphandlers as traphandlers;
} else {
pub use signals as traphandlers;
}
}
std::thread_local!(static TLS: Cell<*mut u8> = const { Cell::new(std::ptr::null_mut()) });
#[inline]
pub fn tls_get() -> *mut u8 {
TLS.with(|p| p.get())
}
#[inline]
pub fn tls_set(ptr: *mut u8) {
TLS.with(|p| p.set(ptr));
}
@@ -1,493 +0,0 @@
//! Trap handling on Unix based on POSIX signals.
use crate::runtime::vm::traphandlers::{tls, TrapRegisters, TrapTest};
use crate::runtime::vm::VMContext;
use std::cell::RefCell;
use std::io;
use std::mem;
use std::ptr::{self, addr_of, addr_of_mut, null_mut};
#[link(name = "wasmtime-helpers")]
extern "C" {
#[wasmtime_versioned_export_macros::versioned_link]
#[allow(improper_ctypes)]
pub fn wasmtime_setjmp(
jmp_buf: *mut *const u8,
callback: extern "C" fn(*mut u8, *mut VMContext),
payload: *mut u8,
callee: *mut VMContext,
) -> i32;
#[wasmtime_versioned_export_macros::versioned_link]
pub fn wasmtime_longjmp(jmp_buf: *const u8) -> !;
}
/// Function which may handle custom signals while processing traps.
pub type SignalHandler<'a> =
dyn Fn(libc::c_int, *const libc::siginfo_t, *const libc::c_void) -> bool + Send + Sync + 'a;
const UNINIT_SIGACTION: libc::sigaction = unsafe { mem::zeroed() };
static mut PREV_SIGSEGV: libc::sigaction = UNINIT_SIGACTION;
static mut PREV_SIGBUS: libc::sigaction = UNINIT_SIGACTION;
static mut PREV_SIGILL: libc::sigaction = UNINIT_SIGACTION;
static mut PREV_SIGFPE: libc::sigaction = UNINIT_SIGACTION;
pub struct TrapHandler;
impl TrapHandler {
/// Installs all trap handlers.
///
/// # Unsafety
///
/// This function is unsafe because it's not safe to call concurrently and
/// it's not safe to call if the trap handlers have already been initialized
/// for this process.
pub unsafe fn new(macos_use_mach_ports: bool) -> TrapHandler {
// Either mach ports shouldn't be in use or we shouldn't be on macOS,
// otherwise the `machports.rs` module should be used instead.
assert!(!macos_use_mach_ports || !cfg!(target_os = "macos"));
foreach_handler(|slot, signal| {
let mut handler: libc::sigaction = mem::zeroed();
// The flags here are relatively careful, and they are...
//
// SA_SIGINFO gives us access to information like the program
// counter from where the fault happened.
//
// SA_ONSTACK allows us to handle signals on an alternate stack,
// so that the handler can run in response to running out of
// stack space on the main stack. Rust installs an alternate
// stack with sigaltstack, so we rely on that.
//
// SA_NODEFER allows us to reenter the signal handler if we
// crash while handling the signal, and fall through to the
// Breakpad handler by testing handlingSegFault.
handler.sa_flags = libc::SA_SIGINFO | libc::SA_NODEFER | libc::SA_ONSTACK;
handler.sa_sigaction = trap_handler as usize;
libc::sigemptyset(&mut handler.sa_mask);
if libc::sigaction(signal, &handler, slot) != 0 {
panic!(
"unable to install signal handler: {}",
io::Error::last_os_error(),
);
}
});
TrapHandler
}
pub fn validate_config(&self, macos_use_mach_ports: bool) {
assert!(!macos_use_mach_ports || !cfg!(target_os = "macos"));
}
}
unsafe fn foreach_handler(mut f: impl FnMut(*mut libc::sigaction, i32)) {
// Allow handling OOB with signals on all architectures
f(addr_of_mut!(PREV_SIGSEGV), libc::SIGSEGV);
// Handle `unreachable` instructions which execute `ud2` right now
f(addr_of_mut!(PREV_SIGILL), libc::SIGILL);
// x86 and s390x use SIGFPE to report division by zero
if cfg!(target_arch = "x86_64") || cfg!(target_arch = "s390x") {
f(addr_of_mut!(PREV_SIGFPE), libc::SIGFPE);
}
// Sometimes we need to handle SIGBUS too:
// - On Darwin, guard page accesses are raised as SIGBUS.
if cfg!(target_os = "macos") || cfg!(target_os = "freebsd") {
f(addr_of_mut!(PREV_SIGBUS), libc::SIGBUS);
}
// TODO(#1980): x86-32, if we support it, will also need a SIGFPE handler.
// TODO(#1173): ARM32, if we support it, will also need a SIGBUS handler.
}
impl Drop for TrapHandler {
fn drop(&mut self) {
unsafe {
foreach_handler(|slot, signal| {
let mut prev: libc::sigaction = mem::zeroed();
// Restore the previous handler that this signal had.
if libc::sigaction(signal, slot, &mut prev) != 0 {
eprintln!(
"unable to reinstall signal handler: {}",
io::Error::last_os_error(),
);
libc::abort();
}
// If our trap handler wasn't currently listed for this process
// then that's a problem because we have just corrupted the
// signal handler state and don't know how to remove ourselves
// from the signal handling state. Inform the user of this and
// abort the process.
if prev.sa_sigaction != trap_handler as usize {
eprintln!(
"
Wasmtime's signal handler was not the last signal handler to be installed
in the process so it's not certain how to unload signal handlers. In this
situation the Engine::unload_process_handlers API is not applicable and requires
perhaps initializing libraries in a different order. The process will be aborted
now.
"
);
libc::abort();
}
});
}
}
}
unsafe extern "C" fn trap_handler(
signum: libc::c_int,
siginfo: *mut libc::siginfo_t,
context: *mut libc::c_void,
) {
let previous = match signum {
libc::SIGSEGV => addr_of!(PREV_SIGSEGV),
libc::SIGBUS => addr_of!(PREV_SIGBUS),
libc::SIGFPE => addr_of!(PREV_SIGFPE),
libc::SIGILL => addr_of!(PREV_SIGILL),
_ => panic!("unknown signal: {signum}"),
};
let handled = tls::with(|info| {
// If no wasm code is executing, we don't handle this as a wasm
// trap.
let info = match info {
Some(info) => info,
None => return false,
};
// If we hit an exception while handling a previous trap, that's
// quite bad, so bail out and let the system handle this
// recursive segfault.
//
// Otherwise flag ourselves as handling a trap, do the trap
// handling, and reset our trap handling flag. Then we figure
// out what to do based on the result of the trap handling.
let faulting_addr = match signum {
libc::SIGSEGV | libc::SIGBUS => Some((*siginfo).si_addr() as usize),
_ => None,
};
let regs = get_trap_registers(context, signum);
let test = info.test_if_trap(regs, faulting_addr, |handler| {
handler(signum, siginfo, context)
});
// Figure out what to do based on the result of this handling of
// the trap. Note that our sentinel value of 1 means that the
// exception was handled by a custom exception handler, so we
// keep executing.
let jmp_buf = match test {
TrapTest::NotWasm => {
if let Some(faulting_addr) = faulting_addr {
let start = info.async_guard_range.start;
let end = info.async_guard_range.end;
if start as usize <= faulting_addr && faulting_addr < end as usize {
abort_stack_overflow();
}
}
return false;
}
TrapTest::HandledByEmbedder => return true,
TrapTest::Trap { jmp_buf } => jmp_buf,
};
// On macOS this is a bit special, unfortunately. If we were to
// `siglongjmp` out of the signal handler that notably does
// *not* reset the sigaltstack state of our signal handler. This
// seems to trick the kernel into thinking that the sigaltstack
// is still in use upon delivery of the next signal, meaning
// that the sigaltstack is not ever used again if we immediately
// call `wasmtime_longjmp` here.
//
// Note that if we use `longjmp` instead of `siglongjmp` then
// the problem is fixed. The problem with that, however, is that
// `setjmp` is much slower than `sigsetjmp` due to the
// preservation of the process's signal mask. The reason
// `longjmp` appears to work is that it seems to call a function
// (according to published macOS sources) called
// `_sigunaltstack` which updates the kernel to say the
// sigaltstack is no longer in use. We ideally want to call that
// here but I don't think there's a stable way for us to call
// that.
//
// Given all that, on macOS only, we do the next best thing. We
// return from the signal handler after updating the register
// context. This will cause control to return to our shim
// function defined here which will perform the
// `wasmtime_longjmp` (`siglongjmp`) for us. The reason this
// works is that by returning from the signal handler we'll
// trigger all the normal machinery for "the signal handler is
// done running" which will clear the sigaltstack flag and allow
// reusing it for the next signal. Then upon resuming in our custom
// code we blow away the stack anyway with a longjmp.
if cfg!(target_os = "macos") {
unsafe extern "C" fn wasmtime_longjmp_shim(jmp_buf: *const u8) {
wasmtime_longjmp(jmp_buf)
}
set_pc(context, wasmtime_longjmp_shim as usize, jmp_buf as usize);
return true;
}
wasmtime_longjmp(jmp_buf)
});
if handled {
return;
}
delegate_signal_to_previous_handler(previous, signum, siginfo, context)
}
pub unsafe fn delegate_signal_to_previous_handler(
previous: *const libc::sigaction,
signum: libc::c_int,
siginfo: *mut libc::siginfo_t,
context: *mut libc::c_void,
) {
// This signal is not for any compiled wasm code we expect, so we
// need to forward the signal to the next handler. If there is no
// next handler (SIG_IGN or SIG_DFL), then it's time to crash. To do
// this, we set the signal back to its original disposition and
// return. This will cause the faulting op to be re-executed which
// will crash in the normal way. If there is a next handler, call
// it. It will either crash synchronously, fix up the instruction
// so that execution can continue and return, or trigger a crash by
// returning the signal to it's original disposition and returning.
let previous = *previous;
if previous.sa_flags & libc::SA_SIGINFO != 0 {
mem::transmute::<usize, extern "C" fn(libc::c_int, *mut libc::siginfo_t, *mut libc::c_void)>(
previous.sa_sigaction,
)(signum, siginfo, context)
} else if previous.sa_sigaction == libc::SIG_DFL || previous.sa_sigaction == libc::SIG_IGN {
libc::sigaction(signum, &previous as *const _, ptr::null_mut());
} else {
mem::transmute::<usize, extern "C" fn(libc::c_int)>(previous.sa_sigaction)(signum)
}
}
pub fn abort_stack_overflow() -> ! {
unsafe {
let msg = "execution on async fiber has overflowed its stack";
libc::write(libc::STDERR_FILENO, msg.as_ptr().cast(), msg.len());
libc::abort();
}
}
#[allow(clippy::cast_possible_truncation)] // too fiddly to handle and wouldn't
// help much anyway
unsafe fn get_trap_registers(cx: *mut libc::c_void, _signum: libc::c_int) -> TrapRegisters {
cfg_if::cfg_if! {
if #[cfg(all(any(target_os = "linux", target_os = "android", target_os = "illumos"), target_arch = "x86_64"))] {
let cx = &*(cx as *const libc::ucontext_t);
TrapRegisters {
pc: cx.uc_mcontext.gregs[libc::REG_RIP as usize] as usize,
fp: cx.uc_mcontext.gregs[libc::REG_RBP as usize] as usize,
}
} else if #[cfg(all(any(target_os = "linux", target_os = "android"), target_arch = "aarch64"))] {
let cx = &*(cx as *const libc::ucontext_t);
TrapRegisters {
pc: cx.uc_mcontext.pc as usize,
fp: cx.uc_mcontext.regs[29] as usize,
}
} else if #[cfg(all(target_os = "linux", target_arch = "s390x"))] {
// On s390x, SIGILL and SIGFPE are delivered with the PSW address
// pointing *after* the faulting instruction, while SIGSEGV and
// SIGBUS are delivered with the PSW address pointing *to* the
// faulting instruction. To handle this, the code generator registers
// any trap that results in one of "late" signals on the last byte
// of the instruction, and any trap that results in one of the "early"
// signals on the first byte of the instruction (as usual). This
// means we simply need to decrement the reported PSW address by
// one in the case of a "late" signal here to ensure we always
// correctly find the associated trap handler.
let trap_offset = match _signum {
libc::SIGILL | libc::SIGFPE => 1,
_ => 0,
};
let cx = &*(cx as *const libc::ucontext_t);
TrapRegisters {
pc: (cx.uc_mcontext.psw.addr - trap_offset) as usize,
fp: *(cx.uc_mcontext.gregs[15] as *const usize),
}
} else if #[cfg(all(target_os = "macos", target_arch = "x86_64"))] {
let cx = &*(cx as *const libc::ucontext_t);
TrapRegisters {
pc: (*cx.uc_mcontext).__ss.__rip as usize,
fp: (*cx.uc_mcontext).__ss.__rbp as usize,
}
} else if #[cfg(all(target_os = "macos", target_arch = "aarch64"))] {
let cx = &*(cx as *const libc::ucontext_t);
TrapRegisters {
pc: (*cx.uc_mcontext).__ss.__pc as usize,
fp: (*cx.uc_mcontext).__ss.__fp as usize,
}
} else if #[cfg(all(target_os = "freebsd", target_arch = "x86_64"))] {
let cx = &*(cx as *const libc::ucontext_t);
TrapRegisters {
pc: cx.uc_mcontext.mc_rip as usize,
fp: cx.uc_mcontext.mc_rbp as usize,
}
} else if #[cfg(all(target_os = "linux", target_arch = "riscv64"))] {
let cx = &*(cx as *const libc::ucontext_t);
TrapRegisters {
pc: cx.uc_mcontext.__gregs[libc::REG_PC] as usize,
fp: cx.uc_mcontext.__gregs[libc::REG_S0] as usize,
}
} else if #[cfg(all(target_os = "freebsd", target_arch = "aarch64"))] {
let cx = &*(cx as *const libc::mcontext_t);
TrapRegisters {
pc: cx.mc_gpregs.gp_elr as usize,
fp: cx.mc_gpregs.gp_x[29] as usize,
}
} else if #[cfg(all(target_os = "openbsd", target_arch = "x86_64"))] {
let cx = &*(cx as *const libc::ucontext_t);
TrapRegisters {
pc: cx.sc_rip as usize,
fp: cx.sc_rbp as usize,
}
} else {
compile_error!("unsupported platform");
panic!();
}
}
}
// This is only used on macOS targets for calling an unwinding shim
// function to ensure that we return from the signal handler.
//
// See more comments above where this is called for what it's doing.
unsafe fn set_pc(cx: *mut libc::c_void, pc: usize, arg1: usize) {
cfg_if::cfg_if! {
if #[cfg(not(target_os = "macos"))] {
let _ = (cx, pc, arg1);
unreachable!(); // not used on these platforms
} else if #[cfg(target_arch = "x86_64")] {
let cx = &mut *(cx as *mut libc::ucontext_t);
(*cx.uc_mcontext).__ss.__rip = pc as u64;
(*cx.uc_mcontext).__ss.__rdi = arg1 as u64;
// We're simulating a "pseudo-call" so we need to ensure
// stack alignment is properly respected, notably that on a
// `call` instruction the stack is 8/16-byte aligned, then
// the function adjusts itself to be 16-byte aligned.
//
// Most of the time the stack pointer is 16-byte aligned at
// the time of the trap but for more robust-ness with JIT
// code where it may ud2 in a prologue check before the
// stack is aligned we double-check here.
if (*cx.uc_mcontext).__ss.__rsp % 16 == 0 {
(*cx.uc_mcontext).__ss.__rsp -= 8;
}
} else if #[cfg(target_arch = "aarch64")] {
let cx = &mut *(cx as *mut libc::ucontext_t);
(*cx.uc_mcontext).__ss.__pc = pc as u64;
(*cx.uc_mcontext).__ss.__x[0] = arg1 as u64;
} else {
compile_error!("unsupported macos target architecture");
}
}
}
/// A function for registering a custom alternate signal stack (sigaltstack).
///
/// Rust's libstd installs an alternate stack with size `SIGSTKSZ`, which is not
/// always large enough for our signal handling code. Override it by creating
/// and registering our own alternate stack that is large enough and has a guard
/// page.
#[cold]
pub fn lazy_per_thread_init() {
// This thread local is purely used to register a `Stack` to get deallocated
// when the thread exists. Otherwise this function is only ever called at
// most once per-thread.
std::thread_local! {
static STACK: RefCell<Option<Stack>> = const { RefCell::new(None) };
}
/// The size of the sigaltstack (not including the guard, which will be
/// added). Make this large enough to run our signal handlers.
///
/// The main current requirement of the signal handler in terms of stack
/// space is that `malloc`/`realloc` are called to create a `Backtrace` of
/// wasm frames.
///
/// Historically this was 16k. Turns out jemalloc requires more than 16k of
/// stack space in debug mode, so this was bumped to 64k.
const MIN_STACK_SIZE: usize = 64 * 4096;
struct Stack {
mmap_ptr: *mut libc::c_void,
mmap_size: usize,
}
return STACK.with(|s| {
*s.borrow_mut() = unsafe { allocate_sigaltstack() };
});
unsafe fn allocate_sigaltstack() -> Option<Stack> {
// Check to see if the existing sigaltstack, if it exists, is big
// enough. If so we don't need to allocate our own.
let mut old_stack = mem::zeroed();
let r = libc::sigaltstack(ptr::null(), &mut old_stack);
assert_eq!(
r,
0,
"learning about sigaltstack failed: {}",
io::Error::last_os_error()
);
if old_stack.ss_flags & libc::SS_DISABLE == 0 && old_stack.ss_size >= MIN_STACK_SIZE {
return None;
}
// ... but failing that we need to allocate our own, so do all that
// here.
let page_size = crate::runtime::vm::host_page_size();
let guard_size = page_size;
let alloc_size = guard_size + MIN_STACK_SIZE;
let ptr = rustix::mm::mmap_anonymous(
null_mut(),
alloc_size,
rustix::mm::ProtFlags::empty(),
rustix::mm::MapFlags::PRIVATE,
)
.expect("failed to allocate memory for sigaltstack");
// Prepare the stack with readable/writable memory and then register it
// with `sigaltstack`.
let stack_ptr = (ptr as usize + guard_size) as *mut std::ffi::c_void;
rustix::mm::mprotect(
stack_ptr,
MIN_STACK_SIZE,
rustix::mm::MprotectFlags::READ | rustix::mm::MprotectFlags::WRITE,
)
.expect("mprotect to configure memory for sigaltstack failed");
let new_stack = libc::stack_t {
ss_sp: stack_ptr,
ss_flags: 0,
ss_size: MIN_STACK_SIZE,
};
let r = libc::sigaltstack(&new_stack, ptr::null_mut());
assert_eq!(
r,
0,
"registering new sigaltstack failed: {}",
io::Error::last_os_error()
);
Some(Stack {
mmap_ptr: ptr,
mmap_size: alloc_size,
})
}
impl Drop for Stack {
fn drop(&mut self) {
unsafe {
// Deallocate the stack memory.
let r = rustix::mm::munmap(self.mmap_ptr, self.mmap_size);
debug_assert!(r.is_ok(), "munmap failed during thread shutdown");
}
}
}
}
@@ -1,190 +0,0 @@
use crate::runtime::vm::sys::DecommitBehavior;
use rustix::fd::AsRawFd;
use rustix::mm::{mmap, mmap_anonymous, mprotect, MapFlags, MprotectFlags, ProtFlags};
use std::fs::File;
use std::io;
#[cfg(feature = "std")]
use std::sync::Arc;
pub unsafe fn expose_existing_mapping(ptr: *mut u8, len: usize) -> io::Result<()> {
mprotect(ptr.cast(), len, MprotectFlags::READ | MprotectFlags::WRITE)?;
Ok(())
}
pub unsafe fn hide_existing_mapping(ptr: *mut u8, len: usize) -> io::Result<()> {
mprotect(ptr.cast(), len, MprotectFlags::empty())?;
Ok(())
}
pub unsafe fn erase_existing_mapping(ptr: *mut u8, len: usize) -> io::Result<()> {
let ret = mmap_anonymous(
ptr.cast(),
len,
ProtFlags::empty(),
MapFlags::PRIVATE | super::mmap::MMAP_NORESERVE_FLAG | MapFlags::FIXED,
)?;
assert_eq!(ptr, ret.cast());
Ok(())
}
#[cfg(feature = "pooling-allocator")]
pub unsafe fn commit_pages(_addr: *mut u8, _len: usize) -> io::Result<()> {
// Pages are always READ | WRITE so there's nothing that needs to be done
// here.
Ok(())
}
pub unsafe fn decommit_pages(addr: *mut u8, len: usize) -> io::Result<()> {
if len == 0 {
return Ok(());
}
unsafe {
cfg_if::cfg_if! {
if #[cfg(target_os = "linux")] {
use rustix::mm::{madvise, Advice};
// On Linux, this is enough to cause the kernel to initialize
// the pages to 0 on next access
madvise(addr as _, len, Advice::LinuxDontNeed)?;
} else {
// By creating a new mapping at the same location, this will
// discard the mapping for the pages in the given range.
// The new mapping will be to the CoW zero page, so this
// effectively zeroes the pages.
mmap_anonymous(
addr as _,
len,
ProtFlags::READ | ProtFlags::WRITE,
MapFlags::PRIVATE | super::mmap::MMAP_NORESERVE_FLAG | MapFlags::FIXED,
)?;
}
}
}
Ok(())
}
pub fn get_page_size() -> usize {
unsafe { libc::sysconf(libc::_SC_PAGESIZE).try_into().unwrap() }
}
pub fn decommit_behavior() -> DecommitBehavior {
if cfg!(target_os = "linux") {
DecommitBehavior::RestoreOriginalMapping
} else {
DecommitBehavior::Zero
}
}
#[derive(Debug)]
pub enum MemoryImageSource {
#[cfg(feature = "std")]
Mmap(Arc<File>),
#[cfg(target_os = "linux")]
Memfd(memfd::Memfd),
}
impl MemoryImageSource {
#[cfg(feature = "std")]
pub fn from_file(file: &Arc<File>) -> Option<MemoryImageSource> {
Some(MemoryImageSource::Mmap(file.clone()))
}
#[cfg(not(target_os = "linux"))]
pub fn from_data(_data: &[u8]) -> io::Result<Option<MemoryImageSource>> {
Ok(None)
}
#[cfg(target_os = "linux")]
pub fn from_data(data: &[u8]) -> anyhow::Result<Option<MemoryImageSource>> {
// On Linux `memfd_create` is used to create an anonymous
// in-memory file to represent the heap image. This anonymous
// file is then used as the basis for further mmaps.
use crate::prelude::*;
use std::io::{ErrorKind, Write};
// Create the memfd. It needs a name, but the documentation for
// `memfd_create()` says that names can be duplicated with no issues.
let memfd = match memfd::MemfdOptions::new()
.allow_sealing(true)
.create("wasm-memory-image")
{
Ok(memfd) => memfd,
// If this kernel is old enough to not support memfd then attempt to
// gracefully handle that and fall back to skipping the memfd
// optimization.
Err(memfd::Error::Create(err)) if err.kind() == ErrorKind::Unsupported => {
return Ok(None)
}
Err(e) => return Err(e.into_anyhow()),
};
memfd.as_file().write_all(data).err2anyhow()?;
// Seal the memfd's data and length.
//
// This is a defense-in-depth security mitigation. The
// memfd will serve as the starting point for the heap of
// every instance of this module. If anything were to
// write to this, it could affect every execution. The
// memfd object itself is owned by the machinery here and
// not exposed elsewhere, but it is still an ambient open
// file descriptor at the syscall level, so some other
// vulnerability that allowed writes to arbitrary fds
// could modify it. Or we could have some issue with the
// way that we map it into each instance. To be
// extra-super-sure that it never changes, and because
// this costs very little, we use the kernel's "seal" API
// to make the memfd image permanently read-only.
memfd
.add_seals(&[
memfd::FileSeal::SealGrow,
memfd::FileSeal::SealShrink,
memfd::FileSeal::SealWrite,
memfd::FileSeal::SealSeal,
])
.err2anyhow()?;
Ok(Some(MemoryImageSource::Memfd(memfd)))
}
fn as_file(&self) -> &File {
match *self {
#[cfg(feature = "std")]
MemoryImageSource::Mmap(ref file) => file,
#[cfg(target_os = "linux")]
MemoryImageSource::Memfd(ref memfd) => memfd.as_file(),
}
}
pub unsafe fn map_at(&self, base: *mut u8, len: usize, offset: u64) -> io::Result<()> {
let ptr = mmap(
base.cast(),
len,
ProtFlags::READ | ProtFlags::WRITE,
MapFlags::PRIVATE | MapFlags::FIXED,
self.as_file(),
offset,
)?;
assert_eq!(base, ptr.cast());
Ok(())
}
pub unsafe fn remap_as_zeros_at(&self, base: *mut u8, len: usize) -> io::Result<()> {
let ptr = mmap_anonymous(
base.cast(),
len,
ProtFlags::READ | ProtFlags::WRITE,
MapFlags::PRIVATE | super::mmap::MMAP_NORESERVE_FLAG | MapFlags::FIXED,
)?;
assert_eq!(base, ptr.cast());
Ok(())
}
}
impl PartialEq for MemoryImageSource {
fn eq(&self, other: &MemoryImageSource) -> bool {
self.as_file().as_raw_fd() == other.as_file().as_raw_fd()
}
}
@@ -1,220 +0,0 @@
use crate::prelude::*;
use crate::runtime::vm::SendSyncPtr;
use std::fs::{File, OpenOptions};
use std::io;
use std::ops::Range;
use std::os::windows::prelude::*;
use std::path::Path;
use std::ptr::{self, NonNull};
use windows_sys::Win32::Foundation::*;
use windows_sys::Win32::Storage::FileSystem::*;
use windows_sys::Win32::System::Memory::*;
#[derive(Debug)]
pub struct Mmap {
memory: SendSyncPtr<[u8]>,
is_file: bool,
}
impl Mmap {
pub fn new_empty() -> Mmap {
Mmap {
memory: crate::vm::sys::empty_mmap(),
is_file: false,
}
}
pub fn new(size: usize) -> Result<Self> {
let ptr = unsafe {
VirtualAlloc(
ptr::null_mut(),
size,
MEM_RESERVE | MEM_COMMIT,
PAGE_READWRITE,
)
};
if ptr.is_null() {
bail!(io::Error::last_os_error())
}
let memory = std::ptr::slice_from_raw_parts_mut(ptr.cast(), size);
let memory = SendSyncPtr::new(NonNull::new(memory).unwrap());
Ok(Self {
memory,
is_file: false,
})
}
pub fn reserve(size: usize) -> Result<Self> {
let ptr = unsafe { VirtualAlloc(ptr::null_mut(), size, MEM_RESERVE, PAGE_NOACCESS) };
if ptr.is_null() {
bail!(io::Error::last_os_error())
}
let memory = std::ptr::slice_from_raw_parts_mut(ptr.cast(), size);
let memory = SendSyncPtr::new(NonNull::new(memory).unwrap());
Ok(Self {
memory,
is_file: false,
})
}
pub fn from_file(path: &Path) -> Result<(Self, File)> {
unsafe {
// Open the file with read/execute access and only share for
// read. This will enable us to perform the proper mmap below
// while also disallowing other processes modifying the file
// and having those modifications show up in our address space.
let file = OpenOptions::new()
.read(true)
.access_mode(FILE_GENERIC_READ | FILE_GENERIC_EXECUTE)
.share_mode(FILE_SHARE_READ)
.open(path)
.err2anyhow()
.context("failed to open file")?;
let len = file
.metadata()
.err2anyhow()
.context("failed to get file metadata")?
.len();
let len = usize::try_from(len).map_err(|_| anyhow!("file too large to map"))?;
// Create a file mapping that allows PAGE_EXECUTE_WRITECOPY.
// This enables up-to these permissions but we won't leave all
// of these permissions active at all times. Execution is
// necessary for the generated code from Cranelift and the
// WRITECOPY part is needed for possibly resolving relocations,
// but otherwise writes don't happen.
let mapping = CreateFileMappingW(
file.as_raw_handle(),
ptr::null_mut(),
PAGE_EXECUTE_WRITECOPY,
0,
0,
ptr::null(),
);
if mapping == INVALID_HANDLE_VALUE {
return Err(io::Error::last_os_error().into_anyhow())
.context("failed to create file mapping");
}
// Create a view for the entire file using all our requisite
// permissions so that we can change the virtual permissions
// later on.
let ptr = MapViewOfFile(
mapping,
FILE_MAP_READ | FILE_MAP_EXECUTE | FILE_MAP_COPY,
0,
0,
len,
)
.Value;
let err = io::Error::last_os_error();
CloseHandle(mapping);
if ptr.is_null() {
return Err(err.into_anyhow())
.context(format!("failed to create map view of {:#x} bytes", len));
}
let memory = std::ptr::slice_from_raw_parts_mut(ptr.cast(), len);
let memory = SendSyncPtr::new(NonNull::new(memory).unwrap());
let mut ret = Self {
memory,
is_file: true,
};
// Protect the entire file as PAGE_WRITECOPY to start (i.e.
// remove the execute bit)
let mut old = 0;
if VirtualProtect(ret.as_mut_ptr().cast(), ret.len(), PAGE_WRITECOPY, &mut old) == 0 {
return Err(io::Error::last_os_error().into_anyhow())
.context("failed change pages to `PAGE_READONLY`");
}
Ok((ret, file))
}
}
pub fn make_accessible(&mut self, start: usize, len: usize) -> Result<()> {
if unsafe {
VirtualAlloc(
self.as_ptr().add(start) as _,
len,
MEM_COMMIT,
PAGE_READWRITE,
)
}
.is_null()
{
bail!(io::Error::last_os_error())
}
Ok(())
}
#[inline]
pub fn as_ptr(&self) -> *const u8 {
self.memory.as_ptr() as *const u8
}
#[inline]
pub fn as_mut_ptr(&mut self) -> *mut u8 {
self.memory.as_ptr().cast()
}
#[inline]
pub fn len(&self) -> usize {
self.memory.as_ptr().len()
}
pub unsafe fn make_executable(
&self,
range: Range<usize>,
enable_branch_protection: bool,
) -> Result<()> {
let flags = if enable_branch_protection {
// TODO: We use this check to avoid an unused variable warning,
// but some of the CFG-related flags might be applicable
PAGE_EXECUTE_READ
} else {
PAGE_EXECUTE_READ
};
let mut old = 0;
let base = self.as_ptr().add(range.start);
let result = VirtualProtect(base as _, range.end - range.start, flags, &mut old);
if result == 0 {
bail!(io::Error::last_os_error());
}
Ok(())
}
pub unsafe fn make_readonly(&self, range: Range<usize>) -> Result<()> {
let mut old = 0;
let base = self.as_ptr().add(range.start);
let result = VirtualProtect(base as _, range.end - range.start, PAGE_READONLY, &mut old);
if result == 0 {
bail!(io::Error::last_os_error());
}
Ok(())
}
}
impl Drop for Mmap {
fn drop(&mut self) {
if self.len() == 0 {
return;
}
if self.is_file {
let r = unsafe {
UnmapViewOfFile(MEMORY_MAPPED_VIEW_ADDRESS {
Value: self.as_mut_ptr().cast(),
})
};
assert_ne!(r, 0);
} else {
let r = unsafe { VirtualFree(self.as_mut_ptr().cast(), 0, MEM_RELEASE) };
assert_ne!(r, 0);
}
}
}
@@ -1,20 +0,0 @@
//! Implementation of Wasmtime's system primitives for Windows.
use std::cell::Cell;
pub mod mmap;
pub mod traphandlers;
pub mod unwind;
pub mod vm;
std::thread_local!(static TLS: Cell<*mut u8> = const { Cell::new(std::ptr::null_mut()) });
#[inline]
pub fn tls_get() -> *mut u8 {
TLS.with(|p| p.get())
}
#[inline]
pub fn tls_set(ptr: *mut u8) {
TLS.with(|p| p.set(ptr));
}
@@ -1,135 +0,0 @@
use crate::runtime::vm::traphandlers::{tls, TrapRegisters, TrapTest};
use crate::runtime::vm::VMContext;
use std::ffi::c_void;
use std::io;
use windows_sys::Win32::Foundation::*;
use windows_sys::Win32::System::Diagnostics::Debug::*;
use windows_sys::Win32::System::Kernel::*;
#[link(name = "wasmtime-helpers")]
extern "C" {
#[wasmtime_versioned_export_macros::versioned_link]
#[allow(improper_ctypes)]
pub fn wasmtime_setjmp(
jmp_buf: *mut *const u8,
callback: extern "C" fn(*mut u8, *mut VMContext),
payload: *mut u8,
callee: *mut VMContext,
) -> i32;
#[wasmtime_versioned_export_macros::versioned_link]
pub fn wasmtime_longjmp(jmp_buf: *const u8) -> !;
}
/// Function which may handle custom signals while processing traps.
pub type SignalHandler<'a> = dyn Fn(*mut EXCEPTION_POINTERS) -> bool + Send + Sync + 'a;
pub struct TrapHandler {
handle: *mut c_void,
}
unsafe impl Send for TrapHandler {}
unsafe impl Sync for TrapHandler {}
impl TrapHandler {
pub unsafe fn new(_macos_use_mach_ports: bool) -> TrapHandler {
// our trap handler needs to go first, so that we can recover from
// wasm faults and continue execution, so pass `1` as a true value
// here.
let handle = AddVectoredExceptionHandler(1, Some(exception_handler));
if handle.is_null() {
panic!(
"failed to add exception handler: {}",
io::Error::last_os_error()
);
}
TrapHandler { handle }
}
pub fn validate_config(&self, _macos_use_mach_ports: bool) {}
}
impl Drop for TrapHandler {
fn drop(&mut self) {
unsafe {
let rc = RemoveVectoredExceptionHandler(self.handle);
if rc == 0 {
eprintln!(
"failed to remove exception handler: {}",
io::Error::last_os_error()
);
libc::abort();
}
}
}
}
unsafe extern "system" fn exception_handler(exception_info: *mut EXCEPTION_POINTERS) -> i32 {
// Check the kind of exception, since we only handle a subset within
// wasm code. If anything else happens we want to defer to whatever
// the rest of the system wants to do for this exception.
let record = &*(*exception_info).ExceptionRecord;
if record.ExceptionCode != EXCEPTION_ACCESS_VIOLATION
&& record.ExceptionCode != EXCEPTION_ILLEGAL_INSTRUCTION
&& record.ExceptionCode != EXCEPTION_INT_DIVIDE_BY_ZERO
&& record.ExceptionCode != EXCEPTION_INT_OVERFLOW
{
return ExceptionContinueSearch;
}
// FIXME: this is what the previous C++ did to make sure that TLS
// works by the time we execute this trap handling code. This isn't
// exactly super easy to call from Rust though and it's not clear we
// necessarily need to do so. Leaving this here in case we need this
// in the future, but for now we can probably wait until we see a
// strange fault before figuring out how to reimplement this in
// Rust.
//
// if (!NtCurrentTeb()->Reserved1[sThreadLocalArrayPointerIndex]) {
// return ExceptionContinueSearch;
// }
// This is basically the same as the unix version above, only with a
// few parameters tweaked here and there.
tls::with(|info| {
let info = match info {
Some(info) => info,
None => return ExceptionContinueSearch,
};
let context = &*(*exception_info).ContextRecord;
cfg_if::cfg_if! {
if #[cfg(target_arch = "x86_64")] {
let regs = TrapRegisters {
pc: context.Rip as usize,
fp: context.Rbp as usize,
};
} else if #[cfg(target_arch = "aarch64")] {
let regs = TrapRegisters {
pc: context.Pc as usize,
fp: context.Anonymous.Anonymous.Fp as usize,
};
} else {
compile_error!("unsupported platform");
}
}
// For access violations the first element in `ExceptionInformation` is
// an indicator as to whether the fault was a read/write. The second
// element is the address of the inaccessible data causing this
// violation.
let faulting_addr = if record.ExceptionCode == EXCEPTION_ACCESS_VIOLATION {
assert!(record.NumberParameters >= 2);
Some(record.ExceptionInformation[1])
} else {
None
};
match info.test_if_trap(regs, faulting_addr, |handler| handler(exception_info)) {
TrapTest::NotWasm => ExceptionContinueSearch,
TrapTest::HandledByEmbedder => ExceptionContinueExecution,
TrapTest::Trap { jmp_buf } => wasmtime_longjmp(jmp_buf),
}
})
}
pub fn lazy_per_thread_init() {
// Unused on Windows
}
@@ -1,12 +0,0 @@
#[cfg(feature = "threads")]
mod parking_spot;
#[cfg(feature = "threads")]
mod shared_memory;
#[cfg(feature = "threads")]
pub use shared_memory::SharedMemory;
#[cfg(not(feature = "threads"))]
mod shared_memory_disabled;
#[cfg(not(feature = "threads"))]
pub use shared_memory_disabled::SharedMemory;
@@ -1,104 +0,0 @@
#![allow(missing_docs)]
use crate::prelude::*;
use crate::runtime::vm::{RuntimeLinearMemory, VMMemoryDefinition, VMStore, WaitResult};
use core::ops::Range;
use core::time::Duration;
use wasmtime_environ::{Trap, Tunables};
#[derive(Clone)]
pub enum SharedMemory {}
impl SharedMemory {
pub fn wrap(
_ty: &wasmtime_environ::Memory,
_tunables: &Tunables,
_memory: Box<dyn RuntimeLinearMemory>,
) -> Result<Self> {
bail!("support for shared memories was disabled at compile time");
}
pub fn ty(&self) -> wasmtime_environ::Memory {
match *self {}
}
pub fn as_memory(self) -> crate::runtime::vm::Memory {
match self {}
}
pub fn vmmemory_ptr(&self) -> *const VMMemoryDefinition {
match *self {}
}
pub fn grow(
&self,
_delta_pages: u64,
_store: Option<&mut dyn VMStore>,
) -> Result<Option<(usize, usize)>> {
match *self {}
}
pub fn atomic_notify(&self, _addr_index: u64, _count: u32) -> Result<u32, Trap> {
match *self {}
}
pub fn atomic_wait32(
&self,
_addr_index: u64,
_expected: u32,
_timeout: Option<Duration>,
) -> Result<WaitResult, Trap> {
match *self {}
}
pub fn atomic_wait64(
&self,
_addr_index: u64,
_expected: u64,
_timeout: Option<Duration>,
) -> Result<WaitResult, Trap> {
match *self {}
}
}
impl RuntimeLinearMemory for SharedMemory {
fn page_size_log2(&self) -> u8 {
match *self {}
}
fn byte_size(&self) -> usize {
match *self {}
}
fn maximum_byte_size(&self) -> Option<usize> {
match *self {}
}
fn grow(
&mut self,
_delta_pages: u64,
_store: Option<&mut dyn VMStore>,
) -> Result<Option<(usize, usize)>> {
match *self {}
}
fn grow_to(&mut self, _size: usize) -> Result<()> {
match *self {}
}
fn vmmemory(&mut self) -> VMMemoryDefinition {
match *self {}
}
fn needs_init(&self) -> bool {
match *self {}
}
fn as_any_mut(&mut self) -> &mut dyn core::any::Any {
match *self {}
}
fn wasm_accessible(&self) -> Range<usize> {
match *self {}
}
}
@@ -1,799 +0,0 @@
//! WebAssembly trap handling, which is built on top of the lower-level
//! signalhandling mechanisms.
mod backtrace;
#[cfg(feature = "coredump")]
#[path = "traphandlers/coredump_enabled.rs"]
mod coredump;
#[cfg(not(feature = "coredump"))]
#[path = "traphandlers/coredump_disabled.rs"]
mod coredump;
use crate::prelude::*;
use crate::runtime::module::lookup_code;
use crate::runtime::vm::sys::traphandlers;
use crate::runtime::vm::{Instance, VMContext, VMRuntimeLimits};
use crate::sync::RwLock;
use core::cell::{Cell, UnsafeCell};
use core::mem::MaybeUninit;
use core::ops::Range;
use core::ptr;
pub use self::backtrace::Backtrace;
pub use self::coredump::CoreDumpStack;
pub use self::tls::{tls_eager_initialize, AsyncWasmCallState, PreviousAsyncWasmCallState};
pub use traphandlers::SignalHandler;
/// Platform-specific trap-handler state.
///
/// This state is protected by a lock to synchronize access to it. Right now
/// it's a `RwLock` but it could be a `Mutex`, and `RwLock` is just chosen for
/// convenience as it's what's implemented in no_std. The performance here
/// should not be of consequence.
///
/// This is initialized to `None` and then set as part of `init_traps`.
static TRAP_HANDLER: RwLock<Option<traphandlers::TrapHandler>> = RwLock::new(None);
/// This function is required to be called before any WebAssembly is entered.
/// This will configure global state such as signal handlers to prepare the
/// process to receive wasm traps.
///
/// # Panics
///
/// This function will panic on macOS if it is called twice or more times with
/// different values of `macos_use_mach_ports`.
///
/// This function will also panic if the `std` feature is disabled and it's
/// called concurrently.
pub fn init_traps(macos_use_mach_ports: bool) {
let mut lock = TRAP_HANDLER.write();
match lock.as_mut() {
Some(state) => state.validate_config(macos_use_mach_ports),
None => *lock = Some(unsafe { traphandlers::TrapHandler::new(macos_use_mach_ports) }),
}
}
/// De-initializes platform-specific state for trap handling.
///
/// # Panics
///
/// This function will also panic if the `std` feature is disabled and it's
/// called concurrently.
///
/// # Aborts
///
/// This may abort the process on some platforms where trap handling state
/// cannot be unloaded.
///
/// # Unsafety
///
/// This is not safe to be called unless all wasm code is unloaded. This is not
/// safe to be called on some platforms, like Unix, when other libraries
/// installed their own signal handlers after `init_traps` was called.
///
/// There's more reasons for unsafety here than those articulated above,
/// generally this can only be called "if you know what you're doing".
pub unsafe fn deinit_traps() {
let mut lock = TRAP_HANDLER.write();
let _ = lock.take();
}
fn lazy_per_thread_init() {
traphandlers::lazy_per_thread_init();
}
/// Raises a trap immediately.
///
/// This function performs as-if a wasm trap was just executed. This trap
/// payload is then returned from `catch_traps` below.
///
/// # Safety
///
/// Only safe to call when wasm code is on the stack, aka `catch_traps` must
/// have been previously called. Additionally no Rust destructors can be on the
/// stack. They will be skipped and not executed.
pub unsafe fn raise_trap(reason: TrapReason) -> ! {
tls::with(|info| info.unwrap().unwind_with(UnwindReason::Trap(reason)))
}
/// Raises a user-defined trap immediately.
///
/// This function performs as-if a wasm trap was just executed, only the trap
/// has a dynamic payload associated with it which is user-provided. This trap
/// payload is then returned from `catch_traps` below.
///
/// # Safety
///
/// Only safe to call when wasm code is on the stack, aka `catch_traps` must
/// have been previously called. Additionally no Rust destructors can be on the
/// stack. They will be skipped and not executed.
pub unsafe fn raise_user_trap(error: Error, needs_backtrace: bool) -> ! {
raise_trap(TrapReason::User {
error,
needs_backtrace,
})
}
/// Invokes the closure `f` and returns the result.
///
/// If `f` panics and this crate is compiled with `panic=unwind` this will
/// catch the panic and capture it to "throw" with `longjmp` to be caught by
/// the nearest `setjmp`. The panic will then be resumed from where it is
/// caught.
///
/// # Safety
///
/// Only safe to call when wasm code is on the stack, aka `catch_traps` must
/// have been previously called. Additionally no Rust destructors can be on the
/// stack. They will be skipped and not executed in the case that `f` panics.
pub unsafe fn catch_unwind_and_longjmp<R>(f: impl FnOnce() -> R) -> R {
// With `panic=unwind` use `std::panic::catch_unwind` to catch possible
// panics to rethrow.
#[cfg(all(feature = "std", panic = "unwind"))]
{
match std::panic::catch_unwind(std::panic::AssertUnwindSafe(f)) {
Ok(ret) => ret,
Err(err) => tls::with(|info| info.unwrap().unwind_with(UnwindReason::Panic(err))),
}
}
// With `panic=abort` there's no use in using `std::panic::catch_unwind`
// since it won't actually catch anything. Note that
// `std::panic::catch_unwind` will technically optimize to this but having
// this branch avoids using the `std::panic` module entirely.
#[cfg(not(all(feature = "std", panic = "unwind")))]
{
f()
}
}
/// Stores trace message with backtrace.
#[derive(Debug)]
pub struct Trap {
/// Original reason from where this trap originated.
pub reason: TrapReason,
/// Wasm backtrace of the trap, if any.
pub backtrace: Option<Backtrace>,
/// The Wasm Coredump, if any.
pub coredumpstack: Option<CoreDumpStack>,
}
/// Enumeration of different methods of raising a trap.
#[derive(Debug)]
pub enum TrapReason {
/// A user-raised trap through `raise_user_trap`.
User {
/// The actual user trap error.
error: Error,
/// Whether we need to capture a backtrace for this error or not.
needs_backtrace: bool,
},
/// A trap raised from Cranelift-generated code.
Jit {
/// The program counter where this trap originated.
///
/// This is later used with side tables from compilation to translate
/// the trapping address to a trap code.
pc: usize,
/// If the trap was a memory-related trap such as SIGSEGV then this
/// field will contain the address of the inaccessible data.
///
/// Note that wasm loads/stores are not guaranteed to fill in this
/// information. Dynamically-bounds-checked memories, for example, will
/// not access an invalid address but may instead load from NULL or may
/// explicitly jump to a `ud2` instruction. This is only available for
/// fault-based traps which are one of the main ways, but not the only
/// way, to run wasm.
faulting_addr: Option<usize>,
/// The trap code associated with this trap.
trap: wasmtime_environ::Trap,
},
/// A trap raised from a wasm libcall
Wasm(wasmtime_environ::Trap),
}
impl TrapReason {
/// Create a new `TrapReason::User` that does not have a backtrace yet.
pub fn user_without_backtrace(error: Error) -> Self {
TrapReason::User {
error,
needs_backtrace: true,
}
}
}
impl From<Error> for TrapReason {
fn from(err: Error) -> Self {
TrapReason::user_without_backtrace(err)
}
}
impl From<wasmtime_environ::Trap> for TrapReason {
fn from(code: wasmtime_environ::Trap) -> Self {
TrapReason::Wasm(code)
}
}
pub(crate) struct TrapRegisters {
pub pc: usize,
pub fp: usize,
}
/// Return value from `test_if_trap`.
pub(crate) enum TrapTest {
/// Not a wasm trap, need to delegate to whatever process handler is next.
NotWasm,
/// This trap was handled by the embedder via custom embedding APIs.
HandledByEmbedder,
/// This is a wasm trap, it needs to be handled.
#[cfg_attr(miri, allow(dead_code))]
Trap {
/// How to longjmp back to the original wasm frame.
jmp_buf: *const u8,
},
}
/// Catches any wasm traps that happen within the execution of `closure`,
/// returning them as a `Result`.
///
/// Highly unsafe since `closure` won't have any dtors run.
pub unsafe fn catch_traps<F>(
signal_handler: Option<*const SignalHandler<'static>>,
capture_backtrace: bool,
capture_coredump: bool,
async_guard_range: Range<*mut u8>,
caller: *mut VMContext,
mut closure: F,
) -> Result<(), Box<Trap>>
where
F: FnMut(*mut VMContext),
{
let limits = Instance::from_vmctx(caller, |i| i.runtime_limits());
let result = CallThreadState::new(
signal_handler,
capture_backtrace,
capture_coredump,
*limits,
async_guard_range,
)
.with(|cx| {
traphandlers::wasmtime_setjmp(
cx.jmp_buf.as_ptr(),
call_closure::<F>,
&mut closure as *mut F as *mut u8,
caller,
)
});
return match result {
Ok(x) => Ok(x),
Err((UnwindReason::Trap(reason), backtrace, coredumpstack)) => Err(Box::new(Trap {
reason,
backtrace,
coredumpstack,
})),
#[cfg(all(feature = "std", panic = "unwind"))]
Err((UnwindReason::Panic(panic), _, _)) => std::panic::resume_unwind(panic),
};
extern "C" fn call_closure<F>(payload: *mut u8, caller: *mut VMContext)
where
F: FnMut(*mut VMContext),
{
unsafe { (*(payload as *mut F))(caller) }
}
}
// Module to hide visibility of the `CallThreadState::prev` field and force
// usage of its accessor methods.
mod call_thread_state {
use super::*;
/// Temporary state stored on the stack which is registered in the `tls` module
/// below for calls into wasm.
pub struct CallThreadState {
pub(super) unwind:
UnsafeCell<MaybeUninit<(UnwindReason, Option<Backtrace>, Option<CoreDumpStack>)>>,
pub(super) jmp_buf: Cell<*const u8>,
pub(super) signal_handler: Option<*const SignalHandler<'static>>,
pub(super) capture_backtrace: bool,
#[cfg(feature = "coredump")]
pub(super) capture_coredump: bool,
pub(crate) limits: *const VMRuntimeLimits,
pub(super) prev: Cell<tls::Ptr>,
#[cfg_attr(any(windows, miri), allow(dead_code))]
pub(crate) async_guard_range: Range<*mut u8>,
// The values of `VMRuntimeLimits::last_wasm_{exit_{pc,fp},entry_sp}`
// for the *previous* `CallThreadState` for this same store/limits. Our
// *current* last wasm PC/FP/SP are saved in `self.limits`. We save a
// copy of the old registers here because the `VMRuntimeLimits`
// typically doesn't change across nested calls into Wasm (i.e. they are
// typically calls back into the same store and `self.limits ==
// self.prev.limits`) and we must to maintain the list of
// contiguous-Wasm-frames stack regions for backtracing purposes.
old_last_wasm_exit_fp: Cell<usize>,
old_last_wasm_exit_pc: Cell<usize>,
old_last_wasm_entry_fp: Cell<usize>,
}
impl Drop for CallThreadState {
fn drop(&mut self) {
unsafe {
*(*self.limits).last_wasm_exit_fp.get() = self.old_last_wasm_exit_fp.get();
*(*self.limits).last_wasm_exit_pc.get() = self.old_last_wasm_exit_pc.get();
*(*self.limits).last_wasm_entry_fp.get() = self.old_last_wasm_entry_fp.get();
}
}
}
impl CallThreadState {
#[inline]
pub(super) fn new(
signal_handler: Option<*const SignalHandler<'static>>,
capture_backtrace: bool,
capture_coredump: bool,
limits: *const VMRuntimeLimits,
async_guard_range: Range<*mut u8>,
) -> CallThreadState {
let _ = capture_coredump;
CallThreadState {
unwind: UnsafeCell::new(MaybeUninit::uninit()),
jmp_buf: Cell::new(ptr::null()),
signal_handler,
capture_backtrace,
#[cfg(feature = "coredump")]
capture_coredump,
limits,
async_guard_range,
prev: Cell::new(ptr::null()),
old_last_wasm_exit_fp: Cell::new(unsafe { *(*limits).last_wasm_exit_fp.get() }),
old_last_wasm_exit_pc: Cell::new(unsafe { *(*limits).last_wasm_exit_pc.get() }),
old_last_wasm_entry_fp: Cell::new(unsafe { *(*limits).last_wasm_entry_fp.get() }),
}
}
/// Get the saved FP upon exit from Wasm for the previous `CallThreadState`.
pub fn old_last_wasm_exit_fp(&self) -> usize {
self.old_last_wasm_exit_fp.get()
}
/// Get the saved PC upon exit from Wasm for the previous `CallThreadState`.
pub fn old_last_wasm_exit_pc(&self) -> usize {
self.old_last_wasm_exit_pc.get()
}
/// Get the saved FP upon entry into Wasm for the previous `CallThreadState`.
pub fn old_last_wasm_entry_fp(&self) -> usize {
self.old_last_wasm_entry_fp.get()
}
/// Get the previous `CallThreadState`.
pub fn prev(&self) -> tls::Ptr {
self.prev.get()
}
#[inline]
pub(crate) unsafe fn push(&self) {
assert!(self.prev.get().is_null());
self.prev.set(tls::raw::replace(self));
}
#[inline]
pub(crate) unsafe fn pop(&self) {
let prev = self.prev.replace(ptr::null());
let head = tls::raw::replace(prev);
assert!(core::ptr::eq(head, self));
}
}
}
pub use call_thread_state::*;
enum UnwindReason {
#[cfg(all(feature = "std", panic = "unwind"))]
Panic(Box<dyn std::any::Any + Send>),
Trap(TrapReason),
}
impl CallThreadState {
#[inline]
fn with(
mut self,
closure: impl FnOnce(&CallThreadState) -> i32,
) -> Result<(), (UnwindReason, Option<Backtrace>, Option<CoreDumpStack>)> {
let ret = tls::set(&mut self, |me| closure(me));
if ret != 0 {
Ok(())
} else {
Err(unsafe { self.read_unwind() })
}
}
#[cold]
unsafe fn read_unwind(&self) -> (UnwindReason, Option<Backtrace>, Option<CoreDumpStack>) {
(*self.unwind.get()).as_ptr().read()
}
fn unwind_with(&self, reason: UnwindReason) -> ! {
let (backtrace, coredump) = match reason {
// Panics don't need backtraces. There is nowhere to attach the
// hypothetical backtrace to and it doesn't really make sense to try
// in the first place since this is a Rust problem rather than a
// Wasm problem.
#[cfg(all(feature = "std", panic = "unwind"))]
UnwindReason::Panic(_) => (None, None),
// And if we are just propagating an existing trap that already has
// a backtrace attached to it, then there is no need to capture a
// new backtrace either.
UnwindReason::Trap(TrapReason::User {
needs_backtrace: false,
..
}) => (None, None),
UnwindReason::Trap(_) => (
self.capture_backtrace(self.limits, None),
self.capture_coredump(self.limits, None),
),
};
unsafe {
(*self.unwind.get())
.as_mut_ptr()
.write((reason, backtrace, coredump));
traphandlers::wasmtime_longjmp(self.jmp_buf.get());
}
}
/// Trap handler using our thread-local state.
///
/// * `pc` - the program counter the trap happened at
/// * `call_handler` - a closure used to invoke the platform-specific
/// signal handler for each instance, if available.
///
/// Attempts to handle the trap if it's a wasm trap. Returns a few
/// different things:
///
/// * null - the trap didn't look like a wasm trap and should continue as a
/// trap
/// * 1 as a pointer - the trap was handled by a custom trap handler on an
/// instance, and the trap handler should quickly return.
/// * a different pointer - a jmp_buf buffer to longjmp to, meaning that
/// the wasm trap was successfully handled.
#[cfg_attr(miri, allow(dead_code))] // miri doesn't handle traps yet
pub(crate) fn test_if_trap(
&self,
regs: TrapRegisters,
faulting_addr: Option<usize>,
call_handler: impl Fn(&SignalHandler) -> bool,
) -> TrapTest {
// If we haven't even started to handle traps yet, bail out.
if self.jmp_buf.get().is_null() {
return TrapTest::NotWasm;
}
// First up see if any instance registered has a custom trap handler,
// in which case run them all. If anything handles the trap then we
// return that the trap was handled.
if let Some(handler) = self.signal_handler {
if unsafe { call_handler(&*handler) } {
return TrapTest::HandledByEmbedder;
}
}
// If this fault wasn't in wasm code, then it's not our problem
let Some((code, text_offset)) = lookup_code(regs.pc) else {
return TrapTest::NotWasm;
};
let Some(trap) = code.lookup_trap_code(text_offset) else {
return TrapTest::NotWasm;
};
self.set_jit_trap(regs, faulting_addr, trap);
// If all that passed then this is indeed a wasm trap, so return the
// `jmp_buf` passed to `wasmtime_longjmp` to resume.
TrapTest::Trap {
jmp_buf: self.take_jmp_buf(),
}
}
pub(crate) fn take_jmp_buf(&self) -> *const u8 {
self.jmp_buf.replace(ptr::null())
}
#[cfg_attr(miri, allow(dead_code))] // miri doesn't handle traps yet
pub(crate) fn set_jit_trap(
&self,
TrapRegisters { pc, fp, .. }: TrapRegisters,
faulting_addr: Option<usize>,
trap: wasmtime_environ::Trap,
) {
let backtrace = self.capture_backtrace(self.limits, Some((pc, fp)));
let coredump = self.capture_coredump(self.limits, Some((pc, fp)));
unsafe {
(*self.unwind.get()).as_mut_ptr().write((
UnwindReason::Trap(TrapReason::Jit {
pc,
faulting_addr,
trap,
}),
backtrace,
coredump,
));
}
}
fn capture_backtrace(
&self,
limits: *const VMRuntimeLimits,
trap_pc_and_fp: Option<(usize, usize)>,
) -> Option<Backtrace> {
if !self.capture_backtrace {
return None;
}
Some(unsafe { Backtrace::new_with_trap_state(limits, self, trap_pc_and_fp) })
}
pub(crate) fn iter<'a>(&'a self) -> impl Iterator<Item = &'a Self> + 'a {
let mut state = Some(self);
core::iter::from_fn(move || {
let this = state?;
state = unsafe { this.prev().as_ref() };
Some(this)
})
}
}
// A private inner module for managing the TLS state that we require across
// calls in wasm. The WebAssembly code is called from C++ and then a trap may
// happen which requires us to read some contextual state to figure out what to
// do with the trap. This `tls` module is used to persist that information from
// the caller to the trap site.
pub(crate) mod tls {
use super::CallThreadState;
use core::mem;
use core::ops::Range;
pub use raw::Ptr;
// An even *more* inner module for dealing with TLS. This actually has the
// thread local variable and has functions to access the variable.
//
// Note that this is specially done to fully encapsulate that the accessors
// for tls may or may not be inlined. Wasmtime's async support employs stack
// switching which can resume execution on different OS threads. This means
// that borrows of our TLS pointer must never live across accesses because
// otherwise the access may be split across two threads and cause unsafety.
//
// This also means that extra care is taken by the runtime to save/restore
// these TLS values when the runtime may have crossed threads.
//
// Note, though, that if async support is disabled at compile time then
// these functions are free to be inlined.
pub(super) mod raw {
use super::CallThreadState;
use sptr::Strict;
pub type Ptr = *const CallThreadState;
const _: () = {
assert!(core::mem::align_of::<CallThreadState>() > 1);
};
fn tls_get() -> (Ptr, bool) {
let mut initialized = false;
let p = Strict::map_addr(crate::runtime::vm::sys::tls_get(), |a| {
initialized = (a & 1) != 0;
a & !1
});
(p.cast(), initialized)
}
fn tls_set(ptr: Ptr, initialized: bool) {
let encoded = Strict::map_addr(ptr, |a| a | usize::from(initialized));
crate::runtime::vm::sys::tls_set(encoded.cast_mut().cast::<u8>());
}
#[cfg_attr(feature = "async", inline(never))] // see module docs
#[cfg_attr(not(feature = "async"), inline)]
pub fn replace(val: Ptr) -> Ptr {
// When a new value is configured that means that we may be
// entering WebAssembly so check to see if this thread has
// performed per-thread initialization for traps.
let (prev, initialized) = tls_get();
if !initialized {
super::super::lazy_per_thread_init();
}
tls_set(val, true);
prev
}
/// Eagerly initialize thread-local runtime functionality. This will be performed
/// lazily by the runtime if users do not perform it eagerly.
#[cfg_attr(feature = "async", inline(never))] // see module docs
#[cfg_attr(not(feature = "async"), inline)]
pub fn initialize() {
let (state, initialized) = tls_get();
if initialized {
return;
}
super::super::lazy_per_thread_init();
tls_set(state, true);
}
#[cfg_attr(feature = "async", inline(never))] // see module docs
#[cfg_attr(not(feature = "async"), inline)]
pub fn get() -> Ptr {
tls_get().0
}
}
pub use raw::initialize as tls_eager_initialize;
/// Opaque state used to persist the state of the `CallThreadState`
/// activations associated with a fiber stack that's used as part of an
/// async wasm call.
pub struct AsyncWasmCallState {
// The head of a linked list of activations that are currently present
// on an async call's fiber stack. This pointer points to the oldest
// activation frame where the `prev` links internally link to younger
// activation frames.
//
// When pushed onto a thread this linked list is traversed to get pushed
// onto the current thread at the time.
state: raw::Ptr,
}
impl AsyncWasmCallState {
/// Creates new state that initially starts as null.
pub fn new() -> AsyncWasmCallState {
AsyncWasmCallState {
state: core::ptr::null_mut(),
}
}
/// Pushes the saved state of this wasm's call onto the current thread's
/// state.
///
/// This will iterate over the linked list of states stored within
/// `self` and push them sequentially onto the current thread's
/// activation list.
///
/// The returned `PreviousAsyncWasmCallState` captures the state of this
/// thread just before this operation, and it must have its `restore`
/// method called to restore the state when the async wasm is suspended
/// from.
///
/// # Unsafety
///
/// Must be carefully coordinated with
/// `PreviousAsyncWasmCallState::restore` and fiber switches to ensure
/// that this doesn't push stale data and the data is popped
/// appropriately.
pub unsafe fn push(self) -> PreviousAsyncWasmCallState {
// Our `state` pointer is a linked list of oldest-to-youngest so by
// pushing in order of the list we restore the youngest-to-oldest
// list as stored in the state of this current thread.
let ret = PreviousAsyncWasmCallState { state: raw::get() };
let mut ptr = self.state;
while let Some(state) = ptr.as_ref() {
ptr = state.prev.replace(core::ptr::null_mut());
state.push();
}
ret
}
/// Performs a runtime check that this state is indeed null.
pub fn assert_null(&self) {
assert!(self.state.is_null());
}
/// Asserts that the current CallThreadState pointer, if present, is not
/// in the `range` specified.
///
/// This is used when exiting a future in Wasmtime to assert that the
/// current CallThreadState pointer does not point within the stack
/// we're leaving (e.g. allocated for a fiber).
pub fn assert_current_state_not_in_range(range: Range<usize>) {
let p = raw::get() as usize;
assert!(p < range.start || range.end < p);
}
}
/// Opaque state used to help control TLS state across stack switches for
/// async support.
pub struct PreviousAsyncWasmCallState {
// The head of a linked list, similar to the TLS state. Note though that
// this list is stored in reverse order to assist with `push` and `pop`
// below.
//
// After a `push` call this stores the previous head for the current
// thread so we know when to stop popping during a `pop`.
state: raw::Ptr,
}
impl PreviousAsyncWasmCallState {
/// Pops a fiber's linked list of activations and stores them in
/// `AsyncWasmCallState`.
///
/// This will pop the top activation of this current thread continuously
/// until it reaches whatever the current activation was when `push` was
/// originally called.
///
/// # Unsafety
///
/// Must be paired with a `push` and only performed at a time when a
/// fiber is being suspended.
pub unsafe fn restore(self) -> AsyncWasmCallState {
let thread_head = self.state;
mem::forget(self);
let mut ret = AsyncWasmCallState::new();
loop {
// If the current TLS state is as we originally found it, then
// this loop is finished.
let ptr = raw::get();
if ptr == thread_head {
break ret;
}
// Pop this activation from the current thread's TLS state, and
// then afterwards push it onto our own linked list within this
// `AsyncWasmCallState`. Note that the linked list in `AsyncWasmCallState` is stored
// in reverse order so a subsequent `push` later on pushes
// everything in the right order.
(*ptr).pop();
if let Some(state) = ret.state.as_ref() {
(*ptr).prev.set(state);
}
ret.state = ptr;
}
}
}
impl Drop for PreviousAsyncWasmCallState {
fn drop(&mut self) {
panic!("must be consumed with `restore`");
}
}
/// Configures thread local state such that for the duration of the
/// execution of `closure` any call to `with` will yield `state`, unless
/// this is recursively called again.
#[inline]
pub fn set<R>(state: &mut CallThreadState, closure: impl FnOnce(&CallThreadState) -> R) -> R {
struct Reset<'a> {
state: &'a CallThreadState,
}
impl Drop for Reset<'_> {
#[inline]
fn drop(&mut self) {
unsafe {
self.state.pop();
}
}
}
unsafe {
state.push();
let reset = Reset { state };
closure(reset.state)
}
}
/// Returns the last pointer configured with `set` above, if any.
pub fn with<R>(closure: impl FnOnce(Option<&CallThreadState>) -> R) -> R {
let p = raw::get();
unsafe { closure(if p.is_null() { None } else { Some(&*p) }) }
}
}
@@ -1,267 +0,0 @@
//! Backtrace and stack walking functionality for Wasm.
//!
//! Walking the Wasm stack is comprised of
//!
//! 1. identifying sequences of contiguous Wasm frames on the stack
//! (i.e. skipping over native host frames), and
//!
//! 2. walking the Wasm frames within such a sequence.
//!
//! To perform (1) we maintain the entry stack pointer (SP) and exit frame
//! pointer (FP) and program counter (PC) each time we call into Wasm and Wasm
//! calls into the host via trampolines (see
//! `crates/wasmtime/src/runtime/vm/trampolines`). The most recent entry is
//! stored in `VMRuntimeLimits` and older entries are saved in
//! `CallThreadState`. This lets us identify ranges of contiguous Wasm frames on
//! the stack.
//!
//! To solve (2) and walk the Wasm frames within a region of contiguous Wasm
//! frames on the stack, we configure Cranelift's `preserve_frame_pointers =
//! true` setting. Then we can do simple frame pointer traversal starting at the
//! exit FP and stopping once we reach the entry SP (meaning that the next older
//! frame is a host frame).
use crate::prelude::*;
use crate::runtime::vm::arch;
use crate::runtime::vm::{
traphandlers::{tls, CallThreadState},
VMRuntimeLimits,
};
use core::ops::ControlFlow;
/// A WebAssembly stack trace.
#[derive(Debug)]
pub struct Backtrace(Vec<Frame>);
/// A stack frame within a Wasm stack trace.
#[derive(Debug)]
pub struct Frame {
pc: usize,
fp: usize,
}
impl Frame {
/// Get this frame's program counter.
pub fn pc(&self) -> usize {
self.pc
}
/// Get this frame's frame pointer.
pub fn fp(&self) -> usize {
self.fp
}
}
impl Backtrace {
/// Returns an empty backtrace
pub fn empty() -> Backtrace {
Backtrace(Vec::new())
}
/// Capture the current Wasm stack in a backtrace.
pub fn new(limits: *const VMRuntimeLimits) -> Backtrace {
tls::with(|state| match state {
Some(state) => unsafe { Self::new_with_trap_state(limits, state, None) },
None => Backtrace(vec![]),
})
}
/// Capture the current Wasm stack trace.
///
/// If Wasm hit a trap, and we calling this from the trap handler, then the
/// Wasm exit trampoline didn't run, and we use the provided PC and FP
/// instead of looking them up in `VMRuntimeLimits`.
pub(crate) unsafe fn new_with_trap_state(
limits: *const VMRuntimeLimits,
state: &CallThreadState,
trap_pc_and_fp: Option<(usize, usize)>,
) -> Backtrace {
let mut frames = vec![];
Self::trace_with_trap_state(limits, state, trap_pc_and_fp, |frame| {
frames.push(frame);
ControlFlow::Continue(())
});
Backtrace(frames)
}
/// Walk the current Wasm stack, calling `f` for each frame we walk.
pub fn trace(limits: *const VMRuntimeLimits, f: impl FnMut(Frame) -> ControlFlow<()>) {
tls::with(|state| match state {
Some(state) => unsafe { Self::trace_with_trap_state(limits, state, None, f) },
None => {}
});
}
/// Walk the current Wasm stack, calling `f` for each frame we walk.
///
/// If Wasm hit a trap, and we calling this from the trap handler, then the
/// Wasm exit trampoline didn't run, and we use the provided PC and FP
/// instead of looking them up in `VMRuntimeLimits`.
pub(crate) unsafe fn trace_with_trap_state(
limits: *const VMRuntimeLimits,
state: &CallThreadState,
trap_pc_and_fp: Option<(usize, usize)>,
mut f: impl FnMut(Frame) -> ControlFlow<()>,
) {
log::trace!("====== Capturing Backtrace ======");
let (last_wasm_exit_pc, last_wasm_exit_fp) = match trap_pc_and_fp {
// If we exited Wasm by catching a trap, then the Wasm-to-host
// trampoline did not get a chance to save the last Wasm PC and FP,
// and we need to use the plumbed-through values instead.
Some((pc, fp)) => {
assert!(core::ptr::eq(limits, state.limits));
(pc, fp)
}
// Either there is no Wasm currently on the stack, or we exited Wasm
// through the Wasm-to-host trampoline.
None => {
let pc = *(*limits).last_wasm_exit_pc.get();
let fp = *(*limits).last_wasm_exit_fp.get();
(pc, fp)
}
};
let activations = core::iter::once((
last_wasm_exit_pc,
last_wasm_exit_fp,
*(*limits).last_wasm_entry_fp.get(),
))
.chain(
state
.iter()
.filter(|state| core::ptr::eq(limits, state.limits))
.map(|state| {
(
state.old_last_wasm_exit_pc(),
state.old_last_wasm_exit_fp(),
state.old_last_wasm_entry_fp(),
)
}),
)
.take_while(|&(pc, fp, sp)| {
if pc == 0 {
debug_assert_eq!(fp, 0);
debug_assert_eq!(sp, 0);
}
pc != 0
});
for (pc, fp, sp) in activations {
if let ControlFlow::Break(()) = Self::trace_through_wasm(pc, fp, sp, &mut f) {
log::trace!("====== Done Capturing Backtrace (closure break) ======");
return;
}
}
log::trace!("====== Done Capturing Backtrace (reached end of activations) ======");
}
/// Walk through a contiguous sequence of Wasm frames starting with the
/// frame at the given PC and FP and ending at `trampoline_sp`.
unsafe fn trace_through_wasm(
mut pc: usize,
mut fp: usize,
trampoline_fp: usize,
mut f: impl FnMut(Frame) -> ControlFlow<()>,
) -> ControlFlow<()> {
log::trace!("=== Tracing through contiguous sequence of Wasm frames ===");
log::trace!("trampoline_fp = 0x{:016x}", trampoline_fp);
log::trace!(" initial pc = 0x{:016x}", pc);
log::trace!(" initial fp = 0x{:016x}", fp);
// We already checked for this case in the `trace_with_trap_state`
// caller.
assert_ne!(pc, 0);
assert_ne!(fp, 0);
assert_ne!(trampoline_fp, 0);
// This loop will walk the linked list of frame pointers starting at
// `fp` and going up until `trampoline_fp`. We know that both `fp` and
// `trampoline_fp` are "trusted values" aka generated and maintained by
// Cranelift. This means that it should be safe to walk the linked list
// of pointers and inspect wasm frames.
//
// Note, though, that any frames outside of this range are not
// guaranteed to have valid frame pointers. For example native code
// might be using the frame pointer as a general purpose register. Thus
// we need to be careful to only walk frame pointers in this one
// contiguous linked list.
//
// To know when to stop iteration all architectures' stacks currently
// look something like this:
//
// | ... |
// | Native Frames |
// | ... |
// |-------------------|
// | ... | <-- Trampoline FP |
// | Trampoline Frame | |
// | ... | <-- Trampoline SP |
// |-------------------| Stack
// | Return Address | Grows
// | Previous FP | <-- Wasm FP Down
// | ... | |
// | Wasm Frames | |
// | ... | V
//
// The trampoline records its own frame pointer (`trampoline_fp`),
// which is guaranteed to be above all Wasm. To check when we've
// reached the trampoline frame, it is therefore sufficient to
// check when the next frame pointer is equal to `trampoline_fp`. Once
// that's hit then we know that the entire linked list has been
// traversed.
//
// Note that it might be possible that this loop doesn't execute at all.
// For example if the entry trampoline called wasm which `return_call`'d
// an imported function which is an exit trampoline, then
// `fp == trampoline_fp` on the entry of this function, meaning the loop
// won't actually execute anything.
while fp != trampoline_fp {
// At the start of each iteration of the loop, we know that `fp` is
// a frame pointer from Wasm code. Therefore, we know it is not
// being used as an extra general-purpose register, and it is safe
// dereference to get the PC and the next older frame pointer.
//
// The stack also grows down, and therefore any frame pointer we are
// dealing with should be less than the frame pointer on entry to
// Wasm. Finally also assert that it's aligned correctly as an
// additional sanity check.
assert!(trampoline_fp > fp, "{trampoline_fp:#x} > {fp:#x}");
arch::assert_fp_is_aligned(fp);
log::trace!("--- Tracing through one Wasm frame ---");
log::trace!("pc = {:p}", pc as *const ());
log::trace!("fp = {:p}", fp as *const ());
f(Frame { pc, fp })?;
pc = arch::get_next_older_pc_from_fp(fp);
// We rely on this offset being zero for all supported architectures
// in `crates/cranelift/src/component/compiler.rs` when we set the
// Wasm exit FP. If this ever changes, we will need to update that
// code as well!
assert_eq!(arch::NEXT_OLDER_FP_FROM_FP_OFFSET, 0);
// Get the next older frame pointer from the current Wasm frame
// pointer.
let next_older_fp = *(fp as *mut usize).add(arch::NEXT_OLDER_FP_FROM_FP_OFFSET);
// Because the stack always grows down, the older FP must be greater
// than the current FP.
assert!(next_older_fp > fp, "{next_older_fp:#x} > {fp:#x}");
fp = next_older_fp;
}
log::trace!("=== Done tracing contiguous sequence of Wasm frames ===");
ControlFlow::Continue(())
}
/// Iterate over the frames inside this backtrace.
pub fn frames<'a>(
&'a self,
) -> impl ExactSizeIterator<Item = &'a Frame> + DoubleEndedIterator + 'a {
self.0.iter()
}
}
File diff suppressed because it is too large. Load diff
-255
View File
@@ -1,255 +0,0 @@
//! Synchronization primitives for Wasmtime for `no_std`.
//!
//! These primitives are intended for use in `no_std` contexts are are not as
//! full-featured as the `std` brethren. Namely these panic and/or return an
//! error on contention. This serves to continue to be correct in the face of
//! actual multiple threads, but if a system actually has multiple threads then
//! something will need to change in the Wasmtime crate to enable the external
//! system to perform necessary synchronization.
//!
//! In the future if these primitives are not suitable we can switch to putting
//! relevant functions in the `capi.rs` module where we basically require
//! embedders to implement them instead of doing it ourselves here. It's unclear
//! if this will be necessary, so this is the chosen starting point.
//!
//! See a brief overview of this module in `sync_std.rs` as well.
use core::cell::UnsafeCell;
use core::mem::MaybeUninit;
use core::ops::{Deref, DerefMut};
use core::sync::atomic::{AtomicU32, AtomicU8, Ordering};
pub struct OnceLock<T> {
val: UnsafeCell<MaybeUninit<T>>,
state: AtomicU8,
}
unsafe impl<T: Send> Send for OnceLock<T> {}
unsafe impl<T: Sync> Sync for OnceLock<T> {}
const UNINITIALIZED: u8 = 0;
const INITIALIZING: u8 = 1;
const INITIALIZED: u8 = 2;
impl<T> OnceLock<T> {
pub const fn new() -> OnceLock<T> {
OnceLock {
state: AtomicU8::new(UNINITIALIZED),
val: UnsafeCell::new(MaybeUninit::uninit()),
}
}
pub fn get_or_init(&self, f: impl FnOnce() -> T) -> &T {
if let Some(ret) = self.get() {
return ret;
}
self.try_init::<()>(|| Ok(f())).unwrap()
}
pub fn get_or_try_init<E>(&self, f: impl FnOnce() -> Result<T, E>) -> Result<&T, E> {
if let Some(ret) = self.get() {
return Ok(ret);
}
self.try_init(f)
}
fn get(&self) -> Option<&T> {
if self.state.load(Ordering::Acquire) == INITIALIZED {
Some(unsafe { (*self.val.get()).assume_init_ref() })
} else {
None
}
}
#[cold]
fn try_init<E>(&self, f: impl FnOnce() -> Result<T, E>) -> Result<&T, E> {
match self.state.compare_exchange(
UNINITIALIZED,
INITIALIZING,
Ordering::Acquire,
Ordering::Acquire,
) {
Ok(UNINITIALIZED) => match f() {
Ok(val) => {
let ret = unsafe { &*(*self.val.get()).write(val) };
let prev = self.state.swap(INITIALIZED, Ordering::Release);
assert_eq!(prev, INITIALIZING);
Ok(ret)
}
Err(e) => match self.state.swap(UNINITIALIZED, Ordering::Release) {
INITIALIZING => Err(e),
_ => unreachable!(),
},
},
Err(INITIALIZING) => panic!("concurrent initialization only allowed with `std`"),
Err(INITIALIZED) => Ok(self.get().unwrap()),
_ => unreachable!(),
}
}
}
#[derive(Debug, Default)]
pub struct RwLock<T> {
val: UnsafeCell<T>,
state: AtomicU32,
}
unsafe impl<T: Send> Send for RwLock<T> {}
unsafe impl<T: Send + Sync> Sync for RwLock<T> {}
impl<T> RwLock<T> {
pub const fn new(val: T) -> RwLock<T> {
RwLock {
val: UnsafeCell::new(val),
state: AtomicU32::new(0),
}
}
pub fn read(&self) -> impl Deref<Target = T> + '_ {
const READER_LIMIT: u32 = u32::MAX / 2;
match self
.state
.fetch_update(Ordering::Acquire, Ordering::Acquire, |x| match x {
u32::MAX => None,
n => {
let next = n + 1;
if next < READER_LIMIT {
Some(next)
} else {
None
}
}
}) {
Ok(_) => RwLockReadGuard { lock: self },
Err(_) => panic!(
"concurrent read request while locked for writing, must use `std` to avoid panic"
),
}
}
pub fn write(&self) -> impl DerefMut<Target = T> + '_ {
match self
.state
.compare_exchange(0, u32::MAX, Ordering::Acquire, Ordering::Relaxed)
{
Ok(0) => RwLockWriteGuard { lock: self },
_ => panic!("concurrent write request, must use `std` to avoid panicking"),
}
}
}
struct RwLockReadGuard<'a, T> {
lock: &'a RwLock<T>,
}
impl<T> Deref for RwLockReadGuard<'_, T> {
type Target = T;
fn deref(&self) -> &T {
unsafe { &*self.lock.val.get() }
}
}
impl<T> Drop for RwLockReadGuard<'_, T> {
fn drop(&mut self) {
self.lock.state.fetch_sub(1, Ordering::Release);
}
}
struct RwLockWriteGuard<'a, T> {
lock: &'a RwLock<T>,
}
impl<T> Deref for RwLockWriteGuard<'_, T> {
type Target = T;
fn deref(&self) -> &T {
unsafe { &*self.lock.val.get() }
}
}
impl<T> DerefMut for RwLockWriteGuard<'_, T> {
fn deref_mut(&mut self) -> &mut T {
unsafe { &mut *self.lock.val.get() }
}
}
impl<T> Drop for RwLockWriteGuard<'_, T> {
fn drop(&mut self) {
match self.lock.state.swap(0, Ordering::Release) {
u32::MAX => {}
_ => unreachable!(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn smoke_once_lock() {
let lock = OnceLock::new();
assert!(lock.get().is_none());
assert_eq!(*lock.get_or_init(|| 1), 1);
assert_eq!(*lock.get_or_init(|| 2), 1);
assert_eq!(*lock.get_or_init(|| 3), 1);
assert_eq!(lock.get_or_try_init::<()>(|| Ok(3)), Ok(&1));
let lock = OnceLock::new();
assert_eq!(lock.get_or_try_init::<()>(|| Ok(3)), Ok(&3));
assert_eq!(*lock.get_or_init(|| 1), 3);
let lock = OnceLock::new();
assert_eq!(lock.get_or_try_init(|| Err(())), Err(()));
assert_eq!(*lock.get_or_init(|| 1), 1);
}
#[test]
fn smoke_rwlock() {
let lock = RwLock::new(1);
assert_eq!(*lock.read(), 1);
let a = lock.read();
let b = lock.read();
assert_eq!(*a, 1);
assert_eq!(*b, 1);
drop((a, b));
assert_eq!(*lock.write(), 1);
*lock.write() = 4;
assert_eq!(*lock.read(), 4);
assert_eq!(*lock.write(), 4);
let a = lock.read();
let b = lock.read();
assert_eq!(*a, 4);
assert_eq!(*b, 4);
drop((a, b));
}
#[test]
#[should_panic(expected = "concurrent write request")]
fn rwlock_panic_read_then_write() {
let lock = RwLock::new(1);
let _a = lock.read();
let _b = lock.write();
}
#[test]
#[should_panic(expected = "concurrent read request")]
fn rwlock_panic_write_then_read() {
let lock = RwLock::new(1);
let _a = lock.write();
let _b = lock.read();
}
#[test]
#[should_panic(expected = "concurrent write request")]
fn rwlock_panic_write_then_write() {
let lock = RwLock::new(1);
let _a = lock.write();
let _b = lock.write();
}
}
+1
View File
@@ -0,0 +1 @@
{"v":1}
+2362
View File
File diff suppressed because it is too large. Load diff
@@ -10,12 +10,13 @@
# See Cargo.toml.orig for the original contents.
[package]
edition = "2021"
rust-version = "1.80.0"
edition = "2024"
rust-version = "1.96.0"
name = "wasmtime"
version = "27.0.0"
version = "49.0.2"
authors = ["The Wasmtime Project Developers"]
build = "build.rs"
autolib = false
autobins = false
autoexamples = false
autotests = false
@@ -25,212 +26,11 @@ 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
[lib]
name = "wasmtime"
path = "src/lib.rs"
[dependencies.addr2line]
version = "0.24.1"
optional = true
default-features = false
[dependencies.anyhow]
version = "1.0.22"
default-features = false
[dependencies.async-trait]
version = "0.1.71"
optional = true
[dependencies.bitflags]
version = "2.0"
[dependencies.bumpalo]
version = "3.11.0"
[dependencies.cfg-if]
version = "1.0"
[dependencies.encoding_rs]
version = "0.8.31"
optional = true
[dependencies.fxprof-processed-profile]
version = "0.6.0"
optional = true
[dependencies.gimli]
version = "0.31.0"
features = ["read"]
optional = true
default-features = false
[dependencies.hashbrown]
version = "0.14"
default-features = false
[dependencies.indexmap]
version = "2.0.0"
default-features = false
[dependencies.libc]
version = "0.2.112"
default-features = true
[dependencies.libm]
version = "0.2.7"
[dependencies.log]
version = "0.4.8"
default-features = false
[dependencies.object]
version = "0.36"
features = [
"read_core",
"elf",
]
default-features = false
[dependencies.once_cell]
version = "1.12.0"
optional = true
[dependencies.paste]
version = "1.0.3"
[dependencies.postcard]
version = "1.0.8"
features = ["alloc"]
default-features = false
[dependencies.pulley-interpreter]
version = "=27.0.0"
optional = true
[dependencies.rayon]
version = "1.0"
optional = true
[dependencies.semver]
version = "1.0.17"
optional = true
default-features = false
[dependencies.serde]
version = "1.0.188"
features = ["alloc"]
default-features = false
[dependencies.serde_derive]
version = "1.0.188"
[dependencies.serde_json]
version = "1.0.80"
optional = true
[dependencies.smallvec]
version = "1.6.1"
features = ["union"]
optional = true
[dependencies.sptr]
version = "0.3.2"
[dependencies.target-lexicon]
version = "0.12.16"
[dependencies.wasm-encoder]
version = "0.219.1"
optional = true
[dependencies.wasmparser]
version = "0.219.1"
default-features = false
[dependencies.wasmtime-asm-macros]
version = "=27.0.0"
optional = true
[dependencies.wasmtime-cache]
version = "=27.0.0"
optional = true
[dependencies.wasmtime-component-macro]
version = "=27.0.0"
optional = true
[dependencies.wasmtime-component-util]
version = "=27.0.0"
optional = true
[dependencies.wasmtime-cranelift]
version = "=27.0.0"
optional = true
[dependencies.wasmtime-environ]
version = "=27.0.0"
[dependencies.wasmtime-fiber]
version = "=27.0.0"
optional = true
[dependencies.wasmtime-jit-debug]
version = "=27.0.0"
features = [
"gdb_jit_int",
"perf_jitdump",
]
optional = true
[dependencies.wasmtime-jit-icache-coherence]
version = "=27.0.0"
optional = true
[dependencies.wasmtime-slab]
version = "=27.0.0"
optional = true
[dependencies.wasmtime-versioned-export-macros]
version = "=27.0.0"
[dependencies.wasmtime-winch]
version = "=27.0.0"
optional = true
[dependencies.wasmtime-wmemcheck]
version = "=27.0.0"
optional = true
[dependencies.wat]
version = "1.219.1"
optional = true
[dev-dependencies.env_logger]
version = "0.11.5"
[dev-dependencies.proptest]
version = "1.0.0"
[dev-dependencies.rand]
version = "0.8.3"
features = ["small_rng"]
[dev-dependencies.tempfile]
version = "3.1.0"
[build-dependencies.cc]
version = "1.0"
optional = true
[build-dependencies.wasmtime-versioned-export-macros]
version = "=27.0.0"
optional = true
[features]
addr2line = [
"dep:addr2line",
@@ -242,27 +42,40 @@ all-arch = [
"wasmtime-winch?/all-arch",
"pulley",
]
anyhow = ["wasmtime-environ/anyhow"]
async = [
"dep:wasmtime-fiber",
"dep:async-trait",
"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",
"wasmtime-cranelift?/component-model",
"wasmtime-winch?/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",
@@ -271,15 +84,24 @@ coredump = [
cranelift = [
"dep:wasmtime-cranelift",
"std",
"wasmtime-unwinder/cranelift",
]
debug-builtins = [
"dep:wasmtime-jit-debug",
"std",
]
default = [
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",
@@ -293,32 +115,33 @@ default = [
"debug-builtins",
"runtime",
"component-model",
"component-model-async",
"threads",
"stack-switching",
"std",
"debug",
"compile-time-builtins",
"wit-parser",
]
demangle = [
"wasmtime-environ/demangle",
"std",
]
gc = [
"wasmtime-environ/gc",
"wasmtime-cranelift?/gc",
demangle = ["wasmtime-environ/demangle"]
gc = ["wasmtime-environ/gc"]
gc-copying = [
"gc",
"wasmtime-environ/gc-copying",
]
gc-drc = [
"gc",
"wasmtime-environ/gc-drc",
"wasmtime-cranelift?/gc-drc",
]
gc-null = [
"gc",
"wasmtime-environ/gc-null",
"wasmtime-cranelift?/gc-null",
]
incremental-cache = [
"wasmtime-cranelift?/incremental-cache",
"std",
incremental-cache = ["std"]
memory-protection-keys = [
"pooling-allocator",
"call-hook",
]
memory-protection-keys = ["pooling-allocator"]
parallel-compilation = [
"dep:rayon",
"std",
@@ -327,58 +150,337 @@ pooling-allocator = [
"runtime",
"std",
]
profile-pulley = [
"anyhow",
"profiling",
"pulley",
"pulley-interpreter/interp",
"pulley-interpreter/profile",
]
profiling = [
"dep:fxprof-processed-profile",
"dep:wasmtime-jit-debug",
"dep:ittapi",
"dep:rustix",
"rustix/thread",
"dep:serde_json",
"std",
"wasmtime-jit-debug/perf_jitdump",
]
pulley = [
"dep:pulley-interpreter",
"wasmtime-cranelift?/pulley",
]
pulley = []
reexport-wasmparser = []
rr = [
"component-model",
"wasmtime-environ/rr",
]
runtime = [
"dep:cc",
"dep:smallvec",
"dep:mach2",
"dep:memfd",
"dep:wasmtime-asm-macros",
"dep:wasmtime-jit-icache-coherence",
"dep:wasmtime-slab",
"dep:wasmtime-versioned-export-macros",
"dep:windows-sys",
"dep:psm",
"dep:rustix",
"rustix/mm",
"pulley-interpreter?/interp",
"pulley-interpreter/interp",
"dep:wasmtime-unwinder",
"dep:async-trait",
]
stack-switching = [
"runtime",
"std",
"wasmtime-environ/stack-switching",
]
std = [
"postcard/use-std",
"wasmtime-component-macro?/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 = [
"wasmtime-cranelift?/threads",
threads = ["std"]
trace-log = ["wasmtime-cranelift?/trace-log"]
wave = [
"dep:wasm-wave",
"component-model",
"std",
]
trace-log = ["wasmtime-cranelift?/trace-log"]
winch = [
"dep:wasmtime-winch",
"std",
]
wit-parser = [
"dep:wit-parser",
"component-model",
"std",
]
wmemcheck = [
"dep:wasmtime-wmemcheck",
"wasmtime-cranelift?/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
@@ -391,20 +493,12 @@ optional = true
version = "0.4.0"
optional = true
[target.'cfg(target_arch = "s390x")'.dependencies.psm]
version = "0.1.11"
optional = true
[target.'cfg(target_os = "linux")'.dependencies.memfd]
version = "0.6.2"
optional = true
[target.'cfg(target_os = "macos")'.dependencies.mach2]
version = "0.4.2"
version = "0.6.5"
optional = true
[target.'cfg(target_os = "windows")'.dependencies.windows-sys]
version = "0.59.0"
version = "0.61.2"
features = [
"Win32_Foundation",
"Win32_System_Kernel",
@@ -416,19 +510,33 @@ features = [
]
optional = true
[target.'cfg(target_vendor = "apple")'.dependencies.mach2]
version = "0.6"
optional = true
[target."cfg(unix)".dependencies.rustix]
version = "0.38.31"
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"
@@ -437,12 +545,19 @@ priority = -1
[lints.rust]
trivial_numeric_casts = "warn"
unstable_features = "warn"
unused-lifetimes = "warn"
unused-macro-rules = "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)"]
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)",
]
@@ -17,49 +17,48 @@ workspace = true
all-features = true
[dependencies]
wasmtime-asm-macros = { workspace = true, optional = true }
wasmtime-environ = { workspace = true }
wasmtime-jit-debug = { workspace = true, features = ["gdb_jit_int", "perf_jitdump"], optional = 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 }
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-slab = { workspace = true, optional = true }
wasmtime-versioned-export-macros = { workspace = true }
wasmtime-wmemcheck = { workspace = true, optional = true }
pulley-interpreter = { 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 }
anyhow = { workspace = 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 }
cfg-if = { workspace = true }
log = { workspace = true }
wat = { workspace = true, optional = true }
serde = { workspace = true }
serde_derive = { workspace = true }
serde_json = { workspace = true, optional = true }
sptr = { workspace = true }
postcard = { workspace = true }
indexmap = { workspace = true }
paste = "1.0.3"
once_cell = { version = "1.12.0", optional = true }
rayon = { version = "1.0", optional = true }
object = { workspace = 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 = "3.11.0"
fxprof-processed-profile = { version = "0.6.0", 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 }
hashbrown = { workspace = true }
libm = "0.2.7"
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
@@ -87,26 +86,38 @@ ittapi = { workspace = true, optional = true }
[target.'cfg(target_os = "linux")'.dependencies]
memfd = { workspace = true, optional = true }
[target.'cfg(target_os = "macos")'.dependencies]
[target.'cfg(target_vendor = "apple")'.dependencies]
mach2 = { workspace = true, optional = true }
[target.'cfg(unix)'.dependencies]
rustix = { workspace = true, optional = true }
[target.'cfg(target_arch = "s390x")'.dependencies]
psm = { workspace = true, optional = true }
rustix = { workspace = true, optional = true, features = ["mm", "param"] }
[dev-dependencies]
env_logger = { workspace = true }
proptest = { workspace = true }
rand = { workspace = true }
rand = { workspace = true, features = ['thread_rng'] }
tempfile = { workspace = true }
wasi-common = { path = "../wasi-common", default-features = 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.
@@ -118,9 +129,12 @@ wasmtime-versioned-export-macros = { workspace = true, optional = true }
# for a particular embedding.
[features]
default = [
'anyhow',
'async',
'backtrace',
'cache',
'gc',
'gc-copying',
'gc-drc',
'gc-null',
'wat',
@@ -134,15 +148,26 @@ default = [
'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"]
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
@@ -154,20 +179,25 @@ winch = ["dep:wasmtime-winch", "std"]
# 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 = ["dep:pulley-interpreter", "wasmtime-cranelift?/pulley"]
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 = ["wasmtime-cranelift?/incremental-cache", "std"]
incremental-cache = ["std"]
# Enables support for profiling guest modules.
profiling = [
"dep:fxprof-processed-profile",
"dep:wasmtime-jit-debug",
"dep:ittapi",
"dep:rustix",
"rustix/thread",
"dep:serde_json",
"std",
"wasmtime-jit-debug/perf_jitdump",
]
# Enables parallel compilation of WebAssembly code.
@@ -180,14 +210,15 @@ cache = ["dep:wasmtime-cache", "std"]
# `async fn` and calling functions asynchronously.
async = [
"dep:wasmtime-fiber",
"dep:async-trait",
"wasmtime-component-macro?/async",
"runtime",
"std",
]
# Enables support for the pooling instance allocation strategy
pooling-allocator = ["runtime", "std"]
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
@@ -199,8 +230,6 @@ all-arch = ["wasmtime-cranelift?/all-arch", "wasmtime-winch?/all-arch", "pulley"
# testing purposes.
component-model = [
"wasmtime-environ/component-model",
"wasmtime-cranelift?/component-model",
"wasmtime-winch?/component-model",
"dep:wasmtime-component-macro",
"dep:wasmtime-component-util",
"dep:encoding_rs",
@@ -209,7 +238,6 @@ component-model = [
wmemcheck = [
"dep:wasmtime-wmemcheck",
"wasmtime-cranelift?/wmemcheck",
"wasmtime-environ/wmemcheck",
"std",
]
@@ -219,14 +247,16 @@ trace-log = ["wasmtime-cranelift?/trace-log"]
# Enables support for demangling WebAssembly function names at runtime in
# errors such as backtraces.
demangle = ["wasmtime-environ/demangle", "std"]
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 = ["dep:wasmtime-jit-debug", "std"]
debug-builtins = [
"wasmtime-jit-debug/gdb_jit_int",
]
# Enable support for executing compiled Wasm modules.
runtime = [
@@ -234,15 +264,11 @@ runtime = [
"dep:smallvec",
"dep:mach2",
"dep:memfd",
"dep:wasmtime-asm-macros",
"dep:wasmtime-jit-icache-coherence",
"dep:wasmtime-slab",
"dep:wasmtime-versioned-export-macros",
"dep:windows-sys",
"dep:psm",
"dep:rustix",
"rustix/mm",
"pulley-interpreter?/interp",
"pulley-interpreter/interp",
"dep:wasmtime-unwinder",
"dep:async-trait",
]
# Enable support for garbage collection-related things.
@@ -261,17 +287,26 @@ runtime = [
# load and run Wasm that uses those proposals.
#
# You can additionally configure which GC implementations are enabled via the
# `gc-drc` and `gc-null` features.
gc = ["wasmtime-environ/gc", "wasmtime-cranelift?/gc"]
# `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", "wasmtime-cranelift?/gc-drc"]
gc-drc = ["gc", "wasmtime-environ/gc-drc"]
# Enable the null garbage collector.
gc-null = ["gc", "wasmtime-environ/gc-null", "wasmtime-cranelift?/gc-null"]
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 = ["wasmtime-cranelift?/threads", "std"]
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
@@ -285,10 +320,17 @@ addr2line = ["dep:addr2line", "dep:gimli", "std"]
# This will be automatically enabled if necessary.
std = [
'postcard/use-std',
'wasmtime-component-macro?/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
@@ -298,7 +340,12 @@ 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"]
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
@@ -315,3 +362,73 @@ memory-protection-keys = ["pooling-allocator"]
# 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"
]
File renamed without changes.
@@ -56,7 +56,7 @@ Wasmtime are:
[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-support.html
[Standards Compliant]: https://docs.wasmtime.dev/stability-wasm-proposals.html
## Example
@@ -115,5 +115,8 @@ 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
Loaded 100 of 346 files, more files were not shown because too many files have changed in this diff. Show more