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:
zach
2023-08-08 14:12:09 -07:00
committed by GitHub
parent 07623ef2da
commit d80584600b
8 changed files with 390 additions and 190 deletions

View File

@@ -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

View File

@@ -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,

View File

@@ -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,
},
}

View File

@@ -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
}

View File

@@ -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) => {

View File

@@ -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;
}

View File

@@ -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

Binary file not shown.