Compare commits

...

3 Commits

Author SHA1 Message Date
Timothée Lecomte
7230657f6f feat: expose building a CompiledPlugin with a fuel limit (#883)
Expose a new method `extism_compiled_plugin_new_with_fuel_limit` to
build a CompiledPlugin with a fuel limit.

Fixes #882
2025-11-25 09:11:25 -08:00
Brian G. Merrell
de81040c99 Update to wasmtime version with exceptions support (#880)
The [wasmtime v37
release](https://github.com/bytecodealliance/wasmtime/releases/tag/v37.0.0)
"now fully implements the WebAssembly exception-handling proposal."
Support for exception-handling is disabled by default, but will be made
default in the future (according to the release notes).

To use this new wasmtime feature, we do two things at a high level:

1. Update wasmtime dependency to version 37
2. Add a "wasmtime-exceptions" Extism feature, disabled by default.

Updating to the new wasmtime dependency required some semantical changes
to how the cache is configured.

There is also a new `ExternType` to match on when handling invalid
imports.

My use case for this feature is being able to execute Lua code from
Extism. In actuality, I'm running a Rust plugin, which is using
[mlua](https://github.com/mlua-rs/mlua) to execute the Lua. I have it
working locally with this change. Fwiw, mlua recently added [support to
compile to wasi](https://github.com/mlua-rs/mlua/issues/366), which
should make this all work end-to-end with no special patches if we can
get this merged.

This is my first time working on the Extism codebase, so please let me
know if I missed anything.
2025-10-09 15:46:23 -07:00
dependabot[bot]
b79e74d516 chore(deps): Update criterion requirement from 0.6.0 to 0.7.0 (#878)
Updates the requirements on
[criterion](https://github.com/bheisler/criterion.rs) to permit the
latest version.
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/bheisler/criterion.rs/blob/master/CHANGELOG.md">criterion's
changelog</a>.</em></p>
<blockquote>
<h2>[0.7.0] - 2025-07-25</h2>
<ul>
<li>Bump version of criterion-plot to align dependencies.</li>
</ul>
<h2>[0.6.0] - 2025-05-17</h2>
<h3>Changed</h3>
<ul>
<li>MSRV bumped to 1.80</li>
<li>The <code>real_blackbox</code> feature no longer has any impact.
Criterion always uses <code>std::hint::black_box()</code> now.
Users of <code>criterion::black_box()</code> should switch to
<code>std::hint::black_box()</code>.</li>
<li><code>clap</code> dependency unpinned.</li>
</ul>
<h3>Fixed</h3>
<ul>
<li>gnuplot version is now correctly detected when using certain Windows
binaries/configurations that used to fail</li>
</ul>
<h3>Added</h3>
<ul>
<li>Async benchmarking with Tokio may be done via a
<code>tokio::runtime::Handle</code>, not only a
<code>tokio::runtime::Runtime</code></li>
</ul>
<h2>[0.5.1] - 2023-05-26</h2>
<h3>Fixed</h3>
<ul>
<li>Quick mode (--quick) no longer crashes with measured times over 5
seconds when --noplot is not active</li>
</ul>
<h2>[0.5.0] - 2023-05-23</h2>
<h3>Changed</h3>
<ul>
<li>Replaced lazy_static dependency with once_cell</li>
<li>Improved documentation of the <code>html_reports</code> feature</li>
<li>Replaced atty dependency with is-terminal</li>
<li>MSRV bumped to 1.64</li>
<li>Upgraded clap dependency to v4</li>
<li>Upgraded tempfile dependency to v3.5.0</li>
</ul>
<h3>Fixed</h3>
<ul>
<li>Quick mode (<code>--quick</code>) no longer outputs 1ms for measured
times over 5 seconds</li>
<li>Documentation updates</li>
</ul>
<h2>[0.4.0] - 2022-09-10</h2>
<h3>Removed</h3>
<ul>
<li>The <code>Criterion::can_plot</code> function has been removed.</li>
<li>The <code>Criterion::bench_function_over_inputs</code> function has
been removed.</li>
<li>The <code>Criterion::bench_functions</code> function has been
removed.</li>
<li>The <code>Criterion::bench</code> function has been removed.</li>
</ul>
<h3>Changed</h3>
<ul>
<li>HTML report hidden behind non-default feature flag:
'html_reports'</li>
<li>Standalone support (ie without cargo-criterion) feature flag:
'cargo_bench_support'</li>
<li>MSRV bumped to 1.57</li>
</ul>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="567405d253"><code>567405d</code></a>
release: bump criterion and criterion-plot versions (<a
href="https://redirect.github.com/bheisler/criterion.rs/issues/878">#878</a>)</li>
<li><a
href="ccccbcc152"><code>ccccbcc</code></a>
fix: deal with throughput in bits (<a
href="https://redirect.github.com/bheisler/criterion.rs/issues/861">#861</a>)</li>
<li><a
href="deb0eb021d"><code>deb0eb0</code></a>
feat: support throughput reports in bits (<a
href="https://redirect.github.com/bheisler/criterion.rs/issues/833">#833</a>)</li>
<li><a
href="d4fd7cc478"><code>d4fd7cc</code></a>
Add CI job checking library builds with oldest allowed dependencies (<a
href="https://redirect.github.com/bheisler/criterion.rs/issues/854">#854</a>)</li>
<li>See full diff in <a
href="https://github.com/bheisler/criterion.rs/compare/0.6.0...0.7.0">compare
view</a></li>
</ul>
</details>
<br />


Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)


</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-28 09:07:54 -07:00
5 changed files with 129 additions and 23 deletions

View File

@@ -9,7 +9,7 @@ repository.workspace = true
version.workspace = true
[dependencies]
wasmtime = { version = ">= 27.0.0, < 31.0.0", default-features = false, features = [
wasmtime = { version="37", default-features = false, features = [
'cache',
'gc',
'gc-drc',
@@ -20,8 +20,8 @@ wasmtime = { version = ">= 27.0.0, < 31.0.0", default-features = false, features
'pooling-allocator',
'demangle',
] }
wasi-common = { version = ">= 27.0.0, < 31.0.0" }
wiggle = { version = ">= 27.0.0, < 31.0.0" }
wasi-common = "37"
wiggle = "37"
anyhow = "1"
serde = { version = "1", features = ["derive"] }
serde_json = "1"
@@ -43,18 +43,20 @@ libc = "0.2"
[features]
default = ["http", "register-http", "register-filesystem", "wasmtime-default-features"]
register-http = ["ureq"] # enables wasm to be downloaded using http
register-filesystem = [] # enables wasm to be loaded from disk
http = ["ureq"] # enables extism_http_request
register-http = ["ureq"] # enables wasm to be downloaded using http
register-filesystem = [] # enables wasm to be loaded from disk
http = ["ureq"] # enables extism_http_request
wasmtime-exceptions = [] # enables exception-handling proposal in wasmtime (requires wasmtime gc feature)
wasmtime-default-features = [
'wasmtime/default',
]
[build-dependencies]
cbindgen = { version = "0.29", default-features = false }
[dev-dependencies]
criterion = "0.6.0"
criterion = "0.7.0"
quickcheck = "1"
rand = "0.9.0"

View File

@@ -191,6 +191,17 @@ ExtismCompiledPlugin *extism_compiled_plugin_new(const uint8_t *wasm,
bool with_wasi,
char **errmsg);
/**
* Pre-compile an Extism plugin and set the number of instructions a plugin is allowed to execute
*/
ExtismCompiledPlugin *extism_compiled_plugin_new_with_fuel_limit(const uint8_t *wasm,
ExtismSize wasm_size,
const ExtismFunction **functions,
ExtismSize n_functions,
bool with_wasi,
uint64_t fuel_limit,
char **errmsg);
/**
* Free `ExtismCompiledPlugin`
*/

View File

@@ -1,6 +1,7 @@
use std::{
any::Any,
collections::{BTreeMap, BTreeSet},
path::PathBuf,
sync::TryLockError,
};
@@ -60,26 +61,16 @@ impl CompiledPlugin {
.wasm_tail_call(true)
.wasm_function_references(true)
.wasm_gc(true);
#[cfg(feature = "wasmtime-exceptions")]
{
config.wasm_exceptions(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()?;
}
}
}
config.cache(Self::configure_cache(&builder.options.cache_config)?);
let engine = Engine::new(&config)?;
@@ -97,6 +88,43 @@ impl CompiledPlugin {
engine,
})
}
/// Return optional cache according to builder options.
fn configure_cache(
cache_opt: &Option<Option<std::path::PathBuf>>,
) -> Result<Option<wasmtime::Cache>, Error> {
match cache_opt {
// Explicitly disabled
Some(None) => Ok(None),
// Explicit path
Some(Some(p)) => {
let cache = wasmtime::Cache::from_file(Some(p.as_path()))?;
Ok(Some(cache))
}
// Unspecified, try environment, then system fallback
None => {
match std::env::var_os("EXTISM_CACHE_CONFIG") {
Some(val) => {
if val.is_empty() {
// Disable cache if env var exists but is empty
Ok(None)
} else {
let p = PathBuf::from(val);
let cache = wasmtime::Cache::from_file(Some(p.as_path()))?;
Ok(Some(cache))
}
}
None => {
// load cache configuration from the system default path
let cache = wasmtime::Cache::from_file(None)?;
Ok(Some(cache))
}
}
}
}
}
}
/// Plugin contains everything needed to execute a WASM function
@@ -344,6 +372,7 @@ fn relink(
let (kind, ty) = match import.ty() {
ExternType::Func(t) => ("function", t.to_string()),
ExternType::Global(t) => ("global", t.content().to_string()),
ExternType::Tag(t) => ("tag", t.ty().to_string()),
ExternType::Table(t) => ("table", t.element().to_string()),
ExternType::Memory(_) => ("memory", String::new()),
};

View File

@@ -49,7 +49,7 @@ impl PoolPlugin {
}
/// Access the underlying plugin
pub fn plugin(&self) -> std::cell::RefMut<Plugin> {
pub fn plugin(&self) -> std::cell::RefMut<'_, Plugin> {
self.0.borrow_mut()
}

View File

@@ -363,6 +363,70 @@ pub unsafe extern "C" fn extism_compiled_plugin_new(
})
}
/// Pre-compile an Extism plugin and set the number of instructions a plugin is allowed to execute
#[no_mangle]
pub unsafe extern "C" fn extism_compiled_plugin_new_with_fuel_limit(
wasm: *const u8,
wasm_size: Size,
functions: *mut *const ExtismFunction,
n_functions: Size,
with_wasi: bool,
fuel_limit: u64,
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)
.with_fuel_limit(fuel_limit);
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) {