From 40b4cfb12c7b35e48a3e2169128a7952f74ddb5c Mon Sep 17 00:00:00 2001 From: Philipp Tessenow Date: Sat, 27 Jul 2024 20:27:48 +0200 Subject: [PATCH 1/2] WIP --- native/wasmex/Cargo.lock | 214 ++++++++++++++--------------- native/wasmex/Cargo.toml | 1 + native/wasmex/src/engine.rs | 3 +- native/wasmex/src/instance.rs | 78 ++++++++--- native/wasmex/src/lib.rs | 24 ++-- native/wasmex/src/store.rs | 65 +-------- native/wasmex/src/store_limits.rs | 219 ++++++++++++++++++++++++++++++ native/wasmex/src/task.rs | 20 +++ 8 files changed, 424 insertions(+), 200 deletions(-) create mode 100644 native/wasmex/src/store_limits.rs create mode 100644 native/wasmex/src/task.rs diff --git a/native/wasmex/Cargo.lock b/native/wasmex/Cargo.lock index e0845a1..6a7209f 100644 --- a/native/wasmex/Cargo.lock +++ b/native/wasmex/Cargo.lock @@ -154,7 +154,7 @@ dependencies = [ "cap-primitives", "cap-std", "io-lifetimes", - "windows-sys 0.52.0", + "windows-sys", ] [[package]] @@ -182,7 +182,7 @@ dependencies = [ "ipnet", "maybe-owned", "rustix", - "windows-sys 0.52.0", + "windows-sys", "winx", ] @@ -528,7 +528,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys", ] [[package]] @@ -545,7 +545,7 @@ checksum = "7e5768da2206272c81ef0b5e951a41862938a6070da63bcea197899942d3b947" dependencies = [ "cfg-if", "rustix", - "windows-sys 0.52.0", + "windows-sys", ] [[package]] @@ -565,7 +565,7 @@ checksum = "033b337d725b97690d86893f9de22b67b80dcc4e9ad815f348254c38119db8fb" dependencies = [ "io-lifetimes", "rustix", - "windows-sys 0.52.0", + "windows-sys", ] [[package]] @@ -789,7 +789,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c9f046b9af244f13b3bd939f55d16830ac3a201e8a9ba9661bfcb03e2be72b9b" dependencies = [ "io-lifetimes", - "windows-sys 0.52.0", + "windows-sys", ] [[package]] @@ -891,6 +891,16 @@ version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + [[package]] name = "log" version = "0.4.22" @@ -938,13 +948,14 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.11" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +checksum = "4569e456d394deccd22ce1c1913e6ea0e54519f577285001215d33557431afe4" dependencies = [ + "hermit-abi", "libc", "wasi", - "windows-sys 0.48.0", + "windows-sys", ] [[package]] @@ -975,16 +986,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "num_cpus" -version = "1.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" -dependencies = [ - "hermit-abi", - "libc", -] - [[package]] name = "object" version = "0.36.1" @@ -1003,6 +1004,29 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +[[package]] +name = "parking_lot" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets", +] + [[package]] name = "paste" version = "1.0.15" @@ -1127,6 +1151,15 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "redox_syscall" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4" +dependencies = [ + "bitflags", +] + [[package]] name = "redox_users" version = "0.4.5" @@ -1204,7 +1237,7 @@ dependencies = [ "libc", "linux-raw-sys", "once_cell", - "windows-sys 0.52.0", + "windows-sys", ] [[package]] @@ -1248,6 +1281,12 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + [[package]] name = "semver" version = "1.0.23" @@ -1317,6 +1356,15 @@ dependencies = [ "dirs", ] +[[package]] +name = "signal-hook-registry" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" +dependencies = [ + "libc", +] + [[package]] name = "slice-group-by" version = "0.3.1" @@ -1339,7 +1387,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys", ] [[package]] @@ -1377,7 +1425,7 @@ dependencies = [ "fd-lock", "io-lifetimes", "rustix", - "windows-sys 0.52.0", + "windows-sys", "winx", ] @@ -1433,18 +1481,31 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.38.1" +version = "1.39.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb2caba9f80616f438e09748d5acda951967e1ea58508ef53d9c6402485a46df" +checksum = "d040ac2b29ab03b09d4129c2f5bbd012a3ac2f79d38ff506a4bf8dd34b0eac8a" dependencies = [ "backtrace", "bytes", "libc", "mio", - "num_cpus", + "parking_lot", "pin-project-lite", + "signal-hook-registry", "socket2", - "windows-sys 0.48.0", + "tokio-macros", + "windows-sys", +] + +[[package]] +name = "tokio-macros" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" +dependencies = [ + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -1619,7 +1680,7 @@ dependencies = [ "tracing", "wasmtime", "wiggle", - "windows-sys 0.52.0", + "windows-sys", ] [[package]] @@ -1701,6 +1762,7 @@ dependencies = [ "once_cell", "rand", "rustler", + "tokio", "wasi-common", "wasmtime", "wasmtime-wasi", @@ -1786,7 +1848,7 @@ dependencies = [ "wasmtime-versioned-export-macros", "wasmtime-winch", "wat", - "windows-sys 0.52.0", + "windows-sys", ] [[package]] @@ -1814,7 +1876,7 @@ dependencies = [ "serde_derive", "sha2", "toml", - "windows-sys 0.52.0", + "windows-sys", "zstd", ] @@ -1902,7 +1964,7 @@ dependencies = [ "rustix", "wasmtime-asm-macros", "wasmtime-versioned-export-macros", - "windows-sys 0.52.0", + "windows-sys", ] [[package]] @@ -1926,7 +1988,7 @@ dependencies = [ "anyhow", "cfg-if", "libc", - "windows-sys 0.52.0", + "windows-sys", ] [[package]] @@ -1988,7 +2050,7 @@ dependencies = [ "url", "wasmtime", "wiggle", - "windows-sys 0.52.0", + "windows-sys", ] [[package]] @@ -2116,7 +2178,7 @@ version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" dependencies = [ - "windows-sys 0.52.0", + "windows-sys", ] [[package]] @@ -2148,16 +2210,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets 0.48.5", + "windows-targets", ] [[package]] @@ -2166,22 +2219,7 @@ version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-targets" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" -dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", + "windows-targets", ] [[package]] @@ -2190,46 +2228,28 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", "windows_i686_gnullvm", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", ] -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" - [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" - [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" -[[package]] -name = "windows_i686_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" - [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -2242,48 +2262,24 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" -[[package]] -name = "windows_i686_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" - [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" - [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" - [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" - [[package]] name = "windows_x86_64_msvc" version = "0.52.6" @@ -2306,7 +2302,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9643b83820c0cd246ecabe5fa454dd04ba4fa67996369466d0747472d337346" dependencies = [ "bitflags", - "windows-sys 0.52.0", + "windows-sys", ] [[package]] diff --git a/native/wasmex/Cargo.toml b/native/wasmex/Cargo.toml index 14e6d9f..1f3e642 100644 --- a/native/wasmex/Cargo.toml +++ b/native/wasmex/Cargo.toml @@ -23,3 +23,4 @@ wasmtime-wasi = "23.0.1" wasi-common = "23.0.1" wiggle = "23.0.1" wat = "1.215.0" +tokio = { version = "1.39.1", features = ["full"] } diff --git a/native/wasmex/src/engine.rs b/native/wasmex/src/engine.rs index 27cb861..346706e 100644 --- a/native/wasmex/src/engine.rs +++ b/native/wasmex/src/engine.rs @@ -30,7 +30,7 @@ pub fn new(config: ExEngineConfig) -> Result, rustle Ok(resource) } -#[rustler::nif(name = "engine_precompile_module")] +#[rustler::nif(name = "engine_precompile_module", schedule = "DirtyCpu")] pub fn precompile_module<'a>( env: rustler::Env<'a>, engine_resource: ResourceArc, @@ -66,6 +66,7 @@ pub(crate) fn engine_config(engine_config: ExEngineConfig) -> Config { config.consume_fuel(engine_config.consume_fuel); config.wasm_backtrace_details(backtrace_details); config.cranelift_opt_level(cranelift_opt_level); + config.async_support(true); config } diff --git a/native/wasmex/src/instance.rs b/native/wasmex/src/instance.rs index ee183d9..4e3ab4e 100644 --- a/native/wasmex/src/instance.rs +++ b/native/wasmex/src/instance.rs @@ -9,6 +9,7 @@ use crate::{ module::ModuleResource, printable_term_type::PrintableTermType, store::{StoreData, StoreOrCaller, StoreOrCallerResource}, + task, }; use rustler::{ env::SavedTerm, @@ -43,34 +44,68 @@ impl rustler::Resource for InstanceResource {} // structure: %{namespace_name: %{import_name: {:fn, param_types, result_types, captured_function}}} #[rustler::nif(name = "instance_new")] pub fn new( + env: rustler::Env, store_or_caller_resource: ResourceArc, module_resource: ResourceArc, imports: MapIterator, linked_modules: Vec, -) -> Result, rustler::Error> { - let module = module_resource.inner.lock().map_err(|e| { - rustler::Error::Term(Box::new(format!( - "Could not unlock module resource as the mutex was poisoned: {e}" - ))) - })?; - let store_or_caller: &mut StoreOrCaller = - &mut *(store_or_caller_resource.inner.lock().map_err(|e| { - rustler::Error::Term(Box::new(format!( - "Could not unlock store_or_caller resource as the mutex was poisoned: {e}" - ))) - })?); +) -> Result<(), rustler::Error> { + // TODO: pass pid as parameter instead of hardcoding it + let pid = env.pid(); + // create erlang environment for the thread + let mut thread_env = OwnedEnv::new(); + + task::spawn(async move { + let module = module_resource.deref().inner.lock().map_err(|e| { + let message = Box::new(format!( + "Could not unlock module resource as the mutex was poisoned: {e}" + )); + + message.encode(env) + }); - let instance = link_and_create_instance(store_or_caller, &module, imports, linked_modules)?; - let resource = ResourceArc::new(InstanceResource { - inner: Mutex::new(instance), + + let store_or_caller = + store_or_caller_resource.deref().inner.lock().map_err(|e| { + let message = Box::new(format!( + "Could not unlock store_or_caller resource as the mutex was poisoned: {e}" + )); + message.encode(env) + }); + + + let result = match link_and_create_instance(store_or_caller, &module, imports, linked_modules).await { + Ok(instance) => { + let resource = ResourceArc::new(InstanceResource { + inner: Mutex::new(instance), + }); + make_tuple(env, &[atoms::ok().encode(env), resource.encode(env)]) + } + Err(_) => todo!(), + }; + + thread_env.send_and_clear(&pid, |thread_env| { + // TODO: pass in forward_term as param + let forward_term = atoms::returned_function_call().encode(thread_env); + + make_tuple( + thread_env, + &[ + // TODO: use a custom atom + atoms::returned_function_call().encode(thread_env), + result, + forward_term, + ], + ) + }); }); - Ok(resource) + Ok() } -fn link_and_create_instance( +async fn link_and_create_instance( store_or_caller: &mut StoreOrCaller, module: &Module, - imports: MapIterator, + imports: MapIterator<'_>, linked_modules: Vec, ) -> Result { let mut linker = Linker::new(store_or_caller.engine()); @@ -84,7 +119,8 @@ fn link_and_create_instance( link_modules(&mut linker, store_or_caller, linked_modules)?; linker - .instantiate(store_or_caller, module) + .instantiate_async(store_or_caller, module) + .await .map_err(|err| Error::Term(Box::new(err.to_string()))) } @@ -207,7 +243,7 @@ pub fn function_export_exists( Ok(result) } -#[rustler::nif(name = "instance_call_exported_function", schedule = "DirtyCpu")] +#[rustler::nif(name = "instance_call_exported_function")] pub fn call_exported_function( env: rustler::Env, store_or_caller_resource: ResourceArc, @@ -223,7 +259,7 @@ pub fn call_exported_function( let function_params = thread_env.save(params); let from = thread_env.save(from); - thread::spawn(move || { + task::spawn(async move { thread_env.send_and_clear(&pid, |thread_env| { execute_function( thread_env, diff --git a/native/wasmex/src/lib.rs b/native/wasmex/src/lib.rs index e4a9aec..02fd950 100644 --- a/native/wasmex/src/lib.rs +++ b/native/wasmex/src/lib.rs @@ -1,13 +1,15 @@ -pub mod atoms; -pub mod caller; -pub mod engine; -pub mod environment; -pub mod functions; -pub mod instance; -pub mod memory; -pub mod module; -pub mod pipe; -pub mod printable_term_type; -pub mod store; +mod atoms; +mod caller; +mod engine; +mod environment; +mod functions; +mod instance; +mod memory; +mod module; +mod pipe; +mod printable_term_type; +mod store; +mod store_limits; +mod task; rustler::init!("Elixir.Wasmex.Native"); diff --git a/native/wasmex/src/store.rs b/native/wasmex/src/store.rs index b5061eb..5abec97 100644 --- a/native/wasmex/src/store.rs +++ b/native/wasmex/src/store.rs @@ -1,15 +1,12 @@ use crate::{ caller::{get_caller, get_caller_mut}, engine::{unwrap_engine, EngineResource}, - pipe::{Pipe, PipeResource}, + pipe::{Pipe, PipeResource}, store_limits::{ExStoreLimits, StoreLimitsAsync}, }; use rustler::{Error, NifStruct, ResourceArc}; use std::{collections::HashMap, sync::Mutex}; use wasi_common::{sync::WasiCtxBuilder, WasiCtx}; -use wasmtime::{ - AsContext, AsContextMut, Engine, Store, StoreContext, StoreContextMut, StoreLimits, - StoreLimitsBuilder, -}; +use wasmtime::{AsContext, AsContextMut, Engine, Store, StoreContext, StoreContextMut}; #[derive(Debug, NifStruct)] #[module = "Wasmex.Wasi.PreopenOptions"] @@ -35,57 +32,9 @@ pub struct ExWasiOptions { preopen: Vec, } -#[derive(NifStruct)] -#[module = "Wasmex.StoreLimits"] -pub struct ExStoreLimits { - memory_size: Option, - table_elements: Option, - instances: Option, - tables: Option, - memories: Option, -} - -impl ExStoreLimits { - pub fn to_wasmtime(&self) -> StoreLimits { - let limits = StoreLimitsBuilder::new(); - - let limits = if let Some(memory_size) = self.memory_size { - limits.memory_size(memory_size) - } else { - limits - }; - - let limits = if let Some(table_elements) = self.table_elements { - limits.table_elements(table_elements) - } else { - limits - }; - - let limits = if let Some(instances) = self.instances { - limits.instances(instances) - } else { - limits - }; - - let limits = if let Some(tables) = self.tables { - limits.tables(tables) - } else { - limits - }; - - let limits = if let Some(memories) = self.memories { - limits.memories(memories) - } else { - limits - }; - - limits.build() - } -} - pub struct StoreData { pub(crate) wasi: Option, - pub(crate) limits: StoreLimits, + pub(crate) limits: StoreLimitsAsync, } pub enum StoreOrCaller { @@ -145,10 +94,10 @@ pub fn new( let limits = if let Some(limits) = limits { limits.to_wasmtime() } else { - StoreLimits::default() + StoreLimitsAsync::default() }; let mut store = Store::new(&engine, StoreData { wasi: None, limits }); - store.limiter(|state| &mut state.limits); + store.limiter_async(|state| &mut state.limits); let resource = ResourceArc::new(StoreOrCallerResource { inner: Mutex::new(StoreOrCaller::Store(store)), }); @@ -191,7 +140,7 @@ pub fn new_wasi( let limits = if let Some(limits) = limits { limits.to_wasmtime() } else { - StoreLimits::default() + StoreLimitsAsync::default() }; let mut store = Store::new( &engine, @@ -200,7 +149,7 @@ pub fn new_wasi( limits, }, ); - store.limiter(|state| &mut state.limits); + store.limiter_async(|state| &mut state.limits); let resource = ResourceArc::new(StoreOrCallerResource { inner: Mutex::new(StoreOrCaller::Store(store)), }); diff --git a/native/wasmex/src/store_limits.rs b/native/wasmex/src/store_limits.rs new file mode 100644 index 0000000..f1c53be --- /dev/null +++ b/native/wasmex/src/store_limits.rs @@ -0,0 +1,219 @@ +// Due to a clippy bug it thinks we needlessly borrow stuff +// when defining the ExStoreLimits struct +// see: https://github.com/rust-lang/rust-clippy/issues/9778 +#![allow(clippy::needless_borrow)] + +use rustler::NifStruct; +use wasmtime::ResourceLimiterAsync; + +#[derive(Clone, Debug)] +pub struct StoreLimitsAsync { + memory_size: Option, + table_elements: Option, + instances: usize, + tables: usize, + memories: usize, + trap_on_grow_failure: bool, +} + +impl Default for StoreLimitsAsync { + fn default() -> Self { + Self { + memory_size: None, + table_elements: None, + instances: wasmtime::DEFAULT_INSTANCE_LIMIT, + tables: wasmtime::DEFAULT_TABLE_LIMIT, + memories: wasmtime::DEFAULT_MEMORY_LIMIT, + trap_on_grow_failure: false, + } + } +} + +#[wiggle::async_trait] +impl ResourceLimiterAsync for StoreLimitsAsync { + async fn memory_growing( + &mut self, + _current: usize, + desired: usize, + maximum: Option, + ) -> wiggle::anyhow::Result { + let allow = match self.memory_size { + Some(limit) if desired > limit => false, + _ => match maximum { + Some(max) if desired > max => false, + _ => true, + }, + }; + if !allow && self.trap_on_grow_failure { + wiggle::anyhow::bail!("forcing trap when growing memory to {desired} bytes") + } else { + Ok(allow) + } + } + async fn table_growing( + &mut self, + _current: u32, + desired: u32, + maximum: Option, + ) -> wiggle::anyhow::Result { + let allow = match self.table_elements { + Some(limit) if desired > limit => false, + _ => match maximum { + Some(max) if desired > max => false, + _ => true, + }, + }; + if !allow && self.trap_on_grow_failure { + wiggle::anyhow::bail!("forcing trap when growing table to {desired} elements") + } else { + Ok(allow) + } + } + + fn instances(&self) -> usize { + self.instances + } + + fn tables(&self) -> usize { + self.tables + } + + fn memories(&self) -> usize { + self.memories + } +} + +/// Used to build [`StoreLimitsAsync`]. +pub struct StoreLimitsAsyncBuilder(StoreLimitsAsync); + +impl StoreLimitsAsyncBuilder { + /// Creates a new [`StoreLimitsAsyncBuilder`]. + /// + /// See the documentation on each builder method for the default for each + /// value. + pub fn new() -> Self { + Self(StoreLimitsAsync::default()) + } + + /// The maximum number of bytes a linear memory can grow to. + /// + /// Growing a linear memory beyond this limit will fail. This limit is + /// applied to each linear memory individually, so if a wasm module has + /// multiple linear memories then they're all allowed to reach up to the + /// `limit` specified. + /// + /// By default, linear memory will not be limited. + pub fn memory_size(mut self, limit: usize) -> Self { + self.0.memory_size = Some(limit); + self + } + + /// The maximum number of elements in a table. + /// + /// Growing a table beyond this limit will fail. This limit is applied to + /// each table individually, so if a wasm module has multiple tables then + /// they're all allowed to reach up to the `limit` specified. + /// + /// By default, table elements will not be limited. + pub fn table_elements(mut self, limit: u32) -> Self { + self.0.table_elements = Some(limit); + self + } + + /// The maximum number of instances that can be created for a [`Store`](crate::Store). + /// + /// Module instantiation will fail if this limit is exceeded. + /// + /// This value defaults to 10,000. + pub fn instances(mut self, limit: usize) -> Self { + self.0.instances = limit; + self + } + + /// The maximum number of tables that can be created for a [`Store`](crate::Store). + /// + /// Module instantiation will fail if this limit is exceeded. + /// + /// This value defaults to 10,000. + pub fn tables(mut self, tables: usize) -> Self { + self.0.tables = tables; + self + } + + /// The maximum number of linear memories that can be created for a [`Store`](crate::Store). + /// + /// Instantiation will fail with an error if this limit is exceeded. + /// + /// This value defaults to 10,000. + pub fn memories(mut self, memories: usize) -> Self { + self.0.memories = memories; + self + } + + /// Indicates that a trap should be raised whenever a growth operation + /// would fail. + /// + /// This operation will force `memory.grow` and `table.grow` instructions + /// to raise a trap on failure instead of returning -1. This is not + /// necessarily spec-compliant, but it can be quite handy when debugging a + /// module that fails to allocate memory and might behave oddly as a result. + /// + /// This value defaults to `false`. + pub fn trap_on_grow_failure(mut self, trap: bool) -> Self { + self.0.trap_on_grow_failure = trap; + self + } + + /// Consumes this builder and returns the [`StoreLimitsAsync`]. + pub fn build(self) -> StoreLimitsAsync { + self.0 + } +} + +#[derive(NifStruct)] +#[module = "Wasmex.StoreLimits"] +pub struct ExStoreLimits { + memory_size: Option, + table_elements: Option, + instances: Option, + tables: Option, + memories: Option, +} + +impl ExStoreLimits { + pub fn to_wasmtime(&self) -> StoreLimitsAsync { + let limits = StoreLimitsAsyncBuilder::new(); + + let limits = if let Some(memory_size) = self.memory_size { + limits.memory_size(memory_size) + } else { + limits + }; + + let limits = if let Some(table_elements) = self.table_elements { + limits.table_elements(table_elements) + } else { + limits + }; + + let limits = if let Some(instances) = self.instances { + limits.instances(instances) + } else { + limits + }; + + let limits = if let Some(tables) = self.tables { + limits.tables(tables) + } else { + limits + }; + + let limits = if let Some(memories) = self.memories { + limits.memories(memories) + } else { + limits + }; + + limits.build() + } +} diff --git a/native/wasmex/src/task.rs b/native/wasmex/src/task.rs new file mode 100644 index 0000000..e4f3717 --- /dev/null +++ b/native/wasmex/src/task.rs @@ -0,0 +1,20 @@ +use once_cell::sync::Lazy; +use std::future::Future; +use tokio::runtime::{Builder, Runtime}; +use tokio::task::JoinHandle; + +static TOKIO: Lazy = Lazy::new(|| { + Builder::new_multi_thread() + .enable_time() + .enable_io() + .build() + .expect("Wasmex.Native: Failed to start tokio runtime") +}); + +pub fn spawn(task: T) -> JoinHandle +where + T: Future + Send + 'static, + T::Output: Send + 'static, +{ + TOKIO.spawn(task) +} From 6f83e907581ef82a5d81b03459517896acff6050 Mon Sep 17 00:00:00 2001 From: Philipp Tessenow Date: Tue, 13 Aug 2024 12:19:07 +0200 Subject: [PATCH 2/2] some fixing going on --- native/wasmex/Cargo.lock | 26 +++++++ native/wasmex/Cargo.toml | 3 +- native/wasmex/src/atoms.rs | 1 + native/wasmex/src/environment.rs | 124 +++++++++++++++++------------- native/wasmex/src/instance.rs | 127 +++++++++++++------------------ native/wasmex/src/store.rs | 3 + native/wasmex/src/task.rs | 35 ++++++++- 7 files changed, 189 insertions(+), 130 deletions(-) diff --git a/native/wasmex/Cargo.lock b/native/wasmex/Cargo.lock index 6a7209f..7f0d162 100644 --- a/native/wasmex/Cargo.lock +++ b/native/wasmex/Cargo.lock @@ -537,6 +537,12 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" +[[package]] +name = "fastrand" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" + [[package]] name = "fd-lock" version = "4.0.2" @@ -604,6 +610,19 @@ version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" +[[package]] +name = "futures-lite" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52527eb5074e35e9339c6b4e8d12600c7128b68fb25dcb9fa9dec18f7c25f3a5" +dependencies = [ + "fastrand", + "futures-core", + "futures-io", + "parking", + "pin-project-lite", +] + [[package]] name = "futures-sink" version = "0.3.30" @@ -1004,6 +1023,12 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +[[package]] +name = "parking" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae" + [[package]] name = "parking_lot" version = "0.12.3" @@ -1759,6 +1784,7 @@ dependencies = [ name = "wasmex" version = "0.9.1" dependencies = [ + "futures-lite", "once_cell", "rand", "rustler", diff --git a/native/wasmex/Cargo.toml b/native/wasmex/Cargo.toml index 1f3e642..06dbdda 100644 --- a/native/wasmex/Cargo.toml +++ b/native/wasmex/Cargo.toml @@ -16,9 +16,10 @@ crate-type = ["dylib"] [dependencies] rustler = { version = "0.34", features = ["big_integer"] } +futures-lite = "2.3.0" once_cell = "1.19.0" rand = "0.8.5" -wasmtime = "23.0.2" +wasmtime = { version = "23.0.2", features = ["async"] } wasmtime-wasi = "23.0.1" wasi-common = "23.0.1" wiggle = "23.0.1" diff --git a/native/wasmex/src/atoms.rs b/native/wasmex/src/atoms.rs index bc89807..a08539a 100644 --- a/native/wasmex/src/atoms.rs +++ b/native/wasmex/src/atoms.rs @@ -41,6 +41,7 @@ rustler::atoms! { // calls to erlang processes returned_function_call, invoke_callback, + async_nif_result, // engine config - cranelift_opt_level none, diff --git a/native/wasmex/src/environment.rs b/native/wasmex/src/environment.rs index a811a3b..f3fa1ab 100644 --- a/native/wasmex/src/environment.rs +++ b/native/wasmex/src/environment.rs @@ -1,12 +1,12 @@ use crate::{ atoms, caller::{remove_caller, set_caller}, - instance::{map_wasm_values_to_vals, LinkedModule, WasmValue}, + instance::{map_wasm_values_to_vals, ImportDefinition, LinkedModule, WasmValue}, memory::MemoryResource, store::{StoreData, StoreOrCaller, StoreOrCallerResource}, }; use rustler::{ - types::tuple, Atom, Encoder, Error, ListIterator, MapIterator, OwnedEnv, ResourceArc, Term, + types::tuple, Atom, Encoder, Error, ListIterator, LocalPid, MapIterator, OwnedEnv, ResourceArc, Term }; use std::sync::{Condvar, Mutex}; use wasmtime::{Caller, Engine, FuncType, Linker, Val, ValType}; @@ -52,49 +52,82 @@ pub fn link_modules( Ok(()) } -pub fn link_imports( - engine: &Engine, - linker: &mut Linker, +pub fn imports_from_map_iterator( imports: MapIterator, -) -> Result<(), Error> { +) -> Result, Error> { + let mut result = Vec::new(); for (namespace_name, namespace_definition) in imports { let namespace_name = namespace_name.decode::()?; let definition: MapIterator = namespace_definition.decode()?; for (import_name, import) in definition { let import_name = import_name.decode::()?; - link_import(engine, linker, &namespace_name, &import_name, import)?; + let import_tuple = tuple::get_tuple(import)?; + + let import_type = import_tuple + .first() + .ok_or(Error::Atom("missing_import_type"))?; + let import_type = + Atom::from_term(*import_type).map_err(|_| Error::Atom("import type must be an atom"))?; + + if atoms::__fn__().eq(&import_type) { + let param_term = import_tuple + .get(1) + .ok_or(Error::Atom("missing_import_params"))?; + let results_term = import_tuple + .get(2) + .ok_or(Error::Atom("missing_import_results"))?; + + let params_signature = param_term + .decode::()? + .map(term_to_arg_type) + .collect::, _>>()?; + + let results_signature = results_term + .decode::()? + .map(term_to_arg_type) + .collect::, _>>()?; + + result.push(ImportDefinition::Function { + namespace: namespace_name.clone(), + name: import_name.clone(), + params: params_signature, + results: results_signature, + }); + } else { + return Err(Error::Atom("unknown import type")); + } } } + Ok(result) +} + +pub fn link_imports( + engine: &Engine, + linker: &mut Linker, + imports: &Vec, + pid: &LocalPid, +) -> Result<(), Error> { + for import_definition in imports { + link_import(engine, linker, import_definition, pid)?; + } Ok(()) } fn link_import( engine: &Engine, linker: &mut Linker, - namespace_name: &str, - import_name: &str, - definition: Term, + import_definition: &ImportDefinition, + pid: &LocalPid, ) -> Result<(), Error> { - let import_tuple = tuple::get_tuple(definition)?; - - let import_type = import_tuple - .first() - .ok_or(Error::Atom("missing_import_type"))?; - let import_type = - Atom::from_term(*import_type).map_err(|_| Error::Atom("import type must be an atom"))?; - - if atoms::__fn__().eq(&import_type) { - return link_imported_function( - engine, - linker, - namespace_name.to_string(), - import_name.to_string(), - definition, - ); + match import_definition { + ImportDefinition::Function { + namespace, + name, + params, + results, + } => link_imported_function(engine, linker, namespace, name, params, results, pid), } - - Err(Error::Atom("unknown import type")) } // Creates a wrapper function used in a Wasm import object. @@ -112,32 +145,19 @@ fn link_import( fn link_imported_function( engine: &Engine, linker: &mut Linker, - namespace_name: String, - import_name: String, - definition: Term, + namespace_name: &String, + import_name: &String, + params_signature: &Vec, + results_signature: &Vec, + pid: &LocalPid, ) -> Result<(), Error> { - let pid = definition.get_env().pid(); - - let import_tuple = tuple::get_tuple(definition)?; - - let param_term = import_tuple - .get(1) - .ok_or(Error::Atom("missing_import_params"))?; - let results_term = import_tuple - .get(2) - .ok_or(Error::Atom("missing_import_results"))?; - - let params_signature = param_term - .decode::()? - .map(term_to_arg_type) - .collect::, _>>()?; - - let results_signature = results_term - .decode::()? - .map(term_to_arg_type) - .collect::, _>>()?; + let namespace_name = namespace_name.clone(); + let import_name = import_name.clone(); + let params_signature = params_signature.clone(); + let results_signature = results_signature.clone(); + let signature = FuncType::new(engine, params_signature.clone(), results_signature.clone()); + let pid = pid.clone(); - let signature = FuncType::new(engine, params_signature, results_signature.clone()); linker .func_new( &namespace_name.clone(), diff --git a/native/wasmex/src/instance.rs b/native/wasmex/src/instance.rs index 4e3ab4e..2c79d8f 100644 --- a/native/wasmex/src/instance.rs +++ b/native/wasmex/src/instance.rs @@ -4,23 +4,22 @@ use crate::{ atoms, - environment::{link_imports, link_modules, CallbackTokenResource}, + environment::{imports_from_map_iterator, link_imports, link_modules, CallbackTokenResource}, functions, module::ModuleResource, printable_term_type::PrintableTermType, store::{StoreData, StoreOrCaller, StoreOrCallerResource}, - task, + task::{self, send_async_nif_result}, }; use rustler::{ env::SavedTerm, types::{tuple::make_tuple, ListIterator}, - Encoder, Env as RustlerEnv, Error, MapIterator, NifMap, NifResult, OwnedEnv, ResourceArc, Term, - TermType, + Atom, Encoder, Env as RustlerEnv, Error, MapIterator, NifMap, NifResult, OwnedEnv, + ResourceArc, Term, TermType, }; use std::ops::Deref; use std::sync::Mutex; -use std::thread; -use wasmtime::{Instance, Linker, Module, Val, ValType}; +use wasmtime::{AsContextMut, Instance, Linker, Val, ValType}; #[derive(NifMap)] pub struct LinkedModule { @@ -28,6 +27,16 @@ pub struct LinkedModule { pub module_resource: ResourceArc, } +#[derive(Debug, Clone)] +pub enum ImportDefinition { + Function { + namespace: String, + name: String, + params: Vec, + results: Vec, + }, +} + pub struct InstanceResource { pub inner: Mutex, } @@ -49,79 +58,47 @@ pub fn new( module_resource: ResourceArc, imports: MapIterator, linked_modules: Vec, -) -> Result<(), rustler::Error> { +) -> NifResult<(Atom, Atom)> { // TODO: pass pid as parameter instead of hardcoding it let pid = env.pid(); // create erlang environment for the thread - let mut thread_env = OwnedEnv::new(); - - task::spawn(async move { - let module = module_resource.deref().inner.lock().map_err(|e| { - let message = Box::new(format!( - "Could not unlock module resource as the mutex was poisoned: {e}" - )); - - message.encode(env) - }); - - - let store_or_caller = - store_or_caller_resource.deref().inner.lock().map_err(|e| { - let message = Box::new(format!( - "Could not unlock store_or_caller resource as the mutex was poisoned: {e}" - )); - message.encode(env) - }); - - - let result = match link_and_create_instance(store_or_caller, &module, imports, linked_modules).await { - Ok(instance) => { - let resource = ResourceArc::new(InstanceResource { + let imports = imports_from_map_iterator(imports)?; + + send_async_nif_result(env, async move { + let mut store_or_caller = (store_or_caller_resource.inner.lock().map_err(|e| { + format!("Could not unlock store resource as the mutex was poisoned: {e}") + }))?; + + let module = module_resource.inner.lock().map_err(|e| { + format!("Could not unlock module resource as the mutex was poisoned: {e}") + }).map(|module| module.clone())?; + + let mut linker = Linker::new(store_or_caller.engine()); + if let Some(_wasi_ctx) = &store_or_caller.data().wasi { + linker.allow_shadowing(true); + wasi_common::sync::add_to_linker(&mut linker, |s: &mut StoreData| s.wasi.as_mut().unwrap()) + .map_err(|err| err.to_string())?; + } + + link_imports(store_or_caller.engine(), &mut linker, &imports, &pid).map_err(|e| format!("{:?}", e))?; + link_modules(&mut linker, &mut *store_or_caller, linked_modules).map_err(|e| format!("{:?}", e))?; + + linker + .instantiate_async(store_or_caller.as_context_mut(), &module) + .await + .map_err(|err| Error::Term(Box::new(err.to_string()))) + .map(|instance| { + ResourceArc::new(InstanceResource { inner: Mutex::new(instance), - }); - make_tuple(env, &[atoms::ok().encode(env), resource.encode(env)]) - } - Err(_) => todo!(), - }; - - thread_env.send_and_clear(&pid, |thread_env| { - // TODO: pass in forward_term as param - let forward_term = atoms::returned_function_call().encode(thread_env); - - make_tuple( - thread_env, - &[ - // TODO: use a custom atom - atoms::returned_function_call().encode(thread_env), - result, - forward_term, - ], - ) - }); - }); - Ok() -} - -async fn link_and_create_instance( - store_or_caller: &mut StoreOrCaller, - module: &Module, - imports: MapIterator<'_>, - linked_modules: Vec, -) -> Result { - let mut linker = Linker::new(store_or_caller.engine()); - if let Some(_wasi_ctx) = &store_or_caller.data().wasi { - linker.allow_shadowing(true); - wasi_common::sync::add_to_linker(&mut linker, |s: &mut StoreData| s.wasi.as_mut().unwrap()) - .map_err(|err| Error::Term(Box::new(err.to_string())))?; - } - - link_imports(store_or_caller.engine(), &mut linker, imports)?; - link_modules(&mut linker, store_or_caller, linked_modules)?; - - linker - .instantiate_async(store_or_caller, module) - .await - .map_err(|err| Error::Term(Box::new(err.to_string()))) + }) + }) + .map(|instance_resource| ( + atoms::returned_function_call(), + (atoms::ok(), instance_resource), + atoms::async_nif_result() + )) + .map_err(|error| format!("{:?}", error)) + }) } #[rustler::nif(name = "instance_get_global_value", schedule = "DirtyCpu")] diff --git a/native/wasmex/src/store.rs b/native/wasmex/src/store.rs index 5abec97..139d3e4 100644 --- a/native/wasmex/src/store.rs +++ b/native/wasmex/src/store.rs @@ -42,6 +42,9 @@ pub enum StoreOrCaller { Caller(i32), } +unsafe impl Send for StoreData {} // TODO: needed? +unsafe impl Send for StoreOrCaller {} // TODO: needed? + pub struct StoreOrCallerResource { pub inner: Mutex, } diff --git a/native/wasmex/src/task.rs b/native/wasmex/src/task.rs index e4f3717..41e15c3 100644 --- a/native/wasmex/src/task.rs +++ b/native/wasmex/src/task.rs @@ -1,8 +1,13 @@ use once_cell::sync::Lazy; -use std::future::Future; +use rustler::{Atom, Encoder, Env, NifResult, OwnedEnv}; +use std::future::{Future, IntoFuture}; use tokio::runtime::{Builder, Runtime}; -use tokio::task::JoinHandle; +use tokio::task::{self, JoinHandle}; +use futures_lite::future; +use crate::atoms; + +// TODO: build the runtime on the NIFs init fn static TOKIO: Lazy = Lazy::new(|| { Builder::new_multi_thread() .enable_time() @@ -18,3 +23,29 @@ where { TOKIO.spawn(task) } + +pub fn send_async_nif_result(env: Env, future: Fut) -> NifResult<(Atom, Atom)> +where + T: Encoder, + E: Encoder, + Fut: future::Future> + Send + 'static, +{ + let pid = env.pid(); + let mut my_env = OwnedEnv::new(); + let result_key = atoms::async_nif_result(); + task::spawn(async move { + let result = future.await; + match result { + Ok(worker) => { + let _ = my_env + .send_and_clear(&pid, |env| (result_key, (atoms::ok(), worker)).encode(env)); + } + Err(err) => { + let _ = my_env + .send_and_clear(&pid, |env| (result_key, (atoms::error(), err)).encode(env)); + } + } + }).into_future(); + + Ok((atoms::ok(), result_key)) +}