1use anyhow::Result;
2use core::panic;
3use heck::{ToShoutySnakeCase, ToUpperCamelCase};
4use std::{
5 collections::{HashMap, HashSet},
6 fmt::Write,
7 mem,
8 ops::Deref,
9};
10use wit_bindgen_core::{
11 AsyncFilterSet, Direction, Files, InterfaceGenerator as CoreInterfaceGenerator, Ns, Source,
12 WorldGenerator,
13 abi::{self, AbiVariant, Bindgen, Bitcast, Instruction, LiftLower, WasmType},
14 uwrite, uwriteln,
15 wit_parser::{
16 Alignment, ArchitectureSize, Docs, Enum, Flags, FlagsRepr, Function, Int, InterfaceId,
17 LiftLowerAbi, LiveTypes, ManglingAndAbi, Param, Record, Resolve, ResourceIntrinsic,
18 Result_, SizeAlign, Tuple, Type, TypeDefKind, TypeId, Variant, WasmExport, WasmExportKind,
19 WasmImport, WorldId, WorldKey,
20 },
21};
22
23use crate::async_support::{AsyncFunctionState, AsyncSupport};
24use crate::pkg::{
25 ASYNC_CORE_DIR, Imports, MoonbitSignature, PkgResolver, ToMoonBitIdent, ToMoonBitTypeIdent,
26};
27
28mod async_support;
29mod ffi;
30mod pkg;
31
32const VERSION: &str = env!("CARGO_PKG_VERSION");
45
46#[derive(Default, Debug, Clone)]
47#[cfg_attr(feature = "clap", derive(clap::Parser))]
48pub struct Opts {
49 #[cfg_attr(feature = "clap", clap(flatten))]
50 pub derive: DeriveOpts,
51
52 #[cfg_attr(feature = "clap", arg(long, default_value_t = false))]
54 pub ignore_stub: bool,
55
56 #[cfg_attr(feature = "clap", arg(long, default_value_t = false))]
58 pub ignore_module_file: bool,
59
60 #[cfg_attr(feature = "clap", arg(long, default_value = "gen"))]
62 pub gen_dir: String,
63
64 #[cfg_attr(feature = "clap", arg(long, default_value = None))]
66 pub project_name: Option<String>,
67
68 #[cfg_attr(feature = "clap", clap(flatten))]
69 pub async_: AsyncFilterSet,
70}
71
72#[derive(Default, Debug, Clone)]
73#[cfg_attr(feature = "clap", derive(clap::Args))]
74pub struct DeriveOpts {
75 #[cfg_attr(feature = "clap", arg(long, default_value_t = false))]
77 pub derive_debug: bool,
78
79 #[cfg_attr(feature = "clap", arg(long, default_value_t = false))]
81 pub derive_show: bool,
82
83 #[cfg_attr(feature = "clap", arg(long, default_value_t = false))]
85 pub derive_eq: bool,
86
87 #[cfg_attr(feature = "clap", arg(long, default_value_t = false))]
89 pub derive_error: bool,
90}
91
92impl Opts {
93 pub fn build(&self) -> Box<dyn WorldGenerator> {
94 Box::new(MoonBit {
95 opts: self.clone(),
96 ..MoonBit::default()
97 })
98 }
99}
100
101#[derive(Default)]
102struct InterfaceFragment {
103 src: String,
104 ffi: String,
105 builtins: HashSet<&'static str>,
106}
107
108impl InterfaceFragment {
109 fn concat(&mut self, other: Self) {
110 self.src.push_str(&other.src);
111 self.ffi.push_str(&other.ffi);
112 self.builtins.extend(other.builtins);
113 }
114}
115
116#[derive(Default)]
117pub struct MoonBit {
118 opts: Opts,
119 project_name: String,
120 import_world_fragment: InterfaceFragment,
121 sizes: SizeAlign,
122
123 interface_ns: Ns,
126 pkg_resolver: PkgResolver,
128 export: HashMap<String, (String, String)>,
130
131 export_ns: Ns,
132
133 async_support: AsyncSupport,
134}
135
136impl MoonBit {
137 fn interface<'a>(
138 &'a mut self,
139 resolve: &'a Resolve,
140 name: &'a str,
141 direction: Direction,
142 interface: Option<&'a WorldKey>,
143 ) -> InterfaceGenerator<'a> {
144 let derive_opts = self.opts.derive.clone();
145 InterfaceGenerator {
146 src: String::new(),
147 ffi: String::new(),
148 world_gen: self,
149 resolve,
150 name,
151 direction,
152 ffi_imports: HashSet::new(),
153 derive_opts,
154 interface,
155 }
156 }
157
158 fn write_moon_pkg(&self, moon_pkg: &mut Source, imports: Option<&Imports>, link: bool) {
159 moon_pkg.push_str("{\n\"warn-list\": \"-44\"");
161 if let Some(imports) = imports {
163 moon_pkg.push_str(",\n\"import\": [\n");
164 moon_pkg.indent(1);
165 let mut deps = imports
166 .packages
167 .iter()
168 .map(|(k, v)| {
169 format!(
170 "{{ \"path\" : \"{}/{}\", \"alias\" : \"{}\" }}",
171 self.project_name,
172 k.replace(".", "/"),
173 v
174 )
175 })
176 .collect::<Vec<_>>();
177 deps.sort();
178 uwrite!(moon_pkg, "{}", deps.join(",\n"));
179 moon_pkg.deindent(1);
180 moon_pkg.push_str("\n]");
181 }
182 let imports_async_core =
183 imports.is_some_and(|imports| imports.packages.contains_key(ASYNC_CORE_DIR));
184 if imports_async_core || (link && self.async_support.is_required()) {
185 moon_pkg.push_str(",\n\"supported-targets\": \"+wasm\"");
186 }
187 if link {
189 let memory_name = self.pkg_resolver.resolve.wasm_export_name(
190 ManglingAndAbi::Legacy(LiftLowerAbi::Sync),
191 WasmExport::Memory,
192 );
193 moon_pkg.push_str(",\n\"link\": {\n\"wasm\": {\n");
194 moon_pkg.push_str(&format!("\"export-memory-name\": \"{memory_name}\",\n"));
195 moon_pkg.push_str("\"heap-start-address\": 16,\n");
196 moon_pkg.push_str("\"exports\": [\n");
197 moon_pkg.indent(1);
198 let mut exports = self
199 .export
200 .iter()
201 .map(|(export_name, (func_name, _))| format!("\"{func_name}:{export_name}\""))
202 .collect::<Vec<_>>();
203 exports.push(format!(
204 "\"mbt_ffi_cabi_realloc:{}\"",
205 self.pkg_resolver.resolve.wasm_export_name(
206 ManglingAndAbi::Legacy(LiftLowerAbi::Sync),
207 WasmExport::Realloc,
208 ),
209 ));
210 exports.sort();
211 uwrite!(moon_pkg, "{}", exports.join(",\n"));
212 moon_pkg.deindent(1);
213 moon_pkg.push_str("\n]\n}\n}\n");
214 }
215 moon_pkg.push_str("\n}\n");
216 }
217}
218
219impl WorldGenerator for MoonBit {
245 fn preprocess(&mut self, resolve: &Resolve, world: WorldId) -> Result<()> {
246 if world_contains_endpoint_fixed_length_list_combination(resolve, world) {
247 anyhow::bail!(
248 "MoonBit async bindings do not yet support combining future or stream types with fixed-length lists"
249 );
250 }
251 if world_contains_future_or_stream(resolve, world) {
252 self.async_support.require_runtime();
253 }
254
255 self.pkg_resolver.resolve = resolve.clone();
256 self.project_name = self
257 .opts
258 .project_name
259 .clone()
260 .or(resolve.worlds[world].package.map(|id| {
261 let package = &resolve.packages[id].name;
262 format!("{}/{}", package.namespace, package.name)
263 }))
264 .unwrap_or("generated".into());
265 self.sizes.fill(resolve);
266 Ok(())
267 }
268
269 fn import_interface(
270 &mut self,
271 resolve: &Resolve,
272 key: &WorldKey,
273 id: InterfaceId,
274 files: &mut Files,
275 ) -> Result<()> {
276 let name = PkgResolver::interface_name(resolve, key);
277 let name = self.interface_ns.tmp(&name);
278 self.pkg_resolver
279 .import_interface_names
280 .insert(id, name.clone());
281
282 let mut r#gen = self.interface(resolve, &name, Direction::Import, Some(key));
283 r#gen.types(id);
284
285 for (_, func) in resolve.interfaces[id].functions.iter() {
286 r#gen.import(func);
287 }
288
289 let fragment = r#gen.finish();
290 {
292 let directory = name.replace('.', "/");
293
294 if let Some(content) = &resolve.interfaces[id].docs.contents
296 && !content.is_empty()
297 {
298 files.push(&format!("{directory}/README.md"), content.as_bytes());
299 }
300
301 let mut src = Source::default();
303 wit_bindgen_core::generated_preamble(&mut src, VERSION);
304 uwriteln!(src, "{}", fragment.src);
305 files.push(&format!("{directory}/top.mbt"), indent(&src).as_bytes());
306
307 let mut ffi = Source::default();
309 wit_bindgen_core::generated_preamble(&mut ffi, VERSION);
310 uwriteln!(ffi, "{}", fragment.ffi);
311 for builtin in fragment.builtins {
312 uwriteln!(ffi, "{}", builtin);
313 }
314 files.push(&format!("{directory}/ffi.mbt"), indent(&ffi).as_bytes());
315
316 let mut moon_pkg = Source::default();
318 self.write_moon_pkg(
319 &mut moon_pkg,
320 self.pkg_resolver.package_import.get(&name),
321 false,
322 );
323 files.push(&format!("{directory}/moon.pkg.json"), moon_pkg.as_bytes());
324 }
325
326 Ok(())
327 }
328
329 fn import_funcs(
330 &mut self,
331 resolve: &Resolve,
332 world: WorldId,
333 funcs: &[(&str, &Function)],
334 _files: &mut Files,
335 ) {
336 let name = PkgResolver::world_name(resolve, world);
337 let mut r#gen = self.interface(resolve, &name, Direction::Import, None);
338
339 for (_, func) in funcs {
340 r#gen.import(func);
341 }
342
343 let result = r#gen.finish();
344 self.import_world_fragment.concat(result);
345 }
346
347 fn import_types(
348 &mut self,
349 resolve: &Resolve,
350 world: WorldId,
351 types: &[(&str, TypeId)],
352 _files: &mut Files,
353 ) {
354 let name = PkgResolver::world_name(resolve, world);
355 let mut r#gen = self.interface(resolve, &name, Direction::Import, None);
356
357 for (ty_name, ty) in types {
358 r#gen.define_type(ty_name, *ty);
359 }
360
361 let result = r#gen.finish();
362 self.import_world_fragment.concat(result);
363 }
364
365 fn finish_imports(&mut self, resolve: &Resolve, world: WorldId, files: &mut Files) {
366 let name = PkgResolver::world_name(resolve, world);
367 let directory = name.replace('.', "/");
368
369 if let Some(content) = &resolve.worlds[world].docs.contents
371 && !content.is_empty()
372 {
373 files.push(&format!("{directory}/README.md"), content.as_bytes());
374 }
375 let mut src = Source::default();
377 wit_bindgen_core::generated_preamble(&mut src, VERSION);
378 uwriteln!(src, "{}", self.import_world_fragment.src);
379 files.push(&format!("{directory}/import.mbt"), indent(&src).as_bytes());
380 let mut ffi = Source::default();
382 let mut builtins: HashSet<&'static str> = HashSet::new();
383 wit_bindgen_core::generated_preamble(&mut ffi, VERSION);
384 uwriteln!(ffi, "{}", self.import_world_fragment.ffi);
385 builtins.extend(self.import_world_fragment.builtins.iter());
386 for b in builtins.iter() {
387 uwriteln!(ffi, "{}", b);
388 }
389 files.push(
390 &format!("{directory}/ffi_import.mbt"),
391 indent(&ffi).as_bytes(),
392 );
393 let mut moon_pkg = Source::default();
395 self.write_moon_pkg(
396 &mut moon_pkg,
397 self.pkg_resolver.package_import.get(&name),
398 false,
399 );
400 files.push(&format!("{directory}/moon.pkg.json"), moon_pkg.as_bytes());
401 }
402
403 fn export_interface(
404 &mut self,
405 resolve: &Resolve,
406 key: &WorldKey,
407 id: InterfaceId,
408 files: &mut Files,
409 ) -> Result<()> {
410 let name = format!(
411 "{}.{}",
412 self.opts.r#gen_dir,
413 PkgResolver::interface_name(resolve, key)
414 );
415 let name = self.interface_ns.tmp(&name);
416 self.pkg_resolver
417 .export_interface_names
418 .insert(id, name.clone());
419
420 let mut r#gen = self.interface(resolve, &name, Direction::Export, Some(key));
421 r#gen.types(id);
422
423 for (_, func) in resolve.interfaces[id].functions.iter() {
424 r#gen.export(func);
425 }
426
427 let fragment = r#gen.finish();
428
429 {
431 let directory = name.replace('.', "/");
432
433 if let Some(content) = &resolve.interfaces[id].docs.contents
435 && !content.is_empty()
436 {
437 files.push(
438 &format!("{}/README.md", name.replace(".", "/")),
439 content.as_bytes(),
440 );
441 }
442 let mut src = Source::default();
444 wit_bindgen_core::generated_preamble(&mut src, VERSION);
445 uwriteln!(src, "{}", fragment.src);
446 files.push(&format!("{directory}/top.mbt"), indent(&src).as_bytes());
447
448 if !self.opts.ignore_stub {
449 let mut moon_pkg = Source::default();
451 self.write_moon_pkg(
452 &mut moon_pkg,
453 self.pkg_resolver.package_import.get(&name),
454 false,
455 );
456 files.push(&format!("{directory}/moon.pkg.json"), moon_pkg.as_bytes());
457 }
458
459 let mut ffi = Source::default();
461 wit_bindgen_core::generated_preamble(&mut ffi, VERSION);
462
463 uwriteln!(&mut ffi, "{}", fragment.ffi);
464 for b in fragment.builtins.iter() {
465 uwriteln!(ffi, "{}", b);
466 }
467 files.push(&format!("{directory}/ffi.mbt",), indent(&ffi).as_bytes());
468 }
469
470 Ok(())
471 }
472
473 fn export_funcs(
474 &mut self,
475 resolve: &Resolve,
476 world: WorldId,
477 funcs: &[(&str, &Function)],
478 files: &mut Files,
479 ) -> Result<()> {
480 let name = format!(
481 "{}.{}",
482 self.opts.r#gen_dir,
483 PkgResolver::world_name(resolve, world)
484 );
485 let mut r#gen = self.interface(resolve, &name, Direction::Export, None);
486
487 for (_, func) in funcs {
488 r#gen.export(func);
489 }
490
491 let fragment = r#gen.finish();
492
493 {
495 let directory = name.replace('.', "/");
496 let mut src = Source::default();
498 wit_bindgen_core::generated_preamble(&mut src, VERSION);
499 uwriteln!(src, "{}", fragment.src);
500 files.push(&format!("{directory}/top.mbt"), indent(&src).as_bytes());
501
502 if !self.opts.ignore_stub {
503 let mut moon_pkg = Source::default();
505 self.write_moon_pkg(
506 &mut moon_pkg,
507 self.pkg_resolver.package_import.get(&name),
508 false,
509 );
510 files.push(&format!("{directory}/moon.pkg.json"), moon_pkg.as_bytes());
511 }
512
513 let mut export = Source::default();
515 wit_bindgen_core::generated_preamble(&mut export, VERSION);
516 uwriteln!(&mut export, "{}", fragment.ffi);
517 for b in fragment.builtins.iter() {
518 uwriteln!(&mut export, "{}", b);
519 }
520 files.push(&format!("{directory}/ffi.mbt",), indent(&export).as_bytes());
521 }
522
523 Ok(())
524 }
525
526 fn finish(&mut self, _resolve: &Resolve, _id: WorldId, files: &mut Files) -> Result<()> {
527 self.async_support.emit_runtime_files(files, VERSION);
529
530 if !self.opts.ignore_stub && !self.opts.ignore_module_file {
532 let mut body = Source::default();
533 uwriteln!(
534 &mut body,
535 "{{ \"name\": \"{}\", \"preferred-target\": \"wasm\" }}",
536 self.project_name
537 );
538 files.push("moon.mod.json", body.as_bytes());
539 }
540
541 let mut body = Source::default();
543 wit_bindgen_core::generated_preamble(&mut body, VERSION);
544 for builtin in [ffi::CABI_REALLOC, ffi::MALLOC, ffi::FREE] {
546 uwriteln!(&mut body, "{}", builtin);
547 }
548 for (_, (_, impl_)) in self.export.iter() {
550 uwriteln!(&mut body, "{impl_}");
551 }
552
553 files.push(
554 &format!("{}/ffi.mbt", self.opts.r#gen_dir),
555 indent(&body).as_bytes(),
556 );
557
558 let mut moon_pkg = Source::default();
559 self.write_moon_pkg(
560 &mut moon_pkg,
561 self.pkg_resolver.package_import.get(&self.opts.r#gen_dir),
562 true,
563 );
564 files.push(
565 &format!("{}/moon.pkg.json", self.opts.r#gen_dir),
566 indent(&moon_pkg).as_bytes(),
567 );
568
569 Ok(())
570 }
571}
572
573struct InterfaceGenerator<'a> {
574 src: String,
575 ffi: String,
576 ffi_imports: HashSet<&'static str>,
578
579 world_gen: &'a mut MoonBit,
580 resolve: &'a Resolve,
581 name: &'a str,
583 direction: Direction,
584 interface: Option<&'a WorldKey>,
585
586 derive_opts: DeriveOpts,
588}
589
590impl InterfaceGenerator<'_> {
591 fn finish(self) -> InterfaceFragment {
592 InterfaceFragment {
593 src: self.src,
594 ffi: self.ffi,
595 builtins: self.ffi_imports,
596 }
597 }
598
599 fn import(&mut self, func: &Function) {
600 let async_plan = self.world_gen.async_support.import_plan(
601 &mut self.world_gen.opts.async_,
602 self.resolve,
603 self.interface,
604 func,
605 );
606 let variant = async_plan.abi_variant();
607 let endpoint_plan = self.import_async_function_plan(self.interface, func);
608 let wasm_sig = self.resolve.wasm_signature(variant, func);
609 let mbt_sig = self.world_gen.pkg_resolver.mbt_sig(self.name, func, false);
610 let (src, needs_cleanup_list, endpoint_state) = if async_plan.is_async() {
611 let body = self.generate_async_import_body(&endpoint_plan, func, &mbt_sig, &wasm_sig);
612 (body.src, body.needs_cleanup_list, body.state)
613 } else {
614 let mut bindgen = FunctionBindgen::new(
615 self,
616 func.params
617 .iter()
618 .map(|Param { name, .. }| name.to_moonbit_ident())
619 .collect(),
620 )
621 .with_async_state(endpoint_plan.state());
622 if endpoint_plan.has_endpoints() {
623 bindgen = bindgen.with_sync_import_commit(
624 func.params.iter().map(|Param { ty, .. }| *ty).collect(),
625 );
626 }
627
628 abi::call(
629 bindgen.interface_gen.resolve,
630 AbiVariant::GuestImport,
631 LiftLower::LowerArgsLiftResults,
632 func,
633 &mut bindgen,
634 false,
635 );
636 (bindgen.src, bindgen.needs_cleanup_list, bindgen.async_state)
637 };
638
639 let cleanup_list = if needs_cleanup_list {
640 "let cleanup_list : Array[Int] = []"
641 } else {
642 ""
643 };
644
645 let (import_module, import_name) = self.resolve.wasm_import_name(
646 async_plan.mangling_and_abi(),
647 WasmImport::Func {
648 interface: self.interface,
649 func,
650 },
651 );
652 let result_type = match &wasm_sig.results[..] {
653 [] => "".into(),
654 [result] => format!("-> {}", wasm_type(*result)),
655 _ => unimplemented!("multi-value results are not supported yet"),
656 };
657 let params = wasm_sig
658 .params
659 .iter()
660 .enumerate()
661 .map(|(i, param)| format!("p{i} : {}", wasm_type(*param)))
662 .collect::<Vec<_>>()
663 .join(", ");
664 let ffi_import_name = format!("wasmImport{}", func.name.to_upper_camel_case());
665 uwriteln!(
666 self.ffi,
667 r#"
668 fn {ffi_import_name}({params}) {result_type} = "{import_module}" "{import_name}"
669 "#
670 );
671
672 self.emit_future_stream_helpers(&endpoint_plan, &endpoint_state);
673
674 print_docs(&mut self.src, &func.docs);
675 let sig = self.sig_string(func, &mbt_sig, async_plan.signature_is_async());
676 uwrite!(
677 self.src,
678 r#"
679 {sig} {{
680 {cleanup_list}
681 {src}
682 }}
683 "#
684 );
685 }
686
687 fn export(&mut self, func: &Function) {
688 let async_plan = self.world_gen.async_support.export_plan(
689 &mut self.world_gen.opts.async_,
690 self.resolve,
691 self.interface,
692 func,
693 );
694 let variant = async_plan.abi_variant();
695 let sig = self.resolve.wasm_signature(variant, func);
696 let mbt_sig = self.world_gen.pkg_resolver.mbt_sig(self.name, func, false);
697 let func_sig = self.sig_string(func, &mbt_sig, async_plan.signature_is_async());
698
699 print_docs(&mut self.src, &func.docs);
700 uwrite!(
701 self.src,
702 r#"
703 declare {func_sig}
704 "#
705 );
706
707 let endpoint_plan = self.export_async_function_plan(self.interface, func);
708 let mut bindgen = FunctionBindgen::new(
709 self,
710 (0..sig.params.len()).map(|i| format!("p{i}")).collect(),
711 )
712 .with_async_state(endpoint_plan.state());
713
714 abi::call(
715 bindgen.interface_gen.resolve,
716 variant,
717 LiftLower::LiftArgsLowerResults,
718 func,
719 &mut bindgen,
720 async_plan.is_async(),
721 );
722
723 let cleanup_list = if bindgen.needs_cleanup_list {
724 "let cleanup_list : Array[Int] = []"
725 } else {
726 ""
727 };
728 let async_state = bindgen.async_state.clone();
729 let src = bindgen.src;
730
731 let result_type = match &sig.results[..] {
732 [] => "Unit",
733 [result] => wasm_type(*result),
734 _ => unreachable!(),
735 };
736
737 let camel_name = func.name.to_upper_camel_case();
738
739 let func_name = self
740 .world_gen
741 .export_ns
742 .tmp(&format!("wasmExport{camel_name}"));
743
744 let params = sig
745 .params
746 .iter()
747 .enumerate()
748 .map(|(i, param)| {
749 let ty = wasm_type(*param);
750 format!("p{i} : {ty}")
751 })
752 .collect::<Vec<_>>()
753 .join(", ");
754
755 let export_name = self.resolve.wasm_export_name(
756 async_plan.mangling_and_abi(),
757 WasmExport::Func {
758 interface: self.interface,
759 func,
760 kind: WasmExportKind::Normal,
761 },
762 );
763 self.emit_future_stream_helpers(&endpoint_plan, &async_state);
764
765 if !self.emit_async_export_wrapper(
766 &async_plan,
767 func,
768 &func_name,
769 ¶ms,
770 result_type,
771 cleanup_list,
772 &src,
773 ) {
774 uwrite!(
775 self.ffi,
776 r#"
777 #doc(hidden)
778 pub fn {func_name}({params}) -> {result_type} {{
779 {cleanup_list}
780 {src}
781 }}
782 "#,
783 );
784 }
785
786 let export = format!(
787 r#"
788 #doc(hidden)
789 pub fn {func_name}({params}) -> {result_type} {{
790 {}{func_name}({})
791 }}
792 "#,
793 self.world_gen
794 .pkg_resolver
795 .qualify_package(self.world_gen.opts.gen_dir.as_str(), self.name),
796 (0..sig.params.len())
797 .map(|i| format!("p{i}"))
798 .collect::<Vec<_>>()
799 .join(", "),
800 );
801
802 self.world_gen
803 .export
804 .insert(export_name, (func_name, export));
805
806 if !self.emit_async_export_callback(
807 &async_plan,
808 self.interface,
809 func,
810 &camel_name,
811 async_state,
812 ) && abi::guest_export_needs_post_return(self.resolve, func)
813 {
814 let params = sig
815 .results
816 .iter()
817 .enumerate()
818 .map(|(i, param)| {
819 let ty = wasm_type(*param);
820 format!("p{i} : {ty}")
821 })
822 .collect::<Vec<_>>()
823 .join(", ");
824
825 let mut bindgen = FunctionBindgen::new(
826 self,
827 (0..sig.results.len()).map(|i| format!("p{i}")).collect(),
828 );
829
830 abi::post_return(bindgen.interface_gen.resolve, func, &mut bindgen);
831
832 let src = bindgen.src;
833
834 let func_name = self
835 .world_gen
836 .export_ns
837 .tmp(&format!("wasmExport{camel_name}PostReturn"));
838
839 uwrite!(
840 self.ffi,
841 r#"
842 #doc(hidden)
843 pub fn {func_name}({params}) -> Unit {{
844 {src}
845 }}
846 "#
847 );
848 let export_name = self.resolve.wasm_export_name(
849 ManglingAndAbi::Legacy(LiftLowerAbi::Sync),
850 WasmExport::Func {
851 interface: self.interface,
852 func,
853 kind: WasmExportKind::PostReturn,
854 },
855 );
856 let export = format!(
857 r#"
858 #doc(hidden)
859 pub fn {func_name}({params}) -> Unit {{
860 {}{func_name}({})
861 }}
862 "#,
863 self.world_gen
864 .pkg_resolver
865 .qualify_package(self.world_gen.opts.gen_dir.as_str(), self.name),
866 (0..sig.results.len())
867 .map(|i| format!("p{i}"))
868 .collect::<Vec<_>>()
869 .join(", "),
870 );
871 self.world_gen
872 .export
873 .insert(export_name, (func_name, export));
874 }
875 }
876
877 fn sig_string(&mut self, func: &Function, sig: &MoonbitSignature, async_: bool) -> String {
878 let mut params = sig
879 .params
880 .iter()
881 .map(|(name, ty)| {
882 let ty = self.world_gen.pkg_resolver.type_name(self.name, ty);
883 format!("{name} : {ty}")
884 })
885 .collect::<Vec<_>>();
886
887 self.add_async_export_stub_parameter(func, async_, &mut params);
888
889 let params = params.join(", ");
890 let result_type = match &sig.result_type {
891 None => "Unit".into(),
892 Some(ty) => self.world_gen.pkg_resolver.type_name(self.name, ty),
893 };
894 format!(
895 "pub {}fn {}({params}) -> {}",
896 if async_ { "async " } else { "" },
897 sig.name,
898 result_type
899 )
900 }
901}
902
903impl<'a> wit_bindgen_core::InterfaceGenerator<'a> for InterfaceGenerator<'a> {
904 fn resolve(&self) -> &'a Resolve {
905 self.resolve
906 }
907
908 fn type_record(&mut self, _id: TypeId, name: &str, record: &Record, docs: &Docs) {
909 print_docs(&mut self.src, docs);
910
911 let name = name.to_moonbit_type_ident();
912
913 let parameters = record
914 .fields
915 .iter()
916 .map(|field| {
917 format!(
918 "{} : {}",
919 field.name.to_moonbit_ident(),
920 self.world_gen.pkg_resolver.type_name(self.name, &field.ty),
921 )
922 })
923 .collect::<Vec<_>>()
924 .join("; ");
925
926 let contains_endpoint = record
927 .fields
928 .iter()
929 .any(|field| type_contains_future_or_stream(self.resolve, &field.ty));
930 let mut deriviation: Vec<_> = Vec::new();
931 if self.derive_opts.derive_debug && !contains_endpoint {
932 deriviation.push("Debug")
933 }
934 if self.derive_opts.derive_show && !contains_endpoint {
935 deriviation.push("Show")
936 }
937 if self.derive_opts.derive_eq && !contains_endpoint {
938 deriviation.push("Eq")
939 }
940
941 let derivation = if deriviation.is_empty() {
942 String::new()
943 } else {
944 format!(" derive({})", deriviation.join(", "))
945 };
946
947 uwrite!(
948 self.src,
949 "
950 pub(all) struct {name} {{
951 {parameters}
952 }}{derivation}
953 "
954 );
955 }
956
957 fn type_resource(&mut self, id: TypeId, name: &str, docs: &Docs) {
958 print_docs(&mut self.src, docs);
959 let name = name.to_moonbit_type_ident();
960
961 let mut deriviation: Vec<_> = Vec::new();
962 if self.derive_opts.derive_debug {
963 deriviation.push("Debug")
964 }
965 if self.derive_opts.derive_show {
966 deriviation.push("Show")
967 }
968 if self.derive_opts.derive_eq {
969 deriviation.push("Eq")
970 }
971 let declaration = if self.derive_opts.derive_error && name.contains("Error") {
972 "suberror"
973 } else {
974 "struct"
975 };
976
977 uwrite!(
978 self.src,
979 r#"
980 pub(all) {declaration} {name}(Int) derive({})
981 "#,
982 deriviation.join(", "),
983 );
984
985 if self.direction == Direction::Import {
986 let (drop_module, drop_name) = self.resolve.wasm_import_name(
987 ManglingAndAbi::Legacy(LiftLowerAbi::Sync),
988 WasmImport::ResourceIntrinsic {
989 resource: id,
990 interface: self.interface,
991 intrinsic: ResourceIntrinsic::ImportedDrop,
992 },
993 );
994 uwrite!(
995 &mut self.src,
996 r#"
997 /// Drops a resource handle.
998 pub fn {name}::drop(self : {name}) -> Unit {{
999 let {name}(resource) = self
1000 wasmImportResourceDrop{name}(resource)
1001 }}
1002 "#,
1003 );
1004
1005 uwrite!(
1006 &mut self.ffi,
1007 r#"
1008 fn wasmImportResourceDrop{name}(resource : Int) = "{drop_module}" "{drop_name}"
1009 "#,
1010 )
1011 } else {
1012 let (drop_module, drop_name) = self.resolve.wasm_import_name(
1013 ManglingAndAbi::Legacy(LiftLowerAbi::Sync),
1014 WasmImport::ResourceIntrinsic {
1015 resource: id,
1016 interface: self.interface,
1017 intrinsic: ResourceIntrinsic::ExportedDrop,
1018 },
1019 );
1020 let (new_module, new_name) = self.resolve.wasm_import_name(
1021 ManglingAndAbi::Legacy(LiftLowerAbi::Sync),
1022 WasmImport::ResourceIntrinsic {
1023 resource: id,
1024 interface: self.interface,
1025 intrinsic: ResourceIntrinsic::ExportedNew,
1026 },
1027 );
1028 let (rep_module, rep_name) = self.resolve.wasm_import_name(
1029 ManglingAndAbi::Legacy(LiftLowerAbi::Sync),
1030 WasmImport::ResourceIntrinsic {
1031 resource: id,
1032 interface: self.interface,
1033 intrinsic: ResourceIntrinsic::ExportedRep,
1034 },
1035 );
1036 uwrite!(
1037 &mut self.src,
1038 r#"
1039 /// Creates a new resource with the given `rep` as its representation and returning the handle to this resource.
1040 pub fn {name}::new(rep : Int) -> {name} {{
1041 {name}::{name}(wasmExportResourceNew{name}(rep))
1042 }}
1043 fn wasmExportResourceNew{name}(rep : Int) -> Int = "{new_module}" "{new_name}"
1044
1045 /// Drops a resource handle.
1046 pub fn {name}::drop(self : Self) -> Unit {{
1047 let {name}(resource) = self
1048 wasmExportResourceDrop{name}(resource)
1049 }}
1050 fn wasmExportResourceDrop{name}(resource : Int) = "{drop_module}" "{drop_name}"
1051
1052 /// Gets the `Int` representation of the resource pointed to the given handle.
1053 pub fn {name}::rep(self : Self) -> Int {{
1054 let {name}(resource) = self
1055 wasmExportResourceRep{name}(resource)
1056 }}
1057 fn wasmExportResourceRep{name}(resource : Int) -> Int = "{rep_module}" "{rep_name}"
1058 "#,
1059 );
1060
1061 uwrite!(
1062 &mut self.src,
1063 r#"
1064 /// Destructor of the resource.
1065 declare pub fn {name}::dtor(_self : {name}) -> Unit
1066 "#
1067 );
1068
1069 let func_name = self
1070 .world_gen
1071 .export_ns
1072 .tmp(&format!("wasmExport{name}Dtor"));
1073
1074 uwrite!(
1075 self.ffi,
1076 r#"
1077 #doc(hidden)
1078 pub fn {func_name}(handle : Int) -> Unit {{
1079 {name}::dtor(handle)
1080 }}
1081 "#,
1082 );
1083
1084 let export_name = self.resolve.wasm_export_name(
1085 ManglingAndAbi::Legacy(LiftLowerAbi::Sync),
1086 WasmExport::ResourceDtor {
1087 interface: self.interface.unwrap(),
1088 resource: id,
1089 },
1090 );
1091
1092 let export = format!(
1093 r#"
1094 #doc(hidden)
1095 pub fn {func_name}(handle : Int) -> Unit {{
1096 {}{func_name}(handle)
1097 }}
1098 "#,
1099 self.world_gen
1100 .pkg_resolver
1101 .qualify_package(self.world_gen.opts.gen_dir.as_str(), self.name),
1102 );
1103 self.world_gen
1104 .export
1105 .insert(export_name, (func_name, export));
1106 }
1107 }
1108
1109 fn type_flags(&mut self, _id: TypeId, name: &str, flags: &Flags, docs: &Docs) {
1110 print_docs(&mut self.src, docs);
1111
1112 let name = name.to_moonbit_type_ident();
1113
1114 let ty = match flags.repr() {
1115 FlagsRepr::U8 => "Byte",
1116 FlagsRepr::U16 | FlagsRepr::U32(1) => "UInt",
1117 FlagsRepr::U32(2) => "UInt64",
1118 _ => unreachable!(), };
1120
1121 let cases = flags
1122 .flags
1123 .iter()
1124 .map(|flag| flag.name.to_shouty_snake_case())
1125 .collect::<Vec<_>>()
1126 .join("; ");
1127
1128 let map_to_int = flags
1129 .flags
1130 .iter()
1131 .enumerate()
1132 .map(|(i, flag)| {
1133 let flag_name = flag.name.to_shouty_snake_case();
1134 let suffix = if matches!(flags.repr(), FlagsRepr::U32(2)) {
1135 "UL"
1136 } else {
1137 "U"
1138 };
1139 let cast = if matches!(flags.repr(), FlagsRepr::U8) {
1140 ".to_byte()"
1141 } else {
1142 ""
1143 };
1144 format!("{flag_name} => ((1{suffix} << {i}){cast})")
1145 })
1146 .collect::<Vec<_>>()
1147 .join("\n ");
1148
1149 let mut deriviation: Vec<_> = Vec::new();
1150 if self.derive_opts.derive_debug {
1151 deriviation.push("Debug")
1152 }
1153 if self.derive_opts.derive_show {
1154 deriviation.push("Show")
1155 }
1156 if self.derive_opts.derive_eq {
1157 deriviation.push("Eq")
1158 }
1159 let declaration = if self.derive_opts.derive_error && name.contains("Error") {
1160 "suberror"
1161 } else {
1162 "struct"
1163 };
1164
1165 uwrite!(
1166 self.src,
1167 "
1168 pub(all) {declaration} {name}({ty}) derive({})
1169 pub fn {name}::default() -> {name} {{
1170 {}
1171 }}
1172 pub(all) enum {name}Flag {{
1173 {cases}
1174 }}
1175 fn {name}Flag::value(self : {name}Flag) -> {ty} {{
1176 match self {{
1177 {map_to_int}
1178 }}
1179 }}
1180 pub fn {name}::set(self : Self, other: {name}Flag) -> {name} {{
1181 let {name}(flag) = self
1182 flag.lor(other.value())
1183 }}
1184 pub fn {name}::unset(self : Self, other: {name}Flag) -> {name} {{
1185 let {name}(flag) = self
1186 flag.land(other.value().lnot())
1187 }}
1188 pub fn {name}::is_set(self : Self, other: {name}Flag) -> Bool {{
1189 let {name}(flag) = self
1190 (flag.land(other.value()) == other.value())
1191 }}
1192 ",
1193 deriviation.join(", "),
1194 match ty {
1195 "Byte" => "b'\\x00'",
1196 "UInt" => "0U",
1197 "UInt64" => "0UL",
1198 _ => unreachable!(),
1199 }
1200 );
1201 }
1202
1203 fn type_tuple(&mut self, _id: TypeId, _name: &str, _tuple: &Tuple, _docs: &Docs) {
1204 }
1206
1207 fn type_variant(&mut self, _id: TypeId, name: &str, variant: &Variant, docs: &Docs) {
1208 print_docs(&mut self.src, docs);
1209
1210 let name = name.to_moonbit_type_ident();
1211
1212 let cases = variant
1213 .cases
1214 .iter()
1215 .map(|case| {
1216 let name = case.name.to_upper_camel_case();
1217 if let Some(ty) = case.ty {
1218 let ty = self.world_gen.pkg_resolver.type_name(self.name, &ty);
1219 format!("{name}({ty})")
1220 } else {
1221 name.to_string()
1222 }
1223 })
1224 .collect::<Vec<_>>()
1225 .join("\n ");
1226
1227 let contains_endpoint = variant
1228 .cases
1229 .iter()
1230 .filter_map(|case| case.ty.as_ref())
1231 .any(|ty| type_contains_future_or_stream(self.resolve, ty));
1232 let mut deriviation: Vec<_> = Vec::new();
1233 if self.derive_opts.derive_debug && !contains_endpoint {
1234 deriviation.push("Debug")
1235 }
1236 if self.derive_opts.derive_show && !contains_endpoint {
1237 deriviation.push("Show")
1238 }
1239 if self.derive_opts.derive_eq && !contains_endpoint {
1240 deriviation.push("Eq")
1241 }
1242 let declaration = if self.derive_opts.derive_error && name.contains("Error") {
1243 "suberror"
1244 } else {
1245 "enum"
1246 };
1247
1248 let derivation = if deriviation.is_empty() {
1249 String::new()
1250 } else {
1251 format!(" derive({})", deriviation.join(", "))
1252 };
1253
1254 uwrite!(
1255 self.src,
1256 "
1257 pub(all) {declaration} {name} {{
1258 {cases}
1259 }}{derivation}
1260 "
1261 );
1262 }
1263
1264 fn type_option(&mut self, _id: TypeId, _name: &str, _payload: &Type, _docs: &Docs) {
1265 }
1267
1268 fn type_result(&mut self, _id: TypeId, _name: &str, _result: &Result_, _docs: &Docs) {
1269 }
1271
1272 fn type_enum(&mut self, _id: TypeId, name: &str, enum_: &Enum, docs: &Docs) {
1273 print_docs(&mut self.src, docs);
1274
1275 let name = name.to_moonbit_type_ident();
1276
1277 let cases = enum_
1279 .cases
1280 .iter()
1281 .map(|case| case.name.to_shouty_snake_case())
1282 .collect::<Vec<_>>()
1283 .join("; ");
1284
1285 let mut deriviation: Vec<_> = Vec::new();
1286 if self.derive_opts.derive_debug {
1287 deriviation.push("Debug")
1288 }
1289 if self.derive_opts.derive_show {
1290 deriviation.push("Show")
1291 }
1292 if self.derive_opts.derive_eq {
1293 deriviation.push("Eq")
1294 }
1295 let declaration = if self.derive_opts.derive_error && name.contains("Error") {
1296 "suberror"
1297 } else {
1298 "enum"
1299 };
1300
1301 uwrite!(
1302 self.src,
1303 "
1304 pub(all) {declaration} {name} {{
1305 {cases}
1306 }} derive({})
1307 ",
1308 deriviation.join(", ")
1309 );
1310
1311 let cases = enum_
1313 .cases
1314 .iter()
1315 .enumerate()
1316 .map(|(i, case)| format!("{} => {i}", case.name.to_shouty_snake_case()))
1317 .collect::<Vec<_>>()
1318 .join("\n ");
1319
1320 uwrite!(
1321 self.src,
1322 "
1323 pub fn {name}::ordinal(self : {name}) -> Int {{
1324 match self {{
1325 {cases}
1326 }}
1327 }}
1328 "
1329 );
1330
1331 let cases = enum_
1333 .cases
1334 .iter()
1335 .enumerate()
1336 .map(|(i, case)| format!("{i} => {}", case.name.to_shouty_snake_case()))
1337 .collect::<Vec<_>>()
1338 .join("\n ");
1339
1340 uwrite!(
1341 self.src,
1342 "
1343 pub fn {name}::from(self : Int) -> {name} {{
1344 match self {{
1345 {cases}
1346 _ => panic()
1347 }}
1348 }}
1349 "
1350 );
1351 }
1352
1353 fn type_alias(&mut self, _id: TypeId, _name: &str, _ty: &Type, _docs: &Docs) {}
1354
1355 fn type_list(&mut self, _id: TypeId, _name: &str, _ty: &Type, _docs: &Docs) {
1356 }
1358
1359 fn type_fixed_length_list(
1360 &mut self,
1361 _id: TypeId,
1362 _name: &str,
1363 _ty: &Type,
1364 _size: u32,
1365 _docs: &Docs,
1366 ) {
1367 }
1369
1370 fn type_map(&mut self, _id: TypeId, _name: &str, _key: &Type, _value: &Type, _docs: &Docs) {
1371 }
1373
1374 fn type_future(&mut self, _id: TypeId, _name: &str, _ty: &Option<Type>, _docs: &Docs) {
1375 }
1377
1378 fn type_stream(&mut self, _id: TypeId, _name: &str, _ty: &Option<Type>, _docs: &Docs) {
1379 }
1381
1382 fn type_builtin(&mut self, _id: TypeId, _name: &str, _ty: &Type, _docs: &Docs) {
1383 unimplemented!();
1384 }
1385}
1386
1387struct Block {
1388 body: String,
1389 results: Vec<String>,
1390}
1391
1392struct Cleanup {
1393 address: String,
1394}
1395
1396struct BlockStorage {
1397 body: String,
1398 cleanup: Vec<Cleanup>,
1399}
1400
1401struct FunctionBindgen<'a, 'b> {
1402 interface_gen: &'b mut InterfaceGenerator<'a>,
1403 type_context: String,
1404 func_interface: String,
1405 params: Box<[String]>,
1406 src: String,
1407 locals: Ns,
1408 block_storage: Vec<BlockStorage>,
1409 blocks: Vec<Block>,
1410 payloads: Vec<String>,
1411 cleanup: Vec<Cleanup>,
1412 needs_cleanup_list: bool,
1413 suppress_block_cleanup: bool,
1414 preserve_guest_allocations: bool,
1415 sync_endpoint_drop: bool,
1416 commit_endpoints: bool,
1417 sync_import_argument_types: Option<Vec<Type>>,
1418 async_state: AsyncFunctionState,
1419}
1420
1421impl<'a, 'b> FunctionBindgen<'a, 'b> {
1422 fn new(
1423 r#gen: &'b mut InterfaceGenerator<'a>,
1424 params: Box<[String]>,
1425 ) -> FunctionBindgen<'a, 'b> {
1426 let mut locals = Ns::default();
1427 params.iter().for_each(|str| {
1428 locals.tmp(str);
1429 });
1430 let type_context = r#gen.name.to_string();
1431 Self {
1432 interface_gen: r#gen,
1433 func_interface: type_context.clone(),
1434 type_context,
1435 params,
1436 src: String::new(),
1437 locals,
1438 block_storage: Vec::new(),
1439 blocks: Vec::new(),
1440 payloads: Vec::new(),
1441 cleanup: Vec::new(),
1442 needs_cleanup_list: false,
1443 suppress_block_cleanup: false,
1444 preserve_guest_allocations: false,
1445 sync_endpoint_drop: false,
1446 commit_endpoints: false,
1447 sync_import_argument_types: None,
1448 async_state: AsyncFunctionState::default(),
1449 }
1450 }
1451
1452 fn lower_variant(
1453 &mut self,
1454 cases: &[(&str, Option<Type>)],
1455 lowered_types: &[WasmType],
1456 op: &str,
1457 results: &mut Vec<String>,
1458 is_result: bool,
1459 ) {
1460 let blocks = self
1461 .blocks
1462 .drain(self.blocks.len() - cases.len()..)
1463 .collect::<Vec<_>>();
1464
1465 let payloads = self
1466 .payloads
1467 .drain(self.payloads.len() - cases.len()..)
1468 .collect::<Vec<_>>();
1469
1470 let lowered = lowered_types
1471 .iter()
1472 .map(|_| self.locals.tmp("lowered"))
1473 .collect::<Vec<_>>();
1474
1475 results.extend(lowered.iter().cloned());
1476
1477 let declarations = lowered.join(",");
1478
1479 let cases = cases
1480 .iter()
1481 .zip(blocks)
1482 .zip(payloads)
1483 .map(|(((name, ty), Block { body, results, .. }), payload)| {
1484 let name = name.to_upper_camel_case();
1485 let assignments = results
1486 .iter()
1487 .map(|result| result.to_string())
1488 .collect::<Vec<_>>()
1489 .join(", ");
1490
1491 let payload = if self
1492 .interface_gen
1493 .world_gen
1494 .pkg_resolver
1495 .non_empty_type(ty.as_ref())
1496 .is_some()
1497 {
1498 payload
1499 } else if is_result {
1500 format!("_{payload}")
1501 } else {
1502 String::new()
1503 };
1504
1505 if payload.is_empty() {
1506 format!(
1507 "{name} => {{
1508 {body}
1509 ({assignments})
1510 }}"
1511 )
1512 } else {
1513 format!(
1514 "{name}({payload}) => {{
1515 {body}
1516 ({assignments})
1517 }}",
1518 )
1519 }
1520 })
1521 .collect::<Vec<_>>()
1522 .join("\n");
1523
1524 if declarations.is_empty() {
1525 uwrite!(
1526 self.src,
1527 r#"
1528 match {op} {{
1529 {cases}
1530 }}
1531 "#
1532 );
1533 } else {
1534 uwrite!(
1535 self.src,
1536 r#"
1537 let ({declarations}) = match {op} {{
1538 {cases}
1539 }}
1540 "#
1541 );
1542 }
1543 }
1544
1545 fn lift_variant(
1546 &mut self,
1547 ty: &Type,
1548 cases: &[(&str, Option<Type>)],
1549 op: &str,
1550 results: &mut Vec<String>,
1551 is_result: bool,
1552 ) {
1553 let blocks = self
1554 .blocks
1555 .drain(self.blocks.len() - cases.len()..)
1556 .collect::<Vec<_>>();
1557
1558 let ty = self.resolve_constructor(ty);
1560 let lifted = self.locals.tmp("lifted");
1561
1562 let cases = cases
1563 .iter()
1564 .zip(blocks)
1565 .enumerate()
1566 .map(|(i, ((case_name, case_ty), Block { body, results, .. }))| {
1567 let payload = if self
1568 .interface_gen
1569 .world_gen
1570 .pkg_resolver
1571 .non_empty_type(case_ty.as_ref())
1572 .is_some()
1573 {
1574 results.into_iter().next().unwrap()
1575 } else {
1576 String::new()
1577 };
1578
1579 let constructor = format!("{ty}::{}", case_name.to_upper_camel_case());
1580
1581 if payload.is_empty() && !is_result {
1582 format!(
1583 "{i} => {{
1584 {body}
1585 {constructor}
1586 }}"
1587 )
1588 } else {
1589 format!(
1590 "{i} => {{
1591 {body}
1592 {constructor}({})
1593 }}",
1594 if payload.is_empty() {
1595 "()".into()
1596 } else {
1597 payload
1598 }
1599 )
1600 }
1601 })
1602 .collect::<Vec<_>>()
1603 .join("\n");
1604
1605 uwrite!(
1606 self.src,
1607 r#"
1608 let {lifted} = match ({op}) {{
1609 {cases}
1610 _ => panic()
1611 }}
1612 "#
1613 );
1614
1615 results.push(lifted);
1616 }
1617
1618 fn resolve_constructor(&mut self, ty: &Type) -> String {
1620 self.interface_gen
1621 .world_gen
1622 .pkg_resolver
1623 .type_constructor(&self.type_context, ty)
1624 }
1625
1626 fn resolve_type_name(&mut self, ty: &Type) -> String {
1627 self.interface_gen
1628 .world_gen
1629 .pkg_resolver
1630 .type_name(&self.type_context, ty)
1631 }
1632
1633 fn use_ffi(&mut self, str: &'static str) {
1634 self.interface_gen.ffi_imports.insert(str);
1635 }
1636}
1637
1638impl Bindgen for FunctionBindgen<'_, '_> {
1639 type Operand = String;
1640
1641 fn emit(
1642 &mut self,
1643 _resolve: &Resolve,
1644 inst: &Instruction<'_>,
1645 operands: &mut Vec<String>,
1646 results: &mut Vec<String>,
1647 ) {
1648 match inst {
1649 Instruction::GetArg { nth } => results.push(self.params[*nth].clone()),
1650 Instruction::I32Const { val } => results.push(format!("({val})")),
1651 Instruction::ConstZero { tys } => results.extend(tys.iter().map(|ty| {
1652 match ty {
1653 WasmType::I32 => "0",
1654 WasmType::I64 => "0L",
1655 WasmType::F32 => "(0.0 : Float)",
1656 WasmType::F64 => "0.0",
1657 WasmType::Pointer => "0",
1658 WasmType::PointerOrI64 => "0L",
1659 WasmType::Length => "0",
1660 }
1661 .to_owned()
1662 })),
1663
1664 Instruction::Bitcasts { casts } => results.extend(
1665 casts
1666 .iter()
1667 .zip(operands)
1668 .map(|(cast, op)| perform_cast(op, cast)),
1669 ),
1670
1671 Instruction::I32FromS32
1672 | Instruction::I64FromS64
1673 | Instruction::S32FromI32
1674 | Instruction::S64FromI64
1675 | Instruction::CoreF64FromF64
1676 | Instruction::F64FromCoreF64
1677 | Instruction::F32FromCoreF32
1678 | Instruction::CoreF32FromF32 => results.push(operands[0].clone()),
1679
1680 Instruction::CharFromI32 => {
1681 results.push(format!("Int::unsafe_to_char({})", operands[0]))
1682 }
1683 Instruction::I32FromChar => results.push(format!("({}).to_int()", operands[0])),
1684
1685 Instruction::I32FromU8 => results.push(format!("({}).to_int()", operands[0])),
1686 Instruction::I32FromU16 => {
1687 results.push(format!("({}).reinterpret_as_int()", operands[0]))
1688 }
1689 Instruction::U8FromI32 => results.push(format!("({}).to_byte()", operands[0])),
1690
1691 Instruction::I32FromS8 => {
1692 self.use_ffi(ffi::EXTEND8);
1693 results.push(format!("mbt_ffi_extend8({})", operands[0]))
1694 }
1695 Instruction::S8FromI32 => results.push(format!("({} - 0x100)", operands[0])),
1696 Instruction::S16FromI32 => results.push(format!("({} - 0x10000)", operands[0])),
1697 Instruction::I32FromS16 => {
1698 self.use_ffi(ffi::EXTEND16);
1699 results.push(format!("mbt_ffi_extend16({})", operands[0]))
1700 }
1701 Instruction::U16FromI32 => results.push(format!(
1702 "({}.land(0xFFFF).reinterpret_as_uint())",
1703 operands[0]
1704 )),
1705 Instruction::U32FromI32 => {
1706 results.push(format!("({}).reinterpret_as_uint()", operands[0]))
1707 }
1708 Instruction::I32FromU32 => {
1709 results.push(format!("({}).reinterpret_as_int()", operands[0]))
1710 }
1711
1712 Instruction::U64FromI64 => {
1713 results.push(format!("({}).reinterpret_as_uint64()", operands[0]))
1714 }
1715 Instruction::I64FromU64 => {
1716 results.push(format!("({}).reinterpret_as_int64()", operands[0]))
1717 }
1718
1719 Instruction::I32FromBool => {
1720 results.push(format!("(if {} {{ 1 }} else {{ 0 }})", operands[0]));
1721 }
1722 Instruction::BoolFromI32 => results.push(format!("({} != 0)", operands[0])),
1723
1724 Instruction::FlagsLower { flags, ty, .. } => match flags_repr(flags) {
1725 Int::U8 => {
1726 let op = &operands[0];
1727 let flag = self.locals.tmp("flag");
1728 let ty = self.resolve_constructor(&Type::Id(*ty));
1729 uwriteln!(
1730 self.src,
1731 r#"
1732 let {ty}({flag}) = {op}
1733 "#
1734 );
1735 results.push(format!("{flag}.to_int()"));
1736 }
1737 Int::U16 | Int::U32 => {
1738 let op = &operands[0];
1739 let flag = self.locals.tmp("flag");
1740 let ty = self.resolve_constructor(&Type::Id(*ty));
1741 uwriteln!(
1742 self.src,
1743 r#"
1744 let {ty}({flag}) = {op}
1745 "#
1746 );
1747 results.push(format!("{flag}.reinterpret_as_int()"));
1748 }
1749 Int::U64 => {
1750 let op = &operands[0];
1751 let flag = self.locals.tmp("flag");
1752 let ty = self.resolve_constructor(&Type::Id(*ty));
1753 uwriteln!(
1754 self.src,
1755 r#"
1756 let {ty}({flag}) = {op}
1757 "#
1758 );
1759 results.push(format!("({flag}.to_int())"));
1760 results.push(format!("({flag} >> 32).to_int())"));
1761 }
1762 },
1763
1764 Instruction::FlagsLift { flags, ty, .. } => match flags_repr(flags) {
1765 Int::U8 => {
1766 results.push(format!(
1767 "{}({}.to_byte())",
1768 self.resolve_type_name(&Type::Id(*ty)),
1769 operands[0]
1770 ));
1771 }
1772 Int::U16 | Int::U32 => {
1773 results.push(format!(
1774 "{}({}.reinterpret_as_uint())",
1775 self.resolve_type_name(&Type::Id(*ty)),
1776 operands[0]
1777 ));
1778 }
1779 Int::U64 => {
1780 results.push(format!(
1781 "{}(({}).reinterpret_as_uint().to_uint64() | (({}).reinterpret_as_uint().to_uint64() << 32))",
1782 self.resolve_type_name(&Type::Id(*ty)),
1783 operands[0],
1784 operands[1]
1785 ));
1786 }
1787 },
1788
1789 Instruction::HandleLower { ty, .. } => {
1790 let op = &operands[0];
1791 let handle = self.locals.tmp("handle");
1792 let ty = self.resolve_constructor(&Type::Id(*ty));
1793 uwrite!(
1794 self.src,
1795 r#"
1796 let {ty}({handle}) = {op}
1797 "#
1798 );
1799 results.push(handle);
1800 }
1801 Instruction::HandleLift { ty, .. } => {
1802 let op = &operands[0];
1803 let ty = self.resolve_constructor(&Type::Id(*ty));
1804 results.push(format!(
1805 "{}::{}({})",
1806 ty,
1807 if ty.starts_with("@") {
1808 ty.split('.').next_back().unwrap()
1809 } else {
1810 &ty
1811 },
1812 op
1813 ));
1814 }
1815
1816 Instruction::RecordLower { record, .. } => {
1817 let op = &operands[0];
1818 for field in record.fields.iter() {
1819 results.push(format!("({op}).{}", field.name.to_moonbit_ident()));
1820 }
1821 }
1822 Instruction::RecordLift { ty, record, .. } => {
1823 let ops = operands
1824 .iter()
1825 .enumerate()
1826 .map(|(i, op)| format!("{} : {}", record.fields[i].name.to_moonbit_ident(), op))
1827 .collect::<Vec<_>>()
1828 .join(", ");
1829
1830 results.push(format!(
1831 "{}::{{{ops}}}",
1832 self.resolve_type_name(&Type::Id(*ty))
1833 ));
1834 }
1835
1836 Instruction::TupleLower { tuple, .. } => {
1837 let op = &operands[0];
1838 if tuple.types.is_empty() {
1841 results.push("()".into());
1842 } else if tuple.types.len() == 1 {
1843 results.push(operands[0].to_string());
1844 } else {
1845 for i in 0..tuple.types.len() {
1846 results.push(format!("({op}).{i}"));
1847 }
1848 }
1849 }
1850 Instruction::TupleLift { .. } => {
1851 let ops = operands
1852 .iter()
1853 .map(|op| op.to_string())
1854 .collect::<Vec<_>>()
1855 .join(", ");
1856 results.push(format!("({ops})"));
1857 }
1858
1859 Instruction::VariantPayloadName => {
1860 let payload = self.locals.tmp("payload");
1861 results.push(payload.clone());
1862 self.payloads.push(payload);
1863 }
1864
1865 Instruction::VariantLower {
1866 variant,
1867 results: lowered_types,
1868 ..
1869 } => self.lower_variant(
1870 &variant
1871 .cases
1872 .iter()
1873 .map(|case| (case.name.deref(), case.ty))
1874 .collect::<Vec<_>>(),
1875 lowered_types,
1876 &operands[0],
1877 results,
1878 false,
1879 ),
1880
1881 Instruction::VariantLift { variant, ty, .. } => self.lift_variant(
1882 &Type::Id(*ty),
1883 &variant
1884 .cases
1885 .iter()
1886 .map(|case| (case.name.deref(), case.ty))
1887 .collect::<Vec<_>>(),
1888 &operands[0],
1889 results,
1890 false,
1891 ),
1892
1893 Instruction::OptionLower {
1894 results: lowered_types,
1895 ..
1896 } => {
1897 let some = self.blocks.pop().unwrap();
1898 let none = self.blocks.pop().unwrap();
1899 let some_payload = self.payloads.pop().unwrap();
1900 let _none_payload = self.payloads.pop().unwrap();
1901
1902 let lowered = lowered_types
1903 .iter()
1904 .map(|_| self.locals.tmp("lowered"))
1905 .collect::<Vec<_>>();
1906
1907 results.extend(lowered.iter().cloned());
1908
1909 let declarations = lowered
1910 .iter()
1911 .map(|lowered| lowered.to_string())
1912 .collect::<Vec<_>>()
1913 .join(", ");
1914
1915 let op = &operands[0];
1916
1917 let block = |Block { body, results, .. }| {
1918 let assignments = results
1919 .iter()
1920 .map(|result| result.to_string())
1921 .collect::<Vec<_>>()
1922 .join(", ");
1923
1924 format!(
1925 "{body}
1926 ({assignments})"
1927 )
1928 };
1929
1930 let none = block(none);
1931 let some = block(some);
1932 let assignment = if declarations.is_empty() {
1933 "".into()
1934 } else {
1935 format!("let ({declarations}) = ")
1936 };
1937 uwrite!(
1938 self.src,
1939 r#"
1940 {assignment}match ({op}) {{
1941 None => {{
1942 {none}
1943 }}
1944 Some({some_payload}) => {{
1945 {some}
1946 }}
1947 }}
1948 "#,
1949 );
1950 }
1951
1952 Instruction::OptionLift { ty, .. } => {
1953 let some = self.blocks.pop().unwrap();
1954 let _none = self.blocks.pop().unwrap();
1955
1956 let ty = self.resolve_type_name(&Type::Id(*ty));
1957 let lifted = self.locals.tmp("lifted");
1958 let op = &operands[0];
1959
1960 let assignment = some.results.first().unwrap();
1961
1962 let some = some.body;
1963
1964 uwrite!(
1965 self.src,
1966 r#"
1967 let {lifted} : {ty} = match {op} {{
1968 0 => Option::None
1969 1 => {{
1970 {some}
1971 Option::Some({assignment})
1972 }}
1973 _ => panic()
1974 }}
1975 "#
1976 );
1977
1978 results.push(lifted);
1979 }
1980
1981 Instruction::ResultLower {
1982 results: lowered_types,
1983 result,
1984 ..
1985 } => self.lower_variant(
1986 &[("Ok", result.ok), ("Err", result.err)],
1987 lowered_types,
1988 &operands[0],
1989 results,
1990 true,
1991 ),
1992
1993 Instruction::ResultLift { result, ty } => self.lift_variant(
1994 &Type::Id(*ty),
1995 &[("Ok", result.ok), ("Err", result.err)],
1996 &operands[0],
1997 results,
1998 true,
1999 ),
2000
2001 Instruction::EnumLower { .. } => results.push(format!("{}.ordinal()", operands[0])),
2002
2003 Instruction::EnumLift { ty, .. } => results.push(format!(
2004 "{}::from({})",
2005 self.resolve_type_name(&Type::Id(*ty)),
2006 operands[0]
2007 )),
2008
2009 Instruction::ListCanonLower { element, realloc } => match element {
2010 Type::U8 => {
2011 let op = &operands[0];
2012 let ptr = self.locals.tmp("ptr");
2013 self.use_ffi(ffi::BYTES2PTR);
2014 uwriteln!(
2015 self.src,
2016 "
2017 let {ptr} = mbt_ffi_bytes2ptr({op})
2018 ",
2019 );
2020 results.push(ptr.clone());
2021 results.push(format!("{op}.length()"));
2022 if realloc.is_none() {
2023 self.cleanup.push(Cleanup { address: ptr });
2024 }
2025 }
2026 Type::U32 | Type::U64 | Type::S32 | Type::S64 | Type::F32 | Type::F64 => {
2027 let op = &operands[0];
2028 let ptr = self.locals.tmp("ptr");
2029 let ty = match element {
2030 Type::U32 => {
2031 self.use_ffi(ffi::UINT_ARRAY2PTR);
2032 "uint"
2033 }
2034 Type::U64 => {
2035 self.use_ffi(ffi::UINT64_ARRAY2PTR);
2036 "uint64"
2037 }
2038 Type::S32 => {
2039 self.use_ffi(ffi::INT_ARRAY2PTR);
2040 "int"
2041 }
2042 Type::S64 => {
2043 self.use_ffi(ffi::INT64_ARRAY2PTR);
2044 "int64"
2045 }
2046 Type::F32 => {
2047 self.use_ffi(ffi::FLOAT_ARRAY2PTR);
2048 "float"
2049 }
2050 Type::F64 => {
2051 self.use_ffi(ffi::DOUBLE_ARRAY2PTR);
2052 "double"
2053 }
2054 _ => unreachable!(),
2055 };
2056
2057 uwriteln!(
2058 self.src,
2059 "
2060 let {ptr} = mbt_ffi_{ty}_array2ptr({op})
2061 ",
2062 );
2063 results.push(ptr.clone());
2064 results.push(format!("{op}.length()"));
2065 if realloc.is_none() {
2066 self.cleanup.push(Cleanup { address: ptr });
2067 }
2068 }
2069 _ => unreachable!("unsupported list element type"),
2070 },
2071
2072 Instruction::ListCanonLift { element, .. } => match element {
2073 Type::U8 => {
2074 let result = self.locals.tmp("result");
2075 let address = &operands[0];
2076 let length = &operands[1];
2077 self.use_ffi(ffi::PTR2BYTES);
2078 uwrite!(
2079 self.src,
2080 "
2081 let {result} = mbt_ffi_ptr2bytes({address}, {length})
2082 ",
2083 );
2084
2085 results.push(result);
2086 }
2087 Type::U32 | Type::U64 | Type::S32 | Type::S64 | Type::F32 | Type::F64 => {
2088 let ty = match element {
2089 Type::U32 => {
2090 self.use_ffi(ffi::PTR2UINT_ARRAY);
2091 "uint"
2092 }
2093 Type::U64 => {
2094 self.use_ffi(ffi::PTR2UINT64_ARRAY);
2095 "uint64"
2096 }
2097 Type::S32 => {
2098 self.use_ffi(ffi::PTR2INT_ARRAY);
2099 "int"
2100 }
2101 Type::S64 => {
2102 self.use_ffi(ffi::PTR2INT64_ARRAY);
2103 "int64"
2104 }
2105 Type::F32 => {
2106 self.use_ffi(ffi::PTR2FLOAT_ARRAY);
2107 "float"
2108 }
2109 Type::F64 => {
2110 self.use_ffi(ffi::PTR2DOUBLE_ARRAY);
2111 "double"
2112 }
2113 _ => unreachable!(),
2114 };
2115
2116 let result = self.locals.tmp("result");
2117 let address = &operands[0];
2118 let length = &operands[1];
2119
2120 uwrite!(
2121 self.src,
2122 "
2123 let {result} = mbt_ffi_ptr2{ty}_array({address}, {length})
2124 ",
2125 );
2126
2127 results.push(result);
2128 }
2129 _ => unreachable!("unsupported list element type"),
2130 },
2131
2132 Instruction::StringLower { realloc } => {
2133 let op = &operands[0];
2134 let ptr = self.locals.tmp("ptr");
2135
2136 self.use_ffi(ffi::STR2PTR);
2137 uwrite!(
2138 self.src,
2139 "
2140 let {ptr} = mbt_ffi_str2ptr({op})
2141 ",
2142 );
2143
2144 results.push(ptr.clone());
2145 results.push(format!("{op}.length()"));
2146 if realloc.is_none() {
2147 self.cleanup.push(Cleanup { address: ptr });
2148 }
2149 }
2150
2151 Instruction::StringLift { .. } => {
2152 let result = self.locals.tmp("result");
2153 let address = &operands[0];
2154 let length = &operands[1];
2155
2156 self.use_ffi(ffi::PTR2STR);
2157 uwrite!(
2158 self.src,
2159 "
2160 let {result} = mbt_ffi_ptr2str({address}, {length})
2161 ",
2162 );
2163
2164 results.push(result);
2165 }
2166
2167 Instruction::ListLower { element, realloc } => {
2168 let Block {
2169 body,
2170 results: block_results,
2171 } = self.blocks.pop().unwrap();
2172 assert!(block_results.is_empty());
2173
2174 let op = &operands[0];
2175 let size = self
2176 .interface_gen
2177 .world_gen
2178 .sizes
2179 .size(element)
2180 .size_wasm32();
2181 let _align = self
2182 .interface_gen
2183 .world_gen
2184 .sizes
2185 .align(element)
2186 .align_wasm32();
2187 let address = self.locals.tmp("address");
2188 let ty = self.resolve_type_name(element);
2189 let index = self.locals.tmp("index");
2190
2191 self.use_ffi(ffi::MALLOC);
2192 uwrite!(
2193 self.src,
2194 "
2195 let {address} = mbt_ffi_malloc(({op}).length() * {size});
2196 for {index} = 0; {index} < ({op}).length(); {index} = {index} + 1 {{
2197 let iter_elem : {ty} = ({op})[({index})]
2198 let iter_base = {address} + ({index} * {size});
2199 {body}
2200 }}
2201 ",
2202 );
2203
2204 results.push(address.clone());
2205 results.push(format!("({op}).length()"));
2206
2207 if realloc.is_none() {
2208 self.cleanup.push(Cleanup { address });
2209 }
2210 }
2211
2212 Instruction::ListLift { element, .. } => {
2213 let Block {
2214 body,
2215 results: block_results,
2216 } = self.blocks.pop().unwrap();
2217 let address = &operands[0];
2218 let length = &operands[1];
2219 let array = self.locals.tmp("array");
2220 let ty = self.resolve_type_name(element);
2221 let size = self
2222 .interface_gen
2223 .world_gen
2224 .sizes
2225 .size(element)
2226 .size_wasm32();
2227 let index = self.locals.tmp("index");
2229
2230 let result = match &block_results[..] {
2231 [result] => result,
2232 _ => todo!("result count == {}", results.len()),
2233 };
2234
2235 self.use_ffi(ffi::FREE);
2236 uwrite!(
2237 self.src,
2238 "
2239 let {array} : Array[{ty}] = [];
2240 for {index} = 0; {index} < ({length}); {index} = {index} + 1 {{
2241 let iter_base = ({address}) + ({index} * {size})
2242 {body}
2243 {array}.push({result})
2244 }}
2245 mbt_ffi_free({address})
2246 ",
2247 );
2248
2249 results.push(array);
2250 }
2251
2252 Instruction::IterElem { .. } => results.push("iter_elem".into()),
2253
2254 Instruction::IterBasePointer => results.push("iter_base".into()),
2255
2256 Instruction::CallWasm { sig, name } => {
2257 let assignment = match &sig.results[..] {
2258 [result] => {
2259 let ty = wasm_type(*result);
2260 let result = self.locals.tmp("result");
2261 let assignment = format!("let {result} : {ty} = ");
2262 results.push(result);
2263 assignment
2264 }
2265
2266 [] => String::new(),
2267
2268 _ => unreachable!(),
2269 };
2270
2271 let func_name = name.to_upper_camel_case();
2272 let call_operands = if self.sync_import_argument_types.is_some() {
2273 operands
2274 .iter()
2275 .map(|operand| {
2276 let stable = self.locals.tmp("lower_arg");
2277 uwriteln!(self.src, "let {stable} = {operand}");
2278 stable
2279 })
2280 .collect::<Vec<_>>()
2281 } else {
2282 operands.clone()
2283 };
2284 let arguments = call_operands.join(", ");
2285 uwriteln!(self.src, "{assignment} wasmImport{func_name}({arguments});");
2287 self.commit_sync_import_arguments(sig, &call_operands);
2288 }
2289
2290 Instruction::CallInterface { func, async_ } => {
2291 if *async_ {
2292 self.emit_async_call_interface(func, operands, results);
2293 return;
2294 }
2295
2296 let name = self.interface_gen.world_gen.pkg_resolver.func_call(
2297 &self.type_context,
2298 func,
2299 &self.func_interface,
2300 );
2301
2302 let args = operands.join(", ");
2303
2304 let assignment = match func.result {
2305 None => "let _ = ".into(),
2306 Some(ty) => {
2307 let ty = format!("({})", self.resolve_type_name(&ty));
2308 let result = self.locals.tmp("result");
2309 if func.result.is_some() {
2310 results.push(result.clone());
2311 }
2312 let assignment = format!("let ({result}) : {ty} = ");
2313 assignment
2314 }
2315 };
2316
2317 uwrite!(
2318 self.src,
2319 "
2320 {assignment}{name}({args});
2321 ",
2322 );
2323 }
2324
2325 Instruction::Return { amt, .. } => {
2326 let return_locals: Vec<String> = if *amt > 0 {
2330 operands
2331 .iter()
2332 .map(|op| {
2333 let local = self.locals.tmp("ret");
2334 uwriteln!(self.src, "let {local} = {op}");
2335 local
2336 })
2337 .collect()
2338 } else {
2339 Vec::new()
2340 };
2341 if !self.cleanup.is_empty() || self.needs_cleanup_list {
2342 self.use_ffi(ffi::FREE);
2343 }
2344 for clean in &self.cleanup {
2345 let address = &clean.address;
2346 uwriteln!(self.src, "mbt_ffi_free({address})",);
2347 }
2348
2349 if self.needs_cleanup_list {
2350 uwrite!(
2351 self.src,
2352 "
2353 cleanup_list.each(mbt_ffi_free)
2354 ",
2355 );
2356 }
2357
2358 match *amt {
2359 0 => (),
2360 1 => uwriteln!(self.src, "return {}", return_locals[0]),
2361 _ => {
2362 let results = return_locals.join(", ");
2363 uwriteln!(self.src, "return ({results})");
2364 }
2365 }
2366 }
2367
2368 Instruction::I32Load { offset }
2369 | Instruction::PointerLoad { offset }
2370 | Instruction::LengthLoad { offset } => {
2371 self.use_ffi(ffi::LOAD32);
2372 results.push(format!(
2373 "mbt_ffi_load32(({}) + {offset})",
2374 operands[0],
2375 offset = offset.size_wasm32()
2376 ))
2377 }
2378
2379 Instruction::I32Load8U { offset } => {
2380 self.use_ffi(ffi::LOAD8_U);
2381 results.push(format!(
2382 "mbt_ffi_load8_u(({}) + {offset})",
2383 operands[0],
2384 offset = offset.size_wasm32()
2385 ))
2386 }
2387
2388 Instruction::I32Load8S { offset } => {
2389 self.use_ffi(ffi::LOAD8);
2390 results.push(format!(
2391 "mbt_ffi_load8(({}) + {offset})",
2392 operands[0],
2393 offset = offset.size_wasm32()
2394 ))
2395 }
2396
2397 Instruction::I32Load16U { offset } => {
2398 self.use_ffi(ffi::LOAD16_U);
2399 results.push(format!(
2400 "mbt_ffi_load16_u(({}) + {offset})",
2401 operands[0],
2402 offset = offset.size_wasm32()
2403 ))
2404 }
2405
2406 Instruction::I32Load16S { offset } => {
2407 self.use_ffi(ffi::LOAD16);
2408 results.push(format!(
2409 "mbt_ffi_load16(({}) + {offset})",
2410 operands[0],
2411 offset = offset.size_wasm32()
2412 ))
2413 }
2414
2415 Instruction::I64Load { offset } => {
2416 self.use_ffi(ffi::LOAD64);
2417 results.push(format!(
2418 "mbt_ffi_load64(({}) + {offset})",
2419 operands[0],
2420 offset = offset.size_wasm32()
2421 ))
2422 }
2423
2424 Instruction::F32Load { offset } => {
2425 self.use_ffi(ffi::LOADF32);
2426 results.push(format!(
2427 "mbt_ffi_loadf32(({}) + {offset})",
2428 operands[0],
2429 offset = offset.size_wasm32()
2430 ))
2431 }
2432
2433 Instruction::F64Load { offset } => {
2434 self.use_ffi(ffi::LOADF64);
2435 results.push(format!(
2436 "mbt_ffi_loadf64(({}) + {offset})",
2437 operands[0],
2438 offset = offset.size_wasm32()
2439 ))
2440 }
2441
2442 Instruction::I32Store { offset }
2443 | Instruction::PointerStore { offset }
2444 | Instruction::LengthStore { offset } => {
2445 self.use_ffi(ffi::STORE32);
2446 uwriteln!(
2447 self.src,
2448 "mbt_ffi_store32(({}) + {offset}, {})",
2449 operands[1],
2450 operands[0],
2451 offset = offset.size_wasm32()
2452 )
2453 }
2454
2455 Instruction::I32Store8 { offset } => {
2456 self.use_ffi(ffi::STORE8);
2457 uwriteln!(
2458 self.src,
2459 "mbt_ffi_store8(({}) + {offset}, {})",
2460 operands[1],
2461 operands[0],
2462 offset = offset.size_wasm32()
2463 )
2464 }
2465
2466 Instruction::I32Store16 { offset } => {
2467 self.use_ffi(ffi::STORE16);
2468 uwriteln!(
2469 self.src,
2470 "mbt_ffi_store16(({}) + {offset}, {})",
2471 operands[1],
2472 operands[0],
2473 offset = offset.size_wasm32()
2474 )
2475 }
2476
2477 Instruction::I64Store { offset } => {
2478 self.use_ffi(ffi::STORE64);
2479 uwriteln!(
2480 self.src,
2481 "mbt_ffi_store64(({}) + {offset}, {})",
2482 operands[1],
2483 operands[0],
2484 offset = offset.size_wasm32()
2485 )
2486 }
2487
2488 Instruction::F32Store { offset } => {
2489 self.use_ffi(ffi::STOREF32);
2490 uwriteln!(
2491 self.src,
2492 "mbt_ffi_storef32(({}) + {offset}, {})",
2493 operands[1],
2494 operands[0],
2495 offset = offset.size_wasm32()
2496 )
2497 }
2498
2499 Instruction::F64Store { offset } => {
2500 self.use_ffi(ffi::STOREF64);
2501 uwriteln!(
2502 self.src,
2503 "mbt_ffi_storef64(({}) + {offset}, {})",
2504 operands[1],
2505 operands[0],
2506 offset = offset.size_wasm32()
2507 )
2508 }
2509 Instruction::Malloc { size, .. } => {
2511 self.use_ffi(ffi::MALLOC);
2512 uwriteln!(self.src, "mbt_ffi_malloc({})", size.size_wasm32())
2513 }
2514
2515 Instruction::GuestDeallocate { .. } => {
2516 if !self.preserve_guest_allocations {
2517 self.use_ffi(ffi::FREE);
2518 uwriteln!(self.src, "mbt_ffi_free({})", operands[0])
2519 }
2520 }
2521
2522 Instruction::GuestDeallocateString => {
2523 if !self.preserve_guest_allocations {
2524 self.use_ffi(ffi::FREE);
2525 uwriteln!(self.src, "mbt_ffi_free({})", operands[0])
2526 }
2527 }
2528
2529 Instruction::GuestDeallocateVariant { blocks } => {
2530 let cases = self
2531 .blocks
2532 .drain(self.blocks.len() - blocks..)
2533 .enumerate()
2534 .map(|(i, Block { body, results, .. })| {
2535 assert!(results.is_empty());
2536 if body.is_empty() {
2537 format!("{i} => ()")
2538 } else {
2539 format!(
2540 "{i} => {{
2541 {body}
2542 }}"
2543 )
2544 }
2545 })
2546 .collect::<Vec<_>>()
2547 .join("\n");
2548
2549 let op = &operands[0];
2550
2551 uwrite!(
2552 self.src,
2553 "
2554 match ({op}) {{
2555 {cases}
2556 _ => panic()
2557 }}
2558 "
2559 );
2560 }
2561
2562 Instruction::GuestDeallocateList { element } => {
2563 let Block { body, results, .. } = self.blocks.pop().unwrap();
2564 assert!(results.is_empty());
2565
2566 let address = &operands[0];
2567 let length = &operands[1];
2568
2569 let size = self
2570 .interface_gen
2571 .world_gen
2572 .sizes
2573 .size(element)
2574 .size_wasm32();
2575 if !body.trim().is_empty() {
2578 let index = self.locals.tmp("index");
2579
2580 uwrite!(
2581 self.src,
2582 "
2583 for {index} = 0; {index} < ({length}); {index} = {index} + 1 {{
2584 let iter_base = ({address}) + ({index} * {size})
2585 {body}
2586 }}
2587 "
2588 );
2589 }
2590
2591 if !self.preserve_guest_allocations {
2592 self.use_ffi(ffi::FREE);
2593 uwriteln!(self.src, "mbt_ffi_free({address})",);
2594 }
2595 }
2596
2597 Instruction::Flush { amt } => {
2598 results.extend(operands.iter().take(*amt).cloned());
2599 }
2600
2601 Instruction::FutureLift { ty, .. } => {
2602 self.emit_future_lift(*ty, operands, results);
2603 }
2604
2605 Instruction::FutureLower { ty, .. } => {
2606 self.emit_future_lower(*ty, operands, results);
2607 }
2608
2609 Instruction::AsyncTaskReturn { params, .. } => {
2610 self.capture_task_return(params, operands);
2611 }
2612
2613 Instruction::StreamLower { ty, .. } => {
2614 self.emit_stream_lower(*ty, operands, results);
2615 }
2616
2617 Instruction::StreamLift { ty, .. } => {
2618 self.emit_stream_lift(*ty, operands, results);
2619 }
2620 Instruction::DropHandle { ty } => {
2621 let is_endpoint = match ty {
2622 Type::Id(id) => matches!(
2623 &self.interface_gen.resolve.types[*id].kind,
2624 TypeDefKind::Future(_) | TypeDefKind::Stream(_)
2625 ),
2626 _ => false,
2627 };
2628 if !self.commit_endpoints {
2629 let method = if self.sync_endpoint_drop && is_endpoint {
2630 "drop_sync"
2631 } else {
2632 "drop"
2633 };
2634 uwriteln!(self.src, "{}.{method}()", operands[0]);
2635 }
2636 }
2637 Instruction::ErrorContextLower { .. } | Instruction::ErrorContextLift { .. } => todo!(),
2638 Instruction::FixedLengthListLift {
2639 element: _,
2640 size,
2641 id: _,
2642 } => {
2643 let array = self.locals.tmp("array");
2644 let mut elements = String::new();
2645 for a in operands.drain(0..(*size as usize)) {
2646 elements.push_str(&a);
2647 elements.push_str(", ");
2648 }
2649 uwriteln!(self.src, "let {array} : FixedArray[_] = [{elements}]");
2650 results.push(array);
2651 }
2652 Instruction::FixedLengthListLower {
2653 element: _,
2654 size,
2655 id: _,
2656 } => {
2657 uwriteln!(
2658 self.src,
2659 "if ({}).length() != {size} {{ panic() }}",
2660 operands[0]
2661 );
2662 for i in 0..(*size as usize) {
2663 results.push(format!("({})[{i}]", operands[0]));
2664 }
2665 }
2666 Instruction::FixedLengthListLowerToMemory {
2667 element,
2668 size: fixed_length,
2669 id: _,
2670 } => {
2671 let Block {
2672 body,
2673 results: block_results,
2674 } = self.blocks.pop().unwrap();
2675 assert!(block_results.is_empty());
2676
2677 let vec = operands[0].clone();
2678 let target = operands[1].clone();
2679 let size = self.sizes().size(element).size_wasm32();
2680 let index = self.locals.tmp("index");
2681
2682 uwrite!(
2683 self.src,
2684 "
2685 if ({vec}).length() != {fixed_length} {{ panic() }}
2686 for {index} = 0; {index} < {fixed_length}; {index} = {index} + 1 {{
2687 let iter_elem = ({vec})[{index}]
2688 let iter_base = ({target}) + ({index} * {size})
2689 {body}
2690 }}
2691 ",
2692 );
2693 }
2694 Instruction::FixedLengthListLiftFromMemory {
2695 element,
2696 size: fll_size,
2697 id: _,
2698 } => {
2699 let Block {
2700 body,
2701 results: block_results,
2702 } = self.blocks.pop().unwrap();
2703 let address = &operands[0];
2704 let array = self.locals.tmp("array");
2705 let ty = self.resolve_type_name(element);
2706 let elem_size = self.sizes().size(element).size_wasm32();
2707 let index = self.locals.tmp("index");
2708
2709 let result = match &block_results[..] {
2710 [result] => result,
2711 _ => todo!("result count == {}", block_results.len()),
2712 };
2713
2714 uwrite!(
2715 self.src,
2716 "
2717 let {array} : Array[{ty}] = []
2718 for {index} = 0; {index} < {fll_size}; {index} = {index} + 1 {{
2719 let iter_base = ({address}) + ({index} * {elem_size})
2720 {body}
2721 {array}.push({result})
2722 }}
2723 ",
2724 );
2725
2726 results.push(format!("FixedArray::from_array({array}[:])"));
2727 }
2728
2729 Instruction::MapLower {
2730 key,
2731 value,
2732 realloc,
2733 } => {
2734 let Block {
2735 body,
2736 results: block_results,
2737 } = self.blocks.pop().unwrap();
2738 assert!(block_results.is_empty());
2739
2740 let op = &operands[0];
2741 let entry = self.interface_gen.world_gen.sizes.record([*key, *value]);
2742 let size = entry.size.size_wasm32();
2743 let address = self.locals.tmp("address");
2744 let index = self.locals.tmp("index");
2745 let iter_map_key = self.locals.tmp("iter_map_key");
2746 let iter_map_value = self.locals.tmp("iter_map_value");
2747
2748 self.use_ffi(ffi::MALLOC);
2749 uwrite!(
2750 self.src,
2751 "
2752 let {address} = mbt_ffi_malloc(({op}).length() * {size});
2753 let mut {index} = 0
2754 ({op}).each(fn({iter_map_key}, {iter_map_value}) {{
2755 let iter_map_key = {iter_map_key}
2756 let iter_map_value = {iter_map_value}
2757 let iter_base = {address} + ({index} * {size})
2758 {body}
2759 {index} = {index} + 1
2760 }})
2761 ",
2762 );
2763
2764 results.push(address.clone());
2765 results.push(format!("({op}).length()"));
2766
2767 if realloc.is_none() {
2768 self.cleanup.push(Cleanup { address });
2769 }
2770 }
2771
2772 Instruction::MapLift { key, value, .. } => {
2773 let Block {
2774 body,
2775 results: block_results,
2776 } = self.blocks.pop().unwrap();
2777 let address = &operands[0];
2778 let length = &operands[1];
2779 let map = self.locals.tmp("map");
2780 let key_ty = self.resolve_type_name(key);
2781 let value_ty = self.resolve_type_name(value);
2782 let entry = self.interface_gen.world_gen.sizes.record([*key, *value]);
2783 let size = entry.size.size_wasm32();
2784 let index = self.locals.tmp("index");
2785
2786 let (body_key, body_value) = match &block_results[..] {
2787 [k, v] => (k, v),
2788 _ => todo!(
2789 "expected 2 results from map lift block, got {}",
2790 block_results.len()
2791 ),
2792 };
2793
2794 self.use_ffi(ffi::FREE);
2795 uwrite!(
2796 self.src,
2797 "
2798 let {map} : Map[{key_ty}, {value_ty}] = {{}}
2799 for {index} = 0; {index} < ({length}); {index} = {index} + 1 {{
2800 let iter_base = ({address}) + ({index} * {size})
2801 {body}
2802 {map}[{body_key}] = {body_value}
2803 }}
2804 mbt_ffi_free({address})
2805 ",
2806 );
2807
2808 results.push(map);
2809 }
2810
2811 Instruction::IterMapKey { .. } => results.push("iter_map_key".into()),
2812
2813 Instruction::IterMapValue { .. } => results.push("iter_map_value".into()),
2814
2815 Instruction::GuestDeallocateMap { key, value } => {
2816 let Block { body, results, .. } = self.blocks.pop().unwrap();
2817 assert!(results.is_empty());
2818
2819 let address = &operands[0];
2820 let length = &operands[1];
2821
2822 let entry = self.interface_gen.world_gen.sizes.record([*key, *value]);
2823 let size = entry.size.size_wasm32();
2824
2825 if !body.trim().is_empty() {
2826 let index = self.locals.tmp("index");
2827
2828 uwrite!(
2829 self.src,
2830 "
2831 for {index} = 0; {index} < ({length}); {index} = {index} + 1 {{
2832 let iter_base = ({address}) + ({index} * {size})
2833 {body}
2834 }}
2835 "
2836 );
2837 }
2838
2839 if !self.preserve_guest_allocations {
2840 self.use_ffi(ffi::FREE);
2841 uwriteln!(self.src, "mbt_ffi_free({address})",);
2842 }
2843 }
2844 }
2845 }
2846
2847 fn return_pointer(&mut self, size: ArchitectureSize, _align: Alignment) -> String {
2848 self.use_ffi(ffi::MALLOC);
2849 let address = self.locals.tmp("return_area");
2850 uwriteln!(
2851 self.src,
2852 "let {address} = mbt_ffi_malloc({})",
2853 size.size_wasm32(),
2854 );
2855 if self.interface_gen.direction == Direction::Import {
2858 self.cleanup.push(Cleanup {
2859 address: address.clone(),
2860 });
2861 }
2862 address
2863 }
2864
2865 fn push_block(&mut self) {
2866 self.block_storage.push(BlockStorage {
2867 body: mem::take(&mut self.src),
2868 cleanup: mem::take(&mut self.cleanup),
2869 });
2870 }
2871
2872 fn finish_block(&mut self, operands: &mut Vec<String>) {
2873 let BlockStorage { body, cleanup } = self.block_storage.pop().unwrap();
2874
2875 if !self.cleanup.is_empty() && !self.suppress_block_cleanup {
2876 self.needs_cleanup_list = true;
2877 self.use_ffi(ffi::FREE);
2878
2879 for cleanup in &self.cleanup {
2880 let address = &cleanup.address;
2881 uwriteln!(self.src, "cleanup_list.push({address})",);
2882 }
2883 }
2884
2885 self.cleanup = cleanup;
2886
2887 self.blocks.push(Block {
2888 body: mem::replace(&mut self.src, body),
2889 results: mem::take(operands),
2890 });
2891 }
2892
2893 fn sizes(&self) -> &SizeAlign {
2894 &self.interface_gen.world_gen.sizes
2895 }
2896
2897 fn is_list_canonical(&self, _resolve: &Resolve, element: &Type) -> bool {
2898 matches!(
2899 element,
2900 Type::U8 | Type::U32 | Type::U64 | Type::S32 | Type::S64 | Type::F32 | Type::F64
2901 )
2902 }
2903}
2904
2905fn perform_cast(op: &str, cast: &Bitcast) -> String {
2906 match cast {
2907 Bitcast::I32ToF32 => {
2908 format!("({op}).reinterpret_as_float()")
2909 }
2910 Bitcast::I64ToF32 => format!("({op}).to_int().reinterpret_as_float()"),
2911 Bitcast::F32ToI32 => {
2912 format!("({op}).reinterpret_as_int()")
2913 }
2914 Bitcast::F32ToI64 => format!("({op}).reinterpret_as_int().to_int64()"),
2915 Bitcast::I64ToF64 => {
2916 format!("({op}).reinterpret_as_double()")
2917 }
2918 Bitcast::F64ToI64 => {
2919 format!("({op}).reinterpret_as_int64()")
2920 }
2921 Bitcast::LToI64 | Bitcast::PToP64 | Bitcast::I32ToI64 => format!("Int::to_int64({op})"),
2922 Bitcast::I64ToL | Bitcast::P64ToP | Bitcast::I64ToI32 => format!("Int64::to_int({op})"),
2923 Bitcast::I64ToP64
2924 | Bitcast::P64ToI64
2925 | Bitcast::I32ToP
2926 | Bitcast::PToI32
2927 | Bitcast::I32ToL
2928 | Bitcast::LToI32
2929 | Bitcast::LToP
2930 | Bitcast::PToL
2931 | Bitcast::None => op.to_owned(),
2932
2933 Bitcast::Sequence(sequence) => {
2934 let [first, second] = &**sequence;
2935 perform_cast(&perform_cast(op, first), second)
2936 }
2937 }
2938}
2939
2940fn wasm_type(ty: WasmType) -> &'static str {
2941 match ty {
2942 WasmType::I32 => "Int",
2943 WasmType::I64 => "Int64",
2944 WasmType::F32 => "Float",
2945 WasmType::F64 => "Double",
2946 WasmType::Pointer => "Int",
2947 WasmType::PointerOrI64 => "Int64",
2948 WasmType::Length => "Int",
2949 }
2950}
2951
2952fn flags_repr(flags: &Flags) -> Int {
2953 match flags.repr() {
2954 FlagsRepr::U8 => Int::U8,
2955 FlagsRepr::U16 => Int::U16,
2956 FlagsRepr::U32(1) => Int::U32,
2957 FlagsRepr::U32(2) => Int::U64,
2958 repr => panic!("unimplemented flags {repr:?}"),
2959 }
2960}
2961
2962fn type_contains_future_or_stream(resolve: &Resolve, ty: &Type) -> bool {
2963 let mut live = LiveTypes::default();
2964 live.add_type(resolve, ty);
2965 live.iter().any(|id| {
2966 matches!(
2967 resolve.types[id].kind,
2968 TypeDefKind::Future(_) | TypeDefKind::Stream(_)
2969 )
2970 })
2971}
2972
2973fn type_contains_endpoint_fixed_length_list_combination(
2974 resolve: &Resolve,
2975 ty: &Type,
2976 inside_fixed_length_list: bool,
2977 inside_endpoint: bool,
2978) -> bool {
2979 let Type::Id(id) = ty else {
2980 return false;
2981 };
2982
2983 match &resolve.types[*id].kind {
2984 TypeDefKind::Future(payload) | TypeDefKind::Stream(payload) => {
2985 inside_fixed_length_list
2986 || payload.as_ref().is_some_and(|ty| {
2987 type_contains_endpoint_fixed_length_list_combination(resolve, ty, false, true)
2988 })
2989 }
2990 TypeDefKind::FixedLengthList(ty, _) => {
2991 inside_endpoint
2992 || type_contains_endpoint_fixed_length_list_combination(resolve, ty, true, false)
2993 }
2994 TypeDefKind::Record(record) => record.fields.iter().any(|field| {
2995 type_contains_endpoint_fixed_length_list_combination(
2996 resolve,
2997 &field.ty,
2998 inside_fixed_length_list,
2999 inside_endpoint,
3000 )
3001 }),
3002 TypeDefKind::Tuple(tuple) => tuple.types.iter().any(|ty| {
3003 type_contains_endpoint_fixed_length_list_combination(
3004 resolve,
3005 ty,
3006 inside_fixed_length_list,
3007 inside_endpoint,
3008 )
3009 }),
3010 TypeDefKind::Variant(variant) => variant
3011 .cases
3012 .iter()
3013 .filter_map(|case| case.ty.as_ref())
3014 .any(|ty| {
3015 type_contains_endpoint_fixed_length_list_combination(
3016 resolve,
3017 ty,
3018 inside_fixed_length_list,
3019 inside_endpoint,
3020 )
3021 }),
3022 TypeDefKind::Option(ty) | TypeDefKind::List(ty) | TypeDefKind::Type(ty) => {
3023 type_contains_endpoint_fixed_length_list_combination(
3024 resolve,
3025 ty,
3026 inside_fixed_length_list,
3027 inside_endpoint,
3028 )
3029 }
3030 TypeDefKind::Map(key, value) => {
3031 type_contains_endpoint_fixed_length_list_combination(
3032 resolve,
3033 key,
3034 inside_fixed_length_list,
3035 inside_endpoint,
3036 ) || type_contains_endpoint_fixed_length_list_combination(
3037 resolve,
3038 value,
3039 inside_fixed_length_list,
3040 inside_endpoint,
3041 )
3042 }
3043 TypeDefKind::Result(result) => result.ok.iter().chain(result.err.iter()).any(|ty| {
3044 type_contains_endpoint_fixed_length_list_combination(
3045 resolve,
3046 ty,
3047 inside_fixed_length_list,
3048 inside_endpoint,
3049 )
3050 }),
3051 TypeDefKind::Resource
3052 | TypeDefKind::Handle(_)
3053 | TypeDefKind::Flags(_)
3054 | TypeDefKind::Enum(_) => false,
3055 TypeDefKind::Unknown => unreachable!(),
3056 }
3057}
3058
3059fn world_contains_endpoint_fixed_length_list_combination(
3060 resolve: &Resolve,
3061 world: WorldId,
3062) -> bool {
3063 let mut live = LiveTypes::default();
3064 live.add_world(resolve, world);
3065 live.iter().any(|id| {
3066 type_contains_endpoint_fixed_length_list_combination(resolve, &Type::Id(id), false, false)
3067 })
3068}
3069
3070fn world_contains_future_or_stream(resolve: &Resolve, world: WorldId) -> bool {
3071 let mut live = LiveTypes::default();
3072 live.add_world(resolve, world);
3073 live.iter().any(|id| {
3074 matches!(
3075 resolve.types[id].kind,
3076 TypeDefKind::Future(_) | TypeDefKind::Stream(_)
3077 )
3078 })
3079}
3080
3081fn indent(code: &str) -> Source {
3082 let mut indented = Source::default();
3083 let mut was_empty = false;
3084 for line in code.lines() {
3085 let trimmed = line.trim();
3086 if trimmed.is_empty() {
3087 if was_empty {
3088 continue;
3089 }
3090 was_empty = true;
3091 } else {
3092 was_empty = false;
3093 }
3094
3095 if trimmed.starts_with('}') {
3096 indented.deindent(2)
3097 }
3098 indented.push_str(trimmed);
3099 if trimmed.ends_with('{') && !trimmed.starts_with("///") {
3100 indented.indent(2)
3101 }
3102 indented.push_str("\n");
3103 }
3104 indented
3105}
3106
3107fn print_docs(src: &mut String, docs: &Docs) {
3108 uwrite!(src, "///|");
3109 if let Some(docs) = &docs.contents {
3110 for line in docs.trim().lines() {
3111 uwrite!(src, "\n/// {line}");
3112 }
3113 }
3114}
3115
3116#[cfg(test)]
3117mod tests {
3118 use super::*;
3119
3120 fn try_generate_with_opts(wit: &str, world: &str, opts: Opts) -> Result<Files> {
3121 let mut resolve = Resolve::default();
3122 let pkg = resolve.push_str("test.wit", wit).unwrap();
3123 let world = resolve.select_world(&[pkg], Some(world)).unwrap();
3124 let mut files = Files::default();
3125 let mut generator = MoonBit {
3126 opts,
3127 ..MoonBit::default()
3128 };
3129 generator.generate(&mut resolve, world, &mut files)?;
3130 Ok(files)
3131 }
3132
3133 fn try_generate(wit: &str, world: &str) -> Result<Files> {
3134 try_generate_with_opts(
3135 wit,
3136 world,
3137 Opts {
3138 gen_dir: "gen".into(),
3139 ..Opts::default()
3140 },
3141 )
3142 }
3143
3144 fn generate(wit: &str, world: &str) -> Files {
3145 try_generate(wit, world).unwrap()
3146 }
3147
3148 fn file<'a>(files: &'a Files, path: &str) -> &'a str {
3149 let contents = files
3150 .iter()
3151 .find_map(|(name, contents)| (name == path).then_some(contents))
3152 .unwrap_or_else(|| {
3153 let names = files
3154 .iter()
3155 .map(|(name, _)| name)
3156 .collect::<Vec<_>>()
3157 .join(", ");
3158 std::panic!("missing generated file `{path}`; generated: {names}")
3159 });
3160 std::str::from_utf8(contents).unwrap()
3161 }
3162
3163 #[test]
3164 fn endpoint_free_sync_generation_matches_golden() {
3165 let files = generate(
3166 r#"
3167 package a:b;
3168
3169 world runner {
3170 import add: func(a: u32, b: u32) -> u32;
3171 export echo: func(value: u32) -> u32;
3172 }
3173 "#,
3174 "runner",
3175 );
3176
3177 let mut entries = files.iter().collect::<Vec<_>>();
3180 entries.sort_by_key(|(name, _)| *name);
3181 let fingerprints = entries
3182 .into_iter()
3183 .map(|(name, contents)| {
3184 let contents = if contents.starts_with(b"// Generated by") {
3185 let preamble_end = contents.iter().position(|byte| *byte == b'\n').unwrap() + 1;
3186 &contents[preamble_end..]
3187 } else {
3188 contents
3189 };
3190 let hash = contents
3191 .iter()
3192 .fold(0xcbf29ce484222325_u64, |mut hash, byte| {
3193 hash ^= u64::from(*byte);
3194 hash.wrapping_mul(0x100000001b3)
3195 });
3196 (name.to_string(), hash)
3197 })
3198 .collect::<Vec<_>>();
3199
3200 assert_eq!(
3201 fingerprints,
3202 vec![
3203 ("gen/ffi.mbt".into(), 10220319382745692950),
3204 ("gen/moon.pkg.json".into(), 15894505084782869543),
3205 ("gen/world/runner/ffi.mbt".into(), 14715999128234894449),
3206 ("gen/world/runner/moon.pkg.json".into(), 6361049410124596525,),
3207 ("gen/world/runner/top.mbt".into(), 12192865914091673515,),
3208 ("moon.mod.json".into(), 14111159726816684443),
3209 ("world/runner/ffi_import.mbt".into(), 17812050158059242657,),
3210 ("world/runner/import.mbt".into(), 5430383198437179961),
3211 ("world/runner/moon.pkg.json".into(), 6361049410124596525,),
3212 ]
3213 );
3214 }
3215
3216 #[test]
3217 fn sync_world_does_not_emit_async_runtime_or_wrappers() {
3218 let files = generate(
3219 r#"
3220 package a:b;
3221
3222 world runner {
3223 import add: func(a: u32, b: u32) -> u32;
3224 }
3225 "#,
3226 "runner",
3227 );
3228
3229 let import = file(&files, "world/runner/import.mbt");
3230 assert!(import.contains("pub fn add("));
3231 for async_name in [
3232 "pub async fn",
3233 "with_waitableset",
3234 "TaskGroup",
3235 "background_group",
3236 "CMFuture",
3237 "CMStream",
3238 ] {
3239 assert!(!import.contains(async_name));
3240 }
3241 assert!(
3242 files
3243 .iter()
3244 .all(|(name, _)| !name.starts_with("async-core/async_"))
3245 );
3246 let package = file(&files, "gen/moon.pkg.json");
3247 assert!(!package.contains("supported-targets"), "{package}");
3248 }
3249
3250 #[test]
3251 fn async_filters_respect_import_export_direction() {
3252 let wit = r#"
3253 package a:b;
3254 world runner { import run: func(); }
3255 "#;
3256
3257 let mut import_opts = Opts {
3258 gen_dir: "gen".into(),
3259 ..Opts::default()
3260 };
3261 import_opts.async_.push("import:run");
3262 let import_files = try_generate_with_opts(wit, "runner", import_opts).unwrap();
3263 let import = file(&import_files, "world/runner/import.mbt");
3264 let import_ffi = file(&import_files, "world/runner/ffi_import.mbt");
3265 assert!(import.contains("pub async fn run("), "{import}");
3266 assert!(import_ffi.contains("[async-lower]run"), "{import_ffi}");
3267
3268 let mut export_opts = Opts {
3269 gen_dir: "gen".into(),
3270 ..Opts::default()
3271 };
3272 export_opts.async_.push("export:run");
3273 let export_files = try_generate_with_opts(wit, "runner", export_opts).unwrap();
3274 let import = file(&export_files, "world/runner/import.mbt");
3275 let import_ffi = file(&export_files, "world/runner/ffi_import.mbt");
3276 assert!(import.contains("pub fn run("), "{import}");
3277 assert!(!import.contains("pub async fn run("), "{import}");
3278 assert!(!import_ffi.contains("[async-lower]run"), "{import_ffi}");
3279 }
3280
3281 #[test]
3282 fn async_import_indirect_params_emit_malloc_builtin() {
3283 let files = generate(
3284 r#"
3285 package a:b;
3286
3287 interface types {
3288 resource descriptor {
3289 run: async func(first: string, second: list<s32>);
3290 }
3291 }
3292
3293 world bindings { import types; }
3294 "#,
3295 "bindings",
3296 );
3297
3298 let ffi = file(&files, "interface/a/b/types/ffi.mbt");
3299 assert!(ffi.contains("extern \"wasm\" fn mbt_ffi_malloc"), "{ffi}");
3300 }
3301
3302 #[test]
3303 fn endpoint_payload_lowering_reserves_destination_pointer() {
3304 let files = generate(
3305 r#"
3306 package a:b;
3307
3308 interface api {
3309 consume: async func(value: future<string>);
3310 }
3311
3312 world client { import api; }
3313 "#,
3314 "client",
3315 );
3316
3317 let ffi = file(&files, "interface/a/b/api/ffi.mbt");
3318 assert!(ffi.contains("let ptr0 = mbt_ffi_str2ptr(value)"), "{ffi}");
3319 assert!(ffi.contains("mbt_ffi_store32((ptr) + 0, ptr0)"), "{ffi}");
3320 }
3321
3322 #[test]
3323 fn type_only_endpoints_emit_async_runtime() {
3324 let files = generate(
3325 r#"
3326 package a:b;
3327
3328 interface types {
3329 record payload { ready: future<u32> }
3330 }
3331
3332 world runner { import types; }
3333 "#,
3334 "runner",
3335 );
3336
3337 let source = file(&files, "interface/a/b/types/top.mbt");
3338 assert!(source.contains("@async-core.Future[UInt]"), "{source}");
3339 let package = file(&files, "interface/a/b/types/moon.pkg.json");
3340 assert!(
3341 package.contains("\"supported-targets\": \"+wasm\""),
3342 "{package}"
3343 );
3344 let link_package = file(&files, "gen/moon.pkg.json");
3345 assert!(
3346 link_package.contains("\"supported-targets\": \"+wasm\""),
3347 "{link_package}"
3348 );
3349 file(&files, "async-core/moon.pkg.json");
3350 file(&files, "async-core/async_trait.mbt");
3351 }
3352
3353 #[test]
3354 fn endpoint_container_types_omit_value_derives() {
3355 let mut resolve = Resolve::default();
3356 let pkg = resolve
3357 .push_str(
3358 "test.wit",
3359 r#"
3360 package a:b;
3361
3362 interface types {
3363 record plain { value: u32 }
3364 record nested { value: option<future<u32>> }
3365 variant streamed { none, value(list<stream<string>>) }
3366 use-types: func(a: plain, b: nested, c: streamed);
3367 }
3368
3369 world runner { import types; }
3370 "#,
3371 )
3372 .unwrap();
3373 let world = resolve.select_world(&[pkg], Some("runner")).unwrap();
3374 let mut files = Files::default();
3375 let mut generator = MoonBit {
3376 opts: Opts {
3377 derive: DeriveOpts {
3378 derive_debug: true,
3379 derive_show: true,
3380 derive_eq: true,
3381 derive_error: false,
3382 },
3383 gen_dir: "gen".into(),
3384 ..Opts::default()
3385 },
3386 ..MoonBit::default()
3387 };
3388 generator.generate(&mut resolve, world, &mut files).unwrap();
3389
3390 let source = file(&files, "interface/a/b/types/top.mbt");
3391 assert!(
3392 source.contains("struct Plain {\n value : UInt\n} derive(Debug, Show, Eq)"),
3393 "{source}"
3394 );
3395 assert!(
3396 source.contains("struct Nested {\n value : @async-core.Future[UInt]?\n}"),
3397 "{source}"
3398 );
3399 assert!(
3400 !source.contains("struct Nested {\n value : @async-core.Future[UInt]?\n} derive(")
3401 );
3402 assert!(
3403 source.contains(
3404 "Streamed {\n None\n Value(Array[@async-core.Stream[String]])\n}"
3405 ),
3406 "{source}"
3407 );
3408 assert!(!source.contains(
3409 "Streamed {\n None\n Value(Array[@async-core.Stream[String]])\n} derive("
3410 ));
3411 }
3412
3413 #[test]
3414 fn sync_functions_with_endpoints_remain_sync() {
3415 let files = generate(
3416 r#"
3417 package a:b;
3418
3419 world runner {
3420 import exchange: func(
3421 input: stream<u8>,
3422 ready: future<u32>,
3423 ) -> tuple<stream<u8>, future<u32>>;
3424 }
3425 "#,
3426 "runner",
3427 );
3428
3429 let import = file(&files, "world/runner/import.mbt");
3430 assert!(import.contains("pub fn exchange("));
3431 assert!(import.contains("@async-core.Stream[Byte]"));
3432 assert!(import.contains("@async-core.Future[UInt]"));
3433 assert!(!import.contains("pub async fn exchange"));
3434 assert!(!import.contains("background_group"));
3435 let call = import.find("wasmImportExchange").unwrap();
3436 let after_call = &import[call..];
3437 assert!(after_call.contains("StreamCommit"), "{import}");
3438 assert!(after_call.contains("FutureCommit"), "{import}");
3439 assert_eq!(import.matches("StreamLower(input)").count(), 1, "{import}");
3440 assert_eq!(import.matches("FutureLower(ready)").count(), 1, "{import}");
3441 assert!(
3442 files
3443 .iter()
3444 .any(|(name, _)| name == "async-core/async_trait.mbt")
3445 );
3446 }
3447
3448 #[test]
3449 fn async_export_background_group_name_is_deconflicted() {
3450 let files = generate(
3451 r#"
3452 package a:b;
3453 world service {
3454 export handle: async func(background-group: u32);
3455 }
3456 "#,
3457 "service",
3458 );
3459 let public = file(&files, "gen/world/service/top.mbt");
3460 assert!(
3461 public.contains(
3462 "background_group : UInt, background_group0 : @async-core.TaskGroup[Unit]"
3463 ),
3464 "{public}"
3465 );
3466 let wrapper = file(&files, "gen/world/service/ffi.mbt");
3467 assert!(
3468 wrapper.contains("with_task_group(async fn(background_group0)"),
3469 "{wrapper}"
3470 );
3471 assert!(
3472 wrapper.contains("handle((p0).reinterpret_as_uint(), background_group0)"),
3473 "{wrapper}"
3474 );
3475 }
3476
3477 #[test]
3478 fn async_export_surface_hides_component_model_bridge_types() {
3479 let files = generate(
3480 r#"
3481 package a:b;
3482
3483 interface handler {
3484 handle: async func(
3485 input: stream<u8>,
3486 ready: future<u32>,
3487 ) -> tuple<stream<u8>, future<u32>>;
3488 }
3489
3490 world service {
3491 export handler;
3492 }
3493 "#,
3494 "service",
3495 );
3496
3497 let public = file(&files, "gen/interface/a/b/handler/top.mbt");
3498 assert!(public.contains("input : @async-core.Stream[Byte]"));
3499 assert!(public.contains("ready : @async-core.Future[UInt]"));
3500 assert!(public.contains("background_group : @async-core.TaskGroup[Unit]"));
3501 for internal_name in [
3502 "CMFuture",
3503 "CMStream",
3504 "VTable",
3505 "take_cm_handle",
3506 "take_producer",
3507 "from_callbacks",
3508 ] {
3509 assert!(!public.contains(internal_name));
3510 }
3511
3512 let wrapper = file(&files, "gen/interface/a/b/handler/ffi.mbt");
3513 assert!(wrapper.contains("with_task_group(async fn(background_group)"));
3514 let root_wrapper = file(&files, "gen/ffi.mbt");
3515 for ffi in [wrapper, root_wrapper] {
3516 let lines = ffi.lines().collect::<Vec<_>>();
3517 for (index, line) in lines.iter().enumerate() {
3518 if line.trim_start().starts_with("pub fn wasmExport") {
3519 assert_eq!(lines[index - 1].trim(), "#doc(hidden)", "{ffi}");
3520 }
3521 }
3522 }
3523
3524 let coroutine = file(&files, "async-core/async_coroutine.mbt");
3525 assert!(!coroutine.contains("fn pause()"));
3526 assert!(!coroutine.contains("wait_until"));
3527 let task = file(&files, "async-core/async_task.mbt");
3528 assert!(task.contains("pub struct Task[X] {\n priv value : Ref[X?]"));
3529 let promise = file(&files, "async-core/async_promise.mbt");
3530 assert!(promise.contains("pub struct Promise[X]"));
3531 assert!(promise.contains("pub fn[X] Future::new()"));
3532 assert!(promise.contains("pub fn[X] Promise::complete"));
3533 let semaphore = file(&files, "async-core/async_semaphore.mbt");
3534 assert!(semaphore.contains("pub struct Semaphore"));
3535 assert!(semaphore.contains("pub async fn Semaphore::acquire"));
3536 let cond_var = file(&files, "async-core/async_cond_var.mbt");
3537 assert!(cond_var.contains("pub struct CondVar"));
3538 assert!(cond_var.contains("pub async fn CondVar::wait"));
3539 let mutex = file(&files, "async-core/async_mutex.mbt");
3540 assert!(mutex.contains("pub struct Mutex"));
3541 assert!(mutex.contains("pub async fn Mutex::acquire"));
3542
3543 let async_core = files
3544 .iter()
3545 .filter(|(name, _)| name.starts_with("async-core/"))
3546 .map(|(_, contents)| String::from_utf8_lossy(contents))
3547 .collect::<Vec<_>>()
3548 .join("\n");
3549 for hidden in [
3550 "#doc(hidden)\npub fn with_waitableset",
3551 "#doc(hidden)\npub fn cb",
3552 "#doc(hidden)\npub fn spawn_component_task_current",
3553 "#doc(hidden)\npub fn has_component_task_scope",
3554 "#doc(hidden)\npub async fn suspend_for_subtask",
3555 "#doc(hidden)\npub async fn suspend_for_future_read",
3556 "#doc(hidden)\npub async fn suspend_for_future_write_terminal",
3557 "#doc(hidden)\npub async fn suspend_for_stream_read",
3558 "#doc(hidden)\npub async fn suspend_for_stream_write",
3559 ] {
3560 assert!(
3561 async_core.contains(hidden),
3562 "runtime helper is public: {hidden}"
3563 );
3564 }
3565 for raw in [
3566 "pub extern \"wasm\" fn malloc",
3567 "pub extern \"wasm\" fn load32",
3568 "pub fn context_set",
3569 "pub fn context_get",
3570 "pub fn task_cancel",
3571 "pub fn backpressure_inc",
3572 "pub fn backpressure_dec",
3573 "[backpressure-inc]",
3574 "[backpressure-dec]",
3575 "pub fn current_coroutine",
3576 "pub fn detach_waitable",
3577 "pub fn has_immediately_ready_task",
3578 "pub fn no_more_work",
3579 "pub fn reschedule",
3580 "pub fn spawn(",
3581 "pub fn spawn_bg_current",
3582 "pub async fn suspend()",
3583 ] {
3584 assert!(
3585 !async_core.contains(raw),
3586 "raw async-core API leaked: {raw}"
3587 );
3588 }
3589 }
3590
3591 #[test]
3592 fn nested_endpoints_use_static_boundary_helpers() {
3593 let files = generate(
3594 r#"
3595 package test:moonbit-nested;
3596
3597 interface nested {
3598 relay: async func(
3599 value: future<future<stream<u8>>>,
3600 ) -> future<future<stream<u8>>>;
3601 relay-stream: async func(
3602 value: stream<future<u8>>,
3603 ) -> stream<future<u8>>;
3604 }
3605
3606 world service { export nested; }
3607 "#,
3608 "service",
3609 );
3610
3611 let ffi = file(&files, "gen/interface/test/moonbit-nested/nested/ffi.mbt");
3612 for site in [
3613 "RelayStream0StreamSource",
3614 "RelayFuture1FutureSource",
3615 "RelayFuture2FutureSource",
3616 "RelayStream3StreamLower",
3617 "RelayFuture4FutureLower",
3618 "RelayFuture5FutureLower",
3619 ] {
3620 assert!(ffi.contains(site), "missing static endpoint site {site}");
3621 }
3622 assert!(ffi.contains("[async-lower][future-read-2]relay"));
3623 assert!(ffi.contains("[async-lower][future-write-2]relay"));
3624 assert!(ffi.contains("suspend_for_future_write_terminal"));
3625 assert!(ffi.contains("let data_len = if data.length() < 1"));
3626 assert!(ffi.contains("let writer_lock = @async-core.Mutex()"));
3627 assert!(ffi.contains("let close_writer_serialized = async fn()"));
3628 assert!(ffi.contains("writer_lock.acquire()"));
3629 assert!(ffi.contains("defer writer_lock.release()"));
3630 assert!(
3631 ffi.contains("() => close_writer_serialized(),\n resume_on_cancel=true,")
3632 );
3633 assert!(!ffi.contains("defer close_writer()"));
3634 assert!(ffi.contains("read_cleanup : @async-core.CondVar"));
3635 assert!(ffi.contains("let read_count = if count < 64"));
3636 assert!(ffi.contains("self.read_cleanup.broadcast()"));
3637 assert!(ffi.contains("@async-core.cancel_future_read("));
3638 assert!(ffi.contains("@async-core.cancel_stream_read("));
3639 assert!(ffi.contains("@async-core.cancel_stream_write("));
3640 assert!(ffi.contains("[stream-cancel-write-"));
3641 assert!(!ffi.contains("suspend_for_future_cancel_read"));
3642 assert!(!ffi.contains("wait_until"));
3643 assert!(ffi.contains("RelayFuture5FutureLowerCommitted"));
3644 assert!(ffi.contains("RelayFuture4FutureRejectPrepared"));
3645 assert!(ffi.contains("producer.future.reject((value :"));
3646 assert!(ffi.contains("rejected component future unexpectedly transferred a value"));
3647 assert!(ffi.contains("RelayStream3StreamRejectPrepared"));
3648 assert!(!ffi.contains("RelayFuture5FutureRejectPrepared"));
3649
3650 let generated = files
3651 .iter()
3652 .map(|(name, contents)| format!("{name}\n{}", String::from_utf8_lossy(contents)))
3653 .collect::<Vec<_>>()
3654 .join("\n");
3655 for legacy in [
3656 "async_cm.mbt",
3657 "CMFutureVTable",
3658 "CMStreamVTable",
3659 "take_cm_handle",
3660 "new_cm_future",
3661 "new_cm_stream",
3662 ] {
3663 assert!(
3664 !generated.contains(legacy),
3665 "legacy bridge leaked: {legacy}"
3666 );
3667 }
3668
3669 let imports = generate(
3670 r#"
3671 package test:moonbit-nested;
3672
3673 interface nested {
3674 relay: async func(
3675 value: future<future<stream<u8>>>,
3676 ) -> future<future<stream<u8>>>;
3677 relay-stream: async func(
3678 value: stream<future<u8>>,
3679 ) -> stream<future<u8>>;
3680 }
3681
3682 world client { import nested; }
3683 "#,
3684 "client",
3685 );
3686 let import = file(&imports, "interface/test/moonbit-nested/nested/top.mbt");
3687 assert!(import.contains("if before_started"));
3688 assert!(import.contains(".drop_sync()"));
3689 assert!(import.contains("defer mbt_ffi_free(result_ptr)"));
3690 for misleading_name in ["_lower_ptr", "_lower_arg", "_subtask_code", "_result_ptr"] {
3691 assert!(!import.contains(misleading_name), "{import}");
3692 }
3693 assert_eq!(import.matches("FutureLower(value)").count(), 1, "{import}");
3694 assert_eq!(import.matches("StreamLower(value)").count(), 1, "{import}");
3695 assert!(!import.contains("cleanup_list"));
3696 }
3697
3698 #[test]
3699 fn fixed_length_lists_use_checked_fixed_arrays_but_reject_nested_endpoints() {
3700 let files = generate(
3701 r#"
3702 package test:fixed-array;
3703
3704 interface api {
3705 type pair = list<u32, 2>;
3706 type block = list<u32, 20>;
3707 accept: func(value: pair) -> pair;
3708 accept-block: func(value: block);
3709 }
3710
3711 world client { import api; }
3712 "#,
3713 "client",
3714 );
3715 let top = file(&files, "interface/test/fixed-array/api/top.mbt");
3716 assert!(top.contains("FixedArray[UInt]"), "{top}");
3717 assert!(top.contains(".length() != 2"), "{top}");
3718 assert!(top.contains(".length() != 20"), "{top}");
3719
3720 for unsupported in [
3721 "type unsupported = list<future<u32>, 1>;",
3722 "type unsupported = future<list<u32, 1>>;",
3723 ] {
3724 let wit = format!(
3725 r#"
3726 package test:fixed-endpoints;
3727
3728 interface api {{
3729 {unsupported}
3730 accept: func(value: unsupported);
3731 }}
3732
3733 world client {{ import api; }}
3734 "#
3735 );
3736 let error = match try_generate(&wit, "client") {
3737 Ok(_) => std::panic!(
3738 "fixed-length list and async endpoint combinations must be rejected"
3739 ),
3740 Err(error) => error,
3741 };
3742 assert!(
3743 error
3744 .to_string()
3745 .contains("combining future or stream types with fixed-length lists"),
3746 "{error:#}"
3747 );
3748 }
3749 }
3750
3751 #[test]
3752 fn cancelled_returned_async_import_recursively_cleans_result() {
3753 let files = generate(
3754 r#"
3755 package test:cancelled-result;
3756
3757 interface api {
3758 resource leaf;
3759 record payload {
3760 label: string,
3761 leaves: list<leaf>,
3762 ready: future<leaf>,
3763 }
3764 load: async func() -> payload;
3765 }
3766
3767 world client { import api; }
3768 "#,
3769 "client",
3770 );
3771
3772 let import = file(&files, "interface/test/cancelled-result/api/top.mbt");
3773 assert!(
3774 import.contains("suspend_for_subtask")
3775 && import.contains("fn() {")
3776 && import.contains(".drop_sync()")
3777 && import.contains(".drop()"),
3778 "{import}"
3779 );
3780 assert!(
3781 import.contains("mbt_ffi_free(mbt_ffi_load32((result_ptr) + 0))")
3782 && import.contains("mbt_ffi_free(mbt_ffi_load32((result_ptr) + 8))")
3783 && import.contains("defer mbt_ffi_free(result_ptr)"),
3784 "cancelled returned result must free its strings, lists, and result area: {import}"
3785 );
3786 }
3787
3788 #[test]
3789 fn async_runtime_traps_unhandled_export_failure() {
3790 let files = generate(
3791 r#"
3792 package test:failing-export;
3793 world service { export run: async func(); }
3794 "#,
3795 "service",
3796 );
3797
3798 let event_loop = file(&files, "async-core/async_ev.mbt");
3799 assert!(
3800 event_loop.contains("abort(\"async export failed before task return\")")
3801 && event_loop.contains("if !(ev.resolved.get(waitable_set) is Some(true))"),
3802 "{event_loop}"
3803 );
3804 }
3805
3806 #[test]
3807 fn async_runtime_borrows_waitable_poll_payload() {
3808 let files = generate(
3809 r#"
3810 package test:poll-payload;
3811 world service { export run: async func(); }
3812 "#,
3813 "service",
3814 );
3815
3816 let abi = file(&files, "async-core/async_abi.mbt");
3817 assert!(
3818 abi.contains("#borrow(array)\nextern \"wasm\" fn int_array2ptr"),
3819 "{abi}"
3820 );
3821 assert!(!abi.contains("#owned(array)"), "{abi}");
3822 }
3823
3824 #[test]
3825 fn async_runtime_uses_baseline_subtask_cancel() {
3826 let files = generate(
3827 r#"
3828 package test:subtask-cancel;
3829 world service { export run: async func(); }
3830 "#,
3831 "service",
3832 );
3833
3834 let abi = file(&files, "async-core/async_abi.mbt");
3835 assert!(
3836 abi.contains(r#""$root" "[subtask-cancel]""#)
3837 && !abi.contains("[async-lower][subtask-cancel]"),
3838 "{abi}"
3839 );
3840 }
3841
3842 #[test]
3843 fn unit_future_intrinsics_use_wit_parser_unit_name() {
3844 let files = generate(
3845 r#"
3846 package a:b;
3847
3848 world runner {
3849 import exchange: func(value: future) -> future;
3850 }
3851 "#,
3852 "runner",
3853 );
3854
3855 let ffi = file(&files, "world/runner/ffi_import.mbt");
3856 assert!(ffi.contains(r#""$root" "[future-new-unit]exchange""#));
3857 assert!(ffi.contains(r#""$root" "[async-lower][future-read-unit]exchange""#));
3858 assert!(ffi.contains(r#""$root" "[future-cancel-read-unit]exchange""#));
3859 }
3860}