Skip to main content

wit_bindgen_moonbit/
lib.rs

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
32// Assumptions:
33// - Data: u8 -> Byte, s8 | s16 | s32 -> Int, u16 | u32 -> UInt, s64 -> Int64, u64 -> UInt64, f32 | f64 -> Double, address -> Int
34// - Encoding: UTF16
35// - Lift/Lower list<T>: T == Int/UInt/Int64/UInt64/Float/Double -> FixedArray[T], T == Byte -> Bytes, T == Char -> String
36// Organization:
37// - one package per interface (export and import are treated as different interfaces)
38// - ffi utils are under `./ffi`, and the project entrance (package as link target) is under `./gen`
39
40// We use Legacy mangling for MoonBit (no specific reason, just because we haven't switched yet)
41// We use AsyncCallback ABI for async functions
42
43// TODO: Export will share the type signatures with the import by using a newtype alias
44const 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    /// Whether or not to generate stub files ; useful for update after WIT change
53    #[cfg_attr(feature = "clap", arg(long, default_value_t = false))]
54    pub ignore_stub: bool,
55
56    /// Whether or not to generate moon.mod.json ; useful if the project is part of a larger project
57    #[cfg_attr(feature = "clap", arg(long, default_value_t = false))]
58    pub ignore_module_file: bool,
59
60    /// The package/dir to generate the program entrance
61    #[cfg_attr(feature = "clap", arg(long, default_value = "gen"))]
62    pub gen_dir: String,
63
64    /// The project name ; or the package path prefix if the project is part of a larger project
65    #[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    /// Whether or not to derive Debug for all types
76    #[cfg_attr(feature = "clap", arg(long, default_value_t = false))]
77    pub derive_debug: bool,
78
79    /// Whether or not to derive Show for all types
80    #[cfg_attr(feature = "clap", arg(long, default_value_t = false))]
81    pub derive_show: bool,
82
83    /// Whether or not to derive Eq for all types
84    #[cfg_attr(feature = "clap", arg(long, default_value_t = false))]
85    pub derive_eq: bool,
86
87    /// Whether or not to declare as Error type for types ".*error"
88    #[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    // Collision may happen when a package is imported with multiple versions.
124    // see multiverison
125    interface_ns: Ns,
126    // dependencies between packages
127    pkg_resolver: PkgResolver,
128    // Wasm export name -> (exported function name, func)
129    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        // Disable warning for invalid inline wasm
160        moon_pkg.push_str("{\n\"warn-list\": \"-44\"");
161        // Dependencies
162        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        // Link target
188        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
219/// World generator implementation for MoonBit.
220///
221/// This implementation connects the generic `wit-bindgen` world generation
222/// workflow with MoonBit-specific codegen details. It consumes the parsed
223/// WIT `Resolve` structure and emits MoonBit source (`*.mbt`) and package
224/// metadata files into the provided `Files` collection.
225///
226/// Responsibilities and behavior:
227/// - `preprocess`: Initialize generator-wide state (package resolver,
228///   project name, and size/align information) for the current world.
229/// - `import_interface` / `export_interface`: Generate per-interface
230///   sources, FFI glue, README documentation and `moon.pkg.json` metadata.
231/// - `import_funcs` / `export_funcs` / `import_types`: Collect and accumulate
232///   world-level functions and types (the `$root` module) into fragments
233///   that are later written out by `finish_imports` or `finish`.
234/// - `finish_imports` / `finish`: Emit aggregated import artifacts and the
235///   final project entrypoints such as the combined FFI module and package
236///   descriptor files.
237///
238/// Implementation notes:
239/// - Namespacing and collision avoidance are handled using `PkgResolver` and
240///   an internal `Ns` to make import/export package names stable even when
241///   multiple package versions are present.
242/// - Inline FFI helpers and builtins are collected and written once into the
243///   final export FFI module. Async helpers are emitted when required.
244impl 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        // Write files
291        {
292            let directory = name.replace('.', "/");
293
294            // README
295            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            // Source
302            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            // FFI
308            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            // moon.pkg.json
317            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        // README
370        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        // Source
376        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        // FFI
381        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        // moon.pkg.json
394        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        // Write files
430        {
431            let directory = name.replace('.', "/");
432
433            // README
434            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            // Source
443            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                // moon.pkg.json
450                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            // FFI
460            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        // Write files
494        {
495            let directory = name.replace('.', "/");
496            // Source
497            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                // moon.pkg.json
504                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            // FFI
514            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        // If async is used, export async utils
528        self.async_support.emit_runtime_files(files, VERSION);
529
530        // Export project files
531        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        // Export project entry point
542        let mut body = Source::default();
543        wit_bindgen_core::generated_preamble(&mut body, VERSION);
544        // CABI Realloc
545        for builtin in [ffi::CABI_REALLOC, ffi::MALLOC, ffi::FREE] {
546            uwriteln!(&mut body, "{}", builtin);
547        }
548        // Import all exported interfaces
549        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    // Collect of FFI imports used in this interface
577    ffi_imports: HashSet<&'static str>,
578
579    world_gen: &'a mut MoonBit,
580    resolve: &'a Resolve,
581    // The current interface getting generated
582    name: &'a str,
583    direction: Direction,
584    interface: Option<&'a WorldKey>,
585
586    // Options for deriving traits
587    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            &params,
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!(), // https://github.com/WebAssembly/component-model/issues/370
1119        };
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        // Not needed. They will become `(T1, T2, ...)` in Moonbit
1205    }
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        // Not needed. They will become `Option[T]` in Moonbit
1266    }
1267
1268    fn type_result(&mut self, _id: TypeId, _name: &str, _result: &Result_, _docs: &Docs) {
1269        // Not needed. They will become `Result[Ok, Err]` in Moonbit
1270    }
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        // Type definition
1278        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        // Case to integer
1312        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        // Integer to case
1332        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        // Not needed. They will become `Array[T]` or `FixedArray[T]` in Moonbit
1357    }
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        // Not needed. They will become `FixedArray[T]` in Moonbit
1368    }
1369
1370    fn type_map(&mut self, _id: TypeId, _name: &str, _key: &Type, _value: &Type, _docs: &Docs) {
1371        // Not needed. Maps become `Map[K, V]` inline in MoonBit
1372    }
1373
1374    fn type_future(&mut self, _id: TypeId, _name: &str, _ty: &Option<Type>, _docs: &Docs) {
1375        // Rendered inline by `PkgResolver::type_name`.
1376    }
1377
1378    fn type_stream(&mut self, _id: TypeId, _name: &str, _ty: &Option<Type>, _docs: &Docs) {
1379        // Rendered inline by `PkgResolver::type_name`.
1380    }
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        // Hacky way to get the type name without type parameter
1559        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    // Utilities
1619    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                // Empty tuple is Unit
1839                // (T) is T
1840                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 align = self.r#gen.r#gen.sizes.align(element);
2228                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                // TODO: handle this to support async functions
2286                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                // Bind return operands to locals BEFORE cleanup to avoid
2327                // use-after-free when operands contain inline loads from
2328                // return_area or other freed memory.
2329                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            // TODO: see what we can do with align
2510            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                // let align = self.r#gen.r#gen.sizes.align(element);
2576
2577                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 the interface is an import, we need to track this for cleanup
2856        // Otherwise, the caller is responsible for cleaning up in post_return
2857        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        // This exact-output fingerprint runs with both async adapters. Ignore only
3178        // the release-version preamble, which is unrelated to generated ABI.
3179        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}