From a44124bdb0595f6eec8d95e5c0209edd544dc67d Mon Sep 17 00:00:00 2001 From: zach Date: Wed, 1 Feb 2023 11:04:15 -0800 Subject: [PATCH] feat: add ability to set host function namespace (#246) - Adds `extism_function_set_namespace` to SDK - Updates SDKs with host function support to allow setting the namespace for a function --- cpp/extism.hpp | 4 ++++ extism.go | 11 +++++++++++ node/src/index.ts | 16 ++++++++++++++++ ocaml/lib/bindings.ml | 3 +++ ocaml/lib/extism.mli | 4 ++++ ocaml/lib/function.ml | 9 ++++++++- python/extism/extism.py | 7 +++++++ runtime/extism.h | 5 +++++ runtime/src/function.rs | 15 +++++++++++++++ runtime/src/plugin.rs | 3 ++- runtime/src/sdk.rs | 11 +++++++++++ rust/src/lib.rs | 23 +++++++++++++++++++++-- rust/src/plugin.rs | 3 +-- rust/src/plugin_builder.rs | 2 +- 14 files changed, 109 insertions(+), 7 deletions(-) diff --git a/cpp/extism.hpp b/cpp/extism.hpp index 1110479..85df685 100644 --- a/cpp/extism.hpp +++ b/cpp/extism.hpp @@ -318,6 +318,10 @@ public: this->func = std::shared_ptr(ptr, extism_function_free); } + void set_namespace(std::string s) { + extism_function_set_namespace(this->func.get(), s.c_str()); + } + Function(const Function &f) { this->func = f.func; } ExtismFunction *get() { return this->func.get(); } diff --git a/extism.go b/extism.go index a6b5042..d25a58c 100644 --- a/extism.go +++ b/extism.go @@ -115,6 +115,17 @@ func NewFunction(name string, inputs []ValType, outputs []ValType, f unsafe.Poin return function } +func (f *Function) SetNamespace(s string) { + cstr := C.CString(s) + defer C.free(unsafe.Pointer(cstr)) + C.extism_function_set_namespace(f.pointer, cstr) +} + +func (f Function) WithNamespace(s string) Function { + f.SetNamespace(s) + return f +} + type CurrentPlugin struct { pointer *C.ExtismCurrentPlugin } diff --git a/node/src/index.ts b/node/src/index.ts index c2c45ef..7a5c6fd 100644 --- a/node/src/index.ts +++ b/node/src/index.ts @@ -73,6 +73,7 @@ const _functions = { ], ], extism_function_free: ["void", [function_t]], + extism_function_set_namespace: ["void", [function_t, "string"]], extism_current_plugin_memory: ["uint8*", ["void*"]], extism_current_plugin_memory_alloc: ["uint64", ["void*", "uint64"]], extism_current_plugin_memory_length: ["uint64", ["void*", "uint64"]], @@ -147,6 +148,7 @@ interface LibExtism { user_data: Buffer | null, free: Buffer | null ) => Buffer; + extism_function_set_namespace: (f: Buffer, s: string) => void; extism_function_free: (f: Buffer) => void; extism_current_plugin_memory: (p: Buffer) => Buffer; extism_current_plugin_memory_alloc: (p: Buffer, n: number) => number; @@ -525,6 +527,20 @@ export class HostFunction { functionRegistry.register(this, this.pointer, this.pointer); } + /** + * Set function namespace + */ + setNamespace(name: string) { + if (this.pointer !== null) { + lib.extism_function_set_namespace(this.pointer, name) + } + } + + withNamespace(name: string) : HostFunction { + this.setNamespace(name) + return this; + } + /** * Free a host function - this should be called to cleanup the associated resources */ diff --git a/ocaml/lib/bindings.ml b/ocaml/lib/bindings.ml index e715c3a..238b55a 100644 --- a/ocaml/lib/bindings.ml +++ b/ocaml/lib/bindings.ml @@ -157,6 +157,9 @@ let extism_function_new = @-> uint64_t @-> extism_function_type @-> ptr void @-> extism_free_user_data @-> returning (ptr void)) +let extism_function_set_namespace = + fn "extism_function_set_namespace" (ptr void @-> string @-> returning void) + let extism_function_free = fn "extism_function_free" (ptr void @-> returning void) diff --git a/ocaml/lib/extism.mli b/ocaml/lib/extism.mli index 9b0bec5..0cae507 100644 --- a/ocaml/lib/extism.mli +++ b/ocaml/lib/extism.mli @@ -155,6 +155,7 @@ module Function : sig val create : string -> + ?namespace:string -> params:Val_type.t list -> results:Val_type.t list -> user_data:'a -> @@ -167,6 +168,9 @@ module Function : sig called. *) + val with_namespace : t -> string -> t + (** Update a function's namespace *) + val free : t -> unit (** Free a function *) diff --git a/ocaml/lib/function.ml b/ocaml/lib/function.ml index 2ae1f5d..99a7f80 100644 --- a/ocaml/lib/function.ml +++ b/ocaml/lib/function.ml @@ -18,7 +18,7 @@ let free t = let free_all l = List.iter free l -let create name ~params ~results ~user_data f = +let create name ?namespace ~params ~results ~user_data f = let inputs = CArray.of_list Bindings.Extism_val_type.t params in let n_inputs = Unsigned.UInt64.of_int (CArray.length inputs) in let outputs = CArray.of_list Bindings.Extism_val_type.t results in @@ -35,6 +35,13 @@ let create name ~params ~results ~user_data f = Bindings.extism_function_new name (CArray.start inputs) n_inputs (CArray.start outputs) n_outputs f user_data free' in + let () = + Option.iter (Bindings.extism_function_set_namespace pointer) namespace + in let t = { pointer; user_data; name } in Gc.finalise free t; t + +let with_namespace f ns = + Bindings.extism_function_set_namespace f.pointer ns; + f diff --git a/python/extism/extism.py b/python/extism/extism.py index fd054fa..4a89832 100644 --- a/python/extism/extism.py +++ b/python/extism/extism.py @@ -216,6 +216,13 @@ class Function: _ffi.NULL, ) + def set_namespace(self, name: str): + _lib.extism_function_set_namespace(self.pointer, name.encode()) + + def with_namespace(self, name: str): + self.set_namespace(name) + return self + def __del__(self): if not hasattr(self, "pointer"): return diff --git a/runtime/extism.h b/runtime/extism.h index b0ea70f..e4f0246 100644 --- a/runtime/extism.h +++ b/runtime/extism.h @@ -143,6 +143,11 @@ ExtismFunction *extism_function_new(const char *name, void *user_data, void (*free_user_data)(void *_)); +/** + * Set the namespace of an `ExtismFunction` + */ +void extism_function_set_namespace(ExtismFunction *ptr, const char *namespace_); + /** * Free an `ExtismFunction` */ diff --git a/runtime/src/function.rs b/runtime/src/function.rs index 4cea096..849966c 100644 --- a/runtime/src/function.rs +++ b/runtime/src/function.rs @@ -155,6 +155,7 @@ pub struct Function { pub(crate) name: String, pub(crate) ty: wasmtime::FuncType, pub(crate) f: std::sync::Arc, + pub(crate) namespace: Option, pub(crate) _user_data: std::sync::Arc, } @@ -183,6 +184,7 @@ impl Function { f: std::sync::Arc::new(move |mut caller, inp, outp| { f(caller.data_mut().plugin_mut(), inp, outp, data.make_copy()) }), + namespace: None, _user_data: std::sync::Arc::new(user_data), } } @@ -191,6 +193,19 @@ impl Function { &self.name } + pub fn namespace(&self) -> Option<&str> { + self.namespace.as_deref() + } + + pub fn set_namespace(&mut self, namespace: impl Into) { + self.namespace = Some(namespace.into()); + } + + pub fn with_namespace(mut self, namespace: impl Into) -> Self { + self.set_namespace(namespace); + self + } + pub fn ty(&self) -> &wasmtime::FuncType { &self.ty } diff --git a/runtime/src/plugin.rs b/runtime/src/plugin.rs index 4c4a881..d860653 100644 --- a/runtime/src/plugin.rs +++ b/runtime/src/plugin.rs @@ -187,10 +187,11 @@ impl Plugin { for f in &mut imports { let name = f.name().to_string(); + let ns = f.namespace().unwrap_or(EXPORT_MODULE_NAME); let func = Func::new(&mut memory.store, f.ty().clone(), unsafe { &*std::sync::Arc::as_ptr(&f.f) }); - linker.define(EXPORT_MODULE_NAME, &name, func)?; + linker.define(ns, &name, func)?; } } } diff --git a/runtime/src/sdk.rs b/runtime/src/sdk.rs index aa3e177..be8399a 100644 --- a/runtime/src/sdk.rs +++ b/runtime/src/sdk.rs @@ -236,6 +236,17 @@ pub unsafe extern "C" fn extism_function_new( Box::into_raw(Box::new(ExtismFunction(f))) } +/// Set the namespace of an `ExtismFunction` +#[no_mangle] +pub unsafe extern "C" fn extism_function_set_namespace( + ptr: *mut ExtismFunction, + namespace: *const std::ffi::c_char, +) { + let namespace = std::ffi::CStr::from_ptr(namespace); + let f = &mut *ptr; + f.0.set_namespace(namespace.to_string_lossy().to_string()); +} + /// Free an `ExtismFunction` #[no_mangle] pub unsafe extern "C" fn extism_function_free(ptr: *mut ExtismFunction) { diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 652567c..15970aa 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -47,6 +47,15 @@ mod tests { Ok(()) } + fn hello_world_panic( + _plugin: &mut CurrentPlugin, + _inputs: &[Val], + _outputs: &mut [Val], + _user_data: UserData, + ) -> Result<(), Error> { + panic!("This should not run"); + } + #[test] fn it_works() { let wasm_start = Instant::now(); @@ -58,8 +67,18 @@ mod tests { [ValType::I64], None, hello_world, - ); - let functions = [&f]; + ) + .with_namespace("env"); + let g = Function::new( + "hello_world", + [ValType::I64], + [ValType::I64], + None, + hello_world_panic, + ) + .with_namespace("test"); + + let functions = [&f, &g]; let mut plugin = Plugin::new(&context, WASM, functions, true).unwrap(); println!("register loaded plugin: {:?}", wasm_start.elapsed()); diff --git a/rust/src/plugin.rs b/rust/src/plugin.rs index a938846..9963d4d 100644 --- a/rust/src/plugin.rs +++ b/rust/src/plugin.rs @@ -72,8 +72,7 @@ impl<'a> Plugin<'a> { ) -> Result<(), Error> { let functions = functions .into_iter() - .map(|x| bindings::ExtismFunction::from(x.clone())) - .collect::>(); + .map(|x| bindings::ExtismFunction::from(x.clone())); let mut functions = functions .into_iter() .map(|x| &x as *const _) diff --git a/rust/src/plugin_builder.rs b/rust/src/plugin_builder.rs index 95eb046..3bf9bf9 100644 --- a/rust/src/plugin_builder.rs +++ b/rust/src/plugin_builder.rs @@ -54,7 +54,7 @@ impl<'a> PluginBuilder<'a> { Source::Manifest(m) => { Plugin::new_with_manifest(context, &m, self.functions, self.wasi) } - Source::Data(d) => Plugin::new(context, &d, self.functions, self.wasi), + Source::Data(d) => Plugin::new(context, d, self.functions, self.wasi), } } }