Fixes https://github.com/extism/extism/issues/537
- Requires wasmtime 14.0.0 or greater for coredump serialization
- Adds `EXTISM_COREDUMP` environment variable to write wasmtime coredump
to a file when an error occurs
- This will create a coredump from the main wasm module, which means it
doesn't give us too much insight into the kernel
- Adds `EXTISM_MEMDUMP` environment variable to write extism linear
memory to a file when an error occurs
- This gives us access to the kernel memory
- Adds some missing profiling options
- Converts timeouts to a Trap instead of a plain error, this helps us
get better information about where the timeout occured
- Some small improvements to error handling after a plugin call
- Adds a test for coredump and memdump generation
- Adds the ability to configure debug options using `PluginBuilder`
- Fixes memory layout and a wasted page of memory in the kernel, found
while debugging a memory dump
---------
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: zshipko <zshipko@users.noreply.github.com>
- Updates to wasmtime 13.0.0 as the lowest supported version
- Removes `wasmtime-wasi-nn` support because it now takes parameters to
specify particular backends and registries during initialization. Since
I don't think anyone is using the `nn` feature I chose to remove it for
now, but we could also expand the manifest to add these options.
- Removes restrictions around when `memory.max_pages` setting can be
used. Before we used `wasmtime::MemoryLimiter` it was hard to determine
how much was being allocated at runtime, so we used to calculate the
totals statically, which required every module to have a maximum memory
set at compile time. This PR allows `memory.max` to be used on any
module!
- Fixes a crash when the `memory.max_pages` field is set
- Adds a test that checks for failure when allocating more than
configured
- Adds `extism-convert` crate with `ToBytes`, `FromBytes` and
`FromBytesOwned` traits
- This serves as a single interface for reading/writing rich types from
WebAssembly linear memory.
- Supports `Json` and `Msgpack` and `Protobuf` encodings out-of-the-box
- Updates `Plugin::call` to take `ToBytes` as the input argument and
return a `FromBytes` value
- Adds `host_fn!` macro to simplify host function creation
- Cleans up generated documentation a little
- PR for the Rust PDK: https://github.com/extism/rust-pdk/pull/31
- Adds a `typed_plugin!` macro to implement type-safe wrappers around
`Plugin`
- After this we should focus on adding similar type-conversion helpers
to the SDKs and other PDKs to make it easier to use across languages.
For example, a Python host running a Rust plugin using Msgpack encoded
types.
## Examples
### Calling a function
Instead of the untyped, bytes-only `call` function:
```rust
let output = plugin.call("func_name", "my data").unwrap();
let output: MyType = serde_json::from_slice(&output).unwrap();
```
We can now use richer types to encode/decode our values directly when
using `call`:
```rust
let Json(output) = plugin.call::<_, Json<MyType>>("func_name", "my data").unwrap();
```
### Allocating inside of a host function
The same interface works for host functions, so instead of:
```rust
fn hello_world(
plugin: &mut CurrentPlugin,
inputs: &[Val],
outputs: &mut [Val],
_user_data: UserData,
) -> Result<(), Error> {
let handle = plugin.memory_handle_val(&inputs[0])?;
let input = plugin.memory_read_str(handle)?;
let output = plugin.memory_alloc_bytes(&input).unwrap();
outputs[0] = output.into();
Ok(())
}
```
Becomes:
```rust
fn hello_world(
plugin: &mut CurrentPlugin,
inputs: &[Val],
outputs: &mut [Val],
_user_data: UserData,
) -> Result<(), Error> {
let my_value: String = plugin.memory_get_val(&inputs[0])?;
let output = plugin.memory_new(&my_value)?;
outputs[0] = plugin.memory_to_val(output);
Ok(())
}
```
Although this isn't much of an improvement, using the `host_fn` macro,
we can really begin to see how the above function is really just an
identity function:
```rust
host_fn!(hello_world(a: String) -> String {
a
});
```
### typed_plugin!
`typed_plugin!` is used to make a typed wrapper around a Plugin:
```rust
/// Create the typed plugin
typed_plugin!(Testing {
count_vowels(&str) -> Json<Count>
});
/// Create the `Plugin` and convert it to `Testing` wrapper
let mut plugin: Testing = Plugin::new(WASM, [f], true).unwrap().into();
/// Call the `count_vowels` function:
let Json(output0): Json<Count> = plugin.count_vowels("abc123")?;
```
It could make sense to convert `host_fn` and/or `typed_plugin` to
proc-macros at some point, but for now they work and provide some
flexibility in experimenting with the interfaces. Another future update
could be to figure out a nice way to make it so input can be written in
multiple chunks, so the entire input doesn't have to get copied into
memory at once.
Just getting my feet wet with the codebase a little bit! I noticed that
TOML manifests weren't loading the extism runtime by default while doing
a walkthrough. This commit ensures the runtime is loaded and adds a
test.
Also, fix a tiny typo in a comment.
- Removes the `ExtismContext` type from runtime and all SDKs
- Removed SDK functions: `extism_context_new`, `extism_context_reset`,
`extism_context_free`
- All SDKs have been updated, but there are still some TODOs below
- Removes `extism_plugin_update`
- Plugins can no longer be updated - a new plugin should be created
instead
- Adds `extism_plugin_id` to uniquely identify plugins
- Merges the `extism-runtime` and `extism` crates (there is no longer an
`extism-runtime` crate)
- Makes `extism::Manifest` an alias for `extism_manifest::Manifest`
instead of a distinct type
- Adds `MemoryHandle` type to SDKs to refer to blocks of Extism memory
that can be accessed in host functions
- Improves thread-safety of Plugins, adds C++ test to call a single
plugin from multiple threads.
- Expands wasmtime bounds to include 12.0
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.
- Delays instantiation to right before a call by using `instantiate_pre`
instead. This fixes an issue with the assemblyscript PDK.
- Makes timeouts only apply to calls
- Also bumps the wasmtime lower-bounds to 10.0, because the return type
of the epoch callback has changed
This PR adds the `kernel` directory which contains a port of the Extism
memory allocator compiled to WebAssembly and removes
`runtime/src/memory.rs` completely.
Being able to re-use memory functions as a WASM module allows us to
begin to experiment with porting Extism to new runtimes!
This is in a draft state while I'm verifying some of these changes.
- Requires modules compiled to run with manifests that set `max_memory`
to have an exported memory with lower and upper bounds
- Includes the size of memory exported from modules when calculating
available memory for plugins
## How to compile a module with bounded memory
You will need to pass `--max-memory=$NUM_BYTES` to wasm-ld. `$NUM_BYTES`
must be a multiple of the page size. Here are some examples for
supported PDK languages:
**C**
Pass `-Wl,--max-memory=65536` to your C compiler
**Rust**:
In a `.cargo/config` file:
```toml
[target.wasm32-unknown-unknown]
rustflags = ["-Clink-args=--max-memory=65536"]
```
**Haskell**
Add the following to the cabal file entry for your `cabal.project` file:
```
package myproject
ghc-options:
-optl -Wl,--max-memory=65536
```
**AssemblyScript**
Pass `--maximumMemory 65536` to the assemblyscropt compiler
**TinyGo**:
Create a `target.json` file:
```json
{
"inherits": [ "wasm" ],
"ldflags": [
"--max-memory=65536",
]
}
```
and build using `tinygo -target ./target.json`
EIP: https://github.com/extism/proposals/pull/8
This PR makes minor breaking changes to several SDKs, but not to runtime
C API. The threadsafety updates in the Rust SDK are kind of specific to
Rust, I'm not sure if it makes sense to add the locks to all the other
SDKs at this point. For the most part the `Context` and `Plugin` types
in the SDKs should be safe to use protected by a mutex but they aren't
inherently threadsafe. That kind of locking should probably be done by
the user.
- Runtime
- improve thread safety
- reinstantiates less
- fixes a potential resource exhaustion bug from re-instantiating using
the same store too many times
- Rust SDK
- adds `Send` and `Sync` implementations for `Context`
- adds test sharing a context between threads
- adds `Plugin::call_map` to call a plugin and handle the output with
the lock held
- adds testing sharing an `Arc<Mutex<Plugin>>` between threads
- adds `Plugin::create` and `Plugin::create_from_manifest` to create a
plugin without a `Context`
- Python
- BREAKING
- changes `Plugin` constructor to take `context` as an optional named
argument, to update use `Plugin(data, context=context)` instead
- Ruby
- BREAKING
- changes `Plugin` constructor to take `context` as an optional named
argument, to update use `Plugin.new(data, context=context)` instead
- Go
- adds `NewPlugin` and `NewPluginFromManifest` functions
- Node
- BREAKING
- changes `Plugin` constructor to take `context` as an optional named
argument, to update use `new Plugin(data, wasi, config, host, context)`
instead of `new Plugin(context, data, wasi, functions, config)` (most
people are probably using `context.plugin` instead of the Plugin
constructor anyway)
- OCaml
- BREAKING
- changes `Plugin.create` and `Plugin.of_manifest` to take `context` as
an optional named argument, to update `Plugin.create ~context data` and
`Plugin.of_manifest ~context data` instead
- Haskell
- adds `createPlugin` and `createPluginFromManifest` functions
- Elixir
- adds `Plugin.new` to make a plugin without going through
`Context.new_plugin`
- Java
- adds new `Plugin` constructors without a `Context` argument
- C++
- BREAKING
- Updates `Plugin` constructor to take an optional context as the last
argument, instead of requiring it to be the first argument
- Use `Plugin(wasm, wasi, functions, ctx)` instead of `Plugin(ctx, wasm,
wasi, functions)`
- Zig
- Adds `Plugin.create` and `Plugin.createWithManifest` to create plugins
in their own context.
---------
Co-authored-by: zach <zach@dylib.so>
Co-authored-by: Benjamin Eckel <bhelx@simst.im>
This PR adds the ability to cancel running plugins from another thread
in the host.
- All SDKs have been updated except .NET
- Adds a new SDK type: `ExtismCancelHandle`
- Adds 2 new SDK functions: `extism_plugin_cancel_handle` and
`extism_plugin_cancel`
- `extism_plugin_cancel_handle` returns a pointer to
`ExtismCancelHandle`, which can be passed to `extism_plugin_cancel` to
stop plugin execution.
- One thing that's worth noting is when plugin is executing a host
function it cannot be cancelled until after the host function returns.
---------
Co-authored-by: Etienne ANNE <etienne.anne@icloud.com>
Unsure if now is the best time to do the `extism` crate version bumps,
but I figured they'd need to happen at some point in the near future
anyways. Happy to revert if there is any opposition. Also, I added back
the local `path` property to the Elixir NIF crate manifest. I want to
see if this breaks again in CI, or if we can leave it.
---------
Co-authored-by: zach <zach@dylib.so>
- New types:
- `ExtismValType` - Enum of WebAssembly types
- `ExtismValUnion` - A union of the possible WebAssembly types
- `ExtismVal` - A struct with `ExtismValType` and `ExtismValUnion`
- `ExtismFunction` - The host function wrapper type
- `ExtismFunctionType` - The type of the host function callback
- `ExtismCurrentPlugin` - Provides access to the currently running
plugin from inside a host function
- New functions:
- `extism_function_new` - Create a new `ExtismFunction`
- `extism_function_free` - Free an `ExtismFunction`
- `extism_current_plugin_memory`, `extism_current_plugin_memory_alloc`,
`extism_current_plugin_memory_free`,
`extism_current_plugin_memory_length` - Manage plugin memory from inside
a host functions
- Updated functions
- `extism_plugin_new` and `extsim_plugin_update` - now accept two extra
parameters for `ExtismFunction*` array and length of that array
## Notes
- Host functions take a user-data argument, which is owned by the
resulting `ExtismFunction` and will be cleaned up when
`extism_function_free` is called (if a cleanup function was passed in
with the user data)
- Host functions in every SDK require working with `ExtismVal` arguments
directly, this is pretty low-level for what is kind of a high-level
feature. We could work on adding some types to the SDKs that make
working with pointers to plugin data more accessible, maybe something
similar to how the Rust PDK handes input/output data.
- In each language the host functions more-or-less share a signature:
`(CurrentPlugin plugin, Val inputs[], Val outputs[], userData)`
- C, C++, OCaml and Go take a single userData argument but Python and
Node take a "rest" argument which allows passing any number of user-data
values
- Go requires the host function to be exported:
f9eb5ed839/go/main.go (L13-L26)
- Zig and Ruby should be relatively simple to add host functions to next
but I haven't really looked into Elixir, .NET or Java yet.
- Also closes#20
After adding the timer code plugins written in Haskell fail with a
timeout when trying to call `hs_init` - this PR fixes the runtime
initialization and updates those functions to respect the configured
timeout.
- Adds `Manifest::timeout_ms` field to specify the plugin timeout in
milliseconds
- Sets a 30 second default timeout
Co-authored-by: Steve Manuel <steve@dylib.so>
- Adds the ability to initialize plugins with additional functions
created by the host (only supported by the Rust SDK right now)
- Adds `PluginBuilder` to the Rust SDK to make it easier to configure all the options
## Hello, world
The following example uses `println` from the host to print to stdout
from inside the plugin.
Host:
```rust
use extism::*;
fn main() -> Result<(), Error> {
let context = Context::new();
let manifest = Manifest::new([std::path::PathBuf::from("code.wasm")]);
let say_hello = Function::new("say_hello", [ValType::I64], [], |caller, input, _out| {
let internal = caller.data();
let r = internal.memory().get_str(input[0].unwrap_i64() as usize)?;
println!("Hello, {r}!");
Ok(())
});
let mut plugin = PluginBuilder::new(manifest)
.with_function(say_hello)
.with_wasi(true)
.build(&context)?;
plugin.call("test", b"world").unwrap();
Ok(())
}
```
Plugin:
```rust
use extism_pdk::*;
extern "C" {
fn say_hello(offs: u64);
}
#[plugin_fn]
pub fn test(input: String) -> FnResult<()> {
let memory = Memory::from_bytes(&input);
unsafe {
say_hello(memory.offset);
}
Ok(())
}
```
- Switches from `stack` to `cabal`
- Cleanup SDK code
- Adds release action (still waiting on Hackage upload approval)
Co-authored-by: Steve Manuel <steve@dylib.so>
Adds ability to make local files available to plugins. Using the Rust
SDK:
```rust
let wasm = include_bytes!("code.wasm");
let mut context = Context::new();
let manifest = Manifest::new(vec![Wasm::data(wasm)]).with_allowed_path("/path/on/disk", "/plugin/path");
let plugin = Plugin::new_with_manifest(&mut context, &manifest, false)?;
...
```
- Adds some helper functions for creating a manifest, mostly helpful for
the Rust SDK
- Renames `ManifestWasm` to `Wasm`
- Renames `ManifestMemory` to `MemoryOptions`
- `ManifestWasm` and `ManifestMemory` have been marked as deprecated
- Adds an alias from `MemoryOptions::max_pages` to `MemoryOptions::max`
in serde specification
Co-authored-by: Steve Manuel <steve@dylib.so>
- Adds `allowed_hosts` to the manifest
- If there are any `allowed_hosts` then `extism_http_request` checks to
make sure a URL's host is listed before performing the reques
- Adds `ExtismContext` instead of global `PLUGINS` registry
- Adds `extism_context_new`, `extism_context_free` and
`extism_context_reset`
- Requires updating nearly every SDK function to add context parameter
- Renames some SDK functions to follow better naming conventions
- `extism_plugin_register` -> `extism_plugin_new`
- `extism_output_get` -> `extism_plugin_output_data`
- `extism_output_length` -> `extism_plugin_output_length`
- `extism_call` -> `extism_plugin_call`
- Updates `extism_error` to return the context error when -1 issued for
the plug-in ID
- Adds `extism_plugin_free` to remove an existing plugin
- Updates SDKs to include these functions
- Updates SDK examples and comments
Co-authored-by: Steve Manuel <steve@dylib.so>
This PR updates `extism_output_get` to return an actual pointer to the
output value (`const uint8_t* extism_output_get(PluginIndex plugin)`
instead of `void extism_output_get(PluginIndex plugin, uint8_t *buffer,
uint64_t length)`), this pointer will only be valid until the next call,
but it makes it possible to access the output data without copying.
The input buffer is also not copied and the same issue applies:
the input buffer must not change during `call`.
Co-authored-by: Steve Manuel <steve@dylib.so>