diff --git a/dotnet/nuget/Extism.runtime.win.csproj b/dotnet/nuget/Extism.runtime.win.csproj index dcb5ff372..589962d40 100644 --- a/dotnet/nuget/Extism.runtime.win.csproj +++ b/dotnet/nuget/Extism.runtime.win.csproj @@ -7,10 +7,11 @@ - Extism.runtime.win-x64 - 0.6.0 + + Extism.runtime.wasm-threads.win-x64 + 0.6.0-threads01 Extism Contributors - Internal implementation package for Extism to work on Windows x64 + Fork of the original Windows Extism 0.6.0 runtime with WASM threads feature enabled for Wasmtime, when EXTISM_WASM_THREADS env is passed. extism, wasm, plugin BSD-3-Clause diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml index 80d0e1cb1..ec6ec38dc 100644 --- a/runtime/Cargo.toml +++ b/runtime/Cargo.toml @@ -9,9 +9,10 @@ repository = "https://github.com/extism/extism" description = "Extism runtime component" [dependencies] -wasmtime = "8.0.0" -wasmtime-wasi = "8.0.0" -wasmtime-wasi-nn = {version = "8.0.0", optional=true} +wasmtime = "9.0.2" +wasmtime-wasi = "9.0.2" +wasmtime-wasi-nn = {version = "9.0.2", optional=true} +wasmtime-wasi-threads = "9.0.2" anyhow = "1" serde = {version = "1", features = ["derive"]} serde_json = "1" @@ -26,6 +27,7 @@ extism-manifest = { version = "0.3.0", path = "../manifest" } pretty-hex = { version = "0.3" } uuid = { version = "1", features = ["v4"] } libc = "0.2" +rand = "0.8.5" [features] default = ["http", "register-http", "register-filesystem"] diff --git a/runtime/src/internal.rs b/runtime/src/internal.rs index bf47a7919..e7ab58f18 100644 --- a/runtime/src/internal.rs +++ b/runtime/src/internal.rs @@ -1,14 +1,17 @@ use std::collections::BTreeMap; +use std::ptr::{null, null_mut}; +use std::sync::{Arc, Mutex, RwLock}; use crate::*; /// Internal stores data that is available to the caller in PDK functions +#[derive(Clone)] pub struct Internal { /// Call input length pub input_length: usize, /// Pointer to call input - pub input: *const u8, + pub input: Arc>>>, /// Memory offset that points to the output pub output_offset: usize, @@ -29,7 +32,7 @@ pub struct Internal { pub vars: BTreeMap>, /// A pointer to the plugin memory, this should mostly be used from the PDK - pub memory: *mut PluginMemory, + pub memory: Arc>>, } /// InternalExt provides a unified way of acessing `memory`, `store` and `internal` values @@ -65,6 +68,18 @@ pub struct Wasi { #[cfg(not(feature = "nn"))] pub nn: (), } +impl Clone for Wasi { + fn clone(&self) -> Self { + Self { + ctx: self.ctx.clone(), + // I'm not sure how to handle cloning here correctly. So we will just create the new instance. + #[cfg(feature = "nn")] + nn: wasmtime_wasi_nn::WasiNnCtx::new().unwrap(), + #[cfg(not(feature = "nn"))] + nn: (), + } + } +} impl Internal { pub(crate) fn new(manifest: &Manifest, wasi: bool) -> Result { @@ -101,9 +116,9 @@ impl Internal { input_length: 0, output_offset: 0, output_length: 0, - input: std::ptr::null(), + input: Arc::new(RwLock::from(None)), wasi, - memory: std::ptr::null_mut(), + memory: Arc::new(Mutex::new(None)), http_status: 0, last_error: std::cell::RefCell::new(None), vars: BTreeMap::new(), @@ -123,10 +138,12 @@ impl Internal { impl InternalExt for Internal { fn memory(&self) -> &PluginMemory { - unsafe { &*self.memory } + let lock = self.memory.lock().unwrap(); + lock.as_ref().unwrap() } fn memory_mut(&mut self) -> &mut PluginMemory { - unsafe { &mut *self.memory } + let mut lock = self.memory.lock().unwrap(); + lock.as_mut().unwrap() } } diff --git a/runtime/src/pdk.rs b/runtime/src/pdk.rs index ac2bb3109..7373a51a4 100644 --- a/runtime/src/pdk.rs +++ b/runtime/src/pdk.rs @@ -40,11 +40,14 @@ pub(crate) fn input_load_u8( output: &mut [Val], ) -> Result<(), Error> { let data: &Internal = caller.data(); - if data.input.is_null() { - return Ok(()); + return match &*data.input.read().unwrap() { + None => Ok(()), + Some(inputData) => { + let val = inputData.get(input[0].unwrap_i64() as usize).unwrap_or(&0); + output[0] = Val::I32(*val as i32); + Ok(()) + } } - output[0] = unsafe { Val::I32(*data.input.add(input[0].unwrap_i64() as usize) as i32) }; - Ok(()) } /// Load an unsigned 64 bit integer from input @@ -56,14 +59,16 @@ pub(crate) fn input_load_u64( output: &mut [Val], ) -> Result<(), Error> { let data: &Internal = caller.data(); - if data.input.is_null() { - return Ok(()); + return match &*data.input.read().unwrap() { + None => Ok(()), + Some(inputData) => { + let offs = args!(input, 0, i64) as usize; + let slice = &inputData[offs..offs+8]; + let byte = u64::from_ne_bytes(slice.try_into().unwrap()); + output[0] = Val::I64(byte as i64); + Ok(()) + } } - let offs = args!(input, 0, i64) as usize; - let slice = unsafe { std::slice::from_raw_parts(data.input.add(offs), 8) }; - let byte = u64::from_ne_bytes(slice.try_into().unwrap()); - output[0] = Val::I64(byte as i64); - Ok(()) } /// Store a byte in memory diff --git a/runtime/src/plugin.rs b/runtime/src/plugin.rs index cbebc14bc..70ac090fa 100644 --- a/runtime/src/plugin.rs +++ b/runtime/src/plugin.rs @@ -1,4 +1,6 @@ use std::collections::BTreeMap; +use std::sync::{Arc, Mutex, RwLock}; +use wasmtime_wasi_threads::WasiThreadsCtx; use crate::*; @@ -62,12 +64,18 @@ impl Plugin { imports: impl IntoIterator, with_wasi: bool, ) -> Result { + // Additional configuration (mostly experimental) coming from env variables. + let with_wasm_threads = std::env::var("EXTISM_WASM_THREADS").is_ok(); + let with_debug = std::env::var("EXTISM_DEBUG").is_ok(); + let with_wasi_threads = std::env::var("EXTISM_WASI_THREADS").is_ok(); + // Create a new engine, if the `EXITSM_DEBUG` environment variable is set // then we enable debug info let engine = Engine::new( Config::new() + .wasm_threads(with_wasm_threads) .epoch_interruption(true) - .debug_info(std::env::var("EXTISM_DEBUG").is_ok()) + .debug_info(with_debug) .profiler(profiling_strategy()), )?; let mut imports = imports.into_iter(); @@ -102,6 +110,12 @@ impl Plugin { (entry.0.as_str(), entry.1) }); + if with_wasi && with_wasi_threads { + wasmtime_wasi_threads::add_to_linker(&mut linker, &mut store, &main,|x: &mut Internal| { + WasiThreadsCtx::new(main.clone(), Arc::new(linker.clone())).as_mut().unwrap() + })?; + } + // Define PDK functions macro_rules! define_funcs { ($m:expr, { $($name:ident($($args:expr),*) $(-> $($r:expr),*)?);* $(;)?}) => { @@ -185,7 +199,7 @@ impl Plugin { }; // Make sure `Internal::memory` is initialized - plugin.internal_mut().memory = plugin.memory.get(); + plugin.internal_mut().memory = Arc::new(Mutex::new(Some(unsafe { plugin.memory.into_inner() }))); // Then detect runtime before returning the new plugin plugin.detect_runtime(); @@ -211,9 +225,10 @@ impl Plugin { } let ptr = self.memory.get(); let internal = self.internal_mut(); - internal.input = input; internal.input_length = len; - internal.memory = ptr + // Shit that is going here is required to ensure that input and memory is safely shared across WASI threads. + internal.input = Arc::new(RwLock::new(Some(unsafe { std::slice::from_raw_parts(input, len).to_vec() }))); + internal.memory = Arc::new(Mutex::new(Some(*unsafe { Box::from_raw(ptr) }))) } /// Dump memory using trace! logging @@ -414,4 +429,4 @@ impl Plugin { pub(crate) enum Runtime { Haskell { init: Func, cleanup: Func }, Wasi { init: Func, cleanup: Option }, -} +} \ No newline at end of file