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