mirror of
https://github.com/extism/extism.git
synced 2026-01-09 13:57:55 -05:00
refactor: Simplify runtime handling (#411)
It seems like our runtime initialization process is a little too aggressive, this PR scales it back to initialize the runtime once if it exists and only reinitializes when `_start` is called. This prevents globals from being wiped out between plugin calls. - Removes Runtime::cleanup - Only initializes runtime once, or if `_start` is called - Improves Haskell reactor support See https://github.com/extism/go-sdk/pull/11 for the go-sdk PR.
This commit is contained in:
@@ -19,9 +19,9 @@ library
|
||||
extra-libraries: extism
|
||||
extra-lib-dirs: /usr/local/lib
|
||||
build-depends:
|
||||
base >= 4.16.1 && < 4.19.0,
|
||||
bytestring >= 0.11.3 && < 0.12,
|
||||
json >= 0.10 && < 0.11,
|
||||
base >= 4.16.1 && < 5,
|
||||
bytestring >= 0.11.3 && <= 0.12,
|
||||
json >= 0.10 && <= 0.11,
|
||||
extism-manifest >= 0.0.0 && < 0.3.0
|
||||
|
||||
test-suite extism-example
|
||||
|
||||
@@ -15,7 +15,7 @@ library
|
||||
hs-source-dirs: .
|
||||
default-language: Haskell2010
|
||||
build-depends:
|
||||
base >= 4.16.1 && < 4.19.0,
|
||||
bytestring >= 0.11.3 && < 0.12,
|
||||
json >= 0.10 && < 0.11,
|
||||
base >= 4.16.1 && < 5,
|
||||
bytestring >= 0.11.3 && <= 0.12,
|
||||
json >= 0.10 && <= 0.11,
|
||||
base64-bytestring >= 1.2.1 && < 1.3,
|
||||
|
||||
@@ -18,7 +18,7 @@ pub struct Plugin {
|
||||
/// Keep track of the number of times we're instantiated, this exists
|
||||
/// to avoid issues with memory piling up since `Instance`s are only
|
||||
/// actually cleaned up along with a `Store`
|
||||
pub instantiations: usize,
|
||||
instantiations: usize,
|
||||
|
||||
/// The ID used to identify this plugin with the `Timer`
|
||||
pub timer_id: uuid::Uuid,
|
||||
@@ -26,7 +26,7 @@ pub struct Plugin {
|
||||
/// A handle used to cancel execution of a plugin
|
||||
pub(crate) cancel_handle: sdk::ExtismCancelHandle,
|
||||
|
||||
/// Runtime determines any initialization and cleanup functions needed
|
||||
/// Runtime determines any initialization functions needed
|
||||
/// to run a module
|
||||
pub(crate) runtime: Option<Runtime>,
|
||||
}
|
||||
@@ -231,13 +231,13 @@ impl Plugin {
|
||||
instance: None,
|
||||
instance_pre,
|
||||
store,
|
||||
instantiations: 1,
|
||||
runtime: None,
|
||||
timer_id,
|
||||
cancel_handle: sdk::ExtismCancelHandle {
|
||||
id: timer_id,
|
||||
epoch_timer_tx: None,
|
||||
},
|
||||
instantiations: 0,
|
||||
};
|
||||
|
||||
plugin.internal_mut().store = &mut plugin.store;
|
||||
@@ -245,72 +245,18 @@ impl Plugin {
|
||||
Ok(plugin)
|
||||
}
|
||||
|
||||
/// Get a function by name
|
||||
pub fn get_func(&mut self, function: impl AsRef<str>) -> Option<Func> {
|
||||
if let None = &self.instance {
|
||||
if let Ok(x) = self.instance_pre.instantiate(&mut self.store) {
|
||||
self.instance = Some(x);
|
||||
self.detect_runtime();
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(instance) = &mut self.instance {
|
||||
instance.get_func(&mut self.store, function.as_ref())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Store input in memory and initialize `Internal` pointer
|
||||
pub(crate) fn set_input(&mut self, input: *const u8, mut len: usize) -> Result<(), Error> {
|
||||
if input.is_null() {
|
||||
len = 0;
|
||||
}
|
||||
|
||||
{
|
||||
let store = &mut self.store as *mut _;
|
||||
let linker = &mut self.linker as *mut _;
|
||||
let internal = self.internal_mut();
|
||||
internal.store = store;
|
||||
internal.linker = linker;
|
||||
}
|
||||
|
||||
let bytes = unsafe { std::slice::from_raw_parts(input, len) };
|
||||
trace!("Input size: {}", bytes.len());
|
||||
|
||||
if let Some(f) = self.linker.get(&mut self.store, "env", "extism_reset") {
|
||||
f.into_func().unwrap().call(&mut self.store, &[], &mut [])?;
|
||||
}
|
||||
|
||||
let offs = self.memory_alloc_bytes(bytes)?;
|
||||
|
||||
if let Some(f) = self.linker.get(&mut self.store, "env", "extism_input_set") {
|
||||
f.into_func().unwrap().call(
|
||||
&mut self.store,
|
||||
&[Val::I64(offs as i64), Val::I64(len as i64)],
|
||||
&mut [],
|
||||
)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Create a new instance from the same modules
|
||||
pub fn reinstantiate(&mut self) -> Result<(), Error> {
|
||||
if let Some(limiter) = self.internal_mut().memory_limiter.as_mut() {
|
||||
limiter.reset();
|
||||
}
|
||||
|
||||
let (main_name, main) = self
|
||||
.modules
|
||||
.get("main")
|
||||
.map(|x| ("main", x))
|
||||
.unwrap_or_else(|| {
|
||||
let entry = self.modules.iter().last().unwrap();
|
||||
(entry.0.as_str(), entry.1)
|
||||
});
|
||||
|
||||
pub(crate) fn reset_store(&mut self) -> Result<(), Error> {
|
||||
self.instance = None;
|
||||
if self.instantiations > 5 {
|
||||
let (main_name, main) = self
|
||||
.modules
|
||||
.get("main")
|
||||
.map(|x| ("main", x))
|
||||
.unwrap_or_else(|| {
|
||||
let entry = self.modules.iter().last().unwrap();
|
||||
(entry.0.as_str(), entry.1)
|
||||
});
|
||||
|
||||
let engine = self.store.engine().clone();
|
||||
let internal = self.internal();
|
||||
self.store = Store::new(
|
||||
@@ -344,8 +290,69 @@ impl Plugin {
|
||||
internal.linker = linker;
|
||||
}
|
||||
|
||||
self.instance = None;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn instantiate(&mut self) -> Result<(), Error> {
|
||||
self.instance = Some(self.instance_pre.instantiate(&mut self.store)?);
|
||||
self.instantiations += 1;
|
||||
if let Some(limiter) = &mut self.internal_mut().memory_limiter {
|
||||
limiter.reset();
|
||||
}
|
||||
self.detect_runtime();
|
||||
self.initialize_runtime()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Get a function by name
|
||||
pub fn get_func(&mut self, function: impl AsRef<str>) -> Option<Func> {
|
||||
if let None = &self.instance {
|
||||
if let Err(e) = self.instantiate() {
|
||||
error!("Unable to instantiate: {e}");
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(instance) = &mut self.instance {
|
||||
instance.get_func(&mut self.store, function.as_ref())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Store input in memory and initialize `Internal` pointer
|
||||
pub(crate) fn set_input(&mut self, input: *const u8, mut len: usize) -> Result<(), Error> {
|
||||
if input.is_null() {
|
||||
len = 0;
|
||||
}
|
||||
|
||||
{
|
||||
let store = &mut self.store as *mut _;
|
||||
let linker = &mut self.linker as *mut _;
|
||||
let internal = self.internal_mut();
|
||||
internal.store = store;
|
||||
internal.linker = linker;
|
||||
}
|
||||
|
||||
let bytes = unsafe { std::slice::from_raw_parts(input, len) };
|
||||
trace!("Input size: {}", bytes.len());
|
||||
|
||||
if let Some(f) = self.linker.get(&mut self.store, "env", "extism_reset") {
|
||||
f.into_func().unwrap().call(&mut self.store, &[], &mut [])?;
|
||||
} else {
|
||||
error!("Call to extism_reset failed");
|
||||
}
|
||||
|
||||
let offs = self.memory_alloc_bytes(bytes)?;
|
||||
|
||||
if let Some(f) = self.linker.get(&mut self.store, "env", "extism_input_set") {
|
||||
f.into_func().unwrap().call(
|
||||
&mut self.store,
|
||||
&[Val::I64(offs as i64), Val::I64(len as i64)],
|
||||
&mut [],
|
||||
)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -356,66 +363,55 @@ impl Plugin {
|
||||
|
||||
fn detect_runtime(&mut self) {
|
||||
// Check for Haskell runtime initialization functions
|
||||
// Initialize Haskell runtime if `hs_init` and `hs_exit` are present,
|
||||
// Initialize Haskell runtime if `hs_init` is present,
|
||||
// by calling the `hs_init` export
|
||||
if let Some(init) = self.get_func("hs_init") {
|
||||
if let Some(cleanup) = self.get_func("hs_exit") {
|
||||
if init.typed::<(i32, i32), ()>(&self.store()).is_err() {
|
||||
trace!(
|
||||
"hs_init function found with type {:?}",
|
||||
init.ty(self.store())
|
||||
);
|
||||
}
|
||||
self.runtime = Some(Runtime::Haskell { init, cleanup });
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for `__wasm_call_ctors` and `__wasm_call_dtors`, this is used by WASI to
|
||||
// initialize certain interfaces.
|
||||
if self.has_wasi() {
|
||||
let init = if let Some(init) = self.get_func("__wasm_call_ctors") {
|
||||
if init.typed::<(), ()>(&self.store()).is_err() {
|
||||
trace!(
|
||||
"__wasm_call_ctors function found with type {:?}",
|
||||
init.ty(self.store())
|
||||
);
|
||||
return;
|
||||
}
|
||||
trace!("WASI runtime detected");
|
||||
init
|
||||
} else if let Some(init) = self.get_func("_initialize") {
|
||||
let reactor_init = if let Some(init) = self.get_func("_initialize") {
|
||||
if init.typed::<(), ()>(&self.store()).is_err() {
|
||||
trace!(
|
||||
"_initialize function found with type {:?}",
|
||||
init.ty(self.store())
|
||||
);
|
||||
return;
|
||||
}
|
||||
trace!("WASI reactor module detected");
|
||||
init
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
|
||||
let cleanup = if let Some(cleanup) = self.get_func("__wasm_call_dtors") {
|
||||
if cleanup.typed::<(), ()>(&self.store()).is_err() {
|
||||
trace!(
|
||||
"__wasm_call_dtors function found with type {:?}",
|
||||
cleanup.ty(self.store())
|
||||
);
|
||||
None
|
||||
} else {
|
||||
Some(cleanup)
|
||||
trace!("WASI reactor module detected");
|
||||
Some(init)
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
self.runtime = Some(Runtime::Wasi { init, cleanup });
|
||||
self.runtime = Some(Runtime::Haskell { init, reactor_init });
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for `__wasm_call_ctors` or `_initialize`, this is used by WASI to
|
||||
// initialize certain interfaces.
|
||||
let init = if let Some(init) = self.get_func("__wasm_call_ctors") {
|
||||
if init.typed::<(), ()>(&self.store()).is_err() {
|
||||
trace!(
|
||||
"__wasm_call_ctors function found with type {:?}",
|
||||
init.ty(self.store())
|
||||
);
|
||||
return;
|
||||
}
|
||||
trace!("WASI runtime detected");
|
||||
init
|
||||
} else if let Some(init) = self.get_func("_initialize") {
|
||||
if init.typed::<(), ()>(&self.store()).is_err() {
|
||||
trace!(
|
||||
"_initialize function found with type {:?}",
|
||||
init.ty(self.store())
|
||||
);
|
||||
return;
|
||||
}
|
||||
trace!("Reactor module detected");
|
||||
init
|
||||
} else {
|
||||
return;
|
||||
};
|
||||
|
||||
self.runtime = Some(Runtime::Wasi { init });
|
||||
|
||||
trace!("No runtime detected");
|
||||
}
|
||||
|
||||
@@ -424,7 +420,10 @@ impl Plugin {
|
||||
if let Some(runtime) = &self.runtime {
|
||||
trace!("Plugin::initialize_runtime");
|
||||
match runtime {
|
||||
Runtime::Haskell { init, cleanup: _ } => {
|
||||
Runtime::Haskell { init, reactor_init } => {
|
||||
if let Some(reactor_init) = reactor_init {
|
||||
reactor_init.call(&mut store, &[], &mut [])?;
|
||||
}
|
||||
let mut results = vec![Val::null(); init.ty(&store).results().len()];
|
||||
init.call(
|
||||
&mut store,
|
||||
@@ -433,7 +432,7 @@ impl Plugin {
|
||||
)?;
|
||||
debug!("Initialized Haskell language runtime");
|
||||
}
|
||||
Runtime::Wasi { init, cleanup: _ } => {
|
||||
Runtime::Wasi { init } => {
|
||||
init.call(&mut store, &[], &mut [])?;
|
||||
debug!("Initialied WASI runtime");
|
||||
}
|
||||
@@ -443,35 +442,6 @@ impl Plugin {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
pub(crate) fn cleanup_runtime(&mut self) -> Result<(), Error> {
|
||||
if let Some(runtime) = self.runtime.clone() {
|
||||
trace!("Plugin::cleanup_runtime");
|
||||
match runtime {
|
||||
Runtime::Wasi {
|
||||
init: _,
|
||||
cleanup: Some(cleanup),
|
||||
} => {
|
||||
debug!("Calling __wasm_call_dtors");
|
||||
cleanup.call(self.store_mut(), &[], &mut [])?;
|
||||
}
|
||||
Runtime::Wasi {
|
||||
init: _,
|
||||
cleanup: None,
|
||||
} => (),
|
||||
// Cleanup Haskell runtime if `hs_exit` and `hs_exit` are present,
|
||||
// by calling the `hs_exit` export
|
||||
Runtime::Haskell { init: _, cleanup } => {
|
||||
let mut results = vec![Val::null(); cleanup.ty(self.store()).results().len()];
|
||||
cleanup.call(self.store_mut(), &[], results.as_mut_slice())?;
|
||||
debug!("Cleaned up Haskell language runtime");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Start the timer for a Plugin - this is used for both timeouts
|
||||
/// and cancellation
|
||||
pub(crate) fn start_timer(
|
||||
@@ -511,6 +481,11 @@ impl Plugin {
|
||||
// Enumerates the supported PDK language runtimes
|
||||
#[derive(Clone)]
|
||||
pub(crate) enum Runtime {
|
||||
Haskell { init: Func, cleanup: Func },
|
||||
Wasi { init: Func, cleanup: Option<Func> },
|
||||
Haskell {
|
||||
init: Func,
|
||||
reactor_init: Option<Func>,
|
||||
},
|
||||
Wasi {
|
||||
init: Func,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -11,17 +11,24 @@ pub struct PluginRef<'a> {
|
||||
|
||||
impl<'a> PluginRef<'a> {
|
||||
/// Initialize the plugin for a new call
|
||||
pub fn start_call(mut self) -> Self {
|
||||
pub(crate) fn start_call(mut self, is_start: bool) -> Self {
|
||||
trace!("PluginRef::start_call: {}", self.id,);
|
||||
let plugin = self.as_mut();
|
||||
|
||||
if plugin.has_wasi() || plugin.runtime.is_some() {
|
||||
if let Err(e) = plugin.reinstantiate() {
|
||||
error!("Failed to reinstantiate: {e:?}");
|
||||
plugin.error(format!("Failed to reinstantiate: {e:?}"), ());
|
||||
return self;
|
||||
let plugin = unsafe { &mut *self.plugin };
|
||||
if is_start {
|
||||
if let Err(e) = plugin.reset_store() {
|
||||
error!("Call to Plugin::reset_store failed: {e:?}");
|
||||
}
|
||||
}
|
||||
|
||||
if plugin.instance.is_none() {
|
||||
trace!("Plugin::instance is none, instantiating");
|
||||
if let Err(e) = plugin.instantiate() {
|
||||
error!("Plugin::instantiate failed: {e:?}");
|
||||
plugin.error(e, ());
|
||||
}
|
||||
}
|
||||
|
||||
self.running = true;
|
||||
self
|
||||
}
|
||||
|
||||
@@ -495,23 +495,23 @@ pub unsafe extern "C" fn extism_plugin_call(
|
||||
) -> i32 {
|
||||
let ctx = &mut *ctx;
|
||||
|
||||
// Get function name
|
||||
let name = std::ffi::CStr::from_ptr(func_name);
|
||||
let name = match name.to_str() {
|
||||
Ok(name) => name,
|
||||
Err(e) => return ctx.error(e, -1),
|
||||
};
|
||||
let is_start = name == "_start";
|
||||
|
||||
// Get a `PluginRef` and call `init` to set up the plugin input and memory, this is only
|
||||
// needed before a new call
|
||||
let mut plugin_ref = match PluginRef::new(ctx, plugin_id, true) {
|
||||
None => return -1,
|
||||
Some(p) => p.start_call(),
|
||||
Some(p) => p.start_call(is_start),
|
||||
};
|
||||
let tx = plugin_ref.epoch_timer_tx.clone();
|
||||
let plugin = plugin_ref.as_mut();
|
||||
|
||||
// Find function
|
||||
let name = std::ffi::CStr::from_ptr(func_name);
|
||||
let name = match name.to_str() {
|
||||
Ok(name) => name,
|
||||
Err(e) => return plugin.error(e, -1),
|
||||
};
|
||||
let is_start = name == "_start";
|
||||
|
||||
let func = match plugin.get_func(name) {
|
||||
Some(x) => x,
|
||||
None => return plugin.error(format!("Function not found: {name}"), -1),
|
||||
@@ -530,13 +530,6 @@ pub unsafe extern "C" fn extism_plugin_call(
|
||||
return plugin.error(e, -1);
|
||||
}
|
||||
|
||||
// Initialize runtime
|
||||
if !is_start {
|
||||
if let Err(e) = plugin.initialize_runtime() {
|
||||
return plugin.error(format!("Failed to initialize runtime: {e:?}"), -1);
|
||||
}
|
||||
}
|
||||
|
||||
if plugin.has_error() {
|
||||
return -1;
|
||||
}
|
||||
@@ -552,13 +545,6 @@ pub unsafe extern "C" fn extism_plugin_call(
|
||||
let mut results = vec![wasmtime::Val::null(); n_results];
|
||||
let res = func.call(plugin.store_mut(), &[], results.as_mut_slice());
|
||||
|
||||
// Cleanup runtime
|
||||
if !is_start {
|
||||
if let Err(e) = plugin.cleanup_runtime() {
|
||||
return plugin.error(format!("Failed to cleanup runtime: {e:?}"), -1);
|
||||
}
|
||||
}
|
||||
|
||||
match res {
|
||||
Ok(()) => (),
|
||||
Err(e) => {
|
||||
|
||||
@@ -1,45 +1,260 @@
|
||||
/* automatically generated by rust-bindgen 0.60.1 */
|
||||
/* automatically generated by rust-bindgen 0.65.1 */
|
||||
|
||||
pub type __uint8_t = ::std::os::raw::c_uchar;
|
||||
pub type __int32_t = ::std::os::raw::c_int;
|
||||
pub type __uint64_t = ::std::os::raw::c_ulong;
|
||||
#[doc = " Signed 32 bit integer."]
|
||||
pub const ExtismValType_I32: ExtismValType = 0;
|
||||
#[doc = " Signed 64 bit integer."]
|
||||
pub const ExtismValType_I64: ExtismValType = 1;
|
||||
#[doc = " Floating point 32 bit integer."]
|
||||
pub const ExtismValType_F32: ExtismValType = 2;
|
||||
#[doc = " Floating point 64 bit integer."]
|
||||
pub const ExtismValType_F64: ExtismValType = 3;
|
||||
#[doc = " A 128 bit number."]
|
||||
pub const ExtismValType_V128: ExtismValType = 4;
|
||||
#[doc = " A reference to a Wasm function."]
|
||||
pub const ExtismValType_FuncRef: ExtismValType = 5;
|
||||
#[doc = " A reference to opaque data in the Wasm instance."]
|
||||
pub const ExtismValType_ExternRef: ExtismValType = 6;
|
||||
#[doc = " A list of all possible value types in WebAssembly."]
|
||||
pub type ExtismValType = ::std::os::raw::c_uint;
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct ExtismContext {
|
||||
_unused: [u8; 0],
|
||||
}
|
||||
pub type ExtismPlugin = i32;
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct ExtismCancelHandle {
|
||||
_unused: [u8; 0],
|
||||
}
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct ExtismFunction {
|
||||
_unused: [u8; 0],
|
||||
}
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct ExtismCurrentPlugin {
|
||||
_unused: [u8; 0],
|
||||
}
|
||||
pub type ExtismSize = u64;
|
||||
#[doc = " A union type for host function argument/return values"]
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone)]
|
||||
pub union ExtismValUnion {
|
||||
pub i32_: i32,
|
||||
pub i64_: i64,
|
||||
pub f32_: f32,
|
||||
pub f64_: f64,
|
||||
}
|
||||
#[test]
|
||||
fn bindgen_test_layout_ExtismValUnion() {
|
||||
const UNINIT: ::std::mem::MaybeUninit<ExtismValUnion> = ::std::mem::MaybeUninit::uninit();
|
||||
let ptr = UNINIT.as_ptr();
|
||||
assert_eq!(
|
||||
::std::mem::size_of::<ExtismValUnion>(),
|
||||
8usize,
|
||||
concat!("Size of: ", stringify!(ExtismValUnion))
|
||||
);
|
||||
assert_eq!(
|
||||
::std::mem::align_of::<ExtismValUnion>(),
|
||||
8usize,
|
||||
concat!("Alignment of ", stringify!(ExtismValUnion))
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { ::std::ptr::addr_of!((*ptr).i32_) as usize - ptr as usize },
|
||||
0usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(ExtismValUnion),
|
||||
"::",
|
||||
stringify!(i32_)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { ::std::ptr::addr_of!((*ptr).i64_) as usize - ptr as usize },
|
||||
0usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(ExtismValUnion),
|
||||
"::",
|
||||
stringify!(i64_)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { ::std::ptr::addr_of!((*ptr).f32_) as usize - ptr as usize },
|
||||
0usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(ExtismValUnion),
|
||||
"::",
|
||||
stringify!(f32_)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { ::std::ptr::addr_of!((*ptr).f64_) as usize - ptr as usize },
|
||||
0usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(ExtismValUnion),
|
||||
"::",
|
||||
stringify!(f64_)
|
||||
)
|
||||
);
|
||||
}
|
||||
#[doc = " `ExtismVal` holds the type and value of a function argument/return"]
|
||||
#[repr(C)]
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct ExtismVal {
|
||||
pub t: ExtismValType,
|
||||
pub v: ExtismValUnion,
|
||||
}
|
||||
#[test]
|
||||
fn bindgen_test_layout_ExtismVal() {
|
||||
const UNINIT: ::std::mem::MaybeUninit<ExtismVal> = ::std::mem::MaybeUninit::uninit();
|
||||
let ptr = UNINIT.as_ptr();
|
||||
assert_eq!(
|
||||
::std::mem::size_of::<ExtismVal>(),
|
||||
16usize,
|
||||
concat!("Size of: ", stringify!(ExtismVal))
|
||||
);
|
||||
assert_eq!(
|
||||
::std::mem::align_of::<ExtismVal>(),
|
||||
8usize,
|
||||
concat!("Alignment of ", stringify!(ExtismVal))
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { ::std::ptr::addr_of!((*ptr).t) as usize - ptr as usize },
|
||||
0usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(ExtismVal),
|
||||
"::",
|
||||
stringify!(t)
|
||||
)
|
||||
);
|
||||
assert_eq!(
|
||||
unsafe { ::std::ptr::addr_of!((*ptr).v) as usize - ptr as usize },
|
||||
8usize,
|
||||
concat!(
|
||||
"Offset of field: ",
|
||||
stringify!(ExtismVal),
|
||||
"::",
|
||||
stringify!(v)
|
||||
)
|
||||
);
|
||||
}
|
||||
#[doc = " Host function signature"]
|
||||
pub type ExtismFunctionType = ::std::option::Option<
|
||||
unsafe extern "C" fn(
|
||||
plugin: *mut ExtismCurrentPlugin,
|
||||
inputs: *const ExtismVal,
|
||||
n_inputs: ExtismSize,
|
||||
outputs: *mut ExtismVal,
|
||||
n_outputs: ExtismSize,
|
||||
data: *mut ::std::os::raw::c_void,
|
||||
),
|
||||
>;
|
||||
pub type ExtismPlugin = i32;
|
||||
extern "C" {
|
||||
#[doc = " Create a new context"]
|
||||
pub fn extism_context_new() -> *mut ExtismContext;
|
||||
}
|
||||
extern "C" {
|
||||
#[doc = " Free a context"]
|
||||
pub fn extism_context_free(ctx: *mut ExtismContext);
|
||||
}
|
||||
extern "C" {
|
||||
#[doc = " Returns a pointer to the memory of the currently running plugin\n NOTE: this should only be called from host functions."]
|
||||
pub fn extism_current_plugin_memory(plugin: *mut ExtismCurrentPlugin) -> *mut u8;
|
||||
}
|
||||
extern "C" {
|
||||
#[doc = " Allocate a memory block in the currently running plugin\n NOTE: this should only be called from host functions."]
|
||||
pub fn extism_current_plugin_memory_alloc(
|
||||
plugin: *mut ExtismCurrentPlugin,
|
||||
n: ExtismSize,
|
||||
) -> u64;
|
||||
}
|
||||
extern "C" {
|
||||
#[doc = " Get the length of an allocated block\n NOTE: this should only be called from host functions."]
|
||||
pub fn extism_current_plugin_memory_length(
|
||||
plugin: *mut ExtismCurrentPlugin,
|
||||
n: ExtismSize,
|
||||
) -> ExtismSize;
|
||||
}
|
||||
extern "C" {
|
||||
#[doc = " Free an allocated memory block\n NOTE: this should only be called from host functions."]
|
||||
pub fn extism_current_plugin_memory_free(plugin: *mut ExtismCurrentPlugin, ptr: u64);
|
||||
}
|
||||
extern "C" {
|
||||
#[doc = " Create a new host function\n\n Arguments\n - `name`: function name, this should be valid UTF-8\n - `inputs`: argument types\n - `n_inputs`: number of argument types\n - `outputs`: return types\n - `n_outputs`: number of return types\n - `func`: the function to call\n - `user_data`: a pointer that will be passed to the function when it's called\n this value should live as long as the function exists\n - `free_user_data`: a callback to release the `user_data` value when the resulting\n `ExtismFunction` is freed.\n\n Returns a new `ExtismFunction` or `null` if the `name` argument is invalid."]
|
||||
pub fn extism_function_new(
|
||||
name: *const ::std::os::raw::c_char,
|
||||
inputs: *const ExtismValType,
|
||||
n_inputs: ExtismSize,
|
||||
outputs: *const ExtismValType,
|
||||
n_outputs: ExtismSize,
|
||||
func: ExtismFunctionType,
|
||||
user_data: *mut ::std::os::raw::c_void,
|
||||
free_user_data: ::std::option::Option<
|
||||
unsafe extern "C" fn(__: *mut ::std::os::raw::c_void),
|
||||
>,
|
||||
) -> *mut ExtismFunction;
|
||||
}
|
||||
extern "C" {
|
||||
#[doc = " Set the namespace of an `ExtismFunction`"]
|
||||
pub fn extism_function_set_namespace(
|
||||
ptr: *mut ExtismFunction,
|
||||
namespace_: *const ::std::os::raw::c_char,
|
||||
);
|
||||
}
|
||||
extern "C" {
|
||||
#[doc = " Free an `ExtismFunction`"]
|
||||
pub fn extism_function_free(ptr: *mut ExtismFunction);
|
||||
}
|
||||
extern "C" {
|
||||
#[doc = " Create a new plugin with additional host functions\n\n `wasm`: is a WASM module (wat or wasm) or a JSON encoded manifest\n `wasm_size`: the length of the `wasm` parameter\n `functions`: an array of `ExtismFunction*`\n `n_functions`: the number of functions provided\n `with_wasi`: enables/disables WASI"]
|
||||
pub fn extism_plugin_new(
|
||||
ctx: *mut ExtismContext,
|
||||
wasm: *const u8,
|
||||
wasm_size: ExtismSize,
|
||||
functions: *mut *const ExtismFunction,
|
||||
n_functions: ExtismSize,
|
||||
with_wasi: bool,
|
||||
) -> ExtismPlugin;
|
||||
}
|
||||
extern "C" {
|
||||
#[doc = " Update a plugin, keeping the existing ID\n\n Similar to `extism_plugin_new` but takes an `index` argument to specify\n which plugin to update\n\n Memory for this plugin will be reset upon update"]
|
||||
pub fn extism_plugin_update(
|
||||
ctx: *mut ExtismContext,
|
||||
index: ExtismPlugin,
|
||||
wasm: *const u8,
|
||||
wasm_size: ExtismSize,
|
||||
functions: *mut *const ExtismFunction,
|
||||
nfunctions: ExtismSize,
|
||||
with_wasi: bool,
|
||||
) -> bool;
|
||||
}
|
||||
extern "C" {
|
||||
#[doc = " Remove a plugin from the registry and free associated memory"]
|
||||
pub fn extism_plugin_free(ctx: *mut ExtismContext, plugin: ExtismPlugin);
|
||||
}
|
||||
extern "C" {
|
||||
#[doc = " Get plugin ID for cancellation"]
|
||||
pub fn extism_plugin_cancel_handle(
|
||||
ctx: *mut ExtismContext,
|
||||
plugin: ExtismPlugin,
|
||||
) -> *const ExtismCancelHandle;
|
||||
}
|
||||
extern "C" {
|
||||
#[doc = " Cancel a running plugin"]
|
||||
pub fn extism_plugin_cancel(handle: *const ExtismCancelHandle) -> bool;
|
||||
}
|
||||
extern "C" {
|
||||
#[doc = " Remove all plugins from the registry"]
|
||||
pub fn extism_context_reset(ctx: *mut ExtismContext);
|
||||
}
|
||||
extern "C" {
|
||||
#[doc = " Update plugin config values, this will merge with the existing values"]
|
||||
pub fn extism_plugin_config(
|
||||
ctx: *mut ExtismContext,
|
||||
plugin: ExtismPlugin,
|
||||
@@ -48,6 +263,7 @@ extern "C" {
|
||||
) -> bool;
|
||||
}
|
||||
extern "C" {
|
||||
#[doc = " Returns true if `func_name` exists"]
|
||||
pub fn extism_plugin_function_exists(
|
||||
ctx: *mut ExtismContext,
|
||||
plugin: ExtismPlugin,
|
||||
@@ -55,6 +271,7 @@ extern "C" {
|
||||
) -> bool;
|
||||
}
|
||||
extern "C" {
|
||||
#[doc = " Call a function\n\n `func_name`: is the function to call\n `data`: is the input data\n `data_len`: is the length of `data`"]
|
||||
pub fn extism_plugin_call(
|
||||
ctx: *mut ExtismContext,
|
||||
plugin_id: ExtismPlugin,
|
||||
@@ -64,26 +281,29 @@ extern "C" {
|
||||
) -> i32;
|
||||
}
|
||||
extern "C" {
|
||||
#[doc = " Get the error associated with a `Context` or `Plugin`, if `plugin` is `-1` then the context\n error will be returned"]
|
||||
pub fn extism_error(
|
||||
ctx: *mut ExtismContext,
|
||||
plugin: ExtismPlugin,
|
||||
) -> *const ::std::os::raw::c_char;
|
||||
}
|
||||
extern "C" {
|
||||
#[doc = " Get the length of a plugin's output data"]
|
||||
pub fn extism_plugin_output_length(ctx: *mut ExtismContext, plugin: ExtismPlugin)
|
||||
-> ExtismSize;
|
||||
}
|
||||
extern "C" {
|
||||
#[doc = " Get a pointer to the output data"]
|
||||
pub fn extism_plugin_output_data(ctx: *mut ExtismContext, plugin: ExtismPlugin) -> *const u8;
|
||||
}
|
||||
extern "C" {
|
||||
#[doc = " Set log file and level"]
|
||||
pub fn extism_log_file(
|
||||
filename: *const ::std::os::raw::c_char,
|
||||
log_level: *const ::std::os::raw::c_char,
|
||||
) -> bool;
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
pub fn extism_version(
|
||||
) -> *const ::std::os::raw::c_char;
|
||||
#[doc = " Get the Extism version string"]
|
||||
pub fn extism_version() -> *const ::std::os::raw::c_char;
|
||||
}
|
||||
|
||||
@@ -44,6 +44,7 @@ mod tests {
|
||||
|
||||
const WASM: &[u8] = include_bytes!("../../wasm/code-functions.wasm");
|
||||
const WASM_LOOP: &[u8] = include_bytes!("../../wasm/loop.wasm");
|
||||
const WASM_GLOBALS: &[u8] = include_bytes!("../../wasm/globals.wasm");
|
||||
|
||||
fn hello_world(
|
||||
_plugin: &mut CurrentPlugin,
|
||||
@@ -303,4 +304,15 @@ mod tests {
|
||||
let _output = plugin.call("count_vowels", "abc123").unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_globals() {
|
||||
let context = Context::new();
|
||||
let mut plugin = Plugin::new(&context, WASM_GLOBALS, [], true).unwrap();
|
||||
for i in 0..1000 {
|
||||
let output = plugin.call("globals", "").unwrap();
|
||||
let count: serde_json::Value = serde_json::from_slice(&output).unwrap();
|
||||
assert_eq!(count.get("count").unwrap().as_i64().unwrap(), i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BIN
wasm/globals.wasm
Executable file
BIN
wasm/globals.wasm
Executable file
Binary file not shown.
Reference in New Issue
Block a user