mirror of
https://github.com/extism/extism.git
synced 2026-01-11 23:08:06 -05:00
Compare commits
19 Commits
v1.8.0
...
fix-androi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3ecc9c0dac | ||
|
|
f57d987d48 | ||
|
|
9da6d43f05 | ||
|
|
d1a248e19e | ||
|
|
7b2db7588b | ||
|
|
a367bc77a3 | ||
|
|
98800fe8a0 | ||
|
|
7e3665ae8c | ||
|
|
3cfde7966d | ||
|
|
5d18cc71eb | ||
|
|
4f599d4b20 | ||
|
|
4db57de98e | ||
|
|
9134635b37 | ||
|
|
75428f26e2 | ||
|
|
7beeee35f1 | ||
|
|
6cf8251d90 | ||
|
|
4d0799ca37 | ||
|
|
14477ceb39 | ||
|
|
af67a6990c |
9
.github/workflows/ci.yml
vendored
9
.github/workflows/ci.yml
vendored
@@ -1,4 +1,4 @@
|
||||
on:
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- .github/actions/extism/**
|
||||
@@ -116,4 +116,9 @@ jobs:
|
||||
path: target/**
|
||||
key: ${{ runner.os }}-target-${{ github.sha }}
|
||||
- run: cargo install cargo-criterion
|
||||
- run: cargo criterion
|
||||
- run:
|
||||
cargo criterion
|
||||
- run: |
|
||||
git fetch
|
||||
git checkout main
|
||||
cargo criterion
|
||||
|
||||
4
.github/workflows/release-dotnet-native.yaml
vendored
4
.github/workflows/release-dotnet-native.yaml
vendored
@@ -1,4 +1,6 @@
|
||||
on:
|
||||
release:
|
||||
types: [published, edited]
|
||||
workflow_dispatch:
|
||||
|
||||
name: Release .NET Native NuGet Packages
|
||||
@@ -18,7 +20,7 @@ jobs:
|
||||
uses: actions/setup-dotnet@v3.0.3
|
||||
with:
|
||||
dotnet-version: 7.x
|
||||
- uses: dawidd6/action-download-artifact@v2
|
||||
- uses: dawidd6/action-download-artifact@v6
|
||||
with:
|
||||
workflow: release.yml
|
||||
name: release-artifacts
|
||||
|
||||
36
.github/workflows/release.yml
vendored
36
.github/workflows/release.yml
vendored
@@ -38,6 +38,20 @@ jobs:
|
||||
static-dll-artifact: ''
|
||||
pc-in: 'extism.pc.in'
|
||||
static-pc-in: 'extism-static.pc.in'
|
||||
- os: 'ubuntu'
|
||||
target: 'aarch64-linux-android'
|
||||
artifact: 'libextism.so'
|
||||
static-artifact: 'libextism.a'
|
||||
static-dll-artifact: ''
|
||||
pc-in: 'extism.pc.in'
|
||||
static-pc-in: 'extism-static.pc.in'
|
||||
- os: 'ubuntu'
|
||||
target: 'x86_64-linux-android'
|
||||
artifact: 'libextism.so'
|
||||
static-artifact: 'libextism.a'
|
||||
static-dll-artifact: ''
|
||||
pc-in: 'extism.pc.in'
|
||||
static-pc-in: 'extism-static.pc.in'
|
||||
- os: 'ubuntu'
|
||||
target: 'aarch64-unknown-linux-gnu'
|
||||
artifact: 'libextism.so'
|
||||
@@ -98,13 +112,11 @@ jobs:
|
||||
pyproject="$(cat extism-maturin/pyproject.toml)"
|
||||
<<<"$pyproject" >extism-maturin/pyproject.toml sed -e 's/^version = "0.0.0.replaced-by-ci"/version = "'"$version"'"/g'
|
||||
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
- uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
profile: minimal
|
||||
override: true
|
||||
target: ${{ matrix.target }}
|
||||
override: true
|
||||
toolchain: stable
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
@@ -113,11 +125,15 @@ jobs:
|
||||
cache-on-failure: "true"
|
||||
|
||||
- name: Build Target (${{ matrix.os }} ${{ matrix.target }})
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
use-cross: ${{ matrix.os != 'windows' }}
|
||||
command: build
|
||||
args: --release --target ${{ matrix.target }} -p ${{ env.RUNTIME_CRATE }}
|
||||
if: ${{ matrix.os != 'windows' }}
|
||||
run: |
|
||||
cargo install cross
|
||||
cross build --release --target ${{ matrix.target }} -p ${{ env.RUNTIME_CRATE }}
|
||||
|
||||
- name: Build Target (${{ matrix.os }} ${{ matrix.target }})
|
||||
if: ${{ matrix.os == 'windows' }}
|
||||
run: |
|
||||
cargo build --release --target ${{ matrix.target }} -p ${{ env.RUNTIME_CRATE }}
|
||||
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
|
||||
72
README.md
72
README.md
@@ -61,11 +61,11 @@ started:
|
||||
|
||||
# Compile WebAssembly to run in Extism Hosts
|
||||
|
||||
Extism Hosts (running the SDK) must execute WebAssembly code that has a [PDK, or Plug-in Development Kit](https://extism.org/docs/concepts/pdk),
|
||||
library compiled in to the `.wasm` binary. PDKs make it easy for plug-in /
|
||||
extension code authors to read input from the host and return data back, read
|
||||
provided configuration, set/get variables, make outbound HTTP calls if allowed,
|
||||
and more.
|
||||
Extism Hosts (running the SDK) must execute WebAssembly code that has a
|
||||
[PDK, or Plug-in Development Kit](https://extism.org/docs/concepts/pdk), library
|
||||
compiled in to the `.wasm` binary. PDKs make it easy for plug-in / extension
|
||||
code authors to read input from the host and return data back, read provided
|
||||
configuration, set/get variables, make outbound HTTP calls if allowed, and more.
|
||||
|
||||
Pick a PDK to import into your Wasm program, and refer to the documentation to
|
||||
get started:
|
||||
@@ -74,13 +74,73 @@ get started:
|
||||
| ------------------ | ---------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------- | --------------------------------------------------------- |
|
||||
| Rust PDK | <img alt="Rust PDK" src="https://extism.org/img/sdk-languages/rust.svg" width="50px"/> | https://github.com/extism/rust-pdk | [Crates.io](https://crates.io/crates/extism-pdk) |
|
||||
| JS PDK | <img alt="JS PDK" src="https://extism.org/img/sdk-languages/js.svg" width="50px"/> | https://github.com/extism/js-pdk | N/A |
|
||||
| Python PDK | <img alt="Python PDK" src="https://extism.org/img/sdk-languages/python.svg" width="50px"/> | https://github.com/extism/python-pdk | N/A |
|
||||
| Go PDK | <img alt="Go PDK" src="https://extism.org/img/sdk-languages/go.svg" width="50px"/> | https://github.com/extism/go-pdk | [Go mod](https://pkg.go.dev/github.com/extism/go-pdk) |
|
||||
| Haskell PDK | <img alt="Haskell PDK" src="https://extism.org/img/sdk-languages/haskell.svg" width="50px"/> | https://github.com/extism/haskell-pdk | [Hackage](https://hackage.haskell.org/package/extism-pdk) |
|
||||
| AssemblyScript PDK | <img alt="AssemblyScript PDK" src="https://extism.org/img/sdk-languages/assemblyscript.svg" width="50px"/> | https://github.com/extism/assemblyscript-pdk | [NPM](https://www.npmjs.com/package/@extism/as-pdk) |
|
||||
| .NET PDK | <img alt=".NET PDK" src="https://extism.org/img/sdk-languages/dotnet.svg" width="50px"/> | https://github.com/extism/dotnet-pdk <br/>(supports C# & F#!) | https://www.nuget.org/packages/Extism.Pdk |
|
||||
| .NET PDK | <img alt=".NET PDK" src="https://extism.org/img/sdk-languages/dotnet.svg" width="50px"/> | https://github.com/extism/dotnet-pdk <br/>(supports C# & F#!) | [Nuget](https://www.nuget.org/packages/Extism.Pdk) |
|
||||
| C PDK | <img alt="C PDK" src="https://extism.org/img/sdk-languages/c.svg" width="50px"/> | https://github.com/extism/c-pdk | N/A |
|
||||
| C++ PDK | <img alt="C++ PDK" src="https://extism.org/img/sdk-languages/cpp.svg" width="50px"/> | https://github.com/extism/cpp-pdk | N/A |
|
||||
| Zig PDK | <img alt="Zig PDK" src="https://extism.org/img/sdk-languages/zig.svg" width="50px"/> | https://github.com/extism/zig-pdk | N/A |
|
||||
|
||||
# Generating Bindings
|
||||
|
||||
It's often very useful to define a schema to describe the function signatures
|
||||
and types you want to use between Extism SDK and PDK languages.
|
||||
|
||||
[XTP Bindgen](https://github.com/dylibso/xtp-bindgen) is an open source
|
||||
framework to generate PDK bindings for Extism plug-ins. It's used by the
|
||||
[XTP Platform](https://www.getxtp.com/), but can be used outside of the platform
|
||||
to define any Extism compatible plug-in system.
|
||||
|
||||
## 1. Install the `xtp` CLI.
|
||||
|
||||
See installation instructions
|
||||
[here](https://docs.xtp.dylibso.com/docs/cli#installation).
|
||||
|
||||
## 2. Create a schema using our OpenAPI-inspired IDL:
|
||||
|
||||
```yaml
|
||||
version: v1-draft
|
||||
exports:
|
||||
CountVowels:
|
||||
input:
|
||||
type: string
|
||||
contentType: text/plain; charset=utf-8
|
||||
output:
|
||||
$ref: "#/components/schemas/VowelReport"
|
||||
contentType: application/json
|
||||
# components.schemas defined in example-schema.yaml...
|
||||
```
|
||||
|
||||
> See an example in [example-schema.yaml](./example-schema.yaml), or a full
|
||||
> "kitchen sink" example on
|
||||
> [the docs page](https://docs.xtp.dylibso.com/docs/concepts/xtp-schema/).
|
||||
|
||||
## 3. Generate bindings to use from your plugins:
|
||||
|
||||
```
|
||||
xtp plugin init --schema-file ./example-schema.yaml
|
||||
> 1. TypeScript
|
||||
2. Go
|
||||
3. Rust
|
||||
4. Python
|
||||
5. C#
|
||||
6. Zig
|
||||
7. C++
|
||||
8. GitHub Template
|
||||
9. Local Template
|
||||
```
|
||||
|
||||
This will create an entire boilerplate plugin project for you to get started
|
||||
with. Implement the empty function(s), and run `xtp plugin build` to compile
|
||||
your plugin.
|
||||
|
||||
> For more information about XTP Bindgen, see the
|
||||
> [dylibso/xtp-bindgen](https://github.com/dylibso/xtp-bindgen) repository and
|
||||
> the official
|
||||
> [XTP Schema documentation](https://docs.xtp.dylibso.com/docs/concepts/xtp-schema).
|
||||
|
||||
# Support
|
||||
|
||||
## Discord
|
||||
|
||||
@@ -55,7 +55,7 @@ encoding!(pub Json, serde_json::to_vec, serde_json::from_slice);
|
||||
#[cfg(feature = "msgpack")]
|
||||
encoding!(pub Msgpack, rmp_serde::to_vec, rmp_serde::from_slice);
|
||||
|
||||
impl<'a> ToBytes<'a> for serde_json::Value {
|
||||
impl ToBytes<'_> for serde_json::Value {
|
||||
type Bytes = Vec<u8>;
|
||||
|
||||
fn to_bytes(&self) -> Result<Self::Bytes, Error> {
|
||||
@@ -85,7 +85,7 @@ impl<T: AsRef<[u8]>> From<T> for Base64<T> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: AsRef<[u8]>> ToBytes<'a> for Base64<T> {
|
||||
impl<T: AsRef<[u8]>> ToBytes<'_> for Base64<T> {
|
||||
type Bytes = String;
|
||||
|
||||
fn to_bytes(&self) -> Result<Self::Bytes, Error> {
|
||||
@@ -124,7 +124,7 @@ impl<T: prost::Message> From<T> for Prost<T> {
|
||||
}
|
||||
|
||||
#[cfg(feature = "prost")]
|
||||
impl<'a, T: prost::Message> ToBytes<'a> for Prost<T> {
|
||||
impl<T: prost::Message> ToBytes<'_> for Prost<T> {
|
||||
type Bytes = Vec<u8>;
|
||||
|
||||
fn to_bytes(&self) -> Result<Self::Bytes, Error> {
|
||||
@@ -146,7 +146,7 @@ impl<T: Default + prost::Message> FromBytesOwned for Prost<T> {
|
||||
pub struct Protobuf<T: protobuf::Message>(pub T);
|
||||
|
||||
#[cfg(feature = "protobuf")]
|
||||
impl<'a, T: protobuf::Message> ToBytes<'a> for Protobuf<T> {
|
||||
impl<T: protobuf::Message> ToBytes<'_> for Protobuf<T> {
|
||||
type Bytes = Vec<u8>;
|
||||
|
||||
fn to_bytes(&self) -> Result<Self::Bytes, Error> {
|
||||
|
||||
@@ -62,7 +62,7 @@ fn rountrip_option() {
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "raw", target_endian = "little"))]
|
||||
mod tests {
|
||||
mod raw_tests {
|
||||
use crate::*;
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -61,21 +61,21 @@ pub trait ToBytes<'a> {
|
||||
fn to_bytes(&self) -> Result<Self::Bytes, Error>;
|
||||
}
|
||||
|
||||
impl<'a> ToBytes<'a> for () {
|
||||
impl ToBytes<'_> for () {
|
||||
type Bytes = [u8; 0];
|
||||
fn to_bytes(&self) -> Result<Self::Bytes, Error> {
|
||||
Ok([])
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> ToBytes<'a> for Vec<u8> {
|
||||
impl ToBytes<'_> for Vec<u8> {
|
||||
type Bytes = Vec<u8>;
|
||||
fn to_bytes(&self) -> Result<Self::Bytes, Error> {
|
||||
Ok(self.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> ToBytes<'a> for String {
|
||||
impl ToBytes<'_> for String {
|
||||
type Bytes = String;
|
||||
fn to_bytes(&self) -> Result<Self::Bytes, Error> {
|
||||
Ok(self.clone())
|
||||
@@ -96,7 +96,7 @@ impl<'a> ToBytes<'a> for &'a str {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> ToBytes<'a> for f64 {
|
||||
impl ToBytes<'_> for f64 {
|
||||
type Bytes = [u8; 8];
|
||||
|
||||
fn to_bytes(&self) -> Result<Self::Bytes, Error> {
|
||||
@@ -104,7 +104,7 @@ impl<'a> ToBytes<'a> for f64 {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> ToBytes<'a> for f32 {
|
||||
impl ToBytes<'_> for f32 {
|
||||
type Bytes = [u8; 4];
|
||||
|
||||
fn to_bytes(&self) -> Result<Self::Bytes, Error> {
|
||||
@@ -112,7 +112,7 @@ impl<'a> ToBytes<'a> for f32 {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> ToBytes<'a> for i64 {
|
||||
impl ToBytes<'_> for i64 {
|
||||
type Bytes = [u8; 8];
|
||||
|
||||
fn to_bytes(&self) -> Result<Self::Bytes, Error> {
|
||||
@@ -120,7 +120,7 @@ impl<'a> ToBytes<'a> for i64 {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> ToBytes<'a> for i32 {
|
||||
impl ToBytes<'_> for i32 {
|
||||
type Bytes = [u8; 4];
|
||||
|
||||
fn to_bytes(&self) -> Result<Self::Bytes, Error> {
|
||||
@@ -128,7 +128,7 @@ impl<'a> ToBytes<'a> for i32 {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> ToBytes<'a> for u64 {
|
||||
impl ToBytes<'_> for u64 {
|
||||
type Bytes = [u8; 8];
|
||||
|
||||
fn to_bytes(&self) -> Result<Self::Bytes, Error> {
|
||||
@@ -136,7 +136,7 @@ impl<'a> ToBytes<'a> for u64 {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> ToBytes<'a> for u32 {
|
||||
impl ToBytes<'_> for u32 {
|
||||
type Bytes = [u8; 4];
|
||||
|
||||
fn to_bytes(&self) -> Result<Self::Bytes, Error> {
|
||||
|
||||
28
example-schema.yaml
Normal file
28
example-schema.yaml
Normal file
@@ -0,0 +1,28 @@
|
||||
# yaml-language-server: $schema=https://xtp.dylibso.com/assets/wasm/schema.json
|
||||
# Learn more at https://docs.xtp.dylibso.com/docs/concepts/xtp-schema
|
||||
version: v1-draft
|
||||
exports:
|
||||
CountVowels:
|
||||
input:
|
||||
type: string
|
||||
contentType: text/plain; charset=utf-8
|
||||
output:
|
||||
$ref: "#/components/schemas/VowelReport"
|
||||
contentType: application/json
|
||||
components:
|
||||
schemas:
|
||||
VowelReport:
|
||||
description: The result of counting vowels on the Vowels input.
|
||||
properties:
|
||||
count:
|
||||
type: integer
|
||||
format: int32
|
||||
description: The count of vowels for input string.
|
||||
total:
|
||||
type: integer
|
||||
format: int32
|
||||
description: The cumulative amount of vowels counted, if this keeps state across multiple function calls.
|
||||
nullable: true
|
||||
vowels:
|
||||
type: string
|
||||
description: The set of vowels used to get the count, e.g. "aAeEiIoOuU"
|
||||
@@ -269,8 +269,8 @@ pub struct Manifest {
|
||||
/// Config values are made accessible using the PDK `extism_config_get` function
|
||||
#[serde(default)]
|
||||
pub config: BTreeMap<String, String>,
|
||||
#[serde(default)]
|
||||
|
||||
#[serde(default)]
|
||||
/// Specifies which hosts may be accessed via HTTP, if this is empty then
|
||||
/// no hosts may be accessed. Wildcards may be used.
|
||||
pub allowed_hosts: Option<Vec<String>>,
|
||||
@@ -417,14 +417,14 @@ mod wasmdata {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<Manifest> for std::borrow::Cow<'a, [u8]> {
|
||||
impl From<Manifest> for std::borrow::Cow<'_, [u8]> {
|
||||
fn from(m: Manifest) -> Self {
|
||||
let s = serde_json::to_vec(&m).unwrap();
|
||||
std::borrow::Cow::Owned(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&Manifest> for std::borrow::Cow<'a, [u8]> {
|
||||
impl From<&Manifest> for std::borrow::Cow<'_, [u8]> {
|
||||
fn from(m: &Manifest) -> Self {
|
||||
let s = serde_json::to_vec(&m).unwrap();
|
||||
std::borrow::Cow::Owned(s)
|
||||
|
||||
@@ -9,9 +9,9 @@ repository.workspace = true
|
||||
version.workspace = true
|
||||
|
||||
[dependencies]
|
||||
wasmtime = ">= 20.0.0, < 24.0.0"
|
||||
wasi-common = ">= 20.0.0, < 24.0.0"
|
||||
wiggle = ">= 20.0.0, < 24.0.0"
|
||||
wasmtime = {version = ">= 26.0.0, < 27.0.0"}
|
||||
wasi-common = {version = ">= 26.0.0, < 27.0.0"}
|
||||
wiggle = {version = ">= 26.0.0, < 27.0.0"}
|
||||
anyhow = "1"
|
||||
serde = {version = "1", features = ["derive"]}
|
||||
serde_json = "1"
|
||||
@@ -34,7 +34,7 @@ register-filesystem = [] # enables wasm to be loaded from disk
|
||||
http = ["ureq"] # enables extism_http_request
|
||||
|
||||
[build-dependencies]
|
||||
cbindgen = { version = "0.27", default-features = false }
|
||||
cbindgen = { version = "0.28", default-features = false }
|
||||
|
||||
[dev-dependencies]
|
||||
criterion = "0.5.1"
|
||||
|
||||
@@ -36,6 +36,31 @@ pub fn create_plugin(c: &mut Criterion) {
|
||||
});
|
||||
}
|
||||
|
||||
pub fn create_compiled(c: &mut Criterion) {
|
||||
let mut g = c.benchmark_group("create");
|
||||
g.noise_threshold(1.0);
|
||||
g.significance_level(0.2);
|
||||
g.bench_function("create_compiled", |b| {
|
||||
b.iter(|| {
|
||||
let plugin = PluginBuilder::new(COUNT_VOWELS).with_wasi(true);
|
||||
let _compiled = CompiledPlugin::new(plugin).unwrap();
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
pub fn create_plugin_compiled(c: &mut Criterion) {
|
||||
let mut g = c.benchmark_group("create");
|
||||
g.noise_threshold(1.0);
|
||||
g.significance_level(0.2);
|
||||
let plugin = PluginBuilder::new(COUNT_VOWELS).with_wasi(true);
|
||||
let compiled = CompiledPlugin::new(plugin).unwrap();
|
||||
g.bench_function("create_plugin_compiled", |b| {
|
||||
b.iter(|| {
|
||||
let _plugin = Plugin::new_from_compiled(&compiled).unwrap();
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
pub fn create_plugin_no_cache(c: &mut Criterion) {
|
||||
let mut g = c.benchmark_group("create");
|
||||
g.noise_threshold(1.0);
|
||||
@@ -279,7 +304,9 @@ criterion_group!(
|
||||
reflect_linked,
|
||||
basic,
|
||||
create_plugin,
|
||||
create_plugin_compiled,
|
||||
create_plugin_no_cache,
|
||||
create_compiled,
|
||||
count_vowels
|
||||
);
|
||||
criterion_main!(benches);
|
||||
|
||||
@@ -24,6 +24,7 @@ fn main() {
|
||||
.rename_item("CurrentPlugin", "ExtismCurrentPlugin")
|
||||
.rename_item("CancelHandle", "ExtismCancelHandle")
|
||||
.rename_item("Plugin", "ExtismPlugin")
|
||||
.rename_item("CompiledPlugin", "ExtismCompiledPlugin")
|
||||
.rename_item("Function", "ExtismFunction")
|
||||
.with_style(cbindgen::Style::Type)
|
||||
.generate()
|
||||
|
||||
@@ -2,6 +2,8 @@ use extism::*;
|
||||
fn main() {
|
||||
let url = Wasm::file("../wasm/read_write.wasm");
|
||||
let manifest = Manifest::new([url])
|
||||
// This will fail because we're using a readonly path (specified with the `ro:` prefix)
|
||||
// to overwrite the data file, remove `ro:` from the path on the following line
|
||||
.with_allowed_path("ro:src/tests/data".to_string(), "/data")
|
||||
.with_config_key("path", "/data/data.txt");
|
||||
|
||||
@@ -18,6 +20,7 @@ fn main() {
|
||||
|
||||
println!("-----------------------------------------------------");
|
||||
|
||||
// If the allowed path is readonly then writing back to the file should fail
|
||||
println!("trying to write file: ");
|
||||
let line = format!(
|
||||
"Hello World at {:?}\n",
|
||||
|
||||
@@ -52,6 +52,8 @@ typedef enum {
|
||||
*/
|
||||
typedef struct ExtismCancelHandle ExtismCancelHandle;
|
||||
|
||||
typedef struct ExtismCompiledPlugin ExtismCompiledPlugin;
|
||||
|
||||
/**
|
||||
* CurrentPlugin stores data that is available to the caller in PDK functions, this should
|
||||
* only be accessed from inside a host function
|
||||
@@ -179,6 +181,21 @@ void extism_function_free(ExtismFunction *f);
|
||||
*/
|
||||
void extism_function_set_namespace(ExtismFunction *ptr, const char *namespace_);
|
||||
|
||||
/**
|
||||
* Pre-compile an Extism plugin
|
||||
*/
|
||||
ExtismCompiledPlugin *extism_compiled_plugin_new(const uint8_t *wasm,
|
||||
ExtismSize wasm_size,
|
||||
const ExtismFunction **functions,
|
||||
ExtismSize n_functions,
|
||||
bool with_wasi,
|
||||
char **errmsg);
|
||||
|
||||
/**
|
||||
* Free `ExtismCompiledPlugin`
|
||||
*/
|
||||
void extism_compiled_plugin_free(ExtismCompiledPlugin *plugin);
|
||||
|
||||
/**
|
||||
* Create a new plugin with host functions, the functions passed to this function no longer need to be manually freed using
|
||||
*
|
||||
@@ -195,6 +212,11 @@ ExtismPlugin *extism_plugin_new(const uint8_t *wasm,
|
||||
bool with_wasi,
|
||||
char **errmsg);
|
||||
|
||||
/**
|
||||
* Create a new plugin from an `ExtismCompiledPlugin`
|
||||
*/
|
||||
ExtismPlugin *extism_plugin_new_from_compiled(const ExtismCompiledPlugin *compiled, char **errmsg);
|
||||
|
||||
/**
|
||||
* Create a new plugin and set the number of instructions a plugin is allowed to execute
|
||||
*/
|
||||
@@ -217,7 +239,7 @@ void extism_plugin_allow_http_response_headers(ExtismPlugin *plugin);
|
||||
void extism_plugin_new_error_free(char *err);
|
||||
|
||||
/**
|
||||
* Remove a plugin from the registry and free associated memory
|
||||
* Free `ExtismPlugin`
|
||||
*/
|
||||
void extism_plugin_free(ExtismPlugin *plugin);
|
||||
|
||||
|
||||
@@ -56,7 +56,12 @@ impl wasmtime::ResourceLimiter for MemoryLimiter {
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
fn table_growing(&mut self, _current: u32, desired: u32, maximum: Option<u32>) -> Result<bool> {
|
||||
fn table_growing(
|
||||
&mut self,
|
||||
_current: usize,
|
||||
desired: usize,
|
||||
maximum: Option<usize>,
|
||||
) -> Result<bool> {
|
||||
if let Some(max) = maximum {
|
||||
return Ok(desired <= max);
|
||||
}
|
||||
|
||||
@@ -39,7 +39,9 @@ pub use current_plugin::CurrentPlugin;
|
||||
pub use extism_convert::{FromBytes, FromBytesOwned, ToBytes};
|
||||
pub use extism_manifest::{Manifest, Wasm, WasmMetadata};
|
||||
pub use function::{Function, UserData, Val, ValType, PTR};
|
||||
pub use plugin::{CancelHandle, Plugin, WasmInput, EXTISM_ENV_MODULE, EXTISM_USER_MODULE};
|
||||
pub use plugin::{
|
||||
CancelHandle, CompiledPlugin, Plugin, WasmInput, EXTISM_ENV_MODULE, EXTISM_USER_MODULE,
|
||||
};
|
||||
pub use plugin_builder::{DebugOptions, PluginBuilder};
|
||||
|
||||
pub(crate) use internal::{Internal, Wasi};
|
||||
|
||||
@@ -4,6 +4,7 @@ use std::{
|
||||
};
|
||||
|
||||
use anyhow::Context;
|
||||
use plugin_builder::PluginBuilderOptions;
|
||||
|
||||
use crate::*;
|
||||
|
||||
@@ -37,6 +38,66 @@ impl CancelHandle {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct CompiledPlugin {
|
||||
pub(crate) manifest: Manifest,
|
||||
pub(crate) modules: BTreeMap<String, Module>,
|
||||
pub(crate) options: PluginBuilderOptions,
|
||||
pub(crate) engine: wasmtime::Engine,
|
||||
}
|
||||
|
||||
impl CompiledPlugin {
|
||||
/// Create a new pre-compiled plugin
|
||||
pub fn new(builder: PluginBuilder) -> Result<CompiledPlugin, Error> {
|
||||
let mut config = builder.config.unwrap_or_default();
|
||||
config
|
||||
.async_support(false)
|
||||
.epoch_interruption(true)
|
||||
.debug_info(builder.options.debug_options.debug_info)
|
||||
.coredump_on_trap(builder.options.debug_options.coredump.is_some())
|
||||
.profiler(builder.options.debug_options.profiling_strategy)
|
||||
.wasm_tail_call(true)
|
||||
.wasm_function_references(true)
|
||||
.wasm_gc(true);
|
||||
|
||||
if builder.options.fuel.is_some() {
|
||||
config.consume_fuel(true);
|
||||
}
|
||||
|
||||
match &builder.options.cache_config {
|
||||
Some(None) => (),
|
||||
Some(Some(path)) => {
|
||||
config.cache_config_load(path)?;
|
||||
}
|
||||
None => {
|
||||
if let Ok(env) = std::env::var("EXTISM_CACHE_CONFIG") {
|
||||
if !env.is_empty() {
|
||||
config.cache_config_load(&env)?;
|
||||
}
|
||||
} else {
|
||||
config.cache_config_load_default()?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let engine = Engine::new(&config)?;
|
||||
|
||||
let (manifest, modules) = manifest::load(&engine, builder.source)?;
|
||||
if modules.len() <= 1 {
|
||||
anyhow::bail!("No wasm modules provided");
|
||||
} else if !modules.contains_key(MAIN_KEY) {
|
||||
anyhow::bail!("No main module provided");
|
||||
}
|
||||
|
||||
Ok(CompiledPlugin {
|
||||
manifest,
|
||||
modules,
|
||||
options: builder.options,
|
||||
engine,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Plugin contains everything needed to execute a WASM function
|
||||
pub struct Plugin {
|
||||
/// A unique ID for each plugin
|
||||
@@ -138,7 +199,7 @@ pub enum WasmInput<'a> {
|
||||
ManifestRef(&'a Manifest),
|
||||
}
|
||||
|
||||
impl<'a> From<Manifest> for WasmInput<'a> {
|
||||
impl From<Manifest> for WasmInput<'_> {
|
||||
fn from(value: Manifest) -> Self {
|
||||
WasmInput::Manifest(value)
|
||||
}
|
||||
@@ -168,7 +229,7 @@ impl<'a> From<&'a str> for WasmInput<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<Vec<u8>> for WasmInput<'a> {
|
||||
impl From<Vec<u8>> for WasmInput<'_> {
|
||||
fn from(value: Vec<u8>) -> Self {
|
||||
WasmInput::Data(value.into())
|
||||
}
|
||||
@@ -264,6 +325,36 @@ fn relink(
|
||||
get_log_level() -> I32;
|
||||
);
|
||||
|
||||
for (name, module) in modules.iter() {
|
||||
if name == EXTISM_ENV_MODULE {
|
||||
continue;
|
||||
}
|
||||
|
||||
for import in module.imports() {
|
||||
if import.module() == EXTISM_ENV_MODULE
|
||||
&& modules[EXTISM_ENV_MODULE]
|
||||
.get_export(import.name())
|
||||
.is_none()
|
||||
&& linker
|
||||
.get(&mut store, EXTISM_ENV_MODULE, import.name())
|
||||
.is_none()
|
||||
{
|
||||
let (kind, ty) = match import.ty() {
|
||||
ExternType::Func(t) => ("function", t.to_string()),
|
||||
ExternType::Global(t) => ("global", t.content().to_string()),
|
||||
ExternType::Table(t) => ("table", t.element().to_string()),
|
||||
ExternType::Memory(_) => ("memory", String::new()),
|
||||
};
|
||||
anyhow::bail!(
|
||||
"Invalid {kind} import from extism:host/env: {} {ty}\n\n\
|
||||
Note: This may indicate that the PDK that was used to build this plugin has additional features that aren't \
|
||||
available in this version of the SDK, try updating the SDK to the latest version.",
|
||||
import.name(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut linked = BTreeSet::new();
|
||||
linker.module(&mut store, EXTISM_ENV_MODULE, &modules[EXTISM_ENV_MODULE])?;
|
||||
linked.insert(EXTISM_ENV_MODULE.to_string());
|
||||
@@ -310,79 +401,45 @@ impl Plugin {
|
||||
imports: impl IntoIterator<Item = Function>,
|
||||
with_wasi: bool,
|
||||
) -> Result<Plugin, Error> {
|
||||
Self::build_new(
|
||||
Self::new_from_compiled(&CompiledPlugin::new(
|
||||
PluginBuilder::new(wasm)
|
||||
.with_functions(imports)
|
||||
.with_wasi(with_wasi),
|
||||
)
|
||||
)?)
|
||||
}
|
||||
|
||||
pub(crate) fn build_new(builder: PluginBuilder) -> Result<Plugin, Error> {
|
||||
// Setup wasmtime types
|
||||
let mut config = builder.config.unwrap_or_default();
|
||||
config
|
||||
.async_support(false)
|
||||
.epoch_interruption(true)
|
||||
.debug_info(builder.debug_options.debug_info)
|
||||
.coredump_on_trap(builder.debug_options.coredump.is_some())
|
||||
.profiler(builder.debug_options.profiling_strategy)
|
||||
.wasm_tail_call(true)
|
||||
.wasm_function_references(true)
|
||||
.wasm_gc(true);
|
||||
|
||||
if builder.fuel.is_some() {
|
||||
config.consume_fuel(true);
|
||||
}
|
||||
|
||||
match builder.cache_config {
|
||||
Some(None) => (),
|
||||
Some(Some(path)) => {
|
||||
config.cache_config_load(path)?;
|
||||
}
|
||||
None => {
|
||||
if let Ok(env) = std::env::var("EXTISM_CACHE_CONFIG") {
|
||||
if !env.is_empty() {
|
||||
config.cache_config_load(&env)?;
|
||||
}
|
||||
} else {
|
||||
config.cache_config_load_default()?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let engine = Engine::new(&config)?;
|
||||
let (manifest, modules) = manifest::load(&engine, builder.source)?;
|
||||
if modules.len() <= 1 {
|
||||
anyhow::bail!("No wasm modules provided");
|
||||
} else if !modules.contains_key(MAIN_KEY) {
|
||||
anyhow::bail!("No main module provided");
|
||||
}
|
||||
|
||||
let available_pages = manifest.memory.max_pages;
|
||||
/// Create a new plugin from a pre-compiled plugin
|
||||
pub fn new_from_compiled(compiled: &CompiledPlugin) -> Result<Plugin, Error> {
|
||||
let available_pages = compiled.manifest.memory.max_pages;
|
||||
debug!("Available pages: {available_pages:?}");
|
||||
|
||||
let id = uuid::Uuid::new_v4();
|
||||
let mut store = Store::new(
|
||||
&engine,
|
||||
&compiled.engine,
|
||||
CurrentPlugin::new(
|
||||
manifest,
|
||||
builder.wasi,
|
||||
compiled.manifest.clone(),
|
||||
compiled.options.wasi,
|
||||
available_pages,
|
||||
builder.http_response_headers,
|
||||
compiled.options.http_response_headers,
|
||||
id,
|
||||
)?,
|
||||
);
|
||||
store.set_epoch_deadline(1);
|
||||
if let Some(fuel) = builder.fuel {
|
||||
if let Some(fuel) = compiled.options.fuel {
|
||||
store.set_fuel(fuel)?;
|
||||
}
|
||||
|
||||
let imports: Vec<Function> = builder.functions.into_iter().collect();
|
||||
let (instance_pre, linker, host_context) =
|
||||
relink(&engine, &mut store, &imports, &modules, builder.wasi)?;
|
||||
let imports: Vec<Function> = compiled.options.functions.to_vec();
|
||||
let (instance_pre, linker, host_context) = relink(
|
||||
&compiled.engine,
|
||||
&mut store,
|
||||
&imports,
|
||||
&compiled.modules,
|
||||
compiled.options.wasi,
|
||||
)?;
|
||||
let timer_tx = Timer::tx();
|
||||
let mut plugin = Plugin {
|
||||
modules,
|
||||
modules: compiled.modules.clone(),
|
||||
linker,
|
||||
instance: std::sync::Arc::new(std::sync::Mutex::new(None)),
|
||||
instance_pre,
|
||||
@@ -394,10 +451,10 @@ impl Plugin {
|
||||
instantiations: 0,
|
||||
output: Output::default(),
|
||||
store_needs_reset: false,
|
||||
debug_options: builder.debug_options,
|
||||
debug_options: compiled.options.debug_options.clone(),
|
||||
_functions: imports,
|
||||
error_msg: None,
|
||||
fuel: builder.fuel,
|
||||
fuel: compiled.options.fuel,
|
||||
host_context,
|
||||
};
|
||||
|
||||
@@ -503,7 +560,7 @@ impl Plugin {
|
||||
}
|
||||
|
||||
/// Returns `true` if the given function exists, otherwise `false`
|
||||
pub fn function_exists(&mut self, function: impl AsRef<str>) -> bool {
|
||||
pub fn function_exists(&self, function: impl AsRef<str>) -> bool {
|
||||
self.modules[MAIN_KEY]
|
||||
.get_export(function.as_ref())
|
||||
.map(|x| {
|
||||
@@ -879,8 +936,7 @@ impl Plugin {
|
||||
}
|
||||
|
||||
// on extism error
|
||||
if output_res.is_ok() && self.output.error_offset != 0 && self.output.error_length != 0
|
||||
{
|
||||
if output_res.is_ok() && self.extism_error_is_set() {
|
||||
let handle = MemoryHandle {
|
||||
offset: self.output.error_offset,
|
||||
length: self.output.error_length,
|
||||
@@ -900,7 +956,7 @@ impl Plugin {
|
||||
}
|
||||
Err(msg) => {
|
||||
res = Err(Error::msg(format!(
|
||||
"Call to Extism plugin function {name} encountered an error: {}",
|
||||
"unable to load error message from memory: {}",
|
||||
msg,
|
||||
)));
|
||||
}
|
||||
@@ -967,11 +1023,12 @@ impl Plugin {
|
||||
plugin = self.id.to_string(),
|
||||
"WASI exit code: {}", exit_code
|
||||
);
|
||||
if exit_code == 0 {
|
||||
|
||||
if exit_code == 0 && !self.extism_error_is_set() {
|
||||
return Ok(0);
|
||||
}
|
||||
|
||||
return Err((e.context("WASI exit code"), exit_code));
|
||||
return Err((e, exit_code));
|
||||
}
|
||||
|
||||
// Handle timeout interrupts
|
||||
@@ -999,6 +1056,10 @@ impl Plugin {
|
||||
}
|
||||
}
|
||||
|
||||
fn extism_error_is_set(&self) -> bool {
|
||||
self.output.error_offset != 0 && self.output.error_length != 0
|
||||
}
|
||||
|
||||
/// Call a function by name with the given input, the return value is
|
||||
/// the output data returned by the plugin. The return type can be anything that implements
|
||||
/// [FromBytes]. This data will be invalidated next time the plugin is called.
|
||||
@@ -1094,6 +1155,25 @@ impl Plugin {
|
||||
anyhow::bail!("Plugin::clear_error failed, extism:host/env::error_set not found")
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the amount of fuel consumed by the plugin.
|
||||
///
|
||||
/// This function calculates the difference between the initial fuel and the remaining fuel.
|
||||
/// If either the initial fuel or the remaining fuel is not set, it returns `None`.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Some(u64)` - The amount of fuel consumed.
|
||||
/// * `None` - If the initial fuel or remaining fuel is not set.
|
||||
pub fn fuel_consumed(&self) -> Option<u64> {
|
||||
self.fuel.map(|x| {
|
||||
x.saturating_sub(
|
||||
self.store
|
||||
.get_fuel()
|
||||
.expect("fuel support should be enabled to use fuel"),
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// Enumerates the PDK languages that need some additional initialization
|
||||
@@ -1137,7 +1217,7 @@ macro_rules! typed_plugin {
|
||||
|
||||
impl TryFrom<$crate::Plugin> for $name {
|
||||
type Error = $crate::Error;
|
||||
fn try_from(mut x: $crate::Plugin) -> Result<Self, Self::Error> {
|
||||
fn try_from(x: $crate::Plugin) -> Result<Self, Self::Error> {
|
||||
$(
|
||||
if !x.function_exists(stringify!($f)) {
|
||||
return Err($crate::Error::msg(format!("Invalid function: {}", stringify!($f))));
|
||||
|
||||
@@ -35,12 +35,17 @@ impl Default for DebugOptions {
|
||||
/// PluginBuilder is used to configure and create `Plugin` instances
|
||||
pub struct PluginBuilder<'a> {
|
||||
pub(crate) source: WasmInput<'a>,
|
||||
pub(crate) config: Option<wasmtime::Config>,
|
||||
pub(crate) options: PluginBuilderOptions,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct PluginBuilderOptions {
|
||||
pub(crate) wasi: bool,
|
||||
pub(crate) functions: Vec<Function>,
|
||||
pub(crate) debug_options: DebugOptions,
|
||||
pub(crate) cache_config: Option<Option<PathBuf>>,
|
||||
pub(crate) fuel: Option<u64>,
|
||||
pub(crate) config: Option<wasmtime::Config>,
|
||||
pub(crate) http_response_headers: bool,
|
||||
}
|
||||
|
||||
@@ -49,19 +54,21 @@ impl<'a> PluginBuilder<'a> {
|
||||
pub fn new(plugin: impl Into<WasmInput<'a>>) -> Self {
|
||||
PluginBuilder {
|
||||
source: plugin.into(),
|
||||
wasi: false,
|
||||
functions: vec![],
|
||||
debug_options: DebugOptions::default(),
|
||||
cache_config: None,
|
||||
fuel: None,
|
||||
config: None,
|
||||
http_response_headers: false,
|
||||
options: PluginBuilderOptions {
|
||||
wasi: false,
|
||||
functions: vec![],
|
||||
debug_options: DebugOptions::default(),
|
||||
cache_config: None,
|
||||
fuel: None,
|
||||
http_response_headers: false,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Enables WASI if the argument is set to `true`
|
||||
pub fn with_wasi(mut self, wasi: bool) -> Self {
|
||||
self.wasi = wasi;
|
||||
self.options.wasi = wasi;
|
||||
self
|
||||
}
|
||||
|
||||
@@ -80,7 +87,8 @@ impl<'a> PluginBuilder<'a> {
|
||||
+ Sync
|
||||
+ Send,
|
||||
{
|
||||
self.functions
|
||||
self.options
|
||||
.functions
|
||||
.push(Function::new(name, args, returns, user_data, f));
|
||||
self
|
||||
}
|
||||
@@ -101,62 +109,63 @@ impl<'a> PluginBuilder<'a> {
|
||||
+ Sync
|
||||
+ Send,
|
||||
{
|
||||
self.functions
|
||||
self.options
|
||||
.functions
|
||||
.push(Function::new(name, args, returns, user_data, f).with_namespace(namespace));
|
||||
self
|
||||
}
|
||||
|
||||
/// Add multiple host functions
|
||||
pub fn with_functions(mut self, f: impl IntoIterator<Item = Function>) -> Self {
|
||||
self.functions.extend(f);
|
||||
self.options.functions.extend(f);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set profiling strategy
|
||||
pub fn with_profiling_strategy(mut self, p: wasmtime::ProfilingStrategy) -> Self {
|
||||
self.debug_options.profiling_strategy = p;
|
||||
self.options.debug_options.profiling_strategy = p;
|
||||
self
|
||||
}
|
||||
|
||||
/// Enable Wasmtime coredump on trap
|
||||
pub fn with_coredump(mut self, path: impl Into<std::path::PathBuf>) -> Self {
|
||||
self.debug_options.coredump = Some(path.into());
|
||||
self.options.debug_options.coredump = Some(path.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Enable Extism memory dump when plugin calls return an error
|
||||
pub fn with_memdump(mut self, path: impl Into<std::path::PathBuf>) -> Self {
|
||||
self.debug_options.memdump = Some(path.into());
|
||||
self.options.debug_options.memdump = Some(path.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Compile with debug info
|
||||
pub fn with_debug_info(mut self) -> Self {
|
||||
self.debug_options.debug_info = true;
|
||||
self.options.debug_options.debug_info = true;
|
||||
self
|
||||
}
|
||||
|
||||
/// Configure debug options
|
||||
pub fn with_debug_options(mut self, options: DebugOptions) -> Self {
|
||||
self.debug_options = options;
|
||||
self.options.debug_options = options;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set wasmtime compilation cache config path
|
||||
pub fn with_cache_config(mut self, dir: impl Into<PathBuf>) -> Self {
|
||||
self.cache_config = Some(Some(dir.into()));
|
||||
self.options.cache_config = Some(Some(dir.into()));
|
||||
self
|
||||
}
|
||||
|
||||
/// Turn wasmtime compilation caching off
|
||||
pub fn with_cache_disabled(mut self) -> Self {
|
||||
self.cache_config = Some(None);
|
||||
self.options.cache_config = Some(None);
|
||||
self
|
||||
}
|
||||
|
||||
/// Limit the number of instructions that can be executed
|
||||
pub fn with_fuel_limit(mut self, fuel: u64) -> Self {
|
||||
self.fuel = Some(fuel);
|
||||
self.options.fuel = Some(fuel);
|
||||
self
|
||||
}
|
||||
|
||||
@@ -180,12 +189,17 @@ impl<'a> PluginBuilder<'a> {
|
||||
|
||||
/// Enables `http_response_headers`, which allows for plugins to access response headers when using `extism:host/env::http_request`
|
||||
pub fn with_http_response_headers(mut self, allow: bool) -> Self {
|
||||
self.http_response_headers = allow;
|
||||
self.options.http_response_headers = allow;
|
||||
self
|
||||
}
|
||||
|
||||
/// Generate a new plugin with the configured settings
|
||||
pub fn build(self) -> Result<Plugin, Error> {
|
||||
Plugin::build_new(self)
|
||||
Plugin::new_from_compiled(&CompiledPlugin::new(self)?)
|
||||
}
|
||||
|
||||
/// Build new `CompiledPlugin`
|
||||
pub fn compile(self) -> Result<CompiledPlugin, Error> {
|
||||
CompiledPlugin::new(self)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,32 +48,32 @@ pub type ExtismFunctionType = extern "C" fn(
|
||||
pub type ExtismLogDrainFunctionType = extern "C" fn(data: *const std::ffi::c_char, size: Size);
|
||||
|
||||
impl ExtismVal {
|
||||
fn from_val(value: &wasmtime::Val, ctx: impl AsContext) -> Self {
|
||||
match value.ty(ctx) {
|
||||
wasmtime::ValType::I32 => ExtismVal {
|
||||
fn from_val(value: &wasmtime::Val, ctx: impl AsContext) -> Result<Self, Error> {
|
||||
match value.ty(ctx)? {
|
||||
wasmtime::ValType::I32 => Ok(ExtismVal {
|
||||
t: ValType::I32,
|
||||
v: ValUnion {
|
||||
i32: value.unwrap_i32(),
|
||||
},
|
||||
},
|
||||
wasmtime::ValType::I64 => ExtismVal {
|
||||
}),
|
||||
wasmtime::ValType::I64 => Ok(ExtismVal {
|
||||
t: ValType::I64,
|
||||
v: ValUnion {
|
||||
i64: value.unwrap_i64(),
|
||||
},
|
||||
},
|
||||
wasmtime::ValType::F32 => ExtismVal {
|
||||
}),
|
||||
wasmtime::ValType::F32 => Ok(ExtismVal {
|
||||
t: ValType::F32,
|
||||
v: ValUnion {
|
||||
f32: value.unwrap_f32(),
|
||||
},
|
||||
},
|
||||
wasmtime::ValType::F64 => ExtismVal {
|
||||
}),
|
||||
wasmtime::ValType::F64 => Ok(ExtismVal {
|
||||
t: ValType::F64,
|
||||
v: ValUnion {
|
||||
f64: value.unwrap_f64(),
|
||||
},
|
||||
},
|
||||
}),
|
||||
t => todo!("{}", t),
|
||||
}
|
||||
}
|
||||
@@ -227,7 +227,7 @@ pub unsafe extern "C" fn extism_function_new(
|
||||
let store = &*plugin.store;
|
||||
let inputs: Vec<_> = inputs
|
||||
.iter()
|
||||
.map(|x| ExtismVal::from_val(x, store))
|
||||
.map(|x| ExtismVal::from_val(x, store).unwrap())
|
||||
.collect();
|
||||
let mut output_tmp: Vec<_> = output_types
|
||||
.iter()
|
||||
@@ -302,6 +302,79 @@ pub unsafe extern "C" fn extism_function_set_namespace(
|
||||
}
|
||||
}
|
||||
|
||||
/// Pre-compile an Extism plugin
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn extism_compiled_plugin_new(
|
||||
wasm: *const u8,
|
||||
wasm_size: Size,
|
||||
functions: *mut *const ExtismFunction,
|
||||
n_functions: Size,
|
||||
with_wasi: bool,
|
||||
errmsg: *mut *mut std::ffi::c_char,
|
||||
) -> *mut CompiledPlugin {
|
||||
trace!("Call to extism_plugin_new with wasm pointer {:?}", wasm);
|
||||
let data = std::slice::from_raw_parts(wasm, wasm_size as usize);
|
||||
|
||||
let mut builder = PluginBuilder::new(data).with_wasi(with_wasi);
|
||||
|
||||
if !functions.is_null() {
|
||||
let funcs = (0..n_functions)
|
||||
.map(|i| unsafe { *functions.add(i as usize) })
|
||||
.map(|ptr| {
|
||||
if ptr.is_null() {
|
||||
return Err("Cannot pass null pointer");
|
||||
}
|
||||
|
||||
let ExtismFunction(func) = &*ptr;
|
||||
let Some(func) = func.take() else {
|
||||
return Err("Function cannot be registered with multiple different Plugins");
|
||||
};
|
||||
|
||||
Ok(func)
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
.unwrap_or_else(|e| {
|
||||
if !errmsg.is_null() {
|
||||
let e = std::ffi::CString::new(e.to_string()).unwrap();
|
||||
*errmsg = e.into_raw();
|
||||
}
|
||||
Vec::new()
|
||||
});
|
||||
|
||||
if funcs.len() != n_functions as usize {
|
||||
return std::ptr::null_mut();
|
||||
}
|
||||
|
||||
builder = builder.with_functions(funcs);
|
||||
}
|
||||
|
||||
CompiledPlugin::new(builder)
|
||||
.map(|v| Box::into_raw(Box::new(v)))
|
||||
.unwrap_or_else(|e| {
|
||||
if !errmsg.is_null() {
|
||||
let e = std::ffi::CString::new(format!(
|
||||
"Unable to compile Extism plugin: {}",
|
||||
e.root_cause(),
|
||||
))
|
||||
.unwrap();
|
||||
*errmsg = e.into_raw();
|
||||
}
|
||||
std::ptr::null_mut()
|
||||
})
|
||||
}
|
||||
|
||||
/// Free `ExtismCompiledPlugin`
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn extism_compiled_plugin_free(plugin: *mut CompiledPlugin) {
|
||||
if plugin.is_null() {
|
||||
return;
|
||||
}
|
||||
|
||||
let plugin = Box::from_raw(plugin);
|
||||
trace!("called extism_compiled_plugin_free");
|
||||
drop(plugin)
|
||||
}
|
||||
|
||||
/// Create a new plugin with host functions, the functions passed to this function no longer need to be manually freed using
|
||||
///
|
||||
/// `wasm`: is a WASM module (wat or wasm) or a JSON encoded manifest
|
||||
@@ -318,36 +391,70 @@ pub unsafe extern "C" fn extism_plugin_new(
|
||||
with_wasi: bool,
|
||||
errmsg: *mut *mut std::ffi::c_char,
|
||||
) -> *mut Plugin {
|
||||
trace!("Call to extism_plugin_new with wasm pointer {:?}", wasm);
|
||||
let data = std::slice::from_raw_parts(wasm, wasm_size as usize);
|
||||
let mut funcs = vec![];
|
||||
|
||||
if !functions.is_null() {
|
||||
for i in 0..n_functions {
|
||||
unsafe {
|
||||
let f = *functions.add(i as usize);
|
||||
if f.is_null() {
|
||||
continue;
|
||||
let funcs = if functions.is_null() {
|
||||
vec![]
|
||||
} else {
|
||||
let funcs = (0..n_functions)
|
||||
.map(|i| unsafe { *functions.add(i as usize) })
|
||||
.map(|ptr| {
|
||||
if ptr.is_null() {
|
||||
return Err("Cannot pass null pointer");
|
||||
}
|
||||
if let Some(f) = (*f).0.take() {
|
||||
funcs.push(f);
|
||||
} else {
|
||||
let e = std::ffi::CString::new(
|
||||
"Function cannot be registered with multiple different Plugins",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let ExtismFunction(func) = &*ptr;
|
||||
let Some(func) = func.take() else {
|
||||
return Err("Function cannot be registered with multiple different Plugins");
|
||||
};
|
||||
|
||||
Ok(func)
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
.unwrap_or_else(|e| {
|
||||
if !errmsg.is_null() {
|
||||
let e = std::ffi::CString::new(e.to_string()).unwrap();
|
||||
*errmsg = e.into_raw();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Vec::new()
|
||||
});
|
||||
|
||||
let plugin = Plugin::new(data, funcs, with_wasi);
|
||||
if funcs.len() != n_functions as usize {
|
||||
return std::ptr::null_mut();
|
||||
}
|
||||
|
||||
funcs
|
||||
};
|
||||
|
||||
Plugin::new(data, funcs, with_wasi)
|
||||
.map(|v| Box::into_raw(Box::new(v)))
|
||||
.unwrap_or_else(|e| {
|
||||
if !errmsg.is_null() {
|
||||
let e = std::ffi::CString::new(format!(
|
||||
"Unable to compile Extism plugin: {}",
|
||||
e.root_cause(),
|
||||
))
|
||||
.unwrap();
|
||||
*errmsg = e.into_raw();
|
||||
}
|
||||
std::ptr::null_mut()
|
||||
})
|
||||
}
|
||||
|
||||
/// Create a new plugin from an `ExtismCompiledPlugin`
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn extism_plugin_new_from_compiled(
|
||||
compiled: *const CompiledPlugin,
|
||||
errmsg: *mut *mut std::ffi::c_char,
|
||||
) -> *mut Plugin {
|
||||
let plugin = Plugin::new_from_compiled(&*compiled);
|
||||
match plugin {
|
||||
Err(e) => {
|
||||
if !errmsg.is_null() {
|
||||
let e = std::ffi::CString::new(format!("Unable to create Extism plugin: {}", e))
|
||||
.unwrap();
|
||||
let e = std::ffi::CString::new(format!(
|
||||
"Unable to create Extism plugin: {}",
|
||||
e.root_cause(),
|
||||
))
|
||||
.unwrap();
|
||||
*errmsg = e.into_raw();
|
||||
}
|
||||
std::ptr::null_mut()
|
||||
@@ -372,40 +479,69 @@ pub unsafe extern "C" fn extism_plugin_new_with_fuel_limit(
|
||||
wasm
|
||||
);
|
||||
let data = std::slice::from_raw_parts(wasm, wasm_size as usize);
|
||||
let mut funcs = vec![];
|
||||
|
||||
if !functions.is_null() {
|
||||
for i in 0..n_functions {
|
||||
unsafe {
|
||||
let f = *functions.add(i as usize);
|
||||
if f.is_null() {
|
||||
continue;
|
||||
let funcs = if functions.is_null() {
|
||||
vec![]
|
||||
} else {
|
||||
let funcs = (0..n_functions)
|
||||
.map(|i| unsafe { *functions.add(i as usize) })
|
||||
.map(|ptr| {
|
||||
if ptr.is_null() {
|
||||
return Err("Cannot pass null pointer");
|
||||
}
|
||||
if let Some(f) = (*f).0.take() {
|
||||
funcs.push(f);
|
||||
} else {
|
||||
let e = std::ffi::CString::new(
|
||||
"Function cannot be registered with multiple different Plugins",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let ExtismFunction(func) = &*ptr;
|
||||
let Some(func) = func.take() else {
|
||||
return Err("Function cannot be registered with multiple different Plugins");
|
||||
};
|
||||
|
||||
Ok(func)
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
.unwrap_or_else(|e| {
|
||||
if !errmsg.is_null() {
|
||||
let e = std::ffi::CString::new(e.to_string()).unwrap();
|
||||
*errmsg = e.into_raw();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Vec::new()
|
||||
});
|
||||
|
||||
let plugin = Plugin::build_new(
|
||||
if funcs.len() != n_functions as usize {
|
||||
return std::ptr::null_mut();
|
||||
}
|
||||
|
||||
funcs
|
||||
};
|
||||
|
||||
let compiled = match CompiledPlugin::new(
|
||||
PluginBuilder::new(data)
|
||||
.with_functions(funcs)
|
||||
.with_wasi(with_wasi)
|
||||
.with_fuel_limit(fuel_limit),
|
||||
);
|
||||
) {
|
||||
Ok(x) => x,
|
||||
Err(e) => {
|
||||
if !errmsg.is_null() {
|
||||
let e = std::ffi::CString::new(format!(
|
||||
"Unable to compile Extism plugin: {}",
|
||||
e.root_cause(),
|
||||
))
|
||||
.unwrap();
|
||||
*errmsg = e.into_raw();
|
||||
}
|
||||
return std::ptr::null_mut();
|
||||
}
|
||||
};
|
||||
|
||||
let plugin = Plugin::new_from_compiled(&compiled);
|
||||
|
||||
match plugin {
|
||||
Err(e) => {
|
||||
if !errmsg.is_null() {
|
||||
let e = std::ffi::CString::new(format!("Unable to create Extism plugin: {}", e))
|
||||
.unwrap();
|
||||
let e = std::ffi::CString::new(format!(
|
||||
"Unable to create Extism plugin: {}",
|
||||
e.root_cause(),
|
||||
))
|
||||
.unwrap();
|
||||
*errmsg = e.into_raw();
|
||||
}
|
||||
std::ptr::null_mut()
|
||||
@@ -430,7 +566,7 @@ pub unsafe extern "C" fn extism_plugin_new_error_free(err: *mut std::ffi::c_char
|
||||
drop(std::ffi::CString::from_raw(err))
|
||||
}
|
||||
|
||||
/// Remove a plugin from the registry and free associated memory
|
||||
/// Free `ExtismPlugin`
|
||||
#[no_mangle]
|
||||
pub unsafe extern "C" fn extism_plugin_free(plugin: *mut Plugin) {
|
||||
if plugin.is_null() {
|
||||
@@ -763,7 +899,7 @@ fn set_log_file(log_file: impl Into<std::path::PathBuf>, filter: &str) -> Result
|
||||
Ok(())
|
||||
}
|
||||
|
||||
static mut LOG_BUFFER: Option<LogBuffer> = None;
|
||||
static LOG_BUFFER: std::sync::Mutex<Option<LogBuffer>> = std::sync::Mutex::new(None);
|
||||
|
||||
/// Enable a custom log handler, this will buffer logs until `extism_log_drain` is called
|
||||
/// Log level should be one of: info, error, trace, debug, warn
|
||||
@@ -795,8 +931,8 @@ unsafe fn set_log_buffer(filter: &str) -> Result<(), Error> {
|
||||
x.parse_lossy(filter)
|
||||
}
|
||||
});
|
||||
LOG_BUFFER = Some(LogBuffer::default());
|
||||
let buf = LOG_BUFFER.clone().unwrap();
|
||||
*LOG_BUFFER.lock().unwrap() = Some(LogBuffer::default());
|
||||
let buf = LOG_BUFFER.lock().unwrap().clone().unwrap();
|
||||
cfg.with_ansi(false)
|
||||
.with_writer(move || buf.clone())
|
||||
.try_init()
|
||||
@@ -808,7 +944,7 @@ unsafe fn set_log_buffer(filter: &str) -> Result<(), Error> {
|
||||
/// Calls the provided callback function for each buffered log line.
|
||||
/// This is only needed when `extism_log_custom` is used.
|
||||
pub unsafe extern "C" fn extism_log_drain(handler: ExtismLogDrainFunctionType) {
|
||||
if let Some(buf) = LOG_BUFFER.as_mut() {
|
||||
if let Some(buf) = LOG_BUFFER.lock().unwrap().as_mut() {
|
||||
if let Ok(mut buf) = buf.buffer.lock() {
|
||||
for (line, len) in buf.drain(..) {
|
||||
handler(line.as_ptr(), len as u64);
|
||||
|
||||
@@ -258,6 +258,23 @@ fn test_fuel() {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fuel_consumption() {
|
||||
let manifest = Manifest::new([extism_manifest::Wasm::data(WASM_LOOP)]);
|
||||
let mut plugin = PluginBuilder::new(manifest)
|
||||
.with_wasi(true)
|
||||
.with_fuel_limit(10000)
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
let output: Result<&[u8], Error> = plugin.call("loop_forever", "abc123");
|
||||
assert!(output.is_err());
|
||||
|
||||
let fuel_consumed = plugin.fuel_consumed().unwrap();
|
||||
println!("Fuel consumed: {}", fuel_consumed);
|
||||
assert!(fuel_consumed > 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(feature = "http")]
|
||||
fn test_http_timeout() {
|
||||
@@ -447,7 +464,7 @@ fn hello_world_set_error(
|
||||
_user_data: UserData<()>,
|
||||
) -> Result<(), Error> {
|
||||
plugin.set_error("TEST")?;
|
||||
outputs[0] = inputs[0].clone();
|
||||
outputs[0] = inputs[0];
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -544,7 +561,7 @@ fn hello_world_user_data(
|
||||
let mut data = data.lock().unwrap();
|
||||
let s = _plugin.memory_get_val(&inputs[0])?;
|
||||
data.write_all(s)?;
|
||||
outputs[0] = inputs[0].clone();
|
||||
outputs[0] = inputs[0];
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -22,18 +22,18 @@ pub(crate) struct Timer {
|
||||
|
||||
#[cfg(not(target_family = "windows"))]
|
||||
extern "C" fn cleanup_timer() {
|
||||
let mut timer = match unsafe { TIMER.lock() } {
|
||||
let mut timer = match TIMER.lock() {
|
||||
Ok(x) => x,
|
||||
Err(e) => e.into_inner(),
|
||||
};
|
||||
drop(timer.take());
|
||||
}
|
||||
|
||||
static mut TIMER: std::sync::Mutex<Option<Timer>> = std::sync::Mutex::new(None);
|
||||
static TIMER: std::sync::Mutex<Option<Timer>> = std::sync::Mutex::new(None);
|
||||
|
||||
impl Timer {
|
||||
pub(crate) fn tx() -> std::sync::mpsc::Sender<TimerAction> {
|
||||
let mut timer = match unsafe { TIMER.lock() } {
|
||||
let mut timer = match TIMER.lock() {
|
||||
Ok(x) => x,
|
||||
Err(e) => e.into_inner(),
|
||||
};
|
||||
@@ -92,25 +92,39 @@ impl Timer {
|
||||
loop {
|
||||
if plugins.is_empty() {
|
||||
if let Ok(x) = rx.recv() {
|
||||
handle!(x)
|
||||
handle!(x);
|
||||
}
|
||||
}
|
||||
|
||||
plugins = plugins
|
||||
.into_iter()
|
||||
.filter(|(_k, (engine, end))| {
|
||||
if let Some(end) = end {
|
||||
let now = std::time::Instant::now();
|
||||
if end <= &now {
|
||||
engine.increment_epoch();
|
||||
return false;
|
||||
let mut timeout: Option<std::time::Duration> = None;
|
||||
|
||||
plugins.retain(|_k, (engine, end)| {
|
||||
if let Some(end) = end {
|
||||
let now = std::time::Instant::now();
|
||||
if *end <= now {
|
||||
engine.increment_epoch();
|
||||
return false;
|
||||
} else {
|
||||
let time_left =
|
||||
(*end - now).saturating_sub(std::time::Duration::from_millis(1));
|
||||
if let Some(t) = &timeout {
|
||||
if time_left < *t {
|
||||
timeout = Some(time_left);
|
||||
}
|
||||
} else {
|
||||
timeout = Some(time_left);
|
||||
}
|
||||
}
|
||||
true
|
||||
})
|
||||
.collect();
|
||||
}
|
||||
|
||||
for x in rx.try_iter() {
|
||||
true
|
||||
});
|
||||
|
||||
if let Some(timeout) = timeout {
|
||||
if let Ok(x) = rx.recv_timeout(timeout) {
|
||||
handle!(x)
|
||||
}
|
||||
} else if let Ok(x) = rx.recv() {
|
||||
handle!(x)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user