1pub(crate) fn decode_ptr_len(packed: i64) -> (u32, u32) {
29 let packed = packed as u64;
30 ((packed >> 32) as u32, (packed & 0xFFFF_FFFF) as u32)
31}
32
33#[allow(dead_code)]
38pub(crate) fn encode_ptr_len(ptr: u32, len: u32) -> i64 {
39 (((ptr as u64) << 32) | (len as u64)) as i64
40}
41
42pub(crate) unsafe fn read_bytes(ptr: u32, len: u32) -> Vec<u8> {
49 if len == 0 {
50 return Vec::new();
51 }
52 #[cfg(target_arch = "wasm32")]
53 {
54 core::slice::from_raw_parts(ptr as usize as *const u8, len as usize).to_vec()
55 }
56 #[cfg(not(target_arch = "wasm32"))]
57 {
58 arena::read_at(ptr as usize, len as usize)
59 }
60}
61
62pub(crate) unsafe fn read_string(ptr: u32, len: u32) -> String {
69 if len == 0 {
70 return String::new();
71 }
72 let bytes = read_bytes(ptr, len);
73 String::from_utf8_lossy(&bytes).into_owned()
74}
75
76pub(crate) unsafe fn read_packed_string(packed: i64) -> Option<String> {
84 if packed == 0 {
85 return None;
86 }
87 let (ptr, len) = decode_ptr_len(packed);
88 Some(read_string(ptr, len))
89}
90
91pub(crate) unsafe fn read_packed_bytes(packed: i64) -> Option<Vec<u8>> {
99 if packed == 0 {
100 return None;
101 }
102 let (ptr, len) = decode_ptr_len(packed);
103 Some(read_bytes(ptr, len))
104}
105
106pub(crate) fn hex_decode(hex: &str) -> Option<Vec<u8>> {
110 let hex = hex.as_bytes();
111 if !hex.len().is_multiple_of(2) {
112 return None;
113 }
114 let mut bytes = Vec::with_capacity(hex.len() / 2);
115 for chunk in hex.chunks_exact(2) {
116 let hi = hex_nibble(chunk[0])?;
117 let lo = hex_nibble(chunk[1])?;
118 bytes.push((hi << 4) | lo);
119 }
120 Some(bytes)
121}
122
123fn hex_nibble(c: u8) -> Option<u8> {
124 match c {
125 b'0'..=b'9' => Some(c - b'0'),
126 b'a'..=b'f' => Some(c - b'a' + 10),
127 b'A'..=b'F' => Some(c - b'A' + 10),
128 _ => None,
129 }
130}
131
132pub(crate) fn host_arg_bytes(data: &[u8]) -> (i32, i32) {
141 #[cfg(target_arch = "wasm32")]
142 {
143 (data.as_ptr() as i32, data.len() as i32)
144 }
145 #[cfg(not(target_arch = "wasm32"))]
146 {
147 if data.is_empty() {
148 return (0, 0);
149 }
150 let offset = guest_alloc(data.len() as i32);
151 if offset == 0 {
152 return (0, 0);
153 }
154 arena::write_at(offset as usize, data);
155 (offset, data.len() as i32)
156 }
157}
158
159pub(crate) fn host_arg_str(s: &str) -> (i32, i32) {
161 host_arg_bytes(s.as_bytes())
162}
163
164const ARENA_SIZE: usize = 256 * 1024;
178
179#[cfg(target_arch = "wasm32")]
180mod arena {
181 use super::ARENA_SIZE;
182 use core::cell::UnsafeCell;
183
184 struct BumpArena {
185 buf: UnsafeCell<[u8; ARENA_SIZE]>,
186 offset: UnsafeCell<usize>,
187 }
188
189 unsafe impl Sync for BumpArena {}
191
192 static ARENA: BumpArena = BumpArena {
193 buf: UnsafeCell::new([0; ARENA_SIZE]),
194 offset: UnsafeCell::new(0),
195 };
196
197 pub fn reset() {
198 unsafe {
200 *ARENA.offset.get() = 0;
201 }
202 }
203
204 pub fn alloc(size: i32) -> i32 {
205 if size <= 0 {
206 return 0;
207 }
208 let size = size as usize;
209 unsafe {
211 let offset = &mut *ARENA.offset.get();
212 let aligned = (*offset + 7) & !7;
213 if aligned.checked_add(size).is_none_or(|end| end > ARENA_SIZE) {
214 return 0;
215 }
216 *offset = aligned + size;
217 let buf = ARENA.buf.get() as *mut u8;
218 buf.add(aligned) as i32
219 }
220 }
221}
222
223#[cfg(not(target_arch = "wasm32"))]
224mod arena {
225 use super::ARENA_SIZE;
226 use std::cell::RefCell;
227
228 const FIRST_OFFSET: usize = 8;
232
233 struct ThreadArena {
234 buf: Box<[u8; ARENA_SIZE]>,
235 offset: usize,
236 }
237
238 impl ThreadArena {
239 fn new() -> Self {
240 Self {
241 buf: Box::new([0; ARENA_SIZE]),
242 offset: FIRST_OFFSET,
243 }
244 }
245 }
246
247 thread_local! {
248 static ARENA: RefCell<ThreadArena> = RefCell::new(ThreadArena::new());
249 }
250
251 pub fn reset() {
252 ARENA.with(|a| a.borrow_mut().offset = FIRST_OFFSET);
253 }
254
255 pub fn alloc(size: i32) -> i32 {
259 if size <= 0 {
260 return 0;
261 }
262 let size = size as usize;
263 ARENA.with(|a| {
264 let mut a = a.borrow_mut();
265 let aligned = (a.offset + 7) & !7;
266 if aligned.checked_add(size).is_none_or(|end| end > ARENA_SIZE) {
267 return 0;
268 }
269 a.offset = aligned + size;
270 aligned as i32
271 })
272 }
273
274 pub fn write_at(offset: usize, data: &[u8]) {
275 ARENA.with(|a| {
276 let mut a = a.borrow_mut();
277 a.buf[offset..offset + data.len()].copy_from_slice(data);
278 })
279 }
280
281 pub fn read_at(offset: usize, len: usize) -> Vec<u8> {
282 ARENA.with(|a| a.borrow().buf[offset..offset + len].to_vec())
283 }
284}
285
286pub fn reset_arena() {
293 arena::reset();
294}
295
296pub fn guest_alloc(size: i32) -> i32 {
303 arena::alloc(size)
304}
305
306#[cfg(not(target_arch = "wasm32"))]
309pub(crate) use arena::{read_at as arena_read_at, write_at as arena_write_at};
310
311#[cfg(test)]
312mod tests {
313 use super::*;
314
315 #[test]
316 fn decode_ptr_len_splits_high_low() {
317 let packed = encode_ptr_len(0xDEAD_BEEF, 0x1234);
318 let (ptr, len) = decode_ptr_len(packed);
319 assert_eq!(ptr, 0xDEAD_BEEF);
320 assert_eq!(len, 0x1234);
321 }
322
323 #[test]
324 fn encode_decode_round_trip_zero() {
325 assert_eq!(decode_ptr_len(0), (0, 0));
326 }
327
328 #[test]
329 fn hex_decode_lowercase() {
330 assert_eq!(hex_decode("deadbeef"), Some(vec![0xde, 0xad, 0xbe, 0xef]));
331 }
332
333 #[test]
334 fn hex_decode_uppercase() {
335 assert_eq!(hex_decode("DEADBEEF"), Some(vec![0xde, 0xad, 0xbe, 0xef]));
336 }
337
338 #[test]
339 fn hex_decode_mixed_case() {
340 assert_eq!(hex_decode("DeAdBeEf"), Some(vec![0xde, 0xad, 0xbe, 0xef]));
341 }
342
343 #[test]
344 fn hex_decode_odd_length_fails() {
345 assert!(hex_decode("abc").is_none());
346 }
347
348 #[test]
349 fn hex_decode_invalid_char_fails() {
350 assert!(hex_decode("zz").is_none());
351 }
352
353 #[test]
354 fn hex_decode_empty_ok() {
355 assert_eq!(hex_decode(""), Some(vec![]));
356 }
357
358 #[test]
359 fn guest_alloc_returns_zero_for_non_positive() {
360 reset_arena();
361 assert_eq!(guest_alloc(0), 0);
362 assert_eq!(guest_alloc(-1), 0);
363 }
364
365 #[test]
366 fn guest_alloc_aligned_to_eight() {
367 reset_arena();
368 let p1 = guest_alloc(1);
369 assert_ne!(p1, 0);
370 let p2 = guest_alloc(1);
371 assert_ne!(p2, 0);
372 assert_eq!(p2 - p1, 8);
374 }
375
376 #[test]
377 fn guest_alloc_returns_zero_when_exhausted() {
378 reset_arena();
379 assert_eq!(guest_alloc((ARENA_SIZE + 1) as i32), 0);
381 }
382
383 #[test]
384 fn reset_arena_recycles_space() {
385 reset_arena();
386 let p1 = guest_alloc(64);
387 reset_arena();
388 let p2 = guest_alloc(64);
389 assert_eq!(p1, p2);
390 }
391
392 #[test]
393 fn read_packed_zero_is_none() {
394 assert!(unsafe { read_packed_string(0) }.is_none());
395 assert!(unsafe { read_packed_bytes(0) }.is_none());
396 }
397
398 #[test]
399 fn read_bytes_zero_len_is_empty() {
400 let bytes = unsafe { read_bytes(0, 0) };
401 assert!(bytes.is_empty());
402 }
403
404 #[test]
405 fn host_arg_bytes_round_trip() {
406 reset_arena();
407 let (ptr, len) = host_arg_bytes(b"hello");
408 #[cfg(not(target_arch = "wasm32"))]
409 {
410 assert!(ptr > 0);
411 assert_eq!(len, 5);
412 let read = unsafe { read_bytes(ptr as u32, len as u32) };
413 assert_eq!(read, b"hello");
414 }
415 #[cfg(target_arch = "wasm32")]
416 {
417 let _ = (ptr, len);
418 }
419 }
420}