Skip to main content

shape_vm/executor/control_flow/
native_abi.rs

1//! Native C ABI linking and invocation for `extern C` foreign functions.
2
3use 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
50/// Extract the inner type argument from a generic type like `name<inner>`.
51///
52/// Given `compact` (whitespace-stripped original) and `type_name` (e.g. "cview"),
53/// returns the trimmed inner string. Returns an error if the angle-bracket
54/// extraction fails or the inner string is empty.
55fn 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
243/// Linked native function handle used by VM foreign-call dispatch.
244pub struct NativeLinkedFunction {
245    signature: CSignature,
246    cif: Cif,
247    code_ptr: CodePtr,
248    layouts: HashMap<String, Arc<NativeTypeLayout>>,
249    /// Keep the dynamic library alive for symbol/call lifetime.
250    _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    // Allow language `int` literals (i48) for float ABI params without opening
682    // lossy conversions for native i64/u64 domains.
683    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}