Skip to main content

vm/
lib.rs

1//使用 cranelift 作为后端 直接 jit 解释脚本
2mod binary;
3mod native;
4pub use native::{ANY, STD};
5
6mod fns;
7use anyhow::{Result, anyhow};
8pub use fns::{FnInfo, FnVariant};
9mod context;
10pub use context::BuildContext;
11
12mod rt;
13use cranelift::prelude::types;
14use dynamic::Type;
15pub use rt::JITRunTime;
16use smol_str::SmolStr;
17mod db_module;
18mod gpu_layout;
19mod gpu_module;
20mod http_module;
21mod llm_module;
22mod root_module;
23pub use gpu_layout::{GpuFieldLayout, GpuStructLayout};
24
25use std::sync::{Mutex, OnceLock, Weak};
26static PTR_TYPE: OnceLock<types::Type> = OnceLock::new();
27pub fn ptr_type() -> types::Type {
28    PTR_TYPE.get().cloned().unwrap()
29}
30
31pub fn get_type(ty: &Type) -> Result<types::Type> {
32    if ty.is_f64() {
33        Ok(types::F64)
34    } else if ty.is_f32() {
35        Ok(types::F32)
36    } else if ty.is_int() | ty.is_uint() {
37        match ty.width() {
38            1 => Ok(types::I8),
39            2 => Ok(types::I16),
40            4 => Ok(types::I32),
41            8 => Ok(types::I64),
42            _ => Err(anyhow!("非法类型 {:?}", ty)),
43        }
44    } else if let Type::Bool = ty {
45        Ok(types::I8)
46    } else {
47        Ok(ptr_type())
48    }
49}
50
51use compiler::Symbol;
52use cranelift::prelude::*;
53
54pub fn init_jit(mut jit: JITRunTime) -> Result<JITRunTime> {
55    jit.add_all()?;
56    Ok(jit)
57}
58
59use std::sync::Arc;
60unsafe impl Send for JITRunTime {}
61unsafe impl Sync for JITRunTime {}
62
63pub(crate) fn with_vm_context<T>(context: *const Weak<Mutex<JITRunTime>>, f: impl FnOnce(&Vm) -> Result<T>) -> Result<T> {
64    if context.is_null() {
65        return Err(anyhow!("VM context is null"));
66    }
67    let jit = unsafe { &*context }.upgrade().ok_or_else(|| anyhow!("VM context has expired"))?;
68    let vm = Vm { jit };
69    f(&vm)
70}
71
72fn add_method_field(jit: &mut JITRunTime, def: &str, method: &str, id: u32) -> Result<()> {
73    let def_id = jit.get_id(def)?;
74    if let Some((_, define)) = jit.compiler.symbols.get_symbol_mut(def_id) {
75        if let Symbol::Struct(Type::Struct { params, fields }, _) = define {
76            fields.push((method.into(), Type::Symbol { id, params: params.clone() }));
77        }
78    }
79    Ok(())
80}
81
82fn add_native_module_fns(jit: &mut JITRunTime, module: &str, fns: &[(&str, &[Type], Type, *const u8)]) -> Result<()> {
83    jit.add_module(module);
84    for (name, arg_tys, ret_ty, fn_ptr) in fns {
85        let full_name = format!("{}::{}", module, name);
86        jit.add_native_ptr(&full_name, name, arg_tys, ret_ty.clone(), *fn_ptr)?;
87    }
88    jit.pop_module();
89    Ok(())
90}
91
92impl JITRunTime {
93    pub fn add_module(&mut self, name: &str) {
94        self.compiler.symbols.add_module(name.into());
95    }
96
97    pub fn pop_module(&mut self) {
98        self.compiler.symbols.pop_module();
99    }
100
101    pub fn add_type(&mut self, name: &str, ty: Type, is_pub: bool) -> u32 {
102        self.compiler.add_symbol(name, Symbol::Struct(ty, is_pub))
103    }
104
105    pub fn add_empty_type(&mut self, name: &str) -> Result<u32> {
106        match self.get_id(name) {
107            Ok(id) => Ok(id),
108            Err(_) => Ok(self.add_type(name, Type::Struct { params: Vec::new(), fields: Vec::new() }, true)),
109        }
110    }
111
112    pub fn add_native_module_ptr(&mut self, module: &str, name: &str, arg_tys: &[Type], ret_ty: Type, fn_ptr: *const u8) -> Result<u32> {
113        self.add_module(module);
114        let full_name = format!("{}::{}", module, name);
115        let result = self.add_native_ptr(&full_name, name, arg_tys, ret_ty, fn_ptr);
116        self.pop_module();
117        result
118    }
119
120    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> {
121        self.add_module(module);
122        let full_name = format!("{}::{}", module, name);
123        let result = self.add_context_native_ptr(&full_name, name, arg_tys, ret_ty, fn_ptr);
124        self.pop_module();
125        result
126    }
127
128    pub fn add_native_method_ptr(&mut self, def: &str, method: &str, arg_tys: &[Type], ret_ty: Type, fn_ptr: *const u8) -> Result<u32> {
129        self.add_empty_type(def)?;
130        let full_name = format!("{}::{}", def, method);
131        let id = self.add_native_ptr(&full_name, &full_name, arg_tys, ret_ty, fn_ptr)?;
132        add_method_field(self, def, method, id)?;
133        Ok(id)
134    }
135
136    pub fn add_std(&mut self) -> Result<()> {
137        self.add_module("std");
138        for (name, arg_tys, ret_ty, fn_ptr) in STD {
139            self.add_native_ptr(name, name, arg_tys, ret_ty, fn_ptr)?;
140        }
141        self.add_context_native_ptr("import", "import", &[Type::Any, Type::Any], Type::Bool, native::import_with_vm as *const u8)?;
142        Ok(())
143    }
144
145    pub fn add_any(&mut self) -> Result<()> {
146        for (name, arg_tys, ret_ty, fn_ptr) in ANY {
147            let (_, method) = name.split_once("::").ok_or_else(|| anyhow!("非法 Any 方法名 {}", name))?;
148            self.add_native_method_ptr("Any", method, arg_tys, ret_ty, fn_ptr)?;
149        }
150        Ok(())
151    }
152
153    pub fn add_vec(&mut self) -> Result<()> {
154        self.add_empty_type("Vec")?;
155        let vec_def = Type::Symbol { id: self.get_id("Vec")?, params: Vec::new() };
156        self.add_inline("Vec::swap", vec![vec_def.clone(), Type::I64, Type::I64], Type::Void, |ctx: Option<&mut BuildContext>, args: Vec<Value>| {
157            if let Some(ctx) = ctx {
158                let width = ctx.builder.ins().iconst(types::I64, 4);
159                let offset_val = ctx.builder.ins().imul(args[1], width); // i * 4 i32大小四字节
160                let final_addr = ctx.builder.ins().iadd(args[0], offset_val); // base + (i*4)
161                let dest = ctx.builder.ins().imul(args[2], width);
162                let dest_addr = ctx.builder.ins().iadd(args[0], dest); // base + (i*4)
163                let dest_val = ctx.builder.ins().load(types::I32, MemFlags::trusted(), dest_addr, 0);
164                let v = ctx.builder.ins().load(types::I32, MemFlags::trusted(), final_addr, 0);
165                ctx.builder.ins().store(MemFlags::trusted(), v, dest_addr, 0);
166                ctx.builder.ins().store(MemFlags::trusted(), dest_val, final_addr, 0);
167            }
168            Err(anyhow!("无返回值"))
169        })?;
170
171        self.add_inline("Vec::get_idx", vec![vec_def.clone(), Type::I64], Type::I32, |ctx: Option<&mut BuildContext>, args: Vec<Value>| {
172            if let Some(ctx) = ctx {
173                let width = ctx.builder.ins().iconst(types::I64, 4);
174                let offset_val = ctx.builder.ins().imul(args[1], width); // i * 4 i32大小四字节
175                let final_addr = ctx.builder.ins().iadd(args[0], offset_val);
176                Ok((Some(ctx.builder.ins().load(types::I32, MemFlags::trusted(), final_addr, 0)), Type::I32))
177            } else {
178                Ok((None, Type::I32))
179            }
180        })?;
181        Ok(())
182    }
183
184    pub fn add_llm(&mut self) -> Result<()> {
185        add_native_module_fns(self, "llm", &llm_module::LLM_NATIVE)
186    }
187
188    pub fn add_root(&mut self) -> Result<()> {
189        add_native_module_fns(self, "root", &root_module::ROOT_NATIVE)?;
190        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)?;
191        Ok(())
192    }
193
194    pub fn add_http(&mut self) -> Result<()> {
195        add_native_module_fns(self, "http", &http_module::HTTP_NATIVE)
196    }
197
198    pub fn add_db(&mut self) -> Result<()> {
199        add_native_module_fns(self, "db", &db_module::DB_NATIVE)
200    }
201
202    pub fn add_gpu(&mut self) -> Result<()> {
203        add_native_module_fns(self, "gpu", &gpu_module::GPU_NATIVE)
204    }
205
206    pub fn add_all(&mut self) -> Result<()> {
207        self.add_std()?;
208        self.add_any()?;
209        self.add_vec()?;
210        self.add_llm()?;
211        self.add_root()?;
212        self.add_http()?;
213        self.add_db()?;
214        self.add_gpu()?;
215        Ok(())
216    }
217}
218
219#[derive(Clone)]
220pub struct Vm {
221    jit: Arc<Mutex<JITRunTime>>,
222}
223
224#[derive(Clone)]
225pub struct CompiledFn {
226    ptr: usize,
227    ret: Type,
228    owner: Vm,
229}
230
231impl CompiledFn {
232    pub fn ptr(&self) -> *const u8 {
233        self.ptr as *const u8
234    }
235
236    pub fn ret_ty(&self) -> &Type {
237        &self.ret
238    }
239
240    pub fn owner(&self) -> &Vm {
241        &self.owner
242    }
243}
244
245impl Vm {
246    pub fn new() -> Self {
247        let jit = Arc::new(Mutex::new(JITRunTime::new(|_| {})));
248        jit.lock().unwrap().set_owner(Arc::downgrade(&jit));
249        Self { jit }
250    }
251
252    pub fn with_all() -> Result<Self> {
253        let vm = Self::new();
254        vm.add_all()?;
255        Ok(vm)
256    }
257
258    pub fn add_module(&self, name: &str) {
259        self.jit.lock().unwrap().add_module(name)
260    }
261
262    pub fn pop_module(&self) {
263        self.jit.lock().unwrap().pop_module()
264    }
265
266    pub fn add_type(&self, name: &str, ty: Type, is_pub: bool) -> u32 {
267        self.jit.lock().unwrap().add_type(name, ty, is_pub)
268    }
269
270    pub fn add_empty_type(&self, name: &str) -> Result<u32> {
271        self.jit.lock().unwrap().add_empty_type(name)
272    }
273
274    pub fn add_std(&self) -> Result<()> {
275        self.jit.lock().unwrap().add_std()
276    }
277
278    pub fn add_any(&self) -> Result<()> {
279        self.jit.lock().unwrap().add_any()
280    }
281
282    pub fn add_vec(&self) -> Result<()> {
283        self.jit.lock().unwrap().add_vec()
284    }
285
286    pub fn add_llm(&self) -> Result<()> {
287        self.jit.lock().unwrap().add_llm()
288    }
289
290    pub fn add_root(&self) -> Result<()> {
291        self.jit.lock().unwrap().add_root()
292    }
293
294    pub fn add_http(&self) -> Result<()> {
295        self.jit.lock().unwrap().add_http()
296    }
297
298    pub fn add_db(&self) -> Result<()> {
299        self.jit.lock().unwrap().add_db()
300    }
301
302    pub fn add_gpu(&self) -> Result<()> {
303        self.jit.lock().unwrap().add_gpu()
304    }
305
306    pub fn add_all(&self) -> Result<()> {
307        self.jit.lock().unwrap().add_all()
308    }
309
310    pub fn add_native_ptr(&self, full_name: &str, name: &str, arg_tys: &[Type], ret_ty: Type, fn_ptr: *const u8) -> Result<u32> {
311        self.jit.lock().unwrap().add_native_ptr(full_name, name, arg_tys, ret_ty, fn_ptr)
312    }
313
314    pub fn add_native_module_ptr(&self, module: &str, name: &str, arg_tys: &[Type], ret_ty: Type, fn_ptr: *const u8) -> Result<u32> {
315        self.jit.lock().unwrap().add_native_module_ptr(module, name, arg_tys, ret_ty, fn_ptr)
316    }
317
318    pub fn add_native_method_ptr(&self, def: &str, method: &str, arg_tys: &[Type], ret_ty: Type, fn_ptr: *const u8) -> Result<u32> {
319        self.jit.lock().unwrap().add_native_method_ptr(def, method, arg_tys, ret_ty, fn_ptr)
320    }
321
322    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> {
323        self.jit.lock().unwrap().add_inline(name, args, ret, f)
324    }
325
326    pub fn import_code(&self, name: &str, code: Vec<u8>) -> Result<()> {
327        self.jit.lock().unwrap().import_code(name, code)
328    }
329
330    pub fn import_file(&self, name: &str, path: &str) -> Result<()> {
331        self.jit.lock().unwrap().compiler.import_file(name, path)?;
332        Ok(())
333    }
334
335    pub fn import(&self, name: &str, path: &str) -> Result<()> {
336        if root::contains(path) {
337            let code = root::get(path).unwrap();
338            if code.is_str() {
339                self.import_code(name, code.as_str().as_bytes().to_vec())
340            } else {
341                self.import_code(name, code.get_dynamic("code").ok_or(anyhow!("{:?} 没有 code 成员", code))?.as_str().as_bytes().to_vec())
342            }
343        } else {
344            self.import_file(name, path)
345        }
346    }
347
348    pub fn infer(&self, name: &str, arg_tys: &[Type]) -> Result<Type> {
349        self.jit.lock().unwrap().get_type(name, arg_tys)
350    }
351
352    pub fn get_fn_ptr(&self, name: &str, arg_tys: &[Type]) -> Result<(*const u8, Type)> {
353        self.jit.lock().unwrap().get_fn_ptr(name, arg_tys)
354    }
355
356    pub fn get_fn(&self, name: &str, arg_tys: &[Type]) -> Result<CompiledFn> {
357        let (ptr, ret) = self.get_fn_ptr(name, arg_tys)?;
358        Ok(CompiledFn { ptr: ptr as usize, ret, owner: self.clone() })
359    }
360
361    pub fn load(&self, code: Vec<u8>, arg_name: SmolStr) -> Result<(i64, Type)> {
362        self.jit.lock().unwrap().load(code, arg_name)
363    }
364
365    pub fn get_symbol(&self, name: &str, params: Vec<Type>) -> Result<Type> {
366        Ok(Type::Symbol { id: self.jit.lock().unwrap().get_id(name)?, params })
367    }
368
369    pub fn gpu_struct_layout(&self, name: &str, params: &[Type]) -> Result<GpuStructLayout> {
370        let jit = self.jit.lock().unwrap();
371        GpuStructLayout::from_symbol_table(&jit.compiler.symbols, name, params)
372    }
373
374    pub fn disassemble(&self, name: &str) -> Result<String> {
375        self.jit.lock().unwrap().compiler.symbols.disassemble(name)
376    }
377
378    #[cfg(feature = "ir-disassembly")]
379    pub fn disassemble_ir(&self, name: &str) -> Result<String> {
380        self.jit.lock().unwrap().disassemble_ir(name)
381    }
382}
383
384impl Default for Vm {
385    fn default() -> Self {
386        Self::new()
387    }
388}
389
390#[cfg(test)]
391mod tests {
392    use super::Vm;
393    use dynamic::{Dynamic, ToJson, Type};
394
395    extern "C" fn math_double(value: i64) -> i64 {
396        value * 2
397    }
398
399    #[test]
400    fn vm_can_add_native_after_jit_creation() -> anyhow::Result<()> {
401        let vm = Vm::new();
402        vm.add_native_module_ptr("math", "double", &[Type::I64], Type::I64, math_double as *const u8)?;
403        vm.import_code(
404            "vm_dynamic_native",
405            br#"
406            pub fn run(value: i64) {
407                math::double(value)
408            }
409            "#
410            .to_vec(),
411        )?;
412
413        let compiled = vm.get_fn("vm_dynamic_native::run", &[Type::I64])?;
414        assert_eq!(compiled.ret_ty(), &Type::I64);
415        let run: extern "C" fn(i64) -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
416        assert_eq!(run(21), 42);
417        Ok(())
418    }
419
420    #[test]
421    fn any_push_does_not_consume_reused_value() -> anyhow::Result<()> {
422        let vm = Vm::with_all()?;
423        vm.import_code(
424            "vm_any_push_reused_value",
425            br#"
426            pub fn run() {
427                let role_id = "acct_role_2";
428                let updated = [];
429                updated.push(role_id);
430                {
431                    ok: true,
432                    user_id: role_id,
433                    first: updated.get_idx(0)
434                }
435            }
436            "#
437            .to_vec(),
438        )?;
439
440        let compiled = vm.get_fn("vm_any_push_reused_value::run", &[])?;
441        assert_eq!(compiled.ret_ty(), &Type::Any);
442        let run: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
443        let result = unsafe { &*run() };
444        assert_eq!(result.get_dynamic("ok").and_then(|value| value.as_bool()), Some(true));
445        assert_eq!(result.get_dynamic("user_id").map(|value| value.as_str().to_string()), Some("acct_role_2".to_string()));
446        assert_eq!(result.get_dynamic("first").map(|value| value.as_str().to_string()), Some("acct_role_2".to_string()));
447        Ok(())
448    }
449
450    #[test]
451    fn compares_any_with_string_literal_as_string() -> anyhow::Result<()> {
452        let vm = Vm::with_all()?;
453        vm.import_code(
454            "vm_string_compare_any",
455            br#"
456            pub fn any_ne_empty(chat_path) {
457                chat_path != ""
458            }
459            "#
460            .to_vec(),
461        )?;
462
463        let compiled = vm.get_fn("vm_string_compare_any::any_ne_empty", &[Type::Any])?;
464        assert_eq!(compiled.ret_ty(), &Type::Bool);
465
466        let any_ne_empty: extern "C" fn(*const Dynamic) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
467        let empty = Dynamic::from("");
468        let non_empty = Dynamic::from("chat");
469
470        assert!(!any_ne_empty(&empty));
471        assert!(any_ne_empty(&non_empty));
472        Ok(())
473    }
474
475    #[test]
476    fn compares_bool_values_and_bool_literals() -> anyhow::Result<()> {
477        let vm = Vm::with_all()?;
478        vm.import_code(
479            "vm_bool_compare",
480            br#"
481            pub fn eq_true(value: bool) {
482                value == true
483            }
484
485            pub fn ne_false(value: bool) {
486                value != false
487            }
488
489            pub fn literal_left(value: bool) {
490                true == value
491            }
492
493            pub fn eq_pair(left: bool, right: bool) {
494                left == right
495            }
496
497            pub fn logic_pair(left: bool, right: bool) {
498                (left && right) || (left == true && right != false)
499            }
500            "#
501            .to_vec(),
502        )?;
503
504        let compiled = vm.get_fn("vm_bool_compare::eq_true", &[Type::Bool])?;
505        assert_eq!(compiled.ret_ty(), &Type::Bool);
506        let eq_true: extern "C" fn(bool) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
507        assert!(eq_true(true));
508        assert!(!eq_true(false));
509
510        let compiled = vm.get_fn("vm_bool_compare::ne_false", &[Type::Bool])?;
511        assert_eq!(compiled.ret_ty(), &Type::Bool);
512        let ne_false: extern "C" fn(bool) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
513        assert!(ne_false(true));
514        assert!(!ne_false(false));
515
516        let compiled = vm.get_fn("vm_bool_compare::literal_left", &[Type::Bool])?;
517        assert_eq!(compiled.ret_ty(), &Type::Bool);
518        let literal_left: extern "C" fn(bool) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
519        assert!(literal_left(true));
520        assert!(!literal_left(false));
521
522        let compiled = vm.get_fn("vm_bool_compare::eq_pair", &[Type::Bool, Type::Bool])?;
523        assert_eq!(compiled.ret_ty(), &Type::Bool);
524        let eq_pair: extern "C" fn(bool, bool) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
525        assert!(eq_pair(true, true));
526        assert!(eq_pair(false, false));
527        assert!(!eq_pair(true, false));
528        assert!(!eq_pair(false, true));
529
530        let compiled = vm.get_fn("vm_bool_compare::logic_pair", &[Type::Bool, Type::Bool])?;
531        assert_eq!(compiled.ret_ty(), &Type::Bool);
532        let logic_pair: extern "C" fn(bool, bool) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
533        assert!(logic_pair(true, true));
534        assert!(!logic_pair(true, false));
535        assert!(!logic_pair(false, true));
536        assert!(!logic_pair(false, false));
537        Ok(())
538    }
539
540    #[test]
541    fn parenthesized_expression_can_call_any_method() -> anyhow::Result<()> {
542        let vm = Vm::with_all()?;
543        vm.import_code(
544            "vm_parenthesized_method_call",
545            br#"
546            pub fn run(value) {
547                (value + 2).to_i64()
548            }
549            "#
550            .to_vec(),
551        )?;
552
553        let compiled = vm.get_fn("vm_parenthesized_method_call::run", &[Type::Any])?;
554        assert_eq!(compiled.ret_ty(), &Type::I64);
555        let run: extern "C" fn(*const Dynamic) -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
556        let value = Dynamic::from(40i64);
557
558        assert_eq!(run(&value), 42);
559        Ok(())
560    }
561
562    #[test]
563    fn any_keys_returns_map_keys_and_empty_list_for_other_values() -> anyhow::Result<()> {
564        let vm = Vm::with_all()?;
565        vm.import_code(
566            "vm_any_keys",
567            br#"
568            pub fn map_keys(value) {
569                let keys = value.keys();
570                keys.len() == 2 && keys.contains("alpha") && keys.contains("beta")
571            }
572
573            pub fn non_map_keys(value) {
574                value.keys().len() == 0
575            }
576            "#
577            .to_vec(),
578        )?;
579
580        let compiled = vm.get_fn("vm_any_keys::map_keys", &[Type::Any])?;
581        assert_eq!(compiled.ret_ty(), &Type::Bool);
582        let map_keys: extern "C" fn(*const Dynamic) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
583        let value = dynamic::map!("alpha"=> 1i64, "beta"=> 2i64);
584        assert!(map_keys(&value));
585
586        let compiled = vm.get_fn("vm_any_keys::non_map_keys", &[Type::Any])?;
587        assert_eq!(compiled.ret_ty(), &Type::Bool);
588        let non_map_keys: extern "C" fn(*const Dynamic) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
589        let value = Dynamic::from("alpha");
590        assert!(non_map_keys(&value));
591        Ok(())
592    }
593
594    #[test]
595    fn compares_concrete_value_with_string_literal_as_string() -> anyhow::Result<()> {
596        let vm = Vm::with_all()?;
597        vm.import_code(
598            "vm_string_compare_imm",
599            br#"
600            pub fn int_eq_str(value: i64) {
601                value == "42"
602            }
603
604            pub fn int_to_str(value: i64) {
605                value + ""
606            }
607            "#
608            .to_vec(),
609        )?;
610
611        let compiled = vm.get_fn("vm_string_compare_imm::int_eq_str", &[Type::I64])?;
612        assert_eq!(compiled.ret_ty(), &Type::Bool);
613
614        let int_eq_str: extern "C" fn(i64) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
615
616        let compiled = vm.get_fn("vm_string_compare_imm::int_to_str", &[Type::I64])?;
617        assert_eq!(compiled.ret_ty(), &Type::Any);
618        let int_to_str: extern "C" fn(i64) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
619        let text = int_to_str(42);
620        assert_eq!(unsafe { &*text }.as_str(), "42");
621
622        assert!(int_eq_str(42));
623        assert!(!int_eq_str(7));
624        Ok(())
625    }
626
627    #[test]
628    fn concatenates_string_with_integer_values() -> anyhow::Result<()> {
629        let vm = Vm::with_all()?;
630        vm.import_code(
631            "vm_string_concat_integer",
632            br#"
633            pub fn idx_key(idx: i64) {
634                "" + idx
635            }
636
637            pub fn level_text(level: i64) {
638                "" + level + " level"
639            }
640
641            pub fn gold_text(currency) {
642                "" + currency.gold
643            }
644            "#
645            .to_vec(),
646        )?;
647
648        let compiled = vm.get_fn("vm_string_concat_integer::idx_key", &[Type::I64])?;
649        let idx_key: extern "C" fn(i64) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
650        let result = unsafe { &*idx_key(7) };
651        assert_eq!(result.as_str(), "7");
652
653        let compiled = vm.get_fn("vm_string_concat_integer::level_text", &[Type::I64])?;
654        let level_text: extern "C" fn(i64) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
655        let result = unsafe { &*level_text(12) };
656        assert_eq!(result.as_str(), "12 level");
657
658        let compiled = vm.get_fn("vm_string_concat_integer::gold_text", &[Type::Any])?;
659        let gold_text: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
660        let currency = dynamic::map!("gold"=> 345i64);
661        let result = unsafe { &*gold_text(&currency) };
662        assert_eq!(result.as_str(), "345");
663        Ok(())
664    }
665
666    #[test]
667    fn coerces_string_concat_to_i64_without_unimplemented_log() -> anyhow::Result<()> {
668        let vm = Vm::with_all()?;
669        vm.import_code(
670            "vm_string_concat_to_i64",
671            br#"
672            pub fn run(idx: i64) {
673                ("" + idx) as i64
674            }
675            "#
676            .to_vec(),
677        )?;
678
679        let compiled = vm.get_fn("vm_string_concat_to_i64::run", &[Type::I64])?;
680        assert_eq!(compiled.ret_ty(), &Type::I64);
681        let run: extern "C" fn(i64) -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
682        assert_eq!(run(7), 0);
683        Ok(())
684    }
685
686    #[test]
687    fn unifies_explicit_return_and_tail_integer_widths() -> anyhow::Result<()> {
688        let vm = Vm::with_all()?;
689        vm.import_code(
690            "vm_return_integer_widths",
691            br#"
692            pub fn selected(flag, slot) {
693                if flag {
694                    return slot;
695                }
696                0
697            }
698            "#
699            .to_vec(),
700        )?;
701
702        let compiled = vm.get_fn("vm_return_integer_widths::selected", &[Type::Bool, Type::I64])?;
703        assert_eq!(compiled.ret_ty(), &Type::I64);
704        let selected: extern "C" fn(bool, i64) -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
705
706        assert_eq!(selected(true, 7), 7);
707        assert_eq!(selected(false, 7), 0);
708        Ok(())
709    }
710
711    #[test]
712    fn root_contains_string_concat_is_bool_condition() -> anyhow::Result<()> {
713        let vm = Vm::with_all()?;
714        vm.import_code(
715            "vm_root_contains_condition",
716            br#"
717            pub fn exists(user_id) {
718                if root::contains("redis/user/" + user_id) {
719                    return 1;
720                }
721                0
722            }
723            "#
724            .to_vec(),
725        )?;
726
727        assert_eq!(vm.infer("root::contains", &[Type::Any])?, Type::Bool);
728        let compiled = vm.get_fn("vm_root_contains_condition::exists", &[Type::Any])?;
729        assert_eq!(compiled.ret_ty(), &Type::I32);
730        Ok(())
731    }
732
733    #[test]
734    fn root_add_map_can_be_printed() -> anyhow::Result<()> {
735        let vm = Vm::with_all()?;
736        assert_eq!(vm.infer("root::add_map", &[Type::Any])?, Type::Bool);
737        vm.import_code(
738            "vm_root_add_map_print",
739            br#"
740            pub fn run() {
741                print(root::add_map("local/world_handlers/til_map_novicevillage"));
742            }
743            "#
744            .to_vec(),
745        )?;
746
747        let compiled = vm.get_fn("vm_root_add_map_print::run", &[])?;
748        assert!(compiled.ret_ty().is_void());
749        Ok(())
750    }
751
752    #[test]
753    fn unary_not_any_loop_var_is_bool_condition() -> anyhow::Result<()> {
754        let vm = Vm::with_all()?;
755        vm.import_code(
756            "vm_unary_not_any_loop_var",
757            br#"
758            pub fn count_missing(flags) {
759                let missing = 0;
760                for exists in flags {
761                    if !exists {
762                        missing = missing + 1;
763                    }
764                }
765                missing
766            }
767            "#
768            .to_vec(),
769        )?;
770
771        let compiled = vm.get_fn("vm_unary_not_any_loop_var::count_missing", &[Type::Any])?;
772        assert_eq!(compiled.ret_ty(), &Type::I32);
773        Ok(())
774    }
775
776    #[test]
777    fn semicolon_tail_call_makes_function_void() -> anyhow::Result<()> {
778        let vm = Vm::with_all()?;
779        vm.import_code(
780            "vm_semicolon_tail_void",
781            br#"
782            pub fn send_role_select(idx, account_id, selected_slot) {
783                root::send("local/ui/send_dialog", {
784                    idx: idx,
785                    account_id: account_id,
786                    selected_slot: selected_slot
787                });
788            }
789            "#
790            .to_vec(),
791        )?;
792
793        let compiled = vm.get_fn("vm_semicolon_tail_void::send_role_select", &[Type::Any, Type::Any, Type::Any])?;
794        assert_eq!(compiled.ret_ty(), &Type::Void);
795        Ok(())
796    }
797
798    #[test]
799    fn bare_return_conflicts_with_non_void_return() -> anyhow::Result<()> {
800        let vm = Vm::with_all()?;
801        vm.import_code(
802            "vm_bare_return_conflict",
803            br#"
804            pub fn run(flag) {
805                if flag {
806                    return;
807                }
808                1
809            }
810            "#
811            .to_vec(),
812        )?;
813
814        let err = match vm.get_fn("vm_bare_return_conflict::run", &[Type::Bool]) {
815            Ok(_) => panic!("expected mismatched return types to fail"),
816            Err(err) => err,
817        };
818        assert!(format!("{err:#}").contains("返回类型不一致"));
819        Ok(())
820    }
821
822    #[test]
823    fn root_get_accepts_string_concat_with_dynamic_field() -> anyhow::Result<()> {
824        let vm = Vm::with_all()?;
825        vm.import_code(
826            "vm_root_get_dynamic_concat",
827            br#"
828            pub fn get_action(req) {
829                root::get("local/game/panel_actions/" + req.idx)
830            }
831            "#
832            .to_vec(),
833        )?;
834
835        root::add("local/game/panel_actions/7", dynamic::map!("id"=> "action-7").into())?;
836        let compiled = vm.get_fn("vm_root_get_dynamic_concat::get_action", &[Type::Any])?;
837        assert_eq!(compiled.ret_ty(), &Type::Any);
838        let get_action: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
839        let req = dynamic::map!("idx"=> 7i64);
840        let result = unsafe { &*get_action(&req) };
841
842        assert_eq!(result.get_dynamic("id").map(|value| value.as_str().to_string()), Some("action-7".to_string()));
843        Ok(())
844    }
845
846    #[test]
847    fn root_add_fn_registers_handler_with_dynamic_field_path_concat() -> anyhow::Result<()> {
848        let vm = Vm::with_all()?;
849        vm.import_code(
850            "vm_registered_panel_action",
851            br#"
852            pub fn panel_action(req) {
853                root::get("local/game/panel_actions/" + req.idx)
854            }
855
856            pub fn register() {
857                root::add_fn("local/ui/panel_action", "vm_registered_panel_action::panel_action")
858            }
859            "#
860            .to_vec(),
861        )?;
862
863        let compiled = vm.get_fn("vm_registered_panel_action::register", &[])?;
864        assert_eq!(compiled.ret_ty(), &Type::Bool);
865        let register: extern "C" fn() -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
866        assert!(register());
867        Ok(())
868    }
869
870    #[test]
871    fn root_add_fn_accepts_string_concat_in_registered_handler() -> anyhow::Result<()> {
872        let vm = Vm::with_all()?;
873        vm.import_code(
874            "vm_registered_string_concat",
875            br#"
876            pub fn send_panel(idx: i64) {
877                let idx_key = "" + idx;
878                idx_key
879            }
880            "#
881            .to_vec(),
882        )?;
883
884        assert!(vm.get_fn_ptr("vm_registered_string_concat::send_panel", &[Type::Any]).is_ok());
885        Ok(())
886    }
887
888    #[test]
889    fn compiles_public_hotspots_with_string_paths_and_keys() -> anyhow::Result<()> {
890        let vm = Vm::with_all()?;
891        vm.import_code(
892            "vm_public_hotspots",
893            br#"
894            pub fn public_hotspot(action_map_path, panel_id, action_id, hotspot) {
895                {
896                    path: action_map_path,
897                    panel_id: panel_id,
898                    action_id: action_id,
899                    id: hotspot.id
900                }
901            }
902
903            pub fn public_hotspots(idx, panel_id, hotspots) {
904                let idx_key = "" + idx;
905                let action_map_path = "local/game/panel_actions/" + idx_key;
906
907                let existing_action_map = root::get(action_map_path);
908                if !existing_action_map.is_map() {
909                    root::add_map(action_map_path);
910                }
911
912                if hotspots.is_map() {
913                    let public_items = {};
914                    for action_id in hotspots.keys() {
915                        public_items[action_id] = public_hotspot(action_map_path, panel_id, action_id, hotspots[action_id]);
916                    }
917                    return public_items;
918                }
919
920                let public_items = [];
921                let i = 0;
922                while i < hotspots.len() {
923                    let hotspot = hotspots.get_idx(i);
924                    let item = public_hotspot(action_map_path, panel_id, hotspot.id, hotspot);
925                    public_items.push(item);
926                    i = i + 1;
927                }
928
929                public_items
930            }
931            "#
932            .to_vec(),
933        )?;
934
935        assert!(vm.get_fn("vm_public_hotspots::public_hotspots", &[Type::I64, Type::Any, Type::Any]).is_ok());
936        assert!(vm.get_fn("vm_public_hotspots::public_hotspots", &[Type::Any, Type::Any, Type::Any]).is_ok());
937        Ok(())
938    }
939
940    #[test]
941    fn send_panel_calls_public_hotspots_with_dynamic_request() -> anyhow::Result<()> {
942        let vm = Vm::with_all()?;
943        vm.import_code(
944            "vm_send_panel_public_hotspots",
945            br#"
946            pub fn ok(value) {
947                value
948            }
949
950            pub fn panel_from_node(req) {
951                {
952                    panel_id: req.panel_id,
953                    hotspots: req.hotspots
954                }
955            }
956
957            pub fn public_hotspot(action_map_path, panel_id, action_id, hotspot) {
958                {
959                    path: action_map_path,
960                    panel_id: panel_id,
961                    action_id: action_id,
962                    id: hotspot.id
963                }
964            }
965
966            pub fn public_hotspots(idx, panel_id, hotspots) {
967                let idx_key = "" + idx;
968                let action_map_path = "local/game/panel_actions/" + idx_key;
969
970                let existing_action_map = root::get(action_map_path);
971                if !existing_action_map.is_map() {
972                    root::add_map(action_map_path);
973                }
974
975                if hotspots.is_map() {
976                    let public_items = {};
977                    for action_id in hotspots.keys() {
978                        public_items[action_id] = public_hotspot(action_map_path, panel_id, action_id, hotspots[action_id]);
979                    }
980                    return public_items;
981                }
982
983                let public_items = [];
984                let i = 0;
985                while i < hotspots.len() {
986                    let hotspot = hotspots.get_idx(i);
987                    let item = public_hotspot(action_map_path, panel_id, hotspot.id, hotspot);
988                    public_items.push(item);
989                    i = i + 1;
990                }
991
992                public_items
993            }
994
995            pub fn send_panel(req) {
996                let panel = req.panel;
997                if !panel.is_map() {
998                    panel = panel_from_node(req);
999                }
1000                if !panel.is_map() {
1001                    return ok({
1002                        id: 4,
1003                        type: "panel_rejected",
1004                        reason: "invalid panel"
1005                    });
1006                }
1007                panel.id = 4;
1008                panel.idx = req.idx;
1009                if !panel.contains("type") {
1010                    panel.type = "panel";
1011                }
1012                if panel.contains("hotspots") {
1013                    panel.hotspots = public_hotspots(req.idx, panel.panel_id, panel.hotspots);
1014                }
1015                root::send_idx("local/ws", req.idx, panel);
1016                ok({
1017                    id: 4,
1018                    type: "panel",
1019                    panel_id: panel.panel_id
1020                })
1021            }
1022            "#
1023            .to_vec(),
1024        )?;
1025
1026        let compiled = vm.get_fn("vm_send_panel_public_hotspots::send_panel", &[Type::Any])?;
1027        assert_eq!(compiled.ret_ty(), &Type::Any);
1028        let send_panel: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1029        let req = dynamic::map!(
1030            "idx"=> 7i64,
1031            "panel"=> dynamic::map!(
1032                "panel_id"=> "main",
1033                "hotspots"=> dynamic::map!(
1034                    "open"=> dynamic::map!("id"=> "open")
1035                )
1036            )
1037        );
1038        let result = unsafe { &*send_panel(&req) };
1039
1040        assert_eq!(result.get_dynamic("type").map(|value| value.as_str().to_string()), Some("panel".to_string()));
1041        assert_eq!(result.get_dynamic("panel_id").map(|value| value.as_str().to_string()), Some("main".to_string()));
1042        Ok(())
1043    }
1044
1045    #[test]
1046    fn map_assignment_accepts_string_concat_key() -> anyhow::Result<()> {
1047        let vm = Vm::with_all()?;
1048        vm.import_code(
1049            "vm_string_concat_map_key",
1050            br##"
1051            pub fn write_action(action_map, panel_id, action_id, action) {
1052                action_map[panel_id + "#" + action_id] = action;
1053                action_map[panel_id + "#" + action_id]
1054            }
1055            "##
1056            .to_vec(),
1057        )?;
1058
1059        let compiled = vm.get_fn("vm_string_concat_map_key::write_action", &[Type::Any, Type::Any, Type::Any, Type::Any])?;
1060        let write_action: extern "C" fn(*const Dynamic, *const Dynamic, *const Dynamic, *const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1061        let action_map = dynamic::map!();
1062        let panel_id: Dynamic = "panel".into();
1063        let action_id: Dynamic = "open".into();
1064        let action = dynamic::map!("id"=> "open");
1065
1066        let result = unsafe { &*write_action(&action_map, &panel_id, &action_id, &action) };
1067
1068        assert_eq!(result.get_dynamic("id").map(|value| value.as_str().to_string()), Some("open".to_string()));
1069        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()));
1070        Ok(())
1071    }
1072
1073    #[test]
1074    fn map_get_key_accepts_string_concat_key_variable() -> anyhow::Result<()> {
1075        let vm = Vm::with_all()?;
1076        vm.import_code(
1077            "vm_get_key_string_concat_key",
1078            br##"
1079            pub fn read_action(action_map, panel_id, action_id) {
1080                let action_key = panel_id + "#" + action_id;
1081                action_map.get_key(action_key)
1082            }
1083            "##
1084            .to_vec(),
1085        )?;
1086
1087        let compiled = vm.get_fn("vm_get_key_string_concat_key::read_action", &[Type::Any, Type::Any, Type::Any])?;
1088        let read_action: extern "C" fn(*const Dynamic, *const Dynamic, *const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1089        let action_map = dynamic::map!("panel#open"=> dynamic::map!("id"=> "open"));
1090        let panel_id: Dynamic = "panel".into();
1091        let action_id: Dynamic = "open".into();
1092
1093        let result = unsafe { &*read_action(&action_map, &panel_id, &action_id) };
1094
1095        assert_eq!(result.get_dynamic("id").map(|value| value.as_str().to_string()), Some("open".to_string()));
1096        Ok(())
1097    }
1098
1099    #[test]
1100    fn map_get_key_accepts_helper_string_key() -> anyhow::Result<()> {
1101        let vm = Vm::with_all()?;
1102        vm.import_code(
1103            "vm_get_key_helper_string_key",
1104            br##"
1105            pub fn make_action_key(panel_id, action_id) {
1106                panel_id + "#" + action_id
1107            }
1108
1109            pub fn read_action(action_map, panel_id, action_id) {
1110                let action_key = make_action_key(panel_id, action_id);
1111                let action = action_map.get_key(action_key);
1112                action
1113            }
1114            "##
1115            .to_vec(),
1116        )?;
1117
1118        let compiled = vm.get_fn("vm_get_key_helper_string_key::read_action", &[Type::Any, Type::Any, Type::Any])?;
1119        let read_action: extern "C" fn(*const Dynamic, *const Dynamic, *const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1120        let action_map = dynamic::map!("panel#open"=> dynamic::map!("id"=> "open"));
1121        let panel_id: Dynamic = "panel".into();
1122        let action_id: Dynamic = "open".into();
1123
1124        let result = unsafe { &*read_action(&action_map, &panel_id, &action_id) };
1125
1126        assert_eq!(result.get_dynamic("id").map(|value| value.as_str().to_string()), Some("open".to_string()));
1127        Ok(())
1128    }
1129
1130    #[test]
1131    fn map_del_key_removes_string_key_and_returns_removed_value() -> anyhow::Result<()> {
1132        let vm = Vm::with_all()?;
1133        vm.import_code(
1134            "vm_del_key_string_key",
1135            br##"
1136            pub fn remove_action(action_map, panel_id, action_id) {
1137                let action_key = panel_id + "#" + action_id;
1138                let removed = action_map.del_key(action_key);
1139                [removed, action_map.get_key(action_key)]
1140            }
1141            "##
1142            .to_vec(),
1143        )?;
1144
1145        let compiled = vm.get_fn("vm_del_key_string_key::remove_action", &[Type::Any, Type::Any, Type::Any])?;
1146        let remove_action: extern "C" fn(*const Dynamic, *const Dynamic, *const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1147        let action_map = dynamic::map!("panel#open"=> dynamic::map!("id"=> "open"));
1148        let panel_id: Dynamic = "panel".into();
1149        let action_id: Dynamic = "open".into();
1150
1151        let result = unsafe { &*remove_action(&action_map, &panel_id, &action_id) };
1152
1153        assert_eq!(result.get_idx(0).and_then(|value| value.get_dynamic("id")).map(|value| value.as_str().to_string()), Some("open".to_string()));
1154        assert!(result.get_idx(1).is_some_and(|value| value.is_null()));
1155        assert!(action_map.get_dynamic("panel#open").is_none());
1156        Ok(())
1157    }
1158
1159    #[test]
1160    fn dynamic_field_value_participates_in_or_expression() -> anyhow::Result<()> {
1161        let vm = Vm::with_all()?;
1162        vm.import_code(
1163            "vm_dynamic_field_or",
1164            r#"
1165            pub fn next_or_start() {
1166                let choice = {
1167                    label: "颜色",
1168                    next: "color"
1169                };
1170                choice.next || "start"
1171            }
1172
1173            pub fn direct_next() {
1174                let choice = {
1175                    label: "颜色",
1176                    next: "color"
1177                };
1178                choice.next
1179            }
1180
1181            pub fn bracket_next() {
1182                let choice = {
1183                    label: "颜色",
1184                    next: "color"
1185                };
1186                choice["next"]
1187            }
1188
1189            pub fn assigned_preview() {
1190                let choice = {
1191                    next: "tax_free"
1192                };
1193                choice.preview = choice.next || "start";
1194                choice
1195            }
1196            "#
1197            .as_bytes()
1198            .to_vec(),
1199        )?;
1200
1201        let compiled = vm.get_fn("vm_dynamic_field_or::direct_next", &[])?;
1202        assert_eq!(compiled.ret_ty(), &Type::Any);
1203        let direct_next: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1204        assert_eq!(unsafe { &*direct_next() }.as_str(), "color");
1205
1206        let compiled = vm.get_fn("vm_dynamic_field_or::bracket_next", &[])?;
1207        assert_eq!(compiled.ret_ty(), &Type::Any);
1208        let bracket_next: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1209        assert_eq!(unsafe { &*bracket_next() }.as_str(), "color");
1210
1211        let compiled = vm.get_fn("vm_dynamic_field_or::next_or_start", &[])?;
1212        assert_eq!(compiled.ret_ty(), &Type::Any);
1213        let next_or_start: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1214        assert_eq!(unsafe { &*next_or_start() }.as_str(), "color");
1215
1216        let compiled = vm.get_fn("vm_dynamic_field_or::assigned_preview", &[])?;
1217        assert_eq!(compiled.ret_ty(), &Type::Any);
1218        let assigned_preview: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1219        let choice = unsafe { &*assigned_preview() };
1220        assert_eq!(choice.get_dynamic("preview").unwrap().as_str(), "tax_free");
1221        Ok(())
1222    }
1223
1224    #[test]
1225    fn empty_object_literal_in_if_branch_stays_dynamic() -> anyhow::Result<()> {
1226        let vm = Vm::with_all()?;
1227        vm.import_code(
1228            "vm_if_empty_object_branch",
1229            r#"
1230            pub fn first_note(steps) {
1231                let first = if steps.len() > 0 { steps[0] } else { {} };
1232                let first_note = first.note || "fallback";
1233                first_note
1234            }
1235
1236            pub fn first_ja(steps) {
1237                let first = if steps.len() > 0 { steps[0] } else { {} };
1238                first.ja || "すみません"
1239            }
1240
1241            pub fn assign_first_note(steps) {
1242                let first = {};
1243                first = if steps.len() > 0 { steps[0] } else { {} };
1244                first.note || "fallback"
1245            }
1246            "#
1247            .as_bytes()
1248            .to_vec(),
1249        )?;
1250
1251        let compiled = vm.get_fn("vm_if_empty_object_branch::first_note", &[Type::Any])?;
1252        assert_eq!(compiled.ret_ty(), &Type::Any);
1253        let first_note: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1254
1255        let empty_steps = Dynamic::list(Vec::new());
1256        assert_eq!(unsafe { &*first_note(&empty_steps) }.as_str(), "fallback");
1257
1258        let mut step = std::collections::BTreeMap::new();
1259        step.insert("note".into(), "hello".into());
1260        let steps = Dynamic::list(vec![Dynamic::map(step)]);
1261        assert_eq!(unsafe { &*first_note(&steps) }.as_str(), "hello");
1262
1263        let compiled = vm.get_fn("vm_if_empty_object_branch::first_ja", &[Type::Any])?;
1264        assert_eq!(compiled.ret_ty(), &Type::Any);
1265        let first_ja: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1266        assert_eq!(unsafe { &*first_ja(&empty_steps) }.as_str(), "すみません");
1267
1268        let compiled = vm.get_fn("vm_if_empty_object_branch::assign_first_note", &[Type::Any])?;
1269        assert_eq!(compiled.ret_ty(), &Type::Any);
1270        let assign_first_note: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1271        assert_eq!(unsafe { &*assign_first_note(&empty_steps) }.as_str(), "fallback");
1272        assert_eq!(unsafe { &*assign_first_note(&steps) }.as_str(), "hello");
1273        Ok(())
1274    }
1275
1276    #[test]
1277    fn list_literal_can_be_function_tail_expression() -> anyhow::Result<()> {
1278        let vm = Vm::with_all()?;
1279        vm.import_code(
1280            "vm_tail_list_literal",
1281            r#"
1282            pub fn numbers() {
1283                [1, 2, 3]
1284            }
1285
1286            pub fn maps() {
1287                [
1288                    {note: "first"},
1289                    {note: "second"}
1290                ]
1291            }
1292
1293            pub fn object_with_maps() {
1294                {
1295                    steps: [
1296                        {note: "first"},
1297                        {note: "second"}
1298                    ]
1299                }
1300            }
1301
1302            pub fn return_maps() {
1303                return [
1304                    {note: "first"},
1305                    {note: "second"}
1306                ];
1307            }
1308
1309            pub fn return_maps_without_semicolon() {
1310                return [
1311                    {note: "first"},
1312                    {note: "second"}
1313                ]
1314            }
1315
1316            pub fn tail_bare_variable() {
1317                let value = [
1318                    {note: "first"},
1319                    {note: "second"}
1320                ];
1321                value
1322            }
1323
1324            pub fn return_bare_variable_without_semicolon() {
1325                let value = [
1326                    {note: "first"},
1327                    {note: "second"}
1328                ];
1329                return value
1330            }
1331
1332            pub fn tail_object_variable() {
1333                let result = {
1334                    steps: [
1335                        {note: "first"},
1336                        {note: "second"}
1337                    ]
1338                };
1339                result
1340            }
1341            "#
1342            .as_bytes()
1343            .to_vec(),
1344        )?;
1345
1346        let compiled = vm.get_fn("vm_tail_list_literal::numbers", &[])?;
1347        assert_eq!(compiled.ret_ty(), &Type::Any);
1348        let numbers: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1349        let result = unsafe { &*numbers() };
1350        assert_eq!(result.len(), 3);
1351        assert_eq!(result.get_idx(1).and_then(|value| value.as_int()), Some(2));
1352
1353        let compiled = vm.get_fn("vm_tail_list_literal::maps", &[])?;
1354        assert_eq!(compiled.ret_ty(), &Type::Any);
1355        let maps: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1356        let result = unsafe { &*maps() };
1357        assert_eq!(result.len(), 2);
1358        assert_eq!(result.get_idx(1).and_then(|value| value.get_dynamic("note")).map(|value| value.as_str().to_string()), Some("second".to_string()));
1359
1360        let compiled = vm.get_fn("vm_tail_list_literal::object_with_maps", &[])?;
1361        assert_eq!(compiled.ret_ty(), &Type::Any);
1362        let object_with_maps: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1363        let result = unsafe { &*object_with_maps() };
1364        let steps = result.get_dynamic("steps").expect("steps");
1365        assert_eq!(steps.len(), 2);
1366        assert_eq!(steps.get_idx(0).and_then(|value| value.get_dynamic("note")).map(|value| value.as_str().to_string()), Some("first".to_string()));
1367
1368        let compiled = vm.get_fn("vm_tail_list_literal::return_maps", &[])?;
1369        assert_eq!(compiled.ret_ty(), &Type::Any);
1370        let return_maps: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1371        let result = unsafe { &*return_maps() };
1372        assert_eq!(result.len(), 2);
1373        assert_eq!(result.get_idx(1).and_then(|value| value.get_dynamic("note")).map(|value| value.as_str().to_string()), Some("second".to_string()));
1374
1375        let compiled = vm.get_fn("vm_tail_list_literal::return_maps_without_semicolon", &[])?;
1376        assert_eq!(compiled.ret_ty(), &Type::Any);
1377        let return_maps_without_semicolon: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1378        let result = unsafe { &*return_maps_without_semicolon() };
1379        assert_eq!(result.len(), 2);
1380        assert_eq!(result.get_idx(0).and_then(|value| value.get_dynamic("note")).map(|value| value.as_str().to_string()), Some("first".to_string()));
1381
1382        let compiled = vm.get_fn("vm_tail_list_literal::tail_bare_variable", &[])?;
1383        assert_eq!(compiled.ret_ty(), &Type::Any);
1384        let tail_bare_variable: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1385        let result = unsafe { &*tail_bare_variable() };
1386        assert_eq!(result.len(), 2);
1387        assert_eq!(result.get_idx(1).and_then(|value| value.get_dynamic("note")).map(|value| value.as_str().to_string()), Some("second".to_string()));
1388
1389        let compiled = vm.get_fn("vm_tail_list_literal::return_bare_variable_without_semicolon", &[])?;
1390        assert_eq!(compiled.ret_ty(), &Type::Any);
1391        let return_bare_variable_without_semicolon: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1392        let result = unsafe { &*return_bare_variable_without_semicolon() };
1393        assert_eq!(result.len(), 2);
1394        assert_eq!(result.get_idx(0).and_then(|value| value.get_dynamic("note")).map(|value| value.as_str().to_string()), Some("first".to_string()));
1395
1396        let compiled = vm.get_fn("vm_tail_list_literal::tail_object_variable", &[])?;
1397        assert_eq!(compiled.ret_ty(), &Type::Any);
1398        let tail_object_variable: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1399        let result = unsafe { &*tail_object_variable() };
1400        let steps = result.get_dynamic("steps").expect("steps");
1401        assert_eq!(steps.len(), 2);
1402        assert_eq!(steps.get_idx(1).and_then(|value| value.get_dynamic("note")).map(|value| value.as_str().to_string()), Some("second".to_string()));
1403        Ok(())
1404    }
1405
1406    #[test]
1407    fn list_return_value_supports_get_idx_method_call() -> anyhow::Result<()> {
1408        let vm = Vm::with_all()?;
1409        vm.import_code(
1410            "vm_returned_list_get_idx",
1411            r#"
1412            pub fn ids() {
1413                [
1414                    "base",
1415                    "2",
1416                    "3"
1417                ]
1418            }
1419
1420            pub fn combinations() {
1421                let result = [];
1422                let values = ids();
1423                let idx = 0;
1424                while idx < values.len() {
1425                    result.push(values.get_idx(idx));
1426                    idx = idx + 1;
1427                }
1428                result
1429            }
1430            "#
1431            .as_bytes()
1432            .to_vec(),
1433        )?;
1434
1435        let compiled = vm.get_fn("vm_returned_list_get_idx::combinations", &[])?;
1436        assert_eq!(compiled.ret_ty(), &Type::Any);
1437        let combinations: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1438        let result = unsafe { &*combinations() };
1439
1440        assert_eq!(result.len(), 3);
1441        assert_eq!(result.get_idx(0).map(|value| value.as_str().to_string()), Some("base".to_string()));
1442        assert_eq!(result.get_idx(2).map(|value| value.as_str().to_string()), Some("3".to_string()));
1443        Ok(())
1444    }
1445
1446    #[test]
1447    fn repeated_deep_step_literals_import_successfully() -> anyhow::Result<()> {
1448        fn extra_page_literal(depth: usize) -> String {
1449            let mut value = "{leaf: \"done\"}".to_string();
1450            for idx in 0..depth {
1451                value = format!("{{kind: \"page\", idx: {idx}, children: [{value}], meta: {{title: \"extra\", visible: true}}}}");
1452            }
1453            value
1454        }
1455
1456        let extra = extra_page_literal(48);
1457        let code = format!(
1458            r#"
1459            pub fn script() {{
1460                return [
1461                    {{ja: "一つ目", note: "first", extra: {extra}}},
1462                    {{ja: "二つ目", note: "second", extra: {extra}}},
1463                    {{ja: "三つ目", note: "third", extra: {extra}}}
1464                ]
1465            }}
1466            "#
1467        );
1468
1469        let vm = Vm::with_all()?;
1470        vm.import_code("vm_repeated_deep_step_literals", code.into_bytes())?;
1471        let compiled = vm.get_fn("vm_repeated_deep_step_literals::script", &[])?;
1472        assert_eq!(compiled.ret_ty(), &Type::Any);
1473        let script: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1474        let result = unsafe { &*script() };
1475        assert_eq!(result.len(), 3);
1476        assert_eq!(result.get_idx(2).and_then(|value| value.get_dynamic("note")).map(|value| value.as_str().to_string()), Some("third".to_string()));
1477        Ok(())
1478    }
1479
1480    #[test]
1481    fn native_import_uses_owning_vm() -> anyhow::Result<()> {
1482        let module_path = std::env::temp_dir().join(format!("zust_vm_import_owner_{}.zs", std::process::id()));
1483        std::fs::write(&module_path, "pub fn value() { 41 }")?;
1484        let module_path = module_path.to_string_lossy().replace('\\', "\\\\").replace('"', "\\\"");
1485
1486        let vm1 = Vm::with_all()?;
1487        vm1.import_code(
1488            "vm_import_owner",
1489            format!(
1490                r#"
1491                pub fn run() {{
1492                    import("vm_imported_owner", "{module_path}");
1493                }}
1494                "#
1495            )
1496            .into_bytes(),
1497        )?;
1498        let compiled = vm1.get_fn("vm_import_owner::run", &[])?;
1499
1500        let vm2 = Vm::with_all()?;
1501        vm2.import_code("vm_import_other", b"pub fn run() { 0 }".to_vec())?;
1502        let _ = vm2.get_fn("vm_import_other::run", &[])?;
1503
1504        let run: extern "C" fn() = unsafe { std::mem::transmute(compiled.ptr()) };
1505        run();
1506
1507        assert!(vm1.get_fn("vm_imported_owner::value", &[]).is_ok());
1508        assert!(vm2.get_fn("vm_imported_owner::value", &[]).is_err());
1509        Ok(())
1510    }
1511
1512    #[test]
1513    fn object_last_field_call_does_not_need_trailing_comma() -> anyhow::Result<()> {
1514        let vm = Vm::with_all()?;
1515        vm.import_code(
1516            "vm_object_last_call_field",
1517            r#"
1518            pub fn extra_page() {
1519                {
1520                    title: "extra",
1521                    pages: [
1522                        {note: "nested"}
1523                    ]
1524                }
1525            }
1526
1527            pub fn data() {
1528                return [
1529                    {
1530                        note: "first",
1531                        choices: ["a", "b"],
1532                        extras: extra_page()
1533                    },
1534                    {
1535                        note: "second",
1536                        choices: ["c"],
1537                        extras: extra_page()
1538                    }
1539                ]
1540            }
1541            "#
1542            .as_bytes()
1543            .to_vec(),
1544        )?;
1545
1546        let compiled = vm.get_fn("vm_object_last_call_field::data", &[])?;
1547        assert_eq!(compiled.ret_ty(), &Type::Any);
1548        let data: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1549        let result = unsafe { &*data() };
1550        assert_eq!(result.len(), 2);
1551        let first = result.get_idx(0).expect("first step");
1552        assert_eq!(first.get_dynamic("extras").and_then(|extras| extras.get_dynamic("title")).map(|title| title.as_str().to_string()), Some("extra".to_string()));
1553        Ok(())
1554    }
1555
1556    #[test]
1557    fn gpu_struct_layout_packs_and_unpacks_dynamic_maps() -> anyhow::Result<()> {
1558        let vm = Vm::with_all()?;
1559        vm.import_code(
1560            "vm_gpu_layout",
1561            br#"
1562            pub struct Params {
1563                a: u32,
1564                b: u32,
1565                c: u32,
1566            }
1567            "#
1568            .to_vec(),
1569        )?;
1570
1571        let layout = vm.gpu_struct_layout("vm_gpu_layout::Params", &[])?;
1572        assert_eq!(layout.size, 16);
1573        assert_eq!(layout.fields.iter().map(|field| (field.name.as_str(), field.offset)).collect::<Vec<_>>(), vec![("a", 0), ("b", 4), ("c", 8)]);
1574
1575        let value = dynamic::map!("a"=> 1u32, "b"=> 2u32, "c"=> 3u32);
1576        let bytes = layout.pack_map(&value)?;
1577        assert_eq!(bytes.len(), 16);
1578        assert_eq!(&bytes[0..4], &1u32.to_ne_bytes());
1579        assert_eq!(&bytes[4..8], &2u32.to_ne_bytes());
1580        assert_eq!(&bytes[8..12], &3u32.to_ne_bytes());
1581
1582        let read = layout.unpack_map(&bytes)?;
1583        assert_eq!(read.get_dynamic("a").and_then(|value| value.as_uint()), Some(1));
1584        assert_eq!(read.get_dynamic("b").and_then(|value| value.as_uint()), Some(2));
1585        assert_eq!(read.get_dynamic("c").and_then(|value| value.as_uint()), Some(3));
1586        Ok(())
1587    }
1588
1589    #[test]
1590    fn root_native_calls_do_not_take_ownership_of_dynamic_args() -> anyhow::Result<()> {
1591        let vm = Vm::with_all()?;
1592        vm.import_code(
1593            "vm_root_clone_bridge",
1594            br#"
1595            pub fn add_then_reuse(arg) {
1596                let user = {
1597                    address: "test-wallet",
1598                    points: 20
1599                };
1600                root::add("local/root-clone-bridge-user", user);
1601                user.points = user.points - 7;
1602                root::add("local/root-clone-bridge-user", user);
1603                {
1604                    user: user,
1605                    points: user.points
1606                }
1607            }
1608
1609            pub fn clone_then_mutate(arg) {
1610                let user = {
1611                    profile: {
1612                        points: 20
1613                    }
1614                };
1615                let copied = user.clone();
1616                copied.profile.points = 13;
1617                user
1618            }
1619            "#
1620            .to_vec(),
1621        )?;
1622
1623        let compiled = vm.get_fn("vm_root_clone_bridge::add_then_reuse", &[Type::Any])?;
1624        assert_eq!(compiled.ret_ty(), &Type::Any);
1625        let add_then_reuse: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1626        let arg = Dynamic::Null;
1627        let result = add_then_reuse(&arg);
1628        let result = unsafe { &*result };
1629
1630        assert_eq!(result.get_dynamic("points").and_then(|value| value.as_int()), Some(13));
1631        let mut json = String::new();
1632        result.to_json(&mut json);
1633        assert!(json.contains("\"points\": 13"));
1634
1635        let clone_then_mutate = vm.get_fn("vm_root_clone_bridge::clone_then_mutate", &[Type::Any])?;
1636        let clone_then_mutate: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(clone_then_mutate.ptr()) };
1637        let result = clone_then_mutate(&arg);
1638        let result = unsafe { &*result };
1639        assert_eq!(result.get_dynamic("profile").unwrap().get_dynamic("points").and_then(|value| value.as_int()), Some(20));
1640        Ok(())
1641    }
1642
1643    struct CounterForTypedReceiver {
1644        value: i64,
1645    }
1646
1647    extern "C" fn counter_for_typed_receiver_get(value: *const Dynamic) -> i64 {
1648        unsafe { &*value }.as_custom::<CounterForTypedReceiver>().map(|counter| counter.value).unwrap_or(-1)
1649    }
1650
1651    struct NavMapForFunctionArg;
1652
1653    extern "C" fn nav_map_for_function_arg_new() -> *const Dynamic {
1654        Box::into_raw(Box::new(Dynamic::custom(NavMapForFunctionArg)))
1655    }
1656
1657    #[test]
1658    fn typed_receiver_method_call_dispatches_with_type_hint() -> anyhow::Result<()> {
1659        let vm = Vm::with_all()?;
1660        vm.add_empty_type("Counter")?;
1661        let counter_ty = vm.get_symbol("Counter", Vec::new())?;
1662        vm.add_native_method_ptr("Counter", "get", &[counter_ty], Type::I64, counter_for_typed_receiver_get as *const u8)?;
1663        vm.import_code(
1664            "vm_typed_receiver_method",
1665            br#"
1666            pub fn run(value) {
1667                value::<Counter>::get()
1668            }
1669            "#
1670            .to_vec(),
1671        )?;
1672
1673        let compiled = vm.get_fn("vm_typed_receiver_method::run", &[Type::Any])?;
1674        assert_eq!(compiled.ret_ty(), &Type::I64);
1675        let run: extern "C" fn(*const Dynamic) -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
1676        let value = Dynamic::custom(CounterForTypedReceiver { value: 42 });
1677
1678        assert_eq!(run(&value), 42);
1679        Ok(())
1680    }
1681
1682    #[test]
1683    fn native_custom_object_can_be_passed_to_zs_function() -> anyhow::Result<()> {
1684        let vm = Vm::with_all()?;
1685        vm.add_empty_type("NavMap")?;
1686        vm.add_native_method_ptr("NavMap", "new", &[], Type::Any, nav_map_for_function_arg_new as *const u8)?;
1687        vm.import_code(
1688            "vm_native_custom_arg",
1689            br#"
1690            pub fn add_nav_spawns(world, navmap) {
1691                navmap
1692            }
1693
1694            pub fn run(world) {
1695                let navmap = NavMap::new();
1696                let with_spawns = add_nav_spawns(world, navmap);
1697                with_spawns
1698            }
1699            "#
1700            .to_vec(),
1701        )?;
1702
1703        let compiled = vm.get_fn("vm_native_custom_arg::run", &[Type::Any])?;
1704        assert_eq!(compiled.ret_ty(), &Type::Any);
1705        let run: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1706        let world = Dynamic::Null;
1707        let result = run(&world);
1708        let result = unsafe { &*result };
1709
1710        assert!(result.as_custom::<NavMapForFunctionArg>().is_some());
1711        Ok(())
1712    }
1713
1714    #[test]
1715    fn native_custom_object_typed_local_can_be_passed_to_zs_function() -> anyhow::Result<()> {
1716        let vm = Vm::with_all()?;
1717        vm.add_empty_type("NavMap")?;
1718        let _nav_map_ty = vm.get_symbol("NavMap", Vec::new())?;
1719        vm.add_native_method_ptr("NavMap", "new", &[], Type::Any, nav_map_for_function_arg_new as *const u8)?;
1720        vm.import_code(
1721            "vm_native_custom_typed_arg",
1722            br#"
1723            pub fn add_nav_spawns(world, navmap) {
1724                navmap
1725            }
1726
1727            pub fn run(world) {
1728                let navmap: NavMap = NavMap::new();
1729                let with_spawns = add_nav_spawns(world, navmap);
1730                with_spawns
1731            }
1732            "#
1733            .to_vec(),
1734        )?;
1735
1736        let compiled = vm.get_fn("vm_native_custom_typed_arg::run", &[Type::Any])?;
1737        let run: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1738        let world = Dynamic::Null;
1739        let result = run(&world);
1740        let result = unsafe { &*result };
1741
1742        assert!(result.as_custom::<NavMapForFunctionArg>().is_some());
1743        Ok(())
1744    }
1745}