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