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 http_module;
19mod llm_module;
20mod root_module;
21
22use std::cell::RefCell;
23use std::sync::{Mutex, OnceLock, Weak};
24static PTR_TYPE: OnceLock<types::Type> = OnceLock::new();
25pub fn ptr_type() -> types::Type {
26    PTR_TYPE.get().cloned().unwrap()
27}
28
29pub fn get_type(ty: &Type) -> Result<types::Type> {
30    if ty.is_f64() {
31        Ok(types::F64)
32    } else if ty.is_f32() {
33        Ok(types::F32)
34    } else if ty.is_int() | ty.is_uint() {
35        match ty.width() {
36            1 => Ok(types::I8),
37            2 => Ok(types::I16),
38            4 => Ok(types::I32),
39            8 => Ok(types::I64),
40            _ => Err(anyhow!("非法类型 {:?}", ty)),
41        }
42    } else if let Type::Bool = ty {
43        Ok(types::I8)
44    } else {
45        Ok(ptr_type())
46    }
47}
48
49use compiler::Symbol;
50use cranelift::prelude::*;
51
52pub fn init_jit(mut jit: JITRunTime) -> Result<JITRunTime> {
53    jit.add_all()?;
54    Ok(jit)
55}
56
57use std::sync::Arc;
58unsafe impl Send for JITRunTime {}
59unsafe impl Sync for JITRunTime {}
60
61thread_local! {
62    static CURRENT_VM: RefCell<Option<Weak<Mutex<JITRunTime>>>> = const { RefCell::new(None) };
63}
64
65fn set_current_vm(vm: &Vm) {
66    CURRENT_VM.with(|current| {
67        *current.borrow_mut() = Some(Arc::downgrade(&vm.jit));
68    });
69}
70
71fn with_current_vm<T>(f: impl FnOnce(&Vm) -> Result<T>) -> Result<T> {
72    CURRENT_VM.with(|current| {
73        let jit = current.borrow().as_ref().and_then(Weak::upgrade).ok_or_else(|| anyhow!("当前线程没有 VM"))?;
74        let vm = Vm { jit };
75        f(&vm)
76    })
77}
78
79pub(crate) fn import_current(name: &str, path: &str) -> Result<()> {
80    with_current_vm(|vm| vm.import(name, path))
81}
82
83pub(crate) fn get_current_fn_ptr(name: &str, arg_tys: &[Type]) -> Result<(*const u8, Type)> {
84    with_current_vm(|vm| vm.get_fn_ptr(name, arg_tys))
85}
86
87fn add_method_field(jit: &mut JITRunTime, def: &str, method: &str, id: u32) -> Result<()> {
88    let def_id = jit.get_id(def)?;
89    if let Some((_, define)) = jit.compiler.symbols.get_symbol_mut(def_id) {
90        if let Symbol::Struct(Type::Struct { params, fields }, _) = define {
91            fields.push((method.into(), Type::Symbol { id, params: params.clone() }));
92        }
93    }
94    Ok(())
95}
96
97fn add_native_module_fns(jit: &mut JITRunTime, module: &str, fns: &[(&str, &[Type], Type, *const u8)]) -> Result<()> {
98    jit.add_module(module);
99    for (name, arg_tys, ret_ty, fn_ptr) in fns {
100        let full_name = format!("{}::{}", module, name);
101        jit.add_native_ptr(&full_name, name, arg_tys, ret_ty.clone(), *fn_ptr)?;
102    }
103    jit.pop_module();
104    Ok(())
105}
106
107impl JITRunTime {
108    pub fn add_module(&mut self, name: &str) {
109        self.compiler.symbols.add_module(name.into());
110    }
111
112    pub fn pop_module(&mut self) {
113        self.compiler.symbols.pop_module();
114    }
115
116    pub fn add_type(&mut self, name: &str, ty: Type, is_pub: bool) -> u32 {
117        self.compiler.add_symbol(name, Symbol::Struct(ty, is_pub))
118    }
119
120    pub fn add_empty_type(&mut self, name: &str) -> Result<u32> {
121        match self.get_id(name) {
122            Ok(id) => Ok(id),
123            Err(_) => Ok(self.add_type(name, Type::Struct { params: Vec::new(), fields: Vec::new() }, true)),
124        }
125    }
126
127    pub fn add_native_module_ptr(&mut self, module: &str, name: &str, arg_tys: &[Type], ret_ty: Type, fn_ptr: *const u8) -> Result<u32> {
128        self.add_module(module);
129        let full_name = format!("{}::{}", module, name);
130        let result = self.add_native_ptr(&full_name, name, arg_tys, ret_ty, fn_ptr);
131        self.pop_module();
132        result
133    }
134
135    pub fn add_native_method_ptr(&mut self, def: &str, method: &str, arg_tys: &[Type], ret_ty: Type, fn_ptr: *const u8) -> Result<u32> {
136        self.add_empty_type(def)?;
137        let full_name = format!("{}::{}", def, method);
138        let id = self.add_native_ptr(&full_name, &full_name, arg_tys, ret_ty, fn_ptr)?;
139        add_method_field(self, def, method, id)?;
140        Ok(id)
141    }
142
143    pub fn add_std(&mut self) -> Result<()> {
144        self.add_module("std");
145        for (name, arg_tys, ret_ty, fn_ptr) in STD {
146            self.add_native_ptr(name, name, arg_tys, ret_ty, fn_ptr)?;
147        }
148        Ok(())
149    }
150
151    pub fn add_any(&mut self) -> Result<()> {
152        for (name, arg_tys, ret_ty, fn_ptr) in ANY {
153            let (_, method) = name.split_once("::").ok_or_else(|| anyhow!("非法 Any 方法名 {}", name))?;
154            self.add_native_method_ptr("Any", method, arg_tys, ret_ty, fn_ptr)?;
155        }
156        Ok(())
157    }
158
159    pub fn add_vec(&mut self) -> Result<()> {
160        self.add_empty_type("Vec")?;
161        let vec_def = Type::Symbol { id: self.get_id("Vec")?, params: Vec::new() };
162        self.add_inline("Vec::swap", vec![vec_def.clone(), Type::I64, Type::I64], Type::Void, |ctx: Option<&mut BuildContext>, args: Vec<Value>| {
163            if let Some(ctx) = ctx {
164                let width = ctx.builder.ins().iconst(types::I64, 4);
165                let offset_val = ctx.builder.ins().imul(args[1], width); // i * 4 i32大小四字节
166                let final_addr = ctx.builder.ins().iadd(args[0], offset_val); // base + (i*4)
167                let dest = ctx.builder.ins().imul(args[2], width);
168                let dest_addr = ctx.builder.ins().iadd(args[0], dest); // base + (i*4)
169                let dest_val = ctx.builder.ins().load(types::I32, MemFlags::trusted(), dest_addr, 0);
170                let v = ctx.builder.ins().load(types::I32, MemFlags::trusted(), final_addr, 0);
171                ctx.builder.ins().store(MemFlags::trusted(), v, dest_addr, 0);
172                ctx.builder.ins().store(MemFlags::trusted(), dest_val, final_addr, 0);
173            }
174            Err(anyhow!("无返回值"))
175        })?;
176
177        self.add_inline("Vec::get_idx", vec![vec_def.clone(), Type::I64], Type::I32, |ctx: Option<&mut BuildContext>, args: Vec<Value>| {
178            if let Some(ctx) = ctx {
179                let width = ctx.builder.ins().iconst(types::I64, 4);
180                let offset_val = ctx.builder.ins().imul(args[1], width); // i * 4 i32大小四字节
181                let final_addr = ctx.builder.ins().iadd(args[0], offset_val);
182                Ok((Some(ctx.builder.ins().load(types::I32, MemFlags::trusted(), final_addr, 0)), Type::I32))
183            } else {
184                Ok((None, Type::I32))
185            }
186        })?;
187        Ok(())
188    }
189
190    pub fn add_llm(&mut self) -> Result<()> {
191        add_native_module_fns(self, "llm", &llm_module::LLM_NATIVE)
192    }
193
194    pub fn add_root(&mut self) -> Result<()> {
195        add_native_module_fns(self, "root", &root_module::ROOT_NATIVE)
196    }
197
198    pub fn add_http(&mut self) -> Result<()> {
199        add_native_module_fns(self, "http", &http_module::HTTP_NATIVE)
200    }
201
202    pub fn add_db(&mut self) -> Result<()> {
203        add_native_module_fns(self, "db", &db_module::DB_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        Ok(())
215    }
216}
217
218#[derive(Clone)]
219pub struct Vm {
220    jit: Arc<Mutex<JITRunTime>>,
221}
222
223#[derive(Clone)]
224pub struct CompiledFn {
225    ptr: usize,
226    ret: Type,
227    owner: Vm,
228}
229
230impl CompiledFn {
231    pub fn ptr(&self) -> *const u8 {
232        set_current_vm(&self.owner);
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        Self { jit: Arc::new(Mutex::new(JITRunTime::new(|_| {}))) }
248    }
249
250    pub fn with_all() -> Result<Self> {
251        let vm = Self::new();
252        vm.add_all()?;
253        Ok(vm)
254    }
255
256    pub fn add_module(&self, name: &str) {
257        self.jit.lock().unwrap().add_module(name)
258    }
259
260    pub fn pop_module(&self) {
261        self.jit.lock().unwrap().pop_module()
262    }
263
264    pub fn add_type(&self, name: &str, ty: Type, is_pub: bool) -> u32 {
265        self.jit.lock().unwrap().add_type(name, ty, is_pub)
266    }
267
268    pub fn add_empty_type(&self, name: &str) -> Result<u32> {
269        self.jit.lock().unwrap().add_empty_type(name)
270    }
271
272    pub fn add_std(&self) -> Result<()> {
273        self.jit.lock().unwrap().add_std()
274    }
275
276    pub fn add_any(&self) -> Result<()> {
277        self.jit.lock().unwrap().add_any()
278    }
279
280    pub fn add_vec(&self) -> Result<()> {
281        self.jit.lock().unwrap().add_vec()
282    }
283
284    pub fn add_llm(&self) -> Result<()> {
285        self.jit.lock().unwrap().add_llm()
286    }
287
288    pub fn add_root(&self) -> Result<()> {
289        self.jit.lock().unwrap().add_root()
290    }
291
292    pub fn add_http(&self) -> Result<()> {
293        self.jit.lock().unwrap().add_http()
294    }
295
296    pub fn add_db(&self) -> Result<()> {
297        self.jit.lock().unwrap().add_db()
298    }
299
300    pub fn add_all(&self) -> Result<()> {
301        self.jit.lock().unwrap().add_all()
302    }
303
304    pub fn add_native_ptr(&self, full_name: &str, name: &str, arg_tys: &[Type], ret_ty: Type, fn_ptr: *const u8) -> Result<u32> {
305        self.jit.lock().unwrap().add_native_ptr(full_name, name, arg_tys, ret_ty, fn_ptr)
306    }
307
308    pub fn add_native_module_ptr(&self, module: &str, name: &str, arg_tys: &[Type], ret_ty: Type, fn_ptr: *const u8) -> Result<u32> {
309        self.jit.lock().unwrap().add_native_module_ptr(module, name, arg_tys, ret_ty, fn_ptr)
310    }
311
312    pub fn add_native_method_ptr(&self, def: &str, method: &str, arg_tys: &[Type], ret_ty: Type, fn_ptr: *const u8) -> Result<u32> {
313        self.jit.lock().unwrap().add_native_method_ptr(def, method, arg_tys, ret_ty, fn_ptr)
314    }
315
316    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> {
317        self.jit.lock().unwrap().add_inline(name, args, ret, f)
318    }
319
320    pub fn import_code(&self, name: &str, code: Vec<u8>) -> Result<()> {
321        self.jit.lock().unwrap().import_code(name, code)
322    }
323
324    pub fn import_file(&self, name: &str, path: &str) -> Result<()> {
325        self.jit.lock().unwrap().compiler.import_file(name, path)?;
326        Ok(())
327    }
328
329    pub fn import(&self, name: &str, path: &str) -> Result<()> {
330        if root::contains(path) {
331            let code = root::get(path).unwrap();
332            if code.is_str() {
333                self.import_code(name, code.as_str().as_bytes().to_vec())
334            } else {
335                self.import_code(name, code.get_dynamic("code").ok_or(anyhow!("{:?} 没有 code 成员", code))?.as_str().as_bytes().to_vec())
336            }
337        } else {
338            self.import_file(name, path)
339        }
340    }
341
342    pub fn infer(&self, name: &str, arg_tys: &[Type]) -> Result<Type> {
343        self.jit.lock().unwrap().get_type(name, arg_tys)
344    }
345
346    pub fn get_fn_ptr(&self, name: &str, arg_tys: &[Type]) -> Result<(*const u8, Type)> {
347        self.jit.lock().unwrap().get_fn_ptr(name, arg_tys)
348    }
349
350    pub fn get_fn(&self, name: &str, arg_tys: &[Type]) -> Result<CompiledFn> {
351        set_current_vm(self);
352        let (ptr, ret) = self.get_fn_ptr(name, arg_tys)?;
353        Ok(CompiledFn { ptr: ptr as usize, ret, owner: self.clone() })
354    }
355
356    pub fn load(&self, code: Vec<u8>, arg_name: SmolStr) -> Result<(i64, Type)> {
357        self.jit.lock().unwrap().load(code, arg_name)
358    }
359
360    pub fn get_symbol(&self, name: &str, params: Vec<Type>) -> Result<Type> {
361        Ok(Type::Symbol { id: self.jit.lock().unwrap().get_id(name)?, params })
362    }
363
364    pub fn disassemble(&self, name: &str) -> Result<String> {
365        self.jit.lock().unwrap().compiler.symbols.disassemble(name)
366    }
367
368    #[cfg(feature = "ir-disassembly")]
369    pub fn disassemble_ir(&self, name: &str) -> Result<String> {
370        self.jit.lock().unwrap().disassemble_ir(name)
371    }
372}
373
374impl Default for Vm {
375    fn default() -> Self {
376        Self::new()
377    }
378}
379
380#[cfg(test)]
381mod tests {
382    use super::Vm;
383    use dynamic::{Dynamic, ToJson, Type};
384
385    extern "C" fn math_double(value: i64) -> i64 {
386        value * 2
387    }
388
389    #[test]
390    fn vm_can_add_native_after_jit_creation() -> anyhow::Result<()> {
391        let vm = Vm::new();
392        vm.add_native_module_ptr("math", "double", &[Type::I64], Type::I64, math_double as *const u8)?;
393        vm.import_code(
394            "vm_dynamic_native",
395            br#"
396            pub fn run(value: i64) {
397                math::double(value)
398            }
399            "#
400            .to_vec(),
401        )?;
402
403        let compiled = vm.get_fn("vm_dynamic_native::run", &[Type::I64])?;
404        assert_eq!(compiled.ret_ty(), &Type::I64);
405        let run: extern "C" fn(i64) -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
406        assert_eq!(run(21), 42);
407        Ok(())
408    }
409
410    #[test]
411    fn compares_any_with_string_literal_as_string() -> anyhow::Result<()> {
412        let vm = Vm::with_all()?;
413        vm.import_code(
414            "vm_string_compare_any",
415            br#"
416            pub fn any_ne_empty(chat_path) {
417                chat_path != ""
418            }
419            "#
420            .to_vec(),
421        )?;
422
423        let compiled = vm.get_fn("vm_string_compare_any::any_ne_empty", &[Type::Any])?;
424        assert_eq!(compiled.ret_ty(), &Type::Bool);
425
426        let any_ne_empty: extern "C" fn(*const Dynamic) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
427        let empty = Dynamic::from("");
428        let non_empty = Dynamic::from("chat");
429
430        assert!(!any_ne_empty(&empty));
431        assert!(any_ne_empty(&non_empty));
432        Ok(())
433    }
434
435    #[test]
436    fn compares_concrete_value_with_string_literal_as_string() -> anyhow::Result<()> {
437        let vm = Vm::with_all()?;
438        vm.import_code(
439            "vm_string_compare_imm",
440            br#"
441            pub fn int_eq_str(value: i64) {
442                value == "42"
443            }
444
445            pub fn int_to_str(value: i64) {
446                value + ""
447            }
448            "#
449            .to_vec(),
450        )?;
451
452        let compiled = vm.get_fn("vm_string_compare_imm::int_eq_str", &[Type::I64])?;
453        assert_eq!(compiled.ret_ty(), &Type::Bool);
454
455        let int_eq_str: extern "C" fn(i64) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
456
457        let compiled = vm.get_fn("vm_string_compare_imm::int_to_str", &[Type::I64])?;
458        assert_eq!(compiled.ret_ty(), &Type::Any);
459        let int_to_str: extern "C" fn(i64) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
460        let text = int_to_str(42);
461        assert_eq!(unsafe { &*text }.as_str(), "42");
462
463        assert!(int_eq_str(42));
464        assert!(!int_eq_str(7));
465        Ok(())
466    }
467
468    #[test]
469    fn dynamic_field_value_participates_in_or_expression() -> anyhow::Result<()> {
470        let vm = Vm::with_all()?;
471        vm.import_code(
472            "vm_dynamic_field_or",
473            r#"
474            pub fn next_or_start() {
475                let choice = {
476                    label: "颜色",
477                    next: "color"
478                };
479                choice.next || "start"
480            }
481
482            pub fn direct_next() {
483                let choice = {
484                    label: "颜色",
485                    next: "color"
486                };
487                choice.next
488            }
489
490            pub fn bracket_next() {
491                let choice = {
492                    label: "颜色",
493                    next: "color"
494                };
495                choice["next"]
496            }
497
498            pub fn assigned_preview() {
499                let choice = {
500                    next: "tax_free"
501                };
502                choice.preview = choice.next || "start";
503                choice
504            }
505            "#
506            .as_bytes()
507            .to_vec(),
508        )?;
509
510        let compiled = vm.get_fn("vm_dynamic_field_or::direct_next", &[])?;
511        assert_eq!(compiled.ret_ty(), &Type::Any);
512        let direct_next: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
513        assert_eq!(unsafe { &*direct_next() }.as_str(), "color");
514
515        let compiled = vm.get_fn("vm_dynamic_field_or::bracket_next", &[])?;
516        assert_eq!(compiled.ret_ty(), &Type::Any);
517        let bracket_next: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
518        assert_eq!(unsafe { &*bracket_next() }.as_str(), "color");
519
520        let compiled = vm.get_fn("vm_dynamic_field_or::next_or_start", &[])?;
521        assert_eq!(compiled.ret_ty(), &Type::Any);
522        let next_or_start: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
523        assert_eq!(unsafe { &*next_or_start() }.as_str(), "color");
524
525        let compiled = vm.get_fn("vm_dynamic_field_or::assigned_preview", &[])?;
526        assert_eq!(compiled.ret_ty(), &Type::Any);
527        let assigned_preview: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
528        let choice = unsafe { &*assigned_preview() };
529        assert_eq!(choice.get_dynamic("preview").unwrap().as_str(), "tax_free");
530        Ok(())
531    }
532
533    #[test]
534    fn empty_object_literal_in_if_branch_stays_dynamic() -> anyhow::Result<()> {
535        let vm = Vm::with_all()?;
536        vm.import_code(
537            "vm_if_empty_object_branch",
538            r#"
539            pub fn first_note(steps) {
540                let first = if steps.len() > 0 { steps[0] } else { {} };
541                let first_note = first.note || "fallback";
542                first_note
543            }
544
545            pub fn first_ja(steps) {
546                let first = if steps.len() > 0 { steps[0] } else { {} };
547                first.ja || "すみません"
548            }
549
550            pub fn assign_first_note(steps) {
551                let first = {};
552                first = if steps.len() > 0 { steps[0] } else { {} };
553                first.note || "fallback"
554            }
555            "#
556            .as_bytes()
557            .to_vec(),
558        )?;
559
560        let compiled = vm.get_fn("vm_if_empty_object_branch::first_note", &[Type::Any])?;
561        assert_eq!(compiled.ret_ty(), &Type::Any);
562        let first_note: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
563
564        let empty_steps = Dynamic::list(Vec::new());
565        assert_eq!(unsafe { &*first_note(&empty_steps) }.as_str(), "fallback");
566
567        let mut step = std::collections::BTreeMap::new();
568        step.insert("note".into(), "hello".into());
569        let steps = Dynamic::list(vec![Dynamic::map(step)]);
570        assert_eq!(unsafe { &*first_note(&steps) }.as_str(), "hello");
571
572        let compiled = vm.get_fn("vm_if_empty_object_branch::first_ja", &[Type::Any])?;
573        assert_eq!(compiled.ret_ty(), &Type::Any);
574        let first_ja: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
575        assert_eq!(unsafe { &*first_ja(&empty_steps) }.as_str(), "すみません");
576
577        let compiled = vm.get_fn("vm_if_empty_object_branch::assign_first_note", &[Type::Any])?;
578        assert_eq!(compiled.ret_ty(), &Type::Any);
579        let assign_first_note: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
580        assert_eq!(unsafe { &*assign_first_note(&empty_steps) }.as_str(), "fallback");
581        assert_eq!(unsafe { &*assign_first_note(&steps) }.as_str(), "hello");
582        Ok(())
583    }
584
585    #[test]
586    fn list_literal_can_be_function_tail_expression() -> anyhow::Result<()> {
587        let vm = Vm::with_all()?;
588        vm.import_code(
589            "vm_tail_list_literal",
590            r#"
591            pub fn numbers() {
592                [1, 2, 3]
593            }
594
595            pub fn maps() {
596                [
597                    {note: "first"},
598                    {note: "second"}
599                ]
600            }
601
602            pub fn object_with_maps() {
603                {
604                    steps: [
605                        {note: "first"},
606                        {note: "second"}
607                    ]
608                }
609            }
610
611            pub fn return_maps() {
612                return [
613                    {note: "first"},
614                    {note: "second"}
615                ];
616            }
617
618            pub fn return_maps_without_semicolon() {
619                return [
620                    {note: "first"},
621                    {note: "second"}
622                ]
623            }
624
625            pub fn tail_bare_variable() {
626                let value = [
627                    {note: "first"},
628                    {note: "second"}
629                ];
630                value
631            }
632
633            pub fn return_bare_variable_without_semicolon() {
634                let value = [
635                    {note: "first"},
636                    {note: "second"}
637                ];
638                return value
639            }
640
641            pub fn tail_object_variable() {
642                let result = {
643                    steps: [
644                        {note: "first"},
645                        {note: "second"}
646                    ]
647                };
648                result
649            }
650            "#
651            .as_bytes()
652            .to_vec(),
653        )?;
654
655        let compiled = vm.get_fn("vm_tail_list_literal::numbers", &[])?;
656        assert_eq!(compiled.ret_ty(), &Type::Any);
657        let numbers: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
658        let result = unsafe { &*numbers() };
659        assert_eq!(result.len(), 3);
660        assert_eq!(result.get_idx(1).and_then(|value| value.as_int()), Some(2));
661
662        let compiled = vm.get_fn("vm_tail_list_literal::maps", &[])?;
663        assert_eq!(compiled.ret_ty(), &Type::Any);
664        let maps: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
665        let result = unsafe { &*maps() };
666        assert_eq!(result.len(), 2);
667        assert_eq!(result.get_idx(1).and_then(|value| value.get_dynamic("note")).map(|value| value.as_str().to_string()), Some("second".to_string()));
668
669        let compiled = vm.get_fn("vm_tail_list_literal::object_with_maps", &[])?;
670        assert_eq!(compiled.ret_ty(), &Type::Any);
671        let object_with_maps: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
672        let result = unsafe { &*object_with_maps() };
673        let steps = result.get_dynamic("steps").expect("steps");
674        assert_eq!(steps.len(), 2);
675        assert_eq!(steps.get_idx(0).and_then(|value| value.get_dynamic("note")).map(|value| value.as_str().to_string()), Some("first".to_string()));
676
677        let compiled = vm.get_fn("vm_tail_list_literal::return_maps", &[])?;
678        assert_eq!(compiled.ret_ty(), &Type::Any);
679        let return_maps: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
680        let result = unsafe { &*return_maps() };
681        assert_eq!(result.len(), 2);
682        assert_eq!(result.get_idx(1).and_then(|value| value.get_dynamic("note")).map(|value| value.as_str().to_string()), Some("second".to_string()));
683
684        let compiled = vm.get_fn("vm_tail_list_literal::return_maps_without_semicolon", &[])?;
685        assert_eq!(compiled.ret_ty(), &Type::Any);
686        let return_maps_without_semicolon: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
687        let result = unsafe { &*return_maps_without_semicolon() };
688        assert_eq!(result.len(), 2);
689        assert_eq!(result.get_idx(0).and_then(|value| value.get_dynamic("note")).map(|value| value.as_str().to_string()), Some("first".to_string()));
690
691        let compiled = vm.get_fn("vm_tail_list_literal::tail_bare_variable", &[])?;
692        assert_eq!(compiled.ret_ty(), &Type::Any);
693        let tail_bare_variable: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
694        let result = unsafe { &*tail_bare_variable() };
695        assert_eq!(result.len(), 2);
696        assert_eq!(result.get_idx(1).and_then(|value| value.get_dynamic("note")).map(|value| value.as_str().to_string()), Some("second".to_string()));
697
698        let compiled = vm.get_fn("vm_tail_list_literal::return_bare_variable_without_semicolon", &[])?;
699        assert_eq!(compiled.ret_ty(), &Type::Any);
700        let return_bare_variable_without_semicolon: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
701        let result = unsafe { &*return_bare_variable_without_semicolon() };
702        assert_eq!(result.len(), 2);
703        assert_eq!(result.get_idx(0).and_then(|value| value.get_dynamic("note")).map(|value| value.as_str().to_string()), Some("first".to_string()));
704
705        let compiled = vm.get_fn("vm_tail_list_literal::tail_object_variable", &[])?;
706        assert_eq!(compiled.ret_ty(), &Type::Any);
707        let tail_object_variable: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
708        let result = unsafe { &*tail_object_variable() };
709        let steps = result.get_dynamic("steps").expect("steps");
710        assert_eq!(steps.len(), 2);
711        assert_eq!(steps.get_idx(1).and_then(|value| value.get_dynamic("note")).map(|value| value.as_str().to_string()), Some("second".to_string()));
712        Ok(())
713    }
714
715    #[test]
716    fn repeated_deep_step_literals_import_successfully() -> anyhow::Result<()> {
717        fn extra_page_literal(depth: usize) -> String {
718            let mut value = "{leaf: \"done\"}".to_string();
719            for idx in 0..depth {
720                value = format!("{{kind: \"page\", idx: {idx}, children: [{value}], meta: {{title: \"extra\", visible: true}}}}");
721            }
722            value
723        }
724
725        let extra = extra_page_literal(48);
726        let code = format!(
727            r#"
728            pub fn script() {{
729                return [
730                    {{ja: "一つ目", note: "first", extra: {extra}}},
731                    {{ja: "二つ目", note: "second", extra: {extra}}},
732                    {{ja: "三つ目", note: "third", extra: {extra}}}
733                ]
734            }}
735            "#
736        );
737
738        let vm = Vm::with_all()?;
739        vm.import_code("vm_repeated_deep_step_literals", code.into_bytes())?;
740        let compiled = vm.get_fn("vm_repeated_deep_step_literals::script", &[])?;
741        assert_eq!(compiled.ret_ty(), &Type::Any);
742        let script: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
743        let result = unsafe { &*script() };
744        assert_eq!(result.len(), 3);
745        assert_eq!(result.get_idx(2).and_then(|value| value.get_dynamic("note")).map(|value| value.as_str().to_string()), Some("third".to_string()));
746        Ok(())
747    }
748
749    #[test]
750    fn object_last_field_call_does_not_need_trailing_comma() -> anyhow::Result<()> {
751        let vm = Vm::with_all()?;
752        vm.import_code(
753            "vm_object_last_call_field",
754            r#"
755            pub fn extra_page() {
756                {
757                    title: "extra",
758                    pages: [
759                        {note: "nested"}
760                    ]
761                }
762            }
763
764            pub fn data() {
765                return [
766                    {
767                        note: "first",
768                        choices: ["a", "b"],
769                        extras: extra_page()
770                    },
771                    {
772                        note: "second",
773                        choices: ["c"],
774                        extras: extra_page()
775                    }
776                ]
777            }
778            "#
779            .as_bytes()
780            .to_vec(),
781        )?;
782
783        let compiled = vm.get_fn("vm_object_last_call_field::data", &[])?;
784        assert_eq!(compiled.ret_ty(), &Type::Any);
785        let data: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
786        let result = unsafe { &*data() };
787        assert_eq!(result.len(), 2);
788        let first = result.get_idx(0).expect("first step");
789        assert_eq!(first.get_dynamic("extras").and_then(|extras| extras.get_dynamic("title")).map(|title| title.as_str().to_string()), Some("extra".to_string()));
790        Ok(())
791    }
792
793    #[test]
794    fn root_native_calls_do_not_take_ownership_of_dynamic_args() -> anyhow::Result<()> {
795        let vm = Vm::with_all()?;
796        vm.import_code(
797            "vm_root_clone_bridge",
798            br#"
799            pub fn add_then_reuse(arg) {
800                let user = {
801                    address: "test-wallet",
802                    points: 20
803                };
804                root::add("local/root-clone-bridge-user", user);
805                user.points = user.points - 7;
806                root::add("local/root-clone-bridge-user", user);
807                {
808                    user: user,
809                    points: user.points
810                }
811            }
812            "#
813            .to_vec(),
814        )?;
815
816        let compiled = vm.get_fn("vm_root_clone_bridge::add_then_reuse", &[Type::Any])?;
817        assert_eq!(compiled.ret_ty(), &Type::Any);
818        let add_then_reuse: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
819        let arg = Dynamic::Null;
820        let result = add_then_reuse(&arg);
821        let result = unsafe { &*result };
822
823        assert_eq!(result.get_dynamic("points").and_then(|value| value.as_int()), Some(13));
824        let mut json = String::new();
825        result.to_json(&mut json);
826        assert!(json.contains("\"points\": 13"));
827        Ok(())
828    }
829}