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 fixed_primitive_streams_use_four_kibibyte_buffer_budget() {
3450 for (wit_type, expected_window) in [
3451 ("u8", 4096),
3452 ("s8", 4096),
3453 ("u16", 2048),
3454 ("s16", 2048),
3455 ("u32", 1024),
3456 ("s32", 1024),
3457 ("f32", 1024),
3458 ("char", 1024),
3459 ("u64", 512),
3460 ("s64", 512),
3461 ("f64", 512),
3462 ("bool", 64),
3463 ("string", 64),
3464 ] {
3465 let wit = format!(
3466 r#"
3467 package a:b;
3468
3469 interface api {{
3470 type scalar = {wit_type};
3471 exchange: func(input: stream<scalar>) -> stream<scalar>;
3472 }}
3473
3474 world runner {{ import api; }}
3475 "#
3476 );
3477 let files = generate(&wit, "runner");
3478 let generated = files
3479 .iter()
3480 .map(|(_, contents)| String::from_utf8_lossy(contents))
3481 .collect::<Vec<_>>()
3482 .join("\n");
3483 assert!(
3484 generated.contains(&format!("let read_count = if count < {expected_window}")),
3485 "unexpected read window for {wit_type}: {generated}"
3486 );
3487 assert!(
3488 generated.contains(&format!(
3489 "let data_len = if data.length() < {expected_window}"
3490 )),
3491 "unexpected write window for {wit_type}: {generated}"
3492 );
3493 let compact = generated.split_whitespace().collect::<Vec<_>>().join(" ");
3494 assert!(
3495 compact.contains(&format!("Some(cleanup_value), {expected_window}, )")),
3496 "sink did not receive the {expected_window}-element window for {wit_type}: \
3497 {generated}"
3498 );
3499 }
3500
3501 let runtime = generate(
3502 r#"
3503 package a:b;
3504 world runner {
3505 import exchange: func(input: stream<u8>) -> stream<u8>;
3506 }
3507 "#,
3508 "runner",
3509 );
3510 let sink = file(&runtime, "async-core/async_trait.mbt");
3511 assert!(sink.contains("priv write_window_size : Int"));
3512 assert!(sink.contains("data.length() < self.write_window_size"));
3513 assert!(sink.contains("FixedArray::from_array(data[:length])"));
3514 assert!(sink.contains("data[:length].to_fixedarray()"));
3515 assert!(!sink.contains("FixedArray::makei"));
3516 }
3517
3518 #[test]
3519 fn async_export_background_group_name_is_deconflicted() {
3520 let files = generate(
3521 r#"
3522 package a:b;
3523 world service {
3524 export handle: async func(background-group: u32);
3525 }
3526 "#,
3527 "service",
3528 );
3529 let public = file(&files, "gen/world/service/top.mbt");
3530 assert!(
3531 public.contains(
3532 "background_group : UInt, background_group0 : @async-core.TaskGroup[Unit]"
3533 ),
3534 "{public}"
3535 );
3536 let wrapper = file(&files, "gen/world/service/ffi.mbt");
3537 assert!(
3538 wrapper.contains("with_task_group(async fn(background_group0)"),
3539 "{wrapper}"
3540 );
3541 assert!(
3542 wrapper.contains("handle((p0).reinterpret_as_uint(), background_group0)"),
3543 "{wrapper}"
3544 );
3545 }
3546
3547 #[test]
3548 fn async_export_surface_hides_component_model_bridge_types() {
3549 let files = generate(
3550 r#"
3551 package a:b;
3552
3553 interface handler {
3554 handle: async func(
3555 input: stream<u8>,
3556 ready: future<u32>,
3557 ) -> tuple<stream<u8>, future<u32>>;
3558 }
3559
3560 world service {
3561 export handler;
3562 }
3563 "#,
3564 "service",
3565 );
3566
3567 let public = file(&files, "gen/interface/a/b/handler/top.mbt");
3568 assert!(public.contains("input : @async-core.Stream[Byte]"));
3569 assert!(public.contains("ready : @async-core.Future[UInt]"));
3570 assert!(public.contains("background_group : @async-core.TaskGroup[Unit]"));
3571 for internal_name in [
3572 "CMFuture",
3573 "CMStream",
3574 "VTable",
3575 "take_cm_handle",
3576 "take_producer",
3577 "from_callbacks",
3578 ] {
3579 assert!(!public.contains(internal_name));
3580 }
3581
3582 let wrapper = file(&files, "gen/interface/a/b/handler/ffi.mbt");
3583 assert!(wrapper.contains("with_task_group(async fn(background_group)"));
3584 let root_wrapper = file(&files, "gen/ffi.mbt");
3585 for ffi in [wrapper, root_wrapper] {
3586 let lines = ffi.lines().collect::<Vec<_>>();
3587 for (index, line) in lines.iter().enumerate() {
3588 if line.trim_start().starts_with("pub fn wasmExport") {
3589 assert_eq!(lines[index - 1].trim(), "#doc(hidden)", "{ffi}");
3590 }
3591 }
3592 }
3593
3594 let coroutine = file(&files, "async-core/async_coroutine.mbt");
3595 assert!(!coroutine.contains("fn pause()"));
3596 assert!(!coroutine.contains("wait_until"));
3597 let task = file(&files, "async-core/async_task.mbt");
3598 assert!(task.contains("pub struct Task[X] {\n priv value : Ref[X?]"));
3599 let promise = file(&files, "async-core/async_promise.mbt");
3600 assert!(promise.contains("pub struct Promise[X]"));
3601 assert!(promise.contains("pub fn[X] Future::new()"));
3602 assert!(promise.contains("pub fn[X] Promise::complete"));
3603 let semaphore = file(&files, "async-core/async_semaphore.mbt");
3604 assert!(semaphore.contains("pub struct Semaphore"));
3605 assert!(semaphore.contains("pub async fn Semaphore::acquire"));
3606 let cond_var = file(&files, "async-core/async_cond_var.mbt");
3607 assert!(cond_var.contains("pub struct CondVar"));
3608 assert!(cond_var.contains("pub async fn CondVar::wait"));
3609 let mutex = file(&files, "async-core/async_mutex.mbt");
3610 assert!(mutex.contains("pub struct Mutex"));
3611 assert!(mutex.contains("pub async fn Mutex::acquire"));
3612
3613 let async_core = files
3614 .iter()
3615 .filter(|(name, _)| name.starts_with("async-core/"))
3616 .map(|(_, contents)| String::from_utf8_lossy(contents))
3617 .collect::<Vec<_>>()
3618 .join("\n");
3619 for hidden in [
3620 "#doc(hidden)\npub fn with_waitableset",
3621 "#doc(hidden)\npub fn cb",
3622 "#doc(hidden)\npub fn spawn_component_task_current",
3623 "#doc(hidden)\npub fn has_component_task_scope",
3624 "#doc(hidden)\npub async fn suspend_for_subtask",
3625 "#doc(hidden)\npub async fn suspend_for_future_read",
3626 "#doc(hidden)\npub async fn suspend_for_future_write_terminal",
3627 "#doc(hidden)\npub async fn suspend_for_stream_read",
3628 "#doc(hidden)\npub async fn suspend_for_stream_write",
3629 ] {
3630 assert!(
3631 async_core.contains(hidden),
3632 "runtime helper is public: {hidden}"
3633 );
3634 }
3635 for raw in [
3636 "pub extern \"wasm\" fn malloc",
3637 "pub extern \"wasm\" fn load32",
3638 "pub fn context_set",
3639 "pub fn context_get",
3640 "pub fn task_cancel",
3641 "pub fn backpressure_inc",
3642 "pub fn backpressure_dec",
3643 "[backpressure-inc]",
3644 "[backpressure-dec]",
3645 "pub fn current_coroutine",
3646 "pub fn detach_waitable",
3647 "pub fn has_immediately_ready_task",
3648 "pub fn no_more_work",
3649 "pub fn reschedule",
3650 "pub fn spawn(",
3651 "pub fn spawn_bg_current",
3652 "pub async fn suspend()",
3653 ] {
3654 assert!(
3655 !async_core.contains(raw),
3656 "raw async-core API leaked: {raw}"
3657 );
3658 }
3659 }
3660
3661 #[test]
3662 fn nested_endpoints_use_static_boundary_helpers() {
3663 let files = generate(
3664 r#"
3665 package test:moonbit-nested;
3666
3667 interface nested {
3668 relay: async func(
3669 value: future<future<stream<u8>>>,
3670 ) -> future<future<stream<u8>>>;
3671 relay-stream: async func(
3672 value: stream<future<u8>>,
3673 ) -> stream<future<u8>>;
3674 }
3675
3676 world service { export nested; }
3677 "#,
3678 "service",
3679 );
3680
3681 let ffi = file(&files, "gen/interface/test/moonbit-nested/nested/ffi.mbt");
3682 for site in [
3683 "RelayStream0StreamSource",
3684 "RelayFuture1FutureSource",
3685 "RelayFuture2FutureSource",
3686 "RelayStream3StreamLower",
3687 "RelayFuture4FutureLower",
3688 "RelayFuture5FutureLower",
3689 ] {
3690 assert!(ffi.contains(site), "missing static endpoint site {site}");
3691 }
3692 assert!(ffi.contains("[async-lower][future-read-2]relay"));
3693 assert!(ffi.contains("[async-lower][future-write-2]relay"));
3694 assert!(ffi.contains("suspend_for_future_write_terminal"));
3695 assert!(ffi.contains("let data_len = if data.length() < 1"));
3696 assert!(ffi.contains("let writer_lock = @async-core.Mutex()"));
3697 assert!(ffi.contains("let close_writer_serialized = async fn()"));
3698 assert!(ffi.contains("writer_lock.acquire()"));
3699 assert!(ffi.contains("defer writer_lock.release()"));
3700 assert!(
3701 ffi.contains("() => close_writer_serialized(),\n resume_on_cancel=true,")
3702 );
3703 assert!(!ffi.contains("defer close_writer()"));
3704 assert!(ffi.contains("read_cleanup : @async-core.CondVar"));
3705 assert!(ffi.contains("let read_count = if count < 64"));
3706 assert!(ffi.contains("self.read_cleanup.broadcast()"));
3707 assert!(ffi.contains("@async-core.cancel_future_read("));
3708 assert!(ffi.contains("@async-core.cancel_stream_read("));
3709 assert!(ffi.contains("@async-core.cancel_stream_write("));
3710 assert!(ffi.contains("[stream-cancel-write-"));
3711 assert!(!ffi.contains("suspend_for_future_cancel_read"));
3712 assert!(!ffi.contains("wait_until"));
3713 assert!(ffi.contains("RelayFuture5FutureLowerCommitted"));
3714 assert!(ffi.contains("RelayFuture4FutureRejectPrepared"));
3715 assert!(ffi.contains("producer.future.reject((value :"));
3716 assert!(ffi.contains("rejected component future unexpectedly transferred a value"));
3717 assert!(ffi.contains("RelayStream3StreamRejectPrepared"));
3718 assert!(!ffi.contains("RelayFuture5FutureRejectPrepared"));
3719
3720 let generated = files
3721 .iter()
3722 .map(|(name, contents)| format!("{name}\n{}", String::from_utf8_lossy(contents)))
3723 .collect::<Vec<_>>()
3724 .join("\n");
3725 for legacy in [
3726 "async_cm.mbt",
3727 "CMFutureVTable",
3728 "CMStreamVTable",
3729 "take_cm_handle",
3730 "new_cm_future",
3731 "new_cm_stream",
3732 ] {
3733 assert!(
3734 !generated.contains(legacy),
3735 "legacy bridge leaked: {legacy}"
3736 );
3737 }
3738
3739 let imports = generate(
3740 r#"
3741 package test:moonbit-nested;
3742
3743 interface nested {
3744 relay: async func(
3745 value: future<future<stream<u8>>>,
3746 ) -> future<future<stream<u8>>>;
3747 relay-stream: async func(
3748 value: stream<future<u8>>,
3749 ) -> stream<future<u8>>;
3750 }
3751
3752 world client { import nested; }
3753 "#,
3754 "client",
3755 );
3756 let import = file(&imports, "interface/test/moonbit-nested/nested/top.mbt");
3757 assert!(import.contains("if before_started"));
3758 assert!(import.contains(".drop_sync()"));
3759 assert!(import.contains("defer mbt_ffi_free(result_ptr)"));
3760 for misleading_name in ["_lower_ptr", "_lower_arg", "_subtask_code", "_result_ptr"] {
3761 assert!(!import.contains(misleading_name), "{import}");
3762 }
3763 assert_eq!(import.matches("FutureLower(value)").count(), 1, "{import}");
3764 assert_eq!(import.matches("StreamLower(value)").count(), 1, "{import}");
3765 assert!(!import.contains("cleanup_list"));
3766 }
3767
3768 #[test]
3769 fn fixed_length_lists_use_checked_fixed_arrays_but_reject_nested_endpoints() {
3770 let files = generate(
3771 r#"
3772 package test:fixed-array;
3773
3774 interface api {
3775 type pair = list<u32, 2>;
3776 type block = list<u32, 20>;
3777 accept: func(value: pair) -> pair;
3778 accept-block: func(value: block);
3779 }
3780
3781 world client { import api; }
3782 "#,
3783 "client",
3784 );
3785 let top = file(&files, "interface/test/fixed-array/api/top.mbt");
3786 assert!(top.contains("FixedArray[UInt]"), "{top}");
3787 assert!(top.contains(".length() != 2"), "{top}");
3788 assert!(top.contains(".length() != 20"), "{top}");
3789
3790 for unsupported in [
3791 "type unsupported = list<future<u32>, 1>;",
3792 "type unsupported = future<list<u32, 1>>;",
3793 ] {
3794 let wit = format!(
3795 r#"
3796 package test:fixed-endpoints;
3797
3798 interface api {{
3799 {unsupported}
3800 accept: func(value: unsupported);
3801 }}
3802
3803 world client {{ import api; }}
3804 "#
3805 );
3806 let error = match try_generate(&wit, "client") {
3807 Ok(_) => std::panic!(
3808 "fixed-length list and async endpoint combinations must be rejected"
3809 ),
3810 Err(error) => error,
3811 };
3812 assert!(
3813 error
3814 .to_string()
3815 .contains("combining future or stream types with fixed-length lists"),
3816 "{error:#}"
3817 );
3818 }
3819 }
3820
3821 #[test]
3822 fn cancelled_returned_async_import_recursively_cleans_result() {
3823 let files = generate(
3824 r#"
3825 package test:cancelled-result;
3826
3827 interface api {
3828 resource leaf;
3829 record payload {
3830 label: string,
3831 leaves: list<leaf>,
3832 ready: future<leaf>,
3833 }
3834 load: async func() -> payload;
3835 }
3836
3837 world client { import api; }
3838 "#,
3839 "client",
3840 );
3841
3842 let import = file(&files, "interface/test/cancelled-result/api/top.mbt");
3843 assert!(
3844 import.contains("suspend_for_subtask")
3845 && import.contains("fn() {")
3846 && import.contains(".drop_sync()")
3847 && import.contains(".drop()"),
3848 "{import}"
3849 );
3850 assert!(
3851 import.contains("mbt_ffi_free(mbt_ffi_load32((result_ptr) + 0))")
3852 && import.contains("mbt_ffi_free(mbt_ffi_load32((result_ptr) + 8))")
3853 && import.contains("defer mbt_ffi_free(result_ptr)"),
3854 "cancelled returned result must free its strings, lists, and result area: {import}"
3855 );
3856 }
3857
3858 #[test]
3859 fn async_runtime_panics_when_export_finishes_unresolved() {
3860 let files = generate(
3861 r#"
3862 package test:failing-export;
3863 world service { export run: async func(); }
3864 "#,
3865 "service",
3866 );
3867
3868 let event_loop = file(&files, "async-core/async_ev.mbt");
3869 assert!(
3870 event_loop.contains("let resolution = task_state.resolution")
3871 && event_loop.contains("guard resolution is Resolved")
3872 && !event_loop.contains("abort("),
3873 "{event_loop}"
3874 );
3875 }
3876
3877 #[test]
3878 fn async_runtime_does_not_reschedule_terminated_task_cancellation() {
3879 let files = generate(
3880 r#"
3881 package test:terminated-task-cancellation;
3882 world service { export run: async func(); }
3883 "#,
3884 "service",
3885 );
3886
3887 let coroutine = file(&files, "async-core/async_coroutine.mbt");
3888 assert!(
3889 coroutine.contains("Done | Fail(_) => return"),
3890 "{coroutine}"
3891 );
3892 }
3893
3894 #[test]
3895 fn async_runtime_traps_callback_without_task_state() {
3896 let files = generate(
3897 r#"
3898 package test:missing-task-state;
3899 world service { export run: async func(); }
3900 "#,
3901 "service",
3902 );
3903
3904 let event_loop = file(&files, "async-core/async_ev.mbt");
3905 assert!(
3906 event_loop.contains(
3907 "guard component_task_state(waitable_set) is Some(task_state) else { panic() }"
3908 ),
3909 "{event_loop}"
3910 );
3911 }
3912
3913 #[test]
3914 fn async_runtime_borrows_waitable_poll_payload() {
3915 let files = generate(
3916 r#"
3917 package test:poll-payload;
3918 world service { export run: async func(); }
3919 "#,
3920 "service",
3921 );
3922
3923 let abi = file(&files, "async-core/async_abi.mbt");
3924 assert!(
3925 abi.contains("#borrow(array)\nextern \"wasm\" fn int_array2ptr"),
3926 "{abi}"
3927 );
3928 assert!(!abi.contains("#owned(array)"), "{abi}");
3929 }
3930
3931 #[test]
3932 fn async_runtime_uses_baseline_subtask_cancel() {
3933 let files = generate(
3934 r#"
3935 package test:subtask-cancel;
3936 world service { export run: async func(); }
3937 "#,
3938 "service",
3939 );
3940
3941 let abi = file(&files, "async-core/async_abi.mbt");
3942 assert!(
3943 abi.contains(r#""$root" "[subtask-cancel]""#)
3944 && !abi.contains("[async-lower][subtask-cancel]"),
3945 "{abi}"
3946 );
3947 }
3948
3949 #[test]
3950 fn unit_future_intrinsics_use_wit_parser_unit_name() {
3951 let files = generate(
3952 r#"
3953 package a:b;
3954
3955 world runner {
3956 import exchange: func(value: future) -> future;
3957 }
3958 "#,
3959 "runner",
3960 );
3961
3962 let ffi = file(&files, "world/runner/ffi_import.mbt");
3963 assert!(ffi.contains(r#""$root" "[future-new-unit]exchange""#));
3964 assert!(ffi.contains(r#""$root" "[async-lower][future-read-unit]exchange""#));
3965 assert!(ffi.contains(r#""$root" "[future-cancel-read-unit]exchange""#));
3966 }
3967}