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(src: *const c_char, dst: *mut u8, dst_len: usize) -> usize {
116    if src.is_null() || dst.is_null() || dst_len == 0 {
117        return 0;
118    }
119
120    unsafe {
121        let c_str = CStr::from_ptr(src);
122        let bytes = c_str.to_bytes();
123        let copy_len = bytes.len().min(dst_len);
124        std::ptr::copy_nonoverlapping(bytes.as_ptr(), dst, copy_len);
125        copy_len
126    }
127}
128
129// ============================================
130// Pointer manipulation helpers
131// ============================================
132
133/// Create a pointer from an integer address.
134#[no_mangle]
135pub extern "C" fn sigil_ptr_from_int(addr: i64) -> *mut u8 {
136    addr as *mut u8
137}
138
139/// Convert a pointer to an integer address.
140#[no_mangle]
141pub extern "C" fn sigil_ptr_to_int(ptr: *const u8) -> i64 {
142    ptr as i64
143}
144
145/// Read a byte from a pointer.
146#[no_mangle]
147pub extern "C" fn sigil_ptr_read_u8(ptr: *const u8) -> u8 {
148    if ptr.is_null() {
149        return 0;
150    }
151    unsafe { *ptr }
152}
153
154/// Write a byte to a pointer.
155#[no_mangle]
156pub extern "C" fn sigil_ptr_write_u8(ptr: *mut u8, value: u8) {
157    if !ptr.is_null() {
158        unsafe {
159            *ptr = value;
160        }
161    }
162}
163
164/// Read a 32-bit integer from a pointer.
165#[no_mangle]
166pub extern "C" fn sigil_ptr_read_i32(ptr: *const i32) -> i32 {
167    if ptr.is_null() {
168        return 0;
169    }
170    unsafe { *ptr }
171}
172
173/// Write a 32-bit integer to a pointer.
174#[no_mangle]
175pub extern "C" fn sigil_ptr_write_i32(ptr: *mut i32, value: i32) {
176    if !ptr.is_null() {
177        unsafe {
178            *ptr = value;
179        }
180    }
181}
182
183/// Read a 64-bit integer from a pointer.
184#[no_mangle]
185pub extern "C" fn sigil_ptr_read_i64(ptr: *const i64) -> i64 {
186    if ptr.is_null() {
187        return 0;
188    }
189    unsafe { *ptr }
190}
191
192/// Write a 64-bit integer to a pointer.
193#[no_mangle]
194pub extern "C" fn sigil_ptr_write_i64(ptr: *mut i64, value: i64) {
195    if !ptr.is_null() {
196        unsafe {
197            *ptr = value;
198        }
199    }
200}
201
202/// Read a double from a pointer.
203#[no_mangle]
204pub extern "C" fn sigil_ptr_read_f64(ptr: *const f64) -> f64 {
205    if ptr.is_null() {
206        return 0.0;
207    }
208    unsafe { *ptr }
209}
210
211/// Write a double to a pointer.
212#[no_mangle]
213pub extern "C" fn sigil_ptr_write_f64(ptr: *mut f64, value: f64) {
214    if !ptr.is_null() {
215        unsafe {
216            *ptr = value;
217        }
218    }
219}
220
221/// Add an offset to a pointer.
222#[no_mangle]
223pub extern "C" fn sigil_ptr_add(ptr: *const u8, offset: i64) -> *const u8 {
224    if ptr.is_null() {
225        return std::ptr::null();
226    }
227    unsafe { ptr.offset(offset as isize) }
228}
229
230/// Check if a pointer is null.
231#[no_mangle]
232pub extern "C" fn sigil_ptr_is_null(ptr: *const u8) -> i64 {
233    if ptr.is_null() {
234        1
235    } else {
236        0
237    }
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}