Compare commits

..

9 Commits

Author SHA1 Message Date
Benjamin Eckel
f4c61e8c44 feat: add default cache config (#615)
Co-authored-by: zach <zach@dylibso.com>
2023-11-29 14:32:01 -06:00
Benjamin Eckel
452bb2f498 chore: Bump 0.5.4 (#549) 2023-10-25 15:46:30 -05:00
zach
c043decd95 chore: backport latest kernel changes (#547)
- Updates to fix a possible issue with a value on stack being stored at
a very high offset in extism kernel memory
2023-10-25 11:37:37 -07:00
Benjamin Eckel
ec70b0e7b8 Release: 0.5.3 stable release (#538)
Cuts a new 0.5.3 release from stable.
2023-10-24 12:29:39 -05:00
zach
1452b9fb42 chore: backport latest kernel changes to stable (#528) 2023-10-20 10:16:33 -07:00
Benjamin Eckel
9f69a68df4 chore: fix elixir release action (#497)
This is 404ing for some reason. But Elixir doesn't need the shared
library so i'm removing it.

https://github.com/extism/extism/actions/runs/6425026823/job/17446865267
2023-10-05 17:32:20 -05:00
Benjamin Eckel
dbf9f34dc6 chore: Release elixir v0.5.1 (#496) 2023-10-05 17:14:54 -05:00
Benjamin Eckel
c8a9478da1 chore: also bump rust SDK to 0.5.2 2023-09-21 10:22:39 -05:00
Benjamin Eckel
e89ddd5a2a chore: Bump to 0.5.2 2023-09-21 09:41:27 -05:00
13 changed files with 112 additions and 108 deletions

View File

@@ -10,13 +10,6 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Install extism shared library
shell: bash
run: |
mkdir -p /home/runner/.local/bin/
export PATH="/home/runner/.local/bin/:$PATH"
curl https://raw.githubusercontent.com/extism/cli/main/install.sh | sh
extism --sudo --prefix /usr/local install
- name: Setup Elixir Host SDK
uses: erlef/setup-beam@v1
with:

View File

@@ -20,6 +20,7 @@ jobs:
target: ${{ matrix.target }}
- name: Release Rust Manifest Crate
if: always()
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_API_TOKEN }}
run: |

View File

@@ -4,7 +4,7 @@ defmodule Extism.MixProject do
def project do
[
app: :extism,
version: "0.5.0",
version: "0.5.1",
elixir: "~> 1.12",
start_permanent: Mix.env() == :prod,
deps: deps(),

View File

@@ -13,6 +13,6 @@ crate-type = ["cdylib"]
[workspace]
[dependencies]
rustler = "0.28.0"
extism = "0.5.0"
rustler = "0.29.1"
extism = "0.5.2"
log = "0.4"

View File

@@ -17,13 +17,13 @@
//! ## Input/Output
//!
//! Input and output are just allocated blocks of memory that are marked as either input or output using
//! the `extism_input_set` or `extism_output_set` functions. The global variables `INPUT_OFFSET` contains
//! the offset in memory to the input data and `INPUT_LENGTH` contains the size of the input data. `OUTPUT_OFFSET`
//! and `OUTPUT_LENGTH` are used for the output data.
//! the `extism_input_set` or `extism_output_set` functions. The MemoryRoot field `input_offset` contains
//! the offset in memory to the input data and `input_length` contains the size of the input data. `output_offset`
//! and `output_length` are used for the output data.
//!
//! ## Error handling
//!
//! The `ERROR` global is used to track the current error message. If it is set to `0` then there is no error.
//! The `error` field is used to track the current error message. If it is set to `0` then there is no error.
//! The length of the error message can be retreived using `extism_length`.
//!
//! ## Memory offsets
@@ -45,31 +45,6 @@ pub type Length = u64;
/// WebAssembly page size
const PAGE_SIZE: usize = 65536;
/// Determines the amount of bytes that can be wasted by re-using a block. If more than this number is wasted by re-using
/// a block then it will be split into two smaller blocks.
const BLOCK_SPLIT_SIZE: usize = 128;
/// Offset to the input data
static mut INPUT_OFFSET: Pointer = 0;
/// Length of the input data
static mut INPUT_LENGTH: Length = 0;
/// Offset to the output data
static mut OUTPUT_OFFSET: Pointer = 0;
/// Offset to the input data
static mut OUTPUT_LENGTH: Length = 0;
/// Current error message
static mut ERROR: AtomicU64 = AtomicU64::new(0);
/// Determines if the kernel has been initialized already
static mut INITIALIZED: AtomicBool = AtomicBool::new(false);
/// A pointer to the first page that will be managed by Extism, this is set during initialization
static mut START_PAGE: usize = 0;
/// Provides information about the usage status of a `MemoryBlock`
#[repr(u8)]
#[derive(PartialEq)]
@@ -98,10 +73,22 @@ pub enum MemoryStatus {
/// including their data.
#[repr(C)]
pub struct MemoryRoot {
/// Set to true after initialization
pub initialized: AtomicBool,
/// Position of the bump allocator, relative to `blocks` field
pub position: AtomicU64,
/// The total size of all data allocated using this allocator
pub length: AtomicU64,
/// Offset of error block
pub error: AtomicU64,
/// Input position in memory
pub input_offset: Pointer,
/// Input length
pub input_length: Length,
/// Output position in memory
pub output_offset: Pointer,
/// Output length
pub output_length: Length,
/// A pointer to the start of the first block
pub blocks: [MemoryBlock; 0],
}
@@ -131,32 +118,41 @@ pub fn num_pages(nbytes: u64) -> usize {
}
}
// Get the `MemoryRoot` at the correct offset in memory
// Get the `MemoryRoot`, this is always stored at offset 1 in memory
#[inline]
unsafe fn memory_root() -> &'static mut MemoryRoot {
&mut *((START_PAGE * PAGE_SIZE) as *mut MemoryRoot)
&mut *(1 as *mut MemoryRoot)
}
impl MemoryRoot {
/// Initialize or load the `MemoryRoot` from the correct position in memory
pub unsafe fn new() -> &'static mut MemoryRoot {
let root = memory_root();
// If this fails then `INITIALIZED` is already `true` and we can just return the
// already initialized `MemoryRoot`
if INITIALIZED
if root
.initialized
.compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed)
.is_err()
{
return memory_root();
return root;
}
// Ensure that at least one page is allocated to store the `MemoryRoot` data
START_PAGE = core::arch::wasm32::memory_grow(0, 1);
if START_PAGE == usize::MAX {
panic!("Out of memory");
if core::arch::wasm32::memory_size(0) == 0 {
if core::arch::wasm32::memory_grow(0, 1) == usize::MAX {
core::arch::wasm32::unreachable()
}
}
root.input_offset = 0;
root.input_length = 0;
root.output_offset = 0;
root.output_length = 0;
root.error.store(0, Ordering::Release);
// Initialize the `MemoryRoot` length, position and data
let root = memory_root();
root.length.store(
PAGE_SIZE as u64 - core::mem::size_of::<MemoryRoot>() as u64,
Ordering::Release,
@@ -181,6 +177,11 @@ impl MemoryRoot {
self.length.load(Ordering::Acquire) as usize,
);
self.position.store(0, Ordering::Release);
self.error.store(0, Ordering::Release);
self.input_offset = 0;
self.input_length = 0;
self.output_offset = 0;
self.output_length = 0;
}
#[inline(always)]
@@ -224,7 +225,7 @@ impl MemoryRoot {
// Re-use freed blocks when they're large enough
if status == MemoryStatus::Free as u8 && b.size >= length as usize {
// Split block if there is too much excess
if b.size - length as usize >= BLOCK_SPLIT_SIZE {
if b.size - length as usize >= 128 {
b.size -= length as usize;
b.used = 0;
@@ -267,13 +268,12 @@ impl MemoryRoot {
// Get the number of bytes available
let mem_left = self_length - self_position - core::mem::size_of::<MemoryRoot>() as u64;
let mem_left = mem_left;
// When the allocation is larger than the number of bytes available
// we will need to try to grow the memory
if length + 1 >= mem_left {
if length >= mem_left {
// Calculate the number of pages needed to cover the remaining bytes
let npages = num_pages(length - mem_left) + 1;
let npages = num_pages(length - mem_left);
let x = core::arch::wasm32::memory_grow(0, npages);
if x == usize::MAX {
return None;
@@ -349,14 +349,15 @@ pub unsafe fn extism_free(p: Pointer) {
if p == 0 {
return;
}
let block = MemoryRoot::new().find_block(p);
let root = MemoryRoot::new();
let block = root.find_block(p);
if let Some(block) = block {
block.free();
// If the input pointer is freed for some reason, make sure the input length to 0
// since the original data is gone
if p == INPUT_OFFSET {
INPUT_LENGTH = 0;
if p == root.input_offset {
root.input_length = 0;
}
}
}
@@ -397,21 +398,23 @@ pub unsafe fn extism_load_u64(p: Pointer) -> u64 {
/// Load a byte from the input data
#[no_mangle]
pub unsafe fn extism_input_load_u8(p: Pointer) -> u8 {
let root = MemoryRoot::new();
#[cfg(feature = "bounds-checking")]
if p >= INPUT_LENGTH {
if p >= root.input_length {
return 0;
}
*((INPUT_OFFSET + p) as *mut u8)
*((root.input_offset + p) as *mut u8)
}
/// Load a u64 from the input data
#[no_mangle]
pub unsafe fn extism_input_load_u64(p: Pointer) -> u64 {
let root = MemoryRoot::new();
#[cfg(feature = "bounds-checking")]
if p + core::mem::size_of::<u64>() as Pointer > INPUT_LENGTH {
if p + core::mem::size_of::<u64>() as Pointer > root.input_length {
return 0;
}
*((INPUT_OFFSET + p) as *mut u64)
*((root.input_offset + p) as *mut u64)
}
/// Write a byte in Extism-managed memory
@@ -437,82 +440,82 @@ pub unsafe fn extism_store_u64(p: Pointer, x: u64) {
/// Set the range of the input data in memory
#[no_mangle]
pub unsafe fn extism_input_set(p: Pointer, len: Length) {
let root = MemoryRoot::new();
#[cfg(feature = "bounds-checking")]
{
let root = MemoryRoot::new();
if !root.pointer_in_bounds(p) || !root.pointer_in_bounds(p + len - 1) {
return;
}
}
INPUT_OFFSET = p;
INPUT_LENGTH = len;
root.input_offset = p;
root.input_length = len;
}
/// Set the range of the output data in memory
#[no_mangle]
pub unsafe fn extism_output_set(p: Pointer, len: Length) {
let root = MemoryRoot::new();
#[cfg(feature = "bounds-checking")]
{
let root = MemoryRoot::new();
if !root.pointer_in_bounds(p) || !root.pointer_in_bounds(p + len - 1) {
return;
}
}
OUTPUT_OFFSET = p;
OUTPUT_LENGTH = len;
root.output_offset = p;
root.output_length = len;
}
/// Get the input length
#[no_mangle]
pub fn extism_input_length() -> Length {
unsafe { INPUT_LENGTH }
unsafe { MemoryRoot::new().input_length }
}
/// Get the input offset in Exitsm-managed memory
#[no_mangle]
pub fn extism_input_offset() -> Length {
unsafe { INPUT_OFFSET }
unsafe { MemoryRoot::new().input_offset }
}
/// Get the output length
#[no_mangle]
pub fn extism_output_length() -> Length {
unsafe { OUTPUT_LENGTH }
pub unsafe fn extism_output_length() -> Length {
unsafe { MemoryRoot::new().output_length }
}
/// Get the output offset in Extism-managed memory
#[no_mangle]
pub fn extism_output_offset() -> Length {
unsafe { OUTPUT_OFFSET }
pub unsafe fn extism_output_offset() -> Length {
MemoryRoot::new().output_offset
}
/// Reset the allocator
#[no_mangle]
pub unsafe fn extism_reset() {
ERROR.store(0, Ordering::SeqCst);
MemoryRoot::new().reset()
}
/// Set the error message offset
#[no_mangle]
pub unsafe fn extism_error_set(ptr: Pointer) {
let root = MemoryRoot::new();
// Allow ERROR to be set to 0
if ptr == 0 {
ERROR.store(ptr, Ordering::SeqCst);
root.error.store(ptr, Ordering::SeqCst);
return;
}
#[cfg(feature = "bounds-checking")]
if !MemoryRoot::new().pointer_in_bounds(ptr) {
if !root.pointer_in_bounds(ptr) {
return;
}
ERROR.store(ptr, Ordering::SeqCst);
root.error.store(ptr, Ordering::SeqCst);
}
/// Get the error message offset, if it's `0` then no error has been set
#[no_mangle]
pub unsafe fn extism_error_get() -> Pointer {
ERROR.load(Ordering::SeqCst)
MemoryRoot::new().error.load(Ordering::SeqCst)
}
/// Get the position of the allocator, this can be used as an indication of how many bytes are currently in-use

View File

@@ -1,6 +1,6 @@
[package]
name = "libextism"
version = "0.5.1"
version = "0.5.4"
edition = "2021"
authors = ["The Extism Authors", "oss@extism.org"]
license = "BSD-3-Clause"

View File

@@ -1,6 +1,6 @@
[package]
name = "extism-runtime"
version = "0.5.1"
version = "0.5.5"
edition = "2021"
authors = ["The Extism Authors", "oss@extism.org"]
license = "BSD-3-Clause"

Binary file not shown.

View File

@@ -104,6 +104,9 @@ pub trait InternalExt {
}
fn memory_alloc(&mut self, n: Size) -> Result<u64, Error> {
if n == 0 {
return Ok(0);
}
let (linker, mut store) = self.linker_and_store();
let output = &mut [Val::I64(0)];
linker
@@ -113,7 +116,7 @@ pub trait InternalExt {
.unwrap()
.call(&mut store, &[Val::I64(n as i64)], output)?;
let offs = output[0].unwrap_i64() as u64;
if offs == 0 {
if offs == 0 && n > 0 {
anyhow::bail!("out of memory")
}
trace!("memory_alloc: {}, {}", offs, n);

View File

@@ -200,6 +200,7 @@ pub(crate) fn http_request(
Some(res.into_reader())
}
Err(e) => {
log::error!("Unable to make HTTP request: {:?}", e);
if let Some(res) = e.into_response() {
data.http_status = res.status();
Some(res.into_reader())

View File

@@ -132,7 +132,8 @@ impl Plugin {
Config::new()
.epoch_interruption(true)
.debug_info(std::env::var("EXTISM_DEBUG").is_ok())
.profiler(profiling_strategy()),
.profiler(profiling_strategy())
.cache_config_load_default()?,
)?;
let mut imports = imports.into_iter();
let (manifest, modules) = Manifest::new(&engine, wasm.as_ref())?;
@@ -334,23 +335,25 @@ impl Plugin {
internal.linker = linker;
}
let bytes = unsafe { std::slice::from_raw_parts(input, len) };
trace!("Input size: {}", bytes.len());
if len > 0 {
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");
}
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)?;
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 [],
)?;
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(())

View File

@@ -1,6 +1,6 @@
[package]
name = "extism"
version = "0.5.1"
version = "0.5.5"
edition = "2021"
authors = ["The Extism Authors", "oss@extism.org"]
license = "BSD-3-Clause"
@@ -10,8 +10,8 @@ description = "Extism Host SDK for Rust"
[dependencies]
extism-manifest = { version = "0.5.0", path = "../manifest" }
extism-runtime = { version = "0.5.1", path = "../runtime"}
extism-runtime = { version = "0.5.5", path = "../runtime"}
serde_json = "1"
log = "0.4"
anyhow = "1"
uuid = { version = "1", features = ["v4"] }
uuid = { version = "1", features = ["v4"] }

View File

@@ -283,7 +283,7 @@ mod tests {
});
let start = std::time::Instant::now();
let _output = plugin.call("infinite_loop", "abc123");
let _output = plugin.call("infinite_loop", "");
let end = std::time::Instant::now();
let time = end - start;
println!("Cancelled plugin ran for {:?}", time);
@@ -310,16 +310,16 @@ mod tests {
}
}
// #[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);
// }
// }
#[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);
}
}
#[test]
fn test_fuzz_reflect_plugin() {