1mod 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); let final_addr = ctx.builder.ins().iadd(args[0], offset_val); let dest = ctx.builder.ins().imul(args[2], width);
171 let dest_addr = ctx.builder.ins().iadd(args[0], dest); 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); 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}