1use crate::bytecode::{NativeAbiSpec, NativeStructLayoutEntry};
4use libffi::{
5 low,
6 middle::{Arg, Cif, Closure, CodePtr, Type},
7};
8use libloading::Library;
9use shape_runtime::module_exports::RawCallableInvoker;
10use shape_value::{
11 NanTag, ValueWord,
12 heap_value::{HeapValue, NativeLayoutField, NativeScalar, NativeTypeLayout},
13};
14use std::collections::HashMap;
15use std::ffi::{CStr, CString, c_char, c_void};
16use std::sync::Arc;
17
18#[derive(Debug, Clone, PartialEq, Eq)]
19enum CType {
20 I8,
21 U8,
22 I16,
23 U16,
24 I32,
25 I64,
26 U32,
27 U64,
28 Isize,
29 Usize,
30 F32,
31 F64,
32 Bool,
33 CString,
34 NullableCString,
35 CSlice(Box<CType>),
36 CMutSlice(Box<CType>),
37 CView(String),
38 CMut(String),
39 Ptr,
40 Callback(Box<CallbackSignature>),
41 Void,
42}
43
44#[derive(Debug, Clone, PartialEq, Eq)]
45struct CallbackSignature {
46 params: Vec<CType>,
47 ret: CType,
48}
49
50impl CType {
51 fn parse(token: &str) -> Result<Self, String> {
52 let compact = token
53 .chars()
54 .filter(|c| !c.is_whitespace())
55 .collect::<String>();
56 let normalized = compact.to_ascii_lowercase();
57
58 if normalized.starts_with("callback(") && normalized.ends_with(')') {
59 let inner = compact
60 .strip_prefix("callback(")
61 .and_then(|rest| rest.strip_suffix(')'))
62 .ok_or_else(|| format!("invalid callback type syntax '{}'", token))?;
63 let parsed = parse_signature(inner)?;
64 if matches!(
65 parsed.ret,
66 CType::CString | CType::NullableCString | CType::CSlice(_) | CType::CMutSlice(_)
67 ) {
68 return Err("callback return type `cstring`/`cstring?`/`cslice<_>`/`cmut_slice<_>` is not supported".to_string());
69 }
70 return Ok(Self::Callback(Box::new(CallbackSignature {
71 params: parsed.params,
72 ret: parsed.ret,
73 })));
74 }
75
76 if normalized.starts_with("cview<") && normalized.ends_with('>') {
77 let inner = compact
78 .split_once('<')
79 .and_then(|(_, rest)| rest.strip_suffix('>'))
80 .map(str::trim)
81 .ok_or_else(|| format!("invalid cview type syntax '{}'", token))?;
82 if inner.is_empty() {
83 return Err("cview<T> requires a layout type name".to_string());
84 }
85 return Ok(Self::CView(inner.to_string()));
86 }
87
88 if normalized.starts_with("cmut<") && normalized.ends_with('>') {
89 let inner = compact
90 .split_once('<')
91 .and_then(|(_, rest)| rest.strip_suffix('>'))
92 .map(str::trim)
93 .ok_or_else(|| format!("invalid cmut type syntax '{}'", token))?;
94 if inner.is_empty() {
95 return Err("cmut<T> requires a layout type name".to_string());
96 }
97 return Ok(Self::CMut(inner.to_string()));
98 }
99
100 if normalized.starts_with("cslice<") && normalized.ends_with('>') {
101 let inner = compact
102 .split_once('<')
103 .and_then(|(_, rest)| rest.strip_suffix('>'))
104 .map(str::trim)
105 .ok_or_else(|| format!("invalid cslice type syntax '{}'", token))?;
106 let elem = CType::parse(inner)?;
107 if !is_supported_slice_element_type(&elem) {
108 return Err(format!(
109 "cslice<T> does not support element type '{}'",
110 inner
111 ));
112 }
113 return Ok(Self::CSlice(Box::new(elem)));
114 }
115
116 if normalized.starts_with("cmut_slice<") && normalized.ends_with('>') {
117 let inner = compact
118 .split_once('<')
119 .and_then(|(_, rest)| rest.strip_suffix('>'))
120 .map(str::trim)
121 .ok_or_else(|| format!("invalid cmut_slice type syntax '{}'", token))?;
122 let elem = CType::parse(inner)?;
123 if !is_supported_slice_element_type(&elem) {
124 return Err(format!(
125 "cmut_slice<T> does not support element type '{}'",
126 inner
127 ));
128 }
129 return Ok(Self::CMutSlice(Box::new(elem)));
130 }
131
132 match normalized.as_str() {
133 "i8" => Ok(Self::I8),
134 "u8" => Ok(Self::U8),
135 "i16" => Ok(Self::I16),
136 "u16" => Ok(Self::U16),
137 "i32" => Ok(Self::I32),
138 "i64" => Ok(Self::I64),
139 "u32" => Ok(Self::U32),
140 "u64" => Ok(Self::U64),
141 "isize" => Ok(Self::Isize),
142 "usize" => Ok(Self::Usize),
143 "f32" => Ok(Self::F32),
144 "f64" => Ok(Self::F64),
145 "bool" => Ok(Self::Bool),
146 "cstring" => Ok(Self::CString),
147 "cstring?" => Ok(Self::NullableCString),
148 "ptr" => Ok(Self::Ptr),
149 "void" => Ok(Self::Void),
150 other => Err(format!(
151 "unsupported native C type '{}'; supported: i8, u8, i16, u16, i32, i64, u32, u64, isize, usize, f32, f64, bool, cstring, cstring?, cslice<...>, cmut_slice<...>, cview<...>, cmut<...>, ptr, callback(...), void",
152 other
153 )),
154 }
155 }
156}
157
158fn is_supported_slice_element_type(ctype: &CType) -> bool {
159 matches!(
160 ctype,
161 CType::I8
162 | CType::U8
163 | CType::I16
164 | CType::U16
165 | CType::I32
166 | CType::I64
167 | CType::U32
168 | CType::U64
169 | CType::Isize
170 | CType::Usize
171 | CType::F32
172 | CType::F64
173 | CType::Bool
174 | CType::CString
175 | CType::NullableCString
176 | CType::Ptr
177 )
178}
179
180#[derive(Debug, Clone)]
181struct CSignature {
182 params: Vec<CType>,
183 ret: CType,
184}
185
186fn build_native_layout_map(
187 entries: &[NativeStructLayoutEntry],
188) -> HashMap<String, Arc<NativeTypeLayout>> {
189 let mut layouts = HashMap::with_capacity(entries.len());
190 for entry in entries {
191 let mapped = NativeTypeLayout {
192 name: entry.name.clone(),
193 abi: entry.abi.clone(),
194 size: entry.size,
195 align: entry.align,
196 fields: entry
197 .fields
198 .iter()
199 .map(|field| NativeLayoutField {
200 name: field.name.clone(),
201 c_type: field.c_type.clone(),
202 offset: field.offset,
203 size: field.size,
204 align: field.align,
205 })
206 .collect(),
207 };
208 layouts.insert(entry.name.clone(), Arc::new(mapped));
209 }
210 layouts
211}
212
213fn collect_layout_references<'a>(ctype: &'a CType, out: &mut Vec<&'a str>) {
214 match ctype {
215 CType::CView(name) | CType::CMut(name) => out.push(name.as_str()),
216 CType::CSlice(elem) | CType::CMutSlice(elem) => collect_layout_references(elem, out),
217 CType::Callback(sig) => {
218 for param in &sig.params {
219 collect_layout_references(param, out);
220 }
221 collect_layout_references(&sig.ret, out);
222 }
223 _ => {}
224 }
225}
226
227fn validate_layout_references(
228 signature: &CSignature,
229 layouts: &HashMap<String, Arc<NativeTypeLayout>>,
230) -> Result<(), String> {
231 let mut refs = Vec::new();
232 for param in &signature.params {
233 collect_layout_references(param, &mut refs);
234 }
235 collect_layout_references(&signature.ret, &mut refs);
236
237 for layout_name in refs {
238 if !layouts.contains_key(layout_name) {
239 return Err(format!(
240 "native signature references unknown `type C` layout '{}'",
241 layout_name
242 ));
243 }
244 }
245 Ok(())
246}
247
248pub struct NativeLinkedFunction {
250 signature: CSignature,
251 cif: Cif,
252 code_ptr: CodePtr,
253 layouts: HashMap<String, Arc<NativeTypeLayout>>,
254 _library: Arc<Library>,
256}
257
258pub fn link_native_function(
259 spec: &NativeAbiSpec,
260 native_layouts: &[NativeStructLayoutEntry],
261 library_cache: &mut HashMap<String, Arc<Library>>,
262) -> Result<NativeLinkedFunction, String> {
263 if spec.abi != "C" {
264 return Err(format!(
265 "unsupported native ABI '{}'; only \"C\" is currently supported",
266 spec.abi
267 ));
268 }
269
270 let signature = parse_signature(&spec.signature)?;
271 let layouts = build_native_layout_map(native_layouts);
272 validate_layout_references(&signature, &layouts)?;
273 let library = if let Some(existing) = library_cache.get(&spec.library) {
274 existing.clone()
275 } else {
276 let opened = unsafe { Library::new(&spec.library) }
277 .map_err(|e| format!("failed to open native library '{}': {}", spec.library, e))?;
278 let shared = Arc::new(opened);
279 library_cache.insert(spec.library.clone(), shared.clone());
280 shared
281 };
282
283 let mut symbol_bytes = spec.symbol.as_bytes().to_vec();
284 if !symbol_bytes.ends_with(&[0]) {
285 symbol_bytes.push(0);
286 }
287 let symbol_ptr = unsafe { library.get::<*const c_void>(&symbol_bytes) }
288 .map_err(|e| {
289 format!(
290 "failed to resolve native symbol '{}' from '{}': {}",
291 spec.symbol, spec.library, e
292 )
293 })
294 .map(|sym| *sym)?;
295
296 let arg_types: Vec<Type> = signature.params.iter().map(c_type_to_ffi_type).collect();
297 let cif = Cif::new(arg_types, c_type_to_ffi_type(&signature.ret));
298 let code_ptr = CodePtr::from_ptr(symbol_ptr as *mut c_void);
299
300 Ok(NativeLinkedFunction {
301 signature,
302 cif,
303 code_ptr,
304 layouts,
305 _library: library,
306 })
307}
308
309#[derive(Debug, Clone)]
310enum MutableArgWritebackPlan {
311 Slice {
312 arg_index: usize,
313 target_slot: usize,
314 elem_type: CType,
315 },
316}
317
318fn resolve_arg_value_for_native_call(
319 value: &ValueWord,
320 arg_idx: usize,
321 vm_stack: Option<&[ValueWord]>,
322) -> Result<(ValueWord, Option<usize>), String> {
323 if let Some(slot) = value.as_ref_slot() {
324 let stack = vm_stack.ok_or_else(|| {
325 format!(
326 "native call arg#{arg_idx} received a reference argument but no VM stack context is available"
327 )
328 })?;
329 let source = stack.get(slot).ok_or_else(|| {
330 format!(
331 "native call arg#{arg_idx} references invalid stack slot {} (stack len {})",
332 slot,
333 stack.len()
334 )
335 })?;
336 Ok((source.clone(), Some(slot)))
337 } else {
338 Ok((value.clone(), None))
339 }
340}
341
342fn build_mutable_writeback_plan(
343 ctype: &CType,
344 arg_idx: usize,
345 source_ref_slot: Option<usize>,
346) -> Result<Option<MutableArgWritebackPlan>, String> {
347 match ctype {
348 CType::CMutSlice(elem) => {
349 let target_slot = source_ref_slot.ok_or_else(|| {
350 format!(
351 "native call arg#{arg_idx} for {} requires a mutable reference argument",
352 c_type_label(ctype)
353 )
354 })?;
355 Ok(Some(MutableArgWritebackPlan::Slice {
356 arg_index: arg_idx,
357 target_slot,
358 elem_type: elem.as_ref().clone(),
359 }))
360 }
361 _ => Ok(None),
362 }
363}
364
365fn apply_mutable_writebacks(
366 stack: &mut [ValueWord],
367 prepared_args: &[PreparedArg],
368 writebacks: &[MutableArgWritebackPlan],
369) -> Result<(), String> {
370 for plan in writebacks {
371 match plan {
372 MutableArgWritebackPlan::Slice {
373 arg_index,
374 target_slot,
375 elem_type,
376 } => {
377 if *target_slot >= stack.len() {
378 return Err(format!(
379 "native call writeback target slot {} out of bounds (stack len {})",
380 target_slot,
381 stack.len()
382 ));
383 }
384 let prepared = prepared_args.get(*arg_index).ok_or_else(|| {
385 format!(
386 "native call writeback references missing prepared arg at index {}",
387 arg_index
388 )
389 })?;
390 let PreparedArg::SliceDesc { desc, .. } = prepared else {
391 return Err(format!(
392 "native call writeback expected slice argument at index {}",
393 arg_index
394 ));
395 };
396 let decoded = decode_slice_elements(
397 *desc,
398 elem_type,
399 &format!("native call arg#{arg_index} writeback"),
400 )?;
401 stack[*target_slot] = ValueWord::from_array(Arc::new(decoded));
402 }
403 }
404 }
405 Ok(())
406}
407
408pub fn invoke_linked_function(
409 linked: &NativeLinkedFunction,
410 args: &[ValueWord],
411 raw_invoker: Option<RawCallableInvoker>,
412 vm_stack: Option<&mut [ValueWord]>,
413) -> Result<ValueWord, String> {
414 if linked.signature.params.len() != args.len() {
415 return Err(format!(
416 "native ABI argument count mismatch: signature expects {}, got {}",
417 linked.signature.params.len(),
418 args.len()
419 ));
420 }
421
422 let mut owned_cstrings: Vec<CString> = Vec::new();
423 let mut owned_callbacks: Vec<OwnedCallScopedCallback> = Vec::new();
424 let mut prepared_args = Vec::with_capacity(linked.signature.params.len());
425 let mut pending_writebacks = Vec::new();
426
427 let stack_view = vm_stack.as_ref().map(|stack| &stack[..]);
428 for (idx, (ctype, value)) in linked.signature.params.iter().zip(args.iter()).enumerate() {
429 let (resolved_value, source_ref_slot) =
430 resolve_arg_value_for_native_call(value, idx, stack_view)?;
431 if let Some(plan) = build_mutable_writeback_plan(ctype, idx, source_ref_slot)? {
432 pending_writebacks.push(plan);
433 }
434 prepared_args.push(encode_arg(
435 &resolved_value,
436 ctype,
437 idx,
438 &mut owned_cstrings,
439 &mut owned_callbacks,
440 &linked.layouts,
441 raw_invoker,
442 )?);
443 }
444
445 let ffi_args: Vec<Arg> = prepared_args.iter().map(PreparedArg::as_arg).collect();
446 let result = match &linked.signature.ret {
447 CType::Void => {
448 unsafe { linked.cif.call::<()>(linked.code_ptr, &ffi_args) };
449 ValueWord::unit()
450 }
451 CType::I8 => {
452 let out = unsafe { linked.cif.call::<i8>(linked.code_ptr, &ffi_args) };
453 ValueWord::from_native_i8(out)
454 }
455 CType::U8 => {
456 let out = unsafe { linked.cif.call::<u8>(linked.code_ptr, &ffi_args) };
457 ValueWord::from_native_u8(out)
458 }
459 CType::I16 => {
460 let out = unsafe { linked.cif.call::<i16>(linked.code_ptr, &ffi_args) };
461 ValueWord::from_native_i16(out)
462 }
463 CType::U16 => {
464 let out = unsafe { linked.cif.call::<u16>(linked.code_ptr, &ffi_args) };
465 ValueWord::from_native_u16(out)
466 }
467 CType::I32 => {
468 let out = unsafe { linked.cif.call::<i32>(linked.code_ptr, &ffi_args) };
469 ValueWord::from_native_i32(out)
470 }
471 CType::I64 => {
472 let out = unsafe { linked.cif.call::<i64>(linked.code_ptr, &ffi_args) };
473 ValueWord::from_native_scalar(NativeScalar::I64(out))
474 }
475 CType::U32 => {
476 let out = unsafe { linked.cif.call::<u32>(linked.code_ptr, &ffi_args) };
477 ValueWord::from_native_u32(out)
478 }
479 CType::U64 => {
480 let out = unsafe { linked.cif.call::<u64>(linked.code_ptr, &ffi_args) };
481 ValueWord::from_native_u64(out)
482 }
483 CType::Isize => {
484 let out = unsafe { linked.cif.call::<isize>(linked.code_ptr, &ffi_args) };
485 ValueWord::from_native_isize(out)
486 }
487 CType::Usize => {
488 let out = unsafe { linked.cif.call::<usize>(linked.code_ptr, &ffi_args) };
489 ValueWord::from_native_usize(out)
490 }
491 CType::Ptr | CType::Callback(_) => {
492 let out = unsafe { linked.cif.call::<usize>(linked.code_ptr, &ffi_args) };
493 ValueWord::from_native_ptr(out)
494 }
495 CType::F32 => {
496 let out = unsafe { linked.cif.call::<f32>(linked.code_ptr, &ffi_args) };
497 ValueWord::from_native_f32(out)
498 }
499 CType::F64 => {
500 let out = unsafe { linked.cif.call::<f64>(linked.code_ptr, &ffi_args) };
501 ValueWord::from_f64(out)
502 }
503 CType::Bool => {
504 let out = unsafe { linked.cif.call::<u8>(linked.code_ptr, &ffi_args) };
505 ValueWord::from_bool(out != 0)
506 }
507 CType::CString => {
508 let out = unsafe { linked.cif.call::<*const c_char>(linked.code_ptr, &ffi_args) };
509 if out.is_null() {
510 return Err("native call returned null cstring pointer".to_string());
511 }
512 let s = unsafe { CStr::from_ptr(out) }.to_string_lossy().to_string();
513 ValueWord::from_string(Arc::new(s))
514 }
515 CType::NullableCString => {
516 let out = unsafe { linked.cif.call::<*const c_char>(linked.code_ptr, &ffi_args) };
517 if out.is_null() {
518 ValueWord::none()
519 } else {
520 let s = unsafe { CStr::from_ptr(out) }.to_string_lossy().to_string();
521 ValueWord::from_some(ValueWord::from_string(Arc::new(s)))
522 }
523 }
524 CType::CSlice(elem) | CType::CMutSlice(elem) => {
525 let out = unsafe { linked.cif.call::<CSliceAbi>(linked.code_ptr, &ffi_args) };
526 let values = decode_slice_elements(out, elem, "native call return")?;
527 ValueWord::from_array(Arc::new(values))
528 }
529 CType::CView(layout_name) => {
530 let out = unsafe { linked.cif.call::<usize>(linked.code_ptr, &ffi_args) };
531 if out == 0 {
532 return Err(format!(
533 "native call returned null pointer for cview<{}>",
534 layout_name
535 ));
536 }
537 let layout = linked.layouts.get(layout_name).ok_or_else(|| {
538 format!(
539 "missing native layout '{}' required by cview return",
540 layout_name
541 )
542 })?;
543 ValueWord::from_c_view(out, layout.clone())
544 }
545 CType::CMut(layout_name) => {
546 let out = unsafe { linked.cif.call::<usize>(linked.code_ptr, &ffi_args) };
547 if out == 0 {
548 return Err(format!(
549 "native call returned null pointer for cmut<{}>",
550 layout_name
551 ));
552 }
553 let layout = linked.layouts.get(layout_name).ok_or_else(|| {
554 format!(
555 "missing native layout '{}' required by cmut return",
556 layout_name
557 )
558 })?;
559 ValueWord::from_c_mut(out, layout.clone())
560 }
561 };
562
563 if !pending_writebacks.is_empty() {
564 let Some(stack) = vm_stack else {
565 return Err(
566 "native call expected VM stack context for mutable argument writeback".to_string(),
567 );
568 };
569 apply_mutable_writebacks(stack, &prepared_args, &pending_writebacks)?;
570 }
571
572 drop(owned_callbacks);
573 drop(owned_cstrings);
574
575 Ok(result)
576}
577
578fn parse_signature(signature: &str) -> Result<CSignature, String> {
579 let mut src = signature.trim();
580 if let Some(rest) = src.strip_prefix("fn") {
581 src = rest.trim_start();
582 }
583
584 let open = src.find('(').ok_or_else(|| {
585 format!(
586 "invalid native signature '{}': expected format `fn(<args>) -> <ret>`",
587 signature
588 )
589 })?;
590 let close = find_matching_paren(src, open).ok_or_else(|| {
591 format!(
592 "invalid native signature '{}': expected closing ')' in argument list",
593 signature
594 )
595 })?;
596
597 let params_src = src[open + 1..close].trim();
598 let tail = src[close + 1..].trim();
599 let ret_src = tail.strip_prefix("->").ok_or_else(|| {
600 format!(
601 "invalid native signature '{}': expected `-> <ret>` return segment",
602 signature
603 )
604 })?;
605 let ret = CType::parse(ret_src.trim())?;
606
607 let params = if params_src.is_empty() || params_src.eq_ignore_ascii_case("void") {
608 Vec::new()
609 } else {
610 split_top_level(params_src, ',')
611 .into_iter()
612 .map(|token| CType::parse(token.trim()))
613 .collect::<Result<Vec<_>, _>>()?
614 };
615
616 if params.iter().any(|ty| matches!(ty, CType::Void)) {
617 return Err(
618 "invalid native signature: `void` is only valid as return type or empty parameter list"
619 .to_string(),
620 );
621 }
622
623 Ok(CSignature { params, ret })
624}
625
626fn find_matching_paren(src: &str, open_idx: usize) -> Option<usize> {
627 let bytes = src.as_bytes();
628 if bytes.get(open_idx).copied()? != b'(' {
629 return None;
630 }
631 let mut depth = 0usize;
632 for (idx, ch) in src.char_indices().skip(open_idx) {
633 match ch {
634 '(' => depth += 1,
635 ')' => {
636 depth = depth.saturating_sub(1);
637 if depth == 0 {
638 return Some(idx);
639 }
640 }
641 _ => {}
642 }
643 }
644 None
645}
646
647fn split_top_level(src: &str, delimiter: char) -> Vec<String> {
648 let mut out = Vec::new();
649 let mut start = 0usize;
650 let mut depth_paren = 0usize;
651 for (idx, ch) in src.char_indices() {
652 match ch {
653 '(' => depth_paren += 1,
654 ')' => depth_paren = depth_paren.saturating_sub(1),
655 _ => {}
656 }
657 if ch == delimiter && depth_paren == 0 {
658 out.push(src[start..idx].trim().to_string());
659 start = idx + ch.len_utf8();
660 }
661 }
662 let tail = src[start..].trim();
663 if !tail.is_empty() {
664 out.push(tail.to_string());
665 }
666 out
667}
668
669fn value_to_int_i64(value: &ValueWord, label: &str) -> Result<i64, String> {
670 if let Some(v) = value.as_i64() {
671 return Ok(v);
672 }
673 if let Some(v) = value.as_bool() {
674 return Ok(if v { 1 } else { 0 });
675 }
676 Err(format!(
677 "native call {} expects an exact integer value, got {}",
678 label, value
679 ))
680}
681
682fn value_to_f64(value: &ValueWord, label: &str) -> Result<f64, String> {
683 if let Some(v) = value.as_number_strict() {
684 return Ok(v);
685 }
686 if matches!(value.tag(), NanTag::I48) {
689 return Ok(value.as_i64().unwrap_or(0) as f64);
690 }
691 Err(format!(
692 "native call {} expects a floating-point compatible value, got {}",
693 label, value
694 ))
695}
696
697fn value_to_u64(value: &ValueWord, label: &str) -> Result<u64, String> {
698 if let Some(scalar) = value.as_native_scalar() {
699 return match scalar {
700 NativeScalar::U8(v) => Ok(v as u64),
701 NativeScalar::U16(v) => Ok(v as u64),
702 NativeScalar::U32(v) => Ok(v as u64),
703 NativeScalar::U64(v) => Ok(v),
704 NativeScalar::Usize(v) => Ok(v as u64),
705 NativeScalar::Ptr(v) => Ok(v as u64),
706 NativeScalar::I8(v) if v >= 0 => Ok(v as u64),
707 NativeScalar::I16(v) if v >= 0 => Ok(v as u64),
708 NativeScalar::I32(v) if v >= 0 => Ok(v as u64),
709 NativeScalar::I64(v) if v >= 0 => Ok(v as u64),
710 NativeScalar::Isize(v) if v >= 0 => Ok(v as u64),
711 _ => Err(format!(
712 "native call {} expects a non-negative integer value, got {}",
713 label, value
714 )),
715 };
716 }
717
718 if let Some(v) = value.as_i64() {
719 if v >= 0 {
720 return Ok(v as u64);
721 }
722 }
723
724 if let Some(v) = value.as_bool() {
725 return Ok(if v { 1 } else { 0 });
726 }
727
728 Err(format!(
729 "native call {} expects a non-negative integer value, got {}",
730 label, value
731 ))
732}
733
734fn value_to_usize(value: &ValueWord, label: &str) -> Result<usize, String> {
735 let v = value_to_u64(value, label)?;
736 usize::try_from(v).map_err(|_| {
737 format!(
738 "native call {} value {} does not fit in usize on this platform",
739 label, v
740 )
741 })
742}
743
744fn is_shape_callable(value: &ValueWord) -> bool {
745 value.as_function().is_some()
746 || value.as_module_function().is_some()
747 || matches!(
748 value.as_heap_ref(),
749 Some(
750 HeapValue::Closure { .. }
751 | HeapValue::HostClosure(_)
752 | HeapValue::FunctionRef { .. }
753 )
754 )
755}
756
757#[repr(C)]
758#[derive(Debug, Clone, Copy)]
759struct CSliceAbi {
760 data: *mut c_void,
761 len: usize,
762}
763
764#[derive(Debug, Clone)]
765enum OwnedSliceBuffer {
766 I8(Vec<i8>),
767 U8(Vec<u8>),
768 I16(Vec<i16>),
769 U16(Vec<u16>),
770 I32(Vec<i32>),
771 I64(Vec<i64>),
772 U32(Vec<u32>),
773 U64(Vec<u64>),
774 Isize(Vec<isize>),
775 Usize(Vec<usize>),
776 F32(Vec<f32>),
777 F64(Vec<f64>),
778 Bool(Vec<u8>),
779 Ptr(Vec<*mut c_void>),
780 CString {
781 _strings: Vec<CString>,
782 ptrs: Vec<*const c_char>,
783 },
784 NullableCString {
785 _strings: Vec<CString>,
786 ptrs: Vec<*const c_char>,
787 },
788}
789
790impl OwnedSliceBuffer {
791 fn len(&self) -> usize {
792 match self {
793 Self::I8(v) => v.len(),
794 Self::U8(v) => v.len(),
795 Self::I16(v) => v.len(),
796 Self::U16(v) => v.len(),
797 Self::I32(v) => v.len(),
798 Self::I64(v) => v.len(),
799 Self::U32(v) => v.len(),
800 Self::U64(v) => v.len(),
801 Self::Isize(v) => v.len(),
802 Self::Usize(v) => v.len(),
803 Self::F32(v) => v.len(),
804 Self::F64(v) => v.len(),
805 Self::Bool(v) => v.len(),
806 Self::Ptr(v) => v.len(),
807 Self::CString { ptrs, .. } => ptrs.len(),
808 Self::NullableCString { ptrs, .. } => ptrs.len(),
809 }
810 }
811
812 fn data_ptr(&self) -> *mut c_void {
813 if self.len() == 0 {
814 return std::ptr::null_mut();
815 }
816 match self {
817 Self::I8(v) => v.as_ptr() as *mut c_void,
818 Self::U8(v) => v.as_ptr() as *mut c_void,
819 Self::I16(v) => v.as_ptr() as *mut c_void,
820 Self::U16(v) => v.as_ptr() as *mut c_void,
821 Self::I32(v) => v.as_ptr() as *mut c_void,
822 Self::I64(v) => v.as_ptr() as *mut c_void,
823 Self::U32(v) => v.as_ptr() as *mut c_void,
824 Self::U64(v) => v.as_ptr() as *mut c_void,
825 Self::Isize(v) => v.as_ptr() as *mut c_void,
826 Self::Usize(v) => v.as_ptr() as *mut c_void,
827 Self::F32(v) => v.as_ptr() as *mut c_void,
828 Self::F64(v) => v.as_ptr() as *mut c_void,
829 Self::Bool(v) => v.as_ptr() as *mut c_void,
830 Self::Ptr(v) => v.as_ptr() as *mut c_void,
831 Self::CString { ptrs, .. } => ptrs.as_ptr() as *mut c_void,
832 Self::NullableCString { ptrs, .. } => ptrs.as_ptr() as *mut c_void,
833 }
834 }
835}
836
837fn c_slice_ffi_type() -> Type {
838 Type::structure(vec![Type::pointer(), Type::usize()])
839}
840
841fn c_type_to_ffi_type(ctype: &CType) -> Type {
842 match ctype {
843 CType::I8 => Type::i8(),
844 CType::U8 => Type::u8(),
845 CType::I16 => Type::i16(),
846 CType::U16 => Type::u16(),
847 CType::I32 => Type::i32(),
848 CType::I64 => Type::i64(),
849 CType::U32 => Type::u32(),
850 CType::U64 => Type::u64(),
851 CType::Isize => Type::isize(),
852 CType::Usize => Type::usize(),
853 CType::F32 => Type::f32(),
854 CType::F64 => Type::f64(),
855 CType::Bool => Type::u8(),
856 CType::CSlice(_) | CType::CMutSlice(_) => c_slice_ffi_type(),
857 CType::CString
858 | CType::NullableCString
859 | CType::CView(_)
860 | CType::CMut(_)
861 | CType::Ptr
862 | CType::Callback(_) => Type::pointer(),
863 CType::Void => Type::void(),
864 }
865}
866
867#[derive(Debug, Clone)]
868enum PreparedArg {
869 I8(i8),
870 U8(u8),
871 I16(i16),
872 U16(u16),
873 I32(i32),
874 I64(i64),
875 U32(u32),
876 U64(u64),
877 Isize(isize),
878 Usize(usize),
879 F32(f32),
880 F64(f64),
881 Bool(u8),
882 Ptr(*mut c_void),
883 SliceDesc {
884 desc: CSliceAbi,
885 _owned: OwnedSliceBuffer,
886 },
887}
888
889impl PreparedArg {
890 fn as_arg(&self) -> Arg {
891 match self {
892 Self::I8(v) => Arg::new(v),
893 Self::U8(v) => Arg::new(v),
894 Self::I16(v) => Arg::new(v),
895 Self::U16(v) => Arg::new(v),
896 Self::I32(v) => Arg::new(v),
897 Self::I64(v) => Arg::new(v),
898 Self::U32(v) => Arg::new(v),
899 Self::U64(v) => Arg::new(v),
900 Self::Isize(v) => Arg::new(v),
901 Self::Usize(v) => Arg::new(v),
902 Self::F32(v) => Arg::new(v),
903 Self::F64(v) => Arg::new(v),
904 Self::Bool(v) => Arg::new(v),
905 Self::Ptr(v) => Arg::new(v),
906 Self::SliceDesc { desc, .. } => Arg::new(desc),
907 }
908 }
909}
910
911struct CallbackUserData {
912 signature: CallbackSignature,
913 callable: ValueWord,
914 raw_invoker: Option<RawCallableInvoker>,
915}
916
917#[derive(Debug)]
918struct OwnedCallScopedCallback {
919 closure: Option<Closure<'static>>,
920 userdata_ptr: *mut CallbackUserData,
921}
922
923impl OwnedCallScopedCallback {
924 fn code_ptr_address(&self) -> usize {
925 let Some(closure) = self.closure.as_ref() else {
926 return 0;
927 };
928 (*closure.code_ptr()) as *const () as usize
929 }
930}
931
932impl Drop for OwnedCallScopedCallback {
933 fn drop(&mut self) {
934 self.closure.take();
935 if !self.userdata_ptr.is_null() {
936 unsafe { drop(Box::from_raw(self.userdata_ptr)) };
937 self.userdata_ptr = std::ptr::null_mut();
938 }
939 }
940}
941
942unsafe fn decode_callback_arg(
943 arg_ptr: *const c_void,
944 ctype: &CType,
945 idx: usize,
946) -> Result<ValueWord, String> {
947 if arg_ptr.is_null() {
948 return Err(format!("callback arg#{idx} pointer is null"));
949 }
950 match ctype {
951 CType::I8 => Ok(ValueWord::from_native_i8(unsafe {
952 *(arg_ptr as *const i8)
953 })),
954 CType::U8 => Ok(ValueWord::from_native_u8(unsafe {
955 *(arg_ptr as *const u8)
956 })),
957 CType::I16 => Ok(ValueWord::from_native_i16(unsafe {
958 *(arg_ptr as *const i16)
959 })),
960 CType::U16 => Ok(ValueWord::from_native_u16(unsafe {
961 *(arg_ptr as *const u16)
962 })),
963 CType::I32 => Ok(ValueWord::from_native_i32(unsafe {
964 *(arg_ptr as *const i32)
965 })),
966 CType::I64 => Ok(ValueWord::from_native_scalar(NativeScalar::I64(unsafe {
967 *(arg_ptr as *const i64)
968 }))),
969 CType::U32 => Ok(ValueWord::from_native_u32(unsafe {
970 *(arg_ptr as *const u32)
971 })),
972 CType::U64 => Ok(ValueWord::from_native_u64(unsafe {
973 *(arg_ptr as *const u64)
974 })),
975 CType::Isize => Ok(ValueWord::from_native_isize(unsafe {
976 *(arg_ptr as *const isize)
977 })),
978 CType::CSlice(elem) | CType::CMutSlice(elem) => {
979 let slice = unsafe { *(arg_ptr as *const CSliceAbi) };
980 let values = decode_slice_elements(slice, elem, &format!("callback arg#{idx}"))?;
981 Ok(ValueWord::from_array(Arc::new(values)))
982 }
983 CType::Usize | CType::Ptr | CType::Callback(_) | CType::CView(_) | CType::CMut(_) => {
984 let raw = unsafe { *(arg_ptr as *const usize) };
985 if matches!(
986 ctype,
987 CType::Ptr | CType::Callback(_) | CType::CView(_) | CType::CMut(_)
988 ) {
989 Ok(ValueWord::from_native_ptr(raw))
990 } else {
991 Ok(ValueWord::from_native_usize(raw))
992 }
993 }
994 CType::F32 => Ok(ValueWord::from_native_f32(unsafe {
995 *(arg_ptr as *const f32)
996 })),
997 CType::F64 => Ok(ValueWord::from_f64(unsafe { *(arg_ptr as *const f64) })),
998 CType::Bool => Ok(ValueWord::from_bool(
999 unsafe { *(arg_ptr as *const u8) } != 0,
1000 )),
1001 CType::CString => {
1002 let s_ptr = unsafe { *(arg_ptr as *const *const c_char) };
1003 if s_ptr.is_null() {
1004 return Err(format!(
1005 "callback arg#{idx} returned null for non-null cstring"
1006 ));
1007 }
1008 let s = unsafe { CStr::from_ptr(s_ptr) }
1009 .to_string_lossy()
1010 .to_string();
1011 Ok(ValueWord::from_string(Arc::new(s)))
1012 }
1013 CType::NullableCString => {
1014 let s_ptr = unsafe { *(arg_ptr as *const *const c_char) };
1015 if s_ptr.is_null() {
1016 Ok(ValueWord::none())
1017 } else {
1018 let s = unsafe { CStr::from_ptr(s_ptr) }
1019 .to_string_lossy()
1020 .to_string();
1021 Ok(ValueWord::from_some(ValueWord::from_string(Arc::new(s))))
1022 }
1023 }
1024 CType::Void => Ok(ValueWord::unit()),
1025 }
1026}
1027
1028unsafe fn decode_callback_args(
1029 args: *const *const c_void,
1030 signature: &CallbackSignature,
1031) -> Result<Vec<ValueWord>, String> {
1032 if args.is_null() && !signature.params.is_empty() {
1033 return Err("callback args pointer is null".to_string());
1034 }
1035
1036 let mut out = Vec::with_capacity(signature.params.len());
1037 for (idx, ctype) in signature.params.iter().enumerate() {
1038 let value_ptr = unsafe { *args.add(idx) };
1039 out.push(unsafe { decode_callback_arg(value_ptr, ctype, idx)? });
1040 }
1041 Ok(out)
1042}
1043
1044fn invoke_callback_userdata(
1045 userdata: &CallbackUserData,
1046 args_ptr: *const *const c_void,
1047) -> Result<ValueWord, String> {
1048 let decoded = unsafe { decode_callback_args(args_ptr, &userdata.signature)? };
1049
1050 if let Some(host_callable) = userdata.callable.as_host_closure() {
1051 return host_callable.call(&decoded);
1052 }
1053
1054 let invoker = userdata.raw_invoker.as_ref().ok_or_else(|| {
1055 "native callback has no callable invoker — callback argument requires VM call context"
1056 .to_string()
1057 })?;
1058 unsafe { invoker.call(&userdata.callable, &decoded) }
1059}
1060
1061fn emit_callback_error(message: &str) {
1062 eprintln!("native callback error: {message}");
1063}
1064
1065unsafe extern "C" fn callback_void(
1066 _cif: &low::ffi_cif,
1067 _result: &mut (),
1068 args: *const *const c_void,
1069 userdata: &CallbackUserData,
1070) {
1071 if let Err(err) = invoke_callback_userdata(userdata, args) {
1072 emit_callback_error(&err);
1073 }
1074}
1075
1076unsafe extern "C" fn callback_i8(
1077 _cif: &low::ffi_cif,
1078 result: &mut i8,
1079 args: *const *const c_void,
1080 userdata: &CallbackUserData,
1081) {
1082 match invoke_callback_userdata(userdata, args)
1083 .and_then(|v| value_to_int_i64(&v, "callback return"))
1084 .and_then(|v| {
1085 if v < i8::MIN as i64 || v > i8::MAX as i64 {
1086 Err("callback return out of range for i8".to_string())
1087 } else {
1088 Ok(v as i8)
1089 }
1090 }) {
1091 Ok(v) => *result = v,
1092 Err(err) => {
1093 *result = 0;
1094 emit_callback_error(&err);
1095 }
1096 }
1097}
1098
1099unsafe extern "C" fn callback_u8(
1100 _cif: &low::ffi_cif,
1101 result: &mut u8,
1102 args: *const *const c_void,
1103 userdata: &CallbackUserData,
1104) {
1105 match invoke_callback_userdata(userdata, args)
1106 .and_then(|v| value_to_int_i64(&v, "callback return"))
1107 .and_then(|v| {
1108 if !(0..=u8::MAX as i64).contains(&v) {
1109 Err("callback return out of range for u8".to_string())
1110 } else {
1111 Ok(v as u8)
1112 }
1113 }) {
1114 Ok(v) => *result = v,
1115 Err(err) => {
1116 *result = 0;
1117 emit_callback_error(&err);
1118 }
1119 }
1120}
1121
1122unsafe extern "C" fn callback_i16(
1123 _cif: &low::ffi_cif,
1124 result: &mut i16,
1125 args: *const *const c_void,
1126 userdata: &CallbackUserData,
1127) {
1128 match invoke_callback_userdata(userdata, args)
1129 .and_then(|v| value_to_int_i64(&v, "callback return"))
1130 .and_then(|v| {
1131 if v < i16::MIN as i64 || v > i16::MAX as i64 {
1132 Err("callback return out of range for i16".to_string())
1133 } else {
1134 Ok(v as i16)
1135 }
1136 }) {
1137 Ok(v) => *result = v,
1138 Err(err) => {
1139 *result = 0;
1140 emit_callback_error(&err);
1141 }
1142 }
1143}
1144
1145unsafe extern "C" fn callback_u16(
1146 _cif: &low::ffi_cif,
1147 result: &mut u16,
1148 args: *const *const c_void,
1149 userdata: &CallbackUserData,
1150) {
1151 match invoke_callback_userdata(userdata, args)
1152 .and_then(|v| value_to_int_i64(&v, "callback return"))
1153 .and_then(|v| {
1154 if !(0..=u16::MAX as i64).contains(&v) {
1155 Err("callback return out of range for u16".to_string())
1156 } else {
1157 Ok(v as u16)
1158 }
1159 }) {
1160 Ok(v) => *result = v,
1161 Err(err) => {
1162 *result = 0;
1163 emit_callback_error(&err);
1164 }
1165 }
1166}
1167
1168unsafe extern "C" fn callback_i32(
1169 _cif: &low::ffi_cif,
1170 result: &mut i32,
1171 args: *const *const c_void,
1172 userdata: &CallbackUserData,
1173) {
1174 match invoke_callback_userdata(userdata, args)
1175 .and_then(|v| value_to_int_i64(&v, "callback return"))
1176 .and_then(|v| {
1177 if v < i32::MIN as i64 || v > i32::MAX as i64 {
1178 Err("callback return out of range for i32".to_string())
1179 } else {
1180 Ok(v as i32)
1181 }
1182 }) {
1183 Ok(v) => *result = v,
1184 Err(err) => {
1185 *result = 0;
1186 emit_callback_error(&err);
1187 }
1188 }
1189}
1190
1191unsafe extern "C" fn callback_i64(
1192 _cif: &low::ffi_cif,
1193 result: &mut i64,
1194 args: *const *const c_void,
1195 userdata: &CallbackUserData,
1196) {
1197 match invoke_callback_userdata(userdata, args)
1198 .and_then(|v| value_to_int_i64(&v, "callback return"))
1199 {
1200 Ok(v) => *result = v,
1201 Err(err) => {
1202 *result = 0;
1203 emit_callback_error(&err);
1204 }
1205 }
1206}
1207
1208unsafe extern "C" fn callback_u32(
1209 _cif: &low::ffi_cif,
1210 result: &mut u32,
1211 args: *const *const c_void,
1212 userdata: &CallbackUserData,
1213) {
1214 match invoke_callback_userdata(userdata, args)
1215 .and_then(|v| value_to_int_i64(&v, "callback return"))
1216 .and_then(|v| {
1217 if !(0..=u32::MAX as i64).contains(&v) {
1218 Err("callback return out of range for u32".to_string())
1219 } else {
1220 Ok(v as u32)
1221 }
1222 }) {
1223 Ok(v) => *result = v,
1224 Err(err) => {
1225 *result = 0;
1226 emit_callback_error(&err);
1227 }
1228 }
1229}
1230
1231unsafe extern "C" fn callback_u64(
1232 _cif: &low::ffi_cif,
1233 result: &mut u64,
1234 args: *const *const c_void,
1235 userdata: &CallbackUserData,
1236) {
1237 match invoke_callback_userdata(userdata, args).and_then(|v| value_to_u64(&v, "callback return"))
1238 {
1239 Ok(v) => *result = v,
1240 Err(err) => {
1241 *result = 0;
1242 emit_callback_error(&err);
1243 }
1244 }
1245}
1246
1247unsafe extern "C" fn callback_isize(
1248 _cif: &low::ffi_cif,
1249 result: &mut isize,
1250 args: *const *const c_void,
1251 userdata: &CallbackUserData,
1252) {
1253 match invoke_callback_userdata(userdata, args)
1254 .and_then(|v| value_to_int_i64(&v, "callback return"))
1255 .and_then(|v| {
1256 if v < isize::MIN as i64 || v > isize::MAX as i64 {
1257 Err("callback return out of range for isize".to_string())
1258 } else {
1259 Ok(v as isize)
1260 }
1261 }) {
1262 Ok(v) => *result = v,
1263 Err(err) => {
1264 *result = 0;
1265 emit_callback_error(&err);
1266 }
1267 }
1268}
1269
1270unsafe extern "C" fn callback_usize(
1271 _cif: &low::ffi_cif,
1272 result: &mut usize,
1273 args: *const *const c_void,
1274 userdata: &CallbackUserData,
1275) {
1276 match invoke_callback_userdata(userdata, args)
1277 .and_then(|v| value_to_usize(&v, "callback return"))
1278 {
1279 Ok(v) => *result = v,
1280 Err(err) => {
1281 *result = 0;
1282 emit_callback_error(&err);
1283 }
1284 }
1285}
1286
1287unsafe extern "C" fn callback_f32(
1288 _cif: &low::ffi_cif,
1289 result: &mut f32,
1290 args: *const *const c_void,
1291 userdata: &CallbackUserData,
1292) {
1293 match invoke_callback_userdata(userdata, args).and_then(|v| value_to_f64(&v, "callback return"))
1294 {
1295 Ok(v) => *result = v as f32,
1296 Err(err) => {
1297 *result = 0.0;
1298 emit_callback_error(&err);
1299 }
1300 }
1301}
1302
1303unsafe extern "C" fn callback_f64(
1304 _cif: &low::ffi_cif,
1305 result: &mut f64,
1306 args: *const *const c_void,
1307 userdata: &CallbackUserData,
1308) {
1309 match invoke_callback_userdata(userdata, args).and_then(|v| value_to_f64(&v, "callback return"))
1310 {
1311 Ok(v) => *result = v,
1312 Err(err) => {
1313 *result = 0.0;
1314 emit_callback_error(&err);
1315 }
1316 }
1317}
1318
1319unsafe extern "C" fn callback_bool(
1320 _cif: &low::ffi_cif,
1321 result: &mut u8,
1322 args: *const *const c_void,
1323 userdata: &CallbackUserData,
1324) {
1325 match invoke_callback_userdata(userdata, args) {
1326 Ok(v) => {
1327 let b = v
1328 .as_bool()
1329 .unwrap_or_else(|| value_to_int_i64(&v, "callback return").unwrap_or(0) != 0);
1330 *result = if b { 1 } else { 0 };
1331 }
1332 Err(err) => {
1333 *result = 0;
1334 emit_callback_error(&err);
1335 }
1336 }
1337}
1338
1339fn create_callback_handle(
1340 signature: &CallbackSignature,
1341 callable: ValueWord,
1342 raw_invoker: Option<RawCallableInvoker>,
1343) -> Result<OwnedCallScopedCallback, String> {
1344 if matches!(
1345 signature.ret,
1346 CType::CString | CType::NullableCString | CType::CSlice(_) | CType::CMutSlice(_)
1347 ) {
1348 return Err(
1349 "callback return types `cstring`/`cstring?`/`cslice<_>`/`cmut_slice<_>` are not supported"
1350 .to_string(),
1351 );
1352 }
1353
1354 let arg_types: Vec<Type> = signature.params.iter().map(c_type_to_ffi_type).collect();
1355 let cif = Cif::new(arg_types, c_type_to_ffi_type(&signature.ret));
1356
1357 let userdata_ptr = Box::into_raw(Box::new(CallbackUserData {
1358 signature: signature.clone(),
1359 callable,
1360 raw_invoker,
1361 }));
1362 let userdata_ref: &'static CallbackUserData = unsafe { &*userdata_ptr };
1363
1364 let closure = match &signature.ret {
1365 CType::Void => Closure::new(cif, callback_void, userdata_ref),
1366 CType::I8 => Closure::new(cif, callback_i8, userdata_ref),
1367 CType::U8 => Closure::new(cif, callback_u8, userdata_ref),
1368 CType::I16 => Closure::new(cif, callback_i16, userdata_ref),
1369 CType::U16 => Closure::new(cif, callback_u16, userdata_ref),
1370 CType::I32 => Closure::new(cif, callback_i32, userdata_ref),
1371 CType::I64 => Closure::new(cif, callback_i64, userdata_ref),
1372 CType::U32 => Closure::new(cif, callback_u32, userdata_ref),
1373 CType::U64 => Closure::new(cif, callback_u64, userdata_ref),
1374 CType::Isize => Closure::new(cif, callback_isize, userdata_ref),
1375 CType::Usize | CType::Ptr | CType::Callback(_) | CType::CView(_) | CType::CMut(_) => {
1376 Closure::new(cif, callback_usize, userdata_ref)
1377 }
1378 CType::F32 => Closure::new(cif, callback_f32, userdata_ref),
1379 CType::F64 => Closure::new(cif, callback_f64, userdata_ref),
1380 CType::Bool => Closure::new(cif, callback_bool, userdata_ref),
1381 CType::CString | CType::NullableCString | CType::CSlice(_) | CType::CMutSlice(_) => {
1382 unreachable!("handled above")
1383 }
1384 };
1385
1386 let owned = OwnedCallScopedCallback {
1387 closure: Some(closure),
1388 userdata_ptr,
1389 };
1390 if owned.code_ptr_address() == 0 {
1391 return Err("failed to allocate callback pointer".to_string());
1392 }
1393 Ok(owned)
1394}
1395
1396fn encode_nullable_cstring_arg(
1397 value: &ValueWord,
1398 label: &str,
1399 owned_cstrings: &mut Vec<CString>,
1400) -> Result<PreparedArg, String> {
1401 if value.is_none() {
1402 return Ok(PreparedArg::Ptr(std::ptr::null_mut()));
1403 }
1404
1405 let string_ref = if let Some(s) = value.as_str() {
1406 Some(s.to_string())
1407 } else {
1408 value
1409 .as_some_inner()
1410 .and_then(|inner| inner.as_str())
1411 .map(|s| s.to_string())
1412 }
1413 .ok_or_else(|| format!("native call {} expects Option<string> for cstring?", label))?;
1414
1415 let cstring = CString::new(string_ref).map_err(|_| {
1416 format!(
1417 "native call {} contains interior NUL byte and cannot be converted to cstring",
1418 label
1419 )
1420 })?;
1421 let ptr = cstring.as_ptr() as *mut c_void;
1422 owned_cstrings.push(cstring);
1423 Ok(PreparedArg::Ptr(ptr))
1424}
1425
1426fn encode_native_view_arg(
1427 value: &ValueWord,
1428 layout_name: &str,
1429 require_mutable: bool,
1430 layouts: &HashMap<String, Arc<NativeTypeLayout>>,
1431 label: &str,
1432) -> Result<PreparedArg, String> {
1433 if !layouts.contains_key(layout_name) {
1434 return Err(format!(
1435 "native call {} references unknown `type C` layout '{}'",
1436 label, layout_name
1437 ));
1438 }
1439
1440 if let Some(view) = value.as_native_view() {
1441 if view.layout.name != layout_name {
1442 return Err(format!(
1443 "native call {} expects {}<{}>, got {}<{}>",
1444 label,
1445 if require_mutable { "cmut" } else { "cview" },
1446 layout_name,
1447 if view.mutable { "cmut" } else { "cview" },
1448 view.layout.name
1449 ));
1450 }
1451 if require_mutable && !view.mutable {
1452 return Err(format!(
1453 "native call {} expects mutable cmut<{}>, got read-only cview<{}>",
1454 label, layout_name, layout_name
1455 ));
1456 }
1457 return Ok(PreparedArg::Ptr(view.ptr as *mut c_void));
1458 }
1459
1460 let ptr = value_to_usize(value, label)?;
1461 Ok(PreparedArg::Ptr(ptr as *mut c_void))
1462}
1463
1464fn c_type_label(ctype: &CType) -> String {
1465 match ctype {
1466 CType::I8 => "i8".to_string(),
1467 CType::U8 => "u8".to_string(),
1468 CType::I16 => "i16".to_string(),
1469 CType::U16 => "u16".to_string(),
1470 CType::I32 => "i32".to_string(),
1471 CType::I64 => "i64".to_string(),
1472 CType::U32 => "u32".to_string(),
1473 CType::U64 => "u64".to_string(),
1474 CType::Isize => "isize".to_string(),
1475 CType::Usize => "usize".to_string(),
1476 CType::F32 => "f32".to_string(),
1477 CType::F64 => "f64".to_string(),
1478 CType::Bool => "bool".to_string(),
1479 CType::CString => "cstring".to_string(),
1480 CType::NullableCString => "cstring?".to_string(),
1481 CType::CSlice(elem) => format!("cslice<{}>", c_type_label(elem)),
1482 CType::CMutSlice(elem) => format!("cmut_slice<{}>", c_type_label(elem)),
1483 CType::CView(name) => format!("cview<{name}>"),
1484 CType::CMut(name) => format!("cmut<{name}>"),
1485 CType::Ptr => "ptr".to_string(),
1486 CType::Callback(sig) => {
1487 let params = sig
1488 .params
1489 .iter()
1490 .map(c_type_label)
1491 .collect::<Vec<_>>()
1492 .join(", ");
1493 format!("callback(fn({params}) -> {})", c_type_label(&sig.ret))
1494 }
1495 CType::Void => "void".to_string(),
1496 }
1497}
1498
1499fn encode_slice_arg(value: &ValueWord, elem: &CType, label: &str) -> Result<PreparedArg, String> {
1500 let array = value
1501 .as_any_array()
1502 .ok_or_else(|| {
1503 format!(
1504 "native call {label} expects Vec<{}>, got {}",
1505 c_type_label(elem),
1506 value
1507 )
1508 })?
1509 .to_generic();
1510 let values = array.as_ref();
1511 let make = |buffer: OwnedSliceBuffer| {
1512 let desc = CSliceAbi {
1513 data: buffer.data_ptr(),
1514 len: buffer.len(),
1515 };
1516 PreparedArg::SliceDesc {
1517 desc,
1518 _owned: buffer,
1519 }
1520 };
1521
1522 let encoded = match elem {
1523 CType::I8 => {
1524 let mut out = Vec::with_capacity(values.len());
1525 for (i, item) in values.iter().enumerate() {
1526 let v = value_to_int_i64(item, &format!("{label}[{i}]"))?;
1527 if !(i8::MIN as i64..=i8::MAX as i64).contains(&v) {
1528 return Err(format!("native call {label}[{i}] out of range for i8"));
1529 }
1530 out.push(v as i8);
1531 }
1532 make(OwnedSliceBuffer::I8(out))
1533 }
1534 CType::U8 => {
1535 let mut out = Vec::with_capacity(values.len());
1536 for (i, item) in values.iter().enumerate() {
1537 let v = value_to_int_i64(item, &format!("{label}[{i}]"))?;
1538 if !(0..=u8::MAX as i64).contains(&v) {
1539 return Err(format!("native call {label}[{i}] out of range for u8"));
1540 }
1541 out.push(v as u8);
1542 }
1543 make(OwnedSliceBuffer::U8(out))
1544 }
1545 CType::I16 => {
1546 let mut out = Vec::with_capacity(values.len());
1547 for (i, item) in values.iter().enumerate() {
1548 let v = value_to_int_i64(item, &format!("{label}[{i}]"))?;
1549 if !(i16::MIN as i64..=i16::MAX as i64).contains(&v) {
1550 return Err(format!("native call {label}[{i}] out of range for i16"));
1551 }
1552 out.push(v as i16);
1553 }
1554 make(OwnedSliceBuffer::I16(out))
1555 }
1556 CType::U16 => {
1557 let mut out = Vec::with_capacity(values.len());
1558 for (i, item) in values.iter().enumerate() {
1559 let v = value_to_int_i64(item, &format!("{label}[{i}]"))?;
1560 if !(0..=u16::MAX as i64).contains(&v) {
1561 return Err(format!("native call {label}[{i}] out of range for u16"));
1562 }
1563 out.push(v as u16);
1564 }
1565 make(OwnedSliceBuffer::U16(out))
1566 }
1567 CType::I32 => {
1568 let mut out = Vec::with_capacity(values.len());
1569 for (i, item) in values.iter().enumerate() {
1570 let v = value_to_int_i64(item, &format!("{label}[{i}]"))?;
1571 if !(i32::MIN as i64..=i32::MAX as i64).contains(&v) {
1572 return Err(format!("native call {label}[{i}] out of range for i32"));
1573 }
1574 out.push(v as i32);
1575 }
1576 make(OwnedSliceBuffer::I32(out))
1577 }
1578 CType::I64 => {
1579 let mut out = Vec::with_capacity(values.len());
1580 for (i, item) in values.iter().enumerate() {
1581 out.push(value_to_int_i64(item, &format!("{label}[{i}]"))?);
1582 }
1583 make(OwnedSliceBuffer::I64(out))
1584 }
1585 CType::U32 => {
1586 let mut out = Vec::with_capacity(values.len());
1587 for (i, item) in values.iter().enumerate() {
1588 let v = value_to_int_i64(item, &format!("{label}[{i}]"))?;
1589 if !(0..=u32::MAX as i64).contains(&v) {
1590 return Err(format!("native call {label}[{i}] out of range for u32"));
1591 }
1592 out.push(v as u32);
1593 }
1594 make(OwnedSliceBuffer::U32(out))
1595 }
1596 CType::U64 => {
1597 let mut out = Vec::with_capacity(values.len());
1598 for (i, item) in values.iter().enumerate() {
1599 out.push(value_to_u64(item, &format!("{label}[{i}]"))?);
1600 }
1601 make(OwnedSliceBuffer::U64(out))
1602 }
1603 CType::Isize => {
1604 let mut out = Vec::with_capacity(values.len());
1605 for (i, item) in values.iter().enumerate() {
1606 let v = value_to_int_i64(item, &format!("{label}[{i}]"))?;
1607 if v < isize::MIN as i64 || v > isize::MAX as i64 {
1608 return Err(format!("native call {label}[{i}] out of range for isize"));
1609 }
1610 out.push(v as isize);
1611 }
1612 make(OwnedSliceBuffer::Isize(out))
1613 }
1614 CType::Usize => {
1615 let mut out = Vec::with_capacity(values.len());
1616 for (i, item) in values.iter().enumerate() {
1617 out.push(value_to_usize(item, &format!("{label}[{i}]"))?);
1618 }
1619 make(OwnedSliceBuffer::Usize(out))
1620 }
1621 CType::F32 => {
1622 let mut out = Vec::with_capacity(values.len());
1623 for (i, item) in values.iter().enumerate() {
1624 out.push(value_to_f64(item, &format!("{label}[{i}]"))? as f32);
1625 }
1626 make(OwnedSliceBuffer::F32(out))
1627 }
1628 CType::F64 => {
1629 let mut out = Vec::with_capacity(values.len());
1630 for (i, item) in values.iter().enumerate() {
1631 out.push(value_to_f64(item, &format!("{label}[{i}]"))?);
1632 }
1633 make(OwnedSliceBuffer::F64(out))
1634 }
1635 CType::Bool => {
1636 let mut out = Vec::with_capacity(values.len());
1637 for (i, item) in values.iter().enumerate() {
1638 let b = if let Some(v) = item.as_bool() {
1639 v
1640 } else {
1641 value_to_int_i64(item, &format!("{label}[{i}]"))? != 0
1642 };
1643 out.push(if b { 1 } else { 0 });
1644 }
1645 make(OwnedSliceBuffer::Bool(out))
1646 }
1647 CType::Ptr => {
1648 let mut out = Vec::with_capacity(values.len());
1649 for (i, item) in values.iter().enumerate() {
1650 out.push(value_to_usize(item, &format!("{label}[{i}]"))? as *mut c_void);
1651 }
1652 make(OwnedSliceBuffer::Ptr(out))
1653 }
1654 CType::CString => {
1655 let mut strings = Vec::with_capacity(values.len());
1656 let mut ptrs = Vec::with_capacity(values.len());
1657 for (i, item) in values.iter().enumerate() {
1658 let s = item.as_str().ok_or_else(|| {
1659 format!("native call {label}[{i}] expects string for cstring element")
1660 })?;
1661 let cstring = CString::new(s).map_err(|_| {
1662 format!(
1663 "native call {label}[{i}] contains interior NUL byte and cannot be converted to cstring"
1664 )
1665 })?;
1666 ptrs.push(cstring.as_ptr());
1667 strings.push(cstring);
1668 }
1669 make(OwnedSliceBuffer::CString {
1670 _strings: strings,
1671 ptrs,
1672 })
1673 }
1674 CType::NullableCString => {
1675 let mut strings = Vec::with_capacity(values.len());
1676 let mut ptrs = Vec::with_capacity(values.len());
1677 for (i, item) in values.iter().enumerate() {
1678 if item.is_none() {
1679 ptrs.push(std::ptr::null());
1680 continue;
1681 }
1682 let s = if let Some(s) = item.as_str() {
1683 Some(s.to_string())
1684 } else {
1685 item.as_some_inner()
1686 .and_then(|inner| inner.as_str())
1687 .map(|s| s.to_string())
1688 }
1689 .ok_or_else(|| {
1690 format!("native call {label}[{i}] expects Option<string> for cstring? element")
1691 })?;
1692 let cstring = CString::new(s).map_err(|_| {
1693 format!(
1694 "native call {label}[{i}] contains interior NUL byte and cannot be converted to cstring?"
1695 )
1696 })?;
1697 ptrs.push(cstring.as_ptr());
1698 strings.push(cstring);
1699 }
1700 make(OwnedSliceBuffer::NullableCString {
1701 _strings: strings,
1702 ptrs,
1703 })
1704 }
1705 other => {
1706 return Err(format!(
1707 "native call {label} unsupported slice element type '{}'",
1708 c_type_label(other)
1709 ));
1710 }
1711 };
1712
1713 Ok(encoded)
1714}
1715
1716fn decode_slice_elements(
1717 slice: CSliceAbi,
1718 elem: &CType,
1719 label: &str,
1720) -> Result<Vec<ValueWord>, String> {
1721 if slice.len == 0 {
1722 return Ok(Vec::new());
1723 }
1724 if slice.data.is_null() {
1725 return Err(format!(
1726 "{label} returned null data pointer for non-empty {}",
1727 c_type_label(elem)
1728 ));
1729 }
1730
1731 let mut out = Vec::with_capacity(slice.len);
1732 match elem {
1733 CType::I8 => {
1734 let ptr = slice.data as *const i8;
1735 for i in 0..slice.len {
1736 out.push(ValueWord::from_native_i8(unsafe { *ptr.add(i) }));
1737 }
1738 }
1739 CType::U8 => {
1740 let ptr = slice.data as *const u8;
1741 for i in 0..slice.len {
1742 out.push(ValueWord::from_native_u8(unsafe { *ptr.add(i) }));
1743 }
1744 }
1745 CType::I16 => {
1746 let ptr = slice.data as *const i16;
1747 for i in 0..slice.len {
1748 out.push(ValueWord::from_native_i16(unsafe { *ptr.add(i) }));
1749 }
1750 }
1751 CType::U16 => {
1752 let ptr = slice.data as *const u16;
1753 for i in 0..slice.len {
1754 out.push(ValueWord::from_native_u16(unsafe { *ptr.add(i) }));
1755 }
1756 }
1757 CType::I32 => {
1758 let ptr = slice.data as *const i32;
1759 for i in 0..slice.len {
1760 out.push(ValueWord::from_native_i32(unsafe { *ptr.add(i) }));
1761 }
1762 }
1763 CType::I64 => {
1764 let ptr = slice.data as *const i64;
1765 for i in 0..slice.len {
1766 out.push(ValueWord::from_native_scalar(NativeScalar::I64(unsafe {
1767 *ptr.add(i)
1768 })));
1769 }
1770 }
1771 CType::U32 => {
1772 let ptr = slice.data as *const u32;
1773 for i in 0..slice.len {
1774 out.push(ValueWord::from_native_u32(unsafe { *ptr.add(i) }));
1775 }
1776 }
1777 CType::U64 => {
1778 let ptr = slice.data as *const u64;
1779 for i in 0..slice.len {
1780 out.push(ValueWord::from_native_u64(unsafe { *ptr.add(i) }));
1781 }
1782 }
1783 CType::Isize => {
1784 let ptr = slice.data as *const isize;
1785 for i in 0..slice.len {
1786 out.push(ValueWord::from_native_isize(unsafe { *ptr.add(i) }));
1787 }
1788 }
1789 CType::Usize => {
1790 let ptr = slice.data as *const usize;
1791 for i in 0..slice.len {
1792 out.push(ValueWord::from_native_usize(unsafe { *ptr.add(i) }));
1793 }
1794 }
1795 CType::F32 => {
1796 let ptr = slice.data as *const f32;
1797 for i in 0..slice.len {
1798 out.push(ValueWord::from_native_f32(unsafe { *ptr.add(i) }));
1799 }
1800 }
1801 CType::F64 => {
1802 let ptr = slice.data as *const f64;
1803 for i in 0..slice.len {
1804 out.push(ValueWord::from_f64(unsafe { *ptr.add(i) }));
1805 }
1806 }
1807 CType::Bool => {
1808 let ptr = slice.data as *const u8;
1809 for i in 0..slice.len {
1810 out.push(ValueWord::from_bool(unsafe { *ptr.add(i) } != 0));
1811 }
1812 }
1813 CType::Ptr => {
1814 let ptr = slice.data as *const *mut c_void;
1815 for i in 0..slice.len {
1816 out.push(ValueWord::from_native_ptr(unsafe { *ptr.add(i) } as usize));
1817 }
1818 }
1819 CType::CString => {
1820 let ptr = slice.data as *const *const c_char;
1821 for i in 0..slice.len {
1822 let item_ptr = unsafe { *ptr.add(i) };
1823 if item_ptr.is_null() {
1824 return Err(format!(
1825 "{label} returned null cstring at index {i}; use cstring? for nullable values"
1826 ));
1827 }
1828 let s = unsafe { CStr::from_ptr(item_ptr) }
1829 .to_string_lossy()
1830 .to_string();
1831 out.push(ValueWord::from_string(Arc::new(s)));
1832 }
1833 }
1834 CType::NullableCString => {
1835 let ptr = slice.data as *const *const c_char;
1836 for i in 0..slice.len {
1837 let item_ptr = unsafe { *ptr.add(i) };
1838 if item_ptr.is_null() {
1839 out.push(ValueWord::none());
1840 } else {
1841 let s = unsafe { CStr::from_ptr(item_ptr) }
1842 .to_string_lossy()
1843 .to_string();
1844 out.push(ValueWord::from_some(ValueWord::from_string(Arc::new(s))));
1845 }
1846 }
1847 }
1848 other => {
1849 return Err(format!(
1850 "{label} does not support slice element type '{}'",
1851 c_type_label(other)
1852 ));
1853 }
1854 }
1855 Ok(out)
1856}
1857
1858fn encode_arg(
1859 value: &ValueWord,
1860 ctype: &CType,
1861 idx: usize,
1862 owned_cstrings: &mut Vec<CString>,
1863 owned_callbacks: &mut Vec<OwnedCallScopedCallback>,
1864 layouts: &HashMap<String, Arc<NativeTypeLayout>>,
1865 raw_invoker: Option<RawCallableInvoker>,
1866) -> Result<PreparedArg, String> {
1867 let label = format!("arg#{idx}");
1868 match ctype {
1869 CType::I8 => {
1870 let v = value_to_int_i64(value, &label)?;
1871 if !(i8::MIN as i64..=i8::MAX as i64).contains(&v) {
1872 return Err(format!("native call {} out of range for i8", label));
1873 }
1874 Ok(PreparedArg::I8(v as i8))
1875 }
1876 CType::U8 => {
1877 let v = value_to_int_i64(value, &label)?;
1878 if !(0..=u8::MAX as i64).contains(&v) {
1879 return Err(format!("native call {} out of range for u8", label));
1880 }
1881 Ok(PreparedArg::U8(v as u8))
1882 }
1883 CType::I16 => {
1884 let v = value_to_int_i64(value, &label)?;
1885 if !(i16::MIN as i64..=i16::MAX as i64).contains(&v) {
1886 return Err(format!("native call {} out of range for i16", label));
1887 }
1888 Ok(PreparedArg::I16(v as i16))
1889 }
1890 CType::U16 => {
1891 let v = value_to_int_i64(value, &label)?;
1892 if !(0..=u16::MAX as i64).contains(&v) {
1893 return Err(format!("native call {} out of range for u16", label));
1894 }
1895 Ok(PreparedArg::U16(v as u16))
1896 }
1897 CType::I32 => {
1898 let v = value_to_int_i64(value, &label)?;
1899 if !(i32::MIN as i64..=i32::MAX as i64).contains(&v) {
1900 return Err(format!("native call {} out of range for i32", label));
1901 }
1902 Ok(PreparedArg::I32(v as i32))
1903 }
1904 CType::I64 => Ok(PreparedArg::I64(value_to_int_i64(value, &label)?)),
1905 CType::U32 => {
1906 let v = value_to_int_i64(value, &label)?;
1907 if !(0..=u32::MAX as i64).contains(&v) {
1908 return Err(format!("native call {} out of range for u32", label));
1909 }
1910 Ok(PreparedArg::U32(v as u32))
1911 }
1912 CType::U64 => Ok(PreparedArg::U64(value_to_u64(value, &label)?)),
1913 CType::Isize => Ok(PreparedArg::Isize(value_to_int_i64(value, &label)? as isize)),
1914 CType::Usize => Ok(PreparedArg::Usize(value_to_usize(value, &label)?)),
1915 CType::F32 => Ok(PreparedArg::F32(value_to_f64(value, &label)? as f32)),
1916 CType::F64 => Ok(PreparedArg::F64(value_to_f64(value, &label)?)),
1917 CType::Bool => {
1918 let b = if let Some(v) = value.as_bool() {
1919 v
1920 } else {
1921 value_to_int_i64(value, &label)? != 0
1922 };
1923 Ok(PreparedArg::Bool(if b { 1 } else { 0 }))
1924 }
1925 CType::CString => {
1926 let s = value
1927 .as_str()
1928 .ok_or_else(|| format!("native call {} expects a string for cstring", label))?;
1929 let cstring = CString::new(s).map_err(|_| {
1930 format!(
1931 "native call {} contains interior NUL byte and cannot be converted to cstring",
1932 label
1933 )
1934 })?;
1935 let ptr = cstring.as_ptr() as *mut c_void;
1936 owned_cstrings.push(cstring);
1937 Ok(PreparedArg::Ptr(ptr))
1938 }
1939 CType::NullableCString => encode_nullable_cstring_arg(value, &label, owned_cstrings),
1940 CType::CView(layout_name) => {
1941 encode_native_view_arg(value, layout_name, false, layouts, &label)
1942 }
1943 CType::CMut(layout_name) => {
1944 encode_native_view_arg(value, layout_name, true, layouts, &label)
1945 }
1946 CType::CSlice(elem) | CType::CMutSlice(elem) => encode_slice_arg(value, elem, &label),
1947 CType::Ptr => {
1948 let ptr = value_to_usize(value, &label)?;
1949 Ok(PreparedArg::Ptr(ptr as *mut c_void))
1950 }
1951 CType::Callback(signature) => {
1952 if is_shape_callable(value) {
1953 let handle = create_callback_handle(signature, value.clone(), raw_invoker)?;
1954 let ptr = handle.code_ptr_address();
1955 if ptr > i64::MAX as usize {
1956 return Err("callback pointer exceeds Shape int range (i64::MAX)".to_string());
1957 }
1958 owned_callbacks.push(handle);
1959 Ok(PreparedArg::Ptr(ptr as *mut c_void))
1960 } else {
1961 let ptr = value_to_usize(value, &label)?;
1962 Ok(PreparedArg::Ptr(ptr as *mut c_void))
1963 }
1964 }
1965 CType::Void => Err("void cannot be used as a function parameter type".to_string()),
1966 }
1967}
1968
1969#[cfg(test)]
1970mod tests {
1971 use super::*;
1972 use shape_value::heap_value::NativeTypeLayout;
1973 use std::collections::HashMap;
1974 use std::sync::Arc;
1975
1976 fn sample_layout(name: &str) -> Arc<NativeTypeLayout> {
1977 Arc::new(NativeTypeLayout {
1978 name: name.to_string(),
1979 abi: "C".to_string(),
1980 size: 16,
1981 align: 8,
1982 fields: Vec::new(),
1983 })
1984 }
1985
1986 #[test]
1987 fn parse_signature_accepts_basic_form() {
1988 let sig = parse_signature("fn(i32, f64) -> bool").expect("valid signature");
1989 assert_eq!(sig.params, vec![CType::I32, CType::F64]);
1990 assert_eq!(sig.ret, CType::Bool);
1991 }
1992
1993 #[test]
1994 fn parse_signature_supports_nullable_cstring() {
1995 let sig = parse_signature("fn(cstring?) -> cstring?").expect("valid nullable string sig");
1996 assert_eq!(sig.params, vec![CType::NullableCString]);
1997 assert_eq!(sig.ret, CType::NullableCString);
1998 }
1999
2000 #[test]
2001 fn parse_signature_supports_callback_type() {
2002 let sig = parse_signature("fn(i32, callback(fn(ptr, ptr) -> i32)) -> i32")
2003 .expect("callback signature should parse");
2004 assert!(matches!(sig.params[1], CType::Callback(_)));
2005 }
2006
2007 #[test]
2008 fn parse_signature_supports_cview_and_cmut() {
2009 let sig = parse_signature("fn(cview<QuoteC>, cmut<QuoteC>) -> cview<QuoteC>")
2010 .expect("native views should parse");
2011 assert!(matches!(sig.params[0], CType::CView(ref name) if name == "QuoteC"));
2012 assert!(matches!(sig.params[1], CType::CMut(ref name) if name == "QuoteC"));
2013 assert!(matches!(sig.ret, CType::CView(ref name) if name == "QuoteC"));
2014 }
2015
2016 #[test]
2017 fn parse_signature_supports_cslice_and_cmut_slice() {
2018 let sig = parse_signature("fn(cslice<u8>, cmut_slice<cstring?>) -> cslice<u32>")
2019 .expect("native slices should parse");
2020 assert!(matches!(sig.params[0], CType::CSlice(_)));
2021 assert!(matches!(sig.params[1], CType::CMutSlice(_)));
2022 assert!(matches!(sig.ret, CType::CSlice(_)));
2023 }
2024
2025 #[test]
2026 fn parse_signature_rejects_void_param() {
2027 let err = parse_signature("fn(void, i32) -> i32").expect_err("void param must fail");
2028 assert!(err.contains("void"));
2029 }
2030
2031 #[test]
2032 fn validate_layout_references_rejects_unknown_type_c_layout() {
2033 let sig = parse_signature("fn(cview<QuoteC>) -> void").expect("signature should parse");
2034 let err = validate_layout_references(&sig, &HashMap::new())
2035 .expect_err("unknown layout should fail validation");
2036 assert!(err.contains("unknown `type C` layout 'QuoteC'"));
2037 }
2038
2039 #[test]
2040 fn encode_arg_preserves_u8_width_and_checks_range() {
2041 let mut owned_cstrings = Vec::new();
2042 let mut owned_callbacks = Vec::new();
2043 let layouts = HashMap::new();
2044 let arg = encode_arg(
2045 &ValueWord::from_native_u8(255),
2046 &CType::U8,
2047 0,
2048 &mut owned_cstrings,
2049 &mut owned_callbacks,
2050 &layouts,
2051 None,
2052 )
2053 .expect("u8 value should encode");
2054 assert!(matches!(arg, PreparedArg::U8(255)));
2055
2056 let err = encode_arg(
2057 &ValueWord::from_i64(256),
2058 &CType::U8,
2059 0,
2060 &mut owned_cstrings,
2061 &mut owned_callbacks,
2062 &layouts,
2063 None,
2064 )
2065 .expect_err("overflow u8 should fail");
2066 assert!(err.contains("out of range for u8"));
2067 }
2068
2069 #[test]
2070 fn encode_arg_i64_accepts_exact_native_i64() {
2071 let mut owned_cstrings = Vec::new();
2072 let mut owned_callbacks = Vec::new();
2073 let layouts = HashMap::new();
2074 let arg = encode_arg(
2075 &ValueWord::from_native_scalar(NativeScalar::I64(i64::MAX)),
2076 &CType::I64,
2077 0,
2078 &mut owned_cstrings,
2079 &mut owned_callbacks,
2080 &layouts,
2081 None,
2082 )
2083 .expect("i64 value should encode");
2084 assert!(matches!(arg, PreparedArg::I64(v) if v == i64::MAX));
2085 }
2086
2087 #[test]
2088 fn encode_arg_f64_rejects_native_i64_without_cast() {
2089 let mut owned_cstrings = Vec::new();
2090 let mut owned_callbacks = Vec::new();
2091 let layouts = HashMap::new();
2092 let err = encode_arg(
2093 &ValueWord::from_native_scalar(NativeScalar::I64(123)),
2094 &CType::F64,
2095 0,
2096 &mut owned_cstrings,
2097 &mut owned_callbacks,
2098 &layouts,
2099 None,
2100 )
2101 .expect_err("native i64 should not auto-coerce to f64");
2102 assert!(err.contains("floating-point compatible value"));
2103 }
2104
2105 #[test]
2106 fn encode_arg_nullable_cstring_maps_none_and_some_string() {
2107 let mut owned_cstrings = Vec::new();
2108 let mut owned_callbacks = Vec::new();
2109 let layouts = HashMap::new();
2110
2111 let null_arg = encode_arg(
2112 &ValueWord::none(),
2113 &CType::NullableCString,
2114 0,
2115 &mut owned_cstrings,
2116 &mut owned_callbacks,
2117 &layouts,
2118 None,
2119 )
2120 .expect("none should map to null cstring pointer");
2121 assert!(matches!(null_arg, PreparedArg::Ptr(ptr) if ptr.is_null()));
2122
2123 let some = ValueWord::from_some(ValueWord::from_string(Arc::new("hello".to_string())));
2124 let some_arg = encode_arg(
2125 &some,
2126 &CType::NullableCString,
2127 0,
2128 &mut owned_cstrings,
2129 &mut owned_callbacks,
2130 &layouts,
2131 None,
2132 )
2133 .expect("Option<string> should map to non-null cstring pointer");
2134 assert!(matches!(some_arg, PreparedArg::Ptr(ptr) if !ptr.is_null()));
2135 }
2136
2137 #[test]
2138 fn encode_arg_cslice_u8_from_vec_byte() {
2139 let mut owned_cstrings = Vec::new();
2140 let mut owned_callbacks = Vec::new();
2141 let layouts = HashMap::new();
2142 let values = ValueWord::from_array(Arc::new(vec![
2143 ValueWord::from_native_u8(1),
2144 ValueWord::from_native_u8(2),
2145 ValueWord::from_native_u8(3),
2146 ]));
2147 let arg = encode_arg(
2148 &values,
2149 &CType::CSlice(Box::new(CType::U8)),
2150 0,
2151 &mut owned_cstrings,
2152 &mut owned_callbacks,
2153 &layouts,
2154 None,
2155 )
2156 .expect("Vec<byte> should encode into cslice<u8>");
2157 assert!(matches!(
2158 arg,
2159 PreparedArg::SliceDesc { desc, .. } if desc.len == 3 && !desc.data.is_null()
2160 ));
2161 }
2162
2163 #[test]
2164 fn encode_arg_cslice_u8_rejects_out_of_range_values() {
2165 let mut owned_cstrings = Vec::new();
2166 let mut owned_callbacks = Vec::new();
2167 let layouts = HashMap::new();
2168 let values = ValueWord::from_array(Arc::new(vec![ValueWord::from_i64(256)]));
2169 let err = encode_arg(
2170 &values,
2171 &CType::CSlice(Box::new(CType::U8)),
2172 0,
2173 &mut owned_cstrings,
2174 &mut owned_callbacks,
2175 &layouts,
2176 None,
2177 )
2178 .expect_err("out-of-range u8 slice value should fail");
2179 assert!(err.contains("out of range for u8"));
2180 }
2181
2182 #[test]
2183 fn resolve_arg_value_dereferences_ref_from_stack() {
2184 let stack = vec![ValueWord::from_native_u8(42)];
2185 let value = ValueWord::from_ref(0);
2186 let (resolved, slot) =
2187 resolve_arg_value_for_native_call(&value, 0, Some(&stack)).expect("ref should resolve");
2188 assert_eq!(slot, Some(0));
2189 assert_eq!(
2190 resolved
2191 .as_native_scalar()
2192 .and_then(|scalar| scalar.as_u64())
2193 .expect("resolved value should be native u8"),
2194 42_u64
2195 );
2196 }
2197
2198 #[test]
2199 fn resolve_arg_value_requires_stack_for_ref() {
2200 let value = ValueWord::from_ref(0);
2201 let err = resolve_arg_value_for_native_call(&value, 0, None)
2202 .expect_err("ref argument without stack context must fail");
2203 assert!(err.contains("no VM stack context"));
2204 }
2205
2206 #[test]
2207 fn cmut_slice_requires_reference_source_for_writeback() {
2208 let err = build_mutable_writeback_plan(&CType::CMutSlice(Box::new(CType::U8)), 0, None)
2209 .expect_err("cmut_slice without ref source should fail");
2210 assert!(err.contains("requires a mutable reference argument"));
2211 }
2212
2213 #[test]
2214 fn apply_mutable_writebacks_updates_u8_slice_target() {
2215 let buffer = OwnedSliceBuffer::U8(vec![9, 8, 7]);
2216 let desc = CSliceAbi {
2217 data: buffer.data_ptr(),
2218 len: buffer.len(),
2219 };
2220 let prepared_args = vec![PreparedArg::SliceDesc {
2221 desc,
2222 _owned: buffer,
2223 }];
2224 let plans = vec![MutableArgWritebackPlan::Slice {
2225 arg_index: 0,
2226 target_slot: 1,
2227 elem_type: CType::U8,
2228 }];
2229
2230 let mut stack = vec![
2231 ValueWord::none(),
2232 ValueWord::from_array(Arc::new(vec![ValueWord::from_native_u8(1)])),
2233 ];
2234 apply_mutable_writebacks(&mut stack, &prepared_args, &plans)
2235 .expect("writeback should succeed");
2236
2237 let out = stack[1]
2238 .as_any_array()
2239 .expect("stack target should be array")
2240 .to_generic();
2241 assert_eq!(out.len(), 3);
2242 assert_eq!(
2243 out[0]
2244 .as_native_scalar()
2245 .and_then(|scalar| scalar.as_u64())
2246 .expect("array[0] should be native u8"),
2247 9_u64
2248 );
2249 assert_eq!(
2250 out[1]
2251 .as_native_scalar()
2252 .and_then(|scalar| scalar.as_u64())
2253 .expect("array[1] should be native u8"),
2254 8_u64
2255 );
2256 }
2257
2258 #[test]
2259 fn apply_mutable_writebacks_supports_nullable_cstring_slice() {
2260 let hello = CString::new("hello").expect("cstring");
2261 let world = CString::new("world").expect("cstring");
2262 let ptrs: Vec<*const c_char> = vec![hello.as_ptr(), std::ptr::null(), world.as_ptr()];
2263 let buffer = OwnedSliceBuffer::NullableCString {
2264 _strings: vec![hello, world],
2265 ptrs,
2266 };
2267 let desc = CSliceAbi {
2268 data: buffer.data_ptr(),
2269 len: buffer.len(),
2270 };
2271 let prepared_args = vec![PreparedArg::SliceDesc {
2272 desc,
2273 _owned: buffer,
2274 }];
2275 let plans = vec![MutableArgWritebackPlan::Slice {
2276 arg_index: 0,
2277 target_slot: 0,
2278 elem_type: CType::NullableCString,
2279 }];
2280
2281 let mut stack = vec![ValueWord::from_array(Arc::new(vec![]))];
2282 apply_mutable_writebacks(&mut stack, &prepared_args, &plans)
2283 .expect("nullable cstring writeback should succeed");
2284
2285 let out = stack[0]
2286 .as_any_array()
2287 .expect("stack target should be array")
2288 .to_generic();
2289 assert_eq!(out.len(), 3);
2290 assert_eq!(
2291 out[0]
2292 .as_some_inner()
2293 .and_then(|inner| inner.as_str())
2294 .expect("first value should be Some(string)"),
2295 "hello"
2296 );
2297 assert!(out[1].is_none());
2298 assert_eq!(
2299 out[2]
2300 .as_some_inner()
2301 .and_then(|inner| inner.as_str())
2302 .expect("third value should be Some(string)"),
2303 "world"
2304 );
2305 }
2306
2307 #[test]
2308 fn decode_slice_elements_handles_nullable_cstring() {
2309 let hello = CString::new("hello").expect("cstring");
2310 let world = CString::new("world").expect("cstring");
2311 let ptrs: Vec<*const c_char> = vec![hello.as_ptr(), std::ptr::null(), world.as_ptr()];
2312 let slice = CSliceAbi {
2313 data: ptrs.as_ptr() as *mut c_void,
2314 len: ptrs.len(),
2315 };
2316 let decoded = decode_slice_elements(slice, &CType::NullableCString, "native call return")
2317 .expect("nullable cstring slice should decode");
2318 assert_eq!(decoded.len(), 3);
2319 assert_eq!(
2320 decoded[0]
2321 .as_some_inner()
2322 .and_then(|inner| inner.as_str())
2323 .expect("first should be Some(string)"),
2324 "hello"
2325 );
2326 assert!(decoded[1].is_none());
2327 assert_eq!(
2328 decoded[2]
2329 .as_some_inner()
2330 .and_then(|inner| inner.as_str())
2331 .expect("third should be Some(string)"),
2332 "world"
2333 );
2334 }
2335
2336 #[test]
2337 fn encode_arg_native_view_enforces_layout_and_mutability() {
2338 let layout = sample_layout("QuoteC");
2339 let mut layouts = HashMap::new();
2340 layouts.insert(layout.name.clone(), layout.clone());
2341 let mut owned_cstrings = Vec::new();
2342 let mut owned_callbacks = Vec::new();
2343
2344 let view = ValueWord::from_c_view(0x1000, layout.clone());
2345 let cview_arg = encode_arg(
2346 &view,
2347 &CType::CView("QuoteC".to_string()),
2348 0,
2349 &mut owned_cstrings,
2350 &mut owned_callbacks,
2351 &layouts,
2352 None,
2353 )
2354 .expect("matching cview should encode");
2355 assert!(matches!(cview_arg, PreparedArg::Ptr(ptr) if ptr as usize == 0x1000));
2356
2357 let err = encode_arg(
2358 &view,
2359 &CType::CMut("QuoteC".to_string()),
2360 0,
2361 &mut owned_cstrings,
2362 &mut owned_callbacks,
2363 &layouts,
2364 None,
2365 )
2366 .expect_err("cview should not satisfy cmut requirement");
2367 assert!(err.contains("expects mutable cmut<QuoteC>"));
2368
2369 let err = encode_arg(
2370 &view,
2371 &CType::CView("OtherC".to_string()),
2372 0,
2373 &mut owned_cstrings,
2374 &mut owned_callbacks,
2375 &layouts,
2376 None,
2377 )
2378 .expect_err("layout mismatch should fail");
2379 assert!(err.contains("unknown `type C` layout 'OtherC'"));
2380
2381 let ptr_value = ValueWord::from_native_ptr(0x2000);
2382 let ptr_arg = encode_arg(
2383 &ptr_value,
2384 &CType::CView("QuoteC".to_string()),
2385 0,
2386 &mut owned_cstrings,
2387 &mut owned_callbacks,
2388 &layouts,
2389 None,
2390 )
2391 .expect("raw pointer should be accepted for cview");
2392 assert!(matches!(ptr_arg, PreparedArg::Ptr(ptr) if ptr as usize == 0x2000));
2393 }
2394
2395 #[test]
2396 fn decode_callback_nullable_cstring_maps_null_to_option_none() {
2397 let null_ptr: *const c_char = std::ptr::null();
2398 let arg_ptr = &null_ptr as *const *const c_char as *const c_void;
2399 let decoded = unsafe { decode_callback_arg(arg_ptr, &CType::NullableCString, 0) }
2400 .expect("null nullable cstring should decode");
2401 assert!(decoded.is_none());
2402 }
2403
2404 #[test]
2405 fn decode_callback_nullable_cstring_maps_non_null_to_option_string() {
2406 let cstring = CString::new("hello").expect("cstring");
2407 let s_ptr: *const c_char = cstring.as_ptr();
2408 let arg_ptr = &s_ptr as *const *const c_char as *const c_void;
2409 let decoded = unsafe { decode_callback_arg(arg_ptr, &CType::NullableCString, 0) }
2410 .expect("non-null nullable cstring should decode");
2411 let inner = decoded
2412 .as_some_inner()
2413 .expect("should decode to Option::Some");
2414 assert_eq!(inner.as_str(), Some("hello"));
2415 }
2416}