Skip to main content

vm/
lib.rs

1//使用 cranelift 作为后端 直接 jit 解释脚本
2mod binary;
3mod memory;
4mod native;
5pub use native::{ANY, STD};
6
7mod fns;
8use anyhow::{Result, anyhow};
9pub use fns::{FnInfo, FnVariant};
10mod context;
11pub use context::BuildContext;
12
13mod rt;
14use cranelift::prelude::types;
15use dynamic::Type;
16pub use rt::JITRunTime;
17use smol_str::SmolStr;
18mod db_module;
19mod gpu_layout;
20mod gpu_module;
21mod http_module;
22mod llm_module;
23mod oss_module;
24mod root_module;
25pub use gpu_layout::{GpuFieldLayout, GpuStructLayout};
26
27use std::sync::{Mutex, OnceLock, Weak};
28static PTR_TYPE: OnceLock<types::Type> = OnceLock::new();
29pub fn ptr_type() -> types::Type {
30    PTR_TYPE.get().cloned().unwrap()
31}
32
33pub fn get_type(ty: &Type) -> Result<types::Type> {
34    if ty.is_f64() {
35        Ok(types::F64)
36    } else if ty.is_f32() {
37        Ok(types::F32)
38    } else if ty.is_int() | ty.is_uint() {
39        match ty.width() {
40            1 => Ok(types::I8),
41            2 => Ok(types::I16),
42            4 => Ok(types::I32),
43            8 => Ok(types::I64),
44            _ => Err(anyhow!("非法类型 {:?}", ty)),
45        }
46    } else if let Type::Bool = ty {
47        Ok(types::I8)
48    } else {
49        Ok(ptr_type())
50    }
51}
52
53use compiler::Symbol;
54use cranelift::prelude::*;
55use cranelift_module::Module;
56
57pub fn init_jit(mut jit: JITRunTime) -> Result<JITRunTime> {
58    jit.add_all()?;
59    Ok(jit)
60}
61
62use std::sync::Arc;
63unsafe impl Send for JITRunTime {}
64unsafe impl Sync for JITRunTime {}
65
66pub(crate) fn with_vm_context<T>(context: *const Weak<Mutex<JITRunTime>>, f: impl FnOnce(&Vm) -> Result<T>) -> Result<T> {
67    if context.is_null() {
68        return Err(anyhow!("VM context is null"));
69    }
70    let jit = unsafe { &*context }.upgrade().ok_or_else(|| anyhow!("VM context has expired"))?;
71    let vm = Vm { jit };
72    f(&vm)
73}
74
75fn add_method_field(jit: &mut JITRunTime, def: &str, method: &str, id: u32) -> Result<()> {
76    let def_id = jit.get_id(def)?;
77    if let Some((_, define)) = jit.compiler.symbols.get_symbol_mut(def_id) {
78        if let Symbol::Struct(Type::Struct { params, fields }, _) = define {
79            fields.push((method.into(), Type::Symbol { id, params: params.clone() }));
80        }
81    }
82    Ok(())
83}
84
85fn add_native_module_fns(jit: &mut JITRunTime, module: &str, fns: &[(&str, &[Type], Type, *const u8)]) -> Result<()> {
86    jit.add_module(module);
87    for (name, arg_tys, ret_ty, fn_ptr) in fns {
88        let full_name = format!("{}::{}", module, name);
89        jit.add_native_ptr(&full_name, name, arg_tys, ret_ty.clone(), *fn_ptr)?;
90    }
91    jit.pop_module();
92    Ok(())
93}
94
95impl JITRunTime {
96    fn add_memory_runtime(&mut self) -> Result<()> {
97        self.native_symbols.write().unwrap().insert("__vm_scope_enter".to_string(), memory::scope_enter as *const () as usize);
98        self.native_symbols.write().unwrap().insert("__vm_scope_exit_void".to_string(), memory::scope_exit_void as *const () as usize);
99        self.native_symbols.write().unwrap().insert("__vm_scope_exit_dynamic".to_string(), memory::scope_exit_dynamic as *const () as usize);
100        self.native_symbols.write().unwrap().insert("__vm_scope_exit_bytes".to_string(), memory::scope_exit_bytes as *const () as usize);
101        self.native_symbols.write().unwrap().insert("__vm_struct_alloc".to_string(), native::struct_alloc as *const () as usize);
102        self.native_symbols.write().unwrap().insert("__vm_struct_from_ptr".to_string(), native::struct_from_ptr as *const () as usize);
103
104        let void_sig = self.get_sig(&[], Type::Void)?;
105        self.scope_enter_fn = Some(self.module.declare_function("__vm_scope_enter", cranelift_module::Linkage::Import, &void_sig)?);
106        self.scope_exit_void_fn = Some(self.module.declare_function("__vm_scope_exit_void", cranelift_module::Linkage::Import, &void_sig)?);
107
108        let dynamic_sig = self.get_sig(&[Type::Any], Type::Any)?;
109        self.scope_exit_dynamic_fn = Some(self.module.declare_function("__vm_scope_exit_dynamic", cranelift_module::Linkage::Import, &dynamic_sig)?);
110
111        let bytes_sig = self.get_sig(&[Type::Any, Type::I64], Type::Any)?;
112        self.scope_exit_bytes_fn = Some(self.module.declare_function("__vm_scope_exit_bytes", cranelift_module::Linkage::Import, &bytes_sig)?);
113
114        let struct_alloc_sig = self.get_sig(&[Type::I64], Type::Any)?;
115        self.struct_alloc_fn = Some(self.module.declare_function("__vm_struct_alloc", cranelift_module::Linkage::Import, &struct_alloc_sig)?);
116
117        let struct_from_ptr_sig = self.get_sig(&[Type::I64, Type::I64], Type::Any)?;
118        self.struct_from_ptr_fn = Some(self.module.declare_function("__vm_struct_from_ptr", cranelift_module::Linkage::Import, &struct_from_ptr_sig)?);
119        Ok(())
120    }
121
122    pub fn add_module(&mut self, name: &str) {
123        self.compiler.symbols.add_module(name.into());
124    }
125
126    pub fn pop_module(&mut self) {
127        self.compiler.symbols.pop_module();
128    }
129
130    pub fn add_type(&mut self, name: &str, ty: Type, is_pub: bool) -> u32 {
131        self.compiler.add_symbol(name, Symbol::Struct(ty, is_pub))
132    }
133
134    pub fn add_empty_type(&mut self, name: &str) -> Result<u32> {
135        match self.get_id(name) {
136            Ok(id) => Ok(id),
137            Err(_) => Ok(self.add_type(name, Type::Struct { params: Vec::new(), fields: Vec::new() }, true)),
138        }
139    }
140
141    pub fn add_native_module_ptr(&mut self, module: &str, name: &str, arg_tys: &[Type], ret_ty: Type, fn_ptr: *const u8) -> Result<u32> {
142        self.add_module(module);
143        let full_name = format!("{}::{}", module, name);
144        let result = self.add_native_ptr(&full_name, name, arg_tys, ret_ty, fn_ptr);
145        self.pop_module();
146        result
147    }
148
149    pub(crate) fn add_native_module_context_ptr(&mut self, module: &str, name: &str, arg_tys: &[Type], ret_ty: Type, fn_ptr: *const u8) -> Result<u32> {
150        self.add_module(module);
151        let full_name = format!("{}::{}", module, name);
152        let result = self.add_context_native_ptr(&full_name, name, arg_tys, ret_ty, fn_ptr);
153        self.pop_module();
154        result
155    }
156
157    pub fn add_native_method_ptr(&mut self, def: &str, method: &str, arg_tys: &[Type], ret_ty: Type, fn_ptr: *const u8) -> Result<u32> {
158        self.add_empty_type(def)?;
159        let full_name = format!("{}::{}", def, method);
160        let id = self.add_native_ptr(&full_name, &full_name, arg_tys, ret_ty, fn_ptr)?;
161        add_method_field(self, def, method, id)?;
162        Ok(id)
163    }
164
165    pub fn add_std(&mut self) -> Result<()> {
166        if self.compiler.symbols.get_id("std::print").is_ok() {
167            return Ok(());
168        }
169        self.add_module("std");
170        for (name, arg_tys, ret_ty, fn_ptr) in STD {
171            self.add_native_ptr(name, name, arg_tys, ret_ty, fn_ptr)?;
172        }
173        self.add_context_native_ptr("import", "import", &[Type::Any, Type::Any], Type::Bool, native::import_with_vm as *const u8)?;
174        Ok(())
175    }
176
177    pub fn add_any(&mut self) -> Result<()> {
178        if self.compiler.symbols.get_id("Any").is_ok() && self.compiler.symbols.get_id("Any::is_map").is_ok() {
179            return Ok(());
180        }
181        for (name, arg_tys, ret_ty, fn_ptr) in ANY {
182            let (_, method) = name.split_once("::").ok_or_else(|| anyhow!("非法 Any 方法名 {}", name))?;
183            self.add_native_method_ptr("Any", method, arg_tys, ret_ty, fn_ptr)?;
184        }
185        Ok(())
186    }
187
188    pub fn add_vec(&mut self) -> Result<()> {
189        self.add_empty_type("Vec")?;
190        let vec_def = Type::Symbol { id: self.get_id("Vec")?, params: Vec::new() };
191        self.add_inline("Vec::swap", vec![vec_def.clone(), Type::I64, Type::I64], Type::Void, |ctx: Option<&mut BuildContext>, args: Vec<Value>| {
192            if let Some(ctx) = ctx {
193                let width = ctx.builder.ins().iconst(types::I64, 4);
194                let offset_val = ctx.builder.ins().imul(args[1], width); // i * 4 i32大小四字节
195                let final_addr = ctx.builder.ins().iadd(args[0], offset_val); // base + (i*4)
196                let dest = ctx.builder.ins().imul(args[2], width);
197                let dest_addr = ctx.builder.ins().iadd(args[0], dest); // base + (i*4)
198                let dest_val = ctx.builder.ins().load(types::I32, MemFlags::trusted(), dest_addr, 0);
199                let v = ctx.builder.ins().load(types::I32, MemFlags::trusted(), final_addr, 0);
200                ctx.builder.ins().store(MemFlags::trusted(), v, dest_addr, 0);
201                ctx.builder.ins().store(MemFlags::trusted(), dest_val, final_addr, 0);
202            }
203            Err(anyhow!("无返回值"))
204        })?;
205
206        self.add_inline("Vec::get_idx", vec![vec_def.clone(), Type::I64], Type::I32, |ctx: Option<&mut BuildContext>, args: Vec<Value>| {
207            if let Some(ctx) = ctx {
208                let width = ctx.builder.ins().iconst(types::I64, 4);
209                let offset_val = ctx.builder.ins().imul(args[1], width); // i * 4 i32大小四字节
210                let final_addr = ctx.builder.ins().iadd(args[0], offset_val);
211                Ok((Some(ctx.builder.ins().load(types::I32, MemFlags::trusted(), final_addr, 0)), Type::I32))
212            } else {
213                Ok((None, Type::I32))
214            }
215        })?;
216        Ok(())
217    }
218
219    pub fn add_llm(&mut self) -> Result<()> {
220        add_native_module_fns(self, "llm", &llm_module::LLM_NATIVE)
221    }
222
223    pub fn add_root(&mut self) -> Result<()> {
224        add_native_module_fns(self, "root", &root_module::ROOT_NATIVE)?;
225        self.add_native_module_context_ptr("root", "add_fn", &[Type::Any, Type::Any], Type::Bool, root_module::root_add_fn_with_vm as *const u8)?;
226        Ok(())
227    }
228
229    pub fn add_http(&mut self) -> Result<()> {
230        add_native_module_fns(self, "http", &http_module::HTTP_NATIVE)
231    }
232
233    pub fn add_oss(&mut self) -> Result<()> {
234        add_native_module_fns(self, "oss", &oss_module::OSS_NATIVE)
235    }
236
237    pub fn add_db(&mut self) -> Result<()> {
238        add_native_module_fns(self, "db", &db_module::DB_NATIVE)
239    }
240
241    pub fn add_gpu(&mut self) -> Result<()> {
242        add_native_module_fns(self, "gpu", &gpu_module::GPU_NATIVE)
243    }
244
245    pub fn add_all(&mut self) -> Result<()> {
246        self.add_std()?;
247        self.add_any()?;
248        self.add_vec()?;
249        self.add_llm()?;
250        self.add_root()?;
251        self.add_http()?;
252        self.add_oss()?;
253        self.add_db()?;
254        self.add_gpu()?;
255        Ok(())
256    }
257}
258
259#[derive(Clone)]
260pub struct Vm {
261    jit: Arc<Mutex<JITRunTime>>,
262}
263
264#[derive(Clone)]
265pub struct CompiledFn {
266    ptr: usize,
267    ret: Type,
268    owner: Vm,
269}
270
271impl CompiledFn {
272    pub fn ptr(&self) -> *const u8 {
273        self.ptr as *const u8
274    }
275
276    pub fn ret_ty(&self) -> &Type {
277        &self.ret
278    }
279
280    pub fn owner(&self) -> &Vm {
281        &self.owner
282    }
283}
284
285impl Vm {
286    pub fn new() -> Self {
287        dynamic::set_dynamic_return_handler(memory::take_dynamic_return);
288        let jit = Arc::new(Mutex::new(JITRunTime::new(|_| {})));
289        {
290            let mut guard = jit.lock().unwrap();
291            guard.set_owner(Arc::downgrade(&jit));
292            guard.add_memory_runtime().expect("register VM memory runtime");
293            guard.add_std().expect("register VM std runtime");
294            guard.add_any().expect("register VM Any runtime");
295        }
296        Self { jit }
297    }
298
299    pub fn with_all() -> Result<Self> {
300        let vm = Self::new();
301        vm.add_all()?;
302        Ok(vm)
303    }
304
305    pub fn add_module(&self, name: &str) {
306        self.jit.lock().unwrap().add_module(name)
307    }
308
309    pub fn pop_module(&self) {
310        self.jit.lock().unwrap().pop_module()
311    }
312
313    pub fn add_type(&self, name: &str, ty: Type, is_pub: bool) -> u32 {
314        self.jit.lock().unwrap().add_type(name, ty, is_pub)
315    }
316
317    pub fn add_empty_type(&self, name: &str) -> Result<u32> {
318        self.jit.lock().unwrap().add_empty_type(name)
319    }
320
321    pub fn add_std(&self) -> Result<()> {
322        self.jit.lock().unwrap().add_std()
323    }
324
325    pub fn add_any(&self) -> Result<()> {
326        self.jit.lock().unwrap().add_any()
327    }
328
329    pub fn add_vec(&self) -> Result<()> {
330        self.jit.lock().unwrap().add_vec()
331    }
332
333    pub fn add_llm(&self) -> Result<()> {
334        self.jit.lock().unwrap().add_llm()
335    }
336
337    pub fn add_root(&self) -> Result<()> {
338        self.jit.lock().unwrap().add_root()
339    }
340
341    pub fn add_http(&self) -> Result<()> {
342        self.jit.lock().unwrap().add_http()
343    }
344
345    pub fn add_oss(&self) -> Result<()> {
346        self.jit.lock().unwrap().add_oss()
347    }
348
349    pub fn add_db(&self) -> Result<()> {
350        self.jit.lock().unwrap().add_db()
351    }
352
353    pub fn add_gpu(&self) -> Result<()> {
354        self.jit.lock().unwrap().add_gpu()
355    }
356
357    pub fn add_all(&self) -> Result<()> {
358        self.jit.lock().unwrap().add_all()
359    }
360
361    pub fn add_native_ptr(&self, full_name: &str, name: &str, arg_tys: &[Type], ret_ty: Type, fn_ptr: *const u8) -> Result<u32> {
362        self.jit.lock().unwrap().add_native_ptr(full_name, name, arg_tys, ret_ty, fn_ptr)
363    }
364
365    pub fn add_native_module_ptr(&self, module: &str, name: &str, arg_tys: &[Type], ret_ty: Type, fn_ptr: *const u8) -> Result<u32> {
366        self.jit.lock().unwrap().add_native_module_ptr(module, name, arg_tys, ret_ty, fn_ptr)
367    }
368
369    pub fn add_native_method_ptr(&self, def: &str, method: &str, arg_tys: &[Type], ret_ty: Type, fn_ptr: *const u8) -> Result<u32> {
370        self.jit.lock().unwrap().add_native_method_ptr(def, method, arg_tys, ret_ty, fn_ptr)
371    }
372
373    pub fn add_inline(&self, name: &str, args: Vec<Type>, ret: Type, f: fn(Option<&mut BuildContext>, Vec<Value>) -> Result<(Option<Value>, Type)>) -> Result<u32> {
374        self.jit.lock().unwrap().add_inline(name, args, ret, f)
375    }
376
377    pub fn import_code(&self, name: &str, code: Vec<u8>) -> Result<()> {
378        self.jit.lock().unwrap().import_code(name, code)
379    }
380
381    pub fn import_file(&self, name: &str, path: &str) -> Result<()> {
382        self.jit.lock().unwrap().compiler.import_file(name, path)?;
383        Ok(())
384    }
385
386    pub fn import(&self, name: &str, path: &str) -> Result<()> {
387        if root::contains(path) {
388            let code = root::get(path).unwrap();
389            if code.is_str() {
390                self.import_code(name, code.as_str().as_bytes().to_vec())
391            } else {
392                self.import_code(name, code.get_dynamic("code").ok_or(anyhow!("{:?} 没有 code 成员", code))?.as_str().as_bytes().to_vec())
393            }
394        } else {
395            self.import_file(name, path)
396        }
397    }
398
399    pub fn infer(&self, name: &str, arg_tys: &[Type]) -> Result<Type> {
400        self.jit.lock().unwrap().get_type(name, arg_tys)
401    }
402
403    pub fn get_fn_ptr(&self, name: &str, arg_tys: &[Type]) -> Result<(*const u8, Type)> {
404        self.jit.lock().unwrap().get_fn_ptr(name, arg_tys)
405    }
406
407    pub fn get_fn(&self, name: &str, arg_tys: &[Type]) -> Result<CompiledFn> {
408        let (ptr, ret) = self.get_fn_ptr(name, arg_tys)?;
409        Ok(CompiledFn { ptr: ptr as usize, ret, owner: self.clone() })
410    }
411
412    pub fn load(&self, code: Vec<u8>, arg_name: SmolStr) -> Result<(i64, Type)> {
413        self.jit.lock().unwrap().load(code, arg_name)
414    }
415
416    pub fn get_symbol(&self, name: &str, params: Vec<Type>) -> Result<Type> {
417        Ok(Type::Symbol { id: self.jit.lock().unwrap().get_id(name)?, params })
418    }
419
420    pub fn gpu_struct_layout(&self, name: &str, params: &[Type]) -> Result<GpuStructLayout> {
421        let jit = self.jit.lock().unwrap();
422        GpuStructLayout::from_symbol_table(&jit.compiler.symbols, name, params)
423    }
424
425    pub fn disassemble(&self, name: &str) -> Result<String> {
426        self.jit.lock().unwrap().compiler.symbols.disassemble(name)
427    }
428
429    #[cfg(feature = "ir-disassembly")]
430    pub fn disassemble_ir(&self, name: &str) -> Result<String> {
431        self.jit.lock().unwrap().disassemble_ir(name)
432    }
433}
434
435impl Default for Vm {
436    fn default() -> Self {
437        Self::new()
438    }
439}
440
441#[cfg(test)]
442mod tests {
443    use super::Vm;
444    use dynamic::{Dynamic, ToJson, Type};
445
446    extern "C" fn math_double(value: i64) -> i64 {
447        value * 2
448    }
449
450    #[test]
451    fn vm_can_add_native_after_jit_creation() -> anyhow::Result<()> {
452        let vm = Vm::new();
453        vm.add_native_module_ptr("math", "double", &[Type::I64], Type::I64, math_double as *const u8)?;
454        vm.import_code(
455            "vm_dynamic_native",
456            br#"
457            pub fn run(value: i64) {
458                math::double(value)
459            }
460            "#
461            .to_vec(),
462        )?;
463
464        let compiled = vm.get_fn("vm_dynamic_native::run", &[Type::I64])?;
465        assert_eq!(compiled.ret_ty(), &Type::I64);
466        let run: extern "C" fn(i64) -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
467        assert_eq!(run(21), 42);
468        Ok(())
469    }
470
471    #[test]
472    fn vm_new_registers_std_and_any() -> anyhow::Result<()> {
473        let vm = Vm::new();
474        vm.add_std()?;
475        vm.add_any()?;
476        assert_eq!(vm.infer("std::print", &[Type::Any])?, Type::Void);
477
478        vm.import_code(
479            "vm_new_default_any",
480            br#"
481            pub fn has_items(content) {
482                if content.is_map() {
483                    if content.contains("items") {
484                        return content.items.len() > 0;
485                    }
486                }
487                false
488            }
489            "#
490            .to_vec(),
491        )?;
492
493        assert_eq!(vm.infer("vm_new_default_any::has_items", &[Type::Any])?, Type::Bool);
494        let compiled = vm.get_fn("vm_new_default_any::has_items", &[Type::Any])?;
495        assert_eq!(compiled.ret_ty(), &Type::Bool);
496        Ok(())
497    }
498
499    #[test]
500    fn nested_struct_arg_return_struct_field_is_static_field_access() -> anyhow::Result<()> {
501        let vm = Vm::with_all()?;
502        vm.import_code(
503            "vm_nested_struct_return_field",
504            br#"
505            pub struct Inner {
506                value: i64,
507            }
508
509            pub struct RoleMini {
510                inner: Inner,
511                hp: i64,
512            }
513
514            pub struct TeamMini {
515                role: RoleMini,
516            }
517
518            pub struct BigSummary {
519                winner: i64,
520                loser: i64,
521            }
522
523            pub fn make_big_with_team(team: TeamMini) {
524                let score = team.role.inner.value;
525                BigSummary{winner: score, loser: 0}
526            }
527
528            pub fn read_team_winner_direct() {
529                let team = TeamMini{role: RoleMini{inner: Inner{value: 9}, hp: 1}};
530                make_big_with_team(team).winner
531            }
532
533            pub fn read_team_winner_bound() {
534                let team = TeamMini{role: RoleMini{inner: Inner{value: 9}, hp: 1}};
535                let summary = make_big_with_team(team);
536                summary.winner
537            }
538            "#
539            .to_vec(),
540        )?;
541
542        let compiled = vm.get_fn("vm_nested_struct_return_field::read_team_winner_direct", &[])?;
543        assert_eq!(compiled.ret_ty(), &Type::I64);
544        let direct: extern "C" fn() -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
545        assert_eq!(direct(), 9);
546
547        let compiled = vm.get_fn("vm_nested_struct_return_field::read_team_winner_bound", &[])?;
548        assert_eq!(compiled.ret_ty(), &Type::I64);
549        let bound: extern "C" fn() -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
550        assert_eq!(bound(), 9);
551        Ok(())
552    }
553
554    #[test]
555    fn any_push_does_not_consume_reused_value() -> anyhow::Result<()> {
556        let vm = Vm::with_all()?;
557        vm.import_code(
558            "vm_any_push_reused_value",
559            br#"
560            pub fn run() {
561                let role_id = "acct_role_2";
562                let updated = [];
563                updated.push(role_id);
564                {
565                    ok: true,
566                    user_id: role_id,
567                    first: updated.get_idx(0)
568                }
569            }
570            "#
571            .to_vec(),
572        )?;
573
574        let compiled = vm.get_fn("vm_any_push_reused_value::run", &[])?;
575        assert_eq!(compiled.ret_ty(), &Type::Any);
576        let run: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
577        let result = unsafe { &*run() };
578        assert_eq!(result.get_dynamic("ok").and_then(|value| value.as_bool()), Some(true));
579        assert_eq!(result.get_dynamic("user_id").map(|value| value.as_str().to_string()), Some("acct_role_2".to_string()));
580        assert_eq!(result.get_dynamic("first").map(|value| value.as_str().to_string()), Some("acct_role_2".to_string()));
581        Ok(())
582    }
583
584    #[test]
585    fn compares_any_with_string_literal_as_string() -> anyhow::Result<()> {
586        let vm = Vm::with_all()?;
587        vm.import_code(
588            "vm_string_compare_any",
589            br#"
590            pub fn any_ne_empty(chat_path) {
591                chat_path != ""
592            }
593            "#
594            .to_vec(),
595        )?;
596
597        let compiled = vm.get_fn("vm_string_compare_any::any_ne_empty", &[Type::Any])?;
598        assert_eq!(compiled.ret_ty(), &Type::Bool);
599
600        let any_ne_empty: extern "C" fn(*const Dynamic) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
601        let empty = Dynamic::from("");
602        let non_empty = Dynamic::from("chat");
603
604        assert!(!any_ne_empty(&empty));
605        assert!(any_ne_empty(&non_empty));
606        Ok(())
607    }
608
609    #[test]
610    fn compares_bool_values_and_bool_literals() -> anyhow::Result<()> {
611        let vm = Vm::with_all()?;
612        vm.import_code(
613            "vm_bool_compare",
614            br#"
615            pub fn eq_true(value: bool) {
616                value == true
617            }
618
619            pub fn ne_false(value: bool) {
620                value != false
621            }
622
623            pub fn literal_left(value: bool) {
624                true == value
625            }
626
627            pub fn eq_pair(left: bool, right: bool) {
628                left == right
629            }
630
631            pub fn logic_pair(left: bool, right: bool) {
632                (left && right) || (left == true && right != false)
633            }
634            "#
635            .to_vec(),
636        )?;
637
638        let compiled = vm.get_fn("vm_bool_compare::eq_true", &[Type::Bool])?;
639        assert_eq!(compiled.ret_ty(), &Type::Bool);
640        let eq_true: extern "C" fn(bool) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
641        assert!(eq_true(true));
642        assert!(!eq_true(false));
643
644        let compiled = vm.get_fn("vm_bool_compare::ne_false", &[Type::Bool])?;
645        assert_eq!(compiled.ret_ty(), &Type::Bool);
646        let ne_false: extern "C" fn(bool) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
647        assert!(ne_false(true));
648        assert!(!ne_false(false));
649
650        let compiled = vm.get_fn("vm_bool_compare::literal_left", &[Type::Bool])?;
651        assert_eq!(compiled.ret_ty(), &Type::Bool);
652        let literal_left: extern "C" fn(bool) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
653        assert!(literal_left(true));
654        assert!(!literal_left(false));
655
656        let compiled = vm.get_fn("vm_bool_compare::eq_pair", &[Type::Bool, Type::Bool])?;
657        assert_eq!(compiled.ret_ty(), &Type::Bool);
658        let eq_pair: extern "C" fn(bool, bool) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
659        assert!(eq_pair(true, true));
660        assert!(eq_pair(false, false));
661        assert!(!eq_pair(true, false));
662        assert!(!eq_pair(false, true));
663
664        let compiled = vm.get_fn("vm_bool_compare::logic_pair", &[Type::Bool, Type::Bool])?;
665        assert_eq!(compiled.ret_ty(), &Type::Bool);
666        let logic_pair: extern "C" fn(bool, bool) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
667        assert!(logic_pair(true, true));
668        assert!(!logic_pair(true, false));
669        assert!(!logic_pair(false, true));
670        assert!(!logic_pair(false, false));
671        Ok(())
672    }
673
674    #[test]
675    fn parenthesized_expression_can_call_any_method() -> anyhow::Result<()> {
676        let vm = Vm::with_all()?;
677        vm.import_code(
678            "vm_parenthesized_method_call",
679            br#"
680            pub fn run(value) {
681                (value + 2).to_i64()
682            }
683            "#
684            .to_vec(),
685        )?;
686
687        let compiled = vm.get_fn("vm_parenthesized_method_call::run", &[Type::Any])?;
688        assert_eq!(compiled.ret_ty(), &Type::I64);
689        let run: extern "C" fn(*const Dynamic) -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
690        let value = Dynamic::from(40i64);
691
692        assert_eq!(run(&value), 42);
693        Ok(())
694    }
695
696    #[test]
697    fn casts_any_float_to_i32_without_zeroing() -> anyhow::Result<()> {
698        let vm = Vm::with_all()?;
699        vm.import_code(
700            "vm_any_float_to_i32",
701            br#"
702            pub fn direct(value) {
703                value as i32
704            }
705
706            pub fn map_field(value) {
707                let field = value.v;
708                field as i32
709            }
710
711            pub fn damage(attacker, def_rate) {
712                let x = attacker.atk * (1.0 - def_rate);
713                x as i32
714            }
715            "#
716            .to_vec(),
717        )?;
718
719        let compiled = vm.get_fn("vm_any_float_to_i32::direct", &[Type::Any])?;
720        assert_eq!(compiled.ret_ty(), &Type::I32);
721        let direct: extern "C" fn(*const Dynamic) -> i32 = unsafe { std::mem::transmute(compiled.ptr()) };
722        let value = Dynamic::from(9.5f64);
723        assert_eq!(direct(&value), 9);
724
725        let compiled = vm.get_fn("vm_any_float_to_i32::map_field", &[Type::Any])?;
726        assert_eq!(compiled.ret_ty(), &Type::I32);
727        let map_field: extern "C" fn(*const Dynamic) -> i32 = unsafe { std::mem::transmute(compiled.ptr()) };
728        let value = dynamic::map!("v"=> 9.5f64);
729        assert_eq!(map_field(&value), 9);
730
731        let compiled = vm.get_fn("vm_any_float_to_i32::damage", &[Type::Any, Type::Any])?;
732        assert_eq!(compiled.ret_ty(), &Type::I32);
733        let damage: extern "C" fn(*const Dynamic, *const Dynamic) -> i32 = unsafe { std::mem::transmute(compiled.ptr()) };
734        let attacker = dynamic::map!("atk"=> 64i64);
735        let def_rate = Dynamic::from(0.17f64);
736        assert_eq!(damage(&attacker, &def_rate), 53);
737        Ok(())
738    }
739
740    #[test]
741    fn binary_imm_promotes_integer_literals_for_float_left_values() -> anyhow::Result<()> {
742        let vm = Vm::with_all()?;
743        vm.import_code(
744            "vm_float_binary_imm",
745            br#"
746            pub fn add_f32(value: f32) {
747                value + 1i32
748            }
749
750            pub fn sub_f32(value: f32) {
751                value - 1i32
752            }
753
754            pub fn mul_f32(value: f32) {
755                value * 2i32
756            }
757
758            pub fn div_f32(value: f32) {
759                value / 2i32
760            }
761
762            pub fn gt_f32(value: f32) {
763                value > 2i32
764            }
765            "#
766            .to_vec(),
767        )?;
768
769        let compiled = vm.get_fn("vm_float_binary_imm::add_f32", &[Type::F32])?;
770        assert_eq!(compiled.ret_ty(), &Type::F32);
771        let add_f32: extern "C" fn(f32) -> f32 = unsafe { std::mem::transmute(compiled.ptr()) };
772        assert_eq!(add_f32(2.5), 3.5);
773
774        let compiled = vm.get_fn("vm_float_binary_imm::sub_f32", &[Type::F32])?;
775        assert_eq!(compiled.ret_ty(), &Type::F32);
776        let sub_f32: extern "C" fn(f32) -> f32 = unsafe { std::mem::transmute(compiled.ptr()) };
777        assert_eq!(sub_f32(2.5), 1.5);
778
779        let compiled = vm.get_fn("vm_float_binary_imm::mul_f32", &[Type::F32])?;
780        assert_eq!(compiled.ret_ty(), &Type::F32);
781        let mul_f32: extern "C" fn(f32) -> f32 = unsafe { std::mem::transmute(compiled.ptr()) };
782        assert_eq!(mul_f32(2.5), 5.0);
783
784        let compiled = vm.get_fn("vm_float_binary_imm::div_f32", &[Type::F32])?;
785        assert_eq!(compiled.ret_ty(), &Type::F32);
786        let div_f32: extern "C" fn(f32) -> f32 = unsafe { std::mem::transmute(compiled.ptr()) };
787        assert_eq!(div_f32(5.0), 2.5);
788
789        let compiled = vm.get_fn("vm_float_binary_imm::gt_f32", &[Type::F32])?;
790        assert_eq!(compiled.ret_ty(), &Type::Bool);
791        let gt_f32: extern "C" fn(f32) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
792        assert!(gt_f32(2.5));
793        assert!(!gt_f32(1.5));
794        Ok(())
795    }
796
797    #[test]
798    fn any_keys_returns_map_keys_and_empty_list_for_other_values() -> anyhow::Result<()> {
799        let vm = Vm::with_all()?;
800        vm.import_code(
801            "vm_any_keys",
802            br#"
803            pub fn map_keys(value) {
804                let keys = value.keys();
805                keys.len() == 2 && keys.contains("alpha") && keys.contains("beta")
806            }
807
808            pub fn non_map_keys(value) {
809                value.keys().len() == 0
810            }
811            "#
812            .to_vec(),
813        )?;
814
815        let compiled = vm.get_fn("vm_any_keys::map_keys", &[Type::Any])?;
816        assert_eq!(compiled.ret_ty(), &Type::Bool);
817        let map_keys: extern "C" fn(*const Dynamic) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
818        let value = dynamic::map!("alpha"=> 1i64, "beta"=> 2i64);
819        assert!(map_keys(&value));
820
821        let compiled = vm.get_fn("vm_any_keys::non_map_keys", &[Type::Any])?;
822        assert_eq!(compiled.ret_ty(), &Type::Bool);
823        let non_map_keys: extern "C" fn(*const Dynamic) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
824        let value = Dynamic::from("alpha");
825        assert!(non_map_keys(&value));
826        Ok(())
827    }
828
829    #[test]
830    fn string_methods_work_on_static_string_and_any_string_values() -> anyhow::Result<()> {
831        let vm = Vm::with_all()?;
832        vm.import_code(
833            "vm_string_methods",
834            br#"
835            pub fn static_string_methods(text: string) {
836                let parts = text.split(",");
837                text.starts_with("alpha")
838                    && text.is_string()
839                    && !text.is_null()
840                    && parts.len() == 2
841                    && parts.get_idx(0) == "alpha"
842                    && parts.get_idx(1) == "beta"
843            }
844
845            pub fn any_string_methods(value) {
846                let parts = value.split(",");
847                value.starts_with("alpha")
848                    && value.is_string()
849                    && !value.is_null()
850                    && parts.len() == 2
851                    && parts.get_idx(0) == "alpha"
852                    && parts.get_idx(1) == "beta"
853            }
854
855            pub fn any_null_methods(value) {
856                value.is_null() && !value.is_string()
857            }
858            "#
859            .to_vec(),
860        )?;
861
862        let compiled = vm.get_fn("vm_string_methods::static_string_methods", &[Type::Str])?;
863        assert_eq!(compiled.ret_ty(), &Type::Bool);
864        let static_string_methods: extern "C" fn(*const Dynamic) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
865        let text = Dynamic::from("alpha,beta");
866        assert!(static_string_methods(&text));
867
868        let compiled = vm.get_fn("vm_string_methods::any_string_methods", &[Type::Any])?;
869        assert_eq!(compiled.ret_ty(), &Type::Bool);
870        let any_string_methods: extern "C" fn(*const Dynamic) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
871        assert!(any_string_methods(&text));
872
873        let compiled = vm.get_fn("vm_string_methods::any_null_methods", &[Type::Any])?;
874        assert_eq!(compiled.ret_ty(), &Type::Bool);
875        let any_null_methods: extern "C" fn(*const Dynamic) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
876        let value = Dynamic::Null;
877        assert!(any_null_methods(&value));
878        Ok(())
879    }
880
881    #[test]
882    fn primitive_type_check_methods_call_any_runtime() -> anyhow::Result<()> {
883        let vm = Vm::with_all()?;
884        vm.import_code(
885            "vm_primitive_type_check_methods",
886            br#"
887            pub fn int_checks() {
888                !42i64.is_list()
889                    && !42i64.is_map()
890                    && !42i64.is_string()
891                    && !42i64.is_null()
892            }
893
894            pub fn bool_checks() {
895                !true.is_list() && !true.is_map() && !true.is_null()
896            }
897            "#
898            .to_vec(),
899        )?;
900
901        let compiled = vm.get_fn("vm_primitive_type_check_methods::int_checks", &[])?;
902        assert_eq!(compiled.ret_ty(), &Type::Bool);
903        let int_checks: extern "C" fn() -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
904        assert!(int_checks());
905
906        let compiled = vm.get_fn("vm_primitive_type_check_methods::bool_checks", &[])?;
907        assert_eq!(compiled.ret_ty(), &Type::Bool);
908        let bool_checks: extern "C" fn() -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
909        assert!(bool_checks());
910        Ok(())
911    }
912
913    #[test]
914    fn for_loop_iterates_any_list_and_map_values() -> anyhow::Result<()> {
915        let vm = Vm::with_all()?;
916        vm.import_code(
917            "vm_for_any_collections",
918            br#"
919            pub fn list_sum(items) {
920                let total = 0i64;
921                for item in items {
922                    total += item;
923                }
924                total
925            }
926
927            pub fn map_sum(data) {
928                let total = 0i64;
929                for (key, value) in data {
930                    total += value;
931                }
932                total
933            }
934            "#
935            .to_vec(),
936        )?;
937
938        let compiled = vm.get_fn("vm_for_any_collections::list_sum", &[Type::Any])?;
939        assert_eq!(compiled.ret_ty(), &Type::Any);
940        let list_sum: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
941        let items = Dynamic::list(vec![1i64.into(), 2i64.into(), 3i64.into()]);
942        let result = unsafe { &*list_sum(&items) };
943        assert_eq!(result.as_int(), Some(6));
944
945        let compiled = vm.get_fn("vm_for_any_collections::map_sum", &[Type::Any])?;
946        assert_eq!(compiled.ret_ty(), &Type::Any);
947        let map_sum: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
948        let data = dynamic::map!("a"=> 4i64, "b"=> 5i64);
949        let result = unsafe { &*map_sum(&data) };
950        assert_eq!(result.as_int(), Some(9));
951        Ok(())
952    }
953
954    #[test]
955    fn compares_concrete_value_with_string_literal_as_string() -> anyhow::Result<()> {
956        let vm = Vm::with_all()?;
957        vm.import_code(
958            "vm_string_compare_imm",
959            br#"
960            pub fn int_eq_str(value: i64) {
961                value == "42"
962            }
963
964            pub fn int_to_str(value: i64) {
965                value + ""
966            }
967            "#
968            .to_vec(),
969        )?;
970
971        let compiled = vm.get_fn("vm_string_compare_imm::int_eq_str", &[Type::I64])?;
972        assert_eq!(compiled.ret_ty(), &Type::Bool);
973
974        let int_eq_str: extern "C" fn(i64) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
975
976        let compiled = vm.get_fn("vm_string_compare_imm::int_to_str", &[Type::I64])?;
977        assert_eq!(compiled.ret_ty(), &Type::Any);
978        let int_to_str: extern "C" fn(i64) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
979        let text = int_to_str(42);
980        assert_eq!(unsafe { &*text }.as_str(), "42");
981
982        assert!(int_eq_str(42));
983        assert!(!int_eq_str(7));
984        Ok(())
985    }
986
987    #[test]
988    fn concatenates_string_with_integer_values() -> anyhow::Result<()> {
989        let vm = Vm::with_all()?;
990        vm.import_code(
991            "vm_string_concat_integer",
992            br#"
993            pub fn idx_key(idx: i64) {
994                "" + idx
995            }
996
997            pub fn level_text(level: i64) {
998                "" + level + " level"
999            }
1000
1001            pub fn gold_text(currency) {
1002                "" + currency.gold
1003            }
1004            "#
1005            .to_vec(),
1006        )?;
1007
1008        let compiled = vm.get_fn("vm_string_concat_integer::idx_key", &[Type::I64])?;
1009        let idx_key: extern "C" fn(i64) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1010        let result = unsafe { &*idx_key(7) };
1011        assert_eq!(result.as_str(), "7");
1012
1013        let compiled = vm.get_fn("vm_string_concat_integer::level_text", &[Type::I64])?;
1014        let level_text: extern "C" fn(i64) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1015        let result = unsafe { &*level_text(12) };
1016        assert_eq!(result.as_str(), "12 level");
1017
1018        let compiled = vm.get_fn("vm_string_concat_integer::gold_text", &[Type::Any])?;
1019        let gold_text: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1020        let currency = dynamic::map!("gold"=> 345i64);
1021        let result = unsafe { &*gold_text(&currency) };
1022        assert_eq!(result.as_str(), "345");
1023        Ok(())
1024    }
1025
1026    #[test]
1027    fn coerces_string_concat_to_i64_without_unimplemented_log() -> anyhow::Result<()> {
1028        let vm = Vm::with_all()?;
1029        vm.import_code(
1030            "vm_string_concat_to_i64",
1031            br#"
1032            pub fn run(idx: i64) {
1033                ("" + idx) as i64
1034            }
1035            "#
1036            .to_vec(),
1037        )?;
1038
1039        let compiled = vm.get_fn("vm_string_concat_to_i64::run", &[Type::I64])?;
1040        assert_eq!(compiled.ret_ty(), &Type::I64);
1041        let run: extern "C" fn(i64) -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
1042        assert_eq!(run(7), 0);
1043        Ok(())
1044    }
1045
1046    #[test]
1047    fn unifies_explicit_return_and_tail_integer_widths() -> anyhow::Result<()> {
1048        let vm = Vm::with_all()?;
1049        vm.import_code(
1050            "vm_return_integer_widths",
1051            br#"
1052            pub fn selected(flag, slot) {
1053                if flag {
1054                    return slot;
1055                }
1056                0
1057            }
1058            "#
1059            .to_vec(),
1060        )?;
1061
1062        let compiled = vm.get_fn("vm_return_integer_widths::selected", &[Type::Bool, Type::I64])?;
1063        assert_eq!(compiled.ret_ty(), &Type::I64);
1064        let selected: extern "C" fn(bool, i64) -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
1065
1066        assert_eq!(selected(true, 7), 7);
1067        assert_eq!(selected(false, 7), 0);
1068        Ok(())
1069    }
1070
1071    #[test]
1072    fn root_contains_string_concat_is_bool_condition() -> anyhow::Result<()> {
1073        let vm = Vm::with_all()?;
1074        vm.import_code(
1075            "vm_root_contains_condition",
1076            br#"
1077            pub fn exists(user_id) {
1078                if root::contains("redis/user/" + user_id) {
1079                    return 1;
1080                }
1081                0
1082            }
1083            "#
1084            .to_vec(),
1085        )?;
1086
1087        assert_eq!(vm.infer("root::contains", &[Type::Any])?, Type::Bool);
1088        let compiled = vm.get_fn("vm_root_contains_condition::exists", &[Type::Any])?;
1089        assert_eq!(compiled.ret_ty(), &Type::I32);
1090        Ok(())
1091    }
1092
1093    #[test]
1094    fn root_add_map_can_be_printed() -> anyhow::Result<()> {
1095        let vm = Vm::with_all()?;
1096        assert_eq!(vm.infer("root::add_map", &[Type::Any])?, Type::Bool);
1097        vm.import_code(
1098            "vm_root_add_map_print",
1099            br#"
1100            pub fn run() {
1101                print(root::add_map("local/world_handlers/til_map_novicevillage"));
1102            }
1103            "#
1104            .to_vec(),
1105        )?;
1106
1107        let compiled = vm.get_fn("vm_root_add_map_print::run", &[])?;
1108        assert!(compiled.ret_ty().is_void());
1109        Ok(())
1110    }
1111
1112    #[test]
1113    fn std_log_accepts_any_and_returns_void() -> anyhow::Result<()> {
1114        let vm = Vm::with_all()?;
1115        vm.import_code(
1116            "vm_std_log",
1117            br#"
1118            pub fn run(value) {
1119                log({ ok: true, value: value });
1120            }
1121            "#
1122            .to_vec(),
1123        )?;
1124
1125        let compiled = vm.get_fn("vm_std_log::run", &[Type::Any])?;
1126        assert!(compiled.ret_ty().is_void());
1127        let run: extern "C" fn(*const Dynamic) = unsafe { std::mem::transmute(compiled.ptr()) };
1128        let value = Dynamic::from(7i64);
1129        run(&value);
1130        Ok(())
1131    }
1132
1133    #[test]
1134    fn unary_not_any_loop_var_is_bool_condition() -> anyhow::Result<()> {
1135        let vm = Vm::with_all()?;
1136        vm.import_code(
1137            "vm_unary_not_any_loop_var",
1138            br#"
1139            pub fn count_missing(flags) {
1140                let missing = 0;
1141                for exists in flags {
1142                    if !exists {
1143                        missing = missing + 1;
1144                    }
1145                }
1146                missing
1147            }
1148            "#
1149            .to_vec(),
1150        )?;
1151
1152        let compiled = vm.get_fn("vm_unary_not_any_loop_var::count_missing", &[Type::Any])?;
1153        assert_eq!(compiled.ret_ty(), &Type::I32);
1154        Ok(())
1155    }
1156
1157    #[test]
1158    fn closure_literal_can_be_called_immediately() -> anyhow::Result<()> {
1159        let vm = Vm::with_all()?;
1160        vm.import_code(
1161            "vm_closure_immediate_call",
1162            br#"
1163            pub fn no_args() {
1164                let r = || { 1i32 }();
1165                r
1166            }
1167
1168            pub fn with_arg() {
1169                |value: i32| { value + 1i32 }(2i32)
1170            }
1171            "#
1172            .to_vec(),
1173        )?;
1174
1175        let compiled = vm.get_fn("vm_closure_immediate_call::no_args", &[])?;
1176        assert_eq!(compiled.ret_ty(), &Type::I32);
1177        let no_args: extern "C" fn() -> i32 = unsafe { std::mem::transmute(compiled.ptr()) };
1178        assert_eq!(no_args(), 1);
1179
1180        let compiled = vm.get_fn("vm_closure_immediate_call::with_arg", &[])?;
1181        assert_eq!(compiled.ret_ty(), &Type::I32);
1182        let with_arg: extern "C" fn() -> i32 = unsafe { std::mem::transmute(compiled.ptr()) };
1183        assert_eq!(with_arg(), 3);
1184        Ok(())
1185    }
1186
1187    #[test]
1188    fn semicolon_tail_call_makes_function_void() -> anyhow::Result<()> {
1189        let vm = Vm::with_all()?;
1190        vm.import_code(
1191            "vm_semicolon_tail_void",
1192            br#"
1193            pub fn send_role_select(idx, account_id, selected_slot) {
1194                root::send("local/ui/send_dialog", {
1195                    idx: idx,
1196                    account_id: account_id,
1197                    selected_slot: selected_slot
1198                });
1199            }
1200            "#
1201            .to_vec(),
1202        )?;
1203
1204        let compiled = vm.get_fn("vm_semicolon_tail_void::send_role_select", &[Type::Any, Type::Any, Type::Any])?;
1205        assert_eq!(compiled.ret_ty(), &Type::Void);
1206        Ok(())
1207    }
1208
1209    #[test]
1210    fn bare_return_conflicts_with_non_void_return() -> anyhow::Result<()> {
1211        let vm = Vm::with_all()?;
1212        vm.import_code(
1213            "vm_bare_return_conflict",
1214            br#"
1215            pub fn run(flag) {
1216                if flag {
1217                    return;
1218                }
1219                1
1220            }
1221            "#
1222            .to_vec(),
1223        )?;
1224
1225        let err = match vm.get_fn("vm_bare_return_conflict::run", &[Type::Bool]) {
1226            Ok(_) => panic!("expected mismatched return types to fail"),
1227            Err(err) => err,
1228        };
1229        assert!(format!("{err:#}").contains("返回类型不一致"));
1230        Ok(())
1231    }
1232
1233    #[test]
1234    fn root_get_accepts_string_concat_with_dynamic_field() -> anyhow::Result<()> {
1235        let vm = Vm::with_all()?;
1236        vm.import_code(
1237            "vm_root_get_dynamic_concat",
1238            br#"
1239            pub fn get_action(req) {
1240                root::get("local/game/panel_actions/" + req.idx)
1241            }
1242            "#
1243            .to_vec(),
1244        )?;
1245
1246        root::add("local/game/panel_actions/7", dynamic::map!("id"=> "action-7").into())?;
1247        let compiled = vm.get_fn("vm_root_get_dynamic_concat::get_action", &[Type::Any])?;
1248        assert_eq!(compiled.ret_ty(), &Type::Any);
1249        let get_action: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1250        let req = dynamic::map!("idx"=> 7i64);
1251        let result = unsafe { &*get_action(&req) };
1252
1253        assert_eq!(result.get_dynamic("id").map(|value| value.as_str().to_string()), Some("action-7".to_string()));
1254        Ok(())
1255    }
1256
1257    #[test]
1258    fn root_add_fn_registers_handler_with_dynamic_field_path_concat() -> anyhow::Result<()> {
1259        let vm = Vm::with_all()?;
1260        vm.import_code(
1261            "vm_registered_panel_action",
1262            br#"
1263            pub fn panel_action(req) {
1264                root::get("local/game/panel_actions/" + req.idx)
1265            }
1266
1267            pub fn register() {
1268                root::add_fn("local/ui/panel_action", "vm_registered_panel_action::panel_action")
1269            }
1270            "#
1271            .to_vec(),
1272        )?;
1273
1274        let compiled = vm.get_fn("vm_registered_panel_action::register", &[])?;
1275        assert_eq!(compiled.ret_ty(), &Type::Bool);
1276        let register: extern "C" fn() -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
1277        assert!(register());
1278        Ok(())
1279    }
1280
1281    #[test]
1282    fn root_add_fn_accepts_string_concat_in_registered_handler() -> anyhow::Result<()> {
1283        let vm = Vm::with_all()?;
1284        vm.import_code(
1285            "vm_registered_string_concat",
1286            br#"
1287            pub fn send_panel(idx: i64) {
1288                let idx_key = "" + idx;
1289                idx_key
1290            }
1291            "#
1292            .to_vec(),
1293        )?;
1294
1295        assert!(vm.get_fn_ptr("vm_registered_string_concat::send_panel", &[Type::Any]).is_ok());
1296        Ok(())
1297    }
1298
1299    #[test]
1300    fn root_send_idx_returns_handler_value() -> anyhow::Result<()> {
1301        fn echo_handler(msg: Dynamic) -> Dynamic {
1302            dynamic::map!("type"=> "echo", "id"=> msg.get_dynamic("id").unwrap_or(Dynamic::Null))
1303        }
1304
1305        let vm = Vm::with_all()?;
1306        vm.import_code(
1307            "vm_root_send_idx_return",
1308            br#"
1309            pub fn call(req) {
1310                root::send_idx("local/send_idx_return_handlers", 0, req)
1311            }
1312            "#
1313            .to_vec(),
1314        )?;
1315
1316        root::add_list("local/send_idx_return_handlers")?;
1317        let (mount, name) = root::get_mount("local/send_idx_return_handlers")?;
1318        mount.push(name, root::Object::Native(echo_handler))?;
1319
1320        assert_eq!(vm.infer("root::send_idx", &[Type::Any, Type::I64, Type::Any])?, Type::Any);
1321        let compiled = vm.get_fn("vm_root_send_idx_return::call", &[Type::Any])?;
1322        assert_eq!(compiled.ret_ty(), &Type::Any);
1323        let call: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1324        let req = dynamic::map!("id"=> 42i64);
1325        let result = unsafe { &*call(&req) };
1326
1327        assert_eq!(result.get_dynamic("type").map(|value| value.as_str().to_string()), Some("echo".to_string()));
1328        assert_eq!(result.get_dynamic("id").and_then(|value| value.as_int()), Some(42));
1329        Ok(())
1330    }
1331
1332    #[test]
1333    fn compiles_public_hotspots_with_string_paths_and_keys() -> anyhow::Result<()> {
1334        let vm = Vm::with_all()?;
1335        vm.import_code(
1336            "vm_public_hotspots",
1337            br#"
1338            pub fn public_hotspot(action_map_path, panel_id, action_id, hotspot) {
1339                {
1340                    path: action_map_path,
1341                    panel_id: panel_id,
1342                    action_id: action_id,
1343                    id: hotspot.id
1344                }
1345            }
1346
1347            pub fn public_hotspots(idx, panel_id, hotspots) {
1348                let idx_key = "" + idx;
1349                let action_map_path = "local/game/panel_actions/" + idx_key;
1350
1351                let existing_action_map = root::get(action_map_path);
1352                if !existing_action_map.is_map() {
1353                    root::add_map(action_map_path);
1354                }
1355
1356                if hotspots.is_map() {
1357                    let public_items = {};
1358                    for action_id in hotspots.keys() {
1359                        public_items[action_id] = public_hotspot(action_map_path, panel_id, action_id, hotspots[action_id]);
1360                    }
1361                    return public_items;
1362                }
1363
1364                let public_items = [];
1365                let i = 0;
1366                while i < hotspots.len() {
1367                    let hotspot = hotspots.get_idx(i);
1368                    let item = public_hotspot(action_map_path, panel_id, hotspot.id, hotspot);
1369                    public_items.push(item);
1370                    i = i + 1;
1371                }
1372
1373                public_items
1374            }
1375            "#
1376            .to_vec(),
1377        )?;
1378
1379        assert!(vm.get_fn("vm_public_hotspots::public_hotspots", &[Type::I64, Type::Any, Type::Any]).is_ok());
1380        assert!(vm.get_fn("vm_public_hotspots::public_hotspots", &[Type::Any, Type::Any, Type::Any]).is_ok());
1381        Ok(())
1382    }
1383
1384    #[test]
1385    fn send_panel_calls_public_hotspots_with_dynamic_request() -> anyhow::Result<()> {
1386        let vm = Vm::with_all()?;
1387        vm.import_code(
1388            "vm_send_panel_public_hotspots",
1389            br#"
1390            pub fn ok(value) {
1391                value
1392            }
1393
1394            pub fn panel_from_node(req) {
1395                {
1396                    panel_id: req.panel_id,
1397                    hotspots: req.hotspots
1398                }
1399            }
1400
1401            pub fn public_hotspot(action_map_path, panel_id, action_id, hotspot) {
1402                {
1403                    path: action_map_path,
1404                    panel_id: panel_id,
1405                    action_id: action_id,
1406                    id: hotspot.id
1407                }
1408            }
1409
1410            pub fn public_hotspots(idx, panel_id, hotspots) {
1411                let idx_key = "" + idx;
1412                let action_map_path = "local/game/panel_actions/" + idx_key;
1413
1414                let existing_action_map = root::get(action_map_path);
1415                if !existing_action_map.is_map() {
1416                    root::add_map(action_map_path);
1417                }
1418
1419                if hotspots.is_map() {
1420                    let public_items = {};
1421                    for action_id in hotspots.keys() {
1422                        public_items[action_id] = public_hotspot(action_map_path, panel_id, action_id, hotspots[action_id]);
1423                    }
1424                    return public_items;
1425                }
1426
1427                let public_items = [];
1428                let i = 0;
1429                while i < hotspots.len() {
1430                    let hotspot = hotspots.get_idx(i);
1431                    let item = public_hotspot(action_map_path, panel_id, hotspot.id, hotspot);
1432                    public_items.push(item);
1433                    i = i + 1;
1434                }
1435
1436                public_items
1437            }
1438
1439            pub fn send_panel(req) {
1440                let panel = req.panel;
1441                if !panel.is_map() {
1442                    panel = panel_from_node(req);
1443                }
1444                if !panel.is_map() {
1445                    return ok({
1446                        id: 4,
1447                        type: "panel_rejected",
1448                        reason: "invalid panel"
1449                    });
1450                }
1451                panel.id = 4;
1452                panel.idx = req.idx;
1453                if !panel.contains("type") {
1454                    panel.type = "panel";
1455                }
1456                if panel.contains("hotspots") {
1457                    panel.hotspots = public_hotspots(req.idx, panel.panel_id, panel.hotspots);
1458                }
1459                root::send_idx("local/ws", req.idx, panel);
1460                ok({
1461                    id: 4,
1462                    type: "panel",
1463                    panel_id: panel.panel_id
1464                })
1465            }
1466            "#
1467            .to_vec(),
1468        )?;
1469
1470        let compiled = vm.get_fn("vm_send_panel_public_hotspots::send_panel", &[Type::Any])?;
1471        assert_eq!(compiled.ret_ty(), &Type::Any);
1472        let send_panel: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1473        let req = dynamic::map!(
1474            "idx"=> 7i64,
1475            "panel"=> dynamic::map!(
1476                "panel_id"=> "main",
1477                "hotspots"=> dynamic::map!(
1478                    "open"=> dynamic::map!("id"=> "open")
1479                )
1480            )
1481        );
1482        let result = unsafe { &*send_panel(&req) };
1483
1484        assert_eq!(result.get_dynamic("type").map(|value| value.as_str().to_string()), Some("panel".to_string()));
1485        assert_eq!(result.get_dynamic("panel_id").map(|value| value.as_str().to_string()), Some("main".to_string()));
1486        Ok(())
1487    }
1488
1489    #[test]
1490    fn map_assignment_accepts_string_concat_key() -> anyhow::Result<()> {
1491        let vm = Vm::with_all()?;
1492        vm.import_code(
1493            "vm_string_concat_map_key",
1494            br##"
1495            pub fn write_action(action_map, panel_id, action_id, action) {
1496                action_map[panel_id + "#" + action_id] = action;
1497                action_map[panel_id + "#" + action_id]
1498            }
1499            "##
1500            .to_vec(),
1501        )?;
1502
1503        let compiled = vm.get_fn("vm_string_concat_map_key::write_action", &[Type::Any, Type::Any, Type::Any, Type::Any])?;
1504        let write_action: extern "C" fn(*const Dynamic, *const Dynamic, *const Dynamic, *const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1505        let action_map = dynamic::map!();
1506        let panel_id: Dynamic = "panel".into();
1507        let action_id: Dynamic = "open".into();
1508        let action = dynamic::map!("id"=> "open");
1509
1510        let result = unsafe { &*write_action(&action_map, &panel_id, &action_id, &action) };
1511
1512        assert_eq!(result.get_dynamic("id").map(|value| value.as_str().to_string()), Some("open".to_string()));
1513        assert_eq!(action_map.get_dynamic("panel#open").and_then(|value| value.get_dynamic("id")).map(|value| value.as_str().to_string()), Some("open".to_string()));
1514        Ok(())
1515    }
1516
1517    #[test]
1518    fn map_get_key_accepts_string_concat_key_variable() -> anyhow::Result<()> {
1519        let vm = Vm::with_all()?;
1520        vm.import_code(
1521            "vm_get_key_string_concat_key",
1522            br##"
1523            pub fn read_action(action_map, panel_id, action_id) {
1524                let action_key = panel_id + "#" + action_id;
1525                action_map.get_key(action_key)
1526            }
1527            "##
1528            .to_vec(),
1529        )?;
1530
1531        let compiled = vm.get_fn("vm_get_key_string_concat_key::read_action", &[Type::Any, Type::Any, Type::Any])?;
1532        let read_action: extern "C" fn(*const Dynamic, *const Dynamic, *const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1533        let action_map = dynamic::map!("panel#open"=> dynamic::map!("id"=> "open"));
1534        let panel_id: Dynamic = "panel".into();
1535        let action_id: Dynamic = "open".into();
1536
1537        let result = unsafe { &*read_action(&action_map, &panel_id, &action_id) };
1538
1539        assert_eq!(result.get_dynamic("id").map(|value| value.as_str().to_string()), Some("open".to_string()));
1540        Ok(())
1541    }
1542
1543    #[test]
1544    fn map_get_key_accepts_helper_string_key() -> anyhow::Result<()> {
1545        let vm = Vm::with_all()?;
1546        vm.import_code(
1547            "vm_get_key_helper_string_key",
1548            br##"
1549            pub fn make_action_key(panel_id, action_id) {
1550                panel_id + "#" + action_id
1551            }
1552
1553            pub fn read_action(action_map, panel_id, action_id) {
1554                let action_key = make_action_key(panel_id, action_id);
1555                let action = action_map.get_key(action_key);
1556                action
1557            }
1558            "##
1559            .to_vec(),
1560        )?;
1561
1562        let compiled = vm.get_fn("vm_get_key_helper_string_key::read_action", &[Type::Any, Type::Any, Type::Any])?;
1563        let read_action: extern "C" fn(*const Dynamic, *const Dynamic, *const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1564        let action_map = dynamic::map!("panel#open"=> dynamic::map!("id"=> "open"));
1565        let panel_id: Dynamic = "panel".into();
1566        let action_id: Dynamic = "open".into();
1567
1568        let result = unsafe { &*read_action(&action_map, &panel_id, &action_id) };
1569
1570        assert_eq!(result.get_dynamic("id").map(|value| value.as_str().to_string()), Some("open".to_string()));
1571        Ok(())
1572    }
1573
1574    #[test]
1575    fn map_del_key_removes_string_key_and_returns_removed_value() -> anyhow::Result<()> {
1576        let vm = Vm::with_all()?;
1577        vm.import_code(
1578            "vm_del_key_string_key",
1579            br##"
1580            pub fn remove_action(action_map, panel_id, action_id) {
1581                let action_key = panel_id + "#" + action_id;
1582                let removed = action_map.del_key(action_key);
1583                [removed, action_map.get_key(action_key)]
1584            }
1585            "##
1586            .to_vec(),
1587        )?;
1588
1589        let compiled = vm.get_fn("vm_del_key_string_key::remove_action", &[Type::Any, Type::Any, Type::Any])?;
1590        let remove_action: extern "C" fn(*const Dynamic, *const Dynamic, *const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1591        let action_map = dynamic::map!("panel#open"=> dynamic::map!("id"=> "open"));
1592        let panel_id: Dynamic = "panel".into();
1593        let action_id: Dynamic = "open".into();
1594
1595        let result = unsafe { &*remove_action(&action_map, &panel_id, &action_id) };
1596
1597        assert_eq!(result.get_idx(0).and_then(|value| value.get_dynamic("id")).map(|value| value.as_str().to_string()), Some("open".to_string()));
1598        assert!(result.get_idx(1).is_some_and(|value| value.is_null()));
1599        assert!(action_map.get_dynamic("panel#open").is_none());
1600        Ok(())
1601    }
1602
1603    #[test]
1604    fn dynamic_field_value_participates_in_or_expression() -> anyhow::Result<()> {
1605        let vm = Vm::with_all()?;
1606        vm.import_code(
1607            "vm_dynamic_field_or",
1608            r#"
1609            pub fn direct_next() {
1610                let choice = {
1611                    label: "颜色",
1612                    next: "color"
1613                };
1614                choice.next
1615            }
1616
1617            pub fn bracket_next() {
1618                let choice = {
1619                    label: "颜色",
1620                    next: "color"
1621                };
1622                choice["next"]
1623            }
1624            "#
1625            .as_bytes()
1626            .to_vec(),
1627        )?;
1628
1629        let compiled = vm.get_fn("vm_dynamic_field_or::direct_next", &[])?;
1630        assert_eq!(compiled.ret_ty(), &Type::Any);
1631        let direct_next: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1632        assert_eq!(unsafe { &*direct_next() }.as_str(), "color");
1633
1634        let compiled = vm.get_fn("vm_dynamic_field_or::bracket_next", &[])?;
1635        assert_eq!(compiled.ret_ty(), &Type::Any);
1636        let bracket_next: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1637        assert_eq!(unsafe { &*bracket_next() }.as_str(), "color");
1638        Ok(())
1639    }
1640
1641    #[test]
1642    fn empty_object_literal_in_if_branch_stays_dynamic() -> anyhow::Result<()> {
1643        let vm = Vm::with_all()?;
1644        vm.import_code(
1645            "vm_if_empty_object_branch",
1646            r#"
1647            pub fn first_note(steps) {
1648                let first = if steps.len() > 0 { steps[0] } else { {} };
1649                let first_note = if first.contains("note") { first.note } else { "fallback" };
1650                first_note
1651            }
1652
1653            pub fn first_ja(steps) {
1654                let first = if steps.len() > 0 { steps[0] } else { {} };
1655                if first.contains("ja") { first.ja } else { "すみません" }
1656            }
1657
1658            pub fn assign_first_note(steps) {
1659                let first = {};
1660                first = if steps.len() > 0 { steps[0] } else { {} };
1661                if first.contains("note") { first.note } else { "fallback" }
1662            }
1663            "#
1664            .as_bytes()
1665            .to_vec(),
1666        )?;
1667
1668        let compiled = vm.get_fn("vm_if_empty_object_branch::first_note", &[Type::Any])?;
1669        assert_eq!(compiled.ret_ty(), &Type::Any);
1670        let first_note: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1671
1672        let empty_steps = Dynamic::list(Vec::new());
1673        assert_eq!(unsafe { &*first_note(&empty_steps) }.as_str(), "fallback");
1674
1675        let mut step = std::collections::BTreeMap::new();
1676        step.insert("note".into(), "hello".into());
1677        let steps = Dynamic::list(vec![Dynamic::map(step)]);
1678        assert_eq!(unsafe { &*first_note(&steps) }.as_str(), "hello");
1679
1680        let compiled = vm.get_fn("vm_if_empty_object_branch::first_ja", &[Type::Any])?;
1681        assert_eq!(compiled.ret_ty(), &Type::Any);
1682        let first_ja: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1683        assert_eq!(unsafe { &*first_ja(&empty_steps) }.as_str(), "すみません");
1684
1685        let compiled = vm.get_fn("vm_if_empty_object_branch::assign_first_note", &[Type::Any])?;
1686        assert_eq!(compiled.ret_ty(), &Type::Any);
1687        let assign_first_note: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1688        assert_eq!(unsafe { &*assign_first_note(&empty_steps) }.as_str(), "fallback");
1689        assert_eq!(unsafe { &*assign_first_note(&steps) }.as_str(), "hello");
1690        Ok(())
1691    }
1692
1693    #[test]
1694    fn list_literal_can_be_function_tail_expression() -> anyhow::Result<()> {
1695        let vm = Vm::with_all()?;
1696        vm.import_code(
1697            "vm_tail_list_literal",
1698            r#"
1699            pub fn numbers() {
1700                [1, 2, 3]
1701            }
1702
1703            pub fn maps() {
1704                [
1705                    {note: "first"},
1706                    {note: "second"}
1707                ]
1708            }
1709
1710            pub fn object_with_maps() {
1711                {
1712                    steps: [
1713                        {note: "first"},
1714                        {note: "second"}
1715                    ]
1716                }
1717            }
1718
1719            pub fn return_maps() {
1720                return [
1721                    {note: "first"},
1722                    {note: "second"}
1723                ];
1724            }
1725
1726            pub fn return_maps_without_semicolon() {
1727                return [
1728                    {note: "first"},
1729                    {note: "second"}
1730                ]
1731            }
1732
1733            pub fn tail_bare_variable() {
1734                let value = [
1735                    {note: "first"},
1736                    {note: "second"}
1737                ];
1738                value
1739            }
1740
1741            pub fn return_bare_variable_without_semicolon() {
1742                let value = [
1743                    {note: "first"},
1744                    {note: "second"}
1745                ];
1746                return value
1747            }
1748
1749            pub fn tail_object_variable() {
1750                let result = {
1751                    steps: [
1752                        {note: "first"},
1753                        {note: "second"}
1754                    ]
1755                };
1756                result
1757            }
1758            "#
1759            .as_bytes()
1760            .to_vec(),
1761        )?;
1762
1763        let compiled = vm.get_fn("vm_tail_list_literal::numbers", &[])?;
1764        assert_eq!(compiled.ret_ty(), &Type::Any);
1765        let numbers: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1766        let result = unsafe { &*numbers() };
1767        assert_eq!(result.len(), 3);
1768        assert_eq!(result.get_idx(1).and_then(|value| value.as_int()), Some(2));
1769
1770        let compiled = vm.get_fn("vm_tail_list_literal::maps", &[])?;
1771        assert_eq!(compiled.ret_ty(), &Type::Any);
1772        let maps: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1773        let result = unsafe { &*maps() };
1774        assert_eq!(result.len(), 2);
1775        assert_eq!(result.get_idx(1).and_then(|value| value.get_dynamic("note")).map(|value| value.as_str().to_string()), Some("second".to_string()));
1776
1777        let compiled = vm.get_fn("vm_tail_list_literal::object_with_maps", &[])?;
1778        assert_eq!(compiled.ret_ty(), &Type::Any);
1779        let object_with_maps: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1780        let result = unsafe { &*object_with_maps() };
1781        let steps = result.get_dynamic("steps").expect("steps");
1782        assert_eq!(steps.len(), 2);
1783        assert_eq!(steps.get_idx(0).and_then(|value| value.get_dynamic("note")).map(|value| value.as_str().to_string()), Some("first".to_string()));
1784
1785        let compiled = vm.get_fn("vm_tail_list_literal::return_maps", &[])?;
1786        assert_eq!(compiled.ret_ty(), &Type::Any);
1787        let return_maps: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1788        let result = unsafe { &*return_maps() };
1789        assert_eq!(result.len(), 2);
1790        assert_eq!(result.get_idx(1).and_then(|value| value.get_dynamic("note")).map(|value| value.as_str().to_string()), Some("second".to_string()));
1791
1792        let compiled = vm.get_fn("vm_tail_list_literal::return_maps_without_semicolon", &[])?;
1793        assert_eq!(compiled.ret_ty(), &Type::Any);
1794        let return_maps_without_semicolon: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1795        let result = unsafe { &*return_maps_without_semicolon() };
1796        assert_eq!(result.len(), 2);
1797        assert_eq!(result.get_idx(0).and_then(|value| value.get_dynamic("note")).map(|value| value.as_str().to_string()), Some("first".to_string()));
1798
1799        let compiled = vm.get_fn("vm_tail_list_literal::tail_bare_variable", &[])?;
1800        assert_eq!(compiled.ret_ty(), &Type::Any);
1801        let tail_bare_variable: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1802        let result = unsafe { &*tail_bare_variable() };
1803        assert_eq!(result.len(), 2);
1804        assert_eq!(result.get_idx(1).and_then(|value| value.get_dynamic("note")).map(|value| value.as_str().to_string()), Some("second".to_string()));
1805
1806        let compiled = vm.get_fn("vm_tail_list_literal::return_bare_variable_without_semicolon", &[])?;
1807        assert_eq!(compiled.ret_ty(), &Type::Any);
1808        let return_bare_variable_without_semicolon: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1809        let result = unsafe { &*return_bare_variable_without_semicolon() };
1810        assert_eq!(result.len(), 2);
1811        assert_eq!(result.get_idx(0).and_then(|value| value.get_dynamic("note")).map(|value| value.as_str().to_string()), Some("first".to_string()));
1812
1813        let compiled = vm.get_fn("vm_tail_list_literal::tail_object_variable", &[])?;
1814        assert_eq!(compiled.ret_ty(), &Type::Any);
1815        let tail_object_variable: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1816        let result = unsafe { &*tail_object_variable() };
1817        let steps = result.get_dynamic("steps").expect("steps");
1818        assert_eq!(steps.len(), 2);
1819        assert_eq!(steps.get_idx(1).and_then(|value| value.get_dynamic("note")).map(|value| value.as_str().to_string()), Some("second".to_string()));
1820        Ok(())
1821    }
1822
1823    #[test]
1824    fn list_return_value_supports_get_idx_method_call() -> anyhow::Result<()> {
1825        let vm = Vm::with_all()?;
1826        vm.import_code(
1827            "vm_returned_list_get_idx",
1828            r#"
1829            pub fn ids() {
1830                [
1831                    "base",
1832                    "2",
1833                    "3"
1834                ]
1835            }
1836
1837            pub fn combinations() {
1838                let result = [];
1839                let values = ids();
1840                let idx = 0;
1841                while idx < values.len() {
1842                    result.push(values.get_idx(idx));
1843                    idx = idx + 1;
1844                }
1845                result
1846            }
1847            "#
1848            .as_bytes()
1849            .to_vec(),
1850        )?;
1851
1852        let compiled = vm.get_fn("vm_returned_list_get_idx::combinations", &[])?;
1853        assert_eq!(compiled.ret_ty(), &Type::Any);
1854        let combinations: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1855        let result = unsafe { &*combinations() };
1856
1857        assert_eq!(result.len(), 3);
1858        assert_eq!(result.get_idx(0).map(|value| value.as_str().to_string()), Some("base".to_string()));
1859        assert_eq!(result.get_idx(2).map(|value| value.as_str().to_string()), Some("3".to_string()));
1860        Ok(())
1861    }
1862
1863    #[test]
1864    fn repeated_deep_step_literals_import_successfully() -> anyhow::Result<()> {
1865        fn extra_page_literal(depth: usize) -> String {
1866            let mut value = "{leaf: \"done\"}".to_string();
1867            for idx in 0..depth {
1868                value = format!("{{kind: \"page\", idx: {idx}, children: [{value}], meta: {{title: \"extra\", visible: true}}}}");
1869            }
1870            value
1871        }
1872
1873        let extra = extra_page_literal(48);
1874        let code = format!(
1875            r#"
1876            pub fn script() {{
1877                return [
1878                    {{ja: "一つ目", note: "first", extra: {extra}}},
1879                    {{ja: "二つ目", note: "second", extra: {extra}}},
1880                    {{ja: "三つ目", note: "third", extra: {extra}}}
1881                ]
1882            }}
1883            "#
1884        );
1885
1886        let vm = Vm::with_all()?;
1887        vm.import_code("vm_repeated_deep_step_literals", code.into_bytes())?;
1888        let compiled = vm.get_fn("vm_repeated_deep_step_literals::script", &[])?;
1889        assert_eq!(compiled.ret_ty(), &Type::Any);
1890        let script: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1891        let result = unsafe { &*script() };
1892        assert_eq!(result.len(), 3);
1893        assert_eq!(result.get_idx(2).and_then(|value| value.get_dynamic("note")).map(|value| value.as_str().to_string()), Some("third".to_string()));
1894        Ok(())
1895    }
1896
1897    #[test]
1898    fn native_import_uses_owning_vm() -> anyhow::Result<()> {
1899        let module_path = std::env::temp_dir().join(format!("zust_vm_import_owner_{}.zs", std::process::id()));
1900        std::fs::write(&module_path, "pub fn value() { 41 }")?;
1901        let module_path = module_path.to_string_lossy().replace('\\', "\\\\").replace('"', "\\\"");
1902
1903        let vm1 = Vm::with_all()?;
1904        vm1.import_code(
1905            "vm_import_owner",
1906            format!(
1907                r#"
1908                pub fn run() {{
1909                    import("vm_imported_owner", "{module_path}");
1910                }}
1911                "#
1912            )
1913            .into_bytes(),
1914        )?;
1915        let compiled = vm1.get_fn("vm_import_owner::run", &[])?;
1916
1917        let vm2 = Vm::with_all()?;
1918        vm2.import_code("vm_import_other", b"pub fn run() { 0 }".to_vec())?;
1919        let _ = vm2.get_fn("vm_import_other::run", &[])?;
1920
1921        let run: extern "C" fn() = unsafe { std::mem::transmute(compiled.ptr()) };
1922        run();
1923
1924        assert!(vm1.get_fn("vm_imported_owner::value", &[]).is_ok());
1925        assert!(vm2.get_fn("vm_imported_owner::value", &[]).is_err());
1926        Ok(())
1927    }
1928
1929    #[test]
1930    fn object_last_field_call_does_not_need_trailing_comma() -> anyhow::Result<()> {
1931        let vm = Vm::with_all()?;
1932        vm.import_code(
1933            "vm_object_last_call_field",
1934            r#"
1935            pub fn extra_page() {
1936                {
1937                    title: "extra",
1938                    pages: [
1939                        {note: "nested"}
1940                    ]
1941                }
1942            }
1943
1944            pub fn data() {
1945                return [
1946                    {
1947                        note: "first",
1948                        choices: ["a", "b"],
1949                        extras: extra_page()
1950                    },
1951                    {
1952                        note: "second",
1953                        choices: ["c"],
1954                        extras: extra_page()
1955                    }
1956                ]
1957            }
1958            "#
1959            .as_bytes()
1960            .to_vec(),
1961        )?;
1962
1963        let compiled = vm.get_fn("vm_object_last_call_field::data", &[])?;
1964        assert_eq!(compiled.ret_ty(), &Type::Any);
1965        let data: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1966        let result = unsafe { &*data() };
1967        assert_eq!(result.len(), 2);
1968        let first = result.get_idx(0).expect("first step");
1969        assert_eq!(first.get_dynamic("extras").and_then(|extras| extras.get_dynamic("title")).map(|title| title.as_str().to_string()), Some("extra".to_string()));
1970        Ok(())
1971    }
1972
1973    #[test]
1974    fn string_return_survives_scope_exit() -> anyhow::Result<()> {
1975        let vm = Vm::with_all()?;
1976        vm.import_code(
1977            "vm_string_return_scope",
1978            r#"
1979            pub fn source_root() {
1980                "../assets/character/男主角换装"
1981            }
1982
1983            pub fn binary_root() {
1984                "character_binary/男主角换装"
1985            }
1986
1987            pub fn runtime_binary_url() {
1988                "/" + binary_root()
1989            }
1990
1991            pub fn action_groups() {
1992                let root = source_root();
1993                let binary_url = runtime_binary_url();
1994                let binary_root = binary_root();
1995                [
1996                    {
1997                        id: "field_bottom",
1998                        source_spine: root + "/战斗外/boy_b.spine",
1999                        skeleton: binary_url + "/战斗外/boy_b/boy_b.skel.bytes",
2000                        export_skeleton: binary_root + "/战斗外/boy_b/boy_b.skel.bytes"
2001                    }
2002                ]
2003            }
2004            "#
2005            .as_bytes()
2006            .to_vec(),
2007        )?;
2008
2009        let compiled = vm.get_fn("vm_string_return_scope::source_root", &[])?;
2010        assert_eq!(compiled.ret_ty(), &Type::Str);
2011        let source_root: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
2012        let source_root = unsafe { &*source_root() };
2013        assert_eq!(source_root.as_str(), "../assets/character/男主角换装");
2014
2015        let compiled = vm.get_fn("vm_string_return_scope::action_groups", &[])?;
2016        assert_eq!(compiled.ret_ty(), &Type::Any);
2017        let action_groups: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
2018        let groups = unsafe { &*action_groups() };
2019        let first = groups.get_idx(0).expect("first action group");
2020        assert_eq!(first.get_dynamic("source_spine").map(|value| value.as_str().to_string()), Some("../assets/character/男主角换装/战斗外/boy_b.spine".to_string()));
2021        assert_eq!(first.get_dynamic("skeleton").map(|value| value.as_str().to_string()), Some("/character_binary/男主角换装/战斗外/boy_b/boy_b.skel.bytes".to_string()));
2022        Ok(())
2023    }
2024
2025    #[test]
2026    fn large_dynamic_object_accepts_inline_call_fields() -> anyhow::Result<()> {
2027        let vm = Vm::with_all()?;
2028        let model_count = 180;
2029        let combination_count = 90;
2030        let models = (0..model_count)
2031            .map(|idx| {
2032                format!(
2033                    r#"{{id: "model_{idx}", name: "模型_{idx}", source: "/美术资源/角色/少年/套装_{idx}/模型_{idx}.model.json", parts: [
2034                        {{slot: "hair", path: "/模型/头发/颜色_{idx}/默认.png", z: 10}},
2035                        {{slot: "body", path: "/模型/身体/套装_{idx}/默认.png", z: 1}},
2036                        {{slot: "face", path: "/模型/表情/表情_{idx}/默认.png", z: 20}}
2037                    ]}}"#
2038                )
2039            })
2040            .collect::<Vec<_>>()
2041            .join(",\n");
2042        let combinations = (0..combination_count).map(|idx| format!(r#"{{hair: "color_{idx}", body: "set_{idx}", face: "face_{idx}"}}"#)).collect::<Vec<_>>().join(",\n");
2043        let code = format!(
2044            r#"
2045            pub fn source_root() {{
2046                "/美术资源/角色/少年/默认"
2047            }}
2048
2049            pub fn runtime_boy_url() {{
2050                "/cdn/runtime/角色/少年/少年.model.json"
2051            }}
2052
2053            pub fn parts() {{
2054                [
2055                    {{id: "hair", path: "/模型/头发/黑色/默认.png", z: 10}},
2056                    {{id: "body", path: "/模型/身体/校服/默认.png", z: 1}},
2057                    {{id: "face", path: "/模型/表情/微笑/默认.png", z: 20}}
2058                ]
2059            }}
2060
2061            pub fn action_groups() {{
2062                {{
2063                    idle: [
2064                        {{id: "stand", name: "站立", frames: ["待机/0001.png", "待机/0002.png"]}},
2065                        {{id: "blink", name: "眨眼", frames: ["表情/眨眼/0001.png", "表情/眨眼/0002.png"]}}
2066                    ],
2067                    move: [
2068                        {{id: "walk", name: "行走", frames: ["行走/0001.png", "行走/0002.png"]}},
2069                        {{id: "run", name: "奔跑", frames: ["奔跑/0001.png", "奔跑/0002.png"]}}
2070                    ]
2071                }}
2072            }}
2073
2074            pub fn default_model() {{
2075                {{
2076                    id: "runtime_boy",
2077                    name: "运行时少年",
2078                    skins: [
2079                        {{id: "school", title: "校服", source: "/套装/校服/model.json"}},
2080                        {{id: "casual", title: "便服", source: "/套装/便服/model.json"}}
2081                    ],
2082                    models: [
2083                        {models}
2084                    ]
2085                }}
2086            }}
2087
2088            pub fn first_nine_combinations() {{
2089                [
2090                    {combinations}
2091                ]
2092            }}
2093
2094            pub fn config() {{
2095                {{
2096                    source_root: source_root(),
2097                    runtime_boy_url: runtime_boy_url(),
2098                    parts: parts(),
2099                    action_groups: action_groups(),
2100                    default_model: default_model(),
2101                    first_nine_combinations: first_nine_combinations()
2102                }}
2103            }}
2104
2105            pub fn start() {{
2106                root::add("local/vm_large_inline_call_object/config", {{
2107                    source_root: source_root(),
2108                    runtime_boy_url: runtime_boy_url(),
2109                    parts: parts(),
2110                    action_groups: action_groups(),
2111                    default_model: default_model(),
2112                    first_nine_combinations: first_nine_combinations()
2113                }})
2114            }}
2115            "#
2116        );
2117        vm.import_code("vm_large_inline_call_object", code.into_bytes())?;
2118
2119        let compiled = vm.get_fn("vm_large_inline_call_object::config", &[])?;
2120        assert_eq!(compiled.ret_ty(), &Type::Any);
2121        let config: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
2122        let result = unsafe { &*config() };
2123        assert_eq!(result.get_dynamic("source_root").map(|value| value.as_str().to_string()), Some("/美术资源/角色/少年/默认".to_string()));
2124        assert_eq!(result.get_dynamic("first_nine_combinations").map(|value| value.len()), Some(combination_count));
2125
2126        let compiled = vm.get_fn("vm_large_inline_call_object::start", &[])?;
2127        assert_eq!(compiled.ret_ty(), &Type::Bool);
2128        let start: extern "C" fn() -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
2129        assert!(start());
2130        let saved = root::get("local/vm_large_inline_call_object/config")?;
2131        assert_eq!(saved.get_dynamic("first_nine_combinations").map(|value| value.len()), Some(combination_count));
2132        Ok(())
2133    }
2134
2135    #[test]
2136    fn gpu_struct_layout_packs_and_unpacks_dynamic_maps() -> anyhow::Result<()> {
2137        let vm = Vm::with_all()?;
2138        vm.import_code(
2139            "vm_gpu_layout",
2140            br#"
2141            pub struct Params {
2142                a: u32,
2143                b: u32,
2144                c: u32,
2145            }
2146            "#
2147            .to_vec(),
2148        )?;
2149
2150        let layout = vm.gpu_struct_layout("vm_gpu_layout::Params", &[])?;
2151        assert_eq!(layout.size, 16);
2152        assert_eq!(layout.fields.iter().map(|field| (field.name.as_str(), field.offset)).collect::<Vec<_>>(), vec![("a", 0), ("b", 4), ("c", 8)]);
2153
2154        let value = dynamic::map!("a"=> 1u32, "b"=> 2u32, "c"=> 3u32);
2155        let bytes = layout.pack_map(&value)?;
2156        assert_eq!(bytes.len(), 16);
2157        assert_eq!(&bytes[0..4], &1u32.to_ne_bytes());
2158        assert_eq!(&bytes[4..8], &2u32.to_ne_bytes());
2159        assert_eq!(&bytes[8..12], &3u32.to_ne_bytes());
2160
2161        let read = layout.unpack_map(&bytes)?;
2162        assert_eq!(read.get_dynamic("a").and_then(|value| value.as_uint()), Some(1));
2163        assert_eq!(read.get_dynamic("b").and_then(|value| value.as_uint()), Some(2));
2164        assert_eq!(read.get_dynamic("c").and_then(|value| value.as_uint()), Some(3));
2165        Ok(())
2166    }
2167
2168    #[test]
2169    fn root_native_calls_do_not_take_ownership_of_dynamic_args() -> anyhow::Result<()> {
2170        let vm = Vm::with_all()?;
2171        vm.import_code(
2172            "vm_root_clone_bridge",
2173            br#"
2174            pub fn add_then_reuse(arg) {
2175                let user = {
2176                    address: "test-wallet",
2177                    points: 20
2178                };
2179                root::add("local/root-clone-bridge-user", user);
2180                user.points = user.points - 7;
2181                root::add("local/root-clone-bridge-user", user);
2182                {
2183                    user: user,
2184                    points: user.points
2185                }
2186            }
2187
2188            pub fn clone_then_mutate(arg) {
2189                let user = {
2190                    profile: {
2191                        points: 20
2192                    }
2193                };
2194                let copied = user.clone();
2195                copied.profile.points = 13;
2196                user
2197            }
2198            "#
2199            .to_vec(),
2200        )?;
2201
2202        let compiled = vm.get_fn("vm_root_clone_bridge::add_then_reuse", &[Type::Any])?;
2203        assert_eq!(compiled.ret_ty(), &Type::Any);
2204        let add_then_reuse: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
2205        let arg = Dynamic::Null;
2206        let result = add_then_reuse(&arg);
2207        let result = unsafe { &*result };
2208
2209        assert_eq!(result.get_dynamic("points").and_then(|value| value.as_int()), Some(13));
2210        let mut json = String::new();
2211        result.to_json(&mut json);
2212        assert!(json.contains("\"points\": 13"));
2213
2214        let clone_then_mutate = vm.get_fn("vm_root_clone_bridge::clone_then_mutate", &[Type::Any])?;
2215        let clone_then_mutate: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(clone_then_mutate.ptr()) };
2216        let result = clone_then_mutate(&arg);
2217        let result = unsafe { &*result };
2218        assert_eq!(result.get_dynamic("profile").unwrap().get_dynamic("points").and_then(|value| value.as_int()), Some(20));
2219        Ok(())
2220    }
2221
2222    struct CounterForTypedReceiver {
2223        value: i64,
2224    }
2225
2226    extern "C" fn counter_for_typed_receiver_get(value: *const Dynamic) -> i64 {
2227        unsafe { &*value }.as_custom::<CounterForTypedReceiver>().map(|counter| counter.value).unwrap_or(-1)
2228    }
2229
2230    struct NavMapForFunctionArg;
2231
2232    extern "C" fn nav_map_for_function_arg_new() -> *const Dynamic {
2233        Box::into_raw(Box::new(Dynamic::custom(NavMapForFunctionArg)))
2234    }
2235
2236    #[test]
2237    fn typed_receiver_method_call_dispatches_with_type_hint() -> anyhow::Result<()> {
2238        let vm = Vm::with_all()?;
2239        vm.add_empty_type("Counter")?;
2240        let counter_ty = vm.get_symbol("Counter", Vec::new())?;
2241        vm.add_native_method_ptr("Counter", "get", &[counter_ty], Type::I64, counter_for_typed_receiver_get as *const u8)?;
2242        vm.import_code(
2243            "vm_typed_receiver_method",
2244            br#"
2245            pub fn run(value) {
2246                value::<Counter>::get()
2247            }
2248            "#
2249            .to_vec(),
2250        )?;
2251
2252        let compiled = vm.get_fn("vm_typed_receiver_method::run", &[Type::Any])?;
2253        assert_eq!(compiled.ret_ty(), &Type::I64);
2254        let run: extern "C" fn(*const Dynamic) -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
2255        let value = Dynamic::custom(CounterForTypedReceiver { value: 42 });
2256
2257        assert_eq!(run(&value), 42);
2258        Ok(())
2259    }
2260
2261    #[test]
2262    fn native_custom_object_can_be_passed_to_zs_function() -> anyhow::Result<()> {
2263        let vm = Vm::with_all()?;
2264        vm.add_empty_type("NavMap")?;
2265        vm.add_native_method_ptr("NavMap", "new", &[], Type::Any, nav_map_for_function_arg_new as *const u8)?;
2266        vm.import_code(
2267            "vm_native_custom_arg",
2268            br#"
2269            pub fn add_nav_spawns(world, navmap) {
2270                navmap
2271            }
2272
2273            pub fn run(world) {
2274                let navmap = NavMap::new();
2275                let with_spawns = add_nav_spawns(world, navmap);
2276                with_spawns
2277            }
2278            "#
2279            .to_vec(),
2280        )?;
2281
2282        let compiled = vm.get_fn("vm_native_custom_arg::run", &[Type::Any])?;
2283        assert_eq!(compiled.ret_ty(), &Type::Any);
2284        let run: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
2285        let world = Dynamic::Null;
2286        let result = run(&world);
2287        let result = unsafe { &*result };
2288
2289        assert!(result.as_custom::<NavMapForFunctionArg>().is_some());
2290        Ok(())
2291    }
2292
2293    #[test]
2294    fn native_custom_object_typed_local_can_be_passed_to_zs_function() -> anyhow::Result<()> {
2295        let vm = Vm::with_all()?;
2296        vm.add_empty_type("NavMap")?;
2297        let _nav_map_ty = vm.get_symbol("NavMap", Vec::new())?;
2298        vm.add_native_method_ptr("NavMap", "new", &[], Type::Any, nav_map_for_function_arg_new as *const u8)?;
2299        vm.import_code(
2300            "vm_native_custom_typed_arg",
2301            br#"
2302            pub fn add_nav_spawns(world, navmap) {
2303                navmap
2304            }
2305
2306            pub fn run(world) {
2307                let navmap: NavMap = NavMap::new();
2308                let with_spawns = add_nav_spawns(world, navmap);
2309                with_spawns
2310            }
2311            "#
2312            .to_vec(),
2313        )?;
2314
2315        let compiled = vm.get_fn("vm_native_custom_typed_arg::run", &[Type::Any])?;
2316        let run: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
2317        let world = Dynamic::Null;
2318        let result = run(&world);
2319        let result = unsafe { &*result };
2320
2321        assert!(result.as_custom::<NavMapForFunctionArg>().is_some());
2322        Ok(())
2323    }
2324
2325    // ---- 新增边界条件测试 ----
2326
2327    #[test]
2328    fn dynamic_type_checks_on_null_and_primitive_values() -> anyhow::Result<()> {
2329        let vm = Vm::with_all()?;
2330        vm.import_code(
2331            "vm_dynamic_type_checks",
2332            br#"
2333            pub fn is_list_on_int() {
2334                let x = 42i64;
2335                x.is_list()
2336            }
2337
2338            pub fn is_map_on_int() {
2339                let x = 42i64;
2340                x.is_map()
2341            }
2342
2343            pub fn is_null_on_int() {
2344                let x = 42i64;
2345                x.is_null()
2346            }
2347            "#
2348            .to_vec(),
2349        )?;
2350
2351        let compiled = vm.get_fn("vm_dynamic_type_checks::is_list_on_int", &[])?;
2352        assert_eq!(compiled.ret_ty(), &Type::Bool);
2353        let is_list_on_int: extern "C" fn() -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
2354        assert!(!is_list_on_int());
2355
2356        let compiled = vm.get_fn("vm_dynamic_type_checks::is_map_on_int", &[])?;
2357        assert_eq!(compiled.ret_ty(), &Type::Bool);
2358        let is_map_on_int: extern "C" fn() -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
2359        assert!(!is_map_on_int());
2360
2361        let compiled = vm.get_fn("vm_dynamic_type_checks::is_null_on_int", &[])?;
2362        assert_eq!(compiled.ret_ty(), &Type::Bool);
2363        let is_null_on_int: extern "C" fn() -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
2364        assert!(!is_null_on_int());
2365        Ok(())
2366    }
2367
2368    #[test]
2369    fn empty_for_loop_range_has_zero_iterations() -> anyhow::Result<()> {
2370        let vm = Vm::with_all()?;
2371        vm.import_code(
2372            "vm_empty_for_range",
2373            br#"
2374            pub fn empty_exclusive() {
2375                let count = 0i32;
2376                for i in 0..0 {
2377                    count += i;
2378                }
2379                count
2380            }
2381
2382            pub fn single_inclusive_iteration() {
2383                let count = 0i32;
2384                for i in 5..=5 {
2385                    count += i;
2386                }
2387                count
2388            }
2389            "#
2390            .to_vec(),
2391        )?;
2392
2393        let compiled = vm.get_fn("vm_empty_for_range::empty_exclusive", &[])?;
2394        assert_eq!(compiled.ret_ty(), &Type::I32);
2395        let empty_exclusive: extern "C" fn() -> i32 = unsafe { std::mem::transmute(compiled.ptr()) };
2396        assert_eq!(empty_exclusive(), 0);
2397
2398        let compiled = vm.get_fn("vm_empty_for_range::single_inclusive_iteration", &[])?;
2399        assert_eq!(compiled.ret_ty(), &Type::I32);
2400        let single_inclusive: extern "C" fn() -> i32 = unsafe { std::mem::transmute(compiled.ptr()) };
2401        assert_eq!(single_inclusive(), 5);
2402        Ok(())
2403    }
2404
2405    #[test]
2406    fn map_contains_key_on_non_existent_and_nested_keys() -> anyhow::Result<()> {
2407        let vm = Vm::with_all()?;
2408        vm.import_code(
2409            "vm_map_contains",
2410            br#"
2411            pub fn contains_existing(data) {
2412                data.contains("name")
2413            }
2414
2415            pub fn contains_missing(data) {
2416                data.contains("nothing")
2417            }
2418            "#
2419            .to_vec(),
2420        )?;
2421
2422        let compiled = vm.get_fn("vm_map_contains::contains_existing", &[Type::Any])?;
2423        assert_eq!(compiled.ret_ty(), &Type::Bool);
2424        let contains_existing: extern "C" fn(*const Dynamic) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
2425        let data = dynamic::map!("name"=> "test");
2426        assert!(contains_existing(&data));
2427
2428        let compiled = vm.get_fn("vm_map_contains::contains_missing", &[Type::Any])?;
2429        assert_eq!(compiled.ret_ty(), &Type::Bool);
2430        let contains_missing: extern "C" fn(*const Dynamic) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
2431        assert!(!contains_missing(&data));
2432        Ok(())
2433    }
2434
2435    #[test]
2436    fn list_pop_on_empty_list_returns_null() -> anyhow::Result<()> {
2437        let vm = Vm::with_all()?;
2438        vm.import_code(
2439            "vm_pop_empty",
2440            br#"
2441            pub fn pop_new_list() {
2442                let items = [];
2443                let value = items.pop();
2444                let still_empty = items.len() == 0;
2445                {value: value, empty: still_empty}
2446            }
2447
2448            pub fn pop_until_empty() {
2449                let items = [1i64, 2i64];
2450                items.pop();
2451                let last = items.pop();
2452                let drained = items.pop();
2453                {last: last, drained: drained}
2454            }
2455            "#
2456            .to_vec(),
2457        )?;
2458
2459        let compiled = vm.get_fn("vm_pop_empty::pop_new_list", &[])?;
2460        assert_eq!(compiled.ret_ty(), &Type::Any);
2461        let pop_new_list: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
2462        let result = unsafe { &*pop_new_list() };
2463        assert!(result.get_dynamic("value").is_some_and(|v| v.is_null()));
2464        assert_eq!(result.get_dynamic("empty").and_then(|v| v.as_bool()), Some(true));
2465
2466        let compiled = vm.get_fn("vm_pop_empty::pop_until_empty", &[])?;
2467        assert_eq!(compiled.ret_ty(), &Type::Any);
2468        let pop_until_empty: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
2469        let result = unsafe { &*pop_until_empty() };
2470        assert_eq!(result.get_dynamic("last").and_then(|v| v.as_int()), Some(1));
2471        assert!(result.get_dynamic("drained").is_some_and(|v| v.is_null()));
2472        Ok(())
2473    }
2474
2475    #[test]
2476    fn void_function_with_multiple_code_paths() -> anyhow::Result<()> {
2477        let vm = Vm::with_all()?;
2478        vm.import_code(
2479            "vm_void_multi_path",
2480            br#"
2481            pub fn log_if_positive(value: i64) {
2482                if value > 0 {
2483                    print(value);
2484                    return;
2485                }
2486                if value < 0 {
2487                    print(-value);
2488                    return;
2489                }
2490                print(0);
2491            }
2492            "#
2493            .to_vec(),
2494        )?;
2495
2496        let compiled = vm.get_fn("vm_void_multi_path::log_if_positive", &[Type::I64])?;
2497        assert!(compiled.ret_ty().is_void());
2498        Ok(())
2499    }
2500
2501    #[test]
2502    fn any_method_call_chain_on_returned_dynamic_value() -> anyhow::Result<()> {
2503        let vm = Vm::with_all()?;
2504        vm.import_code(
2505            "vm_any_method_chain",
2506            br#"
2507            pub fn get_tags(data) {
2508                let tags = data.tags;
2509                if tags.is_list() {
2510                    return tags.len();
2511                }
2512                0
2513            }
2514            "#
2515            .to_vec(),
2516        )?;
2517
2518        let compiled = vm.get_fn("vm_any_method_chain::get_tags", &[Type::Any])?;
2519        assert_eq!(compiled.ret_ty(), &Type::I32);
2520        let get_tags: extern "C" fn(*const Dynamic) -> i32 = unsafe { std::mem::transmute(compiled.ptr()) };
2521        let data = dynamic::map!("tags"=> Dynamic::list(vec!["a".into(), "b".into(), "c".into()]));
2522        assert_eq!(get_tags(&data), 3);
2523
2524        let empty_data = Dynamic::Null;
2525        assert_eq!(get_tags(&empty_data), 0);
2526        Ok(())
2527    }
2528
2529    #[test]
2530    fn infers_any_arg_function_return_before_body_compile() -> anyhow::Result<()> {
2531        let vm = Vm::with_all()?;
2532        vm.import_code(
2533            "vm_infer_any_arg_return",
2534            br#"
2535            pub fn caller(candidate) {
2536                let center = polygon_center(candidate.visualPolygon);
2537                center[0]
2538            }
2539
2540            pub fn polygon_center(point_list) {
2541                let total_x = 0;
2542                let total_y = 0;
2543                let count = 0;
2544                if point_list.is_list() {
2545                    for point in point_list {
2546                        if point.is_list() && point.len() >= 2 {
2547                            total_x += point[0];
2548                            total_y += point[1];
2549                            count += 1;
2550                        }
2551                    }
2552                }
2553                if count == 0 {
2554                    return [0, 0];
2555                }
2556                [total_x / count, total_y / count]
2557            }
2558            "#
2559            .to_vec(),
2560        )?;
2561
2562        let compiled = vm.get_fn("vm_infer_any_arg_return::caller", &[Type::Any])?;
2563        assert_eq!(compiled.ret_ty(), &Type::Any);
2564        let caller: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
2565        let candidate = dynamic::map!(
2566            "visualPolygon"=> Dynamic::list(vec![
2567                Dynamic::list(vec![2i64.into(), 4i64.into()]),
2568                Dynamic::list(vec![6i64.into(), 8i64.into()]),
2569            ])
2570        );
2571        let result = unsafe { &*caller(&candidate) };
2572        assert_eq!(result.as_int(), Some(4));
2573        Ok(())
2574    }
2575
2576    #[test]
2577    fn root_get_returns_null_for_missing_key_which_compares_correctly() -> anyhow::Result<()> {
2578        let vm = Vm::with_all()?;
2579        vm.import_code(
2580            "vm_root_get_missing",
2581            br#"
2582            pub fn check_missing() {
2583                let existing = root::get("local/vm_root_get_missing_test");
2584                if existing.is_map() {
2585                    return false;
2586                }
2587                true
2588            }
2589            "#
2590            .to_vec(),
2591        )?;
2592
2593        let compiled = vm.get_fn("vm_root_get_missing::check_missing", &[])?;
2594        assert_eq!(compiled.ret_ty(), &Type::Bool);
2595        let check_missing: extern "C" fn() -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
2596        assert!(check_missing());
2597        Ok(())
2598    }
2599
2600    #[test]
2601    fn map_get_key_on_null_map_returns_null() -> anyhow::Result<()> {
2602        let vm = Vm::with_all()?;
2603        vm.import_code(
2604            "vm_get_key_null_map",
2605            br#"
2606            pub fn get_key_null(data) {
2607                data.get_key("missing")
2608            }
2609            "#
2610            .to_vec(),
2611        )?;
2612
2613        let compiled = vm.get_fn("vm_get_key_null_map::get_key_null", &[Type::Any])?;
2614        assert_eq!(compiled.ret_ty(), &Type::Any);
2615        let get_key_null: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
2616
2617        let data_map = dynamic::map!("exists"=> 1i64);
2618        let missing = unsafe { &*get_key_null(&data_map) };
2619        assert!(missing.is_null());
2620
2621        let null = Dynamic::Null;
2622        let result = unsafe { &*get_key_null(&null) };
2623        assert!(result.is_null());
2624        Ok(())
2625    }
2626
2627    #[test]
2628    fn keys_on_empty_map_returns_empty_list() -> anyhow::Result<()> {
2629        let vm = Vm::with_all()?;
2630        vm.import_code(
2631            "vm_keys_empty_map",
2632            br#"
2633            pub fn empty_map_keys() {
2634                let data = {};
2635                data.keys().len()
2636            }
2637            "#
2638            .to_vec(),
2639        )?;
2640
2641        let compiled = vm.get_fn("vm_keys_empty_map::empty_map_keys", &[])?;
2642        assert_eq!(compiled.ret_ty(), &Type::I32);
2643        let empty_map_keys: extern "C" fn() -> i32 = unsafe { std::mem::transmute(compiled.ptr()) };
2644        assert_eq!(empty_map_keys(), 0);
2645        Ok(())
2646    }
2647
2648    #[test]
2649    fn cast_between_all_integer_widths() -> anyhow::Result<()> {
2650        let vm = Vm::with_all()?;
2651        vm.import_code(
2652            "vm_cast_integer_widths",
2653            br#"
2654            pub fn i64_to_i32(value: i64) {
2655                value as i32
2656            }
2657
2658            pub fn i32_to_i64(value: i32) {
2659                value as i64
2660            }
2661
2662            pub fn u32_to_i64(value: u32) {
2663                value as i64
2664            }
2665            "#
2666            .to_vec(),
2667        )?;
2668
2669        let compiled = vm.get_fn("vm_cast_integer_widths::i64_to_i32", &[Type::I64])?;
2670        assert_eq!(compiled.ret_ty(), &Type::I32);
2671        let i64_to_i32: extern "C" fn(i64) -> i32 = unsafe { std::mem::transmute(compiled.ptr()) };
2672        assert_eq!(i64_to_i32(42), 42);
2673
2674        let compiled = vm.get_fn("vm_cast_integer_widths::i32_to_i64", &[Type::I32])?;
2675        assert_eq!(compiled.ret_ty(), &Type::I64);
2676        let i32_to_i64: extern "C" fn(i32) -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
2677        assert_eq!(i32_to_i64(-1), -1);
2678
2679        let compiled = vm.get_fn("vm_cast_integer_widths::u32_to_i64", &[Type::U32])?;
2680        assert_eq!(compiled.ret_ty(), &Type::I64);
2681        let u32_to_i64: extern "C" fn(u32) -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
2682        assert_eq!(u32_to_i64(42), 42);
2683        Ok(())
2684    }
2685
2686    #[test]
2687    fn boolean_literals_in_complex_expression_trees() -> anyhow::Result<()> {
2688        let vm = Vm::with_all()?;
2689        vm.import_code(
2690            "vm_complex_boolean",
2691            br#"
2692            pub fn exclusive_or(a: bool, b: bool) {
2693                (a && !b) || (!a && b)
2694            }
2695
2696            pub fn implies(a: bool, b: bool) {
2697                !a || b
2698            }
2699            "#
2700            .to_vec(),
2701        )?;
2702
2703        let compiled = vm.get_fn("vm_complex_boolean::exclusive_or", &[Type::Bool, Type::Bool])?;
2704        assert_eq!(compiled.ret_ty(), &Type::Bool);
2705        let exclusive_or: extern "C" fn(bool, bool) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
2706        assert!(exclusive_or(true, false));
2707        assert!(exclusive_or(false, true));
2708        assert!(!exclusive_or(true, true));
2709        assert!(!exclusive_or(false, false));
2710
2711        let compiled = vm.get_fn("vm_complex_boolean::implies", &[Type::Bool, Type::Bool])?;
2712        assert_eq!(compiled.ret_ty(), &Type::Bool);
2713        let implies: extern "C" fn(bool, bool) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
2714        assert!(implies(false, true));
2715        assert!(implies(false, false));
2716        assert!(implies(true, true));
2717        assert!(!implies(true, false));
2718        Ok(())
2719    }
2720
2721    #[test]
2722    fn concrete_struct_method_returning_self_type() -> anyhow::Result<()> {
2723        let vm = Vm::with_all()?;
2724        vm.import_code(
2725            "vm_struct_method_self",
2726            br#"
2727            pub struct Vec3 {
2728                x: f64,
2729                y: f64,
2730                z: f64,
2731            }
2732
2733            impl Vec3 {
2734                pub fn add(self: Vec3, other: Vec3) {
2735                    Vec3{x: self.x + other.x, y: self.y + other.y, z: self.z + other.z}
2736                }
2737            }
2738
2739            pub fn run() {
2740                let v1 = Vec3{x: 1.0f64, y: 2.0f64, z: 3.0f64};
2741                let v2 = Vec3{x: 4.0f64, y: 5.0f64, z: 6.0f64};
2742                let sum = v1.add(v2);
2743                sum.x + sum.y + sum.z
2744            }
2745            "#
2746            .to_vec(),
2747        )?;
2748
2749        let compiled = vm.get_fn("vm_struct_method_self::run", &[])?;
2750        assert_eq!(compiled.ret_ty(), &Type::F64);
2751        let run: extern "C" fn() -> f64 = unsafe { std::mem::transmute(compiled.ptr()) };
2752        assert_eq!(run(), 21.0);
2753        Ok(())
2754    }
2755
2756    #[test]
2757    fn deep_nested_struct_access_with_multiple_field_levels() -> anyhow::Result<()> {
2758        let vm = Vm::with_all()?;
2759        vm.import_code(
2760            "vm_deep_nested_struct",
2761            br#"
2762            pub struct A {
2763                value: i64,
2764            }
2765
2766            pub struct B {
2767                a: A,
2768            }
2769
2770            pub struct C {
2771                b: B,
2772            }
2773
2774            pub fn direct_access() {
2775                let c = C{b: B{a: A{value: 99}}};
2776                c.b.a.value
2777            }
2778
2779            pub fn via_variable() {
2780                let c = C{b: B{a: A{value: 77}}};
2781                let b = c.b;
2782                let a = b.a;
2783                a.value
2784            }
2785            "#
2786            .to_vec(),
2787        )?;
2788
2789        let compiled = vm.get_fn("vm_deep_nested_struct::direct_access", &[])?;
2790        assert_eq!(compiled.ret_ty(), &Type::I64);
2791        let direct_access: extern "C" fn() -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
2792        assert_eq!(direct_access(), 99);
2793
2794        let compiled = vm.get_fn("vm_deep_nested_struct::via_variable", &[])?;
2795        assert_eq!(compiled.ret_ty(), &Type::I64);
2796        let via_variable: extern "C" fn() -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
2797        assert_eq!(via_variable(), 77);
2798        Ok(())
2799    }
2800
2801    #[test]
2802    fn array_index_with_dynamic_value_via_method() -> anyhow::Result<()> {
2803        let vm = Vm::with_all()?;
2804        vm.import_code(
2805            "vm_array_idx_dynamic",
2806            br#"
2807            pub fn get_by_idx(list, idx) {
2808                list.get_idx(idx)
2809            }
2810            "#
2811            .to_vec(),
2812        )?;
2813
2814        let compiled = vm.get_fn("vm_array_idx_dynamic::get_by_idx", &[Type::Any, Type::I64])?;
2815        assert_eq!(compiled.ret_ty(), &Type::Any);
2816        let get_by_idx: extern "C" fn(*const Dynamic, i64) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
2817
2818        let list = Dynamic::list(vec!["a".into(), "b".into()]);
2819        let first = unsafe { &*get_by_idx(&list, 0) };
2820        assert_eq!(first.as_str(), "a");
2821
2822        let out = unsafe { &*get_by_idx(&list, 10) };
2823        assert!(out.is_null());
2824        Ok(())
2825    }
2826
2827    #[test]
2828    fn dynamic_field_access_with_optional_or_fallback() -> anyhow::Result<()> {
2829        let vm = Vm::with_all()?;
2830        vm.import_code(
2831            "vm_dynamic_or_fallback",
2832            br#"
2833            pub fn with_fallback(data) {
2834                if data.contains("name") { data.name } else { "unknown" }
2835            }
2836
2837            pub fn with_fallback_missing(data) {
2838                if data.contains("nickname") { data.nickname } else { "unnamed" }
2839            }
2840            "#
2841            .to_vec(),
2842        )?;
2843
2844        let compiled = vm.get_fn("vm_dynamic_or_fallback::with_fallback", &[Type::Any])?;
2845        assert_eq!(compiled.ret_ty(), &Type::Any);
2846        let with_fallback: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
2847        let data = dynamic::map!("name"=> "Alice");
2848        let result = unsafe { &*with_fallback(&data) };
2849        assert_eq!(result.as_str(), "Alice");
2850
2851        let compiled = vm.get_fn("vm_dynamic_or_fallback::with_fallback_missing", &[Type::Any])?;
2852        let with_fallback_missing: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
2853        let result = unsafe { &*with_fallback_missing(&data) };
2854        assert_eq!(result.as_str(), "unnamed");
2855        Ok(())
2856    }
2857
2858    #[test]
2859    fn for_in_loop_iterates_over_list_and_map_directly() -> anyhow::Result<()> {
2860        let vm = Vm::with_all()?;
2861        vm.import_code(
2862            "vm_for_in_collection",
2863            br#"
2864            pub fn sum_list(items) {
2865                let total = 0i64;
2866                for item in items {
2867                    total = total + 1;
2868                }
2869                total
2870            }
2871
2872            pub fn count_map_keys(data) {
2873                let count = 0i64;
2874                for key in data.keys() {
2875                    count = count + 1;
2876                }
2877                count
2878            }
2879
2880            pub fn for_in_list_works(items) {
2881                let exists = false;
2882                for item in items {
2883                    exists = true;
2884                }
2885                exists
2886            }
2887
2888            pub fn for_in_map_values_works(data) {
2889                let exists = false;
2890                for value in data {
2891                    exists = true;
2892                }
2893                exists
2894            }
2895            "#
2896            .to_vec(),
2897        )?;
2898
2899        let compiled = vm.get_fn("vm_for_in_collection::sum_list", &[Type::Any])?;
2900        assert_eq!(compiled.ret_ty(), &Type::I64);
2901        let sum_list: extern "C" fn(*const Dynamic) -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
2902        let items = Dynamic::list(vec![Dynamic::from(1i64), Dynamic::from(2i64), Dynamic::from(3i64)]);
2903        assert_eq!(sum_list(&items), 3);
2904
2905        let data = dynamic::map!("x"=> 1i64, "y"=> 2i64);
2906        let compiled = vm.get_fn("vm_for_in_collection::count_map_keys", &[Type::Any])?;
2907        assert_eq!(compiled.ret_ty(), &Type::I64);
2908        let count_map_keys: extern "C" fn(*const Dynamic) -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
2909        assert_eq!(count_map_keys(&data), 2);
2910
2911        let compiled = vm.get_fn("vm_for_in_collection::for_in_list_works", &[Type::Any])?;
2912        assert_eq!(compiled.ret_ty(), &Type::Bool);
2913        let for_in_list_works: extern "C" fn(*const Dynamic) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
2914        let empty = Dynamic::list(Vec::new());
2915        assert!(!for_in_list_works(&empty));
2916        assert!(for_in_list_works(&items));
2917
2918        let compiled = vm.get_fn("vm_for_in_collection::for_in_map_values_works", &[Type::Any])?;
2919        assert_eq!(compiled.ret_ty(), &Type::Bool);
2920        let for_in_map_values_works: extern "C" fn(*const Dynamic) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
2921        let empty_map = dynamic::map!();
2922        assert!(!for_in_map_values_works(&empty_map));
2923        assert!(for_in_map_values_works(&data));
2924
2925        Ok(())
2926    }
2927
2928    #[test]
2929    fn concurrent_100_threads_no_memory_leak() -> anyhow::Result<()> {
2930        let vm = Vm::with_all()?;
2931        vm.import_code(
2932            "vm_stress",
2933            br#"
2934            pub fn heavy_alloc(idx: i64) {
2935                let items = [];
2936                let i = 0;
2937                while i < 50 {
2938                    items.push({
2939                        id: i + idx,
2940                        name: "item-" + i,
2941                        tags: ["tag-a", "tag-b", "tag-c"],
2942                        meta: {
2943                            created: 1234567890i64,
2944                            score: (i * 3.14f64) as i64,
2945                            extra: "prefix/" + i + "/" + idx
2946                        }
2947                    });
2948                    i = i + 1;
2949                }
2950                items
2951            }
2952
2953            pub fn string_concat_stress() {
2954                let i = 0;
2955                let result = "";
2956                while i < 200 {
2957                    result = result + "data-" + i + ",";
2958                    i = i + 1;
2959                }
2960                result
2961            }
2962            "#
2963            .to_vec(),
2964        )?;
2965
2966        let (heavy_ptr, _) = vm.get_fn_ptr("vm_stress::heavy_alloc", &[Type::I64])?;
2967        let (concat_ptr, _) = vm.get_fn_ptr("vm_stress::string_concat_stress", &[])?;
2968
2969        let threads: usize = std::thread::available_parallelism()
2970            .map(|n| n.get())
2971            .unwrap_or(4)
2972            .max(100);
2973        let iters_per_thread = 200;
2974        let total_calls = threads * iters_per_thread * 2;
2975
2976        let before = current_rss_kb();
2977        eprintln!("threads={threads} iters_per_thread={iters_per_thread} total_calls={total_calls} rss_before={before}KB");
2978
2979        // Round 1: first concurrent execution (arena warm-up)
2980        run_stress_round(threads, iters_per_thread, heavy_ptr as usize, concat_ptr as usize);
2981        let r1 = current_rss_kb();
2982        eprintln!("rss_after_round1={r1}KB");
2983
2984        // Round 2: should stabilize (no unbounded growth)
2985        run_stress_round(threads, iters_per_thread, heavy_ptr as usize, concat_ptr as usize);
2986        let r2 = current_rss_kb();
2987        eprintln!("rss_after_round2={r2}KB");
2988
2989        // Round 3: final check
2990        run_stress_round(threads, iters_per_thread, heavy_ptr as usize, concat_ptr as usize);
2991        let r3 = current_rss_kb();
2992        eprintln!("rss_after_round3={r3}KB");
2993
2994        // Growth should decrease (memory stabilizes after arena allocation in round 1)
2995        let d12 = r2.saturating_sub(r1);
2996        let d23 = r3.saturating_sub(r2);
2997        eprintln!("delta_r1→r2={d12}KB delta_r2→r3={d23}KB");
2998
2999        // After arena warm-up, subsequent rounds should not grow significantly.
3000        // Allow up to 20 MB growth per round (OS page cache, thread stack reuse, etc.)
3001        let max_growth_kb = 20 * 1024;
3002        assert!(
3003            d12 < max_growth_kb && d23 < max_growth_kb,
3004            "memory keeps growing between rounds: round1={r1} round2={r2} round3={r3} delta12={d12}KB delta23={d23}KB (max allowed={max_growth_kb}KB)"
3005        );
3006
3007        Ok(())
3008    }
3009
3010    fn run_stress_round(threads: usize, iters: usize, heavy_ptr: usize, concat_ptr: usize) {
3011        std::thread::scope(|scope| {
3012            let mut handles = Vec::with_capacity(threads);
3013            for t in 0..threads {
3014                let heavy_ptr = heavy_ptr;
3015                let concat_ptr = concat_ptr;
3016                handles.push(scope.spawn(move || {
3017                    let heavy_fn: extern "C" fn(i64) -> *const Dynamic =
3018                        unsafe { std::mem::transmute(heavy_ptr as *const u8) };
3019                    let concat_fn: extern "C" fn() -> *const Dynamic =
3020                        unsafe { std::mem::transmute(concat_ptr as *const u8) };
3021                    for i in 0..iters {
3022                        // heavy_alloc: drop returned value to free heap allocation
3023                        let r_ptr = heavy_fn((t * iters + i) as i64);
3024                        assert!(!r_ptr.is_null());
3025                        unsafe {
3026                            let r = &*r_ptr;
3027                            assert!(r.len() > 0, "heavy_alloc returned empty list");
3028                            drop(Box::from_raw(r_ptr as *mut Dynamic));
3029                        }
3030
3031                        // concat: same, drop returned value
3032                        let s_ptr = concat_fn();
3033                        assert!(!s_ptr.is_null());
3034                        unsafe {
3035                            let s = &*s_ptr;
3036                            assert!(s.len() > 0, "string_concat_stress returned empty");
3037                            drop(Box::from_raw(s_ptr as *mut Dynamic));
3038                        }
3039                    }
3040                }));
3041            }
3042            for h in handles {
3043                h.join().unwrap();
3044            }
3045        });
3046    }
3047
3048    fn current_rss_kb() -> u64 {
3049    // macOS: use ps
3050    let pid = std::process::id();
3051    if let Ok(output) = std::process::Command::new("ps")
3052        .args(["-p", &pid.to_string(), "-o", "rss="])
3053        .output()
3054    {
3055        if let Ok(s) = String::from_utf8(output.stdout) {
3056            if let Some(kb) = s.trim().parse::<u64>().ok() {
3057                return kb;
3058            }
3059        }
3060    }
3061    // Linux fallback: /proc/self/statm
3062    if let Ok(statm) = std::fs::read_to_string("/proc/self/statm") {
3063        let parts: Vec<&str> = statm.split_whitespace().collect();
3064        if let Some(rss_pages) = parts.get(1).and_then(|s| s.parse::<u64>().ok()) {
3065            return rss_pages * 4; // pages (4KB) → KB
3066        }
3067    }
3068    0
3069}
3070
3071}