Compare commits

...

12 Commits

Author SHA1 Message Date
Benjamin Eckel
b1e7733680 off by one 2023-10-19 20:56:28 -05:00
Benjamin Eckel
9d2f86e4ef off by one 2023-10-19 20:55:42 -05:00
Benjamin Eckel
b05bd7a12f wait i think we want to undercalculate 2023-10-19 20:50:14 -05:00
Benjamin Eckel
a4ae8a04d7 try off by 400 error 2023-10-19 20:44:01 -05:00
Benjamin Eckel
2176e5e62c try off by one error 2023-10-19 20:43:45 -05:00
Benjamin Eckel
894c719fa1 add an extra page 2023-10-19 20:31:58 -05:00
zach
bb83e7beb3 chore: backport latest kernel changes 2023-10-19 15:34:57 -07:00
zach
93392e0884 fix(stable): improve the way the kernel calculates how many pages to allocate (#471)
Fixes a bug in the kernel memory allocator where the space used by the
`MemoryRoot` wasn't being considered

---------

Co-authored-by: Benjamin Eckel <bhelx@users.noreply.github.com>
2023-09-20 16:02:42 -07:00
Benjamin Eckel
4ebd0eb372 chore: always run the publish of SDK 2023-09-18 19:17:47 -05:00
zach
8feee0c693 cleanup(stable): use wasm-strip on extism-runtime.wasm (#467) 2023-09-18 19:08:27 -05:00
Benjamin Eckel
773ab32a45 chore: Bump to 0.5.1 2023-09-18 18:55:46 -05:00
Benjamin Eckel
6a041d0c39 fix: Fixes rounding issue in kernel num_pages (#466)
There were some scenarios where the kernel was not allocating enough
pages to get data back into the plugin. So some host functions were
failing when the output was right on this boundary.

Related https://github.com/moonrepo/proto/issues/208

We will come back with tests and cherry pick this over to main. For now
we want to get out a 0.5.1 fix
2023-09-18 18:53:30 -05:00
9 changed files with 146 additions and 41 deletions

View File

@@ -29,6 +29,7 @@ jobs:
sleep 5
- name: Release Rust Host SDK
if: always()
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_API_TOKEN }}
run: |

View File

@@ -87,19 +87,22 @@ pub enum MemoryStatus {
///
/// The overall layout of the Extism-manged memory is organized like this:
/// |------|-------|---------|-------|--------------|
/// | Root | Block | Data | Block | Data | ...
/// |------|-------|---------|-------|--------------|
/// |------|-------+---------|-------+--------------|
/// | Root | Block + Data | Block + Data | ...
/// |------|-------+---------|-------+--------------|
///
/// Where `Root` and `Block` are fixed to the size of the `MemoryRoot` and `MemoryBlock` structs. But
/// the size of `Data` is dependent on the allocation size.
///
/// This means that the offset of a `Block` is the size of `Root` plus the size of all existing `Blocks`
/// including their data.
#[repr(C)]
pub struct MemoryRoot {
/// Position of the bump allocator, relative to `START_PAGE`
/// 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,
/// A pointer to where the blocks begin
/// A pointer to the start of the first block
pub blocks: [MemoryBlock; 0],
}
@@ -119,9 +122,13 @@ pub struct MemoryBlock {
/// Returns the number of pages needed for the given number of bytes
pub fn num_pages(nbytes: u64) -> usize {
let nbytes = nbytes as f64;
let page = PAGE_SIZE as f64;
((nbytes / page) + 0.5) as usize
let npages = nbytes / PAGE_SIZE as u64;
let remainder = nbytes % PAGE_SIZE as u64;
if remainder != 0 {
(npages + 1) as usize
} else {
npages as usize
}
}
// Get the `MemoryRoot` at the correct offset in memory
@@ -176,6 +183,22 @@ impl MemoryRoot {
self.position.store(0, Ordering::Release);
}
#[inline(always)]
#[allow(unused)]
fn pointer_in_bounds(&self, p: Pointer) -> bool {
let start_ptr = self.blocks.as_ptr() as Pointer;
p >= start_ptr && p < start_ptr + self.length.load(Ordering::Acquire) as Pointer
}
#[inline(always)]
#[allow(unused)]
fn pointer_in_bounds_fast(p: Pointer) -> bool {
// Similar to `pointer_in_bounds` but less accurate on the upper bound. This uses the total memory size,
// instead of checking `MemoryRoot::length`
let end = core::arch::wasm32::memory_size(0) << 16;
p >= core::mem::size_of::<Self>() as Pointer && p <= end as Pointer
}
// Find a block that is free to use, this can be a new block or an existing freed block. The `self_position` argument
// is used to avoid loading the allocators position more than once when performing an allocation.
unsafe fn find_free_block(
@@ -190,6 +213,7 @@ impl MemoryRoot {
while (block as u64) < self.blocks.as_ptr() as u64 + self_position {
let b = &mut *block;
// Get the block status, this lets us know if we are able to re-use it
let status = b.status.load(Ordering::Acquire);
// An unused block is safe to use
@@ -242,13 +266,14 @@ impl MemoryRoot {
let curr = self.blocks.as_ptr() as u64 + self_position;
// Get the number of bytes available
let mem_left = self_length - self_position;
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 >= mem_left {
if length + 1 >= mem_left {
// Calculate the number of pages needed to cover the remaining bytes
let npages = num_pages(length);
let npages = num_pages(length - mem_left) + 1;
let x = core::arch::wasm32::memory_grow(0, npages);
if x == usize::MAX {
return None;
@@ -276,8 +301,7 @@ impl MemoryRoot {
/// Finds the block at an offset in memory
pub unsafe fn find_block(&mut self, offs: Pointer) -> Option<&mut MemoryBlock> {
if offs >= self.blocks.as_ptr() as Pointer + self.length.load(Ordering::Acquire) as Pointer
{
if !Self::pointer_in_bounds_fast(offs) {
return None;
}
let ptr = offs - core::mem::size_of::<MemoryBlock>() as u64;
@@ -293,9 +317,7 @@ impl MemoryBlock {
/// is calculated based on metadata provided by the current block
#[inline]
pub unsafe fn next_ptr(&mut self) -> *mut MemoryBlock {
self.data
.as_mut_ptr()
.add(self.size + core::mem::size_of::<MemoryBlock>()) as *mut MemoryBlock
self.data.as_mut_ptr().add(self.size) as *mut MemoryBlock
}
/// Mark a block as free
@@ -305,11 +327,14 @@ impl MemoryBlock {
}
}
// Extism functions - these functions should be
// Extism functions
/// Allocate a block of memory and return the offset
#[no_mangle]
pub unsafe fn extism_alloc(n: Length) -> Pointer {
if n == 0 {
return 0;
}
let region = MemoryRoot::new();
let block = region.alloc(n);
match block {
@@ -321,9 +346,18 @@ pub unsafe fn extism_alloc(n: Length) -> Pointer {
/// Free allocated memory
#[no_mangle]
pub unsafe fn extism_free(p: Pointer) {
if p == 0 {
return;
}
let block = MemoryRoot::new().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;
}
}
}
@@ -343,57 +377,89 @@ pub unsafe fn extism_length(p: Pointer) -> Length {
/// Load a byte from Extism-managed memory
#[no_mangle]
pub unsafe fn extism_load_u8(p: Pointer) -> u8 {
#[cfg(feature = "bounds-checking")]
if !MemoryRoot::pointer_in_bounds_fast(p) {
return 0;
}
*(p as *mut u8)
}
/// Load a u64 from Extism-managed memory
#[no_mangle]
pub unsafe fn extism_load_u64(p: Pointer) -> u64 {
#[cfg(feature = "bounds-checking")]
if !MemoryRoot::pointer_in_bounds_fast(p + core::mem::size_of::<u64>() as u64 - 1) {
return 0;
}
*(p as *mut u64)
}
/// Load a byte from the input data
#[no_mangle]
pub unsafe fn extism_input_load_u8(p: Pointer) -> u8 {
#[cfg(feature = "bounds-checking")]
if p >= INPUT_LENGTH {
return 0;
}
*((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 {
#[cfg(feature = "bounds-checking")]
if p + core::mem::size_of::<u64>() as Pointer > INPUT_LENGTH {
return 0;
}
*((INPUT_OFFSET + p) as *mut u64)
}
/// Write a byte in Extism-managed memory
#[no_mangle]
pub unsafe fn extism_store_u8(p: Pointer, x: u8) {
#[cfg(feature = "bounds-checking")]
if !MemoryRoot::pointer_in_bounds_fast(p) {
return;
}
*(p as *mut u8) = x;
}
/// Write a u64 in Extism-managed memory
#[no_mangle]
pub unsafe fn extism_store_u64(p: Pointer, x: u64) {
unsafe {
*(p as *mut u64) = x;
#[cfg(feature = "bounds-checking")]
if !MemoryRoot::pointer_in_bounds_fast(p + core::mem::size_of::<u64>() as u64 - 1) {
return;
}
*(p as *mut u64) = x;
}
/// Set the range of the input data in memory
#[no_mangle]
pub fn extism_input_set(p: Pointer, len: Length) {
unsafe {
INPUT_OFFSET = p;
INPUT_LENGTH = len;
pub unsafe fn extism_input_set(p: Pointer, len: Length) {
#[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;
}
/// Set the range of the output data in memory
#[no_mangle]
pub fn extism_output_set(p: Pointer, len: Length) {
unsafe {
OUTPUT_OFFSET = p;
OUTPUT_LENGTH = len;
pub unsafe fn extism_output_set(p: Pointer, len: Length) {
#[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;
}
/// Get the input length
@@ -430,6 +496,16 @@ pub unsafe fn extism_reset() {
/// Set the error message offset
#[no_mangle]
pub unsafe fn extism_error_set(ptr: Pointer) {
// Allow ERROR to be set to 0
if ptr == 0 {
ERROR.store(ptr, Ordering::SeqCst);
return;
}
#[cfg(feature = "bounds-checking")]
if !MemoryRoot::new().pointer_in_bounds(ptr) {
return;
}
ERROR.store(ptr, Ordering::SeqCst);
}

View File

@@ -1,6 +1,6 @@
[package]
name = "libextism"
version = "0.5.0"
version = "0.5.1"
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.0"
version = "0.5.1"
edition = "2021"
authors = ["The Extism Authors", "oss@extism.org"]
license = "BSD-3-Clause"

View File

@@ -81,7 +81,12 @@ typedef struct {
/**
* Host function signature
*/
typedef void (*ExtismFunctionType)(ExtismCurrentPlugin *plugin, const ExtismVal *inputs, ExtismSize n_inputs, ExtismVal *outputs, ExtismSize n_outputs, void *data);
typedef void (*ExtismFunctionType)(ExtismCurrentPlugin *plugin,
const ExtismVal *inputs,
ExtismSize n_inputs,
ExtismVal *outputs,
ExtismSize n_outputs,
void *data);
typedef int32_t ExtismPlugin;

Binary file not shown.

View File

@@ -1,6 +1,6 @@
[package]
name = "extism"
version = "0.5.0"
version = "0.5.1"
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.0", path = "../runtime"}
extism-runtime = { version = "0.5.1", path = "../runtime"}
serde_json = "1"
log = "0.4"
anyhow = "1"
uuid = { version = "1", features = ["v4"] }
uuid = { version = "1", features = ["v4"] }

View File

@@ -45,6 +45,7 @@ mod tests {
const WASM: &[u8] = include_bytes!("../../wasm/code-functions.wasm");
const WASM_LOOP: &[u8] = include_bytes!("../../wasm/loop.wasm");
const WASM_GLOBALS: &[u8] = include_bytes!("../../wasm/globals.wasm");
const REFLECT_WASM: &[u8] = include_bytes!("../../wasm/reflect.wasm");
fn hello_world(
plugin: &mut CurrentPlugin,
@@ -53,8 +54,8 @@ mod tests {
_user_data: UserData,
) -> Result<(), Error> {
let input_offs = inputs[0].unwrap_i64() as u64;
let input = plugin.memory_read_str(input_offs).unwrap().to_string();
let length = plugin.memory_length(input_offs);
let input = plugin.memory_read(input_offs, length).to_vec();
let output = plugin.memory_alloc_bytes(&input).unwrap();
outputs[0] = Val::I64(output as i64);
Ok(())
@@ -309,14 +310,36 @@ 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() {
fn test_fuzz_reflect_plugin() {
// assert!(set_log_file("stdout", Some(log::Level::Trace)));
let f = Function::new(
"host_reflect",
[ValType::I64],
[ValType::I64],
None,
hello_world,
);
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);
let mut plugin = Plugin::new(&context, REFLECT_WASM, [f], true).unwrap();
for i in 1..65540 {
let input = "a".repeat(i);
let output = plugin.call("reflect", input.clone());
let output = std::str::from_utf8(output.unwrap()).unwrap();
assert_eq!(output, input);
}
}
}

BIN
wasm/reflect.wasm Normal file

Binary file not shown.