From c2aece511c769458d37d8841e85b9ffa6fff3211 Mon Sep 17 00:00:00 2001 From: IoI_xD Date: Fri, 1 Sep 2023 15:32:34 -0700 Subject: [PATCH] finished (and gave up on) wasm support --- build.gradle | 2 +- native/Cargo.toml | 2 + native/src/lib.rs | 42 +++- native/src/wasm.rs | 232 +++++++++++++++--- .../ioixd/blackbox/BlackBoxPluginLoader.java | 3 +- 5 files changed, 235 insertions(+), 46 deletions(-) diff --git a/build.gradle b/build.gradle index b48e365..58378cb 100755 --- a/build.gradle +++ b/build.gradle @@ -8,7 +8,7 @@ plugins { } group = 'net.ioixd' -version = '1.1' +version = '1.2' compileJava { options.compilerArgs += ["-h", file("include")] diff --git a/native/Cargo.toml b/native/Cargo.toml index 50bab2a..18b6226 100755 --- a/native/Cargo.toml +++ b/native/Cargo.toml @@ -11,7 +11,9 @@ lazy_static = "1.4.0" libloading = "0.8.0" once_cell = "1.18.0" parking_lot = "0.12.1" +wasi-common = "12.0.1" wasmtime = "12.0.1" + [lib] crate_type = ["cdylib"] diff --git a/native/src/lib.rs b/native/src/lib.rs index 126675f..4a758c6 100755 --- a/native/src/lib.rs +++ b/native/src/lib.rs @@ -2,7 +2,10 @@ pub mod loader; pub mod shared; pub mod wasm; -use std::{error::Error, fmt::Display}; +use std::{ + error::Error, + fmt::{Debug, Display}, +}; use jni::{ objects::{JObject, JString}, @@ -25,7 +28,7 @@ pub extern "system" fn Java_net_ioixd_blackbox_Native_loadPlugin<'a>( .unwrap() .to_string_lossy() .to_string(); - if wasm == 1 { + if wasm == jni::sys::JNI_TRUE { WASMLoader::load_plugin(env, file) } else { SharedLoader::load_plugin(env, file) @@ -44,7 +47,7 @@ pub extern "system" fn Java_net_ioixd_blackbox_Native_enablePlugin<'a>( .unwrap() .to_string_lossy() .to_string(); - if wasm == 1 { + if wasm == jni::sys::JNI_TRUE { WASMLoader::enable_plugin(file) } else { SharedLoader::enable_plugin(file) @@ -63,7 +66,7 @@ pub extern "system" fn Java_net_ioixd_blackbox_Native_disablePlugin<'a>( .unwrap() .to_string_lossy() .to_string(); - if wasm == 1 { + if wasm == jni::sys::JNI_TRUE { WASMLoader::disable_plugin(file) } else { SharedLoader::disable_plugin(file) @@ -76,7 +79,7 @@ pub extern "system" fn Java_net_ioixd_blackbox_Native_libraryNames<'a>( _obj: JObject, wasm: jboolean, ) -> JString<'a> { - if wasm == 1 { + if wasm == jni::sys::JNI_TRUE { WASMLoader::library_names(env) } else { SharedLoader::library_names(env) @@ -100,7 +103,7 @@ pub extern "system" fn Java_net_ioixd_blackbox_Native_libraryHasFunction<'a>( .to_string_lossy() .to_string(); - if wasm == 1 { + if wasm == jni::sys::JNI_TRUE { WASMLoader::library_has_function(env, libname, funcname).into() } else { SharedLoader::library_has_function(env, libname, funcname).into() @@ -124,7 +127,7 @@ pub extern "system" fn Java_net_ioixd_blackbox_Native_sendEvent<'a>( let funcname = unwrap_result_or_java_error(&mut env, &libname, funcname_raw) .to_string_lossy() .to_string(); - if wasm == 1 { + if wasm == jni::sys::JNI_TRUE { WASMLoader::send_event(env, libname, funcname, ev).into() } else { SharedLoader::send_event(env, libname, funcname, ev).into() @@ -151,7 +154,7 @@ pub extern "system" fn Java_net_ioixd_blackbox_Native_execute<'a>( .to_string_lossy() .to_string(); - if wasm == 1 { + if wasm == jni::sys::JNI_TRUE { WASMLoader::execute(env, libname, funcname, address, plugin, ev) } else { SharedLoader::execute(env, libname, funcname, address, plugin, ev) @@ -167,7 +170,7 @@ pub extern "system" fn Java_net_ioixd_blackbox_BiConsumerLink_acceptNative<'a>( obj2: JObject, wasm: jboolean, ) { - if wasm == 1 { + if wasm == jni::sys::JNI_TRUE { throw_with_error( &mut env, "java/lang/UnsupportedOperationException", @@ -209,6 +212,26 @@ where } } } +pub fn unwrap_wasm_result_or_java_error( + mut env: &mut JNIEnv, + libname: S, + opt: Result, +) -> V +where + S: Into + Display, +{ + match opt { + Ok(v) => v, + Err(err) => { + throw_with_error( + &mut env, + "net/ioixd/blackbox/exceptions/NativeLibrarySymbolLoadException", + format!("error with {}: {:?}", &libname, err), + ); + unreachable!(); + } + } +} pub fn unwrap_option_or_java_error( mut env: &mut JNIEnv, @@ -287,4 +310,5 @@ pub fn throw_with_error( if let Err(_) = er { env.exception_describe().unwrap(); } + env.exception_describe().unwrap(); } diff --git a/native/src/wasm.rs b/native/src/wasm.rs index bc33199..9131ec5 100644 --- a/native/src/wasm.rs +++ b/native/src/wasm.rs @@ -1,49 +1,171 @@ -use std::error::Error; +/* + Ahead lies lots of unused code, and code that I intended to replace later. +*/ +use std::{ + collections::HashMap, + error::Error, + ffi::{c_char, c_void}, + ops::Deref, +}; + +use crate::{ + loader::Loader, unwrap_option_or_java_error, unwrap_result_or_java_error, + unwrap_wasm_result_or_java_error, wasm, +}; +use jni::{ + objects::{JObject, JString}, + sys::*, + JNIEnv, +}; use once_cell::sync::Lazy; -use parking_lot::RwLock; +use parking_lot::{RwLock, RwLockReadGuard, RwLockWriteGuard}; use wasmtime::*; -use crate::loader::Loader; +struct MyState { + name: String, + count: usize, +} + +struct SwitchableInstance { + instance: RwLock, + enabled: RwLock, + store: RwLock>, +} +impl SwitchableInstance { + pub fn new(instance: Instance, store: Store, enabled: bool) -> Self { + Self { + instance: RwLock::new(instance), + enabled: RwLock::new(enabled), + store: RwLock::new(store), + } + } + pub fn instance(&self) -> RwLockReadGuard { + self.instance.read() + } + pub fn set_enabled(&self, enabled: bool) { + *self.enabled.write() = enabled; + } + pub fn get_func(&self, name: &str) -> Option { + let mut st = self.store.write(); + self.instance.read().get_func(st.as_context_mut(), &name) + } + pub fn call_func_if_exists( + &self, + mut env: &mut JNIEnv, + libname: &str, + funcname: &str, + args: Params, + ) -> bool + where + Params: WasmParams, + Results: WasmResults, + { + let mut ok = unsafe { env.unsafe_clone() }; + let mut cl = || -> Result { + let mut st = self.store.write(); + let lib = self.instance(); + if let Some(_) = lib.get_func(st.as_context_mut(), &funcname) { + let func_res = lib.get_typed_func::<(i32, i32), ()>(st.as_context_mut(), &funcname); + let func = unwrap_wasm_result_or_java_error(env, libname, func_res); + let func = lib.get_typed_func::<(i32, i32), ()>(st.as_context_mut(), &funcname)?; + func.call(st.as_context_mut(), (-1, -2))?; + Ok(true) + } else { + Ok(false) + } + }; + unwrap_wasm_result_or_java_error(&mut ok, libname, cl()) + } + pub fn store(&self) -> RwLockWriteGuard> { + self.store.write() + } +} struct WASMRuntime { engine: RwLock, - files: RwLock>, + instances: RwLock>, } impl WASMRuntime { pub fn new() -> Self { Self { engine: RwLock::new(Engine::default()), - files: RwLock::new(Vec::new()), + instances: RwLock::new(HashMap::new()), } } - pub fn push_file(&self, contents: impl AsRef<[u8]>) -> Result> { + pub fn push_file(&self, name: String, file: String) -> Result> { let engine = self.engine.write(); - let mut files = self.files.write(); - - let module = Module::new(&engine, contents)?; - - // Create a `Linker` which will be later used to instantiate this module. - // Host functionality is defined by name within the `Linker`. - let mut linker = Linker::new(&engine); - linker.func_wrap( - "host", - "host_func", - |caller: Caller<'_, u32>, param: i32| { - println!("Got {} from WebAssembly", param); - println!("my host state is: {}", caller.data()); + let mut instances = self.instances.write(); + + let module = Module::from_file(&engine, file).unwrap(); + + let mut store = Store::new( + &engine, + MyState { + name: "what".to_string(), + count: 0, }, - )?; + ); + + // There's some functions that the compiled WASM module expects that we don't have...because this isn't a Javascript context... + // and they shouldn't be there. + // These are stub functions, and the plan was to eventually replace them with actual working functions + let (stub_func_1, stub_func_2, stub_func_3) = ( + Func::wrap( + &mut store, + |mut _caller: Caller<'_, MyState>, a: i32, b: i32| -> i32 { + println!("stub_func_1, {}, {}", a, b); + 0 + }, + ), + Func::wrap( + &mut store, + |mut _caller: Caller<'_, MyState>, a: i32, b: i32, c: i32, d: i32| -> i32 { + println!("stub_func_2, {}, {}, {}, {}", a, b, c, d); + 0 + }, + ), + Func::wrap( + &mut store, + |mut _caller: Caller<'_, MyState>, _a: i32| -> () { + println!("stub_func_3"); + }, + ), + ); // All wasm objects operate within the context of a "store". Each // `Store` has a type parameter to store host-specific data, which in // this case we're using `4` for. - let mut store = Store::new(&engine, 4); - let instance = linker.instantiate(&mut store, &module)?; - files.push(instance); - Ok(files.len()) + let instance = Instance::new( + &mut store, + &module, + &[ + stub_func_1.into(), // wasi_snapshot_preview1::random_get + stub_func_2.into(), // wasi_snapshot_preview1::fd_write + stub_func_1.into(), // wasi_snapshot_preview1::proc_exit + stub_func_1.into(), // wasi_snapshot_preview1::environ_sizes_get + stub_func_3.into(), // wasi_snapshot_preview1::environ_get + ], + ) + .unwrap(); + instances.insert(name, SwitchableInstance::new(instance, store, true)); + + Ok(instances.len()) + } + pub fn instances(&self) -> RwLockReadGuard> { + self.instances.read() + } + pub fn set_enabled(&self, file: String, enabled: bool) { + self.instances().get(&file).unwrap().set_enabled(enabled); + } + pub fn instance_names(&self) -> String { + self.instances() + .iter() + .map(|f| f.0.clone()) + .collect::>() + .join(",") } } @@ -53,42 +175,82 @@ pub struct WASMLoader; impl Loader for WASMLoader { fn execute<'a>( - env: jni::JNIEnv<'a>, + mut env: jni::JNIEnv<'a>, libname: String, funcname: String, address: jni::sys::jint, plugin: jni::objects::JObject, ev: jni::objects::JObject, ) -> jni::objects::JObject<'a> { - todo!() + let manager = WASM_LOADER.instances(); + println!("{}::{}", libname, funcname); + let lib = unwrap_option_or_java_error( + &mut env, + &libname, + &"wasm library".to_string(), + manager.get(&libname), + ); + lib.call_func_if_exists::<(i32, i32), ()>(&mut env, &libname, &funcname, (0, 1)); + JObject::null() } - fn library_has_function<'a>(env: jni::JNIEnv<'a>, libname: String, funcname: String) -> bool { - todo!() + fn library_has_function<'a>( + mut env: jni::JNIEnv<'a>, + libname: String, + funcname: String, + ) -> bool { + let manager = WASM_LOADER.instances(); + println!("{}::{}", libname, funcname); + let lib = unwrap_option_or_java_error( + &mut env, + &libname, + &libname.to_string(), + manager.get(&libname), + ); + + if let Some(_) = lib.get_func(&funcname) { + true + } else { + false + } } fn send_event<'a>( - env: jni::JNIEnv<'a>, + mut env: jni::JNIEnv<'a>, libname: String, funcname: String, ev: jni::objects::JObject, ) -> bool { - todo!() + let manager = WASM_LOADER.instances(); + println!("{}::{}", libname, funcname); + let lib = unwrap_option_or_java_error( + &mut env, + &libname, + &"wasm library".to_string(), + manager.get(&libname), + ); + + lib.call_func_if_exists::<(i32, i32), ()>(&mut env, &libname, &funcname, (0, 0)) } fn load_plugin<'a>(env: jni::JNIEnv<'a>, file: String) { - todo!() + let parts = file + .split(std::path::MAIN_SEPARATOR) + .map(|f| f.to_string()) + .collect::>(); + let name = parts.get(parts.len() - 1).unwrap().clone(); + WASM_LOADER.push_file(name, file).unwrap(); } fn enable_plugin<'a>(file: String) { - todo!() + WASM_LOADER.set_enabled(file, true); } fn disable_plugin<'a>(file: String) { - todo!() + WASM_LOADER.set_enabled(file, false); } - fn library_names<'a>(env: jni::JNIEnv) -> jni::objects::JString { - todo!() + fn library_names<'a>(env: JNIEnv) -> JString { + env.new_string(WASM_LOADER.instance_names()).unwrap() } } diff --git a/src/main/java/net/ioixd/blackbox/BlackBoxPluginLoader.java b/src/main/java/net/ioixd/blackbox/BlackBoxPluginLoader.java index 706eef8..d00144e 100644 --- a/src/main/java/net/ioixd/blackbox/BlackBoxPluginLoader.java +++ b/src/main/java/net/ioixd/blackbox/BlackBoxPluginLoader.java @@ -35,7 +35,7 @@ public final class BlackBoxPluginLoader implements PluginLoader { private boolean wasm = false; public static final Pattern[] fileFilters = new Pattern[] { - Pattern.compile("^((.*)\\" + getFileExtension() + "|\\.wasm)$") }; + Pattern.compile("^(.*)(\\" + getFileExtension() + "|\\.wasm)$") }; public BlackBoxPluginLoader(@NotNull Server instance) { Preconditions.checkArgument(instance != null, "Server cannot be null"); @@ -60,6 +60,7 @@ public Plugin loadPlugin(@NotNull final File file) throws InvalidPluginException return null; } + System.out.println(file.getName() + " is wasm: " + file.getName().endsWith(".wasm")); if (file.getName().endsWith(".wasm")) { this.wasm = true; }