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