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