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