pub(crate) fn decode_ptr_len(packed: i64) -> (u32, u32) {
let packed = packed as u64;
((packed >> 32) as u32, (packed & 0xFFFF_FFFF) as u32)
}
#[allow(dead_code)]
pub(crate) fn encode_ptr_len(ptr: u32, len: u32) -> i64 {
(((ptr as u64) << 32) | (len as u64)) as i64
}
pub(crate) unsafe fn read_bytes(ptr: u32, len: u32) -> Vec<u8> {
if len == 0 {
return Vec::new();
}
#[cfg(target_arch = "wasm32")]
{
core::slice::from_raw_parts(ptr as usize as *const u8, len as usize).to_vec()
}
#[cfg(not(target_arch = "wasm32"))]
{
arena::read_at(ptr as usize, len as usize)
}
}
pub(crate) unsafe fn read_string(ptr: u32, len: u32) -> String {
if len == 0 {
return String::new();
}
let bytes = read_bytes(ptr, len);
String::from_utf8_lossy(&bytes).into_owned()
}
pub(crate) unsafe fn read_packed_string(packed: i64) -> Option<String> {
if packed == 0 {
return None;
}
let (ptr, len) = decode_ptr_len(packed);
Some(read_string(ptr, len))
}
pub(crate) unsafe fn read_packed_bytes(packed: i64) -> Option<Vec<u8>> {
if packed == 0 {
return None;
}
let (ptr, len) = decode_ptr_len(packed);
Some(read_bytes(ptr, len))
}
pub(crate) fn hex_decode(hex: &str) -> Option<Vec<u8>> {
let hex = hex.as_bytes();
if !hex.len().is_multiple_of(2) {
return None;
}
let mut bytes = Vec::with_capacity(hex.len() / 2);
for chunk in hex.chunks_exact(2) {
let hi = hex_nibble(chunk[0])?;
let lo = hex_nibble(chunk[1])?;
bytes.push((hi << 4) | lo);
}
Some(bytes)
}
fn hex_nibble(c: u8) -> Option<u8> {
match c {
b'0'..=b'9' => Some(c - b'0'),
b'a'..=b'f' => Some(c - b'a' + 10),
b'A'..=b'F' => Some(c - b'A' + 10),
_ => None,
}
}
pub(crate) fn host_arg_bytes(data: &[u8]) -> (i32, i32) {
#[cfg(target_arch = "wasm32")]
{
(data.as_ptr() as i32, data.len() as i32)
}
#[cfg(not(target_arch = "wasm32"))]
{
if data.is_empty() {
return (0, 0);
}
let offset = guest_alloc(data.len() as i32);
if offset == 0 {
return (0, 0);
}
arena::write_at(offset as usize, data);
(offset, data.len() as i32)
}
}
pub(crate) fn host_arg_str(s: &str) -> (i32, i32) {
host_arg_bytes(s.as_bytes())
}
const ARENA_SIZE: usize = 256 * 1024;
#[cfg(target_arch = "wasm32")]
mod arena {
use super::ARENA_SIZE;
use core::cell::UnsafeCell;
struct BumpArena {
buf: UnsafeCell<[u8; ARENA_SIZE]>,
offset: UnsafeCell<usize>,
}
unsafe impl Sync for BumpArena {}
static ARENA: BumpArena = BumpArena {
buf: UnsafeCell::new([0; ARENA_SIZE]),
offset: UnsafeCell::new(0),
};
pub fn reset() {
unsafe {
*ARENA.offset.get() = 0;
}
}
pub fn alloc(size: i32) -> i32 {
if size <= 0 {
return 0;
}
let size = size as usize;
unsafe {
let offset = &mut *ARENA.offset.get();
let aligned = (*offset + 7) & !7;
if aligned.checked_add(size).is_none_or(|end| end > ARENA_SIZE) {
return 0;
}
*offset = aligned + size;
let buf = ARENA.buf.get() as *mut u8;
buf.add(aligned) as i32
}
}
}
#[cfg(not(target_arch = "wasm32"))]
mod arena {
use super::ARENA_SIZE;
use std::cell::RefCell;
const FIRST_OFFSET: usize = 8;
struct ThreadArena {
buf: Box<[u8; ARENA_SIZE]>,
offset: usize,
}
impl ThreadArena {
fn new() -> Self {
Self {
buf: Box::new([0; ARENA_SIZE]),
offset: FIRST_OFFSET,
}
}
}
thread_local! {
static ARENA: RefCell<ThreadArena> = RefCell::new(ThreadArena::new());
}
pub fn reset() {
ARENA.with(|a| a.borrow_mut().offset = FIRST_OFFSET);
}
pub fn alloc(size: i32) -> i32 {
if size <= 0 {
return 0;
}
let size = size as usize;
ARENA.with(|a| {
let mut a = a.borrow_mut();
let aligned = (a.offset + 7) & !7;
if aligned.checked_add(size).is_none_or(|end| end > ARENA_SIZE) {
return 0;
}
a.offset = aligned + size;
aligned as i32
})
}
pub fn write_at(offset: usize, data: &[u8]) {
ARENA.with(|a| {
let mut a = a.borrow_mut();
a.buf[offset..offset + data.len()].copy_from_slice(data);
})
}
pub fn read_at(offset: usize, len: usize) -> Vec<u8> {
ARENA.with(|a| a.borrow().buf[offset..offset + len].to_vec())
}
}
pub fn reset_arena() {
arena::reset();
}
pub fn guest_alloc(size: i32) -> i32 {
arena::alloc(size)
}
#[cfg(not(target_arch = "wasm32"))]
pub(crate) use arena::{read_at as arena_read_at, write_at as arena_write_at};
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn decode_ptr_len_splits_high_low() {
let packed = encode_ptr_len(0xDEAD_BEEF, 0x1234);
let (ptr, len) = decode_ptr_len(packed);
assert_eq!(ptr, 0xDEAD_BEEF);
assert_eq!(len, 0x1234);
}
#[test]
fn encode_decode_round_trip_zero() {
assert_eq!(decode_ptr_len(0), (0, 0));
}
#[test]
fn hex_decode_lowercase() {
assert_eq!(hex_decode("deadbeef"), Some(vec![0xde, 0xad, 0xbe, 0xef]));
}
#[test]
fn hex_decode_uppercase() {
assert_eq!(hex_decode("DEADBEEF"), Some(vec![0xde, 0xad, 0xbe, 0xef]));
}
#[test]
fn hex_decode_mixed_case() {
assert_eq!(hex_decode("DeAdBeEf"), Some(vec![0xde, 0xad, 0xbe, 0xef]));
}
#[test]
fn hex_decode_odd_length_fails() {
assert!(hex_decode("abc").is_none());
}
#[test]
fn hex_decode_invalid_char_fails() {
assert!(hex_decode("zz").is_none());
}
#[test]
fn hex_decode_empty_ok() {
assert_eq!(hex_decode(""), Some(vec![]));
}
#[test]
fn guest_alloc_returns_zero_for_non_positive() {
reset_arena();
assert_eq!(guest_alloc(0), 0);
assert_eq!(guest_alloc(-1), 0);
}
#[test]
fn guest_alloc_aligned_to_eight() {
reset_arena();
let p1 = guest_alloc(1);
assert_ne!(p1, 0);
let p2 = guest_alloc(1);
assert_ne!(p2, 0);
assert_eq!(p2 - p1, 8);
}
#[test]
fn guest_alloc_returns_zero_when_exhausted() {
reset_arena();
assert_eq!(guest_alloc((ARENA_SIZE + 1) as i32), 0);
}
#[test]
fn reset_arena_recycles_space() {
reset_arena();
let p1 = guest_alloc(64);
reset_arena();
let p2 = guest_alloc(64);
assert_eq!(p1, p2);
}
#[test]
fn read_packed_zero_is_none() {
assert!(unsafe { read_packed_string(0) }.is_none());
assert!(unsafe { read_packed_bytes(0) }.is_none());
}
#[test]
fn read_bytes_zero_len_is_empty() {
let bytes = unsafe { read_bytes(0, 0) };
assert!(bytes.is_empty());
}
#[test]
fn host_arg_bytes_round_trip() {
reset_arena();
let (ptr, len) = host_arg_bytes(b"hello");
#[cfg(not(target_arch = "wasm32"))]
{
assert!(ptr > 0);
assert_eq!(len, 5);
let read = unsafe { read_bytes(ptr as u32, len as u32) };
assert_eq!(read, b"hello");
}
#[cfg(target_arch = "wasm32")]
{
let _ = (ptr, len);
}
}
}