mirror of
https://github.com/HeyPuter/gex.git
synced 2026-11-13 20:12:43 -10:00
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:
commit
3718d0ca10
346 files changed
+88421
-40154
No files matched your search
Generated
+422
-298
File diff suppressed because it is too large.
Load diff
+2
-2
@@ -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" }
|
||||
@@ -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"] }
|
||||
@@ -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)
|
||||
|
||||
@@ -4,4 +4,4 @@ version.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
[dependencies]
|
||||
wasmtime = { version = "27", default-features = false, features = ["cranelift", "runtime", "std", "threads"] }
|
||||
wasmtime = { version = "49", default-features = false, features = ["cranelift", "runtime", "std", "threads", "gc", "gc-null", "anyhow"] }
|
||||
@@ -0,0 +1,6 @@
|
||||
# wasmtime 49 needs rustc 1.96 (the native engine, with wasm exception
|
||||
# handling for native Wine's setjmp: docs/wine-native.md §4.2).
|
||||
[toolchain]
|
||||
channel = "1.96.0"
|
||||
# the browser shim (shims/browser) builds for wasm32-unknown-unknown
|
||||
targets = ["wasm32-unknown-unknown"]
|
||||
@@ -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`.
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
//! [`REPL_THREADS`] stacks of [`REPL_STACK_BYTES`], one per guest thread by
|
||||
//! context slot. The region is recorded as a mapping so `fork` copies it.
|
||||
|
||||
use crate::WasmtimeContext;
|
||||
use super::{read_u64, write_u64, HostState};
|
||||
use anyhow::{anyhow, bail, Context, Result};
|
||||
use gex_abi::ctx;
|
||||
@@ -499,6 +500,11 @@ fn engine_config() -> wasmtime::Config {
|
||||
let mut config = wasmtime::Config::new();
|
||||
config.wasm_tail_call(true);
|
||||
config.wasm_threads(true);
|
||||
// wasmtime 49 creates no SharedMemory unless asked: the guest's memory is one.
|
||||
config.shared_memory(true);
|
||||
// Wasm exception handling: setjmp/longjmp in native Wine and wasm32
|
||||
// programs (clang's SjLj lowering), docs/wine-native.md §4.2.
|
||||
config.wasm_exceptions(true);
|
||||
config.wasm_bulk_memory(true);
|
||||
config.wasm_multi_memory(true);
|
||||
config
|
||||
@@ -609,9 +615,9 @@ pub(crate) fn call(caller: &mut Caller<'_, HostState>, ctx_addr: i32, pc: u64, i
|
||||
Abi::Ctx | Abi::Entry => {
|
||||
if let Err(e) = func.call(&mut *caller, &[Val::I32(ctx_addr)], &mut []) {
|
||||
if e.is::<super::StopExecution>() {
|
||||
return Err(e);
|
||||
return Err(anyhow::Error::from(e));
|
||||
}
|
||||
return Err(e.context(format!("replacement `{}` for {}", r.export, r.symbol)));
|
||||
return Err(anyhow::Error::from(e).context(format!("replacement `{}` for {}", r.export, r.symbol)));
|
||||
}
|
||||
}
|
||||
Abi::C => {
|
||||
|
||||
@@ -104,14 +104,14 @@ pub(crate) fn override_imports(linker: &mut Linker<HostState>) -> Result<()> {
|
||||
linker.func_wrap(
|
||||
IMPORT_MODULE,
|
||||
imports::DISPATCH_MISS,
|
||||
|mut caller: Caller<'_, HostState>, _ctx: i32, pc: i64| -> Result<i32> { Err(missing(&mut caller, pc as u64, "a dispatch")) },
|
||||
|mut caller: Caller<'_, HostState>, _ctx: i32, pc: i64| -> wasmtime::Result<i32> { crate::host((|| -> anyhow::Result<i32> { Err(missing(&mut caller, pc as u64, "a dispatch")) })()) },
|
||||
)?;
|
||||
// Leave: the root looks the target up, and stops the run if nothing
|
||||
// built ahead of time holds it.
|
||||
linker.func_wrap(
|
||||
IMPORT_MODULE,
|
||||
imports::CALL_OUT,
|
||||
|_caller: Caller<'_, HostState>, _ctx: i32, _target: i64, _ret: i64, _sp: i64| -> Result<i32> { Ok(0) },
|
||||
|_caller: Caller<'_, HostState>, _ctx: i32, _target: i64, _ret: i64, _sp: i64| -> wasmtime::Result<i32> { crate::host((|| -> anyhow::Result<i32> { Ok(0) })()) },
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -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 {
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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};
|
||||
|
||||
@@ -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(())
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
Vendored
-38
@@ -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
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
-3203
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
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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, ¶ms_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(¶m_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, ¶ms));
|
||||
|
||||
// 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(())
|
||||
}
|
||||
-2923
File diff suppressed because it is too large.
Load diff
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
-1030
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);
|
||||
}
|
||||
}
|
||||
@@ -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
@@ -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(())
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
-1554
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(())
|
||||
}
|
||||
}
|
||||
-95
@@ -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));
|
||||
}
|
||||
}
|
||||
-241
@@ -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(())
|
||||
}
|
||||
}
|
||||
-1464
File diff suppressed because it is too large.
Load diff
@@ -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
@@ -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>();
|
||||
}
|
||||
@@ -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;
|
||||
-104
@@ -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()
|
||||
}
|
||||
}
|
||||
-1269
File diff suppressed because it is too large.
Load diff
-255
@@ -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
@@ -0,0 +1 @@
|
||||
{"v":1}
|
||||
+2362
File diff suppressed because it is too large.
Load diff
+372
-257
@@ -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)",
|
||||
]
|
||||
Generated
Vendored
+166
-49
@@ -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.
+4
-1
@@ -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
Reference in new issue
Block a user