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::sync::{Mutex, OnceLock, Weak};
26static PTR_TYPE: OnceLock<types::Type> = OnceLock::new();
27pub fn ptr_type() -> types::Type {
28 PTR_TYPE.get().cloned().unwrap()
29}
30
31pub fn get_type(ty: &Type) -> Result<types::Type> {
32 if ty.is_f64() {
33 Ok(types::F64)
34 } else if ty.is_f32() {
35 Ok(types::F32)
36 } else if ty.is_int() | ty.is_uint() {
37 match ty.width() {
38 1 => Ok(types::I8),
39 2 => Ok(types::I16),
40 4 => Ok(types::I32),
41 8 => Ok(types::I64),
42 _ => Err(anyhow!("非法类型 {:?}", ty)),
43 }
44 } else if let Type::Bool = ty {
45 Ok(types::I8)
46 } else {
47 Ok(ptr_type())
48 }
49}
50
51use compiler::Symbol;
52use cranelift::prelude::*;
53
54pub fn init_jit(mut jit: JITRunTime) -> Result<JITRunTime> {
55 jit.add_all()?;
56 Ok(jit)
57}
58
59use std::sync::Arc;
60unsafe impl Send for JITRunTime {}
61unsafe impl Sync for JITRunTime {}
62
63pub(crate) fn with_vm_context<T>(context: *const Weak<Mutex<JITRunTime>>, f: impl FnOnce(&Vm) -> Result<T>) -> Result<T> {
64 if context.is_null() {
65 return Err(anyhow!("VM context is null"));
66 }
67 let jit = unsafe { &*context }.upgrade().ok_or_else(|| anyhow!("VM context has expired"))?;
68 let vm = Vm { jit };
69 f(&vm)
70}
71
72fn add_method_field(jit: &mut JITRunTime, def: &str, method: &str, id: u32) -> Result<()> {
73 let def_id = jit.get_id(def)?;
74 if let Some((_, define)) = jit.compiler.symbols.get_symbol_mut(def_id) {
75 if let Symbol::Struct(Type::Struct { params, fields }, _) = define {
76 fields.push((method.into(), Type::Symbol { id, params: params.clone() }));
77 }
78 }
79 Ok(())
80}
81
82fn add_native_module_fns(jit: &mut JITRunTime, module: &str, fns: &[(&str, &[Type], Type, *const u8)]) -> Result<()> {
83 jit.add_module(module);
84 for (name, arg_tys, ret_ty, fn_ptr) in fns {
85 let full_name = format!("{}::{}", module, name);
86 jit.add_native_ptr(&full_name, name, arg_tys, ret_ty.clone(), *fn_ptr)?;
87 }
88 jit.pop_module();
89 Ok(())
90}
91
92impl JITRunTime {
93 pub fn add_module(&mut self, name: &str) {
94 self.compiler.symbols.add_module(name.into());
95 }
96
97 pub fn pop_module(&mut self) {
98 self.compiler.symbols.pop_module();
99 }
100
101 pub fn add_type(&mut self, name: &str, ty: Type, is_pub: bool) -> u32 {
102 self.compiler.add_symbol(name, Symbol::Struct(ty, is_pub))
103 }
104
105 pub fn add_empty_type(&mut self, name: &str) -> Result<u32> {
106 match self.get_id(name) {
107 Ok(id) => Ok(id),
108 Err(_) => Ok(self.add_type(name, Type::Struct { params: Vec::new(), fields: Vec::new() }, true)),
109 }
110 }
111
112 pub fn add_native_module_ptr(&mut self, module: &str, name: &str, arg_tys: &[Type], ret_ty: Type, fn_ptr: *const u8) -> Result<u32> {
113 self.add_module(module);
114 let full_name = format!("{}::{}", module, name);
115 let result = self.add_native_ptr(&full_name, name, arg_tys, ret_ty, fn_ptr);
116 self.pop_module();
117 result
118 }
119
120 pub(crate) fn add_native_module_context_ptr(&mut self, module: &str, name: &str, arg_tys: &[Type], ret_ty: Type, fn_ptr: *const u8) -> Result<u32> {
121 self.add_module(module);
122 let full_name = format!("{}::{}", module, name);
123 let result = self.add_context_native_ptr(&full_name, name, arg_tys, ret_ty, fn_ptr);
124 self.pop_module();
125 result
126 }
127
128 pub fn add_native_method_ptr(&mut self, def: &str, method: &str, arg_tys: &[Type], ret_ty: Type, fn_ptr: *const u8) -> Result<u32> {
129 self.add_empty_type(def)?;
130 let full_name = format!("{}::{}", def, method);
131 let id = self.add_native_ptr(&full_name, &full_name, arg_tys, ret_ty, fn_ptr)?;
132 add_method_field(self, def, method, id)?;
133 Ok(id)
134 }
135
136 pub fn add_std(&mut self) -> Result<()> {
137 self.add_module("std");
138 for (name, arg_tys, ret_ty, fn_ptr) in STD {
139 self.add_native_ptr(name, name, arg_tys, ret_ty, fn_ptr)?;
140 }
141 self.add_context_native_ptr("import", "import", &[Type::Any, Type::Any], Type::Bool, native::import_with_vm as *const u8)?;
142 Ok(())
143 }
144
145 pub fn add_any(&mut self) -> Result<()> {
146 for (name, arg_tys, ret_ty, fn_ptr) in ANY {
147 let (_, method) = name.split_once("::").ok_or_else(|| anyhow!("非法 Any 方法名 {}", name))?;
148 self.add_native_method_ptr("Any", method, arg_tys, ret_ty, fn_ptr)?;
149 }
150 Ok(())
151 }
152
153 pub fn add_vec(&mut self) -> Result<()> {
154 self.add_empty_type("Vec")?;
155 let vec_def = Type::Symbol { id: self.get_id("Vec")?, params: Vec::new() };
156 self.add_inline("Vec::swap", vec![vec_def.clone(), Type::I64, Type::I64], Type::Void, |ctx: Option<&mut BuildContext>, args: Vec<Value>| {
157 if let Some(ctx) = ctx {
158 let width = ctx.builder.ins().iconst(types::I64, 4);
159 let offset_val = ctx.builder.ins().imul(args[1], width); let final_addr = ctx.builder.ins().iadd(args[0], offset_val); let dest = ctx.builder.ins().imul(args[2], width);
162 let dest_addr = ctx.builder.ins().iadd(args[0], dest); let dest_val = ctx.builder.ins().load(types::I32, MemFlags::trusted(), dest_addr, 0);
164 let v = ctx.builder.ins().load(types::I32, MemFlags::trusted(), final_addr, 0);
165 ctx.builder.ins().store(MemFlags::trusted(), v, dest_addr, 0);
166 ctx.builder.ins().store(MemFlags::trusted(), dest_val, final_addr, 0);
167 }
168 Err(anyhow!("无返回值"))
169 })?;
170
171 self.add_inline("Vec::get_idx", vec![vec_def.clone(), Type::I64], Type::I32, |ctx: Option<&mut BuildContext>, args: Vec<Value>| {
172 if let Some(ctx) = ctx {
173 let width = ctx.builder.ins().iconst(types::I64, 4);
174 let offset_val = ctx.builder.ins().imul(args[1], width); let final_addr = ctx.builder.ins().iadd(args[0], offset_val);
176 Ok((Some(ctx.builder.ins().load(types::I32, MemFlags::trusted(), final_addr, 0)), Type::I32))
177 } else {
178 Ok((None, Type::I32))
179 }
180 })?;
181 Ok(())
182 }
183
184 pub fn add_llm(&mut self) -> Result<()> {
185 add_native_module_fns(self, "llm", &llm_module::LLM_NATIVE)
186 }
187
188 pub fn add_root(&mut self) -> Result<()> {
189 add_native_module_fns(self, "root", &root_module::ROOT_NATIVE)?;
190 self.add_native_module_context_ptr("root", "add_fn", &[Type::Any, Type::Any], Type::Bool, root_module::root_add_fn_with_vm as *const u8)?;
191 Ok(())
192 }
193
194 pub fn add_http(&mut self) -> Result<()> {
195 add_native_module_fns(self, "http", &http_module::HTTP_NATIVE)
196 }
197
198 pub fn add_db(&mut self) -> Result<()> {
199 add_native_module_fns(self, "db", &db_module::DB_NATIVE)
200 }
201
202 pub fn add_gpu(&mut self) -> Result<()> {
203 add_native_module_fns(self, "gpu", &gpu_module::GPU_NATIVE)
204 }
205
206 pub fn add_all(&mut self) -> Result<()> {
207 self.add_std()?;
208 self.add_any()?;
209 self.add_vec()?;
210 self.add_llm()?;
211 self.add_root()?;
212 self.add_http()?;
213 self.add_db()?;
214 self.add_gpu()?;
215 Ok(())
216 }
217}
218
219#[derive(Clone)]
220pub struct Vm {
221 jit: Arc<Mutex<JITRunTime>>,
222}
223
224#[derive(Clone)]
225pub struct CompiledFn {
226 ptr: usize,
227 ret: Type,
228 owner: Vm,
229}
230
231impl CompiledFn {
232 pub fn ptr(&self) -> *const u8 {
233 self.ptr as *const u8
234 }
235
236 pub fn ret_ty(&self) -> &Type {
237 &self.ret
238 }
239
240 pub fn owner(&self) -> &Vm {
241 &self.owner
242 }
243}
244
245impl Vm {
246 pub fn new() -> Self {
247 let jit = Arc::new(Mutex::new(JITRunTime::new(|_| {})));
248 jit.lock().unwrap().set_owner(Arc::downgrade(&jit));
249 Self { jit }
250 }
251
252 pub fn with_all() -> Result<Self> {
253 let vm = Self::new();
254 vm.add_all()?;
255 Ok(vm)
256 }
257
258 pub fn add_module(&self, name: &str) {
259 self.jit.lock().unwrap().add_module(name)
260 }
261
262 pub fn pop_module(&self) {
263 self.jit.lock().unwrap().pop_module()
264 }
265
266 pub fn add_type(&self, name: &str, ty: Type, is_pub: bool) -> u32 {
267 self.jit.lock().unwrap().add_type(name, ty, is_pub)
268 }
269
270 pub fn add_empty_type(&self, name: &str) -> Result<u32> {
271 self.jit.lock().unwrap().add_empty_type(name)
272 }
273
274 pub fn add_std(&self) -> Result<()> {
275 self.jit.lock().unwrap().add_std()
276 }
277
278 pub fn add_any(&self) -> Result<()> {
279 self.jit.lock().unwrap().add_any()
280 }
281
282 pub fn add_vec(&self) -> Result<()> {
283 self.jit.lock().unwrap().add_vec()
284 }
285
286 pub fn add_llm(&self) -> Result<()> {
287 self.jit.lock().unwrap().add_llm()
288 }
289
290 pub fn add_root(&self) -> Result<()> {
291 self.jit.lock().unwrap().add_root()
292 }
293
294 pub fn add_http(&self) -> Result<()> {
295 self.jit.lock().unwrap().add_http()
296 }
297
298 pub fn add_db(&self) -> Result<()> {
299 self.jit.lock().unwrap().add_db()
300 }
301
302 pub fn add_gpu(&self) -> Result<()> {
303 self.jit.lock().unwrap().add_gpu()
304 }
305
306 pub fn add_all(&self) -> Result<()> {
307 self.jit.lock().unwrap().add_all()
308 }
309
310 pub fn add_native_ptr(&self, full_name: &str, name: &str, arg_tys: &[Type], ret_ty: Type, fn_ptr: *const u8) -> Result<u32> {
311 self.jit.lock().unwrap().add_native_ptr(full_name, name, arg_tys, ret_ty, fn_ptr)
312 }
313
314 pub fn add_native_module_ptr(&self, module: &str, name: &str, arg_tys: &[Type], ret_ty: Type, fn_ptr: *const u8) -> Result<u32> {
315 self.jit.lock().unwrap().add_native_module_ptr(module, name, arg_tys, ret_ty, fn_ptr)
316 }
317
318 pub fn add_native_method_ptr(&self, def: &str, method: &str, arg_tys: &[Type], ret_ty: Type, fn_ptr: *const u8) -> Result<u32> {
319 self.jit.lock().unwrap().add_native_method_ptr(def, method, arg_tys, ret_ty, fn_ptr)
320 }
321
322 pub fn add_inline(&self, name: &str, args: Vec<Type>, ret: Type, f: fn(Option<&mut BuildContext>, Vec<Value>) -> Result<(Option<Value>, Type)>) -> Result<u32> {
323 self.jit.lock().unwrap().add_inline(name, args, ret, f)
324 }
325
326 pub fn import_code(&self, name: &str, code: Vec<u8>) -> Result<()> {
327 self.jit.lock().unwrap().import_code(name, code)
328 }
329
330 pub fn import_file(&self, name: &str, path: &str) -> Result<()> {
331 self.jit.lock().unwrap().compiler.import_file(name, path)?;
332 Ok(())
333 }
334
335 pub fn import(&self, name: &str, path: &str) -> Result<()> {
336 if root::contains(path) {
337 let code = root::get(path).unwrap();
338 if code.is_str() {
339 self.import_code(name, code.as_str().as_bytes().to_vec())
340 } else {
341 self.import_code(name, code.get_dynamic("code").ok_or(anyhow!("{:?} 没有 code 成员", code))?.as_str().as_bytes().to_vec())
342 }
343 } else {
344 self.import_file(name, path)
345 }
346 }
347
348 pub fn infer(&self, name: &str, arg_tys: &[Type]) -> Result<Type> {
349 self.jit.lock().unwrap().get_type(name, arg_tys)
350 }
351
352 pub fn get_fn_ptr(&self, name: &str, arg_tys: &[Type]) -> Result<(*const u8, Type)> {
353 self.jit.lock().unwrap().get_fn_ptr(name, arg_tys)
354 }
355
356 pub fn get_fn(&self, name: &str, arg_tys: &[Type]) -> Result<CompiledFn> {
357 let (ptr, ret) = self.get_fn_ptr(name, arg_tys)?;
358 Ok(CompiledFn { ptr: ptr as usize, ret, owner: self.clone() })
359 }
360
361 pub fn load(&self, code: Vec<u8>, arg_name: SmolStr) -> Result<(i64, Type)> {
362 self.jit.lock().unwrap().load(code, arg_name)
363 }
364
365 pub fn get_symbol(&self, name: &str, params: Vec<Type>) -> Result<Type> {
366 Ok(Type::Symbol { id: self.jit.lock().unwrap().get_id(name)?, params })
367 }
368
369 pub fn gpu_struct_layout(&self, name: &str, params: &[Type]) -> Result<GpuStructLayout> {
370 let jit = self.jit.lock().unwrap();
371 GpuStructLayout::from_symbol_table(&jit.compiler.symbols, name, params)
372 }
373
374 pub fn disassemble(&self, name: &str) -> Result<String> {
375 self.jit.lock().unwrap().compiler.symbols.disassemble(name)
376 }
377
378 #[cfg(feature = "ir-disassembly")]
379 pub fn disassemble_ir(&self, name: &str) -> Result<String> {
380 self.jit.lock().unwrap().disassemble_ir(name)
381 }
382}
383
384impl Default for Vm {
385 fn default() -> Self {
386 Self::new()
387 }
388}
389
390#[cfg(test)]
391mod tests {
392 use super::Vm;
393 use dynamic::{Dynamic, ToJson, Type};
394
395 extern "C" fn math_double(value: i64) -> i64 {
396 value * 2
397 }
398
399 #[test]
400 fn vm_can_add_native_after_jit_creation() -> anyhow::Result<()> {
401 let vm = Vm::new();
402 vm.add_native_module_ptr("math", "double", &[Type::I64], Type::I64, math_double as *const u8)?;
403 vm.import_code(
404 "vm_dynamic_native",
405 br#"
406 pub fn run(value: i64) {
407 math::double(value)
408 }
409 "#
410 .to_vec(),
411 )?;
412
413 let compiled = vm.get_fn("vm_dynamic_native::run", &[Type::I64])?;
414 assert_eq!(compiled.ret_ty(), &Type::I64);
415 let run: extern "C" fn(i64) -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
416 assert_eq!(run(21), 42);
417 Ok(())
418 }
419
420 #[test]
421 fn compares_any_with_string_literal_as_string() -> anyhow::Result<()> {
422 let vm = Vm::with_all()?;
423 vm.import_code(
424 "vm_string_compare_any",
425 br#"
426 pub fn any_ne_empty(chat_path) {
427 chat_path != ""
428 }
429 "#
430 .to_vec(),
431 )?;
432
433 let compiled = vm.get_fn("vm_string_compare_any::any_ne_empty", &[Type::Any])?;
434 assert_eq!(compiled.ret_ty(), &Type::Bool);
435
436 let any_ne_empty: extern "C" fn(*const Dynamic) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
437 let empty = Dynamic::from("");
438 let non_empty = Dynamic::from("chat");
439
440 assert!(!any_ne_empty(&empty));
441 assert!(any_ne_empty(&non_empty));
442 Ok(())
443 }
444
445 #[test]
446 fn parenthesized_expression_can_call_any_method() -> anyhow::Result<()> {
447 let vm = Vm::with_all()?;
448 vm.import_code(
449 "vm_parenthesized_method_call",
450 br#"
451 pub fn run(value) {
452 (value + 2).to_i64()
453 }
454 "#
455 .to_vec(),
456 )?;
457
458 let compiled = vm.get_fn("vm_parenthesized_method_call::run", &[Type::Any])?;
459 assert_eq!(compiled.ret_ty(), &Type::I64);
460 let run: extern "C" fn(*const Dynamic) -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
461 let value = Dynamic::from(40i64);
462
463 assert_eq!(run(&value), 42);
464 Ok(())
465 }
466
467 #[test]
468 fn any_keys_returns_map_keys_and_empty_list_for_other_values() -> anyhow::Result<()> {
469 let vm = Vm::with_all()?;
470 vm.import_code(
471 "vm_any_keys",
472 br#"
473 pub fn map_keys(value) {
474 let keys = value.keys();
475 keys.len() == 2 && keys.contains("alpha") && keys.contains("beta")
476 }
477
478 pub fn non_map_keys(value) {
479 value.keys().len() == 0
480 }
481 "#
482 .to_vec(),
483 )?;
484
485 let compiled = vm.get_fn("vm_any_keys::map_keys", &[Type::Any])?;
486 assert_eq!(compiled.ret_ty(), &Type::Bool);
487 let map_keys: extern "C" fn(*const Dynamic) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
488 let value = dynamic::map!("alpha"=> 1i64, "beta"=> 2i64);
489 assert!(map_keys(&value));
490
491 let compiled = vm.get_fn("vm_any_keys::non_map_keys", &[Type::Any])?;
492 assert_eq!(compiled.ret_ty(), &Type::Bool);
493 let non_map_keys: extern "C" fn(*const Dynamic) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
494 let value = Dynamic::from("alpha");
495 assert!(non_map_keys(&value));
496 Ok(())
497 }
498
499 #[test]
500 fn compares_concrete_value_with_string_literal_as_string() -> anyhow::Result<()> {
501 let vm = Vm::with_all()?;
502 vm.import_code(
503 "vm_string_compare_imm",
504 br#"
505 pub fn int_eq_str(value: i64) {
506 value == "42"
507 }
508
509 pub fn int_to_str(value: i64) {
510 value + ""
511 }
512 "#
513 .to_vec(),
514 )?;
515
516 let compiled = vm.get_fn("vm_string_compare_imm::int_eq_str", &[Type::I64])?;
517 assert_eq!(compiled.ret_ty(), &Type::Bool);
518
519 let int_eq_str: extern "C" fn(i64) -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
520
521 let compiled = vm.get_fn("vm_string_compare_imm::int_to_str", &[Type::I64])?;
522 assert_eq!(compiled.ret_ty(), &Type::Any);
523 let int_to_str: extern "C" fn(i64) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
524 let text = int_to_str(42);
525 assert_eq!(unsafe { &*text }.as_str(), "42");
526
527 assert!(int_eq_str(42));
528 assert!(!int_eq_str(7));
529 Ok(())
530 }
531
532 #[test]
533 fn concatenates_string_with_integer_values() -> anyhow::Result<()> {
534 let vm = Vm::with_all()?;
535 vm.import_code(
536 "vm_string_concat_integer",
537 br#"
538 pub fn idx_key(idx: i64) {
539 "" + idx
540 }
541
542 pub fn level_text(level: i64) {
543 "" + level + " level"
544 }
545
546 pub fn gold_text(currency) {
547 "" + currency.gold
548 }
549 "#
550 .to_vec(),
551 )?;
552
553 let compiled = vm.get_fn("vm_string_concat_integer::idx_key", &[Type::I64])?;
554 let idx_key: extern "C" fn(i64) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
555 let result = unsafe { &*idx_key(7) };
556 assert_eq!(result.as_str(), "7");
557
558 let compiled = vm.get_fn("vm_string_concat_integer::level_text", &[Type::I64])?;
559 let level_text: extern "C" fn(i64) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
560 let result = unsafe { &*level_text(12) };
561 assert_eq!(result.as_str(), "12 level");
562
563 let compiled = vm.get_fn("vm_string_concat_integer::gold_text", &[Type::Any])?;
564 let gold_text: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
565 let currency = dynamic::map!("gold"=> 345i64);
566 let result = unsafe { &*gold_text(¤cy) };
567 assert_eq!(result.as_str(), "345");
568 Ok(())
569 }
570
571 #[test]
572 fn coerces_string_concat_to_i64_without_unimplemented_log() -> anyhow::Result<()> {
573 let vm = Vm::with_all()?;
574 vm.import_code(
575 "vm_string_concat_to_i64",
576 br#"
577 pub fn run(idx: i64) {
578 ("" + idx) as i64
579 }
580 "#
581 .to_vec(),
582 )?;
583
584 let compiled = vm.get_fn("vm_string_concat_to_i64::run", &[Type::I64])?;
585 assert_eq!(compiled.ret_ty(), &Type::I64);
586 let run: extern "C" fn(i64) -> i64 = unsafe { std::mem::transmute(compiled.ptr()) };
587 assert_eq!(run(7), 0);
588 Ok(())
589 }
590
591 #[test]
592 fn root_get_accepts_string_concat_with_dynamic_field() -> anyhow::Result<()> {
593 let vm = Vm::with_all()?;
594 vm.import_code(
595 "vm_root_get_dynamic_concat",
596 br#"
597 pub fn get_action(req) {
598 root::get("local/game/panel_actions/" + req.idx)
599 }
600 "#
601 .to_vec(),
602 )?;
603
604 root::add("local/game/panel_actions/7", dynamic::map!("id"=> "action-7").into())?;
605 let compiled = vm.get_fn("vm_root_get_dynamic_concat::get_action", &[Type::Any])?;
606 assert_eq!(compiled.ret_ty(), &Type::Any);
607 let get_action: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
608 let req = dynamic::map!("idx"=> 7i64);
609 let result = unsafe { &*get_action(&req) };
610
611 assert_eq!(result.get_dynamic("id").map(|value| value.as_str().to_string()), Some("action-7".to_string()));
612 Ok(())
613 }
614
615 #[test]
616 fn root_add_fn_registers_handler_with_dynamic_field_path_concat() -> anyhow::Result<()> {
617 let vm = Vm::with_all()?;
618 vm.import_code(
619 "vm_registered_panel_action",
620 br#"
621 pub fn panel_action(req) {
622 root::get("local/game/panel_actions/" + req.idx)
623 }
624
625 pub fn register() {
626 root::add_fn("local/ui/panel_action", "vm_registered_panel_action::panel_action")
627 }
628 "#
629 .to_vec(),
630 )?;
631
632 let compiled = vm.get_fn("vm_registered_panel_action::register", &[])?;
633 assert_eq!(compiled.ret_ty(), &Type::Bool);
634 let register: extern "C" fn() -> bool = unsafe { std::mem::transmute(compiled.ptr()) };
635 assert!(register());
636 Ok(())
637 }
638
639 #[test]
640 fn root_add_fn_accepts_string_concat_in_registered_handler() -> anyhow::Result<()> {
641 let vm = Vm::with_all()?;
642 vm.import_code(
643 "vm_registered_string_concat",
644 br#"
645 pub fn send_panel(idx: i64) {
646 let idx_key = "" + idx;
647 idx_key
648 }
649 "#
650 .to_vec(),
651 )?;
652
653 assert!(vm.get_fn_ptr("vm_registered_string_concat::send_panel", &[Type::Any]).is_ok());
654 Ok(())
655 }
656
657 #[test]
658 fn compiles_public_hotspots_with_string_paths_and_keys() -> anyhow::Result<()> {
659 let vm = Vm::with_all()?;
660 vm.import_code(
661 "vm_public_hotspots",
662 br#"
663 pub fn public_hotspot(action_map_path, panel_id, action_id, hotspot) {
664 {
665 path: action_map_path,
666 panel_id: panel_id,
667 action_id: action_id,
668 id: hotspot.id
669 }
670 }
671
672 pub fn public_hotspots(idx, panel_id, hotspots) {
673 let idx_key = "" + idx;
674 let action_map_path = "local/game/panel_actions/" + idx_key;
675
676 let existing_action_map = root::get(action_map_path);
677 if !existing_action_map.is_map() {
678 root::add_map(action_map_path);
679 }
680
681 if hotspots.is_map() {
682 let public_items = {};
683 for action_id in hotspots.keys() {
684 public_items[action_id] = public_hotspot(action_map_path, panel_id, action_id, hotspots[action_id]);
685 }
686 return public_items;
687 }
688
689 let public_items = [];
690 let i = 0;
691 while i < hotspots.len() {
692 let hotspot = hotspots.get_idx(i);
693 let item = public_hotspot(action_map_path, panel_id, hotspot.id, hotspot);
694 public_items.push(item);
695 i = i + 1;
696 }
697
698 public_items
699 }
700 "#
701 .to_vec(),
702 )?;
703
704 assert!(vm.get_fn("vm_public_hotspots::public_hotspots", &[Type::I64, Type::Any, Type::Any]).is_ok());
705 assert!(vm.get_fn("vm_public_hotspots::public_hotspots", &[Type::Any, Type::Any, Type::Any]).is_ok());
706 Ok(())
707 }
708
709 #[test]
710 fn send_panel_calls_public_hotspots_with_dynamic_request() -> anyhow::Result<()> {
711 let vm = Vm::with_all()?;
712 vm.import_code(
713 "vm_send_panel_public_hotspots",
714 br#"
715 pub fn ok(value) {
716 value
717 }
718
719 pub fn panel_from_node(req) {
720 {
721 panel_id: req.panel_id,
722 hotspots: req.hotspots
723 }
724 }
725
726 pub fn public_hotspot(action_map_path, panel_id, action_id, hotspot) {
727 {
728 path: action_map_path,
729 panel_id: panel_id,
730 action_id: action_id,
731 id: hotspot.id
732 }
733 }
734
735 pub fn public_hotspots(idx, panel_id, hotspots) {
736 let idx_key = "" + idx;
737 let action_map_path = "local/game/panel_actions/" + idx_key;
738
739 let existing_action_map = root::get(action_map_path);
740 if !existing_action_map.is_map() {
741 root::add_map(action_map_path);
742 }
743
744 if hotspots.is_map() {
745 let public_items = {};
746 for action_id in hotspots.keys() {
747 public_items[action_id] = public_hotspot(action_map_path, panel_id, action_id, hotspots[action_id]);
748 }
749 return public_items;
750 }
751
752 let public_items = [];
753 let i = 0;
754 while i < hotspots.len() {
755 let hotspot = hotspots.get_idx(i);
756 let item = public_hotspot(action_map_path, panel_id, hotspot.id, hotspot);
757 public_items.push(item);
758 i = i + 1;
759 }
760
761 public_items
762 }
763
764 pub fn send_panel(req) {
765 let panel = req.panel;
766 if !panel.is_map() {
767 panel = panel_from_node(req);
768 }
769 if !panel.is_map() {
770 return ok({
771 id: 4,
772 type: "panel_rejected",
773 reason: "invalid panel"
774 });
775 }
776 panel.id = 4;
777 panel.idx = req.idx;
778 if !panel.contains("type") {
779 panel.type = "panel";
780 }
781 if panel.contains("hotspots") {
782 panel.hotspots = public_hotspots(req.idx, panel.panel_id, panel.hotspots);
783 }
784 root::send_idx("local/ws", req.idx, panel);
785 ok({
786 id: 4,
787 type: "panel",
788 panel_id: panel.panel_id
789 })
790 }
791 "#
792 .to_vec(),
793 )?;
794
795 let compiled = vm.get_fn("vm_send_panel_public_hotspots::send_panel", &[Type::Any])?;
796 assert_eq!(compiled.ret_ty(), &Type::Any);
797 let send_panel: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
798 let req = dynamic::map!(
799 "idx"=> 7i64,
800 "panel"=> dynamic::map!(
801 "panel_id"=> "main",
802 "hotspots"=> dynamic::map!(
803 "open"=> dynamic::map!("id"=> "open")
804 )
805 )
806 );
807 let result = unsafe { &*send_panel(&req) };
808
809 assert_eq!(result.get_dynamic("type").map(|value| value.as_str().to_string()), Some("panel".to_string()));
810 assert_eq!(result.get_dynamic("panel_id").map(|value| value.as_str().to_string()), Some("main".to_string()));
811 Ok(())
812 }
813
814 #[test]
815 fn dynamic_field_value_participates_in_or_expression() -> anyhow::Result<()> {
816 let vm = Vm::with_all()?;
817 vm.import_code(
818 "vm_dynamic_field_or",
819 r#"
820 pub fn next_or_start() {
821 let choice = {
822 label: "颜色",
823 next: "color"
824 };
825 choice.next || "start"
826 }
827
828 pub fn direct_next() {
829 let choice = {
830 label: "颜色",
831 next: "color"
832 };
833 choice.next
834 }
835
836 pub fn bracket_next() {
837 let choice = {
838 label: "颜色",
839 next: "color"
840 };
841 choice["next"]
842 }
843
844 pub fn assigned_preview() {
845 let choice = {
846 next: "tax_free"
847 };
848 choice.preview = choice.next || "start";
849 choice
850 }
851 "#
852 .as_bytes()
853 .to_vec(),
854 )?;
855
856 let compiled = vm.get_fn("vm_dynamic_field_or::direct_next", &[])?;
857 assert_eq!(compiled.ret_ty(), &Type::Any);
858 let direct_next: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
859 assert_eq!(unsafe { &*direct_next() }.as_str(), "color");
860
861 let compiled = vm.get_fn("vm_dynamic_field_or::bracket_next", &[])?;
862 assert_eq!(compiled.ret_ty(), &Type::Any);
863 let bracket_next: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
864 assert_eq!(unsafe { &*bracket_next() }.as_str(), "color");
865
866 let compiled = vm.get_fn("vm_dynamic_field_or::next_or_start", &[])?;
867 assert_eq!(compiled.ret_ty(), &Type::Any);
868 let next_or_start: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
869 assert_eq!(unsafe { &*next_or_start() }.as_str(), "color");
870
871 let compiled = vm.get_fn("vm_dynamic_field_or::assigned_preview", &[])?;
872 assert_eq!(compiled.ret_ty(), &Type::Any);
873 let assigned_preview: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
874 let choice = unsafe { &*assigned_preview() };
875 assert_eq!(choice.get_dynamic("preview").unwrap().as_str(), "tax_free");
876 Ok(())
877 }
878
879 #[test]
880 fn empty_object_literal_in_if_branch_stays_dynamic() -> anyhow::Result<()> {
881 let vm = Vm::with_all()?;
882 vm.import_code(
883 "vm_if_empty_object_branch",
884 r#"
885 pub fn first_note(steps) {
886 let first = if steps.len() > 0 { steps[0] } else { {} };
887 let first_note = first.note || "fallback";
888 first_note
889 }
890
891 pub fn first_ja(steps) {
892 let first = if steps.len() > 0 { steps[0] } else { {} };
893 first.ja || "すみません"
894 }
895
896 pub fn assign_first_note(steps) {
897 let first = {};
898 first = if steps.len() > 0 { steps[0] } else { {} };
899 first.note || "fallback"
900 }
901 "#
902 .as_bytes()
903 .to_vec(),
904 )?;
905
906 let compiled = vm.get_fn("vm_if_empty_object_branch::first_note", &[Type::Any])?;
907 assert_eq!(compiled.ret_ty(), &Type::Any);
908 let first_note: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
909
910 let empty_steps = Dynamic::list(Vec::new());
911 assert_eq!(unsafe { &*first_note(&empty_steps) }.as_str(), "fallback");
912
913 let mut step = std::collections::BTreeMap::new();
914 step.insert("note".into(), "hello".into());
915 let steps = Dynamic::list(vec![Dynamic::map(step)]);
916 assert_eq!(unsafe { &*first_note(&steps) }.as_str(), "hello");
917
918 let compiled = vm.get_fn("vm_if_empty_object_branch::first_ja", &[Type::Any])?;
919 assert_eq!(compiled.ret_ty(), &Type::Any);
920 let first_ja: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
921 assert_eq!(unsafe { &*first_ja(&empty_steps) }.as_str(), "すみません");
922
923 let compiled = vm.get_fn("vm_if_empty_object_branch::assign_first_note", &[Type::Any])?;
924 assert_eq!(compiled.ret_ty(), &Type::Any);
925 let assign_first_note: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
926 assert_eq!(unsafe { &*assign_first_note(&empty_steps) }.as_str(), "fallback");
927 assert_eq!(unsafe { &*assign_first_note(&steps) }.as_str(), "hello");
928 Ok(())
929 }
930
931 #[test]
932 fn list_literal_can_be_function_tail_expression() -> anyhow::Result<()> {
933 let vm = Vm::with_all()?;
934 vm.import_code(
935 "vm_tail_list_literal",
936 r#"
937 pub fn numbers() {
938 [1, 2, 3]
939 }
940
941 pub fn maps() {
942 [
943 {note: "first"},
944 {note: "second"}
945 ]
946 }
947
948 pub fn object_with_maps() {
949 {
950 steps: [
951 {note: "first"},
952 {note: "second"}
953 ]
954 }
955 }
956
957 pub fn return_maps() {
958 return [
959 {note: "first"},
960 {note: "second"}
961 ];
962 }
963
964 pub fn return_maps_without_semicolon() {
965 return [
966 {note: "first"},
967 {note: "second"}
968 ]
969 }
970
971 pub fn tail_bare_variable() {
972 let value = [
973 {note: "first"},
974 {note: "second"}
975 ];
976 value
977 }
978
979 pub fn return_bare_variable_without_semicolon() {
980 let value = [
981 {note: "first"},
982 {note: "second"}
983 ];
984 return value
985 }
986
987 pub fn tail_object_variable() {
988 let result = {
989 steps: [
990 {note: "first"},
991 {note: "second"}
992 ]
993 };
994 result
995 }
996 "#
997 .as_bytes()
998 .to_vec(),
999 )?;
1000
1001 let compiled = vm.get_fn("vm_tail_list_literal::numbers", &[])?;
1002 assert_eq!(compiled.ret_ty(), &Type::Any);
1003 let numbers: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1004 let result = unsafe { &*numbers() };
1005 assert_eq!(result.len(), 3);
1006 assert_eq!(result.get_idx(1).and_then(|value| value.as_int()), Some(2));
1007
1008 let compiled = vm.get_fn("vm_tail_list_literal::maps", &[])?;
1009 assert_eq!(compiled.ret_ty(), &Type::Any);
1010 let maps: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1011 let result = unsafe { &*maps() };
1012 assert_eq!(result.len(), 2);
1013 assert_eq!(result.get_idx(1).and_then(|value| value.get_dynamic("note")).map(|value| value.as_str().to_string()), Some("second".to_string()));
1014
1015 let compiled = vm.get_fn("vm_tail_list_literal::object_with_maps", &[])?;
1016 assert_eq!(compiled.ret_ty(), &Type::Any);
1017 let object_with_maps: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1018 let result = unsafe { &*object_with_maps() };
1019 let steps = result.get_dynamic("steps").expect("steps");
1020 assert_eq!(steps.len(), 2);
1021 assert_eq!(steps.get_idx(0).and_then(|value| value.get_dynamic("note")).map(|value| value.as_str().to_string()), Some("first".to_string()));
1022
1023 let compiled = vm.get_fn("vm_tail_list_literal::return_maps", &[])?;
1024 assert_eq!(compiled.ret_ty(), &Type::Any);
1025 let return_maps: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1026 let result = unsafe { &*return_maps() };
1027 assert_eq!(result.len(), 2);
1028 assert_eq!(result.get_idx(1).and_then(|value| value.get_dynamic("note")).map(|value| value.as_str().to_string()), Some("second".to_string()));
1029
1030 let compiled = vm.get_fn("vm_tail_list_literal::return_maps_without_semicolon", &[])?;
1031 assert_eq!(compiled.ret_ty(), &Type::Any);
1032 let return_maps_without_semicolon: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1033 let result = unsafe { &*return_maps_without_semicolon() };
1034 assert_eq!(result.len(), 2);
1035 assert_eq!(result.get_idx(0).and_then(|value| value.get_dynamic("note")).map(|value| value.as_str().to_string()), Some("first".to_string()));
1036
1037 let compiled = vm.get_fn("vm_tail_list_literal::tail_bare_variable", &[])?;
1038 assert_eq!(compiled.ret_ty(), &Type::Any);
1039 let tail_bare_variable: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1040 let result = unsafe { &*tail_bare_variable() };
1041 assert_eq!(result.len(), 2);
1042 assert_eq!(result.get_idx(1).and_then(|value| value.get_dynamic("note")).map(|value| value.as_str().to_string()), Some("second".to_string()));
1043
1044 let compiled = vm.get_fn("vm_tail_list_literal::return_bare_variable_without_semicolon", &[])?;
1045 assert_eq!(compiled.ret_ty(), &Type::Any);
1046 let return_bare_variable_without_semicolon: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1047 let result = unsafe { &*return_bare_variable_without_semicolon() };
1048 assert_eq!(result.len(), 2);
1049 assert_eq!(result.get_idx(0).and_then(|value| value.get_dynamic("note")).map(|value| value.as_str().to_string()), Some("first".to_string()));
1050
1051 let compiled = vm.get_fn("vm_tail_list_literal::tail_object_variable", &[])?;
1052 assert_eq!(compiled.ret_ty(), &Type::Any);
1053 let tail_object_variable: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1054 let result = unsafe { &*tail_object_variable() };
1055 let steps = result.get_dynamic("steps").expect("steps");
1056 assert_eq!(steps.len(), 2);
1057 assert_eq!(steps.get_idx(1).and_then(|value| value.get_dynamic("note")).map(|value| value.as_str().to_string()), Some("second".to_string()));
1058 Ok(())
1059 }
1060
1061 #[test]
1062 fn repeated_deep_step_literals_import_successfully() -> anyhow::Result<()> {
1063 fn extra_page_literal(depth: usize) -> String {
1064 let mut value = "{leaf: \"done\"}".to_string();
1065 for idx in 0..depth {
1066 value = format!("{{kind: \"page\", idx: {idx}, children: [{value}], meta: {{title: \"extra\", visible: true}}}}");
1067 }
1068 value
1069 }
1070
1071 let extra = extra_page_literal(48);
1072 let code = format!(
1073 r#"
1074 pub fn script() {{
1075 return [
1076 {{ja: "一つ目", note: "first", extra: {extra}}},
1077 {{ja: "二つ目", note: "second", extra: {extra}}},
1078 {{ja: "三つ目", note: "third", extra: {extra}}}
1079 ]
1080 }}
1081 "#
1082 );
1083
1084 let vm = Vm::with_all()?;
1085 vm.import_code("vm_repeated_deep_step_literals", code.into_bytes())?;
1086 let compiled = vm.get_fn("vm_repeated_deep_step_literals::script", &[])?;
1087 assert_eq!(compiled.ret_ty(), &Type::Any);
1088 let script: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1089 let result = unsafe { &*script() };
1090 assert_eq!(result.len(), 3);
1091 assert_eq!(result.get_idx(2).and_then(|value| value.get_dynamic("note")).map(|value| value.as_str().to_string()), Some("third".to_string()));
1092 Ok(())
1093 }
1094
1095 #[test]
1096 fn native_import_uses_owning_vm() -> anyhow::Result<()> {
1097 let module_path = std::env::temp_dir().join(format!("zust_vm_import_owner_{}.zs", std::process::id()));
1098 std::fs::write(&module_path, "pub fn value() { 41 }")?;
1099 let module_path = module_path.to_string_lossy().replace('\\', "\\\\").replace('"', "\\\"");
1100
1101 let vm1 = Vm::with_all()?;
1102 vm1.import_code(
1103 "vm_import_owner",
1104 format!(
1105 r#"
1106 pub fn run() {{
1107 import("vm_imported_owner", "{module_path}");
1108 }}
1109 "#
1110 )
1111 .into_bytes(),
1112 )?;
1113 let compiled = vm1.get_fn("vm_import_owner::run", &[])?;
1114
1115 let vm2 = Vm::with_all()?;
1116 vm2.import_code("vm_import_other", b"pub fn run() { 0 }".to_vec())?;
1117 let _ = vm2.get_fn("vm_import_other::run", &[])?;
1118
1119 let run: extern "C" fn() = unsafe { std::mem::transmute(compiled.ptr()) };
1120 run();
1121
1122 assert!(vm1.get_fn("vm_imported_owner::value", &[]).is_ok());
1123 assert!(vm2.get_fn("vm_imported_owner::value", &[]).is_err());
1124 Ok(())
1125 }
1126
1127 #[test]
1128 fn object_last_field_call_does_not_need_trailing_comma() -> anyhow::Result<()> {
1129 let vm = Vm::with_all()?;
1130 vm.import_code(
1131 "vm_object_last_call_field",
1132 r#"
1133 pub fn extra_page() {
1134 {
1135 title: "extra",
1136 pages: [
1137 {note: "nested"}
1138 ]
1139 }
1140 }
1141
1142 pub fn data() {
1143 return [
1144 {
1145 note: "first",
1146 choices: ["a", "b"],
1147 extras: extra_page()
1148 },
1149 {
1150 note: "second",
1151 choices: ["c"],
1152 extras: extra_page()
1153 }
1154 ]
1155 }
1156 "#
1157 .as_bytes()
1158 .to_vec(),
1159 )?;
1160
1161 let compiled = vm.get_fn("vm_object_last_call_field::data", &[])?;
1162 assert_eq!(compiled.ret_ty(), &Type::Any);
1163 let data: extern "C" fn() -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1164 let result = unsafe { &*data() };
1165 assert_eq!(result.len(), 2);
1166 let first = result.get_idx(0).expect("first step");
1167 assert_eq!(first.get_dynamic("extras").and_then(|extras| extras.get_dynamic("title")).map(|title| title.as_str().to_string()), Some("extra".to_string()));
1168 Ok(())
1169 }
1170
1171 #[test]
1172 fn gpu_struct_layout_packs_and_unpacks_dynamic_maps() -> anyhow::Result<()> {
1173 let vm = Vm::with_all()?;
1174 vm.import_code(
1175 "vm_gpu_layout",
1176 br#"
1177 pub struct Params {
1178 a: u32,
1179 b: u32,
1180 c: u32,
1181 }
1182 "#
1183 .to_vec(),
1184 )?;
1185
1186 let layout = vm.gpu_struct_layout("vm_gpu_layout::Params", &[])?;
1187 assert_eq!(layout.size, 16);
1188 assert_eq!(layout.fields.iter().map(|field| (field.name.as_str(), field.offset)).collect::<Vec<_>>(), vec![("a", 0), ("b", 4), ("c", 8)]);
1189
1190 let value = dynamic::map!("a"=> 1u32, "b"=> 2u32, "c"=> 3u32);
1191 let bytes = layout.pack_map(&value)?;
1192 assert_eq!(bytes.len(), 16);
1193 assert_eq!(&bytes[0..4], &1u32.to_ne_bytes());
1194 assert_eq!(&bytes[4..8], &2u32.to_ne_bytes());
1195 assert_eq!(&bytes[8..12], &3u32.to_ne_bytes());
1196
1197 let read = layout.unpack_map(&bytes)?;
1198 assert_eq!(read.get_dynamic("a").and_then(|value| value.as_uint()), Some(1));
1199 assert_eq!(read.get_dynamic("b").and_then(|value| value.as_uint()), Some(2));
1200 assert_eq!(read.get_dynamic("c").and_then(|value| value.as_uint()), Some(3));
1201 Ok(())
1202 }
1203
1204 #[test]
1205 fn root_native_calls_do_not_take_ownership_of_dynamic_args() -> anyhow::Result<()> {
1206 let vm = Vm::with_all()?;
1207 vm.import_code(
1208 "vm_root_clone_bridge",
1209 br#"
1210 pub fn add_then_reuse(arg) {
1211 let user = {
1212 address: "test-wallet",
1213 points: 20
1214 };
1215 root::add("local/root-clone-bridge-user", user);
1216 user.points = user.points - 7;
1217 root::add("local/root-clone-bridge-user", user);
1218 {
1219 user: user,
1220 points: user.points
1221 }
1222 }
1223 "#
1224 .to_vec(),
1225 )?;
1226
1227 let compiled = vm.get_fn("vm_root_clone_bridge::add_then_reuse", &[Type::Any])?;
1228 assert_eq!(compiled.ret_ty(), &Type::Any);
1229 let add_then_reuse: extern "C" fn(*const Dynamic) -> *const Dynamic = unsafe { std::mem::transmute(compiled.ptr()) };
1230 let arg = Dynamic::Null;
1231 let result = add_then_reuse(&arg);
1232 let result = unsafe { &*result };
1233
1234 assert_eq!(result.get_dynamic("points").and_then(|value| value.as_int()), Some(13));
1235 let mut json = String::new();
1236 result.to_json(&mut json);
1237 assert!(json.contains("\"points\": 13"));
1238 Ok(())
1239 }
1240}