Allocate a Perry-side heap string (StringHeader + UTF-8 payload) and return its pointer as the i64 the FFI boundary expects. Mirrors `perry_str`'s layout so a string we hand back is read identically to one Perry passed us. Returning this (never 0) for a `returns:"string"` function is mandatory: Perry's inline `.length` dereferences the pointer, so a null segfaults the caller.
(s: &str)
| 56 | /// Perry's inline `.length` dereferences the pointer, so a null segfaults the |
| 57 | /// caller. |
| 58 | fn alloc_perry_string(s: &str) -> i64 { |
| 59 | let bytes = s.as_bytes(); |
| 60 | let byte_len = bytes.len(); |
| 61 | let utf16_len = if bytes.iter().all(|&b| b < 0x80) { |
| 62 | byte_len |
| 63 | } else { |
| 64 | s.encode_utf16().count() |
| 65 | }; |
| 66 | // A value RETURNED across the FFI boundary is consumed by Perry's internal |
| 67 | // string machinery, whose canonical StringHeader is 5×u32 = 20 bytes |
| 68 | // (utf16_len, byte_len, capacity, refcount, flags) with data at +20. |
| 69 | // (The local 16-byte `StringHeader`/`perry_str` above describe the *incoming* |
| 70 | // arg representation, which omits `flags`; don't conflate the two.) |
| 71 | const HEADER_SIZE: usize = 20; |
| 72 | let total = HEADER_SIZE + byte_len; |
| 73 | unsafe { |
| 74 | let layout = std::alloc::Layout::from_size_align(total, 4).unwrap(); |
| 75 | let ptr = std::alloc::alloc(layout); |
| 76 | if ptr.is_null() { |
| 77 | return 0; |
| 78 | } |
| 79 | *(ptr.add(0) as *mut u32) = utf16_len as u32; |
| 80 | *(ptr.add(4) as *mut u32) = byte_len as u32; |
| 81 | *(ptr.add(8) as *mut u32) = byte_len as u32; // capacity |
| 82 | *(ptr.add(12) as *mut u32) = 1; // refcount = unique |
| 83 | *(ptr.add(16) as *mut u32) = 0; // flags |
| 84 | std::ptr::copy_nonoverlapping(bytes.as_ptr(), ptr.add(HEADER_SIZE), byte_len); |
| 85 | ptr as i64 |
| 86 | } |
| 87 | } |
| 88 | |
| 89 | use std::ffi::{c_char, c_void}; |
| 90 | use std::sync::atomic::{AtomicI64, AtomicU64, AtomicUsize, Ordering}; |
no test coverage detected