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