sigil_parser/ffi/
helpers.rs

1//! FFI Runtime Helpers
2//!
3//! Provides runtime support for FFI operations including:
4//! - String conversion (Sigil <-> C strings)
5//! - Pointer manipulation
6//! - Memory management for FFI allocations
7
8use std::ffi::{CStr, CString};
9use std::os::raw::c_char;
10
11/// A C string wrapper that owns its memory.
12/// Automatically freed when dropped.
13pub struct CStringOwned {
14    ptr: *mut c_char,
15    len: usize,
16}
17
18impl CStringOwned {
19    /// Create a null pointer (represents empty/null string).
20    pub fn null() -> Self {
21        Self {
22            ptr: std::ptr::null_mut(),
23            len: 0,
24        }
25    }
26
27    /// Get the raw pointer for passing to C functions.
28    pub fn as_ptr(&self) -> *const c_char {
29        self.ptr
30    }
31
32    /// Get a mutable raw pointer.
33    pub fn as_mut_ptr(&mut self) -> *mut c_char {
34        self.ptr
35    }
36
37    /// Check if this is a null pointer.
38    pub fn is_null(&self) -> bool {
39        self.ptr.is_null()
40    }
41
42    /// Get the length (excluding null terminator).
43    pub fn len(&self) -> usize {
44        self.len
45    }
46
47    /// Check if empty.
48    pub fn is_empty(&self) -> bool {
49        self.len == 0
50    }
51}
52
53impl Drop for CStringOwned {
54    fn drop(&mut self) {
55        if !self.ptr.is_null() {
56            unsafe {
57                // Free the CString memory
58                drop(CString::from_raw(self.ptr));
59            }
60        }
61    }
62}
63
64// ============================================
65// Runtime helper functions (called from JIT)
66// ============================================
67
68/// Convert a Sigil string (ptr, len) to a null-terminated C string.
69/// Returns a pointer that must be freed with `sigil_cstring_free`.
70#[no_mangle]
71pub extern "C" fn sigil_string_to_cstring(ptr: *const u8, len: usize) -> *mut c_char {
72    if ptr.is_null() || len == 0 {
73        // Return empty string, not null
74        match CString::new("") {
75            Ok(cstr) => cstr.into_raw(),
76            Err(_) => std::ptr::null_mut(),
77        }
78    } else {
79        unsafe {
80            let slice = std::slice::from_raw_parts(ptr, len);
81            match std::str::from_utf8(slice) {
82                Ok(s) => match CString::new(s) {
83                    Ok(cstr) => cstr.into_raw(),
84                    Err(_) => std::ptr::null_mut(),
85                },
86                Err(_) => std::ptr::null_mut(),
87            }
88        }
89    }
90}
91
92/// Free a C string allocated by `sigil_string_to_cstring`.
93#[no_mangle]
94pub extern "C" fn sigil_cstring_free(ptr: *mut c_char) {
95    if !ptr.is_null() {
96        unsafe {
97            drop(CString::from_raw(ptr));
98        }
99    }
100}
101
102/// Convert a null-terminated C string to Sigil string representation.
103/// Returns the length of the string. The pointer remains valid.
104#[no_mangle]
105pub extern "C" fn sigil_cstring_len(ptr: *const c_char) -> usize {
106    if ptr.is_null() {
107        return 0;
108    }
109    unsafe { CStr::from_ptr(ptr).to_bytes().len() }
110}
111
112/// Copy a C string into a Sigil-managed buffer.
113/// Returns the number of bytes copied.
114#[no_mangle]
115pub extern "C" fn sigil_cstring_copy(
116    src: *const c_char,
117    dst: *mut u8,
118    dst_len: usize,
119) -> usize {
120    if src.is_null() || dst.is_null() || dst_len == 0 {
121        return 0;
122    }
123
124    unsafe {
125        let c_str = CStr::from_ptr(src);
126        let bytes = c_str.to_bytes();
127        let copy_len = bytes.len().min(dst_len);
128        std::ptr::copy_nonoverlapping(bytes.as_ptr(), dst, copy_len);
129        copy_len
130    }
131}
132
133// ============================================
134// Pointer manipulation helpers
135// ============================================
136
137/// Create a pointer from an integer address.
138#[no_mangle]
139pub extern "C" fn sigil_ptr_from_int(addr: i64) -> *mut u8 {
140    addr as *mut u8
141}
142
143/// Convert a pointer to an integer address.
144#[no_mangle]
145pub extern "C" fn sigil_ptr_to_int(ptr: *const u8) -> i64 {
146    ptr as i64
147}
148
149/// Read a byte from a pointer.
150#[no_mangle]
151pub extern "C" fn sigil_ptr_read_u8(ptr: *const u8) -> u8 {
152    if ptr.is_null() {
153        return 0;
154    }
155    unsafe { *ptr }
156}
157
158/// Write a byte to a pointer.
159#[no_mangle]
160pub extern "C" fn sigil_ptr_write_u8(ptr: *mut u8, value: u8) {
161    if !ptr.is_null() {
162        unsafe {
163            *ptr = value;
164        }
165    }
166}
167
168/// Read a 32-bit integer from a pointer.
169#[no_mangle]
170pub extern "C" fn sigil_ptr_read_i32(ptr: *const i32) -> i32 {
171    if ptr.is_null() {
172        return 0;
173    }
174    unsafe { *ptr }
175}
176
177/// Write a 32-bit integer to a pointer.
178#[no_mangle]
179pub extern "C" fn sigil_ptr_write_i32(ptr: *mut i32, value: i32) {
180    if !ptr.is_null() {
181        unsafe {
182            *ptr = value;
183        }
184    }
185}
186
187/// Read a 64-bit integer from a pointer.
188#[no_mangle]
189pub extern "C" fn sigil_ptr_read_i64(ptr: *const i64) -> i64 {
190    if ptr.is_null() {
191        return 0;
192    }
193    unsafe { *ptr }
194}
195
196/// Write a 64-bit integer to a pointer.
197#[no_mangle]
198pub extern "C" fn sigil_ptr_write_i64(ptr: *mut i64, value: i64) {
199    if !ptr.is_null() {
200        unsafe {
201            *ptr = value;
202        }
203    }
204}
205
206/// Read a double from a pointer.
207#[no_mangle]
208pub extern "C" fn sigil_ptr_read_f64(ptr: *const f64) -> f64 {
209    if ptr.is_null() {
210        return 0.0;
211    }
212    unsafe { *ptr }
213}
214
215/// Write a double to a pointer.
216#[no_mangle]
217pub extern "C" fn sigil_ptr_write_f64(ptr: *mut f64, value: f64) {
218    if !ptr.is_null() {
219        unsafe {
220            *ptr = value;
221        }
222    }
223}
224
225/// Add an offset to a pointer.
226#[no_mangle]
227pub extern "C" fn sigil_ptr_add(ptr: *const u8, offset: i64) -> *const u8 {
228    if ptr.is_null() {
229        return std::ptr::null();
230    }
231    unsafe { ptr.offset(offset as isize) }
232}
233
234/// Check if a pointer is null.
235#[no_mangle]
236pub extern "C" fn sigil_ptr_is_null(ptr: *const u8) -> i64 {
237    if ptr.is_null() { 1 } else { 0 }
238}
239
240// ============================================
241// Memory allocation helpers
242// ============================================
243
244/// Allocate memory of the given size.
245/// Returns a pointer to the allocated memory, or null on failure.
246#[no_mangle]
247pub extern "C" fn sigil_alloc(size: usize) -> *mut u8 {
248    if size == 0 {
249        return std::ptr::null_mut();
250    }
251
252    let layout = match std::alloc::Layout::from_size_align(size, 8) {
253        Ok(l) => l,
254        Err(_) => return std::ptr::null_mut(),
255    };
256
257    unsafe { std::alloc::alloc_zeroed(layout) }
258}
259
260/// Free memory allocated by `sigil_alloc`.
261#[no_mangle]
262pub extern "C" fn sigil_free(ptr: *mut u8, size: usize) {
263    if ptr.is_null() || size == 0 {
264        return;
265    }
266
267    let layout = match std::alloc::Layout::from_size_align(size, 8) {
268        Ok(l) => l,
269        Err(_) => return,
270    };
271
272    unsafe {
273        std::alloc::dealloc(ptr, layout);
274    }
275}
276
277/// Reallocate memory to a new size.
278#[no_mangle]
279pub extern "C" fn sigil_realloc(ptr: *mut u8, old_size: usize, new_size: usize) -> *mut u8 {
280    if ptr.is_null() {
281        return sigil_alloc(new_size);
282    }
283
284    if new_size == 0 {
285        sigil_free(ptr, old_size);
286        return std::ptr::null_mut();
287    }
288
289    let old_layout = match std::alloc::Layout::from_size_align(old_size, 8) {
290        Ok(l) => l,
291        Err(_) => return std::ptr::null_mut(),
292    };
293
294    unsafe { std::alloc::realloc(ptr, old_layout, new_size) }
295}
296
297/// Copy memory from src to dst.
298#[no_mangle]
299pub extern "C" fn sigil_memcpy(dst: *mut u8, src: *const u8, size: usize) {
300    if dst.is_null() || src.is_null() || size == 0 {
301        return;
302    }
303    unsafe {
304        std::ptr::copy_nonoverlapping(src, dst, size);
305    }
306}
307
308/// Set memory to a value.
309#[no_mangle]
310pub extern "C" fn sigil_memset(ptr: *mut u8, value: u8, size: usize) {
311    if ptr.is_null() || size == 0 {
312        return;
313    }
314    unsafe {
315        std::ptr::write_bytes(ptr, value, size);
316    }
317}
318
319#[cfg(test)]
320mod tests {
321    use super::*;
322
323    #[test]
324    fn test_string_conversion() {
325        let rust_str = "Hello, World!";
326        let bytes = rust_str.as_bytes();
327
328        let c_str = sigil_string_to_cstring(bytes.as_ptr(), bytes.len());
329        assert!(!c_str.is_null());
330
331        let len = sigil_cstring_len(c_str);
332        assert_eq!(len, rust_str.len());
333
334        sigil_cstring_free(c_str);
335    }
336
337    #[test]
338    fn test_pointer_operations() {
339        let value: i64 = 42;
340        let ptr = &value as *const i64;
341
342        let addr = sigil_ptr_to_int(ptr as *const u8);
343        let recovered_ptr = sigil_ptr_from_int(addr) as *const i64;
344
345        let read_value = sigil_ptr_read_i64(recovered_ptr);
346        assert_eq!(read_value, 42);
347    }
348
349    #[test]
350    fn test_memory_allocation() {
351        let ptr = sigil_alloc(1024);
352        assert!(!ptr.is_null());
353
354        // Write some data
355        sigil_memset(ptr, 0xAB, 1024);
356
357        // Verify
358        unsafe {
359            assert_eq!(*ptr, 0xAB);
360            assert_eq!(*ptr.add(512), 0xAB);
361        }
362
363        sigil_free(ptr, 1024);
364    }
365
366    #[test]
367    fn test_ptr_add() {
368        let array: [u8; 4] = [1, 2, 3, 4];
369        let ptr = array.as_ptr();
370
371        let ptr2 = sigil_ptr_add(ptr, 2);
372        assert_eq!(sigil_ptr_read_u8(ptr2), 3);
373    }
374}