1use std::alloc::{Layout, alloc, dealloc};
24use std::sync::atomic::{AtomicU32, Ordering};
25
26const HK_STRING_U16: u16 = 0;
32
33#[repr(C)]
38struct StringObj {
39 refcount: AtomicU32,
41 kind: u16,
43 _pad_header: u16,
45 data: *const u8,
48 len: u32,
50 _pad_tail: u32,
52}
53
54const _: () = {
56 assert!(std::mem::size_of::<StringObj>() == 24);
57 };
60
61fn layout_for(byte_len: usize) -> Layout {
67 let header_layout = Layout::new::<StringObj>();
69 let data_layout = Layout::from_size_align(byte_len.max(1), 1).unwrap();
70 let (combined, _offset) = header_layout.extend(data_layout).unwrap();
71 combined.pad_to_align()
72}
73
74#[inline]
75unsafe fn as_obj(ptr: *mut u8) -> &'static StringObj {
76 unsafe { &*(ptr as *const StringObj) }
77}
78
79#[inline]
80unsafe fn as_obj_mut(ptr: *mut u8) -> &'static mut StringObj {
81 unsafe { &mut *(ptr as *mut StringObj) }
82}
83
84#[unsafe(no_mangle)]
96pub extern "C" fn jit_v2_string_alloc(data: *const u8, len: u32) -> *mut u8 {
97 let byte_len = len as usize;
98 let layout = layout_for(byte_len);
99
100 unsafe {
101 let ptr = alloc(layout);
102 if ptr.is_null() {
103 return std::ptr::null_mut();
105 }
106
107 let data_dst = ptr.add(std::mem::size_of::<StringObj>());
109
110 if byte_len > 0 && !data.is_null() {
112 std::ptr::copy_nonoverlapping(data, data_dst, byte_len);
113 }
114
115 let obj = as_obj_mut(ptr);
117 std::ptr::write(&raw mut obj.refcount, AtomicU32::new(1));
120 obj.kind = HK_STRING_U16;
121 obj._pad_header = 0;
122 obj.data = data_dst;
123 obj.len = len;
124 obj._pad_tail = 0;
125
126 ptr
127 }
128}
129
130#[unsafe(no_mangle)]
132pub extern "C" fn jit_v2_string_len(str_ptr: *mut u8) -> i64 {
133 if str_ptr.is_null() {
134 return 0;
135 }
136 unsafe { as_obj(str_ptr).len as i64 }
137}
138
139#[unsafe(no_mangle)]
141pub extern "C" fn jit_v2_string_data(str_ptr: *mut u8) -> *const u8 {
142 if str_ptr.is_null() {
143 return std::ptr::null();
144 }
145 unsafe { as_obj(str_ptr).data }
146}
147
148#[unsafe(no_mangle)]
151pub extern "C" fn jit_v2_string_concat(a: *mut u8, b: *mut u8) -> *mut u8 {
152 unsafe {
153 let a_len = if a.is_null() {
154 0usize
155 } else {
156 as_obj(a).len as usize
157 };
158 let b_len = if b.is_null() {
159 0usize
160 } else {
161 as_obj(b).len as usize
162 };
163 let total = a_len + b_len;
164
165 let layout = layout_for(total);
166 let ptr = alloc(layout);
167 if ptr.is_null() {
168 return std::ptr::null_mut();
169 }
170
171 let data_dst = ptr.add(std::mem::size_of::<StringObj>());
172
173 if a_len > 0 {
175 std::ptr::copy_nonoverlapping(as_obj(a).data, data_dst, a_len);
176 }
177 if b_len > 0 {
179 std::ptr::copy_nonoverlapping(as_obj(b).data, data_dst.add(a_len), b_len);
180 }
181
182 let obj = as_obj_mut(ptr);
183 std::ptr::write(&raw mut obj.refcount, AtomicU32::new(1));
184 obj.kind = HK_STRING_U16;
185 obj._pad_header = 0;
186 obj.data = data_dst;
187 obj.len = total as u32;
188 obj._pad_tail = 0;
189
190 ptr
191 }
192}
193
194#[unsafe(no_mangle)]
196pub extern "C" fn jit_v2_string_eq(a: *mut u8, b: *mut u8) -> u8 {
197 if a == b {
198 return 1;
199 }
200 if a.is_null() || b.is_null() {
201 return 0;
202 }
203 unsafe {
204 let obj_a = as_obj(a);
205 let obj_b = as_obj(b);
206 if obj_a.len != obj_b.len {
207 return 0;
208 }
209 let len = obj_a.len as usize;
210 if len == 0 {
211 return 1;
212 }
213 let slice_a = std::slice::from_raw_parts(obj_a.data, len);
214 let slice_b = std::slice::from_raw_parts(obj_b.data, len);
215 if slice_a == slice_b { 1 } else { 0 }
216 }
217}
218
219#[unsafe(no_mangle)]
222pub extern "C" fn jit_v2_string_print(str_ptr: *mut u8) {
223 if str_ptr.is_null() {
224 println!();
225 return;
226 }
227 unsafe {
228 let obj = as_obj(str_ptr);
229 let len = obj.len as usize;
230 let slice = std::slice::from_raw_parts(obj.data, len);
231 let s = std::str::from_utf8(slice).unwrap_or("<invalid utf8>");
233 println!("{}", s);
234 }
235}
236
237#[unsafe(no_mangle)]
239pub extern "C" fn jit_v2_string_retain(str_ptr: *mut u8) {
240 if str_ptr.is_null() {
241 return;
242 }
243 unsafe {
244 as_obj(str_ptr).refcount.fetch_add(1, Ordering::Relaxed);
245 }
246}
247
248#[unsafe(no_mangle)]
251pub extern "C" fn jit_v2_string_release(str_ptr: *mut u8) {
252 if str_ptr.is_null() {
253 return;
254 }
255 unsafe {
256 let obj = as_obj(str_ptr);
257 let prev = obj.refcount.fetch_sub(1, Ordering::Release);
260 if prev == 1 {
261 std::sync::atomic::fence(Ordering::Acquire);
263 let byte_len = obj.len as usize;
264 let layout = layout_for(byte_len);
265 dealloc(str_ptr, layout);
266 }
267 }
268}
269
270#[cfg(test)]
275mod tests {
276 use super::*;
277
278 #[test]
279 fn test_string_obj_layout() {
280 assert_eq!(std::mem::size_of::<StringObj>(), 24);
281
282 let obj = StringObj {
284 refcount: AtomicU32::new(0),
285 kind: 0,
286 _pad_header: 0,
287 data: std::ptr::null(),
288 len: 0,
289 _pad_tail: 0,
290 };
291 let base = &obj as *const _ as usize;
292 assert_eq!(
293 &obj.refcount as *const _ as usize - base,
294 0,
295 "refcount at offset 0"
296 );
297 assert_eq!(
298 &obj.kind as *const _ as usize - base,
299 4,
300 "kind at offset 4"
301 );
302 assert_eq!(
303 &obj.data as *const _ as usize - base,
304 8,
305 "data at offset 8"
306 );
307 assert_eq!(
308 &obj.len as *const _ as usize - base,
309 16,
310 "len at offset 16"
311 );
312 }
313
314 #[test]
315 fn test_alloc_and_len() {
316 let src = b"hello";
317 let ptr = jit_v2_string_alloc(src.as_ptr(), src.len() as u32);
318 assert!(!ptr.is_null());
319
320 assert_eq!(jit_v2_string_len(ptr), 5);
321
322 jit_v2_string_release(ptr);
324 }
325
326 #[test]
327 fn test_alloc_empty() {
328 let ptr = jit_v2_string_alloc(std::ptr::null(), 0);
329 assert!(!ptr.is_null());
330 assert_eq!(jit_v2_string_len(ptr), 0);
331 jit_v2_string_release(ptr);
332 }
333
334 #[test]
335 fn test_data_roundtrip() {
336 let src = b"world";
337 let ptr = jit_v2_string_alloc(src.as_ptr(), src.len() as u32);
338 assert!(!ptr.is_null());
339
340 let data = jit_v2_string_data(ptr);
341 let len = jit_v2_string_len(ptr) as usize;
342 let slice = unsafe { std::slice::from_raw_parts(data, len) };
343 assert_eq!(slice, b"world");
344
345 jit_v2_string_release(ptr);
346 }
347
348 #[test]
349 fn test_concat() {
350 let a = jit_v2_string_alloc(b"foo".as_ptr(), 3);
351 let b = jit_v2_string_alloc(b"bar".as_ptr(), 3);
352 let c = jit_v2_string_concat(a, b);
353 assert!(!c.is_null());
354
355 assert_eq!(jit_v2_string_len(c), 6);
356 let data = jit_v2_string_data(c);
357 let slice = unsafe { std::slice::from_raw_parts(data, 6) };
358 assert_eq!(slice, b"foobar");
359
360 jit_v2_string_release(a);
361 jit_v2_string_release(b);
362 jit_v2_string_release(c);
363 }
364
365 #[test]
366 fn test_concat_with_empty() {
367 let a = jit_v2_string_alloc(b"abc".as_ptr(), 3);
368 let b = jit_v2_string_alloc(std::ptr::null(), 0);
369 let c = jit_v2_string_concat(a, b);
370 assert_eq!(jit_v2_string_len(c), 3);
371
372 let data = jit_v2_string_data(c);
373 let slice = unsafe { std::slice::from_raw_parts(data, 3) };
374 assert_eq!(slice, b"abc");
375
376 jit_v2_string_release(a);
377 jit_v2_string_release(b);
378 jit_v2_string_release(c);
379 }
380
381 #[test]
382 fn test_eq_same_content() {
383 let a = jit_v2_string_alloc(b"test".as_ptr(), 4);
384 let b = jit_v2_string_alloc(b"test".as_ptr(), 4);
385 assert_eq!(jit_v2_string_eq(a, b), 1);
386
387 jit_v2_string_release(a);
388 jit_v2_string_release(b);
389 }
390
391 #[test]
392 fn test_eq_different_content() {
393 let a = jit_v2_string_alloc(b"abc".as_ptr(), 3);
394 let b = jit_v2_string_alloc(b"xyz".as_ptr(), 3);
395 assert_eq!(jit_v2_string_eq(a, b), 0);
396
397 jit_v2_string_release(a);
398 jit_v2_string_release(b);
399 }
400
401 #[test]
402 fn test_eq_different_lengths() {
403 let a = jit_v2_string_alloc(b"ab".as_ptr(), 2);
404 let b = jit_v2_string_alloc(b"abc".as_ptr(), 3);
405 assert_eq!(jit_v2_string_eq(a, b), 0);
406
407 jit_v2_string_release(a);
408 jit_v2_string_release(b);
409 }
410
411 #[test]
412 fn test_eq_same_pointer() {
413 let a = jit_v2_string_alloc(b"dup".as_ptr(), 3);
414 assert_eq!(jit_v2_string_eq(a, a), 1);
415 jit_v2_string_release(a);
416 }
417
418 #[test]
419 fn test_eq_null() {
420 let a = jit_v2_string_alloc(b"x".as_ptr(), 1);
421 assert_eq!(jit_v2_string_eq(a, std::ptr::null_mut()), 0);
422 assert_eq!(jit_v2_string_eq(std::ptr::null_mut(), a), 0);
423 assert_eq!(jit_v2_string_eq(std::ptr::null_mut(), std::ptr::null_mut()), 1);
424 jit_v2_string_release(a);
425 }
426
427 #[test]
428 fn test_retain_release() {
429 let ptr = jit_v2_string_alloc(b"rc".as_ptr(), 2);
430 assert!(!ptr.is_null());
431
432 jit_v2_string_retain(ptr);
434 unsafe {
435 assert_eq!(as_obj(ptr).refcount.load(Ordering::Relaxed), 2);
436 }
437
438 jit_v2_string_release(ptr);
440 unsafe {
441 assert_eq!(as_obj(ptr).refcount.load(Ordering::Relaxed), 1);
442 }
443
444 jit_v2_string_release(ptr);
446 }
448
449 #[test]
450 fn test_null_safety() {
451 assert_eq!(jit_v2_string_len(std::ptr::null_mut()), 0);
453 assert_eq!(jit_v2_string_data(std::ptr::null_mut()), std::ptr::null());
454 jit_v2_string_print(std::ptr::null_mut()); jit_v2_string_retain(std::ptr::null_mut()); jit_v2_string_release(std::ptr::null_mut()); }
458
459 #[test]
460 fn test_concat_null_inputs() {
461 let c = jit_v2_string_concat(std::ptr::null_mut(), std::ptr::null_mut());
463 assert!(!c.is_null());
464 assert_eq!(jit_v2_string_len(c), 0);
465 jit_v2_string_release(c);
466 }
467
468 #[test]
469 fn test_unicode() {
470 let src = "hello 🌍";
471 let bytes = src.as_bytes();
472 let ptr = jit_v2_string_alloc(bytes.as_ptr(), bytes.len() as u32);
473 assert!(!ptr.is_null());
474 assert_eq!(jit_v2_string_len(ptr) as usize, bytes.len());
475
476 let data = jit_v2_string_data(ptr);
477 let slice = unsafe { std::slice::from_raw_parts(data, bytes.len()) };
478 assert_eq!(std::str::from_utf8(slice).unwrap(), src);
479
480 jit_v2_string_release(ptr);
481 }
482}