use anyhow::Result;
use anyhow::bail;
use wasmtime::Config;
use wasmtime::Engine;
use wasmtime::Func;
use wasmtime::Memory;
use wasmtime::Module;
use super::PluginSchemaVersion;
use super::instance::Linker;
use super::instance::Store;
use super::instance::WasmHostState;
use super::instance::get_current_plugin_schema_version;
use super::instance::wasi::add_unsupported_wasi_imports;
pub struct WasmInstance {
inner: wasmtime::Instance,
_engine: wasmtime::Engine,
version: PluginSchemaVersion,
}
impl WasmInstance {
pub fn version(&self) -> PluginSchemaVersion {
self.version
}
pub fn set_token(&self, store: &mut Store, token: std::sync::Arc<dyn dprint_core::plugins::CancellationToken>) {
store.data_mut().set_token(token);
}
pub fn get_memory(&self, store: &mut Store, name: &str) -> Option<Memory> {
self.inner.get_memory(store, name)
}
pub fn get_function(&self, store: &mut Store, name: &str) -> Option<Func> {
self.inner.get_func(store, name)
}
}
pub fn load_instance(store: &mut Store, module: &WasmModule, mut linker: Linker) -> Result<WasmInstance> {
if module.inner.get_export("_start").is_some() && module.inner.get_export("_initialize").is_none() {
bail!(
"Error instantiating module: The plugin was built as a WASI command (it exports _start), but it must be built as a WASI reactor (exporting _initialize)."
);
}
if let Err(err) = add_unsupported_wasi_imports(&mut linker, store, &module.inner) {
bail!("Error instantiating module: {:#}", err);
}
let instance = match linker.instantiate(&mut *store, &module.inner) {
Ok(instance) => instance,
Err(err) => bail!("Error instantiating module: {:#}", err),
};
if let Some(memory) = instance.get_memory(&mut *store, "memory") {
store.data_mut().set_memory(memory);
}
if let Some(initialize) = instance.get_func(&mut *store, "_initialize")
&& let Err(err) = initialize.call(&mut *store, &[], &mut [])
{
bail!("Error initializing module: {:#}", err);
}
Ok(WasmInstance {
inner: instance,
_engine: module.engine.clone(),
version: module.version,
})
}
#[derive(Clone)]
pub struct WasmModule {
inner: wasmtime::Module,
engine: wasmtime::Engine,
version: PluginSchemaVersion,
}
impl WasmModule {
pub fn new(module: wasmtime::Module, engine: wasmtime::Engine) -> Result<Self> {
Ok(Self {
version: get_current_plugin_schema_version(&module)?,
inner: module,
engine,
})
}
pub fn version(&self) -> PluginSchemaVersion {
self.version
}
pub fn inner(&self) -> &wasmtime::Module {
&self.inner
}
pub fn engine(&self) -> &wasmtime::Engine {
&self.engine
}
pub fn new_store(&self, data: WasmHostState) -> Store {
Store::new(&self.engine, data)
}
}
pub fn precompile_compatibility_hash() -> u64 {
static HASH: std::sync::OnceLock<u64> = std::sync::OnceLock::new();
*HASH.get_or_init(|| {
use std::hash::Hash;
let mut hasher = crate::utils::FastInsecureHasher::default();
new_engine().precompile_compatibility_hash().hash(&mut hasher);
hasher.finish()
})
}
pub struct WasmModuleCreator {
engine: wasmtime::Engine,
}
impl Default for WasmModuleCreator {
fn default() -> Self {
Self { engine: new_engine() }
}
}
impl WasmModuleCreator {
pub fn create_from_wasm_bytes(&self, wasm_bytes: &[u8]) -> Result<WasmModule> {
let module = Module::new(&self.engine, wasm_bytes)?;
WasmModule::new(module, self.engine.clone())
}
pub fn create_from_serialized(&self, compiled_module_bytes: &[u8]) -> Result<WasmModule> {
unsafe {
match Module::deserialize(&self.engine, compiled_module_bytes) {
Ok(module) => WasmModule::new(module, self.engine.clone()),
Err(err) => bail!("Error deserializing compiled wasm module: {:#}", err),
}
}
}
}
pub const MAX_WASM_STACK_SIZE: usize = 1024 * 1024;
pub const WASM_PLUGIN_THREAD_STACK_SIZE: usize = MAX_WASM_STACK_SIZE + 3 * 1024 * 1024;
fn new_engine() -> wasmtime::Engine {
let mut config = Config::new();
#[cfg(not(use_pulley))]
{
config.cranelift_opt_level(wasmtime::OptLevel::Speed);
}
#[cfg(use_pulley)]
{
let pulley_target = if cfg!(target_endian = "big") { "pulley64be" } else { "pulley64" };
config.target(pulley_target).expect("failed to set pulley target");
}
config.max_wasm_stack(MAX_WASM_STACK_SIZE);
Engine::new(&config).expect("failed to create wasmtime engine")
}
#[cfg(test)]
mod tests {
use wasmtime::Val;
use super::super::instance::create_identity_import_object;
use super::*;
#[test]
fn initializes_wasi_reactor() {
let (mut store, instance) = load(
r#"(import "wasi_snapshot_preview1" "sock_shutdown" (func (param i32 i32) (result i32)))
(global $count (mut i32) (i32.const 0))
(func (export "_initialize") (global.set $count (i32.add (global.get $count) (i32.const 1))))
(func (export "get_count") (result i32) (global.get $count))"#,
)
.unwrap();
let mut results = [Val::I32(0)];
let get_count = instance.get_function(&mut store, "get_count").unwrap();
get_count.call(&mut store, &[], &mut results).unwrap();
assert_eq!(results[0].unwrap_i32(), 1);
}
#[test]
fn errors_when_initializing_fails() {
let err = load(
r#"(import "wasi_snapshot_preview1" "proc_exit" (func $proc_exit (param i32)))
(func (export "_initialize") (call $proc_exit (i32.const 1)))"#,
)
.err()
.unwrap();
let text = format!("{:#}", err);
assert!(text.starts_with("Error initializing module: "), "{}", text);
assert!(text.contains("The plugin attempted to exit with code 1."), "{}", text);
}
#[test]
fn errors_for_wasi_command() {
let err = load(r#"(func (export "_start"))"#).err().unwrap();
assert_eq!(
format!("{:#}", err),
"Error instantiating module: The plugin was built as a WASI command (it exports _start), but it must be built as a WASI reactor (exporting _initialize)."
);
assert!(load(r#"(func (export "_start")) (func (export "_initialize"))"#).is_ok());
}
#[test]
fn errors_for_unknown_import() {
let err = load(r#"(import "dprint" "host_unknown" (func))"#).err().unwrap();
let text = format!("{:#}", err);
assert!(text.starts_with("Error instantiating module: "), "{}", text);
assert!(text.contains("host_unknown"), "{}", text);
}
fn load(body: &str) -> Result<(Store, WasmInstance)> {
let wasm = wat::parse_str(format!(
r#"(module {} (func (export "dprint_plugin_version_4") (result i32) (i32.const 4)))"#,
body
))?;
let module = WasmModuleCreator::default().create_from_wasm_bytes(&wasm)?;
let linker = create_identity_import_object(module.version(), module.engine())?;
let mut store = module.new_store(WasmHostState::Empty);
let instance = load_instance(&mut store, &module, linker)?;
Ok((store, instance))
}
}